Skip to main content

page-retry SDK

Automatically retry failed Playwright navigations using real mobile proxies from Aluvia.

Overview​

When you run Playwright for automation, scraping, or testing, page navigations can fail because of unstable networks, blocked IPs, or temporary connection issues. By default, Playwright throws an error like ETIMEDOUT, net::ERR_CONNECTION_RESET, or TimeoutError and stops running unless you handle it manually.

This SDK solves that problem automatically. It catches retryable navigation errors, fetches a proxy (from your Aluvia account or your own proxy provider), relaunches the browser, and retries the same navigation.

Unlike a full-time proxy setup, this SDK only uses a proxy when a navigation fails. Your sessions start on a direct connection and fall back to a proxy only when needed, keeping your Playwright scripts more stable without extra configuration.

Installation​

You can also find this package on npm: https://www.npmjs.com/package/page-retry

Install the package using your preferred Node.js package manager:

# npm
npm install page-retry

# yarn
yarn add page-retry

# pnpm
pnpm add page-retry

You also need Playwright installed in your project if it isn’t already:

npm init playwright@latest

Quick Start Example​

import { chromium } from "playwright";
import { retryWithProxy } from "page-retry";

const browser = await chromium.launch();
const context = await browser.newContext();
const page = await context.newPage();

const { response, page: retriedPage } = await retryWithProxy(page).goto(
"https://blocked-website.com"
);

console.log("Page title:", await retriedPage.title());
await browser.close();

API Key Setup​

The SDK needs a valid Aluvia API key to fetch proxies when a retry is triggered. You can find your API key from your Aluvia account's Connect to Proxies page.

Set your API key in a .env file:

ALUVIA_API_KEY=your_aluvia_api_key

If you don't provide a custom proxy provider, this key is required. When a navigation fails, the SDK uses this key to request a proxy from Aluvia and relaunch the browser automatically.

Configuration​

You can control how retryWithProxy behaves using environment variables or options passed in code. The environment variables set defaults globally, while the TypeScript options let you override them per call.

Environment Variables​

VariableDescriptionDefault
ALUVIA_API_KEYRequired unless you provide a custom proxyProvider. Used to fetch proxies from Aluvia.none
ALUVIA_MAX_RETRIESNumber of retry attempts after the first failed navigation.1
ALUVIA_BACKOFF_MSBase delay (ms) between retries, grows exponentially with jitter.300
ALUVIA_RETRY_ONComma-separated list of retryable error substrings.ECONNRESET,ETIMEDOUT,net::ERR,Timeout

Example .env​

ALUVIA_API_KEY=your_aluvia_api_key
ALUVIA_MAX_RETRIES=2
ALUVIA_BACKOFF_MS=500
ALUVIA_RETRY_ON=ECONNRESET,ETIMEDOUT,net::ERR,Timeout

TypeScript Options​

You can also configure behavior programmatically by passing options to retryWithProxy().

import { retryWithProxy } from "page-retry";

const { response, page } = await retryWithProxy(page, {
maxRetries: 3,
backoffMs: 500,
retryOn: ["ECONNRESET", /403/],
closeOldBrowser: false,
onRetry: (attempt, maxRetries, lastError) => {
console.log(
`Retry attempt ${attempt} of ${maxRetries} due to error:`,
lastError
);
},
onProxyLoaded: (proxy) => {
console.log(`Proxy loaded: ${proxy.server}`);
},
});

Available Options​

OptionTypeDefaultDescription
maxRetriesnumberprocess.env.ALUVIA_MAX_RETRIES or 1Number of retry attempts after the first failure.
backoffMsnumberprocess.env.ALUVIA_BACKOFF_MS or 300Base delay (in ms) between retries, grows exponentially with jitter.
retryOn(string | RegExp)[]process.env.ALUVIA_RETRY_ONError patterns considered retryable.
closeOldBrowserbooleantrueWhether to close the old browser when relaunching.
proxyProviderProxyProviderUses Aluvia SDKCustom proxy provider that returns proxy credentials.
onRetry(attempt: number, maxRetries: number, lastError: unknown) => void | Promise<void>undefinedCallback invoked before each retry attempt.
onProxyLoaded(proxy: ProxySettings) => void | Promise<void>undefinedCallback fired after a proxy has been successfully fetched (either from the Aluvia API or a custom provider).

Custom Proxy Provider Example​

const myProxyProvider = {
async get() {
return {
server: "http://myproxy.example.com:8000",
username: "user123",
password: "secret",
};
},
};

const { response, page } = await retryWithProxy(page, {
proxyProvider: myProxyProvider,
maxRetries: 3,
});

You can integrate this with any proxy API or local pool, as long as it returns a server, username, and password.

How It Works​

When you call retryWithProxy(page).goto(url), the SDK wraps around Playwright's native page.goto() and adds retry logic with optional proxy handling. It doesn't modify Playwright itself - it just adds smart recovery behavior when a navigation fails.

Here's what happens behind the scenes:

  1. Normal navigation first The SDK first calls page.goto(url) normally, without using any proxy. If it succeeds, you get the same result as a regular Playwright call.
  2. Error detection If the navigation throws an error, the SDK checks if the error message, name, or code matches any of the retry patterns (defined in retryOn or ALUVIA_RETRY_ON). By default, it retries on common network errors such as:
    • ETIMEDOUT
    • ECONNRESET
    • net::ERR_CONNECTION_RESET
    • TimeoutError
  3. Fetch a proxy If the error is retryable, the SDK fetches a proxy.
    • By default, it uses the Aluvia API, reading your API key from ALUVIA_API_KEY.
    • If you provided a custom proxyProvider, it uses that instead.
  4. Relaunch the browser with proxy The SDK then closes (or optionally keeps) the current browser, launches a new instance using the proxy, restores context options (like user agent, viewport, storage state), and opens a new page.
  5. Retry the navigation It retries page.goto() again with the same URL, applying an exponential backoff delay between attempts. The delay is calculated as backoffMs * 2^attempt + random(0–100).
  6. Repeat or fail gracefully This process continues up to the maxRetries limit. If all retries fail, the SDK throws the last encountered error, just like Playwright would, with no silent failures.

Requirements​

  • Node.js 18 or later
  • Playwright (installed separately)
  • Aluvia account and API key (if not using a custom proxy provider)

FAQ​

Does it always use a proxy?

No. It tries direct navigation first. Only on a retryable failure does it fetch a proxy and relaunch.

Can I plug in Bright Data, Oxylabs, or my own pool?

Yes. Implement proxyProvider.get() and return { server, username, password }. You can integrate any proxy API, database, or in-house rotation system that follows this shape.

Can I keep the old browser running?

Yes. Pass { closeOldBrowser: false }. You're responsible for closing or reusing the old browser instance manually.

Will my event handlers survive a relaunch?

No. When the browser relaunches, a completely new Page and Context are created. You'll need to reattach event listeners or bindings after a retry.

Will it keep my cookies or sessions after a relaunch?

Usually yes. The SDK reuses the original context's storage state, viewport, and user agent. However, if your session depends on dynamic runtime JS variables, those won’t carry over.

Does this modify Playwright itself?

No. The SDK doesn't patch or override Playwright's core modules. It just wraps your existing page.goto() calls and adds retry + proxy logic externally.

Does it retry failed requests made inside the page (XHR/fetch)?

No. It only retries top-level navigations triggered by page.goto(). If you need granular retry control for API calls inside the page, handle those separately with Playwright's request interception APIs.

Does it work with Firefox or WebKit?

Yes. It supports all official Playwright browser types (chromium, firefox, and webkit). The SDK automatically detects which browser you’re using and relaunches it accordingly.