<?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: ZP</title>
    <description>The latest articles on DEV Community by ZP (@zp_4579).</description>
    <link>https://dev.to/zp_4579</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3957975%2F92091d7a-8763-44c1-9034-e10e57a46719.png</url>
      <title>DEV Community: ZP</title>
      <link>https://dev.to/zp_4579</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/zp_4579"/>
    <language>en</language>
    <item>
      <title>Debugging Playwright CDP Sessions That Lose Cookies and Proxy Context</title>
      <dc:creator>ZP</dc:creator>
      <pubDate>Sat, 30 May 2026 05:22:00 +0000</pubDate>
      <link>https://dev.to/zp_4579/debugging-playwright-cdp-sessions-that-lose-cookies-and-proxy-context-1bm6</link>
      <guid>https://dev.to/zp_4579/debugging-playwright-cdp-sessions-that-lose-cookies-and-proxy-context-1bm6</guid>
      <description>&lt;p&gt;I started treating this as a separate bug class after seeing the same failure pattern repeat:&lt;/p&gt;

&lt;p&gt;A human opens a &lt;strong&gt;browser profile&lt;/strong&gt; and is logged in.&lt;br&gt;
A &lt;strong&gt;Playwright&lt;/strong&gt; script attaches through &lt;strong&gt;CDP&lt;/strong&gt;.&lt;br&gt;
The page opens.&lt;br&gt;
Then the workflow behaves as if the &lt;strong&gt;session&lt;/strong&gt; is empty, the account is logged out, or the request is coming from the wrong &lt;strong&gt;proxy&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The tempting fix is to add waits or rewrite selectors.&lt;/p&gt;

&lt;p&gt;That is usually the wrong first move.&lt;/p&gt;

&lt;p&gt;When a &lt;strong&gt;CDP-attached session&lt;/strong&gt; loses &lt;strong&gt;auth state&lt;/strong&gt; or &lt;strong&gt;proxy context&lt;/strong&gt;, the first question is not:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which selector changed?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Did my script attach to the same browser identity that I verified manually?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This note is a practical debugging checklist for that problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; Playwright attaches to an existing Chromium browser, but &lt;strong&gt;cookies&lt;/strong&gt;, &lt;strong&gt;session state&lt;/strong&gt;, or &lt;strong&gt;proxy context&lt;/strong&gt; do not match what I expected.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scope:&lt;/strong&gt; &lt;strong&gt;Chromium&lt;/strong&gt;, &lt;strong&gt;connectOverCDP&lt;/strong&gt;, &lt;strong&gt;browser profiles&lt;/strong&gt;, &lt;strong&gt;persistent sessions&lt;/strong&gt;, &lt;strong&gt;headless checks&lt;/strong&gt;, and &lt;strong&gt;AI agent debugging&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not covered:&lt;/strong&gt; bypassing platform protections, CAPTCHA handling, or moving auth data between unrelated accounts.&lt;/p&gt;
&lt;h2&gt;
  
  
  The failure pattern
&lt;/h2&gt;

&lt;p&gt;The run usually looks healthy from the outside.&lt;/p&gt;

&lt;p&gt;The browser starts. The &lt;strong&gt;CDP endpoint&lt;/strong&gt; responds. &lt;strong&gt;Playwright&lt;/strong&gt; connects. The page loads. The script can click and read the DOM.&lt;/p&gt;

&lt;p&gt;But the environment is wrong.&lt;/p&gt;

&lt;p&gt;You may see one or more of these symptoms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cookies&lt;/strong&gt; are empty or fewer than expected.&lt;/li&gt;
&lt;li&gt;The app redirects to login after refresh.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;localStorage&lt;/strong&gt; exists, but the app still treats the user as logged out.&lt;/li&gt;
&lt;li&gt;The account works manually but fails when attached through &lt;strong&gt;CDP&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The public IP does not match the expected &lt;strong&gt;proxy route&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The task works in visible mode but fails in &lt;strong&gt;headless mode&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;An &lt;strong&gt;AI browser agent&lt;/strong&gt; retries the task and marks it successful from a different context.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last case is especially easy to miss.&lt;/p&gt;

&lt;p&gt;A normal script often stops when the environment changes. An &lt;strong&gt;AI agent&lt;/strong&gt; may continue, adapt, and hide the real bug.&lt;/p&gt;

&lt;p&gt;So before debugging selectors, I try to prove one thing:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The script is running from the expected browser identity.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  First separate three persistence models
&lt;/h2&gt;

&lt;p&gt;Before debugging, name the model you are actually using.&lt;/p&gt;

&lt;p&gt;A lot of &lt;strong&gt;Playwright session bugs&lt;/strong&gt; become confusing because these three models are mixed together:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;storageState&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;launchPersistentContext&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;connectOverCDP&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They are related, but they are not interchangeable.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. storageState
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;storageState&lt;/strong&gt; is a snapshot.&lt;/p&gt;

&lt;p&gt;It is useful when auth is represented by &lt;strong&gt;cookies&lt;/strong&gt;, &lt;strong&gt;localStorage&lt;/strong&gt;, and sometimes &lt;strong&gt;IndexedDB&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It is not the same thing as a full &lt;strong&gt;browser profile&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Good fit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;test accounts&lt;/li&gt;
&lt;li&gt;repeatable login setup&lt;/li&gt;
&lt;li&gt;CI workflows&lt;/li&gt;
&lt;li&gt;clean browser contexts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Risky assumption:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My real Chrome profile is logged in, so storageState must contain everything.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It may not.&lt;/p&gt;

&lt;p&gt;Some apps also involve &lt;strong&gt;sessionStorage&lt;/strong&gt;, service workers, extensions, device-bound state, or app-specific storage.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. launchPersistentContext
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;launchPersistentContext&lt;/strong&gt; starts a browser with a specific &lt;strong&gt;user data directory&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That directory stores browser session data such as &lt;strong&gt;cookies&lt;/strong&gt; and &lt;strong&gt;localStorage&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Good fit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;long-lived automation profile&lt;/li&gt;
&lt;li&gt;local debugging&lt;/li&gt;
&lt;li&gt;workflows where the profile directory is the source of truth&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Risky assumption:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I can point multiple browser instances at the same profile directory.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Do not do that.&lt;/p&gt;

&lt;p&gt;Treat the &lt;strong&gt;user data directory&lt;/strong&gt; like a lockable database. One browser owns it during the run.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. connectOverCDP
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;connectOverCDP&lt;/strong&gt; attaches Playwright to an existing &lt;strong&gt;Chromium&lt;/strong&gt; browser over the &lt;strong&gt;Chrome DevTools Protocol&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Good fit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;attaching to a browser started by another tool&lt;/li&gt;
&lt;li&gt;debugging an already-open profile&lt;/li&gt;
&lt;li&gt;connecting automation to a manually inspected session&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Risky assumption:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CDP attachment gives me the exact same behavior as a Playwright-launched browser.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It often works well, but it is not the same connection model. When the bug involves &lt;strong&gt;cookies&lt;/strong&gt;, &lt;strong&gt;profiles&lt;/strong&gt;, or &lt;strong&gt;proxy context&lt;/strong&gt;, verify the attached browser before touching the task logic.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 1: start a dedicated browser profile
&lt;/h2&gt;

&lt;p&gt;Do not debug this against your daily Chrome profile.&lt;/p&gt;

&lt;p&gt;Use a dedicated automation directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;google-chrome &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--remote-debugging-port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;9222 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--user-data-dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/tmp/pw-cdp-profile &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--no-first-run&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then log in manually in that browser if the test requires an authenticated state.&lt;/p&gt;

&lt;p&gt;Checklist:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;userDataDir&lt;/strong&gt; is dedicated to this test.&lt;/li&gt;
&lt;li&gt;No other Chrome process is using the same directory.&lt;/li&gt;
&lt;li&gt;The browser was started with the expected &lt;strong&gt;remote debugging port&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The profile is not your personal default Chrome profile.&lt;/li&gt;
&lt;li&gt;The profile can be reopened manually and still shows the expected &lt;strong&gt;login state&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Validation standard:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The profile must be trusted manually before it is trusted by automation.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If the manual browser is already logged out, Playwright is not the problem yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: attach through CDP and inspect the actual context
&lt;/h2&gt;

&lt;p&gt;Run the smallest possible script before running your real workflow.&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;playwright&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&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="nx"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connectOverCDP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://127.0.0.1:9222&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contexts&lt;/span&gt;&lt;span class="p"&gt;()[&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;No browser context found. Check the CDP endpoint.&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cookies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;storage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluate&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="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;localStorageKeys&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;sessionStorageKeys&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sessionStorage&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="na"&gt;contextCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contexts&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;pageCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;cookieCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not skip this.&lt;/p&gt;

&lt;p&gt;Checklist:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;contextCount&lt;/strong&gt; is what you expected.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;pageCount&lt;/strong&gt; is what you expected.&lt;/li&gt;
&lt;li&gt;The script is using the expected tab or creates a new tab in the expected context.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;cookieCount&lt;/strong&gt; is not unexpectedly zero.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;localStorageKeys&lt;/strong&gt; match the app you logged into.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;sessionStorageKeys&lt;/strong&gt; are not the only place where auth appears to live.&lt;/li&gt;
&lt;li&gt;Reloading the page does not immediately redirect to login.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If this minimal script fails, the real workflow is irrelevant.&lt;/p&gt;

&lt;p&gt;The problem is the attached &lt;strong&gt;browser context&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: check whether cookies are actually the auth source
&lt;/h2&gt;

&lt;p&gt;A common debugging mistake is treating &lt;strong&gt;cookies&lt;/strong&gt; as the whole session.&lt;/p&gt;

&lt;p&gt;For many apps, that is not true.&lt;/p&gt;

&lt;p&gt;Check each layer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cookies&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;localStorage&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IndexedDB&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;sessionStorage&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;service worker state&lt;/li&gt;
&lt;li&gt;extension state&lt;/li&gt;
&lt;li&gt;app-specific device or session binding&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A useful question is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I explain the login state using only the data that Playwright can see?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use this snapshot for inspection, not as a blind copy-paste mechanism:&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;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;storageState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;indexedDB&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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="na"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cookie&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cookie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cookie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cookie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;secure&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cookie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;secure&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;sameSite&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cookie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sameSite&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;})),&lt;/span&gt;
  &lt;span class="na"&gt;origins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;origins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;origin&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;localStorageKeys&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that this logs metadata, not secret values.&lt;/p&gt;

&lt;p&gt;Checklist:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Required &lt;strong&gt;cookie domains&lt;/strong&gt; match the current URL.&lt;/li&gt;
&lt;li&gt;Required &lt;strong&gt;cookie paths&lt;/strong&gt; are not too narrow.&lt;/li&gt;
&lt;li&gt;Required cookies are not expired.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Secure&lt;/code&gt; cookies are used over HTTPS.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SameSite&lt;/code&gt; behavior makes sense for the redirect flow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IndexedDB&lt;/strong&gt; is checked when the app stores auth there.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;sessionStorage&lt;/strong&gt; is considered if login disappears across new tabs or reloads.&lt;/li&gt;
&lt;li&gt;No auth values are printed into CI logs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Validation standard:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do not call it a cookie bug until you know where the app stores auth.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: verify the proxy from inside the attached session
&lt;/h2&gt;

&lt;p&gt;The page loading is not enough.&lt;/p&gt;

&lt;p&gt;The page must load through the expected &lt;strong&gt;network identity&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Run an IP check from the same attached page:&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://api.ipify.org?format=json&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="na"&gt;waitUntil&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;domcontentloaded&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="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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;public ip:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;body&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For production systems, replace the public endpoint with your own internal IP echo service.&lt;/p&gt;

&lt;p&gt;Checklist:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Observed &lt;strong&gt;public IP&lt;/strong&gt; matches the expected proxy group.&lt;/li&gt;
&lt;li&gt;Observed &lt;strong&gt;IP region&lt;/strong&gt; matches the profile plan.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timezone&lt;/strong&gt;, &lt;strong&gt;locale&lt;/strong&gt;, and &lt;strong&gt;language&lt;/strong&gt; are consistent with that region.&lt;/li&gt;
&lt;li&gt;The proxy was configured at the layer you think it was configured at.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;CDP-attached browser&lt;/strong&gt; and the &lt;strong&gt;headless worker&lt;/strong&gt; do not use different proxy paths.&lt;/li&gt;
&lt;li&gt;Retry logic does not switch to another proxy silently.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Validation standard:&lt;/p&gt;

&lt;p&gt;A run is valid only when these match:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;profile ID&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;browser profile directory&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CDP endpoint&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;cookie state&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;storage state&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;proxy route&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IP region&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;execution mode&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the IP is wrong, stop debugging selectors.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: compare headed, CDP-attached, and headless behavior
&lt;/h2&gt;

&lt;p&gt;When a workflow works manually but fails in automation, compare the environments side by side.&lt;/p&gt;

&lt;p&gt;Use this matrix:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;Headed manual profile&lt;/th&gt;
&lt;th&gt;CDP-attached Playwright&lt;/th&gt;
&lt;th&gt;Headless run&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Same profile path&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cookie count&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;localStorage keys&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;IndexedDB needed&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Public IP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Proxy region&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Timezone&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Locale&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Landing URL after reload&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Login challenge shown&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The goal is not to make every number identical.&lt;/p&gt;

&lt;p&gt;The goal is to explain every difference.&lt;/p&gt;

&lt;p&gt;Red flags:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CDP-attached Playwright&lt;/strong&gt; has fewer cookies than manual mode.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Headless&lt;/strong&gt; uses a different proxy than headed mode.&lt;/li&gt;
&lt;li&gt;The same URL redirects differently in each mode.&lt;/li&gt;
&lt;li&gt;The profile path is different but the run log does not show it.&lt;/li&gt;
&lt;li&gt;The agent retry opens a fresh context instead of the attached context.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Validation standard:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A headed success does not prove headless readiness. A CDP connection does not prove profile correctness.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: add stop conditions before an AI agent continues
&lt;/h2&gt;

&lt;p&gt;This is where &lt;strong&gt;AI agent debugging&lt;/strong&gt; differs from script debugging.&lt;/p&gt;

&lt;p&gt;A deterministic script often fails loudly.&lt;/p&gt;

&lt;p&gt;An agent can adapt and continue.&lt;/p&gt;

&lt;p&gt;That is useful for UI variation, but dangerous for &lt;strong&gt;identity mismatch&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Before giving an agent a logged-in browser, require environment checks.&lt;/p&gt;

&lt;p&gt;Stop the agent when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;cookie count&lt;/strong&gt; drops unexpectedly.&lt;/li&gt;
&lt;li&gt;The page redirects to login.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;public IP&lt;/strong&gt; does not match the expected proxy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IP region&lt;/strong&gt; changes during retry.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;timezone&lt;/strong&gt; and proxy region disagree.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;localStorage&lt;/strong&gt; or &lt;strong&gt;IndexedDB&lt;/strong&gt; is empty when it should not be.&lt;/li&gt;
&lt;li&gt;The agent is not operating in the expected &lt;strong&gt;CDP-attached context&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The page enters wallet, payment, account settings, deletion, permission, or destructive flows.&lt;/li&gt;
&lt;li&gt;The agent cannot produce a run log that ties result to &lt;strong&gt;profile&lt;/strong&gt;, &lt;strong&gt;proxy&lt;/strong&gt;, &lt;strong&gt;time&lt;/strong&gt;, and &lt;strong&gt;mode&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A good agent run should answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Which profile did I use?&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Which browser context did I attach to?&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Which proxy route did I use?&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;What IP did the page observe?&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;What session state did I verify before acting?&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Why was I allowed to continue?&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;What would have made me stop?&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the agent cannot answer those, it should not act on a logged-in session.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 7: use a small run log for every debug attempt
&lt;/h2&gt;

&lt;p&gt;The log does not need to be fancy.&lt;/p&gt;

&lt;p&gt;It just needs to capture the identity of the run.&lt;/p&gt;

&lt;p&gt;Example fields:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;run_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2026-05-30-cdp-auth-debug-001&lt;/span&gt;
&lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cdp-attached&lt;/span&gt;
&lt;span class="na"&gt;cdp_endpoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://127.0.0.1:9222&lt;/span&gt;
&lt;span class="na"&gt;profile_label&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;qa-us-account-03&lt;/span&gt;
&lt;span class="na"&gt;user_data_dir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/tmp/pw-cdp-profile&lt;/span&gt;
&lt;span class="na"&gt;expected_proxy_region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;US&lt;/span&gt;
&lt;span class="na"&gt;observed_public_ip&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;203.0.113.10&lt;/span&gt;
&lt;span class="na"&gt;cookie_count_before&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;18&lt;/span&gt;
&lt;span class="na"&gt;cookie_count_after_reload&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;18&lt;/span&gt;
&lt;span class="na"&gt;local_storage_origins&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;https://app.example.test&lt;/span&gt;
&lt;span class="na"&gt;indexeddb_checked&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;landing_url_after_reload&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://app.example.test/dashboard&lt;/span&gt;
&lt;span class="na"&gt;agent_allowed_to_continue&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="na"&gt;stop_reason&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;proxy_region_not_verified&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This prevents vague debugging notes like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It worked locally but failed in the agent.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That sentence is not enough.&lt;/p&gt;

&lt;p&gt;A better note is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The &lt;strong&gt;CDP-attached&lt;/strong&gt; run used the expected profile and cookie count, but the observed IP did not match the expected &lt;strong&gt;proxy region&lt;/strong&gt;, so the agent was stopped before account actions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is debuggable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common causes and first checks
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Cause 1: wrong browser was attached
&lt;/h3&gt;

&lt;p&gt;Symptoms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;context.cookies()&lt;/code&gt; returns fewer cookies than expected.&lt;/li&gt;
&lt;li&gt;The page is logged out even though another Chrome window is logged in.&lt;/li&gt;
&lt;li&gt;The CDP port belongs to an older browser process.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;First checks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kill all test Chrome processes.&lt;/li&gt;
&lt;li&gt;Start one browser with one &lt;strong&gt;userDataDir&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Reopen the &lt;strong&gt;CDP endpoint&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Print &lt;code&gt;browser.contexts().length&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Print the current URL and cookie count before running actions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Cause 2: wrong profile directory
&lt;/h3&gt;

&lt;p&gt;Symptoms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Login works in one browser, but CDP sees a clean session.&lt;/li&gt;
&lt;li&gt;The app asks for first-time setup.&lt;/li&gt;
&lt;li&gt;The cookie jar is empty.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;First checks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Confirm the exact &lt;strong&gt;userDataDir&lt;/strong&gt; used at launch.&lt;/li&gt;
&lt;li&gt;Check &lt;code&gt;chrome://version&lt;/code&gt; manually.&lt;/li&gt;
&lt;li&gt;Do not use the daily default Chrome profile.&lt;/li&gt;
&lt;li&gt;Do not let two browser instances share the same directory.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Cause 3: auth is not only stored in cookies
&lt;/h3&gt;

&lt;p&gt;Symptoms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cookies exist, but the app still logs out.&lt;/li&gt;
&lt;li&gt;Login survives reload but not a new tab.&lt;/li&gt;
&lt;li&gt;OAuth callback succeeds but dashboard redirects back to login.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;First checks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Inspect &lt;strong&gt;localStorage&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Inspect &lt;strong&gt;IndexedDB&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Check &lt;strong&gt;sessionStorage&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Check service worker and extension dependencies.&lt;/li&gt;
&lt;li&gt;Verify cookie domain, path, &lt;code&gt;Secure&lt;/code&gt;, and &lt;code&gt;SameSite&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Cause 4: proxy context drifted
&lt;/h3&gt;

&lt;p&gt;Symptoms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The page loads but shows another region.&lt;/li&gt;
&lt;li&gt;Some accounts see different layouts.&lt;/li&gt;
&lt;li&gt;Verification prompts appear only in automation.&lt;/li&gt;
&lt;li&gt;Headless tasks produce different results from visible sessions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;First checks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Check public IP inside the attached page.&lt;/li&gt;
&lt;li&gt;Compare IP, region, timezone, and locale.&lt;/li&gt;
&lt;li&gt;Verify where the proxy is configured.&lt;/li&gt;
&lt;li&gt;Disable silent proxy failover during debugging.&lt;/li&gt;
&lt;li&gt;Record proxy identity with every run.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Cause 5: the AI agent retried from another context
&lt;/h3&gt;

&lt;p&gt;Symptoms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The first attempt fails.&lt;/li&gt;
&lt;li&gt;The retry succeeds.&lt;/li&gt;
&lt;li&gt;The result is not reproducible manually.&lt;/li&gt;
&lt;li&gt;Logs do not show which profile or proxy was used.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;First checks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Disable automatic retry.&lt;/li&gt;
&lt;li&gt;Require pre-run identity checks.&lt;/li&gt;
&lt;li&gt;Require post-run identity checks.&lt;/li&gt;
&lt;li&gt;Stop if the retry changes &lt;strong&gt;profile&lt;/strong&gt;, &lt;strong&gt;context&lt;/strong&gt;, &lt;strong&gt;proxy&lt;/strong&gt;, or &lt;strong&gt;mode&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Recovery checklist
&lt;/h2&gt;

&lt;p&gt;When the session or proxy context looks wrong, I use this order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Stop the workflow.&lt;/li&gt;
&lt;li&gt;Save screenshot, URL, cookie count, storage keys, and observed IP.&lt;/li&gt;
&lt;li&gt;Close the attached browser.&lt;/li&gt;
&lt;li&gt;Kill stale Chrome processes.&lt;/li&gt;
&lt;li&gt;Restart one browser with the expected &lt;strong&gt;userDataDir&lt;/strong&gt; and &lt;strong&gt;CDP port&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Manually confirm login state.&lt;/li&gt;
&lt;li&gt;Reattach Playwright.&lt;/li&gt;
&lt;li&gt;Re-run the minimal inspection script.&lt;/li&gt;
&lt;li&gt;Verify &lt;strong&gt;cookies&lt;/strong&gt;, &lt;strong&gt;localStorage&lt;/strong&gt;, &lt;strong&gt;IndexedDB&lt;/strong&gt;, and &lt;strong&gt;sessionStorage&lt;/strong&gt; assumptions.&lt;/li&gt;
&lt;li&gt;Verify &lt;strong&gt;proxy&lt;/strong&gt;, &lt;strong&gt;IP region&lt;/strong&gt;, &lt;strong&gt;timezone&lt;/strong&gt;, and &lt;strong&gt;locale&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Run one low-risk page read.&lt;/li&gt;
&lt;li&gt;Only then run the real workflow.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Do not recover by blindly copying cookies across unrelated profiles.&lt;/p&gt;

&lt;p&gt;That often creates a more confusing state than the original bug.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a trustworthy run looks like
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;Playwright CDP&lt;/strong&gt; run is trustworthy when you can prove:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Correct browser instance&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Correct CDP endpoint&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Correct browser profile&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Expected cookie count&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Expected storage origins&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Auth source understood&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Expected proxy route&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Expected IP region&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Expected timezone and locale&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Same behavior after reload&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Headed and headless differences explained&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AI agent stop conditions enabled&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Run evidence saved&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important shift is this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The browser opening is not proof.&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;The page loading is not proof.&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;The task completing is not proof.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For profile-based automation, the proof is that the task ran from the expected &lt;strong&gt;browser identity&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final note
&lt;/h2&gt;

&lt;p&gt;As soon as you manage more than a few profiles, this debugging problem becomes less about one script and more about operational visibility.&lt;/p&gt;

&lt;p&gt;You need to know which &lt;strong&gt;browser profile&lt;/strong&gt;, which &lt;strong&gt;session state&lt;/strong&gt;, which &lt;strong&gt;proxy route&lt;/strong&gt;, which &lt;strong&gt;execution mode&lt;/strong&gt;, and which &lt;strong&gt;agent decision&lt;/strong&gt; produced the result.&lt;/p&gt;

&lt;p&gt;A &lt;a href="https://web4browser.io/" rel="noopener noreferrer"&gt;profile-aware browser automation workspace&lt;/a&gt; can help keep those relationships visible, but the debugging principle stays the same:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before asking whether automation worked, verify which identity it worked from.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>playwright</category>
      <category>automation</category>
      <category>webdev</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
