<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: KindProxy</title>
    <description>The latest articles on DEV Community by KindProxy (@andy_7c5a10068ad5642ac440).</description>
    <link>https://dev.to/andy_7c5a10068ad5642ac440</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4184340%2F2813fa8d-f0a0-4242-9665-b366e0550af8.png</url>
      <title>DEV Community: KindProxy</title>
      <link>https://dev.to/andy_7c5a10068ad5642ac440</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/andy_7c5a10068ad5642ac440"/>
    <language>en</language>
    <item>
      <title>Playwright Proxy Authentication: Common Connection Errors and How to Fix Them</title>
      <dc:creator>KindProxy</dc:creator>
      <pubDate>Mon, 02 Nov 2026 16:47:50 +0000</pubDate>
      <link>https://dev.to/andy_7c5a10068ad5642ac440/playwright-proxy-authentication-common-connection-errors-and-how-to-fix-them-1ap8</link>
      <guid>https://dev.to/andy_7c5a10068ad5642ac440/playwright-proxy-authentication-common-connection-errors-and-how-to-fix-them-1ap8</guid>
      <description>&lt;p&gt;When Playwright cannot open a page through a proxy, changing random settings usually makes the problem harder to find. First identify which part failed: Chromium startup, the connection to the proxy, proxy authentication, the HTTPS connection to the destination, or the page itself.&lt;/p&gt;

&lt;p&gt;This guide walks through those checks in order. It covers HTTP and SOCKS proxy URLs, the difference between proxy authentication and website login, common errors such as &lt;code&gt;407&lt;/code&gt; and &lt;code&gt;ERR_PROXY_CONNECTION_FAILED&lt;/code&gt;, and a Python script that tests the proxy exit and an HTTPS destination separately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start by locating the failure
&lt;/h2&gt;

&lt;p&gt;Think of a browser request as a short chain. A failure at one step does not automatically mean the next step is broken.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F993h5j93j9b90u0jbq12.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F993h5j93j9b90u0jbq12.png" alt="Playwright proxy troubleshooting flowchart showing browser startup, proxy connectivity, authentication, exit IP verification, and HTTPS destination checks" width="799" height="333"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The browser process can start even when a proxy is unreachable. Likewise, an IP check can work while another destination times out. Test each stage separately before changing credentials, protocol, and timeout values at the same time.&lt;/p&gt;

&lt;h2&gt;
  
  
  HTTP and SOCKS proxy settings in Playwright
&lt;/h2&gt;

&lt;p&gt;Playwright accepts proxy settings through a &lt;code&gt;proxy&lt;/code&gt; object. The &lt;code&gt;server&lt;/code&gt; value identifies the proxy endpoint and protocol. The &lt;code&gt;username&lt;/code&gt; and &lt;code&gt;password&lt;/code&gt; fields provide credentials for supported HTTP proxy authentication.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;proxy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;server&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://proxy.example.com:8080&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;username&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_USERNAME&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;password&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_PASSWORD&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;playwright&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;proxy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Playwright also accepts SOCKS proxy URLs, such as &lt;code&gt;socks5://proxy.example.com:1080&lt;/code&gt;. Its documentation describes the &lt;code&gt;username&lt;/code&gt; and &lt;code&gt;password&lt;/code&gt; fields as optional credentials for an HTTP proxy. That distinction matters: accepting a SOCKS URL does not guarantee that every browser and SOCKS service combination supports username/password authentication. Check the exact endpoint and authentication method you use against the &lt;a href="https://playwright.dev/python/docs/network" rel="noopener noreferrer"&gt;Playwright Python network documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In our Chromium test, the KindProxy SOCKS5 endpoint with username/password authentication returned &lt;code&gt;Browser does not support socks5 proxy authentication&lt;/code&gt;. The HTTP proxy gateway with username/password worked in that test. This is an observation from that test configuration, not a claim that every Playwright or Chromium version has the same limitation. For this guide, the main runnable example uses HTTP proxy authentication. Test authenticated SOCKS5 separately with the exact browser, endpoint, and authentication method you intend to deploy.&lt;/p&gt;

&lt;h3&gt;
  
  
  The proxy URL and the destination URL are different
&lt;/h3&gt;

&lt;p&gt;An HTTP proxy can carry requests to HTTPS websites. For example, &lt;code&gt;http://proxy.example.com:8080&lt;/code&gt; describes the connection to the proxy; your page can still be &lt;code&gt;https://example.com&lt;/code&gt;. The browser typically asks the HTTP proxy to establish a tunnel for the HTTPS destination, then negotiates TLS with that destination through the tunnel.&lt;/p&gt;

&lt;p&gt;An &lt;code&gt;https://&lt;/code&gt; proxy URL means the connection from the browser to the proxy itself uses TLS, if the provider supports that endpoint. It does not mean the target website is HTTPS; the target URL is configured separately. Use the exact scheme and port supplied by your proxy provider.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep proxy authentication separate from website authentication
&lt;/h2&gt;

&lt;p&gt;Three different mechanisms are easy to mix up:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mechanism&lt;/th&gt;
&lt;th&gt;Who checks it?&lt;/th&gt;
&lt;th&gt;Typical symptom&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;HTTP proxy authentication&lt;/td&gt;
&lt;td&gt;The proxy server&lt;/td&gt;
&lt;td&gt;&lt;code&gt;407 Proxy Authentication Required&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IP allowlisting&lt;/td&gt;
&lt;td&gt;The proxy service checks your client IP&lt;/td&gt;
&lt;td&gt;The proxy rejects a machine whose public IP is not on the allowlist&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Website authentication&lt;/td&gt;
&lt;td&gt;The destination website&lt;/td&gt;
&lt;td&gt;A login page, &lt;code&gt;401&lt;/code&gt;, &lt;code&gt;403&lt;/code&gt;, or an application-specific message&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If a page shows a website login form, changing the proxy password is unlikely to fix it. If the response is &lt;code&gt;407&lt;/code&gt;, investigate the proxy authentication step first. A website may return &lt;code&gt;403&lt;/code&gt; for many reasons; do not assume it is a proxy credential error without checking where the response came from.&lt;/p&gt;

&lt;p&gt;For username/password authentication, pass the values as separate fields instead of embedding them in the proxy URL. Keep them in environment variables or a local &lt;code&gt;.env&lt;/code&gt; file that is excluded from version control. If your proxy uses IP allowlisting, follow that setup instead of inventing a username/password pair.&lt;/p&gt;

&lt;p&gt;For a complete first-time connection walkthrough, see our &lt;a href="https://www.kindproxy.com/blog/en/blog/how-to-use-residential-proxies-with-playwright-2026/" rel="noopener noreferrer"&gt;Playwright residential proxy setup guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix &lt;code&gt;407 Proxy Authentication Required&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;A &lt;code&gt;407&lt;/code&gt; response means the proxy requires authentication or did not accept the credentials it received. Depending on how the browser and proxy handle the failed authentication, Chromium may expose a 407 response or surface a navigation/network error instead. Do not rely on &lt;code&gt;page.goto()&lt;/code&gt; always returning a response object for a failed proxy authentication attempt.&lt;/p&gt;

&lt;p&gt;Check these items in order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Confirm the authentication method.&lt;/strong&gt; Is the account set to username/password, or to IP allowlisting?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Copy the full credentials again.&lt;/strong&gt; Proxy usernames may include targeting or session fields generated by the provider. Preserve the complete value.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check the endpoint and port.&lt;/strong&gt; Credentials can be valid but sent to the wrong gateway or protocol endpoint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check how the fields are passed.&lt;/strong&gt; In Playwright, use the &lt;code&gt;proxy&lt;/code&gt; object's &lt;code&gt;username&lt;/code&gt; and &lt;code&gt;password&lt;/code&gt; fields for HTTP proxy authentication.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check allowlisting if enabled.&lt;/strong&gt; Make sure the machine running Playwright exits through an IP that the proxy account allows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test from the same host.&lt;/strong&gt; A successful connection from your laptop does not validate credentials or allowlisting from a server with a different public IP.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Avoid adding a second, conflicting proxy authentication method. Also avoid pasting credentials into a shared terminal transcript, source repository, or screenshot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix &lt;code&gt;ERR_PROXY_CONNECTION_FAILED&lt;/code&gt; and timeouts
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;ERR_PROXY_CONNECTION_FAILED&lt;/code&gt; usually points to the browser's inability to establish a connection to the configured proxy. It is different from a page that loads slowly after the proxy connection succeeds.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;First checks&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Chromium exits before opening a page&lt;/td&gt;
&lt;td&gt;Browser installation, operating-system libraries, and launch options&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ERR_PROXY_CONNECTION_FAILED&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Proxy hostname, port, protocol prefix, DNS resolution, and outbound network access&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A &lt;code&gt;407&lt;/code&gt; response or authentication failure&lt;/td&gt;
&lt;td&gt;Authentication method, username/password, or allowlist&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IP check succeeds but target page times out&lt;/td&gt;
&lt;td&gt;Target reachability, destination-specific response, and navigation timeout&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Page loads but a locator times out&lt;/td&gt;
&lt;td&gt;Whether the expected page loaded and whether the selector still matches&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Connection resets during HTTPS navigation&lt;/td&gt;
&lt;td&gt;Proxy tunnel, destination connection, and TLS error details&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Set a reasonable navigation timeout, but do not treat a larger timeout as a fix for an unreachable proxy. If one endpoint fails, test the IP check and destination independently from the same machine. Change one variable at a time so the result is useful.&lt;/p&gt;

&lt;p&gt;Playwright's navigation wait condition also matters. &lt;code&gt;domcontentloaded&lt;/code&gt; waits for the document's DOM content to be parsed; it does not mean every image, API request, or dynamically rendered component has finished. &lt;code&gt;networkidle&lt;/code&gt; is not a universal solution either: pages with analytics, polling, or persistent requests may never become idle. Wait for the specific page element or application state your task needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Diagnose DNS, TLS, and HTTPS errors
&lt;/h2&gt;

&lt;h3&gt;
  
  
  DNS errors
&lt;/h3&gt;

&lt;p&gt;First distinguish the proxy hostname from the destination hostname. If the machine cannot resolve the proxy gateway, Playwright cannot connect to that endpoint. Destination name resolution may be handled differently depending on the proxy protocol and implementation. Compare a request through the configured proxy with a direct request and record the exact browser error instead of assuming all DNS failures happen locally.&lt;/p&gt;

&lt;h3&gt;
  
  
  TLS and certificate errors
&lt;/h3&gt;

&lt;p&gt;HTTPS involves a TLS connection to the destination. If the proxy endpoint itself uses TLS, there is also a separate TLS connection between the client and proxy. A failure in one connection does not identify a failure in the other; inspect the failing host and the full error message.&lt;/p&gt;

&lt;p&gt;Do not use &lt;code&gt;ignore_https_errors=True&lt;/code&gt; as a general repair. It suppresses certificate validation and may hide a certificate, interception, or configuration problem. Use it only in a controlled test when you understand why certificate validation is failing—not to make an unexplained production error disappear.&lt;/p&gt;

&lt;h3&gt;
  
  
  Common Chromium network errors
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ERR_NAME_NOT_RESOLVED&lt;/code&gt;: check which hostname failed to resolve and from which side of the proxy connection.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ERR_TUNNEL_CONNECTION_FAILED&lt;/code&gt;: inspect the proxy tunnel setup, endpoint, and destination response.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ERR_CERT_AUTHORITY_INVALID&lt;/code&gt; or another certificate error: identify whether the certificate belongs to the proxy connection or target site before changing trust settings.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ERR_CONNECTION_RESET&lt;/code&gt;: compare the same request through the proxy and directly, then test a second destination.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These error names are diagnostic clues, not conclusive root causes. Keep the browser output and compare it with the proxy provider's connection instructions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verify the proxy exit IP and test HTTPS separately
&lt;/h2&gt;

&lt;p&gt;An IP-check endpoint answers one question: which public IP did that request use? It does not confirm that another website is reachable or that the website will return the content you expect.&lt;/p&gt;

&lt;p&gt;The script below makes two separate requests through the same browser context:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;An IP-check endpoint, to verify the observed exit IP.&lt;/li&gt;
&lt;li&gt;A separate HTTPS page, to check destination navigation and response status.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the first succeeds and the second fails, keep that distinction in your diagnosis. Also remember that an IP geolocation lookup is approximate; it is not proof that a site will display a particular market's price or content.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Python diagnostic script
&lt;/h2&gt;

&lt;p&gt;The script uses Playwright's Chromium browser and reads settings from environment variables. It tests the IP-check endpoint and target URL in separate pages, so one navigation does not overwrite the other page's state.&lt;/p&gt;

&lt;p&gt;Install Playwright and &lt;code&gt;python-dotenv&lt;/code&gt;, then install Chromium:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv

&lt;span class="c"&gt;# macOS / Linux&lt;/span&gt;
&lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate

&lt;span class="c"&gt;# Windows PowerShell&lt;/span&gt;
&lt;span class="c"&gt;# .venv\Scripts\Activate.ps1&lt;/span&gt;

python &lt;span class="nt"&gt;-m&lt;/span&gt; pip &lt;span class="nb"&gt;install &lt;/span&gt;playwright python-dotenv
python &lt;span class="nt"&gt;-m&lt;/span&gt; playwright &lt;span class="nb"&gt;install &lt;/span&gt;chromium
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Linux, install Chromium's system dependencies if the browser reports missing shared libraries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; playwright install-deps chromium
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a &lt;code&gt;.env&lt;/code&gt; file next to &lt;code&gt;diagnose_proxy.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PROXY_SERVER=http://gw.kindproxy.com:12000
PROXY_USERNAME=your_dashboard_username
PROXY_PASSWORD=your_dashboard_password
CHECK_URL=https://api.ipify.org?format=json
TARGET_URL=https://example.com/
NAVIGATION_TIMEOUT_MS=30000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace the placeholders with the exact endpoint and credentials from your dashboard. Never commit &lt;code&gt;.env&lt;/code&gt; or include real credentials in logs. If you use IP allowlisting, configure it as your provider instructs and leave both &lt;code&gt;PROXY_USERNAME&lt;/code&gt; and &lt;code&gt;PROXY_PASSWORD&lt;/code&gt; empty.&lt;/p&gt;

&lt;p&gt;Save the following as &lt;code&gt;diagnose_proxy.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dotenv&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;load_dotenv&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;playwright.async_api&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;Error&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;PlaywrightError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;TimeoutError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;PlaywrightTimeoutError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;async_playwright&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;load_dotenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__file__&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;with_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.env&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;classify_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Return a useful hint without hiding the original Playwright error.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;checks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;407&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Proxy authentication may have failed. Check credentials or IP allowlisting.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ERR_PROXY_CONNECTION_FAILED&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Chromium could not connect to the configured proxy endpoint.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ERR_TUNNEL_CONNECTION_FAILED&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The HTTPS tunnel through the proxy could not be established.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ERR_NAME_NOT_RESOLVED&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A hostname could not be resolved. Identify whether it is the proxy or destination hostname.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ERR_CERT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TLS certificate validation failed. Identify whether the certificate belongs to the proxy or destination.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ERR_CONNECTION_RESET&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The connection was reset. Compare the proxy route and destination.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;marker&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;explanation&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;checks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;marker&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;explanation&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;No known error marker matched. Inspect the original Playwright error below.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;timeout_ms&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;expect_ip_json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;[&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;] &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;wait_until&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;domcontentloaded&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timeout_ms&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;No main-resource response was returned.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HTTP status: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Final URL: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;407&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The response is HTTP 407. Check proxy authentication &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;or the account&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s IP allowlist.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Request returned a non-success HTTP status: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;expect_ip_json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;inner_text&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JSONDecodeError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Expected JSON from the IP-check endpoint. Body: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

            &lt;span class="n"&gt;exit_ip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ip&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;exit_ip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The IP-check response did not contain an &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ip&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; field.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Observed exit IP: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;exit_ip&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;

    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;PlaywrightTimeoutError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Navigation timed out. Check the destination and the timeout separately.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Exception type: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Details: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;PlaywrightError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;classify_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Exception type: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Details: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;


&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;proxy_server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PROXY_SERVER&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;proxy_username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PROXY_USERNAME&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;proxy_password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PROXY_PASSWORD&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;check_endpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CHECK_URL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.ipify.org?format=json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;target_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TARGET_URL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://example.com/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;timeout_ms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NAVIGATION_TIMEOUT_MS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;30000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;ValueError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NAVIGATION_TIMEOUT_MS must be an integer.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;proxy_server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Set PROXY_SERVER to the proxy endpoint you want to test.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;proxy_username&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nf"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;proxy_password&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Set both PROXY_USERNAME and PROXY_PASSWORD, or leave both empty &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;when using IP allowlisting.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;timeout_ms&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NAVIGATION_TIMEOUT_MS must be greater than zero.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;proxy_settings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;server&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;proxy_server&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;proxy_username&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;proxy_password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;proxy_settings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;username&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;proxy_username&lt;/span&gt;
        &lt;span class="n"&gt;proxy_settings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;password&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;proxy_password&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;async_playwright&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;playwright&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;playwright&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;proxy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;proxy_settings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;PlaywrightError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Chromium could not launch with the configured proxy settings.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;classify_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Exception type: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Details: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new_context&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;ip_page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new_page&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;target_page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new_page&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

            &lt;span class="n"&gt;ip_ok&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;check_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;ip_page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PROXY IP CHECK&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;check_endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;timeout_ms&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;expect_ip_json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;target_ok&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;check_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;target_page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HTTPS TARGET&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;target_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;timeout_ms&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Summary:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Proxy IP check: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;PASS&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ip_ok&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;FAIL&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HTTPS target check: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;PASS&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;target_ok&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;FAIL&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ip_ok&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;target_ok&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;SystemExit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it from the same environment where Playwright is installed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python diagnose_proxy.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script never prints the proxy username or password. It preserves the exception type and message so you can see the browser's original diagnostic instead of a generic “connection failed.” A &lt;code&gt;PASS&lt;/code&gt; for the IP check and &lt;code&gt;FAIL&lt;/code&gt; for the HTTPS target means the two destinations behaved differently; continue troubleshooting the target route rather than rewriting valid credentials.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example output format
&lt;/h3&gt;

&lt;p&gt;The values below illustrate the format only; they are not a recorded test result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[PROXY IP CHECK] https://api.ipify.org?format=json

HTTP status: 200
Observed exit IP: 203.0.113.10

[HTTPS TARGET] https://example.com/

HTTP status: 200
Final URL: https://example.com/

Summary:
Proxy IP check: PASS
HTTPS target check: PASS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;203.0.113.10&lt;/code&gt; is a documentation-only address. Replace this block with real output only after running the script, and remove or mask any real exit IP and credentials before publishing screenshots.&lt;/p&gt;

&lt;h3&gt;
  
  
  Real sticky-session test
&lt;/h3&gt;

&lt;p&gt;We also ran the diagnostic script five consecutive times using the same sticky session, with a three-second pause between runs. All five IP checks and HTTPS destination requests succeeded with HTTP 200, and the observed exit IP remained &lt;code&gt;99.140.235.18&lt;/code&gt; throughout this short test.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftsu9gov2u48gjq5pi9qx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftsu9gov2u48gjq5pi9qx.png" alt="Five consecutive Playwright proxy diagnostic tests showing successful IP checks, HTTPS requests, and a consistent sticky-session exit IP" width="459" height="783"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This confirms consistency across these five short-interval tests only. It does not establish long-term session stability or guarantee access to other websites. Mask the exit IP in the public image if you do not want to disclose it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick troubleshooting checklist
&lt;/h2&gt;

&lt;p&gt;Before changing multiple settings, check these items in order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Does Chromium launch without a proxy?&lt;/li&gt;
&lt;li&gt;Does the proxy hostname resolve from the machine running Playwright?&lt;/li&gt;
&lt;li&gt;Are the scheme, endpoint, and port the ones issued by the provider?&lt;/li&gt;
&lt;li&gt;Is the account configured for username/password or IP allowlisting?&lt;/li&gt;
&lt;li&gt;Does the IP-check request return an IP through the browser?&lt;/li&gt;
&lt;li&gt;Does a separate HTTPS destination return a response?&lt;/li&gt;
&lt;li&gt;Is the failure a navigation timeout, a page status, or a selector wait?&lt;/li&gt;
&lt;li&gt;Are certificates being validated normally? Do not turn off verification to hide an unexplained error.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the proxy check and target request still fail after these checks, save the exact exception type, message, timestamp, endpoint, and destination. Share logs only after removing credentials and personal data.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Is a &lt;code&gt;407&lt;/code&gt; error caused by the website?
&lt;/h3&gt;

&lt;p&gt;Usually, &lt;code&gt;407 Proxy Authentication Required&lt;/code&gt; is a proxy authentication response. A target website's login or access response is more commonly a login page, &lt;code&gt;401&lt;/code&gt;, or &lt;code&gt;403&lt;/code&gt;. Confirm the response source before changing website credentials. Also note that a failed proxy authentication may surface as a browser navigation error rather than a response object.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does Playwright support SOCKS5?
&lt;/h3&gt;

&lt;p&gt;Playwright accepts SOCKS proxy URLs. Authentication compatibility depends on the browser and endpoint. Its documented username/password proxy fields are for HTTP proxy authentication, and our Chromium test could not use the tested KindProxy SOCKS5 endpoint with username/password authentication. Use a verified configuration for your account and runtime.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why does &lt;code&gt;curl&lt;/code&gt; work while Playwright fails?
&lt;/h3&gt;

&lt;p&gt;The two clients may use different proxy syntax, authentication handling, DNS behavior, TLS settings, or timeouts. Run both checks from the same machine, against the same destination, and compare the exact error messages.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should I increase the timeout?
&lt;/h3&gt;

&lt;p&gt;Increase it only when the connection is progressing but the destination needs more time. A timeout will not repair invalid credentials, a wrong port, a DNS failure, or a proxy that cannot reach the destination.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;Diagnose Playwright proxy errors one layer at a time. Separate proxy authentication from website login, verify the exit IP independently from the target site, and preserve the original error details.&lt;/p&gt;

&lt;p&gt;For the initial setup, see our &lt;a href="https://www.kindproxy.com/blog/en/blog/how-to-use-residential-proxies-with-playwright-2026/" rel="noopener noreferrer"&gt;Playwright proxy integration guide&lt;/a&gt;. If a residential proxy fits your workflow, compare &lt;a href="https://www.kindproxy.com/pricing/residential-proxies/traffic/?spread_code=dev.to"&gt;KindProxy residential proxy plans&lt;/a&gt; and test the exact endpoint and authentication method before scaling.&lt;/p&gt;

</description>
      <category>debugging</category>
      <category>playwright</category>
      <category>python</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How WebRTC Can Reveal IP Addresses Behind a VPN or Proxy</title>
      <dc:creator>KindProxy</dc:creator>
      <pubDate>Wed, 14 Oct 2026 10:14:56 +0000</pubDate>
      <link>https://dev.to/andy_7c5a10068ad5642ac440/how-webrtc-can-reveal-ip-addresses-behind-a-vpn-or-proxy-10bm</link>
      <guid>https://dev.to/andy_7c5a10068ad5642ac440/how-webrtc-can-reveal-ip-addresses-behind-a-vpn-or-proxy-10bm</guid>
      <description>&lt;h1&gt;
  
  
  How WebRTC Can Reveal IP Addresses Behind a VPN or Proxy
&lt;/h1&gt;

&lt;p&gt;VPNs and proxies route many of your internet connections through another network endpoint. But your browser can also use other networking mechanisms. One worth understanding is &lt;strong&gt;WebRTC&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;WebRTC powers real-time features such as video calls, voice calls, and peer-to-peer data exchange. As part of setting up a connection, the browser gathers network information called &lt;strong&gt;ICE candidates&lt;/strong&gt;. Depending on your browser and network setup, some candidates may expose public IP addresses that differ from the address seen by a normal HTTP request.&lt;/p&gt;

&lt;p&gt;This guide explains what those candidates mean, why they can differ from your VPN or proxy’s HTTP exit IP, and how to interpret the results of a WebRTC leak test.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. What Is WebRTC?
&lt;/h2&gt;

&lt;p&gt;WebRTC (Web Real-Time Communication) is a set of browser APIs and protocols for real-time communication without a separate plugin. It is used by applications such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Video conferencing tools&lt;/li&gt;
&lt;li&gt;Browser-based voice calls&lt;/li&gt;
&lt;li&gt;Peer-to-peer file sharing&lt;/li&gt;
&lt;li&gt;Real-time collaboration apps&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;WebRTC uses &lt;strong&gt;ICE&lt;/strong&gt; (Interactive Connectivity Establishment) to find possible network paths between peers. During this process, the browser gathers information about available network addresses and connection options. Each possible path is represented by an ICE candidate.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. What Are ICE Candidates?
&lt;/h2&gt;

&lt;p&gt;ICE candidates describe possible ways to establish a WebRTC connection.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Candidate type&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;host&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;An address associated with a local network interface. Modern browsers may conceal local addresses using mDNS hostnames.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;srflx&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A server-reflexive candidate, typically a public-facing address discovered through STUN.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;prflx&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A peer-reflexive candidate discovered during connectivity checks.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;relay&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A candidate provided by a relay server, typically using TURN.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Candidates have different privacy implications. A private address such as &lt;code&gt;192.168.1.10&lt;/code&gt; is not a public IP address. An mDNS hostname ending in &lt;code&gt;.local&lt;/code&gt; should not, by itself, be treated as evidence that a public IP has leaked.&lt;/p&gt;

&lt;p&gt;A public candidate deserves closer attention. It may reveal a public-facing network address, but the candidate type alone does not establish whether that address is unexpected.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. How Does STUN Discover a Public Address?
&lt;/h2&gt;

&lt;p&gt;STUN stands for Session Traversal Utilities for NAT. When a browser uses a STUN server during ICE gathering, the server can observe the source address of the request. The browser may then receive a server-reflexive candidate containing the public-facing address and port observed by that server.&lt;/p&gt;

&lt;p&gt;The process looks roughly like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The browser creates an &lt;code&gt;RTCPeerConnection&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;ICE gathering begins.&lt;/li&gt;
&lt;li&gt;The browser may contact a configured STUN server.&lt;/li&gt;
&lt;li&gt;The STUN server observes the request’s source address.&lt;/li&gt;
&lt;li&gt;The browser may receive a server-reflexive (&lt;code&gt;srflx&lt;/code&gt;) candidate with that address.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is normal WebRTC behavior; STUN is not inherently a security vulnerability. However, the STUN operator can observe the network address from which the request arrives.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Why Might a WebRTC Address Differ From a VPN or Proxy IP?
&lt;/h2&gt;

&lt;p&gt;Suppose your browser sends an ordinary HTTP request through a VPN. The destination website sees the VPN’s public exit IP.&lt;/p&gt;

&lt;p&gt;WebRTC traffic may take a different route, depending on the VPN, proxy, browser, operating system, and network configuration. That means a public WebRTC candidate can differ from the address seen by an HTTP lookup.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Observation&lt;/th&gt;
&lt;th&gt;Example address&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Expected HTTP exit IP&lt;/td&gt;
&lt;td&gt;&lt;code&gt;198.51.100.20&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Public WebRTC candidate&lt;/td&gt;
&lt;td&gt;&lt;code&gt;203.0.113.45&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These are documentation-only IPv4 addresses used as examples.&lt;/p&gt;

&lt;p&gt;If a real test finds different public addresses, investigate the result. A mismatch alone does &lt;strong&gt;not&lt;/strong&gt; prove that the VPN has failed or that your original IP address has been exposed. Possible explanations include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The browser has multiple network interfaces.&lt;/li&gt;
&lt;li&gt;IPv4 and IPv6 use different routes.&lt;/li&gt;
&lt;li&gt;HTTP and STUN requests follow different network paths.&lt;/li&gt;
&lt;li&gt;A VPN or proxy handles some traffic differently from other traffic.&lt;/li&gt;
&lt;li&gt;The HTTP lookup is reaching a different endpoint than expected.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The key question is whether the observed address differs from the network endpoint you intended to use.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. How to Test for a Potential WebRTC IP Leak
&lt;/h2&gt;

&lt;p&gt;A useful test gathers ICE candidates, classifies their addresses, and—when a comparison IP is available—checks public candidates against the expected HTTP exit IP.&lt;/p&gt;

&lt;p&gt;The general process is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Obtain the HTTP exit IP you expect.&lt;/li&gt;
&lt;li&gt;Create an &lt;code&gt;RTCPeerConnection&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Gather ICE candidates, allowing enough time for the process to finish.&lt;/li&gt;
&lt;li&gt;Classify candidate types and addresses.&lt;/li&gt;
&lt;li&gt;Compare &lt;strong&gt;all public candidate addresses&lt;/strong&gt;, not just &lt;code&gt;srflx&lt;/code&gt; candidates, with the expected HTTP exit IP.&lt;/li&gt;
&lt;li&gt;Treat any mismatch as a signal to investigate, not a definitive conclusion.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is a simplified browser-side example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RTCPeerConnection&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;iceServers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stun:stun.l.google.com:19302&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;pc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onicecandidate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;pc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createDataChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;pc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createOffer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;offer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;pc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setLocalDescription&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;offer&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This example starts ICE candidate gathering; it is not a complete leak detector. Production code should also handle timeouts, cleanup, unsupported environments, candidate parsing, and address classification.&lt;/p&gt;

&lt;p&gt;The STUN server shown is an example. The detector’s default configuration uses Google’s public STUN server, so gathering candidates contacts that service. A deployment should choose appropriate ICE servers and explain the resulting network activity.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. How to Interpret the Results
&lt;/h2&gt;

&lt;p&gt;A detector should avoid treating every address difference as a confirmed security problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Potential mismatch&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A public WebRTC address differs from the supplied HTTP exit IP. This is a reason to investigate whether the difference is expected for your network setup.&lt;/p&gt;

&lt;p&gt;If no HTTP exit IP is supplied, this detector flags discovered public addresses as &lt;strong&gt;unverified&lt;/strong&gt; and reports &lt;code&gt;potential_leak&lt;/code&gt;. That result does not mean a mismatch was confirmed; there was no comparison IP to check against.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No public mismatch detected&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The observed public candidates match the supplied HTTP exit IP. This is useful evidence, but it does not guarantee that all network information is hidden.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Private or mDNS addresses only&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Private addresses and mDNS hostnames may appear during ICE gathering. Their presence alone does not prove that a public IP has leaked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No candidates gathered&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The browser may have blocked WebRTC, gathering may have timed out, or no candidates may have been produced for another reason. An empty result does not automatically mean the test passed, and the detector may not be able to determine why no candidates were returned.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Try the Open-Source WebRTC Leak Detector
&lt;/h2&gt;

&lt;p&gt;I built an open-source, browser-based WebRTC leak detector to make the process easier to inspect. It gathers and classifies ICE candidates, distinguishes public addresses from private and mDNS addresses, and can compare public candidate addresses against an optional HTTP exit IP.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live demo:&lt;/strong&gt; &lt;a href="https://www.kindproxy.com/webrtc-leak-test/" rel="noopener noreferrer"&gt;kindproxy.com/webrtc-leak-test&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source code:&lt;/strong&gt; &lt;a href="https://github.com/andy-smith-999/webrtc-leak-detector" rel="noopener noreferrer"&gt;github.com/andy-smith-999/webrtc-leak-detector&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The detector uses plain JavaScript ES modules and is designed to be provider-independent. The demo uses a public STUN server for ICE gathering and can use an HTTP IP lookup for comparison. The source code and privacy documentation describe the detection process and its limitations.&lt;/p&gt;

&lt;p&gt;Contributions, bug reports, and feedback are welcome.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Final Thoughts
&lt;/h2&gt;

&lt;p&gt;WebRTC and ICE candidate gathering are normal parts of how browsers establish real-time connections. The privacy question is whether the addresses observed during that process match the network behavior you expect.&lt;/p&gt;

&lt;p&gt;A difference between HTTP and WebRTC addresses is worth examining, but it is not enough by itself to prove that your real IP address has leaked. Understanding ICE candidates, STUN, address classification, and network routing can help you interpret test results more accurately.&lt;/p&gt;

&lt;p&gt;If you work on browser privacy, networking, or VPN testing, try the detector, inspect the source, and share any issues or improvements you find.&lt;/p&gt;

</description>
      <category>cybersecurity</category>
      <category>networking</category>
      <category>privacy</category>
      <category>security</category>
    </item>
  </channel>
</rss>
