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β
| Variable | Description | Default |
|---|---|---|
ALUVIA_API_KEY | Required unless you provide a custom proxyProvider. Used to fetch proxies from Aluvia. | none |
ALUVIA_MAX_RETRIES | Number of retry attempts after the first failed navigation. | 1 |
ALUVIA_BACKOFF_MS | Base delay (ms) between retries, grows exponentially with jitter. | 300 |
ALUVIA_RETRY_ON | Comma-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β
| Option | Type | Default | Description |
|---|---|---|---|
maxRetries | number | process.env.ALUVIA_MAX_RETRIES or 1 | Number of retry attempts after the first failure. |
backoffMs | number | process.env.ALUVIA_BACKOFF_MS or 300 | Base delay (in ms) between retries, grows exponentially with jitter. |
retryOn | (string | RegExp)[] | process.env.ALUVIA_RETRY_ON | Error patterns considered retryable. |
closeOldBrowser | boolean | true | Whether to close the old browser when relaunching. |
proxyProvider | ProxyProvider | Uses Aluvia SDK | Custom proxy provider that returns proxy credentials. |
onRetry | (attempt: number, maxRetries: number, lastError: unknown) => void | Promise<void> | undefined | Callback invoked before each retry attempt. |
onProxyLoaded | (proxy: ProxySettings) => void | Promise<void> | undefined | Callback 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:
- 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. - 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
retryOnorALUVIA_RETRY_ON). By default, it retries on common network errors such as:ETIMEDOUTECONNRESETnet::ERR_CONNECTION_RESETTimeoutError
- 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.
- By default, it uses the Aluvia API, reading your API key from
- 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.
- Retry the navigation
It retries
page.goto()again with the same URL, applying an exponential backoff delay between attempts. The delay is calculated asbackoffMs * 2^attempt + random(0β100). - Repeat or fail gracefully
This process continues up to the
maxRetrieslimit. 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.