<?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: web4browser</title>
    <description>The latest articles on DEV Community by web4browser (@web4browser).</description>
    <link>https://dev.to/web4browser</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%2F3915407%2F12faea94-0106-4571-86ca-0ab3e1a34829.png</url>
      <title>DEV Community: web4browser</title>
      <link>https://dev.to/web4browser</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/web4browser"/>
    <language>en</language>
    <item>
      <title>When a Playwright Script Should Become a Browser Skill</title>
      <dc:creator>web4browser</dc:creator>
      <pubDate>Wed, 27 May 2026 08:24:18 +0000</pubDate>
      <link>https://dev.to/web4browser/when-a-playwright-script-should-become-a-browser-skill-2jk2</link>
      <guid>https://dev.to/web4browser/when-a-playwright-script-should-become-a-browser-skill-2jk2</guid>
      <description>&lt;p&gt;Most browser automation starts in a simple way.&lt;/p&gt;

&lt;p&gt;You have a page to open.&lt;br&gt;
A button to click.&lt;br&gt;
A dashboard to check.&lt;br&gt;
A report to export.&lt;br&gt;
A screenshot to save.&lt;/p&gt;

&lt;p&gt;So you write a Playwright script.&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;launch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;headless&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&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="k"&gt;await&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;newPage&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;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://example.com/dashboard&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;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text=Export&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;screenshot&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;result.png&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;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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a small task, this is perfect.&lt;/p&gt;

&lt;p&gt;The problem starts later, when the script depends on things that are not visible in the code.&lt;/p&gt;

&lt;p&gt;Which &lt;strong&gt;account&lt;/strong&gt; was logged in?&lt;br&gt;
Which &lt;strong&gt;browser profile&lt;/strong&gt; was used?&lt;br&gt;
Which &lt;strong&gt;proxy&lt;/strong&gt; was active?&lt;br&gt;
Was the session fresh, or restored from yesterday?&lt;br&gt;
Was the script allowed to submit a form, or only inspect the page?&lt;br&gt;
What should happen if a verification prompt appears?&lt;/p&gt;

&lt;p&gt;At that point, the automation is no longer just a script.&lt;/p&gt;

&lt;p&gt;It has become a workflow.&lt;/p&gt;

&lt;p&gt;And in many teams, that workflow should become a &lt;strong&gt;browser skill&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  A script is fine until the workflow starts remembering things
&lt;/h2&gt;

&lt;p&gt;A simple script controls the browser.&lt;/p&gt;

&lt;p&gt;A real workflow remembers context.&lt;/p&gt;

&lt;p&gt;That context might include &lt;strong&gt;login state&lt;/strong&gt;, &lt;strong&gt;cookies&lt;/strong&gt;, &lt;strong&gt;local storage&lt;/strong&gt;, &lt;strong&gt;extension state&lt;/strong&gt;, &lt;strong&gt;proxy region&lt;/strong&gt;, &lt;strong&gt;account ownership&lt;/strong&gt;, &lt;strong&gt;previous failures&lt;/strong&gt;, &lt;strong&gt;review rules&lt;/strong&gt;, and &lt;strong&gt;output evidence&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Those are not small implementation details.&lt;/p&gt;

&lt;p&gt;They decide whether the automation is safe to run, repeatable, and understandable by someone other than the original author.&lt;/p&gt;

&lt;p&gt;A script like this may look harmless:&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://example.com/account&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;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text=Settings&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;screenshot&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;settings.png&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;But the code does not answer the operational questions.&lt;/p&gt;

&lt;p&gt;Which account is this?&lt;br&gt;
Is this account allowed to open settings?&lt;br&gt;
Is the current IP expected for this account?&lt;br&gt;
Should the script stop if it sees a security page?&lt;br&gt;
Where should the evidence be saved?&lt;br&gt;
Who reviews the result?&lt;/p&gt;

&lt;p&gt;When these questions matter, the browser task needs a stronger structure than a loose script file.&lt;/p&gt;
&lt;h2&gt;
  
  
  The first signal is repeated manual setup
&lt;/h2&gt;

&lt;p&gt;The first signal is usually not technical.&lt;/p&gt;

&lt;p&gt;It is human.&lt;/p&gt;

&lt;p&gt;Before running the script, someone has to prepare the browser manually.&lt;/p&gt;

&lt;p&gt;They open the right profile.&lt;br&gt;
They check the proxy.&lt;br&gt;
They confirm the account is still logged in.&lt;br&gt;
They make sure the extension is installed.&lt;br&gt;
They choose visible mode instead of headless mode.&lt;br&gt;
They paste a note into Slack explaining which account should be used.&lt;/p&gt;

&lt;p&gt;That setup is not separate from the automation.&lt;/p&gt;

&lt;p&gt;It is part of the automation.&lt;/p&gt;

&lt;p&gt;If the script only works after someone prepares the browser by hand, the preparation should be declared as part of the workflow.&lt;/p&gt;

&lt;p&gt;A browser skill should make setup explicit.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"requires"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"logged_in_session"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"browser_profile"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"persistent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"proxy_region"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"US"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"visible_mode_allowed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This does not make the automation more complicated.&lt;/p&gt;

&lt;p&gt;It makes the real complexity visible.&lt;/p&gt;

&lt;h2&gt;
  
  
  The second signal is account context
&lt;/h2&gt;

&lt;p&gt;Browser automation becomes much harder when one script is used across many accounts.&lt;/p&gt;

&lt;p&gt;A public scraping script can often be stateless.&lt;/p&gt;

&lt;p&gt;An &lt;strong&gt;account-aware&lt;/strong&gt; task cannot.&lt;/p&gt;

&lt;p&gt;Once the script needs to know which account, profile, proxy, and region belong together, those values should stop living in filenames, comments, spreadsheets, or memory.&lt;/p&gt;

&lt;p&gt;They should become structured inputs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"account_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"acct_us_018"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"profile_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"profile_us_018"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"proxy_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"proxy_us_dallas_02"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"expected_region"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"US"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"task"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"check_dashboard_status"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is one of the clearest signs that a script should become a skill.&lt;/p&gt;

&lt;p&gt;A skill does not just say, “open this page.”&lt;/p&gt;

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

&lt;p&gt;Use this account.&lt;br&gt;
Use this browser profile.&lt;br&gt;
Use this proxy context.&lt;br&gt;
Run this allowed operation.&lt;br&gt;
Stop if the identity boundary looks wrong.&lt;br&gt;
Save evidence in a predictable place.&lt;/p&gt;

&lt;p&gt;That is especially important for &lt;strong&gt;multi-account automation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Without account context, two runs of the same script may produce completely different risk profiles.&lt;/p&gt;

&lt;p&gt;One run may be a harmless dashboard check.&lt;/p&gt;

&lt;p&gt;Another may be a sensitive action inside the wrong account.&lt;/p&gt;

&lt;p&gt;The code may be identical.&lt;/p&gt;

&lt;p&gt;The context is not.&lt;/p&gt;
&lt;h2&gt;
  
  
  The third signal is failure handling
&lt;/h2&gt;

&lt;p&gt;One-off scripts usually fail loudly.&lt;/p&gt;

&lt;p&gt;They throw an error, exit, and leave the operator to figure out what happened.&lt;/p&gt;

&lt;p&gt;That is fine for local experiments.&lt;/p&gt;

&lt;p&gt;It is not enough for recurring browser workflows.&lt;/p&gt;

&lt;p&gt;A reusable browser skill should know which failures are &lt;strong&gt;retryable&lt;/strong&gt;, which failures require a &lt;strong&gt;hard stop&lt;/strong&gt;, and which failures need &lt;strong&gt;human review&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"retry_on"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"timeout"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"temporary_5xx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"navigation_interrupted"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"stop_on"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"login_required"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"verification_prompt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"proxy_region_mismatch"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"review_required_on"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"payment_page"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"security_settings"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"wallet_action"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where many automation projects quietly become fragile.&lt;/p&gt;

&lt;p&gt;The team keeps adding &lt;code&gt;try/catch&lt;/code&gt; blocks.&lt;/p&gt;

&lt;p&gt;Then it adds screenshots.&lt;/p&gt;

&lt;p&gt;Then it adds retries.&lt;/p&gt;

&lt;p&gt;Then it adds a message to notify someone.&lt;/p&gt;

&lt;p&gt;Then it adds special cases for login pages, captchas, blocked accounts, changed selectors, and unexpected redirects.&lt;/p&gt;

&lt;p&gt;Eventually, the script is not just automating a browser anymore.&lt;/p&gt;

&lt;p&gt;It is carrying operational policy.&lt;/p&gt;

&lt;p&gt;That policy should be declared clearly.&lt;/p&gt;

&lt;p&gt;A browser skill makes failure behavior part of the task definition, not an accidental pile of exception handling.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fourth signal is shared use by a team
&lt;/h2&gt;

&lt;p&gt;A personal script can depend on personal memory.&lt;/p&gt;

&lt;p&gt;A team workflow cannot.&lt;/p&gt;

&lt;p&gt;If only one developer knows how to run a script, it is not really reusable.&lt;/p&gt;

&lt;p&gt;This becomes obvious when someone asks:&lt;/p&gt;

&lt;p&gt;Which profile should I use?&lt;br&gt;
Can this run headless?&lt;br&gt;
What does success look like?&lt;br&gt;
Where are screenshots saved?&lt;br&gt;
Can this task click submit?&lt;br&gt;
Who checks the output?&lt;br&gt;
What should I do if login expires?&lt;/p&gt;

&lt;p&gt;If the answer lives in someone’s head, the automation has a handoff problem.&lt;/p&gt;

&lt;p&gt;A browser skill should make these parts obvious:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Inputs
Allowed actions
Blocked actions
Expected browser state
Stop conditions
Evidence saved
Reviewer
Completion rule
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is not bureaucracy.&lt;/p&gt;

&lt;p&gt;It is how browser automation becomes safe enough for repeated use.&lt;/p&gt;

&lt;p&gt;The more accounts, profiles, proxies, and operators a team has, the less it can rely on informal knowledge.&lt;/p&gt;

&lt;h2&gt;
  
  
  A browser skill is not a bigger script
&lt;/h2&gt;

&lt;p&gt;A browser skill is not just a longer Playwright file.&lt;/p&gt;

&lt;p&gt;It is a reusable operation with &lt;strong&gt;inputs&lt;/strong&gt;, &lt;strong&gt;rules&lt;/strong&gt;, &lt;strong&gt;outputs&lt;/strong&gt;, and &lt;strong&gt;boundaries&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A useful way to separate the layers is this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Script:
Controls the browser.

Skill:
Defines a reusable browser operation.

Agent:
Chooses or orchestrates skills.

Tool layer:
Exposes skills to external systems.

Workspace:
Keeps accounts, profiles, proxies, logs, and review states connected.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This distinction matters.&lt;/p&gt;

&lt;p&gt;A script might say:&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;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text=Export&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;A skill should define whether exporting is allowed, which account is being used, where the file goes, what evidence is saved, and when the task should stop.&lt;/p&gt;

&lt;p&gt;For many teams, the missing layer is not another automation library.&lt;/p&gt;

&lt;p&gt;It is an operating layer around browser work.&lt;/p&gt;

&lt;p&gt;At that point, many teams need more than a script folder. They need &lt;a href="https://web4browser.io/" rel="noopener noreferrer"&gt;a browser automation workspace for multi-account teams&lt;/a&gt; where profiles, proxies, task rules, and execution logs stay connected.&lt;/p&gt;

&lt;p&gt;The goal is not to make every small script enterprise-grade.&lt;/p&gt;

&lt;p&gt;The goal is to promote the right scripts into reusable skills before they become unreviewable automation debt.&lt;/p&gt;

&lt;h2&gt;
  
  
  A minimal browser skill template
&lt;/h2&gt;

&lt;p&gt;A browser skill does not need to start as a huge framework.&lt;/p&gt;

&lt;p&gt;A small template is often enough.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"skill_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"check_account_dashboard"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Open an account dashboard, inspect status, capture evidence, and write a summary."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"inputs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"account_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"profile_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"proxy_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"target_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"required"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"allowed_actions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"open_page"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"inspect_status"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"capture_screenshot"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"write_summary"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"blocked_actions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"change_password"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"submit_payment"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"edit_security_settings"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"approve_transaction"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"preflight_checks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"profile_exists"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"proxy_region_matches_account"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"session_available"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"stop_conditions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"login_required"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"verification_prompt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"proxy_region_mismatch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"unexpected_account"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"outputs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"status_summary"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"screenshot_path"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"execution_log"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"review_flag"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This template does something important.&lt;/p&gt;

&lt;p&gt;It separates browser control from workflow intent.&lt;/p&gt;

&lt;p&gt;The Playwright code can still do the actual page operations.&lt;/p&gt;

&lt;p&gt;But the skill definition explains what the operation is allowed to do, what it must not do, and what evidence it must leave behind.&lt;/p&gt;

&lt;p&gt;That makes the automation easier to review.&lt;/p&gt;

&lt;p&gt;It also makes it easier for an &lt;strong&gt;AI agent&lt;/strong&gt; or orchestration layer to call the task safely.&lt;/p&gt;

&lt;h2&gt;
  
  
  A simple Playwright wrapper can be enough
&lt;/h2&gt;

&lt;p&gt;You do not need to rebuild everything on day one.&lt;/p&gt;

&lt;p&gt;A practical starting point is to wrap your Playwright function with a skill runner.&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;runDashboardCheck&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;evidence&lt;/span&gt; &lt;span class="p"&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;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;targetUrl&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="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;getByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Verify your identity&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isVisible&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;review_required&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;verification_prompt&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;title&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;title&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;screenshotPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;evidence&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-dashboard.png`&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;screenshot&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;screenshotPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fullPage&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;completed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;screenshotPath&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;Then keep the operational rules outside the function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"account"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"acct_us_018"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"targetUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://example.com/dashboard"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"expectedRegion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"US"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"evidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dir"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./runs/2026-05-27/acct_us_018"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"rules"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"stopOnVerification"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"allowSensitiveActions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is still simple.&lt;/p&gt;

&lt;p&gt;But it is already better than a script that silently assumes everything is safe.&lt;/p&gt;

&lt;p&gt;The runner can check inputs.&lt;br&gt;
The skill can return structured results.&lt;br&gt;
The operator can inspect evidence.&lt;br&gt;
The team can reuse the same operation across accounts.&lt;/p&gt;

&lt;p&gt;That is the path from script to skill.&lt;/p&gt;
&lt;h2&gt;
  
  
  When the script should stay a script
&lt;/h2&gt;

&lt;p&gt;Not every automation task needs to become a skill.&lt;/p&gt;

&lt;p&gt;Some scripts should stay simple.&lt;/p&gt;

&lt;p&gt;A script is usually enough when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The page is public&lt;/li&gt;
&lt;li&gt;No login state is required&lt;/li&gt;
&lt;li&gt;No account identity is involved&lt;/li&gt;
&lt;li&gt;No proxy or region mapping matters&lt;/li&gt;
&lt;li&gt;No sensitive action can be triggered&lt;/li&gt;
&lt;li&gt;The task is deterministic&lt;/li&gt;
&lt;li&gt;The output is only used locally&lt;/li&gt;
&lt;li&gt;The script is owned and used by one person&lt;/li&gt;
&lt;li&gt;Failure does not require review evidence&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, a simple public page health check may not need a skill definition.&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;response&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;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://example.com/status&lt;/span&gt;&lt;span class="dl"&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Turning every small script into a formal workflow creates its own overhead.&lt;/p&gt;

&lt;p&gt;The point is not to over-engineer browser automation.&lt;/p&gt;

&lt;p&gt;The point is to notice when a script has already become a workflow, even if the codebase has not admitted it yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  When it should become a skill
&lt;/h2&gt;

&lt;p&gt;A script should probably become a browser skill when several of these are true:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The same task runs daily or weekly&lt;/li&gt;
&lt;li&gt;The task runs across multiple accounts&lt;/li&gt;
&lt;li&gt;Each account has a dedicated browser profile&lt;/li&gt;
&lt;li&gt;Each profile has a proxy or region expectation&lt;/li&gt;
&lt;li&gt;The task may encounter login, verification, or security screens&lt;/li&gt;
&lt;li&gt;The task has actions that should be blocked&lt;/li&gt;
&lt;li&gt;A human may need to review the output&lt;/li&gt;
&lt;li&gt;Screenshots or logs are required as evidence&lt;/li&gt;
&lt;li&gt;Non-authors need to run the automation&lt;/li&gt;
&lt;li&gt;An AI agent needs to call the task&lt;/li&gt;
&lt;li&gt;The task switches between headless and visible mode&lt;/li&gt;
&lt;li&gt;The result must be saved into a team record&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The more boxes you check, the less your automation is just a script.&lt;/p&gt;

&lt;p&gt;It is an operational unit.&lt;/p&gt;

&lt;p&gt;That unit deserves a name, inputs, boundaries, and outputs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The best automation is boring to repeat
&lt;/h2&gt;

&lt;p&gt;Good browser automation is not only about making the browser move.&lt;/p&gt;

&lt;p&gt;It is about making the same browser task safe to repeat.&lt;/p&gt;

&lt;p&gt;That means the &lt;strong&gt;account&lt;/strong&gt; is known.&lt;br&gt;
The &lt;strong&gt;profile&lt;/strong&gt; is known.&lt;br&gt;
The &lt;strong&gt;proxy context&lt;/strong&gt; is known.&lt;br&gt;
The &lt;strong&gt;allowed actions&lt;/strong&gt; are known.&lt;br&gt;
The &lt;strong&gt;stop conditions&lt;/strong&gt; are known.&lt;br&gt;
The &lt;strong&gt;evidence&lt;/strong&gt; is saved.&lt;br&gt;
The &lt;strong&gt;result&lt;/strong&gt; can be reviewed.&lt;/p&gt;

&lt;p&gt;A Playwright script is a great starting point.&lt;/p&gt;

&lt;p&gt;But once the workflow depends on &lt;strong&gt;account identity&lt;/strong&gt;, &lt;strong&gt;browser state&lt;/strong&gt;, &lt;strong&gt;proxy mapping&lt;/strong&gt;, &lt;strong&gt;review rules&lt;/strong&gt;, and &lt;strong&gt;repeatable evidence&lt;/strong&gt;, it should become something more durable.&lt;/p&gt;

&lt;p&gt;Keep simple scripts simple.&lt;/p&gt;

&lt;p&gt;Promote repeated account-aware tasks into browser skills.&lt;/p&gt;

&lt;p&gt;And make browser state, task boundaries, and execution logs first-class parts of the automation system.&lt;/p&gt;

&lt;p&gt;For more practical notes on browser profiles, proxy checks, MCP workflows, and account-aware automation, see these &lt;a href="https://web4browser.io/blog/" rel="noopener noreferrer"&gt;more browser automation and profile workflow notes&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>playwright</category>
      <category>automation</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Designing a Recovery Model for AI Browser Agents</title>
      <dc:creator>web4browser</dc:creator>
      <pubDate>Sat, 23 May 2026 03:22:08 +0000</pubDate>
      <link>https://dev.to/web4browser/designing-a-recovery-model-for-ai-browser-agents-gg8</link>
      <guid>https://dev.to/web4browser/designing-a-recovery-model-for-ai-browser-agents-gg8</guid>
      <description>&lt;p&gt;AI browser agents do not fail the same way traditional automation scripts fail.&lt;/p&gt;

&lt;p&gt;A normal script usually fails loudly.&lt;/p&gt;

&lt;p&gt;A selector is missing. A page times out. A proxy returns an error. An assertion does not match. A browser context crashes.&lt;/p&gt;

&lt;p&gt;Those failures are frustrating, but they are visible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI browser agents&lt;/strong&gt; create a quieter kind of risk. They may continue after making the wrong interpretation. They may click a valid button in the wrong account. They may retry an action that should have been stopped. They may finish a workflow while leaving no clear evidence for the next operator to review.&lt;/p&gt;

&lt;p&gt;That is why the real question is not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How do we make the agent retry?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The better question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When is it safe for the agent to continue?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For &lt;strong&gt;account-aware browser automation&lt;/strong&gt;, recovery is not just a retry loop. It is a decision system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Browser Agents Do Not Fail Like Normal Scripts
&lt;/h2&gt;

&lt;p&gt;Traditional browser automation is usually deterministic.&lt;/p&gt;

&lt;p&gt;You write the steps. The script follows them. The failure happens when the page no longer matches what the script expected.&lt;/p&gt;

&lt;p&gt;Common failures include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Selector not found&lt;/li&gt;
&lt;li&gt;Request timeout&lt;/li&gt;
&lt;li&gt;Proxy authentication error&lt;/li&gt;
&lt;li&gt;Page load failure&lt;/li&gt;
&lt;li&gt;Assertion mismatch&lt;/li&gt;
&lt;li&gt;Browser crash&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These errors are not pleasant, but they are usually easy to classify.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI browser agents&lt;/strong&gt; are different because they make decisions during execution.&lt;/p&gt;

&lt;p&gt;They read the page. They infer intent. They choose the next action. They may adapt when the layout changes.&lt;/p&gt;

&lt;p&gt;That flexibility is useful, but it also creates softer failure modes.&lt;/p&gt;

&lt;p&gt;An AI browser agent can fail by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reading the right page but reaching the wrong conclusion&lt;/li&gt;
&lt;li&gt;Clicking a valid button in the wrong workflow&lt;/li&gt;
&lt;li&gt;Continuing under the wrong &lt;strong&gt;browser profile&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Using the right task with the wrong account&lt;/li&gt;
&lt;li&gt;Retrying a form submission that already succeeded&lt;/li&gt;
&lt;li&gt;Treating a verification page as a temporary obstacle&lt;/li&gt;
&lt;li&gt;Completing the task without enough reviewable evidence&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The dangerous failures are not always the loud ones.&lt;/p&gt;

&lt;p&gt;They are the plausible ones.&lt;/p&gt;

&lt;p&gt;A timeout is obvious. A wrong-account action may look normal until much later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recovery Starts Before the Failure
&lt;/h2&gt;

&lt;p&gt;A recovery model should not begin when the task breaks.&lt;/p&gt;

&lt;p&gt;It should begin before the task starts.&lt;/p&gt;

&lt;p&gt;Before an AI browser agent acts, the system needs enough context to decide whether a later recovery action is safe.&lt;/p&gt;

&lt;p&gt;At minimum, each task should know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which &lt;strong&gt;browser profile&lt;/strong&gt; is expected&lt;/li&gt;
&lt;li&gt;Which &lt;strong&gt;account label&lt;/strong&gt; is expected&lt;/li&gt;
&lt;li&gt;Which &lt;strong&gt;proxy region&lt;/strong&gt; is expected&lt;/li&gt;
&lt;li&gt;Which domain is allowed&lt;/li&gt;
&lt;li&gt;What the task is allowed to do&lt;/li&gt;
&lt;li&gt;Which actions are blocked&lt;/li&gt;
&lt;li&gt;Which events require review&lt;/li&gt;
&lt;li&gt;What output counts as success&lt;/li&gt;
&lt;li&gt;When the agent must stop&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A simple task contract might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"profile_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"profile_us_042"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"account_label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ads-review-us-03"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"proxy_region"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"US"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"allowed_domains"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"example.com"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"task_goal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"check dashboard status"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"allowed_actions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"open_page"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"read_status"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"capture_result"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"blocked_actions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"change_password"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"submit_payment"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"delete_data"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"review_required_if"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"login_prompt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"captcha"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"unexpected_account"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"region_mismatch"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is not just metadata.&lt;/p&gt;

&lt;p&gt;It is the boundary that tells the agent what kind of recovery is allowed.&lt;/p&gt;

&lt;p&gt;If the task is read-only, a retry may be safe. If the task changes account state, retrying may create damage. If the account identity is uncertain, the agent should not continue.&lt;/p&gt;

&lt;p&gt;This is also why some teams are moving from loose scripts toward an &lt;a href="https://web4browser.io/" rel="noopener noreferrer"&gt;account-aware browser workspace&lt;/a&gt;, where profiles, proxies, task rules, and logs are managed together instead of being scattered across scripts and local folders.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate Retryable Failures From Stop Conditions
&lt;/h2&gt;

&lt;p&gt;Not every failure deserves the same response.&lt;/p&gt;

&lt;p&gt;Some failures are safe to retry. Some failures should stop the agent immediately.&lt;/p&gt;

&lt;p&gt;The mistake is treating both groups as generic &lt;strong&gt;automation errors&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;They are not the same.&lt;/p&gt;

&lt;h3&gt;
  
  
  Retryable failures
&lt;/h3&gt;

&lt;p&gt;A failure may be retryable when the &lt;strong&gt;account context&lt;/strong&gt; is still trusted and no sensitive action has occurred.&lt;/p&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Temporary timeout&lt;/li&gt;
&lt;li&gt;Page load failure&lt;/li&gt;
&lt;li&gt;Network reset&lt;/li&gt;
&lt;li&gt;Tab crash before action&lt;/li&gt;
&lt;li&gt;Read-only dashboard check failed&lt;/li&gt;
&lt;li&gt;Agent lost focus before clicking&lt;/li&gt;
&lt;li&gt;Screenshot capture failed&lt;/li&gt;
&lt;li&gt;Non-sensitive status check returned empty&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In these cases, the system can often retry once, reload the page, reopen the tab, or restart the browser context.&lt;/p&gt;

&lt;p&gt;But the retry should still be limited.&lt;/p&gt;

&lt;p&gt;A safe retry policy should answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How many retries are allowed?&lt;/li&gt;
&lt;li&gt;Was the task read-only?&lt;/li&gt;
&lt;li&gt;Did the agent submit anything?&lt;/li&gt;
&lt;li&gt;Is the profile still correct?&lt;/li&gt;
&lt;li&gt;Is the account still correct?&lt;/li&gt;
&lt;li&gt;Is the proxy still correct?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;A retry is safe only when the account context is still trusted.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Stop conditions
&lt;/h3&gt;

&lt;p&gt;Some events should not be retried automatically.&lt;/p&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Login required&lt;/li&gt;
&lt;li&gt;CAPTCHA challenge&lt;/li&gt;
&lt;li&gt;Verification prompt&lt;/li&gt;
&lt;li&gt;Wrong account detected&lt;/li&gt;
&lt;li&gt;Unexpected user profile&lt;/li&gt;
&lt;li&gt;Proxy region mismatch&lt;/li&gt;
&lt;li&gt;Password page opened&lt;/li&gt;
&lt;li&gt;Payment page opened&lt;/li&gt;
&lt;li&gt;Account settings page opened&lt;/li&gt;
&lt;li&gt;Cookie or storage mismatch&lt;/li&gt;
&lt;li&gt;Identity signal is uncertain&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are not normal errors.&lt;/p&gt;

&lt;p&gt;They are &lt;strong&gt;trust boundary events&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When they appear, the question is no longer “Can the agent continue?”&lt;/p&gt;

&lt;p&gt;The question becomes “Do we still trust the current browser context?”&lt;/p&gt;

&lt;p&gt;If the answer is uncertain, the agent should stop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Recovery Levels Instead of One Retry Loop
&lt;/h2&gt;

&lt;p&gt;A single retry loop is too blunt for AI browser agents.&lt;/p&gt;

&lt;p&gt;A better model is to define &lt;strong&gt;recovery levels&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Each level gives the agent a different amount of freedom.&lt;/p&gt;

&lt;h2&gt;
  
  
  Level 0: Observe
&lt;/h2&gt;

&lt;p&gt;At this level, the agent does not change anything.&lt;/p&gt;

&lt;p&gt;It only collects evidence.&lt;/p&gt;

&lt;p&gt;Allowed actions include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Read current URL&lt;/li&gt;
&lt;li&gt;Read page title&lt;/li&gt;
&lt;li&gt;Inspect visible text&lt;/li&gt;
&lt;li&gt;Capture screenshot&lt;/li&gt;
&lt;li&gt;Save console errors&lt;/li&gt;
&lt;li&gt;Save network error summary&lt;/li&gt;
&lt;li&gt;Check whether the expected account label appears&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This level is useful when something looks wrong but the system does not yet know why.&lt;/p&gt;

&lt;p&gt;The agent should not click, submit, edit, or navigate deeply.&lt;/p&gt;

&lt;p&gt;The goal is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Understand the state before changing the state.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Level 1: Refresh
&lt;/h2&gt;

&lt;p&gt;At this level, the agent can perform light recovery.&lt;/p&gt;

&lt;p&gt;Allowed actions include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reload the page&lt;/li&gt;
&lt;li&gt;Wait again&lt;/li&gt;
&lt;li&gt;Reopen the tab&lt;/li&gt;
&lt;li&gt;Repeat a read-only check&lt;/li&gt;
&lt;li&gt;Re-run a harmless status inspection&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This level is usually safe for dashboards, reports, monitoring pages, and non-sensitive reads.&lt;/p&gt;

&lt;p&gt;But it should still be limited.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"recovery_level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"allowed_attempts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"allowed_actions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"reload_page"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"repeat_read_only_check"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"stop_if"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"login_prompt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"captcha"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"account_changed"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key rule is that &lt;strong&gt;Level 1 should not repeat state-changing actions&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Refreshing a failed dashboard read is different from resubmitting a payment form.&lt;/p&gt;

&lt;h2&gt;
  
  
  Level 2: Rebuild Context
&lt;/h2&gt;

&lt;p&gt;At this level, the system rebuilds the browser environment before continuing.&lt;/p&gt;

&lt;p&gt;This may include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Relaunching the browser profile&lt;/li&gt;
&lt;li&gt;Rebinding the proxy&lt;/li&gt;
&lt;li&gt;Rechecking IP region&lt;/li&gt;
&lt;li&gt;Rechecking timezone&lt;/li&gt;
&lt;li&gt;Rechecking locale&lt;/li&gt;
&lt;li&gt;Reloading storage state&lt;/li&gt;
&lt;li&gt;Verifying the account label&lt;/li&gt;
&lt;li&gt;Reopening the task from a clean entry point&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This level is useful when the environment may have drifted.&lt;/p&gt;

&lt;p&gt;For example, the page may have failed because the proxy changed, the browser state became stale, or the account session no longer matches the expected profile.&lt;/p&gt;

&lt;p&gt;But &lt;strong&gt;Level 2 should be stricter than Level 1&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Before continuing, the system should verify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Expected profile&lt;/li&gt;
&lt;li&gt;Expected account&lt;/li&gt;
&lt;li&gt;Expected proxy region&lt;/li&gt;
&lt;li&gt;Expected domain&lt;/li&gt;
&lt;li&gt;Expected session state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If one of those checks fails, the agent should not continue the workflow.&lt;/p&gt;

&lt;p&gt;It should escalate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Level 3: Human Review
&lt;/h2&gt;

&lt;p&gt;Some situations should always require human review.&lt;/p&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Login challenge&lt;/li&gt;
&lt;li&gt;CAPTCHA&lt;/li&gt;
&lt;li&gt;Account risk warning&lt;/li&gt;
&lt;li&gt;Unexpected permission screen&lt;/li&gt;
&lt;li&gt;Payment confirmation&lt;/li&gt;
&lt;li&gt;Password change page&lt;/li&gt;
&lt;li&gt;Account deletion page&lt;/li&gt;
&lt;li&gt;Wrong user detected&lt;/li&gt;
&lt;li&gt;Sensitive setting opened&lt;/li&gt;
&lt;li&gt;Agent cannot explain what happened&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At this level, the agent should stop and prepare a review package.&lt;/p&gt;

&lt;p&gt;That package should include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Screenshot&lt;/li&gt;
&lt;li&gt;Current URL&lt;/li&gt;
&lt;li&gt;Profile ID&lt;/li&gt;
&lt;li&gt;Account label&lt;/li&gt;
&lt;li&gt;Proxy region&lt;/li&gt;
&lt;li&gt;Last successful action&lt;/li&gt;
&lt;li&gt;Failed action&lt;/li&gt;
&lt;li&gt;Recovery attempts already used&lt;/li&gt;
&lt;li&gt;Reason for stopping&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A useful stop event might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"event"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"human_review_required"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"unexpected_account"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"profile_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"profile_us_042"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"expected_account"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ads-review-us-03"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"observed_account"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ads-review-us-07"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"last_action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"opened_dashboard"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"recovery_attempts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"next_action_blocked"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The higher the recovery level, the less the agent should decide alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Logs Should Explain Why the Agent Continued
&lt;/h2&gt;

&lt;p&gt;Many automation logs are only useful after a crash.&lt;/p&gt;

&lt;p&gt;They tell you what failed. They do not tell you why the system believed it was safe to continue.&lt;/p&gt;

&lt;p&gt;AI browser agents need better logs.&lt;/p&gt;

&lt;p&gt;A useful recovery log should explain the decision.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"event"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"recovery_decision"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"failure"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"page_timeout"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"context_verified"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"profile_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"profile_us_042"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"account_label_checked"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"proxy_region_checked"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"task_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"read_only"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"decision"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"retry_once"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dashboard read failed before any state-changing action"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This kind of log helps the next operator decide whether to trust the result.&lt;/p&gt;

&lt;p&gt;It also helps debug agent behavior over time.&lt;/p&gt;

&lt;p&gt;Bad log:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Retrying because page timed out.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Better log:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Retrying once because the task is read-only, no submit action occurred, and profile/account/proxy checks still match the task contract.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The difference matters.&lt;/p&gt;

&lt;p&gt;The first log records an error. The second log records judgment.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Browser Profile Is Part of the Runtime
&lt;/h2&gt;

&lt;p&gt;In traditional automation, the runtime is usually understood as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Code&lt;/li&gt;
&lt;li&gt;Browser&lt;/li&gt;
&lt;li&gt;Page&lt;/li&gt;
&lt;li&gt;Network&lt;/li&gt;
&lt;li&gt;Test runner&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For &lt;strong&gt;account-aware browser agents&lt;/strong&gt;, that model is incomplete.&lt;/p&gt;

&lt;p&gt;The browser profile is also part of the runtime.&lt;/p&gt;

&lt;p&gt;So are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cookies&lt;/li&gt;
&lt;li&gt;Local storage&lt;/li&gt;
&lt;li&gt;Fingerprint settings&lt;/li&gt;
&lt;li&gt;Proxy mapping&lt;/li&gt;
&lt;li&gt;IP region&lt;/li&gt;
&lt;li&gt;Timezone&lt;/li&gt;
&lt;li&gt;Locale&lt;/li&gt;
&lt;li&gt;Account label&lt;/li&gt;
&lt;li&gt;Task history&lt;/li&gt;
&lt;li&gt;Recovery logs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If those pieces drift apart, the agent may still run, but it may no longer be operating in the right identity context.&lt;/p&gt;

&lt;p&gt;That is why AI browser automation should not treat profiles as passive folders.&lt;/p&gt;

&lt;p&gt;A profile is not just where the session is stored.&lt;/p&gt;

&lt;p&gt;It is the &lt;strong&gt;identity boundary&lt;/strong&gt; of the task.&lt;/p&gt;

&lt;p&gt;For a deeper breakdown of this problem, see &lt;a href="https://web4browser.io/blog/32.html" rel="noopener noreferrer"&gt;why browser automation fails without account context&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Practical Recovery Checklist
&lt;/h2&gt;

&lt;p&gt;Before an AI browser agent retries, the system should ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is the current profile still the expected profile?&lt;/li&gt;
&lt;li&gt;Is the current account still the expected account?&lt;/li&gt;
&lt;li&gt;Is the proxy still mapped to the expected region?&lt;/li&gt;
&lt;li&gt;Is the current domain allowed for this task?&lt;/li&gt;
&lt;li&gt;Did the agent submit anything before failing?&lt;/li&gt;
&lt;li&gt;Is the next action read-only or state-changing?&lt;/li&gt;
&lt;li&gt;Would repeating the action create duplicate changes?&lt;/li&gt;
&lt;li&gt;Has the page shown a login, CAPTCHA, or verification prompt?&lt;/li&gt;
&lt;li&gt;Is there enough evidence for review?&lt;/li&gt;
&lt;li&gt;Can the agent explain why continuing is safe?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If any answer is uncertain, the agent should pause.&lt;/p&gt;

&lt;p&gt;That may sound conservative, but it is usually cheaper than cleaning up a wrong-account action later.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Simple Recovery Policy Template
&lt;/h2&gt;

&lt;p&gt;A basic recovery policy can be written as a task-level rule.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"task_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"read_only_dashboard_check"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"max_retries"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"allow_rebuild_context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"require_human_review_for"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"login_prompt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"captcha"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"unexpected_account"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"proxy_region_mismatch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"sensitive_page"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"state_changing_action_uncertain"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"retry_allowed_only_if"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"profile_verified"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"account_verified"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"proxy_verified"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"no_submit_action_occurred"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"task_is_read_only"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This does not need to be complex at first.&lt;/p&gt;

&lt;p&gt;The important thing is to make the decision explicit.&lt;/p&gt;

&lt;p&gt;Once recovery rules are explicit, they can be reviewed, tested, improved, and reused.&lt;/p&gt;

&lt;p&gt;Without explicit rules, every failure becomes a prompt problem.&lt;/p&gt;

&lt;p&gt;And not every browser automation failure can be solved with a better prompt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion: Safer Agents Are Slower at the Right Moments
&lt;/h2&gt;

&lt;p&gt;Fast agents are useful.&lt;/p&gt;

&lt;p&gt;Recoverable agents are safer.&lt;/p&gt;

&lt;p&gt;Auditable agents are operationally valuable.&lt;/p&gt;

&lt;p&gt;An &lt;strong&gt;AI browser agent&lt;/strong&gt; should not only know how to act. It should know when the &lt;strong&gt;browser context&lt;/strong&gt; is no longer trustworthy enough to continue.&lt;/p&gt;

&lt;p&gt;That requires more than retries.&lt;/p&gt;

&lt;p&gt;It requires &lt;strong&gt;task contracts&lt;/strong&gt;, &lt;strong&gt;profile checks&lt;/strong&gt;, &lt;strong&gt;proxy checks&lt;/strong&gt;, &lt;strong&gt;stop conditions&lt;/strong&gt;, &lt;strong&gt;recovery levels&lt;/strong&gt;, and logs that explain why the agent continued.&lt;/p&gt;

&lt;p&gt;The goal is not to make agents afraid to act.&lt;/p&gt;

&lt;p&gt;The goal is to make them slow down at the moments where speed creates risk.&lt;/p&gt;

&lt;p&gt;For teams managing many profiles, proxies, and repeated browser tasks, the next step is not only better prompts.&lt;/p&gt;

&lt;p&gt;It is a more controlled browser execution environment.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>playwright</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Why AI Browser Agents Need a Runbook Before They Need More Prompts</title>
      <dc:creator>web4browser</dc:creator>
      <pubDate>Wed, 20 May 2026 05:05:22 +0000</pubDate>
      <link>https://dev.to/web4browser/why-ai-browser-agents-need-a-runbook-before-they-need-more-prompts-1619</link>
      <guid>https://dev.to/web4browser/why-ai-browser-agents-need-a-runbook-before-they-need-more-prompts-1619</guid>
      <description>&lt;p&gt;When an &lt;strong&gt;AI browser agent&lt;/strong&gt; fails, the first instinct is often to rewrite the prompt.&lt;/p&gt;

&lt;p&gt;Make it clearer.&lt;/p&gt;

&lt;p&gt;Add more steps.&lt;/p&gt;

&lt;p&gt;Add more warnings.&lt;/p&gt;

&lt;p&gt;Tell the agent to be careful.&lt;/p&gt;

&lt;p&gt;That can help sometimes. But in real browser workflows, especially workflows involving &lt;strong&gt;logged-in accounts&lt;/strong&gt;, &lt;strong&gt;persistent browser profiles&lt;/strong&gt;, &lt;strong&gt;proxies&lt;/strong&gt;, and &lt;strong&gt;human review&lt;/strong&gt;, the problem is often not the prompt.&lt;/p&gt;

&lt;p&gt;The problem is that the agent has no &lt;strong&gt;runbook&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A prompt tells the agent what you want.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;runbook&lt;/strong&gt; tells the agent how to operate inside a real browser environment.&lt;/p&gt;

&lt;p&gt;That distinction matters.&lt;/p&gt;

&lt;p&gt;A browser agent that can click buttons is useful. A browser agent that knows &lt;strong&gt;which account&lt;/strong&gt; it is using, &lt;strong&gt;which profile&lt;/strong&gt; is loaded, &lt;strong&gt;which proxy&lt;/strong&gt; should be active, &lt;strong&gt;when to stop&lt;/strong&gt;, &lt;strong&gt;when not to retry&lt;/strong&gt;, and &lt;strong&gt;what evidence to save&lt;/strong&gt; is much more useful.&lt;/p&gt;

&lt;p&gt;This article is about that missing layer.&lt;/p&gt;

&lt;p&gt;Not more prompts.&lt;/p&gt;

&lt;p&gt;Better &lt;strong&gt;browser operations&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  A prompt is not an operating model
&lt;/h2&gt;

&lt;p&gt;A prompt is good for expressing intent.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Check this account and summarize any issues.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;But it does not answer the operational questions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Which account?
Which browser profile?
Which proxy?
Which region?
What can be changed?
What must never be changed?
When should the agent stop?
How many retries are allowed?
What evidence should be saved?
Who reviews risky steps?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a public page, this may not matter much.&lt;/p&gt;

&lt;p&gt;For a &lt;strong&gt;logged-in browser profile&lt;/strong&gt;, it matters a lot.&lt;/p&gt;

&lt;p&gt;The browser is no longer just a runtime. It is carrying &lt;strong&gt;account state&lt;/strong&gt;: cookies, local storage, permissions, previous sessions, extensions, proxy assumptions, language settings, and sometimes team history.&lt;/p&gt;

&lt;p&gt;If the agent is operating inside that environment, the environment needs rules.&lt;/p&gt;

&lt;p&gt;Putting all of those rules into one giant prompt usually creates a brittle workflow.&lt;/p&gt;

&lt;p&gt;A better pattern is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Prompt = task intent
Runbook = operating rules
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The prompt can stay short.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;runbook&lt;/strong&gt; carries the boundaries.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why browser agents fail in real workflows
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;AI browser agents&lt;/strong&gt; usually do not fail in only one way.&lt;/p&gt;

&lt;p&gt;They fail at the edges between &lt;strong&gt;automation&lt;/strong&gt;, &lt;strong&gt;identity&lt;/strong&gt;, and &lt;strong&gt;operations&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wrong account context
&lt;/h3&gt;

&lt;p&gt;The agent opens the correct page, but the wrong account is logged in.&lt;/p&gt;

&lt;p&gt;The task may still appear successful. The dashboard loads. The agent extracts data. The summary looks reasonable.&lt;/p&gt;

&lt;p&gt;But the result belongs to the wrong account.&lt;/p&gt;

&lt;p&gt;That is worse than a visible failure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Profile drift
&lt;/h3&gt;

&lt;p&gt;A &lt;strong&gt;persistent browser profile&lt;/strong&gt; slowly changes over time.&lt;/p&gt;

&lt;p&gt;Cookies expire. Local storage changes. Timezone settings drift. Proxy bindings are updated. Locale assumptions become outdated. Extensions may be enabled or disabled.&lt;/p&gt;

&lt;p&gt;The agent is still using a profile, but not necessarily the profile state you expected.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prompt overreach
&lt;/h3&gt;

&lt;p&gt;A human writes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Find the problem and fix it.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent interprets “fix it” broadly.&lt;/p&gt;

&lt;p&gt;It changes settings, retries logins, clicks recovery flows, or updates account details.&lt;/p&gt;

&lt;p&gt;The original goal may have been inspection. The actual behavior became account modification.&lt;/p&gt;

&lt;h3&gt;
  
  
  Silent retry loops
&lt;/h3&gt;

&lt;p&gt;Network timeouts can be retried.&lt;/p&gt;

&lt;p&gt;Temporary 5xx errors can often be retried.&lt;/p&gt;

&lt;p&gt;But &lt;strong&gt;login failure&lt;/strong&gt;, &lt;strong&gt;verification prompts&lt;/strong&gt;, &lt;strong&gt;permission errors&lt;/strong&gt;, and &lt;strong&gt;region mismatches&lt;/strong&gt; should usually stop the run.&lt;/p&gt;

&lt;p&gt;Without retry rules, an agent may keep trying and turn a small issue into a bigger one.&lt;/p&gt;

&lt;h3&gt;
  
  
  No human checkpoint
&lt;/h3&gt;

&lt;p&gt;Some actions should not be fully automatic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;payment&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;credential entry&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;wallet action&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;security setting change&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;account recovery&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;password reset&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;identity verification&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A workflow that does not define human review points is relying on the model to improvise.&lt;/p&gt;

&lt;p&gt;That is not a safety strategy.&lt;/p&gt;

&lt;h3&gt;
  
  
  No evidence trail
&lt;/h3&gt;

&lt;p&gt;A run fails and the only output is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error: timeout
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That does not tell the team whether the issue came from the page, the profile, the proxy, the task instruction, the account state, or the agent’s reasoning.&lt;/p&gt;

&lt;p&gt;Without &lt;strong&gt;evidence&lt;/strong&gt;, the same failure will happen again.&lt;/p&gt;




&lt;h2&gt;
  
  
  What a browser agent runbook should contain
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;browser agent runbook&lt;/strong&gt; does not need to be complicated.&lt;/p&gt;

&lt;p&gt;It only needs to make the hidden assumptions explicit.&lt;/p&gt;

&lt;p&gt;Here are the fields I would define before letting an AI browser agent operate inside a logged-in profile.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Account context
&lt;/h2&gt;

&lt;p&gt;Do not give the agent only a URL.&lt;/p&gt;

&lt;p&gt;Give it an &lt;strong&gt;account context&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"account_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"acct_us_042"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"profile_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"profile_us_042"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"account_group"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"us-social-review"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key field is &lt;strong&gt;account_id&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Everything else should map around it.&lt;/p&gt;

&lt;p&gt;The agent should know:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;This is the account I am operating for.
This is the browser profile attached to it.
This is the account group or workflow category.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This prevents a common failure: correct page, wrong account.&lt;/p&gt;

&lt;p&gt;For &lt;strong&gt;multi-account workflows&lt;/strong&gt;, account context should not live in someone’s memory or a spreadsheet note. It should be part of the run.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Environment assumptions
&lt;/h2&gt;

&lt;p&gt;A browser run often depends on &lt;strong&gt;environment assumptions&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"expected_country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"US"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timezone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"America/New_York"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"locale"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"en-US"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"proxy_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"proxy_us_07"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These fields are not decoration.&lt;/p&gt;

&lt;p&gt;They define the expected operating environment.&lt;/p&gt;

&lt;p&gt;If &lt;strong&gt;expected_country&lt;/strong&gt; is &lt;code&gt;US&lt;/code&gt;, but the current exit IP is somewhere else, the agent should not continue blindly.&lt;/p&gt;

&lt;p&gt;If the profile assumes &lt;strong&gt;America/New_York&lt;/strong&gt;, but the browser timezone does not match, that should be visible before the task starts.&lt;/p&gt;

&lt;p&gt;In many browser automation failures, the page is not the problem.&lt;/p&gt;

&lt;p&gt;The environment is.&lt;/p&gt;

&lt;p&gt;A runbook should make &lt;strong&gt;proxy&lt;/strong&gt;, &lt;strong&gt;timezone&lt;/strong&gt;, &lt;strong&gt;locale&lt;/strong&gt;, and &lt;strong&gt;region&lt;/strong&gt; assumptions checkable.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Task scope
&lt;/h2&gt;

&lt;p&gt;The agent needs to know what kind of task it is performing.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;read-only inspection&lt;/strong&gt; is different from an &lt;strong&gt;account-changing action&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"task_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"read-only-inspection"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"allowed_actions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"inspect"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"summarize"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"export_report"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"blocked_actions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"payment"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"password_change"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"security_settings"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is more reliable than writing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Be careful.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;“Be careful” is vague.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;blocked_actions&lt;/strong&gt; is explicit.&lt;/p&gt;

&lt;p&gt;For browser agents, &lt;strong&gt;task scope&lt;/strong&gt; is one of the most important runbook fields because agents are flexible by design. They can adapt, interpret, and recover.&lt;/p&gt;

&lt;p&gt;That flexibility needs a boundary.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Stop conditions
&lt;/h2&gt;

&lt;p&gt;A good agent is not one that always continues.&lt;/p&gt;

&lt;p&gt;A good agent knows when to stop.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"stop_if"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"verification_prompt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"unexpected_login_page"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"payment_page"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"proxy_region_mismatch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"repeated_failed_attempts"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Stop conditions&lt;/strong&gt; are especially important for logged-in workflows.&lt;/p&gt;

&lt;p&gt;The agent should stop if:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A verification prompt appears.
A login page appears unexpectedly.
A payment page appears.
The proxy region does not match the expected region.
The same action fails repeatedly.
The page asks for sensitive account recovery.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Stopping is not failure.&lt;/p&gt;

&lt;p&gt;Stopping is part of the workflow.&lt;/p&gt;

&lt;p&gt;A runbook makes that behavior predictable.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Retry policy
&lt;/h2&gt;

&lt;p&gt;Retries are useful.&lt;/p&gt;

&lt;p&gt;Unbounded retries are not.&lt;/p&gt;

&lt;p&gt;A runbook should define what can be retried and what should stop immediately.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"retry_policy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"max_attempts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"retry_on"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"network_timeout"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"temporary_5xx"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"do_not_retry_on"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"login_failed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"verification_required"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"permission_denied"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This keeps the agent from treating every error as a temporary obstacle.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;network timeout&lt;/strong&gt; is not the same as a &lt;strong&gt;failed login&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;502&lt;/strong&gt; is not the same as a &lt;strong&gt;permission denial&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;verification challenge&lt;/strong&gt; is not something to brute-force with more clicks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Retry policy&lt;/strong&gt; is boring.&lt;/p&gt;

&lt;p&gt;That is why it is useful.&lt;/p&gt;

&lt;p&gt;It turns panic behavior into predictable behavior.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Human review rule
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Human-in-the-loop&lt;/strong&gt; is not a weakness.&lt;/p&gt;

&lt;p&gt;For browser automation, it is often the safety layer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"human_review_required_for"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"credential_entry"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"wallet_action"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"payment"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"account_recovery"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"security_change"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells the agent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You may inspect.
You may summarize.
You may prepare.
But you may not cross these lines without review.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That matters because browser agents operate in environments where some clicks have real consequences.&lt;/p&gt;

&lt;p&gt;A review point should not depend on the model deciding whether something “feels risky.”&lt;/p&gt;

&lt;p&gt;It should be defined before the run starts.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Evidence requirements
&lt;/h2&gt;

&lt;p&gt;Every run should leave enough &lt;strong&gt;evidence&lt;/strong&gt; for review.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"evidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"save_screenshot"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"save_dom_snapshot"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"save_console_log"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"save_proxy_check"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"save_final_summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Evidence does not need to be excessive.&lt;/p&gt;

&lt;p&gt;But it should answer the basic questions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Which account was used?
Which profile was loaded?
Which proxy was active?
What did the agent observe?
Where did it stop?
What error appeared?
What did it summarize?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For development teams, this feels similar to test artifacts.&lt;/p&gt;

&lt;p&gt;A failed CI run without logs is frustrating.&lt;/p&gt;

&lt;p&gt;A failed browser agent run without evidence is worse, because it may involve &lt;strong&gt;account state&lt;/strong&gt;, &lt;strong&gt;browser state&lt;/strong&gt;, &lt;strong&gt;proxy state&lt;/strong&gt;, and &lt;strong&gt;model decisions&lt;/strong&gt; at the same time.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Completion criteria
&lt;/h2&gt;

&lt;p&gt;An agent should not decide that a task is done just because it reached a plausible stopping point.&lt;/p&gt;

&lt;p&gt;Define what &lt;strong&gt;done&lt;/strong&gt; means.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"done_when"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"account_status_collected"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"no_blocking_error_found"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"summary_saved"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"evidence_attached"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes completion verifiable.&lt;/p&gt;

&lt;p&gt;For example, a status inspection is not complete until:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The account status was collected.
No blocking error was found.
The summary was saved.
Required evidence was attached.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without &lt;strong&gt;completion criteria&lt;/strong&gt;, an agent may produce a confident summary for a half-finished task.&lt;/p&gt;

&lt;p&gt;That is one of the easiest ways to get a polished but unreliable result.&lt;/p&gt;




&lt;h2&gt;
  
  
  A minimal browser agent runbook template
&lt;/h2&gt;

&lt;p&gt;Here is a compact template you can adapt.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"run_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"run_2026_05_20_001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"account"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"account_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"acct_us_042"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"profile_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"profile_us_042"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"account_group"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"us-social-review"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"environment"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"expected_country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"US"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"timezone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"America/New_York"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"locale"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"en-US"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"proxy_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"proxy_us_07"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"task"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"task_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"read-only-inspection"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"allowed_actions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"inspect"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"summarize"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"export_report"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"blocked_actions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"payment"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"password_change"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"security_settings"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"stop_if"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"verification_prompt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"unexpected_login_page"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"proxy_region_mismatch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"repeated_failed_attempts"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"retry_policy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"max_attempts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"retry_on"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"network_timeout"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"temporary_5xx"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"do_not_retry_on"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"login_failed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"verification_required"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"permission_denied"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"human_review_required_for"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"credential_entry"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"payment"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"account_recovery"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"security_change"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"evidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"save_screenshot"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"save_console_log"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"save_proxy_check"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"save_final_summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"done_when"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"account_status_collected"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"summary_saved"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"evidence_attached"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part is not the exact schema.&lt;/p&gt;

&lt;p&gt;The important part is that the agent is no longer operating in a vague environment.&lt;/p&gt;

&lt;p&gt;It has a declared &lt;strong&gt;account&lt;/strong&gt;, &lt;strong&gt;environment&lt;/strong&gt;, &lt;strong&gt;task scope&lt;/strong&gt;, &lt;strong&gt;stop logic&lt;/strong&gt;, &lt;strong&gt;retry policy&lt;/strong&gt;, &lt;strong&gt;review rule&lt;/strong&gt;, &lt;strong&gt;evidence requirement&lt;/strong&gt;, and &lt;strong&gt;completion definition&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  How this changes the prompt
&lt;/h2&gt;

&lt;p&gt;Without a runbook, the prompt often becomes overloaded:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Check this account and fix any issues. Be careful. Do not do anything risky. If something seems wrong, stop. Make sure to save useful information.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That sounds reasonable, but it is vague.&lt;/p&gt;

&lt;p&gt;With a runbook, the prompt can be shorter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Use the attached runbook.
Perform only read-only inspection.
Stop if verification, payment, login failure, or proxy mismatch appears.
Save evidence and summarize only what was observed.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the prompt is not carrying the entire operating model.&lt;/p&gt;

&lt;p&gt;It is only invoking it.&lt;/p&gt;

&lt;p&gt;This is easier to review, easier to reuse, and easier to debug.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where Playwright, MCP, and browser-use fit
&lt;/h2&gt;

&lt;p&gt;A runbook does not replace browser automation tools.&lt;/p&gt;

&lt;p&gt;It gives them operating rules.&lt;/p&gt;

&lt;p&gt;A simple way to think about the layers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Playwright controls the browser.
MCP exposes browser capabilities.
The agent decides the next step.
The runbook defines what is allowed.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These layers solve different problems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Playwright&lt;/strong&gt; is good at deterministic browser control.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MCP&lt;/strong&gt; or a tool layer can expose browser actions to an AI agent.&lt;/p&gt;

&lt;p&gt;An &lt;strong&gt;agent framework&lt;/strong&gt; can plan and adapt.&lt;/p&gt;

&lt;p&gt;But none of those automatically defines &lt;strong&gt;account boundaries&lt;/strong&gt;, &lt;strong&gt;retry rules&lt;/strong&gt;, &lt;strong&gt;stop conditions&lt;/strong&gt;, &lt;strong&gt;human review points&lt;/strong&gt;, or &lt;strong&gt;evidence requirements&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That is what the runbook is for.&lt;/p&gt;

&lt;p&gt;If your workflow depends on persistent login state, it is also worth understanding the difference between &lt;a href="https://dev.to/web4browser/playwright-storagestate-vs-persistent-context-which-one-should-you-use-for-multi-account-k86"&gt;storageState vs persistent context&lt;/a&gt;. The more your automation depends on long-lived account continuity, the more important the operating layer becomes.&lt;/p&gt;




&lt;h2&gt;
  
  
  When a simple script is still better
&lt;/h2&gt;

&lt;p&gt;Not every workflow needs an &lt;strong&gt;AI browser agent&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Sometimes a script is better.&lt;/p&gt;

&lt;p&gt;Use a normal &lt;strong&gt;Playwright&lt;/strong&gt; or &lt;strong&gt;Puppeteer&lt;/strong&gt; script when:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The page is public.
The task is deterministic.
There is no persistent account identity.
There is no sensitive state.
There is no human review step.
There are no high-risk actions.
The workflow is short-lived.
The expected result is easy to assert.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Examples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Take screenshots of public pages.
Run a CI smoke test.
Check whether a landing page loads.
Submit a staging form.
Validate a basic UI flow.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In those cases, adding an AI agent may only make the system harder to reason about.&lt;/p&gt;

&lt;p&gt;If the task is &lt;strong&gt;deterministic&lt;/strong&gt;, &lt;strong&gt;low-risk&lt;/strong&gt;, and &lt;strong&gt;short-lived&lt;/strong&gt;, a script is usually better than an agent.&lt;/p&gt;




&lt;h2&gt;
  
  
  When a browser workspace becomes useful
&lt;/h2&gt;

&lt;p&gt;A workspace layer becomes useful when the browser environment itself becomes part of the workflow.&lt;/p&gt;

&lt;p&gt;That usually happens when you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;multiple long-lived accounts&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;persistent browser profiles&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;proxy-region mapping&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;recurring account checks&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MCP or reusable browser skills&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;human review&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;execution logs&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;team handoff&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;headless and headed modes used together&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At that point, the problem is no longer only browser control.&lt;/p&gt;

&lt;p&gt;The problem is coordination.&lt;/p&gt;

&lt;p&gt;You need to keep the runbook close to the real operating environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Account
Profile
Proxy
Task
Permission
Review
Evidence
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For teams moving from single scripts to repeatable &lt;strong&gt;account-aware browser workflows&lt;/strong&gt;, an &lt;a href="https://web4browser.io/ai-browser-agent.html" rel="noopener noreferrer"&gt;account-aware browser workspace&lt;/a&gt; can make runbooks easier to keep close to profiles, proxies, tasks, logs, and review steps.&lt;/p&gt;

&lt;p&gt;The workspace layer does not replace Playwright.&lt;/p&gt;

&lt;p&gt;It gives Playwright and AI agents a more reliable place to operate.&lt;/p&gt;




&lt;h2&gt;
  
  
  A practical pre-run checklist
&lt;/h2&gt;

&lt;p&gt;Before the agent starts, ask:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ ] Is the correct account selected?
[ ] Is the correct browser profile loaded?
[ ] Does the proxy match the expected region?
[ ] Do timezone and locale match the account assumptions?
[ ] Is the task scope read-only or action-taking?
[ ] Are blocked actions clearly defined?
[ ] Are stop conditions defined?
[ ] Is the retry policy safe?
[ ] Are human review points defined?
[ ] Will screenshots, logs, or summaries be saved?
[ ] Is done clearly defined?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This checklist is simple.&lt;/p&gt;

&lt;p&gt;That is the point.&lt;/p&gt;

&lt;p&gt;A browser agent should not need to guess the operating model every time it runs.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final thought
&lt;/h2&gt;

&lt;p&gt;Better prompts can help an &lt;strong&gt;AI browser agent&lt;/strong&gt; follow instructions.&lt;/p&gt;

&lt;p&gt;But prompts alone do not create reliable operations.&lt;/p&gt;

&lt;p&gt;For &lt;strong&gt;logged-in browser workflows&lt;/strong&gt;, the missing layer is often a runbook:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Account context
Environment assumptions
Task scope
Stop conditions
Retry policy
Human review
Evidence
Completion criteria
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The future of &lt;strong&gt;AI browser automation&lt;/strong&gt; is not just agents that can click.&lt;/p&gt;

&lt;p&gt;It is agents that understand the rules of the environment they are operating in.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>playwright</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Before You Let an AI Agent Use a Logged-In Browser, Define These 7 Boundaries</title>
      <dc:creator>web4browser</dc:creator>
      <pubDate>Tue, 19 May 2026 04:15:52 +0000</pubDate>
      <link>https://dev.to/web4browser/before-you-let-an-ai-agent-use-a-logged-in-browser-define-these-7-boundaries-4jj</link>
      <guid>https://dev.to/web4browser/before-you-let-an-ai-agent-use-a-logged-in-browser-define-these-7-boundaries-4jj</guid>
      <description>&lt;p&gt;&lt;strong&gt;AI browser agents&lt;/strong&gt; are becoming surprisingly capable.&lt;/p&gt;

&lt;p&gt;They can open pages, inspect dashboards, fill forms, extract data, run checks, and summarize results. With tools like &lt;strong&gt;Playwright&lt;/strong&gt;, &lt;strong&gt;MCP workflows&lt;/strong&gt;, and browser-use style agents, it is getting easier to turn a natural-language task into browser actions.&lt;/p&gt;

&lt;p&gt;But the moment an agent runs inside a &lt;strong&gt;logged-in browser profile&lt;/strong&gt;, the main question changes.&lt;/p&gt;

&lt;p&gt;It is no longer only:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can the agent automate this page?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The better question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What is this agent allowed to touch?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A script that fails on a public test page is usually a technical problem.&lt;/p&gt;

&lt;p&gt;An agent that continues inside the wrong &lt;strong&gt;account&lt;/strong&gt;, with the wrong &lt;strong&gt;browser profile&lt;/strong&gt;, wrong &lt;strong&gt;proxy region&lt;/strong&gt;, wrong &lt;strong&gt;permission level&lt;/strong&gt;, or wrong &lt;strong&gt;task boundary&lt;/strong&gt;, becomes an operational risk.&lt;/p&gt;

&lt;p&gt;This article is a practical checklist for teams building &lt;strong&gt;AI browser automation&lt;/strong&gt; around real accounts, persistent profiles, proxy-aware workflows, and human review.&lt;/p&gt;

&lt;p&gt;It is not about making agents click faster.&lt;/p&gt;

&lt;p&gt;It is about making &lt;strong&gt;browser automation accountable&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Responsible use note: only automate accounts, systems, and workflows you own or are explicitly authorized to operate. &lt;strong&gt;Logged-in browser automation&lt;/strong&gt; should respect platform rules, user privacy, and security boundaries.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Why logged-in browser agents are different
&lt;/h2&gt;

&lt;p&gt;Traditional &lt;strong&gt;browser automation&lt;/strong&gt; usually starts from a clean assumption:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Open browser.
Go to URL.
Run steps.
Assert result.
Close browser.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That model works well for many tests.&lt;/p&gt;

&lt;p&gt;But &lt;strong&gt;logged-in browser agents&lt;/strong&gt; are different.&lt;/p&gt;

&lt;p&gt;They do not just interact with a page.&lt;/p&gt;

&lt;p&gt;They operate inside an &lt;strong&gt;identity&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That identity may include:&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;local storage&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;browser permissions&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;extensions&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;proxy region&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;timezone&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;locale&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;account-specific workflows&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;human operator history&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;previous automation results&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a single test account, this may be manageable.&lt;/p&gt;

&lt;p&gt;For multiple long-lived accounts, the browser environment becomes part of the account itself.&lt;/p&gt;

&lt;p&gt;That is why &lt;strong&gt;AI browser agents&lt;/strong&gt; need boundaries before they need more autonomy.&lt;/p&gt;




&lt;h2&gt;
  
  
  Boundary 1: Account identity
&lt;/h2&gt;

&lt;p&gt;Every run should start with a clear &lt;strong&gt;account identity&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Not just a URL.&lt;/p&gt;

&lt;p&gt;Not just a prompt.&lt;/p&gt;

&lt;p&gt;Not just:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Open the dashboard and check status.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent should know which account it is operating for, which profile belongs to that account, and what type of task it is allowed to perform.&lt;/p&gt;

&lt;p&gt;A minimal account declaration might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"account_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"acct_us_042"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"profile_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"profile_us_042"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"task"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"read-only-inspection"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This prevents a common failure mode:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The agent opens the correct page, but inside the wrong account.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That failure can be hard to notice if the UI looks similar across accounts.&lt;/p&gt;

&lt;p&gt;Before the agent starts, ask:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Which account is this run for?
Which browser profile belongs to that account?
Is this task allowed for that account?
Should this run be read-only or action-taking?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the agent cannot answer those questions, it should not continue.&lt;/p&gt;

&lt;p&gt;The key field here is &lt;strong&gt;account_id&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Everything else should be mapped around it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Boundary 2: Browser profile
&lt;/h2&gt;

&lt;p&gt;Many &lt;strong&gt;Playwright&lt;/strong&gt; users start with &lt;code&gt;storageState&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That makes sense.&lt;/p&gt;

&lt;p&gt;For tests, &lt;code&gt;storageState&lt;/code&gt; is useful because it saves cookies and local storage so you can skip login. For internal apps, CI tests, and role-based testing, that is often enough.&lt;/p&gt;

&lt;p&gt;But a logged-in AI workflow may need more than a login shortcut.&lt;/p&gt;

&lt;p&gt;It may need a &lt;strong&gt;persistent browser profile&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A persistent profile can carry more continuity across runs:&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;local storage&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;cache&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;permissions&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;extension state&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;repeated account history&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;human review context&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;debugging context&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A useful rule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Use storageState for test login shortcuts.
Use persistent profiles for long-lived account continuity.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The difference matters because a &lt;strong&gt;logged-in browser agent&lt;/strong&gt; is not only "using a session."&lt;/p&gt;

&lt;p&gt;It is operating inside an &lt;strong&gt;account environment&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you want a deeper breakdown, this article on &lt;a href="https://dev.to/web4browser/playwright-storagestate-vs-persistent-context-which-one-should-you-use-for-multi-account-k86"&gt;storageState vs persistent context&lt;/a&gt; explains where each one fits.&lt;/p&gt;

&lt;p&gt;For AI browser agents, the profile should be treated as the operating memory of the account.&lt;/p&gt;

&lt;p&gt;That means the profile should not be swapped casually, shared across unrelated accounts, or reused without metadata.&lt;/p&gt;

&lt;p&gt;A better profile record might include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"profile_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"profile_us_042"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"account_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"acct_us_042"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"created_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-05-19T10:30:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"default_region"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"US"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"default_timezone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"America/New_York"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"default_locale"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"en-US"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"last_successful_run"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-05-19T12:15:00Z"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The goal is not to make the profile complicated.&lt;/p&gt;

&lt;p&gt;The goal is to make it &lt;strong&gt;traceable&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The key field here is &lt;strong&gt;profile_id&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Boundary 3: Proxy, timezone, and locale
&lt;/h2&gt;

&lt;p&gt;A proxy should not be treated as a random launch option.&lt;/p&gt;

&lt;p&gt;In &lt;strong&gt;multi-account automation&lt;/strong&gt;, the proxy is part of the account context.&lt;/p&gt;

&lt;p&gt;If a browser profile usually operates in one region, but the agent suddenly runs it from another region, the script may still technically work. The page may still load. The agent may still click buttons.&lt;/p&gt;

&lt;p&gt;But the workflow is no longer running under the same assumptions.&lt;/p&gt;

&lt;p&gt;Before the agent starts, check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Expected country
Actual exit IP country
Timezone
Locale
Accept-Language
Browser profile
Account group
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A basic pre-run consistency check might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"proxy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"proxy_us_res_07"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"expected_country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"US"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"exit_ip_country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"US"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"environment"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"timezone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"America/New_York"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"locale"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"en-US"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"accept_language"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"en-US,en;q=0.9"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is not only a networking issue.&lt;/p&gt;

&lt;p&gt;It is an &lt;strong&gt;identity boundary&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For multi-account teams, it helps to manage proxies, regions, languages, and profiles as one mapped environment rather than separate settings. A &lt;a href="https://web4browser.io/proxy-manager.html" rel="noopener noreferrer"&gt;profile-level proxy and environment control&lt;/a&gt; layer can make this easier to reason about.&lt;/p&gt;

&lt;p&gt;The important point is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Do not let the agent run first and discover the mismatch later.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Check the environment before the run.&lt;/p&gt;

&lt;p&gt;The key fields here are &lt;strong&gt;proxy_id&lt;/strong&gt;, &lt;strong&gt;expected_country&lt;/strong&gt;, &lt;strong&gt;timezone&lt;/strong&gt;, and &lt;strong&gt;locale&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Boundary 4: Task permissions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;AI browser agents&lt;/strong&gt; are flexible.&lt;/p&gt;

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

&lt;p&gt;It is also the reason they need &lt;strong&gt;permission boundaries&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A fixed script usually does only what it was written to do. An agent can interpret the page, adjust its path, recover from errors, and keep moving.&lt;/p&gt;

&lt;p&gt;That is powerful, but not every task should be auto-run.&lt;/p&gt;

&lt;p&gt;Separate tasks by risk level:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Task type&lt;/th&gt;
&lt;th&gt;Auto-run?&lt;/th&gt;
&lt;th&gt;Human review?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Page inspection&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Status check&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Export report&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Optional&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Form draft&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Maybe&lt;/td&gt;
&lt;td&gt;Recommended&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Retry failed login&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Change account settings&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Payment action&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Credential or wallet action&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Security setting change&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Required&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A good browser agent should know when to stop.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"workflow"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"allowed_tasks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"page-inspection"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"status-check"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"report-export"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"blocked_tasks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"payment"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"credential-entry"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"security-settings-change"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"requires_human_review"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"verification"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"unexpected-login-page"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"account-settings-change"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives the agent a clear operating zone.&lt;/p&gt;

&lt;p&gt;It can inspect.&lt;/p&gt;

&lt;p&gt;It can summarize.&lt;/p&gt;

&lt;p&gt;It can prepare.&lt;/p&gt;

&lt;p&gt;But it should not silently cross into high-risk actions.&lt;/p&gt;

&lt;p&gt;The key fields here are &lt;strong&gt;allowed_tasks&lt;/strong&gt;, &lt;strong&gt;blocked_tasks&lt;/strong&gt;, and &lt;strong&gt;requires_human_review&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Boundary 5: Secrets and credentials
&lt;/h2&gt;

&lt;p&gt;Do not put secrets in prompts.&lt;/p&gt;

&lt;p&gt;Do not put passwords in plain JSON.&lt;/p&gt;

&lt;p&gt;Do not paste API keys into agent instructions.&lt;/p&gt;

&lt;p&gt;Do not let the agent casually see credentials it does not need to reason about.&lt;/p&gt;

&lt;p&gt;This is especially important when browser agents are connected to &lt;strong&gt;LLMs&lt;/strong&gt;, external tools, logs, or workflow systems.&lt;/p&gt;

&lt;p&gt;The agent may need to know that a credential exists.&lt;/p&gt;

&lt;p&gt;It does not always need to see the credential.&lt;/p&gt;

&lt;p&gt;Use references instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"account_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"acct_us_042"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"secrets"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"password_ref"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vault://accounts/acct_us_042/password"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"api_key_ref"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vault://services/reporting/read-only-key"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That pattern keeps the manifest useful without turning it into a secret dump.&lt;/p&gt;

&lt;p&gt;A practical rule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The agent can request a secret through an approved flow.
The agent should not store, print, summarize, or expose the secret.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also make sure execution logs do not accidentally capture sensitive values.&lt;/p&gt;

&lt;p&gt;Avoid logs like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Typed password: my-real-password
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Prefer logs like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Credential submitted through approved secret reference.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That small difference matters when multiple team members review automation results.&lt;/p&gt;

&lt;p&gt;The key fields here are &lt;strong&gt;password_ref&lt;/strong&gt;, &lt;strong&gt;api_key_ref&lt;/strong&gt;, and &lt;strong&gt;secret reference&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Boundary 6: Human review checkpoints
&lt;/h2&gt;

&lt;p&gt;A reliable &lt;strong&gt;AI browser workflow&lt;/strong&gt; is not always fully automatic.&lt;/p&gt;

&lt;p&gt;Sometimes the best action is to pause.&lt;/p&gt;

&lt;p&gt;The agent should pause when it encounters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;verification prompts&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;unexpected login pages&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;payment pages&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;password reset screens&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;account security settings&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;region mismatch&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;repeated failed attempts&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;suspicious redirects&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;unclear destructive actions&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;unexpected permission requests&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A pause is not a failure.&lt;/p&gt;

&lt;p&gt;A pause is a safety feature.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"review_checkpoints"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"condition"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"verification_prompt_detected"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pause_and_request_review"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"condition"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"payment_page_detected"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pause_and_request_review"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"condition"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"proxy_region_mismatch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"stop_run"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For real teams, this matters more than it looks.&lt;/p&gt;

&lt;p&gt;A browser agent that can complete a task is useful.&lt;/p&gt;

&lt;p&gt;A browser agent that can explain why it stopped is much more useful.&lt;/p&gt;

&lt;p&gt;This is where &lt;a href="https://web4browser.io/ai-collaboration.html" rel="noopener noreferrer"&gt;reviewable browser workflows&lt;/a&gt; become important: the workflow should keep account context, page status, exceptions, and human review in the same execution path.&lt;/p&gt;

&lt;p&gt;The key fields here are &lt;strong&gt;review_checkpoints&lt;/strong&gt;, &lt;strong&gt;condition&lt;/strong&gt;, and &lt;strong&gt;action&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Boundary 7: Evidence and audit logs
&lt;/h2&gt;

&lt;p&gt;If an agent completes a task but nobody can reconstruct what happened, the automation is not trustworthy.&lt;/p&gt;

&lt;p&gt;Every run should produce enough evidence for debugging and review.&lt;/p&gt;

&lt;p&gt;At minimum, log:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;account_id
profile_id
proxy_id
expected region
actual exit IP region
timezone
locale
task type
permission level
start time
end time
result
pause reason, if any
screenshots, if needed
execution log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A failed run should not only return:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error: timeout
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That does not help the team understand whether the issue came from the page, the proxy, the browser profile, the login state, or the task instruction.&lt;/p&gt;

&lt;p&gt;A better result looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"run_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"run_2026_05_19_001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"account_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"acct_us_042"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"profile_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"profile_us_042"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"proxy_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"proxy_us_res_07"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"task"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"read-only-inspection"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"result"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"paused"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"pause_reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"verification_prompt_detected"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"evidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"screenshot"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"execution_log"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"proxy_check"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The goal is to produce &lt;strong&gt;traceable work&lt;/strong&gt;, not just browser activity.&lt;/p&gt;

&lt;p&gt;The key fields here are &lt;strong&gt;run_id&lt;/strong&gt;, &lt;strong&gt;result&lt;/strong&gt;, &lt;strong&gt;pause_reason&lt;/strong&gt;, and &lt;strong&gt;evidence&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  A minimal boundary manifest
&lt;/h2&gt;

&lt;p&gt;Here is a simple manifest that combines the seven boundaries.&lt;/p&gt;

&lt;p&gt;It is not meant to be a universal standard.&lt;/p&gt;

&lt;p&gt;It is a practical starting point for &lt;strong&gt;account-aware browser automation&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"account_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"acct_us_042"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"profile_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"profile_us_042"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"proxy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"proxy_us_res_07"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"expected_country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"US"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"timezone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"America/New_York"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"locale"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"en-US"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"browser"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"headed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"persistent_context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"workflow"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"task"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"account-status-inspection"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"allowed_tasks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"read-only-inspection"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"status-check"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"report-export"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"blocked_tasks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"payment"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"credential-entry"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"security-settings-change"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"requires_human_review"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"verification"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"payment"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"settings-change"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"profile-reset"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"unexpected-login-page"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"secrets"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"password_ref"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vault://accounts/acct_us_042/password"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"evidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"save_screenshot"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"save_execution_log"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"log_proxy_check"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"log_environment_check"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key idea is not the JSON itself.&lt;/p&gt;

&lt;p&gt;The key idea is that the agent should not run in a vague environment.&lt;/p&gt;

&lt;p&gt;It should run inside a declared &lt;strong&gt;account context&lt;/strong&gt; with explicit boundaries.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pre-run checklist
&lt;/h2&gt;

&lt;p&gt;Before the agent starts, ask:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ ] Is the expected account selected?
[ ] Is the expected browser profile loaded?
[ ] Is the profile tied to the right account?
[ ] Does the proxy region match the expected region?
[ ] Do timezone, locale, and language match the account assumptions?
[ ] Is the task read-only, low-risk, or high-risk?
[ ] Are blocked actions clearly defined?
[ ] Are secrets referenced instead of exposed?
[ ] Are human-review checkpoints defined?
[ ] Will the run save enough evidence for debugging?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If any answer is unclear, the agent should not proceed silently.&lt;/p&gt;

&lt;p&gt;This checklist is the simplest way to prevent many &lt;strong&gt;AI browser automation&lt;/strong&gt; failures before they happen.&lt;/p&gt;




&lt;h2&gt;
  
  
  When scripts are enough
&lt;/h2&gt;

&lt;p&gt;You do not always need a full workflow layer.&lt;/p&gt;

&lt;p&gt;A normal &lt;strong&gt;Playwright&lt;/strong&gt; or &lt;strong&gt;Puppeteer&lt;/strong&gt; script may be enough when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the page is public&lt;/li&gt;
&lt;li&gt;the task is short-lived&lt;/li&gt;
&lt;li&gt;there is no persistent account identity&lt;/li&gt;
&lt;li&gt;the test data is disposable&lt;/li&gt;
&lt;li&gt;the browser starts clean every time&lt;/li&gt;
&lt;li&gt;there is no human handoff&lt;/li&gt;
&lt;li&gt;there are no high-risk actions&lt;/li&gt;
&lt;li&gt;there is no need for long-term profile continuity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Check whether a landing page loads.
Test a form in staging.
Take screenshots of public pages.
Run CI checks for an internal dashboard.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In those cases, a script is clean, simple, and usually better.&lt;/p&gt;




&lt;h2&gt;
  
  
  When you need a workspace layer
&lt;/h2&gt;

&lt;p&gt;A workspace layer becomes useful when the browser environment itself becomes part of the workflow.&lt;/p&gt;

&lt;p&gt;That usually happens when you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;multiple long-lived accounts&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;persistent browser profiles&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;proxy-region assumptions&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;recurring account checks&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;human review&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;execution logs&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;team handoffs&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;reusable browser skills&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AI agents operating across accounts&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;headless and headed modes used together&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At that point, the problem is not only automation.&lt;/p&gt;

&lt;p&gt;The problem is coordination.&lt;/p&gt;

&lt;p&gt;You need to know:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Which account?
Which profile?
Which proxy?
Which task?
Which permission level?
Which review rule?
Which evidence trail?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For teams moving from one-off scripts to repeatable &lt;strong&gt;account-aware workflows&lt;/strong&gt;, an &lt;a href="https://web4browser.io/ai-browser-agent.html" rel="noopener noreferrer"&gt;account-aware browser workspace&lt;/a&gt; can help keep profiles, proxies, tasks, logs, and review steps in one operating layer.&lt;/p&gt;

&lt;p&gt;That workspace layer does not replace Playwright.&lt;/p&gt;

&lt;p&gt;It gives Playwright and AI agents a safer environment to operate in.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final thought
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;AI browser agents&lt;/strong&gt; do not only need access to a browser.&lt;/p&gt;

&lt;p&gt;They need boundaries.&lt;/p&gt;

&lt;p&gt;Before giving an agent a logged-in profile, define:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Account identity
Browser profile
Proxy, timezone, and locale
Task permissions
Secret handling
Human review checkpoints
Evidence and audit logs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The goal is not to make agents click faster.&lt;/p&gt;

&lt;p&gt;The goal is to make &lt;strong&gt;browser automation accountable&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A good agent should not only know how to act.&lt;/p&gt;

&lt;p&gt;It should know when not to act.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>playwright</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Designing an Account Context Manifest for AI Browser Agents</title>
      <dc:creator>web4browser</dc:creator>
      <pubDate>Mon, 18 May 2026 08:17:06 +0000</pubDate>
      <link>https://dev.to/web4browser/designing-an-account-context-manifest-for-ai-browser-agents-262d</link>
      <guid>https://dev.to/web4browser/designing-an-account-context-manifest-for-ai-browser-agents-262d</guid>
      <description>&lt;p&gt;Your &lt;strong&gt;AI browser agent&lt;/strong&gt; can open a page.&lt;/p&gt;

&lt;p&gt;It can click buttons.&lt;/p&gt;

&lt;p&gt;It can fill forms.&lt;/p&gt;

&lt;p&gt;It can even complete a workflow that looks correct from the outside.&lt;/p&gt;

&lt;p&gt;But here is the more important question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can you prove it used the right account, the right browser profile, the right proxy, the right session history, and the right workflow boundary?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For many browser automation projects, the answer is no.&lt;/p&gt;

&lt;p&gt;Not because the automation framework is bad. &lt;strong&gt;Playwright&lt;/strong&gt;, &lt;strong&gt;Puppeteer&lt;/strong&gt;, browser &lt;strong&gt;MCP servers&lt;/strong&gt;, and AI agents are all powerful.&lt;/p&gt;

&lt;p&gt;The problem is more basic:&lt;/p&gt;

&lt;p&gt;Most systems treat &lt;strong&gt;browser control&lt;/strong&gt; as the whole workflow.&lt;/p&gt;

&lt;p&gt;In real account-based automation, browser control is only one layer.&lt;/p&gt;

&lt;p&gt;The missing layer is &lt;strong&gt;account context&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This post introduces a simple pattern I call an &lt;strong&gt;Account Context Manifest&lt;/strong&gt;: a structured file that tells an AI browser agent which &lt;strong&gt;account environment&lt;/strong&gt; it is allowed to use, what assumptions must stay stable, and what evidence should be recorded.&lt;/p&gt;

&lt;p&gt;It is not a silver bullet.&lt;/p&gt;

&lt;p&gt;It is not a replacement for security, platform compliance, or human review.&lt;/p&gt;

&lt;p&gt;But it is a practical way to stop AI browser automation from becoming a pile of disconnected scripts, state files, proxy flags, and screenshots.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Browser control&lt;/strong&gt; is not &lt;strong&gt;account-aware execution&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Most browser automation examples start like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&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;launch&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="k"&gt;await&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;newPage&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;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://example.com&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;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text=Login&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;That is fine for tests, demos, and simple one-off tasks.&lt;/p&gt;

&lt;p&gt;But real account workflows are different.&lt;/p&gt;

&lt;p&gt;A real workflow usually depends on more than a URL and a selector.&lt;/p&gt;

&lt;p&gt;It depends on questions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which &lt;strong&gt;account&lt;/strong&gt; is being used?&lt;/li&gt;
&lt;li&gt;Which &lt;strong&gt;browser profile&lt;/strong&gt; belongs to that account?&lt;/li&gt;
&lt;li&gt;Which &lt;strong&gt;proxy&lt;/strong&gt; or network route should be used?&lt;/li&gt;
&lt;li&gt;Does the &lt;strong&gt;timezone&lt;/strong&gt; match the proxy region?&lt;/li&gt;
&lt;li&gt;Does the &lt;strong&gt;locale&lt;/strong&gt; match the account environment?&lt;/li&gt;
&lt;li&gt;Is the session expected to be fresh or persistent?&lt;/li&gt;
&lt;li&gt;Are browser &lt;strong&gt;extensions&lt;/strong&gt; required?&lt;/li&gt;
&lt;li&gt;Is this task safe for &lt;strong&gt;headless execution&lt;/strong&gt;?&lt;/li&gt;
&lt;li&gt;When should the agent stop for &lt;strong&gt;human review&lt;/strong&gt;?&lt;/li&gt;
&lt;li&gt;What evidence should be saved if the workflow fails?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If these details live in different places, the agent may still run.&lt;/p&gt;

&lt;p&gt;But the result becomes hard to audit.&lt;/p&gt;

&lt;p&gt;That is where many browser agents fail in practice.&lt;/p&gt;

&lt;p&gt;Not because they cannot click.&lt;/p&gt;

&lt;p&gt;But because they do not know enough about the &lt;strong&gt;account environment&lt;/strong&gt; they are clicking inside.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is an &lt;strong&gt;Account Context Manifest&lt;/strong&gt;?
&lt;/h2&gt;

&lt;p&gt;An &lt;strong&gt;Account Context Manifest&lt;/strong&gt; is a structured definition of the browser environment an automation run is allowed to use.&lt;/p&gt;

&lt;p&gt;It connects these fields into one object:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;account identity&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;browser profile&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;proxy configuration&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;timezone and locale&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;session state&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;browser mode&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;workflow permissions&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;human review rules&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;debugging evidence&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is a minimal example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"account_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"acct_us_042"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"profile_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"profile_us_042"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"profile_path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./profiles/acct_us_042"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"proxy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"proxy_us_res_07"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"US"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"timezone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"America/New_York"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"locale"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"en-US"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"browser"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"headed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"persistent_context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"extensions_required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"wallet"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"password-manager"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"state"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"storage_state_path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./states/acct_us_042.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"last_verified_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-05-18T10:00:00Z"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"workflow"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"allowed_tasks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"login-check"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"page-inspection"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"report-export"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"requires_human_review"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"verification"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"payment"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"profile-reset"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"evidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"save_screenshot"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"save_dom_snapshot"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"log_proxy_check"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The manifest does not make the agent smarter by itself.&lt;/p&gt;

&lt;p&gt;Instead, it makes the &lt;strong&gt;execution environment&lt;/strong&gt; explicit.&lt;/p&gt;

&lt;p&gt;Before the agent acts, it should know:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I am operating account &lt;code&gt;acct_us_042&lt;/code&gt;, inside profile &lt;code&gt;profile_us_042&lt;/code&gt;, using a US proxy, with New York timezone, in headed mode, and I must stop before verification or payment actions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is very different from:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Open Chromium and do the task.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Key fields in the manifest
&lt;/h2&gt;

&lt;p&gt;A useful &lt;strong&gt;Account Context Manifest&lt;/strong&gt; does not need to be complex.&lt;/p&gt;

&lt;p&gt;But a few fields should be treated as first-class execution inputs.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;th&gt;Why it matters&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;account_id&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The business or platform account being used&lt;/td&gt;
&lt;td&gt;Prevents account confusion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;profile_id&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The browser profile assigned to the account&lt;/td&gt;
&lt;td&gt;Keeps browser identity consistent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;profile_path&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Local path for persistent browser context&lt;/td&gt;
&lt;td&gt;Allows repeatable, long-lived sessions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;proxy.id&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The selected proxy route&lt;/td&gt;
&lt;td&gt;Makes network identity auditable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;proxy.timezone&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Expected timezone for the proxy region&lt;/td&gt;
&lt;td&gt;Reduces environment mismatch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;proxy.locale&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Expected language and locale&lt;/td&gt;
&lt;td&gt;Keeps browser behavior consistent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;browser.mode&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;headed&lt;/code&gt; or &lt;code&gt;headless&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Helps reproduce failures&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;persistent_context&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Whether a long-lived profile is used&lt;/td&gt;
&lt;td&gt;Keeps session continuity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;allowed_tasks&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Tasks this account can run&lt;/td&gt;
&lt;td&gt;Prevents accidental misuse&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;requires_human_review&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Actions where the agent must stop&lt;/td&gt;
&lt;td&gt;Adds safety boundaries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;evidence&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Logs, screenshots, snapshots&lt;/td&gt;
&lt;td&gt;Makes debugging possible&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These fields turn hidden assumptions into explicit constraints.&lt;/p&gt;

&lt;p&gt;That is the main value of the manifest.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why &lt;strong&gt;storageState&lt;/strong&gt; alone is not enough
&lt;/h2&gt;

&lt;p&gt;Playwright’s &lt;strong&gt;storageState&lt;/strong&gt; is useful.&lt;/p&gt;

&lt;p&gt;It can save cookies and local storage. It can help skip repeated login steps. For many testing workflows, that is exactly what you need.&lt;/p&gt;

&lt;p&gt;But &lt;strong&gt;storageState&lt;/strong&gt; is only a snapshot of part of the browser state.&lt;/p&gt;

&lt;p&gt;It does not describe the full account environment.&lt;/p&gt;

&lt;p&gt;For example, &lt;strong&gt;storageState&lt;/strong&gt; does not tell you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;which long-lived &lt;strong&gt;browser profile&lt;/strong&gt; the account belongs to&lt;/li&gt;
&lt;li&gt;whether the workflow was run in &lt;strong&gt;headed&lt;/strong&gt; or &lt;strong&gt;headless&lt;/strong&gt; mode&lt;/li&gt;
&lt;li&gt;which &lt;strong&gt;proxy&lt;/strong&gt; was used&lt;/li&gt;
&lt;li&gt;whether the proxy region matched the &lt;strong&gt;timezone&lt;/strong&gt; and &lt;strong&gt;locale&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;whether required &lt;strong&gt;extensions&lt;/strong&gt; were available&lt;/li&gt;
&lt;li&gt;whether the run required a &lt;strong&gt;human review&lt;/strong&gt; point&lt;/li&gt;
&lt;li&gt;whether the run produced useful &lt;strong&gt;debugging evidence&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So storageState is helpful, but it should not be the only source of truth for account-based automation.&lt;/p&gt;

&lt;p&gt;The manifest gives the state file context.&lt;/p&gt;

&lt;p&gt;Instead of treating a state file as a magic login shortcut, the agent treats it as one part of a larger &lt;strong&gt;account environment&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  A minimal &lt;strong&gt;Playwright&lt;/strong&gt; implementation
&lt;/h2&gt;

&lt;p&gt;Here is a simple TypeScript example using &lt;strong&gt;Playwright&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The point is not to build a full production system.&lt;/p&gt;

&lt;p&gt;The point is to show that a browser run should begin by loading &lt;strong&gt;account context&lt;/strong&gt;, not by launching a random browser.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&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="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;fs&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;fs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;AccountManifest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;account_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;profile_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;profile_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;country&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="nl"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;headed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;headless&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;persistent_context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;extensions_required&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="nl"&gt;workflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;allowed_tasks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="nl"&gt;requires_human_review&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="nl"&gt;evidence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;save_screenshot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;save_dom_snapshot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;log_proxy_check&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;manifest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AccountManifest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./manifests/acct_us_042.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;utf-8&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;function&lt;/span&gt; &lt;span class="nf"&gt;assertTaskAllowed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;taskName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;manifest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AccountManifest&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="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;manifest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;workflow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;allowed_tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;taskName&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="s2"&gt;`Task "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;taskName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" is not allowed for account &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;manifest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;account_id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;taskName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;page-inspection&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;assertTaskAllowed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;taskName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;manifest&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="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;launchPersistentContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;manifest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;profile_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;headless&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;manifest&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="nx"&gt;mode&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;headless&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;timezoneId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;manifest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;manifest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;locale&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="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="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;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;browser_context_started&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;account_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;manifest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;profile_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;manifest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;profile_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;profile_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;manifest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;profile_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;proxy_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;manifest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;manifest&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="nx"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;taskName&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;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://example.com&lt;/span&gt;&lt;span class="dl"&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;manifest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;evidence&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;save_screenshot&lt;/span&gt;&lt;span class="p"&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;screenshot&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="s2"&gt;`./evidence/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;manifest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;account_id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;taskName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.png`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;fullPage&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="p"&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;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A production version should do more:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;validate the manifest with a schema&lt;/li&gt;
&lt;li&gt;check proxy availability before starting&lt;/li&gt;
&lt;li&gt;confirm account identity after login&lt;/li&gt;
&lt;li&gt;save structured logs&lt;/li&gt;
&lt;li&gt;encrypt sensitive fields&lt;/li&gt;
&lt;li&gt;separate read-only tasks from account-changing tasks&lt;/li&gt;
&lt;li&gt;stop before risky actions&lt;/li&gt;
&lt;li&gt;require review for sensitive workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But even this minimal version creates a better habit:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The agent reads the account context before it touches the browser.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Add schema validation before execution
&lt;/h2&gt;

&lt;p&gt;If an AI agent can trigger browser workflows, the manifest should be validated before execution.&lt;/p&gt;

&lt;p&gt;A lightweight schema can prevent avoidable mistakes.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&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;z&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;zod&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;fs&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;fs&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;ManifestSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;account_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;profile_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;profile_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

  &lt;span class="na"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;country&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;

  &lt;span class="na"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;headed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;headless&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="na"&gt;persistent_context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;extensions_required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;

  &lt;span class="na"&gt;workflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;allowed_tasks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
    &lt;span class="na"&gt;requires_human_review&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;

  &lt;span class="na"&gt;evidence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;save_screenshot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;save_dom_snapshot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;log_proxy_check&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;boolean&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;manifest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ManifestSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./manifests/acct_us_042.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;utf-8&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is especially useful when manifests are generated by another system, edited by operators, or selected by an AI agent.&lt;/p&gt;

&lt;p&gt;Do not let the agent guess the environment.&lt;/p&gt;

&lt;p&gt;Make the environment explicit.&lt;/p&gt;

&lt;p&gt;Validate it first.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pre-run checks before the agent acts
&lt;/h2&gt;

&lt;p&gt;Before an AI browser agent starts a workflow, it should pass a few basic checks.&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;Why it matters&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Is the selected &lt;strong&gt;profile&lt;/strong&gt; the expected one?&lt;/td&gt;
&lt;td&gt;Prevents the agent from using a clean or wrong browser profile&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Does the &lt;strong&gt;proxy region&lt;/strong&gt; match timezone and locale?&lt;/td&gt;
&lt;td&gt;Reduces inconsistent execution environments&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Is the run &lt;strong&gt;headed&lt;/strong&gt; or &lt;strong&gt;headless&lt;/strong&gt;?&lt;/td&gt;
&lt;td&gt;Makes failures easier to reproduce&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Is this &lt;strong&gt;task allowed&lt;/strong&gt; for this account?&lt;/td&gt;
&lt;td&gt;Prevents unsafe or unintended actions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Are cookies and local storage from the expected profile?&lt;/td&gt;
&lt;td&gt;Avoids state confusion between accounts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Does the workflow require &lt;strong&gt;human review&lt;/strong&gt;?&lt;/td&gt;
&lt;td&gt;Stops the agent before sensitive steps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Will &lt;strong&gt;evidence&lt;/strong&gt; be saved?&lt;/td&gt;
&lt;td&gt;Makes debugging possible after failure&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These checks are not glamorous.&lt;/p&gt;

&lt;p&gt;But they prevent a common failure mode in AI automation:&lt;/p&gt;

&lt;p&gt;The agent completes a task, but nobody can explain the environment in which it happened.&lt;/p&gt;




&lt;h2&gt;
  
  
  Common failure modes the manifest prevents
&lt;/h2&gt;

&lt;p&gt;A manifest is useful because it turns hidden assumptions into visible inputs.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Failure mode&lt;/th&gt;
&lt;th&gt;Without manifest&lt;/th&gt;
&lt;th&gt;With manifest&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Wrong proxy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A script runs with a random proxy flag&lt;/td&gt;
&lt;td&gt;Proxy is bound to account context&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Wrong browser profile&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The agent opens a clean browser&lt;/td&gt;
&lt;td&gt;The agent launches the expected persistent profile&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Headed/headless mismatch&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Failure is hard to reproduce&lt;/td&gt;
&lt;td&gt;Execution mode is logged&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cookie confusion&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;State files are reused blindly&lt;/td&gt;
&lt;td&gt;State file is tied to account and profile&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Unsafe retry&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Agent repeats account-changing actions&lt;/td&gt;
&lt;td&gt;Workflow rules define review boundaries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Weak debugging&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;“It worked yesterday”&lt;/td&gt;
&lt;td&gt;Logs show account, profile, proxy, task, and evidence&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The manifest does not remove the need for testing.&lt;/p&gt;

&lt;p&gt;It gives your tests, agents, and human reviewers a shared language.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where &lt;strong&gt;MCP&lt;/strong&gt; and browser agents fit
&lt;/h2&gt;

&lt;p&gt;Browser &lt;strong&gt;MCP servers&lt;/strong&gt; and AI agents are useful because they make browser actions easier to expose as tools.&lt;/p&gt;

&lt;p&gt;An agent can navigate, click, read the page, summarize content, and decide the next step.&lt;/p&gt;

&lt;p&gt;But MCP does not automatically solve &lt;strong&gt;account context&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The agent still needs to know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;which browser target it is allowed to use&lt;/li&gt;
&lt;li&gt;which &lt;strong&gt;profile&lt;/strong&gt; belongs to the account&lt;/li&gt;
&lt;li&gt;which &lt;strong&gt;workflow&lt;/strong&gt; it is allowed to run&lt;/li&gt;
&lt;li&gt;which actions require &lt;strong&gt;human review&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;which logs or screenshots should be saved&lt;/li&gt;
&lt;li&gt;which environment assumptions should not change&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without that boundary, the agent is improvising.&lt;/p&gt;

&lt;p&gt;With a manifest, the agent is still flexible, but it operates inside a defined environment.&lt;/p&gt;

&lt;p&gt;That is the difference between &lt;strong&gt;browser access&lt;/strong&gt; and &lt;strong&gt;account-aware execution&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Keep sensitive data out of the manifest
&lt;/h2&gt;

&lt;p&gt;One important rule:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do not put secrets directly into the manifest.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Avoid storing raw passwords, private keys, seed phrases, API keys, or payment credentials in a plain JSON file.&lt;/p&gt;

&lt;p&gt;Instead, the manifest should reference secure storage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"account_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"acct_us_042"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"secrets"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"password_ref"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vault://accounts/acct_us_042/password"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"api_key_ref"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vault://accounts/acct_us_042/api-key"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The manifest should describe the account environment.&lt;/p&gt;

&lt;p&gt;It should not become a secret dump.&lt;/p&gt;

&lt;p&gt;For teams, this distinction matters a lot.&lt;/p&gt;

&lt;p&gt;The more agents, profiles, and workflows you add, the more important it becomes to separate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;identity metadata&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;browser state&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;secrets&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;permissions&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;audit logs&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A manifest can connect these pieces without exposing all of them in one file.&lt;/p&gt;




&lt;h2&gt;
  
  
  When a manifest becomes a workspace
&lt;/h2&gt;

&lt;p&gt;A manifest works well when you have a few accounts and a disciplined team.&lt;/p&gt;

&lt;p&gt;But as the system grows, you may start to manage:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;many &lt;strong&gt;browser profiles&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;many &lt;strong&gt;proxy routes&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;persistent sessions&lt;/li&gt;
&lt;li&gt;headed and headless workflows&lt;/li&gt;
&lt;li&gt;AI agent tasks&lt;/li&gt;
&lt;li&gt;browser MCP tools&lt;/li&gt;
&lt;li&gt;human review points&lt;/li&gt;
&lt;li&gt;recurring logs and screenshots&lt;/li&gt;
&lt;li&gt;team permissions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At that point, the manifest often becomes part of a larger operating layer.&lt;/p&gt;

&lt;p&gt;Some teams build this internally. Others use a &lt;a href="https://web4browser.io/" rel="noopener noreferrer"&gt;&lt;strong&gt;browser automation workspace for account context&lt;/strong&gt;&lt;/a&gt; so that profiles, proxies, automation tasks, and review evidence stay connected instead of being scattered across scripts and folders.&lt;/p&gt;

&lt;p&gt;The important idea is not the tool name.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Account identity, browser state, proxy mapping, automation logic, and evidence should not be separated after the workflow starts.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;They should be connected before the agent acts.&lt;/p&gt;




&lt;h2&gt;
  
  
  A practical starting template
&lt;/h2&gt;

&lt;p&gt;If you are building your own system, start small.&lt;/p&gt;

&lt;p&gt;Create one manifest per account:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"account_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"acct_example_001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"profile_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"profile_example_001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"profile_path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./profiles/acct_example_001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"proxy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"proxy_001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"US"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"timezone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"America/New_York"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"locale"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"en-US"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"browser"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"headed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"persistent_context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"workflow"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"allowed_tasks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"login-check"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"read-only-inspection"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"requires_human_review"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"verification"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"payment"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"settings-change"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"evidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"save_screenshot"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"save_dom_snapshot"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"log_proxy_check"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add three rules:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Every browser run must load a &lt;strong&gt;manifest&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Every manifest must be &lt;strong&gt;validated&lt;/strong&gt; before execution.&lt;/li&gt;
&lt;li&gt;Every run must log the &lt;strong&gt;account ID&lt;/strong&gt;, &lt;strong&gt;profile ID&lt;/strong&gt;, &lt;strong&gt;proxy ID&lt;/strong&gt;, &lt;strong&gt;task name&lt;/strong&gt;, and &lt;strong&gt;execution mode&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That alone will make many automation failures easier to understand.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final takeaway
&lt;/h2&gt;

&lt;p&gt;AI browser agents do not only need browser access.&lt;/p&gt;

&lt;p&gt;They need &lt;strong&gt;account context&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Before scaling browser automation, define the &lt;strong&gt;account identity&lt;/strong&gt;, &lt;strong&gt;browser profile&lt;/strong&gt;, &lt;strong&gt;proxy mapping&lt;/strong&gt;, &lt;strong&gt;workflow boundary&lt;/strong&gt;, and &lt;strong&gt;evidence trail&lt;/strong&gt; as first-class execution inputs.&lt;/p&gt;

&lt;p&gt;A simple manifest is a good start.&lt;/p&gt;

&lt;p&gt;When the manifest becomes too hard to maintain manually, move the same logic into a shared workspace.&lt;/p&gt;

&lt;p&gt;The goal is not just to make agents click faster.&lt;/p&gt;

&lt;p&gt;The goal is to make browser automation &lt;strong&gt;reproducible&lt;/strong&gt;, &lt;strong&gt;reviewable&lt;/strong&gt;, and &lt;strong&gt;safe enough for real account workflows&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Related reading:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you are deciding how to manage login state in Playwright, this breakdown of &lt;a href="https://dev.to/web4browser/playwright-storagestate-vs-persistent-context-which-one-should-you-use-for-multi-account-k86"&gt;&lt;strong&gt;storageState vs persistent context&lt;/strong&gt;&lt;/a&gt; may be useful.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>playwright</category>
      <category>automation</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Playwright storageState vs Persistent Context: Which One Should You Use for Multi-Account Automation?</title>
      <dc:creator>web4browser</dc:creator>
      <pubDate>Thu, 14 May 2026 07:28:06 +0000</pubDate>
      <link>https://dev.to/web4browser/playwright-storagestate-vs-persistent-context-which-one-should-you-use-for-multi-account-k86</link>
      <guid>https://dev.to/web4browser/playwright-storagestate-vs-persistent-context-which-one-should-you-use-for-multi-account-k86</guid>
      <description>&lt;p&gt;Many &lt;strong&gt;Playwright&lt;/strong&gt; users start with a simple goal:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Save the login state so the script does not need to log in every time.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Playwright makes that easy with &lt;strong&gt;&lt;code&gt;storageState&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For testing, that is often enough. You log in once, save the &lt;strong&gt;cookies&lt;/strong&gt; and &lt;strong&gt;local storage&lt;/strong&gt;, then reuse that state in future test runs.&lt;/p&gt;

&lt;p&gt;But in &lt;strong&gt;multi-account automation&lt;/strong&gt;, the question becomes more complicated.&lt;/p&gt;

&lt;p&gt;You are no longer only asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How do I skip the login step?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You are asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What kind of &lt;strong&gt;browser identity&lt;/strong&gt; does this account need to keep working safely and consistently?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is where the difference between &lt;strong&gt;&lt;code&gt;storageState&lt;/code&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;code&gt;persistent context&lt;/code&gt;&lt;/strong&gt; matters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;storageState&lt;/code&gt;&lt;/strong&gt; is great when you need a &lt;strong&gt;login shortcut&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;persistent context&lt;/code&gt;&lt;/strong&gt; is better when the account needs &lt;strong&gt;long-lived browser continuity&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This article explains where each one fits, where each one starts to break down, and how to choose between them when you are managing &lt;strong&gt;multiple accounts&lt;/strong&gt;, &lt;strong&gt;proxies&lt;/strong&gt;, &lt;strong&gt;browser profiles&lt;/strong&gt;, and recurring automation tasks.&lt;/p&gt;




&lt;h2&gt;
  
  
  The short answer
&lt;/h2&gt;

&lt;p&gt;Use &lt;strong&gt;&lt;code&gt;storageState&lt;/code&gt;&lt;/strong&gt; when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you are running &lt;strong&gt;repeatable tests&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;the account state is simple&lt;/li&gt;
&lt;li&gt;login is only needed as a shortcut&lt;/li&gt;
&lt;li&gt;each run can start from a mostly clean browser context&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;cookies&lt;/strong&gt; and &lt;strong&gt;local storage&lt;/strong&gt; are enough&lt;/li&gt;
&lt;li&gt;the app under test is predictable&lt;/li&gt;
&lt;li&gt;the account does not need long-term browser history&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use &lt;strong&gt;&lt;code&gt;persistent context&lt;/code&gt;&lt;/strong&gt; when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the account needs &lt;strong&gt;long-lived browser history&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;the same account returns repeatedly&lt;/li&gt;
&lt;li&gt;extensions matter&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IndexedDB&lt;/strong&gt; or cache matters&lt;/li&gt;
&lt;li&gt;browser permissions matter&lt;/li&gt;
&lt;li&gt;the profile is tied to a &lt;strong&gt;proxy&lt;/strong&gt; or region&lt;/li&gt;
&lt;li&gt;human review and automation share the same environment&lt;/li&gt;
&lt;li&gt;you need to debug account behavior across runs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A simple rule:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Use &lt;strong&gt;&lt;code&gt;storageState&lt;/code&gt;&lt;/strong&gt; for test login shortcuts. Use &lt;strong&gt;&lt;code&gt;persistent context&lt;/code&gt;&lt;/strong&gt; for account continuity.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  What storageState actually saves
&lt;/h2&gt;

&lt;p&gt;In Playwright, &lt;strong&gt;&lt;code&gt;storageState&lt;/code&gt;&lt;/strong&gt; lets you save and reuse browser storage for a context.&lt;/p&gt;

&lt;p&gt;A common pattern looks like this:&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;launch&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="k"&gt;await&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;newContext&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="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="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://example.com/login&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Perform login here.&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;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;account-a.json&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;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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then later:&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;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;launch&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="k"&gt;await&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;newContext&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;storageState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;account-a.json&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;page&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="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://example.com/dashboard&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;This is clean and useful.&lt;/p&gt;

&lt;p&gt;But it is important to understand what you are saving.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;storageState&lt;/code&gt;&lt;/strong&gt; mainly captures:&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;local storage&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is enough for many web app tests.&lt;/p&gt;

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

&lt;p&gt;It does not represent everything a normal browser session may accumulate over time.&lt;/p&gt;

&lt;p&gt;It should not be treated as a complete &lt;strong&gt;account environment&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where storageState works well
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;storageState&lt;/code&gt;&lt;/strong&gt; works very well when your goal is &lt;strong&gt;test repeatability&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For example, it is a good fit for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CI tests&lt;/li&gt;
&lt;li&gt;login shortcuts&lt;/li&gt;
&lt;li&gt;role-based testing&lt;/li&gt;
&lt;li&gt;admin and user test accounts&lt;/li&gt;
&lt;li&gt;short-lived browser flows&lt;/li&gt;
&lt;li&gt;predictable web applications&lt;/li&gt;
&lt;li&gt;tests where the browser starts clean each time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Imagine you are testing an internal dashboard.&lt;/p&gt;

&lt;p&gt;You have three roles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;admin&lt;/li&gt;
&lt;li&gt;editor&lt;/li&gt;
&lt;li&gt;viewer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can save one state file for each role:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;states/
  admin.json
  editor.json
  viewer.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then use them in different test suites:&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;adminContext&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;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newContext&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;storageState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;states/admin.json&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;That is exactly where &lt;strong&gt;&lt;code&gt;storageState&lt;/code&gt;&lt;/strong&gt; shines.&lt;/p&gt;

&lt;p&gt;It gives you a fast, repeatable way to skip login without carrying around unnecessary browser history.&lt;/p&gt;

&lt;p&gt;For &lt;strong&gt;test automation&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;storageState&lt;/code&gt;&lt;/strong&gt; is often the right level of state.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where storageState starts to break down
&lt;/h2&gt;

&lt;p&gt;The problem is not that &lt;strong&gt;&lt;code&gt;storageState&lt;/code&gt;&lt;/strong&gt; is broken.&lt;/p&gt;

&lt;p&gt;The problem is that teams often ask it to behave like a full &lt;strong&gt;browser profile&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That starts to create trouble in &lt;strong&gt;multi-account automation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one &lt;strong&gt;&lt;code&gt;storageState&lt;/code&gt;&lt;/strong&gt; file is reused across multiple accounts&lt;/li&gt;
&lt;li&gt;a state file created in one &lt;strong&gt;proxy region&lt;/strong&gt; is reused in another region&lt;/li&gt;
&lt;li&gt;a saved login state is old but the script still trusts it&lt;/li&gt;
&lt;li&gt;the account depends on &lt;strong&gt;IndexedDB&lt;/strong&gt; or cache data&lt;/li&gt;
&lt;li&gt;the task needs browser extensions&lt;/li&gt;
&lt;li&gt;the account was operated manually yesterday&lt;/li&gt;
&lt;li&gt;headless automation uses state created from a headed login&lt;/li&gt;
&lt;li&gt;the script does not know which &lt;strong&gt;profile&lt;/strong&gt; or &lt;strong&gt;proxy&lt;/strong&gt; created the state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In those situations, the saved state may technically load, but the account behavior can still look wrong.&lt;/p&gt;

&lt;p&gt;The site may ask for verification.&lt;/p&gt;

&lt;p&gt;The session may disappear.&lt;/p&gt;

&lt;p&gt;The page may redirect back to login.&lt;/p&gt;

&lt;p&gt;The account may behave differently in &lt;strong&gt;headless mode&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The team may not know whether the problem came from &lt;strong&gt;cookies&lt;/strong&gt;, &lt;strong&gt;proxy region&lt;/strong&gt;, &lt;strong&gt;profile history&lt;/strong&gt;, or &lt;strong&gt;execution mode&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That is the real issue.&lt;/p&gt;

&lt;p&gt;A state file without context becomes hard to trust.&lt;/p&gt;




&lt;h2&gt;
  
  
  storageState is a snapshot, not an identity
&lt;/h2&gt;

&lt;p&gt;A useful way to think about &lt;strong&gt;&lt;code&gt;storageState&lt;/code&gt;&lt;/strong&gt; is this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;storageState&lt;/code&gt;&lt;/strong&gt; is a snapshot. It is not the full identity of the account.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A snapshot can be useful.&lt;/p&gt;

&lt;p&gt;But a snapshot does not explain its own history.&lt;/p&gt;

&lt;p&gt;For example, this file name tells you almost nothing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;login.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is better:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;account-a-us-headed-2026-05-14.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And this is better still:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"state_file"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"account-a-us-headed-2026-05-14.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"account_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"account-a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"profile_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"profile-a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"proxy_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"proxy-us-01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"proxy_region"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"US"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"created_from"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"headed-login"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"created_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-05-14T09:20:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"last_successful_run"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-05-14T10:05:00Z"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can ask useful debugging questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which account created this state?&lt;/li&gt;
&lt;li&gt;Which proxy was used?&lt;/li&gt;
&lt;li&gt;Was it created in &lt;strong&gt;headed&lt;/strong&gt; or &lt;strong&gt;headless&lt;/strong&gt; mode?&lt;/li&gt;
&lt;li&gt;Was the account verified after this state was saved?&lt;/li&gt;
&lt;li&gt;Has the proxy region changed since then?&lt;/li&gt;
&lt;li&gt;Is the script using the same profile assumptions?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without that information, &lt;strong&gt;&lt;code&gt;storageState&lt;/code&gt;&lt;/strong&gt; becomes a loose file that people pass around until something breaks.&lt;/p&gt;




&lt;h2&gt;
  
  
  What persistent context gives you
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;persistent context&lt;/strong&gt; is different.&lt;/p&gt;

&lt;p&gt;Instead of creating a temporary browser context and injecting a saved state file, you launch a browser context with a real &lt;strong&gt;user data directory&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;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="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;context&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;launchPersistentContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./profiles/account-a&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;headless&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&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="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="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://example.com&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;context&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The folder becomes the long-lived &lt;strong&gt;browser profile&lt;/strong&gt; for that account.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;persistent context&lt;/strong&gt; can preserve more browser behavior across runs, including things that may not fit neatly into a simple state file.&lt;/p&gt;

&lt;p&gt;Depending on the browser and site behavior, this may include:&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;local storage&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IndexedDB&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;cache&lt;/li&gt;
&lt;li&gt;permissions&lt;/li&gt;
&lt;li&gt;browsing state&lt;/li&gt;
&lt;li&gt;extension-related state&lt;/li&gt;
&lt;li&gt;repeated account history&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This does not mean &lt;strong&gt;persistent context&lt;/strong&gt; magically solves every automation problem.&lt;/p&gt;

&lt;p&gt;It does not guarantee trust.&lt;/p&gt;

&lt;p&gt;It does not guarantee that an account will never be challenged.&lt;/p&gt;

&lt;p&gt;It does not replace good &lt;strong&gt;proxy&lt;/strong&gt;, &lt;strong&gt;timezone&lt;/strong&gt;, &lt;strong&gt;locale&lt;/strong&gt;, and behavior management.&lt;/p&gt;

&lt;p&gt;But it gives you a more realistic long-lived browser environment than a temporary context with a small login snapshot.&lt;/p&gt;




&lt;h2&gt;
  
  
  When persistent context is the better choice
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Persistent context&lt;/strong&gt; is usually the better choice when the account itself has &lt;strong&gt;operational history&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That includes workflows like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;recurring account checks&lt;/li&gt;
&lt;li&gt;social media account handling&lt;/li&gt;
&lt;li&gt;marketplace account operation&lt;/li&gt;
&lt;li&gt;proxy-aware browser workflows&lt;/li&gt;
&lt;li&gt;Web3 wallet workflows&lt;/li&gt;
&lt;li&gt;extension-based automation&lt;/li&gt;
&lt;li&gt;human handoff&lt;/li&gt;
&lt;li&gt;AI agent tasks that need continuity&lt;/li&gt;
&lt;li&gt;long-running multi-account tasks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In these workflows, the account is not just a login token.&lt;/p&gt;

&lt;p&gt;It has an environment.&lt;/p&gt;

&lt;p&gt;It may have a normal region.&lt;/p&gt;

&lt;p&gt;It may have permissions.&lt;/p&gt;

&lt;p&gt;It may have extension state.&lt;/p&gt;

&lt;p&gt;It may have a browser history pattern.&lt;/p&gt;

&lt;p&gt;It may be reviewed by a human operator.&lt;/p&gt;

&lt;p&gt;It may be reused by an automation task later.&lt;/p&gt;

&lt;p&gt;That is where a real &lt;strong&gt;profile directory&lt;/strong&gt; becomes easier to reason about.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;profiles/
  account-a/
  account-b/
  account-c/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each account gets its own profile folder.&lt;/p&gt;

&lt;p&gt;You can still export &lt;strong&gt;&lt;code&gt;storageState&lt;/code&gt;&lt;/strong&gt; when needed, but the profile folder is the long-lived source of continuity.&lt;/p&gt;




&lt;h2&gt;
  
  
  The decision table
&lt;/h2&gt;

&lt;p&gt;Here is a practical way to choose.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Use storageState&lt;/th&gt;
&lt;th&gt;Use persistent context&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CI login shortcut&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Usually no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Short functional test&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Usually no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Role-based app testing&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Usually no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One account, one-off flow&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Maybe&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multiple long-lived accounts&lt;/td&gt;
&lt;td&gt;Risky&lt;/td&gt;
&lt;td&gt;Better&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Proxy-region-bound accounts&lt;/td&gt;
&lt;td&gt;Risky&lt;/td&gt;
&lt;td&gt;Better&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Extension or wallet state&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Better&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Human and automation share one account&lt;/td&gt;
&lt;td&gt;Risky&lt;/td&gt;
&lt;td&gt;Better&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Need full profile continuity&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Better&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Need simple repeatable tests&lt;/td&gt;
&lt;td&gt;Better&lt;/td&gt;
&lt;td&gt;Usually too heavy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Need to debug behavior across runs&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Better&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This table is not a rule of law.&lt;/p&gt;

&lt;p&gt;It is a starting point.&lt;/p&gt;

&lt;p&gt;If the job is a test, &lt;strong&gt;&lt;code&gt;storageState&lt;/code&gt;&lt;/strong&gt; is probably enough.&lt;/p&gt;

&lt;p&gt;If the job is ongoing &lt;strong&gt;account operation&lt;/strong&gt;, &lt;strong&gt;persistent context&lt;/strong&gt; is usually safer and easier to debug.&lt;/p&gt;




&lt;h2&gt;
  
  
  A safer folder and state model
&lt;/h2&gt;

&lt;p&gt;A simple project structure can prevent many mistakes.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;automation-workspace/
  profiles/
    account-a/
    account-b/
    account-c/

  states/
    account-a-login.json
    account-b-login.json
    account-c-login.json

  logs/
    account-a/
      2026-05-14-login-check.json
    account-b/
      2026-05-14-login-check.json

  proxy-map.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The idea is simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;profiles/&lt;/code&gt;&lt;/strong&gt; stores long-lived browser profiles&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;states/&lt;/code&gt;&lt;/strong&gt; stores login snapshots&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;logs/&lt;/code&gt;&lt;/strong&gt; stores run history&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;proxy-map.json&lt;/code&gt;&lt;/strong&gt; records which proxy belongs to which account&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A proxy map might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"account-a"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"profile"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"profiles/account-a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"proxy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"proxy-us-01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"region"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"US"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"timezone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"America/New_York"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"locale"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"en-US"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"account-b"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"profile"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"profiles/account-b"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"proxy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"proxy-de-01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"region"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"timezone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Europe/Berlin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"locale"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"de-DE"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you a clear relationship between &lt;strong&gt;account&lt;/strong&gt;, &lt;strong&gt;profile&lt;/strong&gt;, &lt;strong&gt;proxy&lt;/strong&gt;, &lt;strong&gt;region&lt;/strong&gt;, &lt;strong&gt;timezone&lt;/strong&gt;, and &lt;strong&gt;locale&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That relationship matters more as automation becomes more complex.&lt;/p&gt;




&lt;h2&gt;
  
  
  Do not mix account state by accident
&lt;/h2&gt;

&lt;p&gt;A common mistake is to think only about scripts.&lt;/p&gt;

&lt;p&gt;But in &lt;strong&gt;multi-account automation&lt;/strong&gt;, folder discipline matters too.&lt;/p&gt;

&lt;p&gt;Avoid patterns like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;states/
  login.json
  backup.json
  new-login.json
  final-login.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nobody knows which account those files belong to.&lt;/p&gt;

&lt;p&gt;Nobody knows which proxy created them.&lt;/p&gt;

&lt;p&gt;Nobody knows whether they are still valid.&lt;/p&gt;

&lt;p&gt;Use names that carry context:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;states/
  account-a-us-headed-login.json
  account-b-de-headed-login.json
  account-c-sg-headless-check.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same applies to profiles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;profiles/
  account-a/
  account-b/
  account-c/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not share one profile across unrelated accounts.&lt;/p&gt;

&lt;p&gt;Do not let one account accidentally inherit another account’s &lt;strong&gt;storage&lt;/strong&gt;, &lt;strong&gt;cookies&lt;/strong&gt;, &lt;strong&gt;extension state&lt;/strong&gt;, or &lt;strong&gt;permissions&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That is how debugging becomes impossible.&lt;/p&gt;




&lt;h2&gt;
  
  
  Headed and headless should be tracked
&lt;/h2&gt;

&lt;p&gt;Another common mistake is switching between &lt;strong&gt;headed&lt;/strong&gt; and &lt;strong&gt;headless&lt;/strong&gt; mode without recording it.&lt;/p&gt;

&lt;p&gt;For example, the team logs in manually in headed mode:&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;context&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;launchPersistentContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./profiles/account-a&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;headless&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then a scheduled job later runs headless:&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;context&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;launchPersistentContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./profiles/account-a&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;headless&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That may work.&lt;/p&gt;

&lt;p&gt;But if it fails, you need to know the mode changed.&lt;/p&gt;

&lt;p&gt;At minimum, every run log should include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"account_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"account-a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"profile_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"profile-a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"headless"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"proxy_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"proxy-us-01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timezone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"America/New_York"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"locale"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"en-US"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"failed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"failure_step"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dashboard_load"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This helps you avoid vague debugging conversations like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“It worked yesterday.”&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;blockquote&gt;
&lt;p&gt;“What changed between the last successful run and this failed run?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Headed versus headless&lt;/strong&gt; is often one of those changes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Common mistakes
&lt;/h2&gt;

&lt;p&gt;Here are the mistakes that cause the most confusion.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using one storageState file for multiple accounts
&lt;/h3&gt;

&lt;p&gt;This is convenient at first and painful later.&lt;/p&gt;

&lt;p&gt;Each account should have its own &lt;strong&gt;state file&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Saving state from one proxy region and reusing it in another
&lt;/h3&gt;

&lt;p&gt;If an account usually operates from one region, do not casually move its saved state to another region without logging it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Assuming storageState includes everything
&lt;/h3&gt;

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

&lt;p&gt;It is useful, but it is not a full &lt;strong&gt;browser profile&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deleting profile folders without recording why
&lt;/h3&gt;

&lt;p&gt;If you delete a profile folder, you remove history.&lt;/p&gt;

&lt;p&gt;Sometimes that is needed, but it should be intentional.&lt;/p&gt;

&lt;h3&gt;
  
  
  Switching headed and headless without tracking it
&lt;/h3&gt;

&lt;p&gt;A mode change can change behavior.&lt;/p&gt;

&lt;p&gt;Track it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Letting humans and scripts overwrite each other
&lt;/h3&gt;

&lt;p&gt;If a human operator logs in, solves a challenge, changes settings, or updates permissions, the automation log should reflect that.&lt;/p&gt;

&lt;h3&gt;
  
  
  Debugging login failure without knowing which profile was used
&lt;/h3&gt;

&lt;p&gt;If you cannot answer “which profile, which proxy, which state file, which mode,” you are not debugging yet.&lt;/p&gt;

&lt;p&gt;You are guessing.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where a browser workspace becomes useful
&lt;/h2&gt;

&lt;p&gt;Small scripts can manage a few accounts with folders and JSON files.&lt;/p&gt;

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

&lt;p&gt;But once the workflow includes many &lt;strong&gt;profiles&lt;/strong&gt;, &lt;strong&gt;proxies&lt;/strong&gt;, recurring checks, human review, AI-driven steps, and automation logs, the problem becomes harder to manage with scripts alone.&lt;/p&gt;

&lt;p&gt;You need a place to connect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;account identity&lt;/li&gt;
&lt;li&gt;browser profile&lt;/li&gt;
&lt;li&gt;proxy mapping&lt;/li&gt;
&lt;li&gt;storage state&lt;/li&gt;
&lt;li&gt;task history&lt;/li&gt;
&lt;li&gt;manual review&lt;/li&gt;
&lt;li&gt;screenshots&lt;/li&gt;
&lt;li&gt;failure logs&lt;/li&gt;
&lt;li&gt;headed and headless execution&lt;/li&gt;
&lt;li&gt;recurring automation workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is where a &lt;a href="https://web4browser.io/" rel="noopener noreferrer"&gt;browser automation workspace for account context&lt;/a&gt; becomes useful.&lt;/p&gt;

&lt;p&gt;The point is not to replace Playwright.&lt;/p&gt;

&lt;p&gt;The point is to stop treating &lt;strong&gt;profiles&lt;/strong&gt;, &lt;strong&gt;proxies&lt;/strong&gt;, &lt;strong&gt;state files&lt;/strong&gt;, and &lt;strong&gt;logs&lt;/strong&gt; as disconnected pieces.&lt;/p&gt;

&lt;p&gt;When those pieces stay connected, automation becomes easier to debug and safer to operate.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final rule of thumb
&lt;/h2&gt;

&lt;p&gt;Use &lt;strong&gt;&lt;code&gt;storageState&lt;/code&gt;&lt;/strong&gt; when you need a login shortcut.&lt;/p&gt;

&lt;p&gt;Use &lt;strong&gt;&lt;code&gt;persistent context&lt;/code&gt;&lt;/strong&gt; when the account needs continuity.&lt;/p&gt;

&lt;p&gt;Use a &lt;strong&gt;browser workspace&lt;/strong&gt; when many accounts, proxies, tasks, and logs need to stay connected.&lt;/p&gt;

&lt;p&gt;The mistake is not choosing one Playwright API over another.&lt;/p&gt;

&lt;p&gt;The mistake is failing to define what the account needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a short-lived test state&lt;/li&gt;
&lt;li&gt;a reusable login snapshot&lt;/li&gt;
&lt;li&gt;a long-lived browser profile&lt;/li&gt;
&lt;li&gt;a full operational workspace&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once you know that, the technical choice becomes much easier.&lt;/p&gt;

&lt;p&gt;For more notes on browser automation, profile workflows, and account-context debugging, see these &lt;a href="https://web4browser.io/blog/" rel="noopener noreferrer"&gt;more browser automation and profile workflow notes&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>playwright</category>
      <category>testing</category>
      <category>automation</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Playwright Proxy and Browser Profile Debugging: A Practical Checklist for Multi-Account Automation</title>
      <dc:creator>web4browser</dc:creator>
      <pubDate>Wed, 13 May 2026 06:22:08 +0000</pubDate>
      <link>https://dev.to/web4browser/playwright-proxy-and-browser-profile-debugging-a-practical-checklist-for-multi-account-automation-3mc1</link>
      <guid>https://dev.to/web4browser/playwright-proxy-and-browser-profile-debugging-a-practical-checklist-for-multi-account-automation-3mc1</guid>
      <description>&lt;p&gt;A &lt;strong&gt;Playwright script&lt;/strong&gt; can pass every local test and still fail the moment you add a &lt;strong&gt;proxy&lt;/strong&gt;, switch accounts, run &lt;strong&gt;headless&lt;/strong&gt;, or reuse a saved session.&lt;/p&gt;

&lt;p&gt;That does not always mean the proxy is bad.&lt;/p&gt;

&lt;p&gt;In &lt;strong&gt;multi-account automation&lt;/strong&gt;, the browser is not just a runtime. It becomes part of the account’s identity. The &lt;strong&gt;proxy&lt;/strong&gt;, &lt;strong&gt;browser profile&lt;/strong&gt;, &lt;strong&gt;cookies&lt;/strong&gt;, &lt;strong&gt;local storage&lt;/strong&gt;, &lt;strong&gt;timezone&lt;/strong&gt;, &lt;strong&gt;locale&lt;/strong&gt;, &lt;strong&gt;WebRTC behavior&lt;/strong&gt;, &lt;strong&gt;viewport&lt;/strong&gt;, and &lt;strong&gt;execution mode&lt;/strong&gt; all need to make sense together.&lt;/p&gt;

&lt;p&gt;When one piece changes without the others, failures often look like ordinary automation bugs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;407 proxy authentication errors&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;403 responses&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;endless login loops&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;sudden verification prompts&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;sessions that work in Chrome but fail in Playwright&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;headed mode&lt;/strong&gt; working while &lt;strong&gt;headless mode&lt;/strong&gt; breaks&lt;/li&gt;
&lt;li&gt;accounts becoming suspicious after repeated retries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This checklist is for debugging those failures without guessing.&lt;/p&gt;




&lt;h2&gt;
  
  
  The common trap: treating every proxy failure as a network failure
&lt;/h2&gt;

&lt;p&gt;When automation fails after adding a proxy, the first reaction is usually:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“The proxy is dead. Try another one.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sometimes that is true.&lt;/p&gt;

&lt;p&gt;But in &lt;strong&gt;multi-account browser automation&lt;/strong&gt;, the proxy is only one layer. The real problem may be a mismatch between the account and the browser environment around it.&lt;/p&gt;

&lt;p&gt;Before replacing the proxy, separate the failure into layers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can the proxy connect at all?&lt;/li&gt;
&lt;li&gt;Is &lt;strong&gt;proxy authentication&lt;/strong&gt; working?&lt;/li&gt;
&lt;li&gt;Does the &lt;strong&gt;exit IP&lt;/strong&gt; match the expected region?&lt;/li&gt;
&lt;li&gt;Does the &lt;strong&gt;browser profile&lt;/strong&gt; match the account?&lt;/li&gt;
&lt;li&gt;Are &lt;strong&gt;cookies&lt;/strong&gt; and &lt;strong&gt;storage state&lt;/strong&gt; from the same account?&lt;/li&gt;
&lt;li&gt;Does &lt;strong&gt;timezone&lt;/strong&gt; match the proxy region?&lt;/li&gt;
&lt;li&gt;Does &lt;strong&gt;locale&lt;/strong&gt; match the expected browser environment?&lt;/li&gt;
&lt;li&gt;Is &lt;strong&gt;WebRTC&lt;/strong&gt; exposing another network path?&lt;/li&gt;
&lt;li&gt;Does behavior change between &lt;strong&gt;headed&lt;/strong&gt; and &lt;strong&gt;headless&lt;/strong&gt; mode?&lt;/li&gt;
&lt;li&gt;Are retries reusing a broken or inconsistent state?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A proxy can be working perfectly while the account still fails because the environment around it looks wrong.&lt;/p&gt;




&lt;h2&gt;
  
  
  Start with a failure map
&lt;/h2&gt;

&lt;p&gt;Do not start by changing everything.&lt;/p&gt;

&lt;p&gt;First, classify the failure.&lt;/p&gt;

&lt;h3&gt;
  
  
  The page never loads
&lt;/h3&gt;

&lt;p&gt;This usually points to a lower-level issue.&lt;/p&gt;

&lt;p&gt;Possible causes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Proxy host is unreachable&lt;/li&gt;
&lt;li&gt;Proxy port is wrong&lt;/li&gt;
&lt;li&gt;Proxy credentials are invalid&lt;/li&gt;
&lt;li&gt;Proxy protocol is incorrect&lt;/li&gt;
&lt;li&gt;DNS resolution fails through the proxy&lt;/li&gt;
&lt;li&gt;TLS handshake fails&lt;/li&gt;
&lt;li&gt;The target domain blocks the proxy network&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the easiest category to verify because the browser profile may not even matter yet.&lt;/p&gt;

&lt;h3&gt;
  
  
  The page loads but login fails
&lt;/h3&gt;

&lt;p&gt;Now the problem is probably above the network layer.&lt;/p&gt;

&lt;p&gt;Possible causes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cookies belong to another account&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;storageState&lt;/strong&gt; is expired&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;localStorage&lt;/strong&gt; or &lt;strong&gt;IndexedDB&lt;/strong&gt; is incomplete&lt;/li&gt;
&lt;li&gt;The account was previously used from another region&lt;/li&gt;
&lt;li&gt;The profile changed too much since the last successful login&lt;/li&gt;
&lt;li&gt;The login flow depends on browser state that was not persisted&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where Playwright users often over-trust &lt;strong&gt;storageState&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A saved session is useful, but it is not the same as a long-lived browser identity.&lt;/p&gt;

&lt;h3&gt;
  
  
  The account logs in but triggers verification
&lt;/h3&gt;

&lt;p&gt;This usually means the site accepts the session but does not trust the environment.&lt;/p&gt;

&lt;p&gt;Possible causes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;IP region&lt;/strong&gt; and &lt;strong&gt;timezone&lt;/strong&gt; do not match&lt;/li&gt;
&lt;li&gt;Browser language and account history do not match&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WebRTC&lt;/strong&gt; exposes a different network path&lt;/li&gt;
&lt;li&gt;Viewport or device signals changed suddenly&lt;/li&gt;
&lt;li&gt;The browser profile is reused across unrelated accounts&lt;/li&gt;
&lt;li&gt;Automation timing looks different from normal use&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Headless mode&lt;/strong&gt; exposes different behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At this point, changing proxies randomly can make things worse because every retry creates more unusual account history.&lt;/p&gt;

&lt;h3&gt;
  
  
  Headed mode works but headless mode fails
&lt;/h3&gt;

&lt;p&gt;This is one of the most common automation traps.&lt;/p&gt;

&lt;p&gt;Possible causes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Headless browser signals&lt;/strong&gt; differ from headed mode&lt;/li&gt;
&lt;li&gt;Extensions are unavailable&lt;/li&gt;
&lt;li&gt;Persistent profile data is missing&lt;/li&gt;
&lt;li&gt;Viewport defaults are different&lt;/li&gt;
&lt;li&gt;Fonts or media behavior differ&lt;/li&gt;
&lt;li&gt;Timing changes enough to affect detection or login flows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If &lt;strong&gt;headed mode&lt;/strong&gt; works and &lt;strong&gt;headless mode&lt;/strong&gt; fails, do not assume the site is broken. The account may be reacting to a different browser environment.&lt;/p&gt;




&lt;h2&gt;
  
  
  Verify the proxy before blaming the browser
&lt;/h2&gt;

&lt;p&gt;Start outside Playwright.&lt;/p&gt;

&lt;p&gt;Use a simple request to confirm that the proxy works independently.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-x&lt;/span&gt; http://user:pass@host:port https://api.ipify.org
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then check location metadata:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-x&lt;/span&gt; http://user:pass@host:port https://ipinfo.io/json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You are checking four things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The proxy accepts your credentials&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;exit IP&lt;/strong&gt; is returned consistently&lt;/li&gt;
&lt;li&gt;The region is what you expect&lt;/li&gt;
&lt;li&gt;The proxy can reach the target network&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If this fails outside Playwright, fix the proxy layer first.&lt;/p&gt;

&lt;p&gt;If this works outside Playwright but fails in the browser, the problem is likely in how Playwright is configured or how the browser environment is being created.&lt;/p&gt;

&lt;p&gt;A basic Playwright proxy setup may look like this:&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;launch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;headless&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;server&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://host:port&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pass&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;context&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;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newContext&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="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="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&lt;/span&gt;&lt;span class="dl"&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="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;span class="k"&gt;await&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;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This only confirms that Playwright can route traffic through the proxy.&lt;/p&gt;

&lt;p&gt;It does not confirm that the account environment is safe, stable, or consistent.&lt;/p&gt;




&lt;h2&gt;
  
  
  Check whether the profile matches the account
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;browser profile&lt;/strong&gt; is not just a folder.&lt;/p&gt;

&lt;p&gt;In &lt;strong&gt;multi-account automation&lt;/strong&gt;, the profile is the account’s operating context.&lt;/p&gt;

&lt;p&gt;It may include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cookies&lt;/li&gt;
&lt;li&gt;Local storage&lt;/li&gt;
&lt;li&gt;IndexedDB&lt;/li&gt;
&lt;li&gt;Cache&lt;/li&gt;
&lt;li&gt;Login history&lt;/li&gt;
&lt;li&gt;Extension state&lt;/li&gt;
&lt;li&gt;Wallet state&lt;/li&gt;
&lt;li&gt;Language preferences&lt;/li&gt;
&lt;li&gt;Permission decisions&lt;/li&gt;
&lt;li&gt;Site-specific device assumptions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you use a clean temporary context every time, the site may see the account as constantly returning from a new device.&lt;/p&gt;

&lt;p&gt;If you reuse one context across multiple accounts, the site may see unrelated identities bleeding into each other.&lt;/p&gt;

&lt;p&gt;Both patterns can cause failure.&lt;/p&gt;

&lt;p&gt;A safer model is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"account_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"account-a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"profile_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"profile-a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"proxy_region"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"US"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timezone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"America/New_York"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"locale"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"en-US"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"storage_state"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"account-a-us.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"last_verified"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-05-13"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This does not need to be complicated.&lt;/p&gt;

&lt;p&gt;The important part is that each account has a traceable relationship with its &lt;strong&gt;profile&lt;/strong&gt;, &lt;strong&gt;proxy&lt;/strong&gt;, &lt;strong&gt;region&lt;/strong&gt;, and &lt;strong&gt;storage state&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Do not reuse storage state blindly
&lt;/h2&gt;

&lt;p&gt;Playwright’s &lt;strong&gt;storageState&lt;/strong&gt; is extremely useful for tests.&lt;/p&gt;

&lt;p&gt;But it can become dangerous in &lt;strong&gt;multi-account automation&lt;/strong&gt; when teams treat it as a portable identity file.&lt;/p&gt;

&lt;p&gt;For 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;context&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;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newContext&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;storageState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;account-a.json&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;This may work when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The account is used in the same region&lt;/li&gt;
&lt;li&gt;The browser environment is stable&lt;/li&gt;
&lt;li&gt;The session is fresh&lt;/li&gt;
&lt;li&gt;The site does not require deeper profile continuity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But it can fail when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The same file is reused across accounts&lt;/li&gt;
&lt;li&gt;The account moves between proxy regions&lt;/li&gt;
&lt;li&gt;The browser profile changes too much&lt;/li&gt;
&lt;li&gt;The saved state is old&lt;/li&gt;
&lt;li&gt;The site expects data outside cookies and local storage&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Headless mode&lt;/strong&gt; behaves differently from the original login environment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A better rule:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Treat &lt;strong&gt;storage state&lt;/strong&gt; as one part of &lt;strong&gt;account context&lt;/strong&gt;, not the whole account context.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Track where it came from.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"storage_state_file"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"account-a-us.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"account_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"account-a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"profile_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"profile-a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"proxy_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"proxy-us-01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"created_from"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"headed-login"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"created_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-05-13T10:30:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"last_successful_run"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-05-13T11:10:00Z"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a failure happens, this gives you a way to ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Was this state created with the same proxy?&lt;/li&gt;
&lt;li&gt;Was it created in headed or headless mode?&lt;/li&gt;
&lt;li&gt;Was it created with the same profile assumptions?&lt;/li&gt;
&lt;li&gt;Has the account passed verification since then?&lt;/li&gt;
&lt;li&gt;Did we change only one variable before retrying?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without this record, debugging becomes guesswork.&lt;/p&gt;




&lt;h2&gt;
  
  
  Compare headed, headless, and persistent context results
&lt;/h2&gt;

&lt;p&gt;When failures are unclear, run the same account through three controlled tests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Run headed with the same proxy
&lt;/h3&gt;

&lt;p&gt;Use the same proxy and open the browser visibly.&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;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;launch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;headless&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;server&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://host:port&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pass&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check whether the site loads, whether login works, and whether verification appears.&lt;/p&gt;

&lt;p&gt;If headed mode fails, do not debug headless yet.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Run headless with the same assumptions
&lt;/h3&gt;

&lt;p&gt;Now switch only one variable.&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;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;launch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;headless&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="na"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;server&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://host:port&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pass&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If headed works and headless fails, the issue is not simply the proxy.&lt;/p&gt;

&lt;p&gt;You are debugging an &lt;strong&gt;execution-mode difference&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Test persistent context
&lt;/h3&gt;

&lt;p&gt;Temporary contexts are clean and useful for testing.&lt;/p&gt;

&lt;p&gt;Persistent contexts are closer to real account operation.&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;context&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;launchPersistentContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./profiles/account-a&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;headless&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;server&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://host:port&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pass&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="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="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://example.com&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;A &lt;strong&gt;persistent context&lt;/strong&gt; can preserve browser state across runs, which is often closer to how real accounts behave.&lt;/p&gt;

&lt;p&gt;It also makes profile mistakes easier to detect because each account has an actual profile directory.&lt;/p&gt;




&lt;h2&gt;
  
  
  Check timezone, locale, and IP region together
&lt;/h2&gt;

&lt;p&gt;A common mistake is checking the &lt;strong&gt;exit IP&lt;/strong&gt; but ignoring the rest of the environment.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Proxy region: Germany&lt;/li&gt;
&lt;li&gt;Browser locale: en-US&lt;/li&gt;
&lt;li&gt;Timezone: Asia/Singapore&lt;/li&gt;
&lt;li&gt;Account history: mostly United States&lt;/li&gt;
&lt;li&gt;WebRTC: leaking local network information&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Any single signal may be explainable.&lt;/p&gt;

&lt;p&gt;Together, they may look strange.&lt;/p&gt;

&lt;p&gt;At minimum, log these values for each run:&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;info&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;userAgent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userAgent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;language&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;language&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;languages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;languages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Intl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DateTimeFormat&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;resolvedOptions&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;timeZone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;webdriver&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;webdriver&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;info&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then compare them with the proxy result.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-x&lt;/span&gt; http://user:pass@host:port https://ipinfo.io/json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You are not trying to create a perfect fingerprint.&lt;/p&gt;

&lt;p&gt;You are trying to avoid obvious contradictions.&lt;/p&gt;




&lt;h2&gt;
  
  
  Add logs that explain context, not only errors
&lt;/h2&gt;

&lt;p&gt;Many automation logs are too thin.&lt;/p&gt;

&lt;p&gt;They say:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Login failed.
Timeout.
403.
Navigation error.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is not enough for &lt;strong&gt;multi-account debugging&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A better log should explain the environment around the failure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"task_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"login-check-1842"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"account_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"account-a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"profile_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"profile-a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"proxy_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"proxy-us-01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exit_ip"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"203.0.113.10"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"proxy_region"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"US"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timezone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"America/New_York"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"locale"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"en-US"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"headless"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"storage_state"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"account-a-us.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status_code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"last_successful_step"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"email_submitted"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"failure_step"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"password_submit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"screenshot"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"login-failed.png"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This kind of log lets you compare failures.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Did all failures happen with the same proxy?&lt;/li&gt;
&lt;li&gt;Did only headless runs fail?&lt;/li&gt;
&lt;li&gt;Did one account fail after a storage state update?&lt;/li&gt;
&lt;li&gt;Did verification start after a timezone change?&lt;/li&gt;
&lt;li&gt;Did failures begin after moving profiles between machines?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without &lt;strong&gt;context logs&lt;/strong&gt;, teams often keep retrying the same broken combination.&lt;/p&gt;




&lt;h2&gt;
  
  
  Use a one-variable retry rule
&lt;/h2&gt;

&lt;p&gt;When a run fails, do not change five things at once.&lt;/p&gt;

&lt;p&gt;Do not rotate the proxy, clear cookies, switch headless mode, change viewport, and regenerate storage state in the same retry.&lt;/p&gt;

&lt;p&gt;That may get the script working once, but you will not know what fixed it.&lt;/p&gt;

&lt;p&gt;Use a simple retry order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Keep the same profile and proxy, retry once&lt;/li&gt;
&lt;li&gt;Keep the same profile, test the proxy outside Playwright&lt;/li&gt;
&lt;li&gt;Keep the same proxy, run headed mode&lt;/li&gt;
&lt;li&gt;Keep headed mode, test persistent context&lt;/li&gt;
&lt;li&gt;Keep the same account, regenerate storage state only once&lt;/li&gt;
&lt;li&gt;Change proxy only after logging the previous result&lt;/li&gt;
&lt;li&gt;Change profile only if you know the old profile is contaminated&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The goal is not just to pass the task.&lt;/p&gt;

&lt;p&gt;The goal is to learn which layer failed.&lt;/p&gt;




&lt;h2&gt;
  
  
  A practical debugging order
&lt;/h2&gt;

&lt;p&gt;Here is the full checklist for &lt;strong&gt;multi-account automation failures&lt;/strong&gt;.&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.amazonaws.com%2Fuploads%2Farticles%2Fovzttn6qkv7ml6exs086.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.amazonaws.com%2Fuploads%2Farticles%2Fovzttn6qkv7ml6exs086.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Confirm the proxy works outside Playwright
&lt;/h3&gt;

&lt;p&gt;Use &lt;code&gt;curl&lt;/code&gt; before changing browser code.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Confirm the target is reachable through the proxy
&lt;/h3&gt;

&lt;p&gt;Some proxies work for basic IP checks but fail on the actual target domain.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Confirm proxy authentication inside Playwright
&lt;/h3&gt;

&lt;p&gt;A working proxy URL in &lt;code&gt;curl&lt;/code&gt; does not always mean your Playwright config is correct.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Log the exit IP from inside the browser
&lt;/h3&gt;

&lt;p&gt;Do not assume Playwright traffic is using the proxy. Prove it.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Compare IP region, timezone, and locale
&lt;/h3&gt;

&lt;p&gt;Avoid obvious contradictions between network and browser environment.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Confirm the account uses the right profile
&lt;/h3&gt;

&lt;p&gt;One account, one profile. Do not mix unrelated accounts in the same browser state.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Confirm storage belongs to the same account
&lt;/h3&gt;

&lt;p&gt;Do not reuse &lt;strong&gt;storage state&lt;/strong&gt; files across accounts, proxies, or regions without tracking them.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Compare headed and headless
&lt;/h3&gt;

&lt;p&gt;If headed works and headless fails, debug execution mode before blaming the account.&lt;/p&gt;

&lt;h3&gt;
  
  
  9. Compare temporary and persistent contexts
&lt;/h3&gt;

&lt;p&gt;Temporary contexts are good for clean tests. Persistent contexts are often better for long-running account workflows.&lt;/p&gt;

&lt;h3&gt;
  
  
  10. Capture screenshots, HTML, status codes, and redirects
&lt;/h3&gt;

&lt;p&gt;A screenshot can show verification. HTML can show hidden error states. Status codes reveal whether the page really loaded.&lt;/p&gt;

&lt;h3&gt;
  
  
  11. Retry after changing only one variable
&lt;/h3&gt;

&lt;p&gt;This keeps your debugging path readable.&lt;/p&gt;

&lt;h3&gt;
  
  
  12. Record the final working combination
&lt;/h3&gt;

&lt;p&gt;When it works, save the combination:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;account&lt;/li&gt;
&lt;li&gt;profile&lt;/li&gt;
&lt;li&gt;proxy&lt;/li&gt;
&lt;li&gt;region&lt;/li&gt;
&lt;li&gt;timezone&lt;/li&gt;
&lt;li&gt;locale&lt;/li&gt;
&lt;li&gt;storage state&lt;/li&gt;
&lt;li&gt;headed or headless mode&lt;/li&gt;
&lt;li&gt;last successful checkpoint&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That record is what turns a fragile script into an operational workflow.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where an account-aware browser workspace helps
&lt;/h2&gt;

&lt;p&gt;For small tests, a few scripts and notes may be enough.&lt;/p&gt;

&lt;p&gt;Once a team manages many accounts, proxies, browser profiles, recurring tasks, and headless checks, the debugging problem becomes an operating problem.&lt;/p&gt;

&lt;p&gt;You no longer need only a script runner.&lt;/p&gt;

&lt;p&gt;You need a place to connect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;account identity&lt;/li&gt;
&lt;li&gt;browser profile&lt;/li&gt;
&lt;li&gt;proxy mapping&lt;/li&gt;
&lt;li&gt;task history&lt;/li&gt;
&lt;li&gt;storage state&lt;/li&gt;
&lt;li&gt;workflow logs&lt;/li&gt;
&lt;li&gt;screenshots&lt;/li&gt;
&lt;li&gt;exception records&lt;/li&gt;
&lt;li&gt;headed and headless execution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is where an &lt;a href="https://web4browser.io/" rel="noopener noreferrer"&gt;account-aware browser automation workspace&lt;/a&gt; becomes useful.&lt;/p&gt;

&lt;p&gt;The point is not to replace Playwright.&lt;/p&gt;

&lt;p&gt;The point is to stop treating &lt;strong&gt;Playwright tasks&lt;/strong&gt;, &lt;strong&gt;browser profiles&lt;/strong&gt;, &lt;strong&gt;proxy choices&lt;/strong&gt;, and &lt;strong&gt;account history&lt;/strong&gt; as separate notes scattered across scripts and spreadsheets.&lt;/p&gt;

&lt;p&gt;When those pieces are managed together, failures become easier to reproduce, compare, and fix.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final checklist
&lt;/h2&gt;

&lt;p&gt;Before blaming the proxy, ask these questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does the proxy work outside Playwright?&lt;/li&gt;
&lt;li&gt;Does Playwright actually use the proxy?&lt;/li&gt;
&lt;li&gt;Does the exit IP match the expected account region?&lt;/li&gt;
&lt;li&gt;Does timezone match the proxy region?&lt;/li&gt;
&lt;li&gt;Does locale match the account history?&lt;/li&gt;
&lt;li&gt;Is WebRTC exposing a different network path?&lt;/li&gt;
&lt;li&gt;Is the browser profile unique to this account?&lt;/li&gt;
&lt;li&gt;Does the storage state belong to the same account?&lt;/li&gt;
&lt;li&gt;Was the storage state created in the same environment?&lt;/li&gt;
&lt;li&gt;Does headed mode work?&lt;/li&gt;
&lt;li&gt;Does headless mode fail differently?&lt;/li&gt;
&lt;li&gt;Are you using temporary context when the account needs persistence?&lt;/li&gt;
&lt;li&gt;Did you change only one variable before retrying?&lt;/li&gt;
&lt;li&gt;Did you record the final working combination?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Multi-account automation&lt;/strong&gt; fails when context becomes invisible.&lt;/p&gt;

&lt;p&gt;The fix is not always a better proxy or a longer timeout. Often, the fix is making the &lt;strong&gt;account environment&lt;/strong&gt; traceable: &lt;strong&gt;profile&lt;/strong&gt;, &lt;strong&gt;proxy&lt;/strong&gt;, &lt;strong&gt;storage&lt;/strong&gt;, &lt;strong&gt;browser signals&lt;/strong&gt;, and &lt;strong&gt;execution mode&lt;/strong&gt; all moving as one system.&lt;/p&gt;

&lt;p&gt;For more notes on profile isolation, browser automation, and debugging account workflows, see these &lt;a href="https://web4browser.io/blog/" rel="noopener noreferrer"&gt;browser automation and profile debugging notes&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>playwright</category>
      <category>automation</category>
      <category>testing</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Playwright Proxy Debugging: Why Your Script Works Locally but Fails With Proxies</title>
      <dc:creator>web4browser</dc:creator>
      <pubDate>Tue, 12 May 2026 06:05:41 +0000</pubDate>
      <link>https://dev.to/web4browser/playwright-proxy-debugging-why-your-script-works-locally-but-fails-with-proxies-14kn</link>
      <guid>https://dev.to/web4browser/playwright-proxy-debugging-why-your-script-works-locally-but-fails-with-proxies-14kn</guid>
      <description>&lt;p&gt;A &lt;strong&gt;Playwright&lt;/strong&gt; script can look stable until a &lt;strong&gt;proxy&lt;/strong&gt; enters the workflow.&lt;/p&gt;

&lt;p&gt;Without a proxy, the page opens. The selector works. The click happens. The test passes.&lt;/p&gt;

&lt;p&gt;Then you add a proxy.&lt;/p&gt;

&lt;p&gt;Suddenly the page hangs. Authentication fails. Login behaves differently. A request times out. The same script works in &lt;strong&gt;visible mode&lt;/strong&gt; but fails in &lt;strong&gt;headless mode&lt;/strong&gt;. A retry succeeds once and fails again five minutes later.&lt;/p&gt;

&lt;p&gt;The first reaction is usually simple:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The proxy is bad.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Sometimes that is true.&lt;/p&gt;

&lt;p&gt;But a &lt;strong&gt;Playwright proxy failure&lt;/strong&gt; is not always a proxy failure. It can be a &lt;strong&gt;browser context&lt;/strong&gt;, &lt;strong&gt;proxy authentication&lt;/strong&gt;, &lt;strong&gt;profile state&lt;/strong&gt;, &lt;strong&gt;region mismatch&lt;/strong&gt;, or &lt;strong&gt;retry-boundary&lt;/strong&gt; problem.&lt;/p&gt;

&lt;p&gt;If you debug all of those as “proxy not working,” you will waste time changing IPs while the real issue stays hidden.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Playwright proxy bugs are hard to diagnose
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Proxy issues&lt;/strong&gt; are hard to debug because they sit between several layers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the proxy server&lt;/li&gt;
&lt;li&gt;the Playwright launch configuration&lt;/li&gt;
&lt;li&gt;the browser engine&lt;/li&gt;
&lt;li&gt;the browser context&lt;/li&gt;
&lt;li&gt;the target site&lt;/li&gt;
&lt;li&gt;the account state inside the browser&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A small mismatch in any of those layers can create the same symptom: the page does not behave as expected.&lt;/p&gt;

&lt;p&gt;A script may appear to use one proxy while a specific &lt;strong&gt;browser context&lt;/strong&gt;, retry, or browser mode uses another. A proxy may work for an IP check page but fail when the target page loads API calls, images, WebSocket connections, or login redirects. A proxy credential may work locally but break in CI because a password character was escaped incorrectly.&lt;/p&gt;

&lt;p&gt;This is why &lt;strong&gt;proxy debugging&lt;/strong&gt; should start with stable evidence, not random retries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Symptom 1: The script works without proxy but hangs with proxy
&lt;/h2&gt;

&lt;p&gt;This is the most common starting point.&lt;/p&gt;

&lt;p&gt;The script works on your normal connection. After adding a proxy, it hangs on navigation, waits forever for an element, or times out during login.&lt;/p&gt;

&lt;p&gt;The mistake is assuming that one successful proxy test proves the full workflow is healthy.&lt;/p&gt;

&lt;p&gt;Opening an IP check page only proves that the browser can reach one simple page through the proxy. It does not prove that the target workflow is stable.&lt;/p&gt;

&lt;p&gt;A real web flow may include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the main document request&lt;/li&gt;
&lt;li&gt;API calls&lt;/li&gt;
&lt;li&gt;CDN assets&lt;/li&gt;
&lt;li&gt;images and fonts&lt;/li&gt;
&lt;li&gt;tracking or risk scripts&lt;/li&gt;
&lt;li&gt;WebSocket connections&lt;/li&gt;
&lt;li&gt;login redirects&lt;/li&gt;
&lt;li&gt;cross-domain requests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A proxy may pass the first request and still fail later in the chain.&lt;/p&gt;

&lt;p&gt;A better debugging flow is staged:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open a simple IP check page.&lt;/li&gt;
&lt;li&gt;Open the target homepage.&lt;/li&gt;
&lt;li&gt;Open the exact page used by the script.&lt;/li&gt;
&lt;li&gt;Run the first interaction.&lt;/li&gt;
&lt;li&gt;Run login or account-sensitive steps.&lt;/li&gt;
&lt;li&gt;Record the first point of failure.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Do not start by changing the proxy five times.&lt;/p&gt;

&lt;p&gt;First, find where the failure begins.&lt;/p&gt;

&lt;p&gt;If the script hangs on a selector, the selector may not be the problem. The page may not have reached the expected state because one earlier request failed through the proxy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Symptom 2: Proxy authentication fails in one environment but not another
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Proxy authentication&lt;/strong&gt; bugs often look random.&lt;/p&gt;

&lt;p&gt;The proxy works in a browser extension. It works in curl. It works on your local machine. Then it fails in Playwright or CI.&lt;/p&gt;

&lt;p&gt;Common causes include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;wrong protocol prefix&lt;/li&gt;
&lt;li&gt;wrong host or port&lt;/li&gt;
&lt;li&gt;username and password placed in the wrong format&lt;/li&gt;
&lt;li&gt;special characters in the password&lt;/li&gt;
&lt;li&gt;CI environment variables escaping credentials&lt;/li&gt;
&lt;li&gt;proxy provider requiring IP whitelist&lt;/li&gt;
&lt;li&gt;mixing HTTP, HTTPS, and SOCKS expectations&lt;/li&gt;
&lt;li&gt;different behavior across Chromium, Firefox, or WebKit&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Credentials are especially easy to break.&lt;/p&gt;

&lt;p&gt;A password that contains &lt;code&gt;@&lt;/code&gt;, &lt;code&gt;:&lt;/code&gt;, &lt;code&gt;/&lt;/code&gt;, &lt;code&gt;#&lt;/code&gt;, &lt;code&gt;%&lt;/code&gt;, or shell-sensitive characters may behave differently when passed through a URL, an environment variable, a JSON config, or a command-line script.&lt;/p&gt;

&lt;p&gt;When debugging, remove ambiguity.&lt;/p&gt;

&lt;p&gt;Use the simplest possible test case. Use one browser engine. Use one proxy. Use one target page. Avoid rotation. Avoid retries. Avoid multiple contexts.&lt;/p&gt;

&lt;p&gt;Then verify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;protocol&lt;/li&gt;
&lt;li&gt;host&lt;/li&gt;
&lt;li&gt;port&lt;/li&gt;
&lt;li&gt;username&lt;/li&gt;
&lt;li&gt;password&lt;/li&gt;
&lt;li&gt;IP whitelist rules&lt;/li&gt;
&lt;li&gt;whether the proxy provider expects HTTP, HTTPS, or SOCKS&lt;/li&gt;
&lt;li&gt;whether CI is changing the credential string&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If authentication fails before the page loads, do not debug selectors yet. You do not have a browser automation problem. You have a connection or credential problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Symptom 3: Browser-level proxy and context-level proxy are mixed
&lt;/h2&gt;

&lt;p&gt;This is where Playwright projects often get confusing.&lt;/p&gt;

&lt;p&gt;A team starts with one global proxy at browser launch. Later, they add multiple accounts and want different proxies per account. Then they introduce &lt;strong&gt;browser contexts&lt;/strong&gt;, &lt;strong&gt;storage states&lt;/strong&gt;, retries, and parallel runs.&lt;/p&gt;

&lt;p&gt;At some point, nobody is fully sure which proxy belongs to which context.&lt;/p&gt;

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

&lt;p&gt;For simple tasks, a &lt;strong&gt;browser-level proxy&lt;/strong&gt; may be fine. Every context inside that browser shares the same network route.&lt;/p&gt;

&lt;p&gt;For account-based workflows, the team usually needs stricter mapping:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;account → profile → browser context → proxy → task run&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If that mapping is not explicit, the run result is hard to trust.&lt;/p&gt;

&lt;p&gt;You may think account A used proxy A. The logs may only say the task failed. The retry may have used proxy B. A developer may reproduce the issue using proxy C.&lt;/p&gt;

&lt;p&gt;Now you are comparing different environments.&lt;/p&gt;

&lt;p&gt;The key question is simple:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can you prove which context owned the proxy during the failed run?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If not, do not keep adding retries. Fix the mapping first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Symptom 4: Headless and visible runs use different proxy paths
&lt;/h2&gt;

&lt;p&gt;A workflow may pass in a &lt;strong&gt;visible browser&lt;/strong&gt; and fail in &lt;strong&gt;headless mode&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The first assumption is often that headless mode is being detected. That can happen, but it is not the only explanation.&lt;/p&gt;

&lt;p&gt;Sometimes visible and headless runs are not using the same environment.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the visible browser uses a persistent profile&lt;/li&gt;
&lt;li&gt;the headless script launches a clean context&lt;/li&gt;
&lt;li&gt;the visible test uses one proxy&lt;/li&gt;
&lt;li&gt;the headless run uses another proxy&lt;/li&gt;
&lt;li&gt;the manual session has existing cookies&lt;/li&gt;
&lt;li&gt;the automated run starts without the same storage state&lt;/li&gt;
&lt;li&gt;launch arguments differ between modes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you compare those runs directly, the conclusion will be weak.&lt;/p&gt;

&lt;p&gt;To compare fairly, keep the variables stable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;same account&lt;/li&gt;
&lt;li&gt;same proxy&lt;/li&gt;
&lt;li&gt;same profile or storage state&lt;/li&gt;
&lt;li&gt;same region settings&lt;/li&gt;
&lt;li&gt;same browser engine&lt;/li&gt;
&lt;li&gt;same entry URL&lt;/li&gt;
&lt;li&gt;same retry rules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Only then does the &lt;strong&gt;headless-versus-visible&lt;/strong&gt; comparison mean anything.&lt;/p&gt;

&lt;p&gt;Otherwise, you may not be debugging headless behavior at all. You may be debugging an &lt;strong&gt;environment mismatch&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Symptom 5: Proxy rotation makes debugging worse
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Proxy rotation&lt;/strong&gt; is useful in some production workflows.&lt;/p&gt;

&lt;p&gt;It is terrible during early debugging.&lt;/p&gt;

&lt;p&gt;When each retry uses a different IP, you destroy the evidence you need to understand the failure. The first attempt may fail because of proxy authentication. The second may fail because of region mismatch. The third may fail because the account entered a review state. The fourth may pass because it landed on a cleaner route.&lt;/p&gt;

&lt;p&gt;That does not mean the script is fixed.&lt;/p&gt;

&lt;p&gt;It means the test is no longer controlled.&lt;/p&gt;

&lt;p&gt;During debugging, freeze rotation.&lt;/p&gt;

&lt;p&gt;Use one proxy. Use one account. Use one browser context. Run the smallest version of the workflow. Record what happens. Only after the base flow is stable should you reintroduce rotation.&lt;/p&gt;

&lt;p&gt;For login, dashboard, account management, or long-running workflows, a &lt;strong&gt;sticky proxy session&lt;/strong&gt; is usually easier to debug than a rotating route.&lt;/p&gt;

&lt;p&gt;Do not rotate away your evidence before you understand the failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Symptom 6: The proxy is correct but the account context is wrong
&lt;/h2&gt;

&lt;p&gt;Sometimes the proxy is working exactly as configured.&lt;/p&gt;

&lt;p&gt;The IP is correct. The authentication works. The target page loads.&lt;/p&gt;

&lt;p&gt;The workflow still fails.&lt;/p&gt;

&lt;p&gt;That is when you need to look beyond the proxy.&lt;/p&gt;

&lt;p&gt;A browser run is not only a network route. It also carries &lt;strong&gt;browser state&lt;/strong&gt; and &lt;strong&gt;environment signals&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;cookies&lt;/li&gt;
&lt;li&gt;local storage&lt;/li&gt;
&lt;li&gt;IndexedDB&lt;/li&gt;
&lt;li&gt;timezone&lt;/li&gt;
&lt;li&gt;language&lt;/li&gt;
&lt;li&gt;WebRTC behavior&lt;/li&gt;
&lt;li&gt;browser fingerprint settings&lt;/li&gt;
&lt;li&gt;account history&lt;/li&gt;
&lt;li&gt;previous task state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the proxy region says one thing but the browser environment says another, the target site may treat the session differently.&lt;/p&gt;

&lt;p&gt;For longer account workflows, teams often move proxy assignment, profile state, and run logs into a &lt;a href="https://web4browser.io/" rel="noopener noreferrer"&gt;proxy-aware browser workspace&lt;/a&gt; instead of scattering them across scripts and config files.&lt;/p&gt;

&lt;p&gt;The point is not to make proxy setup look complicated.&lt;/p&gt;

&lt;p&gt;The point is to keep the account, profile, proxy, and task run connected enough that a failed run can be explained later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Playwright proxy debugging checklist
&lt;/h2&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.amazonaws.com%2Fuploads%2Farticles%2Flmce19dvk782am8cswro.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.amazonaws.com%2Fuploads%2Farticles%2Flmce19dvk782am8cswro.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
Use this checklist before replacing your proxy provider or rewriting your script.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Verify the proxy outside Playwright first&lt;/strong&gt;&lt;br&gt;
Test the same proxy with a simple tool before blaming Playwright.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Confirm protocol, host, port, username, and password&lt;/strong&gt;&lt;br&gt;
Small credential mistakes can produce confusing browser symptoms.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Watch for special characters in credentials&lt;/strong&gt;&lt;br&gt;
Passwords may behave differently in URLs, JSON files, shell commands, and CI variables.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Test one browser engine first&lt;/strong&gt;&lt;br&gt;
Do not debug Chromium, Firefox, and WebKit at the same time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Avoid mixing browser-level and context-level proxy rules&lt;/strong&gt;&lt;br&gt;
Decide where the proxy is configured and log that decision.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Freeze proxy rotation while debugging&lt;/strong&gt;&lt;br&gt;
One account, one proxy, one context, one failure point.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. Compare visible and headless runs with the same environment&lt;/strong&gt;&lt;br&gt;
Use the same account, proxy, profile state, and entry point.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;8. Log proxy ID, context ID, profile ID, and retry number&lt;/strong&gt;&lt;br&gt;
A failed run without environment metadata is hard to reproduce.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;9. Compare IP region with timezone and language settings&lt;/strong&gt;&lt;br&gt;
Proxy correctness does not guarantee environment consistency.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;10. Separate proxy failure from account-state failure&lt;/strong&gt;&lt;br&gt;
An account in review state is not fixed by changing IP.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;11. Re-enable rotation only after the base flow is stable&lt;/strong&gt;&lt;br&gt;
Rotation should scale a known-good flow, not hide an unknown failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  When the proxy is not the problem
&lt;/h2&gt;

&lt;p&gt;Not every failure belongs to the proxy layer.&lt;/p&gt;

&lt;p&gt;A script can fail because the target page changed. A selector may have broken. The account may lack permission. A rate limit may have been triggered. A CAPTCHA or verification state may require review. CI may have network restrictions. DNS or certificate handling may differ across environments. A reused storage state may be wrong.&lt;/p&gt;

&lt;p&gt;That is why proxy debugging should not become proxy obsession.&lt;/p&gt;

&lt;p&gt;The goal is to narrow the failure boundary.&lt;/p&gt;

&lt;p&gt;Once you know the proxy works, the context owns the expected proxy, the account state is valid, and visible/headless runs use the same environment, you can debug the script with much more confidence.&lt;/p&gt;

&lt;p&gt;Good proxy debugging does not prove the proxy is always innocent.&lt;/p&gt;

&lt;p&gt;It proves which layer deserves attention next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where structured proxy management helps
&lt;/h2&gt;

&lt;p&gt;A few scripts can survive with &lt;code&gt;.env&lt;/code&gt; files, comments, and careful naming.&lt;/p&gt;

&lt;p&gt;That does not scale well.&lt;/p&gt;

&lt;p&gt;Once a workflow includes multiple accounts, multiple profiles, multiple proxies, retries, headless runs, visible reviews, and possibly AI-assisted actions, the team needs a more structured way to prove what happened.&lt;/p&gt;

&lt;p&gt;A &lt;a href="https://web4browser.io/" rel="noopener noreferrer"&gt;controlled automation environment&lt;/a&gt; is useful when the team needs to answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which profile ran this task?&lt;/li&gt;
&lt;li&gt;Which proxy was attached?&lt;/li&gt;
&lt;li&gt;Which browser context owned the proxy?&lt;/li&gt;
&lt;li&gt;Was it headless or visible?&lt;/li&gt;
&lt;li&gt;Was the run a retry?&lt;/li&gt;
&lt;li&gt;Did the retry change the proxy?&lt;/li&gt;
&lt;li&gt;Did the account already have risky state?&lt;/li&gt;
&lt;li&gt;What logs explain the failure?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without those answers, proxy debugging becomes guesswork.&lt;/p&gt;

&lt;p&gt;With those answers, a failed run becomes an inspectable event.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with stable evidence, not random retries
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Playwright proxy debugging&lt;/strong&gt; should start with stable evidence.&lt;/p&gt;

&lt;p&gt;Before changing proxy providers, confirm the credential path.&lt;/p&gt;

&lt;p&gt;Before rotating IPs, freeze one route and reproduce the failure.&lt;/p&gt;

&lt;p&gt;Before blaming headless mode, compare the same proxy and profile in both modes.&lt;/p&gt;

&lt;p&gt;Before rewriting selectors, check whether the page reached the expected state through the proxy.&lt;/p&gt;

&lt;p&gt;Before scaling the workflow, log which proxy, context, and profile were actually used.&lt;/p&gt;

&lt;p&gt;That discipline matters.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;proxy&lt;/strong&gt; is not just a launch option once accounts, sessions, regions, profiles, and retries enter the workflow.&lt;/p&gt;

&lt;p&gt;It becomes part of the &lt;strong&gt;automation context&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If that context is unclear, every failure looks random.&lt;/p&gt;

&lt;p&gt;The goal is not to make proxy debugging more complicated.&lt;/p&gt;

&lt;p&gt;The goal is to stop guessing long enough to find the real layer that broke.&lt;/p&gt;

</description>
      <category>playwright</category>
      <category>automation</category>
      <category>testing</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Browser Profile Isolation Failure Diagnosis for Developers</title>
      <dc:creator>web4browser</dc:creator>
      <pubDate>Mon, 11 May 2026 06:56:16 +0000</pubDate>
      <link>https://dev.to/web4browser/browser-profile-isolation-failure-diagnosis-for-developers-508l</link>
      <guid>https://dev.to/web4browser/browser-profile-isolation-failure-diagnosis-for-developers-508l</guid>
      <description>&lt;p&gt;A browser automation problem does not always look like a browser automation problem.&lt;/p&gt;

&lt;p&gt;Sometimes the script runs correctly. The page loads. The selector works. The form submits. The proxy is connected.&lt;/p&gt;

&lt;p&gt;But the account still behaves as if something is wrong.&lt;/p&gt;

&lt;p&gt;Maybe two accounts start seeing similar verification steps. Maybe one profile seems to remember something it should not remember. Maybe a proxy change does not fix the issue. Maybe a headless run behaves differently from the visible browser you tested manually.&lt;/p&gt;

&lt;p&gt;At that point, many teams start blaming the obvious things:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The proxy is bad.&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Playwright is being detected.&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;The selector is unstable.&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;The target site changed something.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;All of those can be true.&lt;/p&gt;

&lt;p&gt;But before you blame the proxy, the script, or the site, check one layer first:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is your browser profile actually isolated?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For account-aware automation, &lt;strong&gt;browser profile isolation&lt;/strong&gt; is not a small implementation detail. It is the boundary that decides whether &lt;strong&gt;cookies&lt;/strong&gt;, &lt;strong&gt;local storage&lt;/strong&gt;, &lt;strong&gt;IndexedDB&lt;/strong&gt;, &lt;strong&gt;fingerprint settings&lt;/strong&gt;, &lt;strong&gt;proxy assignments&lt;/strong&gt;, and &lt;strong&gt;task history&lt;/strong&gt; stay separated between accounts.&lt;/p&gt;

&lt;p&gt;If that boundary is unclear, every other debugging step becomes noisy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Profile isolation is not the same as opening another window
&lt;/h2&gt;

&lt;p&gt;One common mistake is treating multiple browser windows as multiple browser identities.&lt;/p&gt;

&lt;p&gt;They are not the same thing.&lt;/p&gt;

&lt;p&gt;Two windows can still share the same user data directory, extension state, cache, or storage behavior. A new tab is not a new profile. An incognito window is not always a repeatable profile strategy. A temporary browser context may be clean, but it may also lose the state that your real workflow depends on.&lt;/p&gt;

&lt;p&gt;A proper &lt;strong&gt;browser profile&lt;/strong&gt; may include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;cookies&lt;/li&gt;
&lt;li&gt;local storage&lt;/li&gt;
&lt;li&gt;IndexedDB&lt;/li&gt;
&lt;li&gt;cache&lt;/li&gt;
&lt;li&gt;service workers&lt;/li&gt;
&lt;li&gt;saved permissions&lt;/li&gt;
&lt;li&gt;extension state&lt;/li&gt;
&lt;li&gt;login sessions&lt;/li&gt;
&lt;li&gt;browser fingerprint settings&lt;/li&gt;
&lt;li&gt;timezone and language settings&lt;/li&gt;
&lt;li&gt;proxy assignment&lt;/li&gt;
&lt;li&gt;automation run history&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That means profile isolation is not only about privacy. It is also about &lt;strong&gt;automation reliability&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you cannot say exactly which account used which profile, which proxy, which storage state, and which browser mode, you are not debugging from facts. You are guessing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Symptom 1: Two accounts behave as if they share history
&lt;/h2&gt;

&lt;p&gt;This is one of the clearest signs that &lt;strong&gt;profile isolation&lt;/strong&gt; may be weak.&lt;/p&gt;

&lt;p&gt;Two accounts should behave independently, but they start showing similar state. One account logs out, and another account behaves strangely. A setting changed in one environment seems to appear somewhere else. Verification patterns look suspiciously similar. Recommendations, region hints, or interface language do not match what you expected.&lt;/p&gt;

&lt;p&gt;Start by checking whether those accounts are really using separate profile directories.&lt;/p&gt;

&lt;p&gt;In automation projects, this mistake often happens quietly. A developer creates multiple account configs, but all of them launch with the same &lt;code&gt;userDataDir&lt;/code&gt;. Or a test script creates separate profile names in code, but the actual launch path still points to the same folder. Or a storage state file is copied across accounts because it was convenient during early testing.&lt;/p&gt;

&lt;p&gt;At small scale, this feels random.&lt;/p&gt;

&lt;p&gt;At larger scale, it becomes a system problem.&lt;/p&gt;

&lt;p&gt;A basic check should answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does each account have a unique &lt;strong&gt;profile directory&lt;/strong&gt;?&lt;/li&gt;
&lt;li&gt;Are &lt;strong&gt;storage state files&lt;/strong&gt; reused across accounts?&lt;/li&gt;
&lt;li&gt;Are extensions storing shared data?&lt;/li&gt;
&lt;li&gt;Are cache and service worker states separated?&lt;/li&gt;
&lt;li&gt;Are operators accidentally opening the wrong profile manually?&lt;/li&gt;
&lt;li&gt;Does the automation log show the actual profile path used during the run?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the answer is unclear, profile isolation is not verified yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Symptom 2: Cookies were cleared, but recognition continues
&lt;/h2&gt;

&lt;p&gt;Clearing cookies is not the same as resetting a browser identity.&lt;/p&gt;

&lt;p&gt;Cookies are only one part of browser state. A site or workflow can still be affected by &lt;strong&gt;localStorage&lt;/strong&gt;, &lt;strong&gt;IndexedDB&lt;/strong&gt;, &lt;strong&gt;service workers&lt;/strong&gt;, cache, saved permissions, extension state, or browser-level signals.&lt;/p&gt;

&lt;p&gt;This is why “I cleared cookies, but it still remembers me” is not always surprising.&lt;/p&gt;

&lt;p&gt;In real automation, check more than cookies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;localStorage values&lt;/li&gt;
&lt;li&gt;IndexedDB databases&lt;/li&gt;
&lt;li&gt;cache behavior&lt;/li&gt;
&lt;li&gt;service worker registration&lt;/li&gt;
&lt;li&gt;notification or location permissions&lt;/li&gt;
&lt;li&gt;extension storage&lt;/li&gt;
&lt;li&gt;saved credentials&lt;/li&gt;
&lt;li&gt;persistent login recovery signals&lt;/li&gt;
&lt;li&gt;fingerprint-related settings&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This does not mean every piece of storage is dangerous. It means &lt;strong&gt;cookie-only debugging is incomplete&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A clean cookie jar inside a messy profile is not a clean identity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Symptom 3: The proxy changed, but the problem stayed
&lt;/h2&gt;

&lt;p&gt;Proxy changes are often used as the first fix.&lt;/p&gt;

&lt;p&gt;That is understandable. Network identity is visible and easy to test. If something looks wrong, changing the exit IP feels like a fast reset.&lt;/p&gt;

&lt;p&gt;But a &lt;strong&gt;proxy&lt;/strong&gt; is only one layer.&lt;/p&gt;

&lt;p&gt;If the browser profile still carries old storage, mismatched timezone settings, reused fingerprint signals, or the wrong language configuration, changing the IP may not solve anything. In some cases, rotating the proxy too early makes debugging worse because now you have changed two variables at once.&lt;/p&gt;

&lt;p&gt;Before treating the proxy as the cause, check whether the profile and network environment are aligned:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does the profile have the intended proxy assigned?&lt;/li&gt;
&lt;li&gt;Does the browser timezone match the expected region?&lt;/li&gt;
&lt;li&gt;Does the language setting make sense for the account?&lt;/li&gt;
&lt;li&gt;Is WebRTC controlled according to the workflow?&lt;/li&gt;
&lt;li&gt;Does the visible browser use the same proxy as the headless run?&lt;/li&gt;
&lt;li&gt;Did retry logic switch to a different proxy without recording it?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Proxy drift&lt;/strong&gt; can look like script flakiness.&lt;/p&gt;

&lt;p&gt;If a task fails after a retry, you need to know whether the retry used the same &lt;strong&gt;profile&lt;/strong&gt;, the same &lt;strong&gt;proxy&lt;/strong&gt;, and the same &lt;strong&gt;browser mode&lt;/strong&gt;. Without that, you are not comparing the same environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Symptom 4: Headless and visible runs do not match
&lt;/h2&gt;

&lt;p&gt;Many teams test a workflow manually in a visible browser and then automate it in &lt;strong&gt;headless mode&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The manual test works. The headless run fails.&lt;/p&gt;

&lt;p&gt;The first assumption is often that headless mode itself is the problem. Sometimes it is. But just as often, the two runs are not using the same environment.&lt;/p&gt;

&lt;p&gt;A visible browser may use a persistent profile with existing cookies, saved permissions, and known state. A headless script may launch a clean context. Or the headless script may use a different profile path, proxy config, launch argument set, or extension setup.&lt;/p&gt;

&lt;p&gt;To compare visible and headless behavior fairly, keep the variables stable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;same account&lt;/li&gt;
&lt;li&gt;same profile&lt;/li&gt;
&lt;li&gt;same proxy&lt;/li&gt;
&lt;li&gt;same region settings&lt;/li&gt;
&lt;li&gt;same browser engine&lt;/li&gt;
&lt;li&gt;same fingerprint configuration&lt;/li&gt;
&lt;li&gt;same task entry point&lt;/li&gt;
&lt;li&gt;same retry rules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the visible browser and headless browser are not using the same profile state, the comparison is weak.&lt;/p&gt;

&lt;p&gt;You may not be seeing a headless problem. You may be seeing an &lt;strong&gt;environment mismatch&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Symptom 5: The same script works locally but fails at scale
&lt;/h2&gt;

&lt;p&gt;A script that works for three accounts can fail badly with three hundred.&lt;/p&gt;

&lt;p&gt;At small scale, profile management can be informal. A folder name, a spreadsheet, or a few config files may be enough. Someone on the team remembers which profile belongs to which account.&lt;/p&gt;

&lt;p&gt;At scale, memory breaks.&lt;/p&gt;

&lt;p&gt;Profiles get copied. Proxy assignments drift. Retry jobs reuse the wrong profile. Operators open the wrong environment. Logs say that a task failed, but not which profile, proxy, or mode was used. A task gets retried after an account entered review state, making the problem worse.&lt;/p&gt;

&lt;p&gt;This is where profile isolation becomes operational, not just technical.&lt;/p&gt;

&lt;p&gt;The question is no longer:&lt;/p&gt;

&lt;p&gt;“Can this script run?”&lt;/p&gt;

&lt;p&gt;The better question is:&lt;/p&gt;

&lt;p&gt;“Can we prove which environment this script ran inside?”&lt;/p&gt;

&lt;p&gt;For scalable automation, every run should leave enough evidence to answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which account was used?&lt;/li&gt;
&lt;li&gt;Which profile was used?&lt;/li&gt;
&lt;li&gt;Which proxy was attached?&lt;/li&gt;
&lt;li&gt;Was it headless or visible?&lt;/li&gt;
&lt;li&gt;Which task triggered the run?&lt;/li&gt;
&lt;li&gt;Was this the first run or a retry?&lt;/li&gt;
&lt;li&gt;Did the account require human review?&lt;/li&gt;
&lt;li&gt;What state changed after the run?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without those records, debugging turns into archaeology.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical profile isolation checklist
&lt;/h2&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.amazonaws.com%2Fuploads%2Farticles%2F7u6bw3ktnn2jbxi0kq9z.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.amazonaws.com%2Fuploads%2Farticles%2F7u6bw3ktnn2jbxi0kq9z.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
Use this checklist before blaming proxies, selectors, or the target site.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Confirm each account uses a separate profile directory&lt;/strong&gt;&lt;br&gt;
Do not rely on account names in your config. Check the actual runtime path.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Check more than cookies&lt;/strong&gt;&lt;br&gt;
Review &lt;strong&gt;cookies&lt;/strong&gt;, &lt;strong&gt;localStorage&lt;/strong&gt;, &lt;strong&gt;IndexedDB&lt;/strong&gt;, cache, service workers, permissions, and extension state.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Avoid reusing storage state files across accounts&lt;/strong&gt;&lt;br&gt;
A copied storage file can silently destroy isolation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Bind each profile to the intended proxy&lt;/strong&gt;&lt;br&gt;
The proxy should not live only in a launch command that can change during retries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Compare timezone, language, and region signals&lt;/strong&gt;&lt;br&gt;
An IP from one region with browser settings from another can create confusing results.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Verify visible and headless runs use the same environment&lt;/strong&gt;&lt;br&gt;
Do not compare a manual persistent profile against a clean headless context.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. Log profile ID, proxy ID, task ID, and run mode&lt;/strong&gt;&lt;br&gt;
A failed run without environment metadata is hard to debug.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;8. Stop retries when the account enters a review state&lt;/strong&gt;&lt;br&gt;
Retrying blindly can turn a small issue into a larger one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;9. Keep a human review path&lt;/strong&gt;&lt;br&gt;
Some states should not be handled automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;10. Re-test with one variable changed at a time&lt;/strong&gt;&lt;br&gt;
Changing profile, proxy, script, and browser mode together makes the result useless.&lt;/p&gt;

&lt;p&gt;This checklist is simple, but it prevents a common mistake: treating every automation failure as a script failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where an automation workspace helps
&lt;/h2&gt;

&lt;p&gt;When a team only manages a few scripts, profile isolation can be handled with careful folder naming and disciplined config files.&lt;/p&gt;

&lt;p&gt;That does not scale forever.&lt;/p&gt;

&lt;p&gt;Once the workflow includes many profiles, many proxies, AI-assisted steps, headless runs, visible reviews, and retry logic, the browser layer needs more structure.&lt;/p&gt;

&lt;p&gt;An &lt;a href="https://web4browser.io/" rel="noopener noreferrer"&gt;AI browser automation workspace&lt;/a&gt; helps when teams need to manage &lt;strong&gt;profiles&lt;/strong&gt;, &lt;strong&gt;proxy bindings&lt;/strong&gt;, &lt;strong&gt;fingerprint environments&lt;/strong&gt;, &lt;strong&gt;automation access&lt;/strong&gt;, and &lt;strong&gt;task review&lt;/strong&gt; in one place instead of spreading them across scripts and manual notes.&lt;/p&gt;

&lt;p&gt;The value is not just convenience.&lt;/p&gt;

&lt;p&gt;The value is fewer unknowns.&lt;/p&gt;

&lt;p&gt;If a workflow fails, the team should be able to inspect the profile, proxy, run mode, task history, and review state without reconstructing the whole story from scattered logs.&lt;/p&gt;

&lt;p&gt;That is what makes automation repeatable.&lt;/p&gt;

&lt;h2&gt;
  
  
  When profile isolation is not the problem
&lt;/h2&gt;

&lt;p&gt;Profile isolation is important, but it is not the answer to every failure.&lt;/p&gt;

&lt;p&gt;Sometimes the target site changed its flow. Sometimes a selector really did break. Sometimes the proxy reputation is poor. Sometimes the account itself has a permission issue. Sometimes rate limits are being triggered. Sometimes credentials are wrong. Sometimes the workflow is entering a state that should not be automated at all.&lt;/p&gt;

&lt;p&gt;That is why isolation should be the first boundary check, not the only diagnosis.&lt;/p&gt;

&lt;p&gt;Once you know the profile is isolated, the proxy is attached correctly, and the run mode is consistent, you can debug the script with more confidence.&lt;/p&gt;

&lt;p&gt;Good isolation does not remove every failure.&lt;/p&gt;

&lt;p&gt;It removes unnecessary uncertainty.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real goal is a clean debugging boundary
&lt;/h2&gt;

&lt;p&gt;Browser automation debugging should start with boundaries, not guesses.&lt;/p&gt;

&lt;p&gt;Before asking whether the selector broke, ask whether the right profile ran.&lt;/p&gt;

&lt;p&gt;Before rotating proxies, ask whether the browser state was already contaminated.&lt;/p&gt;

&lt;p&gt;Before blaming headless mode, ask whether visible and headless runs used the same environment.&lt;/p&gt;

&lt;p&gt;Before scaling a script, ask whether every run leaves enough evidence to review later.&lt;/p&gt;

&lt;p&gt;That shift matters.&lt;/p&gt;

&lt;p&gt;A browser profile is not just a storage folder. In account-aware automation, it is part of the operating context.&lt;/p&gt;

&lt;p&gt;If the context is not isolated, the workflow is not stable.&lt;/p&gt;

&lt;p&gt;For teams building multi-account operations, proxy-aware automation, AI-assisted browser workflows, or repeatable testing systems, &lt;a href="https://web4browser.io/" rel="noopener noreferrer"&gt;Web4 Browser&lt;/a&gt; is one example of how the browser layer can move from loose profile folders toward a controlled automation workspace.&lt;/p&gt;

&lt;p&gt;The goal is not to make automation more complicated.&lt;/p&gt;

&lt;p&gt;The goal is to make failures easier to understand before they become harder&lt;/p&gt;

</description>
      <category>automation</category>
      <category>testing</category>
      <category>playwright</category>
      <category>webdev</category>
    </item>
    <item>
      <title>AI Agent Browser Automation: Why Headless Scripts Are Not Enough for Real Workflows</title>
      <dc:creator>web4browser</dc:creator>
      <pubDate>Sat, 09 May 2026 06:05:56 +0000</pubDate>
      <link>https://dev.to/web4browser/ai-agent-browser-automation-why-headless-scripts-are-not-enough-for-real-workflows-4ocd</link>
      <guid>https://dev.to/web4browser/ai-agent-browser-automation-why-headless-scripts-are-not-enough-for-real-workflows-4ocd</guid>
      <description>&lt;p&gt;Most developers meet &lt;strong&gt;browser automation&lt;/strong&gt; through a clean demo.&lt;/p&gt;

&lt;p&gt;Open a page. Click a button. Fill a form. Read the result. Close the browser.&lt;/p&gt;

&lt;p&gt;For that kind of task, a &lt;strong&gt;headless script&lt;/strong&gt; is often enough. &lt;strong&gt;Playwright&lt;/strong&gt;, &lt;strong&gt;Puppeteer&lt;/strong&gt;, &lt;strong&gt;Selenium&lt;/strong&gt;, and &lt;strong&gt;CDP-based tools&lt;/strong&gt; are excellent when the path is stable and the browser state does not carry much risk.&lt;/p&gt;

&lt;p&gt;But &lt;strong&gt;AI agent browser automation&lt;/strong&gt; changes the problem.&lt;/p&gt;

&lt;p&gt;Once an agent is expected to work across logged-in accounts, persistent sessions, different proxy routes, repeated workflows, and human review points, the hard part is no longer just controlling a page.&lt;/p&gt;

&lt;p&gt;The hard part is keeping the right context around the task.&lt;/p&gt;

&lt;p&gt;That is where a simple headless script starts to feel too thin. Real browser automation needs a workspace that can manage &lt;strong&gt;identity&lt;/strong&gt;, &lt;strong&gt;environment&lt;/strong&gt;, &lt;strong&gt;proxy&lt;/strong&gt;, &lt;strong&gt;state&lt;/strong&gt;, &lt;strong&gt;execution&lt;/strong&gt;, and &lt;strong&gt;review&lt;/strong&gt; together.&lt;/p&gt;

&lt;p&gt;For teams building account-aware workflows, an &lt;a href="https://web4browser.io/" rel="noopener noreferrer"&gt;AI fingerprint browser workspace&lt;/a&gt; is not just a nicer browser launcher. It becomes the operating layer between scripts, agents, profiles, and real work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Headless scripts are still useful
&lt;/h2&gt;

&lt;p&gt;This is not an argument against headless automation.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;headless script&lt;/strong&gt; is still a good fit when the task is narrow, public, and predictable. It works well for checking whether a public page loads, running a smoke test in CI, collecting simple public data, validating an internal dashboard, or confirming that one element exists.&lt;/p&gt;

&lt;p&gt;In these cases, the browser is mostly disposable. The script starts, does the job, and exits.&lt;/p&gt;

&lt;p&gt;If something breaks, the failure is usually easy to inspect: a selector changed, a response failed, a timeout was too short, or the page structure moved.&lt;/p&gt;

&lt;p&gt;That model works because &lt;strong&gt;browser context&lt;/strong&gt; is not the main asset.&lt;/p&gt;

&lt;p&gt;Account-based automation is different. The context becomes part of the work itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real workflows break for reasons scripts do not always see
&lt;/h2&gt;

&lt;p&gt;A browser automation workflow may fail even when the page technically loads.&lt;/p&gt;

&lt;p&gt;The selector may be correct. The click may happen. The form may submit. The response may return 200.&lt;/p&gt;

&lt;p&gt;The task can still be wrong.&lt;/p&gt;

&lt;p&gt;Maybe the account is already in a review state. Maybe the proxy exit no longer matches the expected region. Maybe the browser language and timezone no longer fit the account profile. Maybe the previous login session was reused incorrectly. Maybe a retry silently changed the environment.&lt;/p&gt;

&lt;p&gt;A traditional script often sees the page. It does not always see the account situation around the page.&lt;/p&gt;

&lt;p&gt;That is where real automation becomes messy.&lt;/p&gt;

&lt;p&gt;For simple tasks, the browser is only a runtime. For &lt;strong&gt;multi-account automation&lt;/strong&gt;, the browser is part of the identity.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI agents make context errors more expensive
&lt;/h2&gt;

&lt;p&gt;A fixed script usually fails in a predictable way. It reaches a missing selector, throws an error, and stops.&lt;/p&gt;

&lt;p&gt;An &lt;strong&gt;AI agent&lt;/strong&gt; may do something more dangerous: it may continue.&lt;/p&gt;

&lt;p&gt;That is the power of agents, and also the risk. An agent can interpret a page, adapt to small interface changes, and find a new path forward. That flexibility is useful when the workflow is safe.&lt;/p&gt;

&lt;p&gt;But if the surrounding context is wrong, flexibility can amplify the mistake.&lt;/p&gt;

&lt;p&gt;An agent might keep trying after an account enters a risk checkpoint. It might treat a verification page as a normal workflow step. It might continue from the wrong logged-in session. It might retry a task under a different proxy route. It might complete an action that should have required &lt;strong&gt;human review&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The problem is not that the agent cannot use a browser.&lt;/p&gt;

&lt;p&gt;The problem is that the agent may not know when the browser context is no longer safe.&lt;/p&gt;

&lt;p&gt;That is why &lt;strong&gt;AI agent browser automation&lt;/strong&gt; needs more than page control. It needs &lt;strong&gt;context control&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Browser profiles are operating memory
&lt;/h2&gt;

&lt;p&gt;In real browser workflows, a profile is not just a folder full of cookies.&lt;/p&gt;

&lt;p&gt;It is the account’s operating memory.&lt;/p&gt;

&lt;p&gt;A useful &lt;strong&gt;browser profile&lt;/strong&gt; may contain cookies, local storage, IndexedDB state, saved permissions, previous login sessions, browser fingerprint settings, timezone and language configuration, proxy assignment, and known task history.&lt;/p&gt;

&lt;p&gt;If an automation system treats every run as disposable, it keeps rebuilding context from scratch. That may be acceptable for public pages. It is not ideal for long-running account workflows.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;persistent browser profile&lt;/strong&gt; helps each account stay tied to its own environment. It also makes it easier to separate one identity from another instead of letting multiple accounts share the same loose runtime.&lt;/p&gt;

&lt;p&gt;This is especially important when automation moves from one-time testing into daily operations.&lt;/p&gt;

&lt;p&gt;For &lt;strong&gt;account-aware automation&lt;/strong&gt;, the browser profile is part of the application state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Proxy mapping is not just a command-line flag
&lt;/h2&gt;

&lt;p&gt;Many browser automation setups treat proxy configuration as a simple launch option.&lt;/p&gt;

&lt;p&gt;That works for basic tests. It is not enough for real workflows.&lt;/p&gt;

&lt;p&gt;In account-based browser automation, a &lt;strong&gt;proxy&lt;/strong&gt; is part of the account environment. The exit region, protocol, authentication method, retry behavior, and profile binding all affect whether the task remains consistent.&lt;/p&gt;

&lt;p&gt;A workflow can look technically successful while still creating &lt;strong&gt;identity drift&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For example, an account may usually work from one region, but a retry uses another. Browser timezone and proxy location may not match. Language settings may conflict with the expected environment. A headless run may use one proxy while the visible browser uses another. Several accounts may accidentally share the same proxy endpoint.&lt;/p&gt;

&lt;p&gt;These are not just network details. They are workflow reliability details.&lt;/p&gt;

&lt;p&gt;When a team says its automation is flaky, the cause is not always the script. Sometimes the script is only exposing a deeper environment problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Proxy drift&lt;/strong&gt; can look like script flakiness.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fingerprint consistency is a debugging problem too
&lt;/h2&gt;

&lt;p&gt;A browser fingerprint is not only a detection topic.&lt;/p&gt;

&lt;p&gt;It is also a consistency topic.&lt;/p&gt;

&lt;p&gt;If the same account appears with unstable environment signals, the workflow becomes harder to trust. Even if the task finishes, the team may not know whether the account was handled under the right conditions.&lt;/p&gt;

&lt;p&gt;This becomes more important when the same workflow runs every day, across many accounts, with both human and automated actions.&lt;/p&gt;

&lt;p&gt;A real automation setup should make it easy to answer basic questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which &lt;strong&gt;profile&lt;/strong&gt; ran this task?&lt;/li&gt;
&lt;li&gt;Which &lt;strong&gt;proxy&lt;/strong&gt; was attached?&lt;/li&gt;
&lt;li&gt;Was the browser visible or headless?&lt;/li&gt;
&lt;li&gt;Which &lt;strong&gt;fingerprint environment&lt;/strong&gt; was used?&lt;/li&gt;
&lt;li&gt;Did the task run under the expected region?&lt;/li&gt;
&lt;li&gt;Was the account already in a risky state?&lt;/li&gt;
&lt;li&gt;Was this a fresh run, a retry, or a human handoff?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without these answers, automation becomes difficult to debug and even harder to scale.&lt;/p&gt;

&lt;p&gt;A stable environment makes automation easier to debug before it makes it easier to scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  From scripts to reusable Skills and MCP workflows
&lt;/h2&gt;

&lt;p&gt;A script is usually written for a task.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;Skill&lt;/strong&gt; is designed to become repeatable.&lt;/p&gt;

&lt;p&gt;That difference matters for &lt;strong&gt;AI Agent workflows&lt;/strong&gt;. If every browser task starts with the agent interpreting everything from zero, the workflow may become inconsistent. One run may handle a page one way. The next run may choose a slightly different path.&lt;/p&gt;

&lt;p&gt;Reusable &lt;strong&gt;Skills&lt;/strong&gt;, workflow templates, or &lt;strong&gt;MCP-connected routines&lt;/strong&gt; help reduce that randomness.&lt;/p&gt;

&lt;p&gt;A browser task can be packaged around a known purpose:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;check account status&lt;/li&gt;
&lt;li&gt;inspect a landing page&lt;/li&gt;
&lt;li&gt;verify dashboard changes&lt;/li&gt;
&lt;li&gt;collect public page data&lt;/li&gt;
&lt;li&gt;review notifications&lt;/li&gt;
&lt;li&gt;export a report&lt;/li&gt;
&lt;li&gt;flag exceptions for human review&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When these workflows are connected through &lt;strong&gt;MCP&lt;/strong&gt; or automation APIs, the browser becomes more than a screen for the agent. It becomes part of a larger tool system.&lt;/p&gt;

&lt;p&gt;The important point is not only that an agent can act.&lt;/p&gt;

&lt;p&gt;The important point is that the team can define how the agent should act, when it should stop, and what evidence it should leave behind.&lt;/p&gt;

&lt;p&gt;The future is not only agents clicking better. It is teams packaging repeatable browser work into controlled workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Local-first execution is about control
&lt;/h2&gt;

&lt;p&gt;As browser automation becomes more sensitive, teams start asking a different set of questions.&lt;/p&gt;

&lt;p&gt;Where is the browser data stored? Who can access the session state? Are cookies, local storage, and profile data uploaded somewhere? Can the team inspect what happened after a failed run? Can a human take over the same environment without rebuilding everything?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Local-first execution&lt;/strong&gt; matters because browser state is often sensitive. Account sessions, proxy settings, task logs, and workflow outputs can reveal more than teams expect.&lt;/p&gt;

&lt;p&gt;Keeping profile data on the device gives teams stronger control over their operating environment. It also makes the workflow easier to inspect when something goes wrong.&lt;/p&gt;

&lt;p&gt;This does not mean every workflow must be local forever. But for &lt;strong&gt;account-aware automation&lt;/strong&gt;, local control is often the safer default.&lt;/p&gt;

&lt;h2&gt;
  
  
  Visible browser and headless mode should not be separate worlds
&lt;/h2&gt;

&lt;p&gt;A common automation problem is the gap between &lt;strong&gt;visible browser work&lt;/strong&gt; and &lt;strong&gt;headless execution&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A human opens one environment. A script runs another. An agent sees a third. The logs do not clearly connect them.&lt;/p&gt;

&lt;p&gt;That split makes debugging painful.&lt;/p&gt;

&lt;p&gt;A better workflow lets teams move between visible operation, headless automation, and AI-assisted execution without losing profile context.&lt;/p&gt;

&lt;p&gt;A human should be able to inspect what the agent saw. A script should be able to run against the right browser instance. An agent should be able to continue from a controlled environment instead of a random fresh session.&lt;/p&gt;

&lt;p&gt;This is where a browser workspace becomes useful.&lt;/p&gt;

&lt;p&gt;It gives teams a shared place to manage profiles, proxies, automation access, and task execution instead of scattering them across scripts, browser folders, and manual notes.&lt;/p&gt;

&lt;h2&gt;
  
  
  When a simple headless script is enough
&lt;/h2&gt;

&lt;p&gt;Not every team needs a browser workspace.&lt;/p&gt;

&lt;p&gt;A simple &lt;strong&gt;headless script&lt;/strong&gt; is enough when the page is public, login state does not matter, proxy consistency is not important, no long-term profile is needed, failures can be safely retried, no human review is required, and the workflow does not touch account assets.&lt;/p&gt;

&lt;p&gt;For many testing and data tasks, that is perfectly fine.&lt;/p&gt;

&lt;p&gt;The mistake is trying to stretch the same model into account-aware automation without changing the architecture.&lt;/p&gt;

&lt;p&gt;Once the workflow depends on &lt;strong&gt;persistent identity&lt;/strong&gt;, &lt;strong&gt;proxy mapping&lt;/strong&gt;, &lt;strong&gt;profile state&lt;/strong&gt;, &lt;strong&gt;task history&lt;/strong&gt;, and &lt;strong&gt;review boundaries&lt;/strong&gt;, the browser is no longer a disposable process.&lt;/p&gt;

&lt;p&gt;It becomes an operating environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to look for in an AI browser automation workspace
&lt;/h2&gt;

&lt;p&gt;If you are evaluating whether your team has outgrown basic headless scripts, look for these capabilities.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Persistent profile state&lt;/strong&gt;&lt;br&gt;
Each account should have its own cookies, local storage, history, and browser data instead of rebuilding from a clean session every time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Proxy and region mapping&lt;/strong&gt;&lt;br&gt;
Proxy settings should be tied to profiles and workflows, not scattered across launch commands and config files.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fingerprint environment consistency&lt;/strong&gt;&lt;br&gt;
Timezone, language, screen parameters, browser engine, and fingerprint settings should remain stable enough for repeated account work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Automation API access&lt;/strong&gt;&lt;br&gt;
The workspace should still work with tools developers already use, including &lt;strong&gt;Playwright&lt;/strong&gt;, &lt;strong&gt;Puppeteer&lt;/strong&gt;, &lt;strong&gt;Selenium&lt;/strong&gt;, or &lt;strong&gt;CDP-based integrations&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI agent execution layer&lt;/strong&gt;&lt;br&gt;
Agents should be able to operate inside controlled environments rather than free-running against disconnected browser sessions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reusable workflow templates&lt;/strong&gt;&lt;br&gt;
Repeated browser tasks should become &lt;strong&gt;Skills&lt;/strong&gt; or templates that can be improved over time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Headless and visible handoff&lt;/strong&gt;&lt;br&gt;
A human should be able to inspect, interrupt, or continue a task without losing environment context.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Logs and review states&lt;/strong&gt;&lt;br&gt;
The system should show what happened, which environment was used, and where human review is needed.&lt;/p&gt;

&lt;p&gt;These features are not about making automation look more complex. They are about making automation safer to repeat.&lt;/p&gt;

&lt;h2&gt;
  
  
  The shift is from page control to context control
&lt;/h2&gt;

&lt;p&gt;The first wave of browser automation was about controlling pages.&lt;/p&gt;

&lt;p&gt;The next wave is about controlling context.&lt;/p&gt;

&lt;p&gt;AI agents make browser automation more flexible, but they also make context management more important. When an agent can make decisions, retry actions, and adapt to changing pages, the surrounding environment must become more explicit.&lt;/p&gt;

&lt;p&gt;A headless script can open a page.&lt;/p&gt;

&lt;p&gt;A browser workspace can preserve the identity that makes the task meaningful.&lt;/p&gt;

&lt;p&gt;That is the real difference.&lt;/p&gt;

&lt;p&gt;For teams working on multi-account operations, AI-assisted workflows, proxy-aware automation, or long-running browser tasks, &lt;a href="https://web4browser.io/" rel="noopener noreferrer"&gt;Web4 Browser&lt;/a&gt; is one example of how the browser layer can move from isolated windows toward a controlled automation workspace.&lt;/p&gt;

&lt;p&gt;The goal is not to replace scripts.&lt;/p&gt;

&lt;p&gt;The goal is to give scripts and agents a safer place to run.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>automation</category>
      <category>testing</category>
    </item>
    <item>
      <title>AI Agent Browser Automation: Why Headless Scripts Are Not Enough for Real Workflows</title>
      <dc:creator>web4browser</dc:creator>
      <pubDate>Fri, 08 May 2026 05:18:14 +0000</pubDate>
      <link>https://dev.to/web4browser/ai-agent-browser-automation-why-headless-scripts-are-not-enough-for-real-workflows-7f3</link>
      <guid>https://dev.to/web4browser/ai-agent-browser-automation-why-headless-scripts-are-not-enough-for-real-workflows-7f3</guid>
      <description>&lt;p&gt;Most developers meet &lt;strong&gt;browser automation&lt;/strong&gt; through a clean demo.&lt;/p&gt;

&lt;p&gt;Open a page. Click a button. Fill a form. Read the result. Close the browser.&lt;/p&gt;

&lt;p&gt;For that kind of task, a headless script is often enough. &lt;strong&gt;Playwright&lt;/strong&gt;, &lt;strong&gt;Puppeteer&lt;/strong&gt;, &lt;strong&gt;Selenium&lt;/strong&gt;, and CDP-based tools are excellent when the path is stable and the browser state does not carry much business risk.&lt;/p&gt;

&lt;p&gt;But &lt;strong&gt;AI agent browser automation&lt;/strong&gt; changes the problem.&lt;/p&gt;

&lt;p&gt;Once an agent is expected to work across logged-in accounts, persistent sessions, different proxy routes, repeated workflows, and human review points, the hard part is no longer just controlling a page.&lt;/p&gt;

&lt;p&gt;The hard part is keeping the right context around the task.&lt;/p&gt;

&lt;p&gt;That is where a simple headless script starts to feel too thin. Real browser automation needs a workspace that can manage &lt;strong&gt;identity&lt;/strong&gt;, &lt;strong&gt;environment&lt;/strong&gt;, &lt;strong&gt;proxy&lt;/strong&gt;, &lt;strong&gt;state&lt;/strong&gt;, &lt;strong&gt;execution&lt;/strong&gt;, and &lt;strong&gt;review&lt;/strong&gt; together.&lt;/p&gt;

&lt;p&gt;For teams building account-aware workflows, an &lt;a href="https://web4browser.io/" rel="noopener noreferrer"&gt;AI fingerprint browser workspace&lt;/a&gt; is not just a nicer browser launcher. It becomes the operating layer between scripts, agents, profiles, and real work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Headless scripts are still useful
&lt;/h2&gt;

&lt;p&gt;This is not an argument against headless automation.&lt;/p&gt;

&lt;p&gt;A headless script is still a good fit when the task is narrow and predictable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;checking whether a public page loads&lt;/li&gt;
&lt;li&gt;running a smoke test in CI&lt;/li&gt;
&lt;li&gt;collecting simple public data&lt;/li&gt;
&lt;li&gt;validating an internal dashboard&lt;/li&gt;
&lt;li&gt;confirming that one element exists&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In these cases, the browser is mostly disposable. The script starts, does the job, and exits. If something breaks, the failure is usually easy to inspect: a selector changed, a response failed, a timeout was too short, or the page structure moved.&lt;/p&gt;

&lt;p&gt;That model works because the browser context is not the main asset.&lt;/p&gt;

&lt;p&gt;Account-based automation is different. The context becomes part of the work itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real workflows break for reasons scripts do not always see
&lt;/h2&gt;

&lt;p&gt;A browser automation workflow may fail even when the page technically loads.&lt;/p&gt;

&lt;p&gt;The selector may be correct. The click may happen. The form may submit. The response may return 200.&lt;/p&gt;

&lt;p&gt;The task can still be wrong.&lt;/p&gt;

&lt;p&gt;Maybe the account is already in a review state. Maybe the proxy exit no longer matches the expected region. Maybe the browser language and timezone no longer fit the account profile. Maybe the previous login session was reused incorrectly. Maybe a retry silently changed the environment.&lt;/p&gt;

&lt;p&gt;A traditional script often sees the page. It does not always see the account situation around the page.&lt;/p&gt;

&lt;p&gt;That is where real automation becomes messy.&lt;/p&gt;

&lt;p&gt;For simple tasks, the browser is only a runtime. For &lt;strong&gt;multi-account automation&lt;/strong&gt;, the browser is part of the identity.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI agents make context errors more expensive
&lt;/h2&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.amazonaws.com%2Fuploads%2Farticles%2F6ea4biakskpswuqxvxss.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.amazonaws.com%2Fuploads%2Farticles%2F6ea4biakskpswuqxvxss.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
A fixed script usually fails in a predictable way. It reaches a missing selector, throws an error, and stops.&lt;/p&gt;

&lt;p&gt;An AI agent may do something more dangerous: it may continue.&lt;/p&gt;

&lt;p&gt;That is the power of agents, and also the risk. An agent can interpret a page, adapt to small interface changes, and find a new path forward. That flexibility is useful when the workflow is safe.&lt;/p&gt;

&lt;p&gt;But if the surrounding context is wrong, flexibility can amplify the mistake.&lt;/p&gt;

&lt;p&gt;An agent might keep trying after an account enters a risk checkpoint. It might treat a verification page as a normal workflow step. It might continue from the wrong logged-in session. It might retry a task under a different proxy route. It might complete an action that should have required human review.&lt;/p&gt;

&lt;p&gt;The problem is not that the agent cannot use a browser.&lt;/p&gt;

&lt;p&gt;The problem is that the agent may not know when the browser context is no longer safe.&lt;/p&gt;

&lt;p&gt;That is why &lt;strong&gt;AI agent browser automation&lt;/strong&gt; needs more than page control. It needs &lt;strong&gt;context control&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Browser profiles are operating memory
&lt;/h2&gt;

&lt;p&gt;In real browser workflows, a profile is not just a folder full of cookies.&lt;/p&gt;

&lt;p&gt;It is the account’s operating memory.&lt;/p&gt;

&lt;p&gt;A useful profile may contain cookies, local storage, IndexedDB state, saved permissions, previous login sessions, browser fingerprint settings, timezone and language configuration, proxy assignment, and known task history.&lt;/p&gt;

&lt;p&gt;If an automation system treats every run as disposable, it keeps rebuilding context from scratch. That may be acceptable for public pages. It is not ideal for long-running account workflows.&lt;/p&gt;

&lt;p&gt;A persistent browser profile helps each account stay tied to its own environment. It also makes it easier to separate one identity from another instead of letting multiple accounts share the same loose runtime.&lt;/p&gt;

&lt;p&gt;This is especially important when automation moves from one-time testing into daily operations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Proxy mapping is not just a command-line flag
&lt;/h2&gt;

&lt;p&gt;Many browser automation setups treat proxy configuration as a simple launch option.&lt;/p&gt;

&lt;p&gt;That works for basic tests. It is not enough for real workflows.&lt;/p&gt;

&lt;p&gt;In account-based browser automation, a proxy is part of the account environment. The exit region, protocol, authentication method, retry behavior, and profile binding all affect whether the task remains consistent.&lt;/p&gt;

&lt;p&gt;A workflow can look technically successful while still creating identity drift.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an account usually works from one region, but a retry uses another&lt;/li&gt;
&lt;li&gt;browser timezone and proxy location do not match&lt;/li&gt;
&lt;li&gt;language settings conflict with the expected environment&lt;/li&gt;
&lt;li&gt;a headless run uses one proxy while the visible browser uses another&lt;/li&gt;
&lt;li&gt;several accounts accidentally share the same proxy endpoint&lt;/li&gt;
&lt;li&gt;a failed session is retried with a different network path&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are not just network details. They are workflow reliability details.&lt;/p&gt;

&lt;p&gt;When a team says its automation is flaky, the cause is not always the script. Sometimes the script is only exposing a deeper environment problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fingerprint consistency matters when automation becomes repeatable
&lt;/h2&gt;

&lt;p&gt;A browser fingerprint is not only a detection topic. It is also a consistency topic.&lt;/p&gt;

&lt;p&gt;If the same account appears with unstable environment signals, the workflow becomes harder to trust. Even if the task finishes, the team may not know whether the account was handled under the right conditions.&lt;/p&gt;

&lt;p&gt;This becomes more important when the same workflow runs every day, across many accounts, with both human and automated actions.&lt;/p&gt;

&lt;p&gt;A real automation setup should make it easy to answer basic questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which profile ran this task?&lt;/li&gt;
&lt;li&gt;Which proxy was attached?&lt;/li&gt;
&lt;li&gt;Was the browser visible or headless?&lt;/li&gt;
&lt;li&gt;Which fingerprint template was used?&lt;/li&gt;
&lt;li&gt;Did the task run under the expected region?&lt;/li&gt;
&lt;li&gt;Was the account already in a risky state?&lt;/li&gt;
&lt;li&gt;Was this a fresh run, a retry, or a human handoff?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without these answers, automation becomes difficult to debug and even harder to scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  From scripts to reusable skills
&lt;/h2&gt;

&lt;p&gt;A script is usually written for a task.&lt;/p&gt;

&lt;p&gt;A skill is designed to become repeatable.&lt;/p&gt;

&lt;p&gt;That difference matters for &lt;strong&gt;AI Agent workflows&lt;/strong&gt;. If every browser task starts with the agent interpreting everything from zero, the workflow may become inconsistent. One run may handle a page one way; the next run may choose a slightly different path.&lt;/p&gt;

&lt;p&gt;Reusable &lt;strong&gt;Skills&lt;/strong&gt;, workflow templates, or MCP-connected routines help reduce that randomness.&lt;/p&gt;

&lt;p&gt;A browser task can be packaged around a known purpose:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;check account status&lt;/li&gt;
&lt;li&gt;inspect a landing page&lt;/li&gt;
&lt;li&gt;verify dashboard changes&lt;/li&gt;
&lt;li&gt;collect public page data&lt;/li&gt;
&lt;li&gt;review notifications&lt;/li&gt;
&lt;li&gt;export a report&lt;/li&gt;
&lt;li&gt;flag exceptions for human review&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When these workflows are connected through &lt;strong&gt;MCP&lt;/strong&gt; or automation APIs, the browser becomes more than a screen for the agent. It becomes part of a larger tool system.&lt;/p&gt;

&lt;p&gt;The important point is not only that an agent can act.&lt;/p&gt;

&lt;p&gt;The important point is that the team can define how the agent should act, when it should stop, and what evidence it should leave behind.&lt;/p&gt;

&lt;h2&gt;
  
  
  Local-first execution is about control
&lt;/h2&gt;

&lt;p&gt;As browser automation becomes more sensitive, teams start asking a different set of questions.&lt;/p&gt;

&lt;p&gt;Where is the browser data stored? Who can access the session state? Are cookies, local storage, and profile data uploaded somewhere? Can the team inspect what happened after a failed run? Can a human take over the same environment without rebuilding everything?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Local-first execution&lt;/strong&gt; matters because browser state is often sensitive. Account sessions, proxy settings, task logs, and workflow outputs can reveal more than teams expect.&lt;/p&gt;

&lt;p&gt;Keeping profile data on the device gives teams stronger control over their operating environment. It also makes the workflow easier to inspect when something goes wrong.&lt;/p&gt;

&lt;p&gt;This does not mean every workflow must be local forever. But for account-aware automation, local control is often the safer default.&lt;/p&gt;

&lt;h2&gt;
  
  
  Visible browser and headless mode should not be separate worlds
&lt;/h2&gt;

&lt;p&gt;A common automation problem is the gap between visible browser work and headless execution.&lt;/p&gt;

&lt;p&gt;A human opens one environment. A script runs another. An agent sees a third. The logs do not clearly connect them.&lt;/p&gt;

&lt;p&gt;That split makes debugging painful.&lt;/p&gt;

&lt;p&gt;A better workflow lets teams move between visible operation, headless automation, and AI-assisted execution without losing profile context. A human should be able to inspect what the agent saw. A script should be able to run against the right browser instance. An agent should be able to continue from a controlled environment instead of a random fresh session.&lt;/p&gt;

&lt;p&gt;This is where a browser workspace becomes useful.&lt;/p&gt;

&lt;p&gt;It gives teams a shared place to manage profiles, proxies, automation access, and task execution instead of scattering them across scripts, browser folders, and manual notes.&lt;/p&gt;

&lt;h2&gt;
  
  
  When a simple headless script is enough
&lt;/h2&gt;

&lt;p&gt;Not every team needs a browser workspace.&lt;/p&gt;

&lt;p&gt;A simple headless script is enough when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the page is public&lt;/li&gt;
&lt;li&gt;login state does not matter&lt;/li&gt;
&lt;li&gt;proxy consistency is not important&lt;/li&gt;
&lt;li&gt;no long-term profile is needed&lt;/li&gt;
&lt;li&gt;failures can be safely retried&lt;/li&gt;
&lt;li&gt;no human review is required&lt;/li&gt;
&lt;li&gt;the workflow does not touch account assets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For many testing and data tasks, that is perfectly fine.&lt;/p&gt;

&lt;p&gt;The mistake is trying to stretch the same model into account-aware automation without changing the architecture.&lt;/p&gt;

&lt;p&gt;Once the workflow depends on persistent identity, proxy mapping, profile state, task history, and review boundaries, the browser is no longer a disposable process.&lt;/p&gt;

&lt;p&gt;It becomes an operating environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to look for in an AI browser automation workspace
&lt;/h2&gt;

&lt;p&gt;If you are evaluating whether your team has outgrown basic headless scripts, look for these capabilities.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Persistent profile state&lt;/strong&gt;&lt;br&gt;
Each account should have its own cookies, local storage, history, and browser data instead of rebuilding from a clean session every time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Proxy and region mapping&lt;/strong&gt;&lt;br&gt;
Proxy settings should be tied to profiles and workflows, not scattered across launch commands and config files.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fingerprint environment consistency&lt;/strong&gt;&lt;br&gt;
Timezone, language, screen parameters, browser engine, and fingerprint settings should remain stable enough for repeated account work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Automation API access&lt;/strong&gt;&lt;br&gt;
The workspace should still work with tools developers already use, including Playwright, Puppeteer, Selenium, or CDP-based integrations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI agent execution layer&lt;/strong&gt;&lt;br&gt;
Agents should be able to operate inside controlled environments rather than free-running against disconnected browser sessions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reusable workflow templates&lt;/strong&gt;&lt;br&gt;
Repeated browser tasks should become skills or templates that can be improved over time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Headless and visible handoff&lt;/strong&gt;&lt;br&gt;
A human should be able to inspect, interrupt, or continue a task without losing environment context.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Logs and review states&lt;/strong&gt;&lt;br&gt;
The system should show what happened, which environment was used, and where human review is needed.&lt;/p&gt;

&lt;p&gt;These features are not about making automation look more complex. They are about making automation safer to repeat.&lt;/p&gt;

&lt;h2&gt;
  
  
  The shift is from page control to context control
&lt;/h2&gt;

&lt;p&gt;The first wave of browser automation was about controlling pages.&lt;/p&gt;

&lt;p&gt;The next wave is about controlling context.&lt;/p&gt;

&lt;p&gt;AI agents make browser automation more flexible, but they also make context management more important. When an agent can make decisions, retry actions, and adapt to changing pages, the surrounding environment must become more explicit.&lt;/p&gt;

&lt;p&gt;A headless script can open a page.&lt;/p&gt;

&lt;p&gt;A browser workspace can preserve the identity that makes the task meaningful.&lt;/p&gt;

&lt;p&gt;That is the real difference.&lt;/p&gt;

&lt;p&gt;For teams working on multi-account operations, AI-assisted workflows, proxy-aware automation, or long-running browser tasks, &lt;a href="https://web4browser.io/" rel="noopener noreferrer"&gt;Web4 Browser&lt;/a&gt; is one example of how the browser layer can move from isolated windows toward a controlled automation workspace.&lt;/p&gt;

&lt;p&gt;The goal is not to replace scripts.&lt;/p&gt;

&lt;p&gt;The goal is to give scripts and agents&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>webdev</category>
      <category>testing</category>
    </item>
    <item>
      <title>When Browser Automation Should Not Run Fully Headless</title>
      <dc:creator>web4browser</dc:creator>
      <pubDate>Thu, 07 May 2026 04:51:45 +0000</pubDate>
      <link>https://dev.to/web4browser/when-browser-automation-should-not-run-fully-headless-42dd</link>
      <guid>https://dev.to/web4browser/when-browser-automation-should-not-run-fully-headless-42dd</guid>
      <description>&lt;p&gt;Headless browser automation is attractive for one simple reason: it removes friction.&lt;/p&gt;

&lt;p&gt;No visible browser window. No manual clicking. No one waiting beside the screen. No local desktop dependency.&lt;/p&gt;

&lt;p&gt;A task can run in the background, repeat on a schedule, collect results, and move on. For many automation workflows, this is exactly what teams want. Playwright, Puppeteer, and Selenium all make headless execution easy, and many browser automation systems treat headless mode as the natural path to scale.&lt;/p&gt;

&lt;p&gt;But there is a catch.&lt;/p&gt;

&lt;p&gt;Not every browser workflow should run fully invisible.&lt;/p&gt;

&lt;p&gt;A browser task is not only a sequence of clicks. It also carries account state, login state, proxy behavior, fingerprint environment, regional context, page uncertainty, and sometimes human judgment.&lt;/p&gt;

&lt;p&gt;The real question is not:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Can this run headless?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The better question is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Should this step continue without anyone seeing what changed?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For teams working with cross-border e-commerce, advertising checks, social media operations, automated testing, or data research, headless automation works best when it is part of a controlled &lt;a href="https://web4browser.io/" rel="noopener noreferrer"&gt;browser automation workspace&lt;/a&gt;, not when every step is forced into the background.&lt;/p&gt;

&lt;h2&gt;
  
  
  Headless Automation Works Best When the State Is Predictable
&lt;/h2&gt;

&lt;p&gt;Headless automation is very effective when the task state is predictable.&lt;/p&gt;

&lt;p&gt;It works well for page availability checks, structured data collection from stable pages, scheduled QA regression steps, repeated page monitoring, routine account status inspection, and known form paths in test environments.&lt;/p&gt;

&lt;p&gt;In these cases, the workflow is clear. The page structure is known. The expected result is machine-checkable. The failure condition is easy to define. The action does not require subjective judgment.&lt;/p&gt;

&lt;p&gt;This is where headless mode shines.&lt;/p&gt;

&lt;p&gt;A browser does not need to be visible just to confirm that a page returns expected content, that a known element exists, or that a routine flow still works.&lt;/p&gt;

&lt;p&gt;If the state is stable, headless execution saves time and makes repeated tasks easier to schedule.&lt;/p&gt;

&lt;p&gt;The problem starts when the state is no longer stable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Risk Starts When Browser State Becomes Ambiguous
&lt;/h2&gt;

&lt;p&gt;Browser automation becomes risky when the page enters an uncertain state.&lt;/p&gt;

&lt;p&gt;That uncertainty may not look like a technical error.&lt;/p&gt;

&lt;p&gt;The script may still run. The browser may not crash. The selector may still match something. The task may even return a result.&lt;/p&gt;

&lt;p&gt;But the result may no longer mean what the system thinks it means.&lt;/p&gt;

&lt;p&gt;For example, a login session may expire. A verification prompt may appear. A region may change unexpectedly. A page language may switch. A proxy may behave differently. An account status may change. A redirect may lead to a different page. A button may still exist, but its meaning may no longer be the same.&lt;/p&gt;

&lt;p&gt;In visible mode, a human can often notice these changes immediately.&lt;/p&gt;

&lt;p&gt;In fully headless mode, the automation may continue silently.&lt;/p&gt;

&lt;p&gt;That is the danger: headless mode can hide the exact moment when the workflow becomes uncertain.&lt;/p&gt;

&lt;p&gt;A task that should have paused for review may keep running as if everything were normal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-Account Workflows Make Headless Riskier
&lt;/h2&gt;

&lt;p&gt;Headless automation is easier to reason about when there is only one session.&lt;/p&gt;

&lt;p&gt;A simple workflow may look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;script -&amp;gt; browser -&amp;gt; page -&amp;gt; result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If something goes wrong, the debugging scope is relatively small.&lt;/p&gt;

&lt;p&gt;Multi-account workflows are different:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;task -&amp;gt; account profile -&amp;gt; proxy -&amp;gt; fingerprint -&amp;gt; browser state -&amp;gt; headless execution -&amp;gt; log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the problem is not only whether the script clicked the right element.&lt;/p&gt;

&lt;p&gt;The system also needs to know which account was used, which browser profile launched, which proxy or IP was active, which fingerprint environment applied, whether the account was already logged in, whether the page matched the expected region, whether the result was reviewed, and whether the task should retry or stop.&lt;/p&gt;

&lt;p&gt;If one layer is wrong, the output may become unreliable.&lt;/p&gt;

&lt;p&gt;A task may technically complete under the wrong proxy. A profile may launch with stale cookies. A headless retry may run after the account state has changed. A result may be logged without enough context to explain it later.&lt;/p&gt;

&lt;p&gt;In multi-account automation, invisible execution is not automatically safer or more scalable.&lt;/p&gt;

&lt;p&gt;Sometimes it only makes mistakes harder to see.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Better Model: Headless First, Visible When Needed
&lt;/h2&gt;

&lt;p&gt;A stronger model is not “never use headless.”&lt;/p&gt;

&lt;p&gt;That would be inefficient.&lt;/p&gt;

&lt;p&gt;A better model is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Run predictable steps headlessly.
Pause or switch to visible mode when uncertainty appears.
Resume automation after review.
Store the result with account context.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This keeps the efficiency of headless automation without pretending that every decision can safely happen in the background.&lt;/p&gt;

&lt;p&gt;The goal is to separate routine execution from uncertain judgment.&lt;/p&gt;

&lt;p&gt;Routine steps can run headlessly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;open known page
check expected element
collect structured output
save result
close session
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Uncertain steps should trigger review:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;unexpected login state
verification prompt
region mismatch
account warning
changed page layout
irreversible action
unclear result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is especially important for teams.&lt;/p&gt;

&lt;p&gt;A single developer can sometimes watch logs and infer what happened. A team needs a repeatable rule for when automation should continue and when it should stop.&lt;/p&gt;

&lt;p&gt;The best headless workflow is not the one that never shows a browser.&lt;/p&gt;

&lt;p&gt;It is the one that knows when visibility matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Rules for Deciding Headless or Visible
&lt;/h2&gt;

&lt;p&gt;A simple rule works well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;If the expected result is clear and machine-checkable, headless is usually safe.
If the result depends on interpretation, identity, or risk, switch to visible review.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Safe to Run Headless
&lt;/h3&gt;

&lt;p&gt;Headless mode is usually a good fit when the page structure is stable, the account is already authenticated, the proxy/profile mapping is confirmed, the expected output is machine-checkable, the failure condition is clearly defined, the task does not perform irreversible actions, and the result can be validated from logs or screenshots.&lt;/p&gt;

&lt;p&gt;For example, a scheduled page availability check does not need a visible browser window.&lt;/p&gt;

&lt;p&gt;A regression test that checks whether a known checkout button still appears can also run headlessly.&lt;/p&gt;

&lt;p&gt;A data collection task from a stable page may be safe if the team already knows what the output should look like.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should Switch to Visible Review
&lt;/h3&gt;

&lt;p&gt;Visible review is safer when a new login prompt appears, a verification challenge appears, the page language changes, the region appears incorrect, account status changes, payment or publishing actions are involved, the page layout no longer matches expectations, or the result requires human judgment.&lt;/p&gt;

&lt;p&gt;These cases are not just technical branches.&lt;/p&gt;

&lt;p&gt;They are decision points.&lt;/p&gt;

&lt;p&gt;A script can detect that something changed, but a person may still need to decide what that change means.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where AI Agents and Skills Fit Into This
&lt;/h2&gt;

&lt;p&gt;AI agents and reusable skills make browser automation more powerful.&lt;/p&gt;

&lt;p&gt;A Skill can package a repeatable browser operation. An Agent can decide which step to run next. MCP can connect external tools and context. Headless tasks can execute routine work in the background.&lt;/p&gt;

&lt;p&gt;But these capabilities also need boundaries.&lt;/p&gt;

&lt;p&gt;An agent should not only know what to do next.&lt;/p&gt;

&lt;p&gt;It should also know when not to continue silently.&lt;/p&gt;

&lt;p&gt;For example, an agent may be able to navigate a website, fill a form, collect page data, and summarize the result. But if the account enters a verification state, or if the page shows an unexpected warning, the safest action may not be another automated step.&lt;/p&gt;

&lt;p&gt;The safest action may be to stop, expose the browser state, and ask for review.&lt;/p&gt;

&lt;p&gt;This is why headless automation and AI-assisted workflows should not be treated as separate systems.&lt;/p&gt;

&lt;p&gt;They need to share the same execution context:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;account profile
proxy/IP
fingerprint environment
task state
review rule
execution log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without that context, AI automation may become fast but difficult to audit.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Teams Should Log
&lt;/h2&gt;

&lt;p&gt;A headless task should not only log whether it succeeded or failed.&lt;/p&gt;

&lt;p&gt;For team workflows, the log should explain the environment in which the task ran.&lt;/p&gt;

&lt;p&gt;Useful fields include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;account profile
browser profile state
proxy or IP region
fingerprint configuration
execution mode
task step
detected page state
failure reason
retry decision
review decision
final result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This matters because browser automation failures are often not obvious from the final error message.&lt;/p&gt;

&lt;p&gt;A task may fail because the selector changed.&lt;/p&gt;

&lt;p&gt;But it may also fail because the account was logged out, the proxy region shifted, the fingerprint environment did not match the expected profile, or a headless step continued after a review point.&lt;/p&gt;

&lt;p&gt;The more accounts and workflows a team manages, the more important this context becomes.&lt;/p&gt;

&lt;p&gt;Logs are not only for debugging.&lt;/p&gt;

&lt;p&gt;They are part of controlled execution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Headless Automation Is Not the Final Goal
&lt;/h2&gt;

&lt;p&gt;Headless automation is useful.&lt;/p&gt;

&lt;p&gt;It saves time, reduces manual work, and makes scheduled browser tasks easier to operate.&lt;/p&gt;

&lt;p&gt;But headless mode is not the final goal.&lt;/p&gt;

&lt;p&gt;The real goal is controlled execution.&lt;/p&gt;

&lt;p&gt;A good automation system should know:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;what can run invisibly
what needs review
which account is involved
which proxy is active
which browser state was used
which workflow rule applied
what result was produced
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the difference between running a script and operating a browser automation system.&lt;/p&gt;

&lt;p&gt;For individual developers, headless mode may simply mean faster execution.&lt;/p&gt;

&lt;p&gt;For teams, it should mean something more precise:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;routine work runs in the background
uncertain states become visible
sensitive actions require review
results stay attached to account context
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the balance that makes browser automation reliable at scale.&lt;/p&gt;

&lt;p&gt;For teams that need isolated browser profiles, proxy/IP binding, AI-assisted workflows, Skills/MCP execution, and headless tasks with review boundaries, &lt;a href="https://web4browser.io/" rel="noopener noreferrer"&gt;Web4 Browser&lt;/a&gt; is built around this broader browser workspace model.&lt;/p&gt;

&lt;p&gt;Headless automation should not make work invisible at any cost.&lt;/p&gt;

&lt;p&gt;It should make the right parts invisible while keeping the important decisions visible.&lt;/p&gt;

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