Puppeteer & Playwright
Run headless Chromium, Firefox and WebKit through authenticated mobile, residential and static ISP proxies with Puppeteer and Playwright, per browser or per context.
On this page
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#
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-serverper browser process. For several proxies, launch several browsers, or use one incognito context per proxy (Puppeteer ≥ 22):browser.createBrowserContext({ proxyServer: "http://host:port" }), thenauthenticate()on each page of that context. - SOCKS5 with authentication is not supported by Chromium. Use the HTTP port, or IP whitelist with
--proxy-server=socks5://host:port.
Playwright#
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#
Playwright can give each browser context its own proxy — ideal for running several identities in one browser process:
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#
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#
With a mobile proxy, rotate between tasks rather than mid-page: open connections are dropped when the modem reconnects.
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#
- 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: 5–10 concurrent pages per mobile proxy is a sensible ceiling.
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