{"id":844,"date":"2026-07-19T17:19:22","date_gmt":"2026-07-19T14:19:22","guid":{"rendered":"https:\/\/mexela.com\/blog\/playwright-proxy\/"},"modified":"2026-08-04T14:45:03","modified_gmt":"2026-08-04T11:45:03","slug":"playwright-proxy","status":"publish","type":"post","link":"https:\/\/mexela.com\/blog\/playwright-proxy\/","title":{"rendered":"Playwright Proxy: Auth, Contexts, and Testing"},"content":{"rendered":"<p class=\"mexela-answer\">To use a proxy with Playwright, set <code>use.proxy<\/code> in <code>playwright.config.ts<\/code> for an entire test project, pass <code>proxy<\/code> to <code>chromium.launch()<\/code> for a library-managed browser, or pass it to <code>browser.newContext()<\/code> when a test needs an isolated route. Use separate <code>server<\/code>, <code>username<\/code>, and <code>password<\/code> fields, load secrets from protected environment variables, and verify the observed route only against an endpoint you own or are authorized to test.<\/p>\n<p class=\"mexela-scope\"><strong>Scope:<\/strong> this guide covers a browser launch proxy, context-level proxy, HTTP proxy authentication, bypass rules, route verification, and browser troubleshooting in Playwright. Compare non-browser clients in the <a href=\"\/blog\/use-proxies-curl-python-nodejs\/\">developer proxy overview<\/a>, and find other browser integrations in the <a href=\"\/blog\/proxy-setup-developer-guides\/\">Developer Guides hub<\/a>.<\/p>\n<p>A Playwright proxy changes the browser&#8217;s network path; it does not grant access to a destination or automatically change geolocation permissions, JavaScript coordinates, locale, timezone, cookies, or account state. The setup should therefore be treated as one part of a reproducible QA profile. The examples below use TypeScript, placeholder <code>.invalid<\/code> values, and an <code>APPROVED_TEST_URL<\/code> environment variable so no real proxy endpoint, credential, or third-party diagnostic service is embedded in source code.<\/p>\n<h2>Set a global Playwright proxy in the test config<\/h2>\n<p>For a test project in which every page should use the same route, put the proxy under <code>use<\/code>. Playwright applies those options to the contexts created by the test runner. Keeping the server and credentials as separate fields avoids building or logging a complete credential-bearing URL. A small helper also fails before browser startup when required configuration is missing.<\/p>\n<pre><code class=\"language-typescript\">import { defineConfig } from '@playwright\/test';\n\nfunction requiredEnv(name: string): string {\n  const value = process.env[name];\n  if (!value) throw new Error(`Missing required environment variable: ${name}`);\n  return value;\n}\n\nexport default defineConfig({\n  use: {\n    proxy: {\n      server: requiredEnv('PROXY_SERVER'),\n      username: 'USER',\n      password: 'PASSWORD',\n      bypass: process.env.PROXY_BYPASS,\n    },\n  },\n});<\/code><\/pre>\n<p>Use a value such as <code>http:\/\/HOST:3128<\/code> for <code>PROXY_SERVER<\/code> in documentation and substitute an approved endpoint through the deployment secret mechanism. Do not print the environment object or Playwright configuration during debugging because it can contain proxy and application secrets. Environment variables are an injection mechanism, not automatically a vault; in CI, use masked secrets and restrict which jobs and users can read them.<\/p>\n<p>The <a href=\"https:\/\/playwright.dev\/docs\/network\">official Playwright network guide<\/a> documents global and per-context proxy settings, HTTP(S) and SOCKSv5 support, optional HTTP proxy credentials, and bypass hosts. The separate <code>username<\/code> and <code>password<\/code> fields are for HTTP proxy authentication; they are not the same as a destination website&#8217;s HTTP authentication or application login. See the <a href=\"\/blog\/proxy-authentication-username-password-vs-ip-auth\/\">proxy authentication methods guide<\/a> for the operational difference between credentials and IP allowlisting.<\/p>\n<h2>Configure a launch-level proxy with the Playwright library<\/h2>\n<p>Scripts that use the <code>playwright<\/code> library directly can set the route when launching the browser. This is useful for a single-purpose automation process whose contexts all share one network path. Close both the context and browser deterministically so processes, temporary profiles, downloads, and recordings do not accumulate after failures.<\/p>\n<pre><code class=\"language-typescript\">import { chromium } from 'playwright';\n\nfunction requiredEnv(name: string): string {\n  const value = process.env[name];\n  if (!value) throw new Error(`Missing required environment variable: ${name}`);\n  return value;\n}\n\nasync function main(): Promise&lt;void&gt; {\n  const browser = await chromium.launch({\n    proxy: {\n      server: requiredEnv('PROXY_SERVER'),\n      username: 'USER',\n      password: 'PASSWORD',\n    },\n  });\n\n  try {\n    const context = await browser.newContext();\n    const page = await context.newPage();\n    page.setDefaultNavigationTimeout(30_000);\n    await page.goto(requiredEnv('APPROVED_TEST_URL'), { waitUntil: 'domcontentloaded' });\n    await context.close();\n  } finally {\n    await browser.close();\n  }\n}\n\nvoid main();<\/code><\/pre>\n<p>Prefer the test-runner configuration when projects, fixtures, retries, reports, and parallel workers are already part of the suite. Prefer launch-level configuration for a focused library script. Avoid undocumented browser command-line flags when the supported <code>proxy<\/code> object expresses the requirement; custom arguments can change behavior across browser releases and make failures harder to reproduce.<\/p>\n<h2>Use a Playwright proxy per context for route isolation<\/h2>\n<p>A browser context is an isolated, incognito-like session with its own cookies and storage. Playwright also accepts proxy settings on <code>browser.newContext()<\/code>, allowing a library script to give contexts different approved routes without launching a browser process for every route. That can reduce startup cost, but it does not make the contexts interchangeable: each route and test identity still needs independent evidence and cleanup.<\/p>\n<pre><code class=\"language-typescript\">import { chromium, type BrowserContextOptions } from 'playwright';\n\ntype ApprovedProfile = {\n  name: string;\n  proxy: NonNullable&lt;BrowserContextOptions['proxy']&gt;;\n  url: string;\n};\n\nconst profiles: ApprovedProfile[] = [\n  {\n    name: 'approved-eu-route',\n    proxy: { server: 'http:\/\/HOST:3128' },\n    url: 'https:\/\/app.example.invalid\/status',\n  },\n  {\n    name: 'approved-us-route',\n    proxy: { server: 'socks5:\/\/proxy-us.example.invalid:1080' },\n    url: 'https:\/\/app.example.invalid\/status',\n  },\n];\n\nconst browser = await chromium.launch();\ntry {\n  for (const profile of profiles) {\n    const context = await browser.newContext({ proxy: profile.proxy });\n    try {\n      const page = await context.newPage();\n      await page.goto(profile.url, { waitUntil: 'domcontentloaded' });\n      console.log(profile.name, 'completed');\n    } finally {\n      await context.close();\n    }\n  }\n} finally {\n  await browser.close();\n}<\/code><\/pre>\n<p>The sample is intentionally sequential. Parallelism multiplies load on the proxy, the destination, and shared test data. Increase it only after the destination owner and proxy plan allow the proposed traffic, and give state-changing tests separate test accounts. Playwright&#8217;s <a href=\"https:\/\/playwright.dev\/docs\/auth\">authentication guidance<\/a> recommends distinct accounts for parallel tests that modify shared server-side state and warns that saved browser state may contain sensitive cookies and headers. A changed proxy context does not sanitize a reused <code>storageState<\/code> file.<\/p>\n<p>Choose context-level routing when isolation is part of the test case; choose a global proxy when the whole project has one expected egress. If you need many unrelated long-lived identities, separate workers or browser processes may be easier to observe and contain. The related <a href=\"\/blog\/use-proxies-curl-python-nodejs\/\">Python Requests proxy guide<\/a> covers the same route-versus-destination distinction for an HTTP client without browser state.<\/p>\n<h2>Handle authentication, protocols, and bypass rules deliberately<\/h2>\n<p>For an authenticated HTTP proxy, supply <code>username<\/code> and <code>password<\/code> separately. Keep them out of screenshots, traces, CI command lines, exception text, and copied configuration. Rotate exposed credentials and prefer short-lived or narrowly scoped access when the provider supports it. A <code>407 Proxy Authentication Required<\/code> response points to the proxy hop; a <code>401 Unauthorized<\/code> response usually comes from the destination. Treat them as different systems.<\/p>\n<p>Playwright supports HTTP and SOCKS proxy servers. An HTTP proxy is often the straightforward choice for browser web traffic and may tunnel HTTPS destinations. A SOCKS5 endpoint operates at a lower transport level and can suit applications that require it. Use an explicit scheme such as <code>http:\/\/<\/code> or <code>socks5:\/\/<\/code> so reviewers can see the intended protocol, even though Playwright treats a short <code>host:port<\/code> form as HTTP. Confirm that the proxy service, browser engine, and authentication method support the same combination; do not assume HTTP credential behavior applies to every SOCKS service. The <a href=\"\/blog\/http-https-socks5-proxies\/\">HTTP versus SOCKS5 guide<\/a> provides a broader protocol comparison.<\/p>\n<p>The optional <code>bypass<\/code> field is a comma-separated domain list. Bypassed destinations connect directly, so the list is a routing and data-exposure decision, not merely an optimization. Keep it narrow, review changes, and test every entry from the same runner environment. A typo can send traffic through the proxy when direct routing was intended, while an overly broad suffix can expose requests that were expected to use the proxy.<\/p>\n<h2>Verify the route with a controlled Playwright test<\/h2>\n<p>A successful page load proves only that one navigation completed. A useful route check compares a direct baseline with the proxy run and validates the observed egress through an owned or explicitly approved diagnostic endpoint. The endpoint can return a small JSON document such as <code>{\"ip\":\"redacted in logs\"}<\/code>. Assert the expected value from a protected environment variable, but do not print the actual address or the proxy URL into public CI output.<\/p>\n<pre><code class=\"language-typescript\">import { test, expect } from '@playwright\/test';\n\ntest('uses the approved proxy egress', async ({ page }) =&gt; {\n  const testUrl = process.env.APPROVED_TEST_URL ?? '';\n  const expectedEgress = process.env.EXPECTED_EGRESS_IP ?? '';\n  test.skip(!testUrl || !expectedEgress, 'Approved route-check settings are required');\n\n  const response = await page.goto(testUrl, { waitUntil: 'domcontentloaded' });\n  expect(response?.ok()).toBe(true);\n\n  const body = (await response?.json()) as { ip?: string } | undefined;\n  expect(body?.ip).toBe(expectedEgress);\n});<\/code><\/pre>\n<p>Run a small number of checks and record the stage rather than retrying blindly. Validate egress, expected country or region when applicable, TLS, navigation status, and stability separately. Never make a public IP-check service an unreviewed dependency of every test. The <a href=\"\/blog\/test-if-your-proxy-is-working\/\">proxy verification guide<\/a> explains why connectivity and suitability are different outcomes, while the <a href=\"\/blog\/proxy-leak-test\/\">DNS and WebRTC leak guide<\/a> covers browser-side checks that a simple egress assertion does not.<\/p>\n<h2>Troubleshoot by identifying the failing hop<\/h2>\n<p>Do not fix every network error by increasing timeouts or disabling checks. Keep TLS validation enabled; <code>ignoreHTTPSErrors<\/code> can hide a certificate or interception problem and is not a proxy repair. Start with the direct baseline, then confirm the endpoint scheme and port, proxy credentials or allowlist, DNS reachability, proxy connection, TLS tunnel, destination response, and application assertion in that order.<\/p>\n<table>\n<thead>\n<tr>\n<th>Symptom<\/th>\n<th>Likely boundary<\/th>\n<th>Next controlled check<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Browser executable is missing<\/td>\n<td>Playwright runtime<\/td>\n<td>Install the pinned browser and rerun a direct owned-site test.<\/td>\n<\/tr>\n<tr>\n<td>Connection refused or timed out before navigation<\/td>\n<td>Runner to proxy<\/td>\n<td>Confirm scheme, hostname, port, firewall, and allowlist without printing secrets.<\/td>\n<\/tr>\n<tr>\n<td>407 response<\/td>\n<td>Proxy authentication<\/td>\n<td>Check the approved credential source and authentication method.<\/td>\n<\/tr>\n<tr>\n<td>401, 403, or CAPTCHA page<\/td>\n<td>Destination policy or application auth<\/td>\n<td>Stop retries and confirm authorization, test account, API option, and destination rules.<\/td>\n<\/tr>\n<tr>\n<td>TLS or certificate error<\/td>\n<td>Tunnel or trust chain<\/td>\n<td>Inspect the certificate path and install the approved CA where required; do not suppress verification.<\/td>\n<\/tr>\n<tr>\n<td>Expected country but wrong language or timezone<\/td>\n<td>Browser or account profile<\/td>\n<td>Set and assert locale, timezone, geolocation, cookies, and account state separately.<\/td>\n<\/tr>\n<tr>\n<td>Some hosts avoid the proxy<\/td>\n<td>Bypass configuration<\/td>\n<td>Review the comma-separated domains and run one approved check per rule.<\/td>\n<\/tr>\n<tr>\n<td>Only CI fails<\/td>\n<td>Environment difference<\/td>\n<td>Compare masked variables, outbound firewall, browser version, certificates, and DNS.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Capture enough evidence to name the failing stage, but redact proxy credentials, authorization headers, cookies, query strings, and complete storage-state files. Traces and HAR files can contain sensitive request data, so limit collection, access, and retention. Follow the <a href=\"\/blog\/common-proxy-errors-fixes\/\">proxy troubleshooting guide<\/a> when the failure is not specific to Playwright.<\/p>\n<h2 id=\"source-boundaries\">Match configuration to the current Playwright API<\/h2>\n<p>The official <a href=\"https:\/\/playwright.dev\/docs\/api\/class-browsertype#browser-type-launch-option-proxy\" rel=\"noopener\">BrowserType proxy option<\/a> defines server, bypass, username, and password fields. The <a href=\"https:\/\/playwright.dev\/docs\/network#http-proxy\" rel=\"noopener\">Playwright network guide<\/a> shows global and per-context proxy scopes, while the <a href=\"https:\/\/playwright.dev\/docs\/test-use-options#network-options\" rel=\"noopener\">test-use network options<\/a> document configuration for Playwright Test. Keep the Playwright package and bundled browser versions aligned.<\/p>\n<p class=\"mexela-expected\"><strong>Expected observation:<\/strong> the selected browser or context uses the intended proxy, HTTP credentials are supplied at the proxy boundary, bypass hosts remain direct by design, and a clean context reports the assigned exit without leaking state from another test.<\/p>\n<p class=\"mexela-limits\"><strong>Operational limits:<\/strong> browser automation must remain authorized and rate-aware. A proxy does not erase cookies, account history, browser fingerprints, consent state, or destination policy. Keep concurrency bounded, isolate contexts, and stop when access is denied.<\/p>\n<h2 id=\"next-step\">Choose capacity only after the client passes<\/h2>\n<p>If regional QA is part of an approved test plan, compare the verified country, latency, and browser-session requirements with currently available proxy locations. <a href=\"\/proxy-locations\/\">Review the relevant Mexela option<\/a> only after the acceptance test is repeatable.<\/p>\n<h2>Frequently asked questions<\/h2>\n<div class=\"mexela-faq\">\n<h3>How do I set a proxy in Playwright?<\/h3>\n<p>Set <code>use.proxy<\/code> in <code>playwright.config.ts<\/code> for a test project, pass <code>proxy<\/code> to <code>browserType.launch()<\/code> for a whole library-managed browser, or pass it to <code>browser.newContext()<\/code> for an isolated context. Provide an explicit server scheme and test the route through an approved endpoint.<\/p>\n<h3>How does Playwright proxy authentication work?<\/h3>\n<p>For an HTTP proxy, Playwright accepts separate <code>username<\/code> and <code>password<\/code> fields alongside <code>server<\/code>. Load them from a protected secret source and distinguish a proxy&#8217;s 407 response from destination authentication failures.<\/p>\n<h3>Can Playwright use a SOCKS5 proxy?<\/h3>\n<p>Yes. Playwright documents SOCKSv5 support and accepts a server such as <code>socks5:\/\/HOST:1080<\/code>. Confirm the endpoint, browser engine, and provider&#8217;s authentication requirements instead of assuming HTTP proxy credential behavior applies to SOCKS.<\/p>\n<h3>Can each Playwright browser context use a different proxy?<\/h3>\n<p>Yes. Pass a proxy object to <code>browser.newContext()<\/code>. Per-context routing can isolate approved test routes while sharing a browser process, but cookies, storage state, test accounts, traffic limits, and cleanup still need deliberate isolation.<\/p>\n<h3>Does a Playwright proxy change browser geolocation and timezone?<\/h3>\n<p>No. A proxy can change network egress, but Playwright geolocation, locale, timezone, permissions, cookies, and account state are separate settings. Define and assert each signal required by the authorized geo-QA scenario.<\/p>\n<h3>Why does my Playwright proxy return 407 or fail only in CI?<\/h3>\n<p>A 407 response normally indicates proxy authentication. CI-only failures can also come from missing masked variables, outbound firewall rules, IP allowlists, DNS, certificate trust, or a different browser build. Compare the failing stage with a direct baseline and redact all secrets from evidence.<\/p>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Configure a Playwright proxy in TypeScript with safer authentication, global or per-context routing, bypass rules, controlled checks, and geo-QA safeguards.<\/p>\n","protected":false},"author":0,"featured_media":845,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[47],"tags":[],"_links":{"self":[{"href":"https:\/\/mexela.com\/blog\/wp-json\/wp\/v2\/posts\/844"}],"collection":[{"href":"https:\/\/mexela.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/mexela.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"replies":[{"embeddable":true,"href":"https:\/\/mexela.com\/blog\/wp-json\/wp\/v2\/comments?post=844"}],"version-history":[{"count":2,"href":"https:\/\/mexela.com\/blog\/wp-json\/wp\/v2\/posts\/844\/revisions"}],"predecessor-version":[{"id":919,"href":"https:\/\/mexela.com\/blog\/wp-json\/wp\/v2\/posts\/844\/revisions\/919"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/mexela.com\/blog\/wp-json\/wp\/v2\/media\/845"}],"wp:attachment":[{"href":"https:\/\/mexela.com\/blog\/wp-json\/wp\/v2\/media?parent=844"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mexela.com\/blog\/wp-json\/wp\/v2\/categories?post=844"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mexela.com\/blog\/wp-json\/wp\/v2\/tags?post=844"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}