Using proxies with Playwright: a complete guide
Per-context proxies, credential handling, bandwidth control and the failure modes that waste an afternoon.
Jonas Okonkwo
Developer relations
30 Jun 2026 · 9 min read
Playwright has the best proxy support of the major automation frameworks: credentials are first-class, and proxies can be set per browser context rather than per browser process. That second point is the one that matters for anything at scale.
The basic setup
import { chromium } from "playwright";
const browser = await chromium.launch({
proxy: {
server: "http://res.wproxy.io:8000",
username: "wp-acc4821-country-us-session-a1",
password: "pass",
},
});Per-context is what you want
Launching a browser process per exit costs roughly 80 MB of RAM and a second of startup each. Contexts cost almost nothing and are fully isolated — separate cookies, storage and cache — so one browser can hold dozens of independent identities.
const browser = await chromium.launch();
async function contextFor(country) {
const session = Math.random().toString(36).slice(2, 10);
return browser.newContext({
proxy: {
server: "http://res.wproxy.io:8000",
username: `wp-acc4821-country-${country}-session-${session}`,
password: "pass",
},
locale: country === "de" ? "de-DE" : "en-US",
viewport: { width: 1440, height: 900 },
});
}
const contexts = await Promise.all(["us", "gb", "de", "jp"].map(contextFor));Cut the bandwidth bill
A browser fetches everything a browser fetches: hero images, web fonts, analytics beacons, video preloads. On a metered residential plan you pay for all of it, and for most scraping none of it is data you want.
const BLOCKED = new Set(["image", "media", "font", "stylesheet"]);
await context.route("**/*", (route) =>
BLOCKED.has(route.request().resourceType())
? route.abort()
: route.continue(),
);Typical saving: 60–80%
On an image-heavy retail site, blocking those four resource types took one customer's monthly usage from 1.4 TB to 310 GB with no change in the data extracted. Do check the site still renders — a few sites lazy-load content through CSS.
Failure modes
| Symptom | Cause | Fix |
|---|---|---|
| ERR_TUNNEL_CONNECTION_FAILED | Bad credentials or wrong port | Test the same credentials with curl first |
| Blank page, no error | Resource blocking too aggressive | Allow stylesheets and re-test |
| Works headed, fails headless | Environment fingerprinting | Use a stealth plugin or run headed in Xvfb |
| Random mid-session logouts | Sticky exit rotated | Watch x-wproxy-session-rotated and re-authenticate |
| Sudden bandwidth spike | A page started autoplaying video | Block the media resource type |
Verify the exit from inside the page
const page = await context.newPage();
await page.goto("https://ipinfo.io/json");
console.log(JSON.parse(await page.textContent("pre")));
// { ip: "72.14.201.88", city: "Chicago", country: "US", ... }