# Puppeteer & Playwright
Source: https://proxuno.com/docs/integration-puppeteer-playwright
Updated: 2026-09-02

Integrations

Run headless Chromium, Firefox and WebKit through authenticated mobile, residential and static ISP proxies with Puppeteer and Playwright, per browser or per context.

Updated 2 Sept 2026 1 min read API v1

### On this page

- [Puppeteer](#puppeteer)
- [Playwright](#playwright)
- [One proxy per context](#one-proxy-per-context)
- [Python](#python)
- [Rotating between page loads](#rotating-between-page-loads)
- [Performance tips](#performance-tips)
- [Troubleshooting](#troubleshooting)

Chromium does not accept credentials in the `--proxy-server` flag. Both tools solve this differently: Puppeteer answers the proxy's authentication challenge with `page.authenticate()`, Playwright takes `username` and `password` in its proxy option.

## Puppeteer[#](#puppeteer)

Node.jsCopy

```javascript
import puppeteer from "puppeteer";

const browser = await puppeteer.launch({
args: ["--proxy-server=http://fr.mobile.gw.proxuno.com:10421"],
});
const page = await browser.newPage();
await page.authenticate({ username: "m_k3v9q2xa", password: "Hq7Lr2Nw8Zt4Bc6D" });

await page.goto("https://api.ipify.org?format=json", { waitUntil: "domcontentloaded", timeout: 60_000 });
console.log(await page.evaluate(() => document.body.innerText));
await browser.close();
```

- Call `authenticate()` on **every** new page before navigating.
- One `--proxy-server` per browser process. For several proxies, launch several browsers, or use one incognito context per proxy (Puppeteer ≥ 22): `browser.createBrowserContext({ proxyServer: "http://host:port" })`, then `authenticate()` on each page of that context.
- SOCKS5 with authentication is **not** supported by Chromium. Use the HTTP port, or [IP whitelist](/docs/ip-whitelist) with `--proxy-server=socks5://host:port`.

## Playwright[#](#playwright)

Node.jsCopy

```javascript
import { chromium } from "playwright";

const browser = await chromium.launch({
proxy: {
server: "http://fr.mobile.gw.proxuno.com:10421",
username: "m_k3v9q2xa",
password: "Hq7Lr2Nw8Zt4Bc6D",
},
});
const page = await browser.newPage();
await page.goto("https://api.ipify.org?format=json");
console.log(await page.textContent("body"));
await browser.close();
```

### One proxy per context[#](#one-proxy-per-context)

Playwright can give each browser context its own proxy — ideal for running several identities in one browser process:

Node.jsCopy

```javascript
const browser = await chromium.launch();

const contexts = await Promise.all(
["fr", "de", "it"].map((cc) =>
browser.newContext({
proxy: {
server: "http://resi.gw.proxuno.com:7000",
username: `px4k2m9q7a-country-${cc}-session-${Math.random().toString(36).slice(2, 10)}-ttl-30`,
password: process.env.RESI_PASSWORD,
},
locale: { fr: "fr-FR", de: "de-DE", it: "it-IT" }[cc],
timezoneId: { fr: "Europe/Paris", de: "Europe/Berlin", it: "Europe/Rome" }[cc],
})
)
);
```

The sticky session keeps each context on one residential IP for 30 minutes. Match `locale` and `timezoneId` to the exit country.

### Python[#](#python)

PythonCopy

```python
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
browser = p.firefox.launch(proxy={
"server": "http://203.0.113.47:8000",
"username": "s_r7d2k9mw",
"password": "Lp4Xv8Qc2Nh6Jt3B",
})
page = browser.new_page()
page.goto("https://api.ipify.org")
print(page.inner_text("body"))
browser.close()
```

## Rotating between page loads[#](#rotating-between-page-loads)

With a mobile proxy, rotate between tasks rather than mid-page: open connections are dropped when the modem reconnects.

Node.jsCopy

```javascript
async function rotate(link) {
const res = await fetch(link);                 // the proxy's rotation link
if (res.status === 429) {
await new Promise((r) => setTimeout(r, Number(res.headers.get("retry-after")) * 1000));
return rotate(link);
}
return (await res.json()).ip;
}

for (const url of urls) {
const context = await browser.newContext();    // fresh cookies per task
const page = await context.newPage();
await page.goto(url, { timeout: 60_000 });
// … work …
await context.close();
await rotate(process.env.ROTATION_LINK);
}
```

## Performance tips[#](#performance-tips)

- Block heavy resources you do not need — images, media, fonts — with `page.route()`; on mobile proxies this often halves page-load time.
- Use `waitUntil: "domcontentloaded"` instead of `"networkidle"` on sites with long-polling or analytics beacons.
- Raise navigation timeouts to 60 s on mobile proxies at peak hours.
- Keep browser and context counts in line with [Limits & best practices](/docs/limits-best-practices): 5–10 concurrent pages per mobile proxy is a sensible ceiling.

## Troubleshooting[#](#troubleshooting)

| Error | Cause |
| --- | --- |
| `net::ERR_INVALID_AUTH_CREDENTIALS` | Wrong credentials, expired proxy, or residential flags with a typo. |
| `net::ERR_TUNNEL_CONNECTION_FAILED` | The proxy could not reach the target (`502`), or the modem was rotating. |
| `net::ERR_NO_SUPPORTED_PROXIES` | A `socks5://` server with credentials in Chromium — use HTTP. |
| Puppeteer shows an auth dialog then hangs | `page.authenticate()` was not called before `goto()`. |

Something unclear, outdated or wrong on this page? Tell us — documentation issues are fixed in the next release at the latest.

[Report an issue with this page](/contact?topic=support&subject=Documentation%20issue%3A%20Puppeteer%20%26%20Playwright)
