Playwright's built-in proxy support
Playwright, Microsoft's browser automation library for Chromium, Firefox and WebKit, has first-class proxy support. Unlike Puppeteer, where authentication needs page.authenticate(), Playwright accepts a username and password directly in its proxy configuration, and it supports separate proxies per browser context out of the box.
Launch-level proxy (Node.js)
import { chromium } from 'playwright';
const browser = await chromium.launch({
proxy: {
server: 'http://gate.example.com:7777',
username: 'customer-USER-country-us',
password: 'PASS',
},
});
const page = await browser.newPage();
await page.goto('https://httpbin.org/ip');
console.log(await page.textContent('body'));
await browser.close();Launch-level proxy (Python)
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(proxy={
"server": "http://gate.example.com:7777",
"username": "customer-USER-country-us",
"password": "PASS",
})
page = browser.new_page()
page.goto("https://httpbin.org/ip")
print(page.text_content("body"))
browser.close()Per-context proxies
Each browser context can have its own proxy, which is ideal for running several identities or locations in one browser:
const browser = await chromium.launch();
const us = await browser.newContext({
proxy: { server: 'http://gate.example.com:7777', username: 'customer-USER-country-us', password: 'PASS' },
locale: 'en-US',
timezoneId: 'America/New_York',
});
const de = await browser.newContext({
proxy: { server: 'http://gate.example.com:7777', username: 'customer-USER-country-de', password: 'PASS' },
locale: 'de-DE',
timezoneId: 'Europe/Berlin',
});Note the locale and timezoneId options: Playwright makes it easy to match the browser to the proxy's location, which reduces fingerprint mismatches. See browser fingerprinting and proxies.
On older Playwright versions with Chromium on Windows, per-context proxies required launching the browser with a placeholder global proxy (for example proxy: { server: 'http://per-context' }). Current versions don't need this, but keep it in mind if you're on an old release.
SOCKS5
const browser = await chromium.launch({ proxy: { server: 'socks5://203.0.113.10:1080' } });As with Puppeteer, SOCKS5 authentication isn't supported by Chromium in Playwright; use IP whitelisting or a local forwarder. See SOCKS5 proxies.
Bypassing the proxy for some hosts
proxy: { server: 'http://gate.example.com:7777', bypass: 'localhost,.internal.example.com' }Rotating proxies in Playwright
Create a new context per session with a new session ID (for rotating residential) or the next proxy from your list (for static proxies):
const newSession = () => `customer-USER-country-us-session-${Math.random().toString(36).slice(2, 10)}`;
async function scrape(browser, url) {
const ctx = await browser.newContext({
proxy: { server: 'http://gate.example.com:7777', username: newSession(), password: 'PASS' },
});
try {
const page = await ctx.newPage();
const res = await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 45000 });
return { status: res?.status(), html: await page.content() };
} finally {
await ctx.close();
}
}See proxy rotation strategies.
Saving bandwidth
Block heavy resources with routing:
await ctx.route('**/*', (route) => {
const t = route.request().resourceType();
return ['image', 'media', 'font'].includes(t) ? route.abort() : route.continue();
});On per-GB residential proxies, this can cut costs dramatically.
Proxy tip: If you need a pool to run this against, Geonode works with every tool in this guide: rotate per request for stateless pages, or hold a sticky session for logins and pagination. See using Geonode with Playwright for working code.
Troubleshooting
| Problem | Fix |
|---|---|
407 Proxy Authentication Required | Check username/password; ensure they're in the proxy object, not the server URL |
ERR_PROXY_CONNECTION_FAILED | Wrong host/port or scheme |
| Per-context proxy ignored | Update Playwright; on old versions use the per-context placeholder |
| SOCKS5 fails with credentials | Whitelist IP or use a forwarder |
See common proxy errors.
Playwright vs Puppeteer for proxies
| Playwright | Puppeteer | |
|---|---|---|
| Auth in config | Yes | No (page.authenticate()) |
| Per-context proxy | Yes, mature | Yes, in recent versions |
| Locale/timezone per context | Built in | Via page emulation |
| Browsers | Chromium, Firefox, WebKit | Chrome/Chromium (Firefox experimental support) |
Playwright proxy patterns by use case
| Use case | Pattern |
|---|---|
| One proxy for everything | Launch-level proxy |
| Many locations in parallel | One context per location with its own proxy, locale and timezone |
| Rotating residential | New context per session with a new session ID |
| Logged-in accounts | One persistent context per account (launchPersistentContext) with a static ISP proxy |
| Mixed proxies and direct traffic | bypass list for internal hosts |
Persistent contexts for accounts
For workflows that need cookies to survive between runs, use a persistent context with a dedicated proxy:
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
ctx = p.chromium.launch_persistent_context(
user_data_dir="profiles/account-01",
proxy={"server": "http://203.0.113.21:8000", "username": "u", "password": "p"},
locale="en-GB",
timezone_id="Europe/London",
)
page = ctx.new_page()
page.goto("https://example.com/account")
ctx.close()Each account keeps its own profile directory and static IP. See static residential proxies.
Checking the exit location per context
async function exitInfo(context) {
const page = await context.newPage();
await page.goto('https://ipinfo.io/json');
const info = JSON.parse(await page.textContent('body'));
await page.close();
return info; // { ip, city, region, country, org, timezone }
}You can use the returned timezone to set up a new context with matching settings, which reduces fingerprint mismatches. See browser fingerprinting and proxies.
Firefox and WebKit
Playwright's proxy options work across Chromium, Firefox and WebKit, which lets you vary browser engines as part of your fingerprint strategy. Behaviour details (such as SOCKS authentication) can differ by engine, so test each.
For more on browsers and proxies, see headless browser proxies and our Puppeteer proxies guide.
Playwright and proxies in test suites
Playwright Test can set a proxy in the config so every test runs through it, useful for testing your own site from another region:
// playwright.config.js
import { defineConfig } from '@playwright/test';
export default defineConfig({
use: {
proxy: { server: 'http://gate.example.com:7777', username: process.env.PROXY_USER, password: process.env.PROXY_PASS },
locale: 'de-DE',
timezoneId: 'Europe/Berlin',
},
});Define separate projects for different regions (each with its own proxy, locale and timezone) to run the same tests across markets. For QA of your own sites, datacenter proxies in each region are usually sufficient and cheap.
Debugging proxy problems with the trace viewer
When a proxied Playwright run fails, the trace viewer often shows why faster than log statements. Record a trace around the failing flow:
const context = await browser.newContext({
proxy: { server: 'http://gate.example.com:7777', username: 'USER', password: 'PASS' },
});
await context.tracing.start({ screenshots: true, snapshots: true });
const page = await context.newPage();
await page.goto('https://example.com');
await context.tracing.stop({ path: 'trace.zip' });Open it with npx playwright show-trace trace.zip. The Network tab lists every request with its status and timing, so you can see:
- Which requests failed and with what status. A wall of 407s points to credentials; 403s on the main document point to the target.
- Whether a challenge page was served instead of content, from the snapshots.
- Slow requests that suggest a slow exit, or resources you should be blocking to save bandwidth.
Keep traces off in normal runs, since they're large, and turn them on for failing jobs or a small sample. Treat trace files as sensitive: they can contain cookies and page content.
The official guide to Playwright's proxy settings covers the options used above. For errors and their meanings, see common proxy errors, and for comparing frameworks, headless browser proxies.
Key takeaways
- Playwright accepts proxy server, username and password directly at launch or per context.
- Per-context proxies, with matching
localeandtimezoneId, make multi-location work straightforward. - SOCKS5 authentication isn't supported in Chromium; use IP whitelisting or a forwarder.
- Rotate by creating a new context per session; block heavy resources with routing to save bandwidth.
- Persistent contexts with static ISP proxies suit logged-in accounts. See headless browser proxies.
FAQ
Can Playwright use a different proxy for each page?
Per context, yes. Pages in the same context share the context's proxy.
Does Playwright work with residential proxies?
Yes, with any HTTP(S) proxy. Use sticky sessions per context.
What are the best proxies for Playwright?
Same as for Puppeteer: rotating residential with sticky sessions for protected sites, ISP for logged-in sessions, datacenter for lenient sites. See best proxies for web scraping.
Editorial note: Proxy providers change their pools, features and prices often, so treat any figures here as typical ranges at the time of writing and confirm current terms with the provider before you buy.