<?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: Simon Gerber</title>
    <description>The latest articles on DEV Community by Simon Gerber (@orbitpickle307).</description>
    <link>https://dev.to/orbitpickle307</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3908206%2Fa127719e-0394-4144-9152-c099a1fed303.png</url>
      <title>DEV Community: Simon Gerber</title>
      <link>https://dev.to/orbitpickle307</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/orbitpickle307"/>
    <language>en</language>
    <item>
      <title>The Second Browser Tab Is Where Good Tests Go to Die</title>
      <dc:creator>Simon Gerber</dc:creator>
      <pubDate>Wed, 14 Oct 2026 21:11:57 +0000</pubDate>
      <link>https://dev.to/orbitpickle307/the-second-browser-tab-is-where-good-tests-go-to-die-13h2</link>
      <guid>https://dev.to/orbitpickle307/the-second-browser-tab-is-where-good-tests-go-to-die-13h2</guid>
      <description>&lt;p&gt;The sign-in test passes locally.&lt;/p&gt;

&lt;p&gt;Then you run it in CI and the browser sits on a blank tab until the job times out.&lt;/p&gt;

&lt;p&gt;The application is fine. The identity provider is fine. The browser opened another window, and the automation is still trying to click a button in the first one.&lt;/p&gt;

&lt;p&gt;Welcome to one of the less glamorous parts of browser automation: keeping track of which browser context you're actually controlling.&lt;/p&gt;

&lt;h2&gt;
  
  
  OAuth is easy until the provider changes the route
&lt;/h2&gt;

&lt;p&gt;Consider a typical third-party login:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click "Continue with Google".&lt;/li&gt;
&lt;li&gt;A popup opens on another origin.&lt;/li&gt;
&lt;li&gt;Enter credentials in the provider's window.&lt;/li&gt;
&lt;li&gt;The provider redirects or closes the popup.&lt;/li&gt;
&lt;li&gt;The original application receives the result and renders the signed-in state.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It's tempting to automate that as a series of clicks. But the test really has two responsibilities: interact with the correct window during authentication, and prove that control returned to the app afterward.&lt;/p&gt;

&lt;p&gt;That distinction matters when the popup sometimes redirects inside itself and sometimes closes. A hardcoded assumption about the "second tab" can break as soon as a browser, account state, or provider configuration changes.&lt;/p&gt;

&lt;p&gt;The discussion in &lt;a href="https://playwright-vs-selenium.com/how-to-test-cross-origin-popups-oauth-redirect-returns-and-window-handle-swaps-without-losing-control-of-the-browser-session/" rel="noopener noreferrer"&gt;this guide to cross-origin popups, OAuth returns, and window-handle swaps&lt;/a&gt; is a useful reminder to follow window lifecycle events, not only tab positions.&lt;/p&gt;

&lt;h2&gt;
  
  
  The wrong assertion can make a broken login look successful
&lt;/h2&gt;

&lt;p&gt;Here's a suspiciously common test:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click Sign in.&lt;/li&gt;
&lt;li&gt;Enter username and password.&lt;/li&gt;
&lt;li&gt;Click Continue.&lt;/li&gt;
&lt;li&gt;Assert that the login popup disappeared.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Congratulations. You've verified that a popup disappeared.&lt;/p&gt;

&lt;p&gt;It might have closed because login succeeded. It might have crashed. It might have been blocked. The user might still be signed out.&lt;/p&gt;

&lt;p&gt;The assertion should live in the application: a signed-in session exists, the right account is shown, and a protected page behaves as expected. If the purpose of the test is specifically to inspect OAuth errors, that's a different scenario.&lt;/p&gt;

&lt;p&gt;This sounds obvious until you browse a test suite with 300 steps and discover that half of the "success" checks only verify that a modal closed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tool choice matters, but not in the usual way
&lt;/h2&gt;

&lt;p&gt;Framework comparisons often start with syntax and speed. I care more about how visible the browser state is when something goes wrong.&lt;/p&gt;

&lt;p&gt;Can the test identify the new page or handle without guessing? Can it work across origins? Can it show you what happened in each window? Can the person debugging it understand the sequence without replaying the failure on their laptop?&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://browserslack.com/cypress-vs-webdriverio-which-stack-fits-teams-that-need-faster-debugging-more-browser-control-or-less-framework-lock-in/" rel="noopener noreferrer"&gt;Cypress vs WebdriverIO comparison&lt;/a&gt; explores some of the tradeoffs around browser control and debugging. The right choice depends on the flows you actually need to support, especially if your app relies on popups, multiple tabs, or external authentication.&lt;/p&gt;

&lt;p&gt;Don't decide based solely on the prettiest demo. Give both candidates the same annoying real-world login flow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Repeatable tests start with boring session hygiene
&lt;/h2&gt;

&lt;p&gt;A second issue often gets mixed up with window handling: test state leaks.&lt;/p&gt;

&lt;p&gt;The test opens with an already-authenticated cookie. Or the provider remembers the previous account. Or the registration email is already in use. Now the flow takes a different path from yesterday, and somebody labels it flaky.&lt;/p&gt;

&lt;p&gt;Before each run, define the desired starting state. Is this a first-time login or a returning user? Are cookies preserved intentionally? What happens to the user record afterward? If the test creates data, who deletes it?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://ai-testing-tools.com/what-to-look-for-in-ai-testing-platforms-for-disposable-test-data-session-hygiene-and-reliable-reset-workflows/" rel="noopener noreferrer"&gt;This evaluation guide to disposable test data and session resets&lt;/a&gt; asks a surprisingly valuable question: does the testing platform survive a rerun without someone manually cleaning up?&lt;/p&gt;

&lt;p&gt;A great first run followed by a broken second run is not a great test.&lt;/p&gt;

&lt;h2&gt;
  
  
  Capture the handoff, not just the ending
&lt;/h2&gt;

&lt;p&gt;If a multi-window test fails, a screenshot of the last visible page is often insufficient. You want the sequence:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which window was active before the click?&lt;/li&gt;
&lt;li&gt;Which new window appeared, and when?&lt;/li&gt;
&lt;li&gt;What URL did each context reach?&lt;/li&gt;
&lt;li&gt;When was the original context restored?&lt;/li&gt;
&lt;li&gt;What did the application show after return?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's also why I find &lt;a href="https://testautomationreviews.com/benchmark-plan-comparing-browser-testing-platforms-on-multi-window-handoffs-evidence-capture-and-failure-replay-clarity/" rel="noopener noreferrer"&gt;this multi-window testing benchmark plan&lt;/a&gt; more useful than a generic pass-rate comparison. It's interested in handoffs and the evidence left behind when they fail.&lt;/p&gt;

&lt;p&gt;You don't need a giant framework to think this way. Even a simple test can explicitly record the active context, verify the return, and reset its session.&lt;/p&gt;

&lt;p&gt;The irony is that a one-line login assertion can depend on five systems: your frontend, your backend, a browser, an identity provider, and the test runner's context management. When it fails, "add a sleep" is rarely the best investigative strategy.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>automation</category>
      <category>webdev</category>
      <category>security</category>
    </item>
    <item>
      <title>How to Make Playwright Autonomous (Without Letting an AI Agent Run Wild)</title>
      <dc:creator>Simon Gerber</dc:creator>
      <pubDate>Sat, 10 Oct 2026 21:14:46 +0000</pubDate>
      <link>https://dev.to/orbitpickle307/how-to-make-playwright-autonomous-without-letting-an-ai-agent-run-wild-3ck4</link>
      <guid>https://dev.to/orbitpickle307/how-to-make-playwright-autonomous-without-letting-an-ai-agent-run-wild-3ck4</guid>
      <description>&lt;p&gt;Playwright is very good at following instructions.&lt;/p&gt;

&lt;p&gt;Tell it to click a button, it clicks the button. Tell it to fill an input, it fills the input. Give it a test with 40 steps and, assuming the page behaves, it'll run all 40.&lt;/p&gt;

&lt;p&gt;The problem is that someone has to write those steps.&lt;/p&gt;

&lt;p&gt;What if you gave Playwright a goal instead?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Add two items to a shopping cart, apply the discount code, and check that the total is correct.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The browser would need to inspect the page, work out what to click, notice when something changes, and decide what to do next. That's a different kind of automation. And it's a fun engineering problem, provided you don't confuse &lt;em&gt;the agent completed its plan&lt;/em&gt; with &lt;em&gt;the application passed a test&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Let's build a small version. No giant agent framework. Just Playwright, an LLM API, and a loop we can inspect.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we're actually building
&lt;/h2&gt;

&lt;p&gt;A normal Playwright script is a list of instructions:&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;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&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;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Add notebook&lt;/span&gt;&lt;span class="dl"&gt;'&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="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;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&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;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Checkout&lt;/span&gt;&lt;span class="dl"&gt;'&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our agent will do something closer to this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Observe the current page using an accessibility snapshot.&lt;/li&gt;
&lt;li&gt;Ask a language model for exactly one next action.&lt;/li&gt;
&lt;li&gt;Validate that the action belongs to a small set of permitted operations.&lt;/li&gt;
&lt;li&gt;Execute it with Playwright.&lt;/li&gt;
&lt;li&gt;Observe the new state and repeat.&lt;/li&gt;
&lt;li&gt;Separately assert the result in code.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The important word is &lt;strong&gt;separately&lt;/strong&gt;. An agent is useful for finding a path through the interface. It shouldn't get the final vote on whether the system works.&lt;/p&gt;

&lt;h2&gt;
  
  
  A tiny app to test
&lt;/h2&gt;

&lt;p&gt;We'll use a local checkout page so this tutorial doesn't depend on somebody else's website or an account with real payment details.&lt;/p&gt;

&lt;p&gt;Create a directory and install the dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;autonomous-playwright
&lt;span class="nb"&gt;cd &lt;/span&gt;autonomous-playwright
npm init &lt;span class="nt"&gt;-y&lt;/span&gt;
npm &lt;span class="nb"&gt;install &lt;/span&gt;playwright dotenv
npx playwright &lt;span class="nb"&gt;install &lt;/span&gt;chromium
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll also need an API key for an OpenAI-compatible chat completions endpoint. The example below uses OpenAI's endpoint and defaults to &lt;code&gt;gpt-4.1-mini&lt;/code&gt;. You can select a different compatible model with an environment variable.&lt;/p&gt;

&lt;p&gt;Create &lt;code&gt;index.html&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!doctype html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Tiny Shop&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;main&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Tiny Shop&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Notebook: $12&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Pen: $3&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"notebook"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Add notebook&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"pen"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Add pen&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"cart"&lt;/span&gt; &lt;span class="na"&gt;aria-live=&lt;/span&gt;&lt;span class="s"&gt;"polite"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Cart: 0 items&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"code"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Discount code&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"code"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"apply"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Apply discount&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"discount"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Discount: $0&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"checkout"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Checkout&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"receipt"&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"status"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/main&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;notebookCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;penCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;discount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&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;$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;renderCart&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cart&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`Cart: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;notebookCount&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;penCount&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; items`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;notebook&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;onclick&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;notebookCount&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nf"&gt;renderCart&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pen&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;onclick&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;penCount&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nf"&gt;renderCart&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;apply&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;onclick&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;discount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;code&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SAVE5&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;discount&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`Discount: $&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;discount&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="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;checkout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;onclick&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;notebookCount&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;penCount&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;discount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;receipt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`Order total: $&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;total&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="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is deliberately boring. Boring fixtures are good. If the experiment goes sideways, we want to know whether the problem is in the agent, not the store.&lt;/p&gt;

&lt;p&gt;Run the page in one terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; http.server 4173
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should now have a checkout page at &lt;code&gt;http://127.0.0.1:4173&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give Playwright a brain, but very small hands
&lt;/h2&gt;

&lt;p&gt;Put your API key in &lt;code&gt;.env&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OPENAI_API_KEY=your_api_key_here
MODEL=gpt-4.1-mini
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't commit this file to Git.&lt;/p&gt;

&lt;p&gt;Now create &lt;code&gt;agent.mjs&lt;/code&gt;:&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dotenv/config&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="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="s1"&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="s1"&gt;node:fs/promises&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;BASE_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://127.0.0.1:4173&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;MAX_STEPS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;12&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;MODEL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MODEL&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gpt-4.1-mini&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;GOAL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Add exactly one notebook and one pen, apply code SAVE5, &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="s1"&gt;then checkout. The correct final total is $10.&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Set OPENAI_API_KEY in .env&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="c1"&gt;// The model can choose only one of these operations.&lt;/span&gt;
&lt;span class="c1"&gt;// It cannot submit raw JS, shell commands, or arbitrary URLs.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SYSTEM_PROMPT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`
You operate a local demo shopping page through a restricted browser API.
Return ONLY a JSON object with one of these shapes:
{"action":"click","role":"button","name":"visible accessible name"}
{"action":"fill","role":"textbox","name":"visible accessible name","value":"text"}
{"action":"finish","reason":"brief explanation"}

Choose ONE action per response. Base it on the current snapshot and history.
Do not invent elements. Do not repeat an action that already succeeded.
Only use the role and accessible name shown on the page.
When the checkout receipt shows the requested total, choose finish.
`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;chooseAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;history&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;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.openai.com/v1/chat/completions&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;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OPENAI_API_KEY&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&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="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&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;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;response_format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;json_object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;system&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SYSTEM_PROMPT&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&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;content&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;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;goal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GOAL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;history&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;snapshot&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="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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&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;`Model API returned &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="nx"&gt;status&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="k"&gt;await&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;text&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&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;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;?.[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;content&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;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Model returned no content&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&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;content&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;validateAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&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;a&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Invalid action&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;finish&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reason&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Missing finish reason&lt;/span&gt;&lt;span class="dl"&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="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;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fill&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="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;`Unsupported action: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;action&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&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="s1"&gt;button&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="s1"&gt;textbox&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="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Role not permitted for this action&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Invalid accessible name&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fill&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Invalid input value&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;applyAction&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;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;validateAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&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;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;finish&lt;/span&gt;&lt;span class="dl"&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;locator&lt;/span&gt; &lt;span class="o"&gt;=&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;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;exact&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="c1"&gt;// Do not guess when a locator is ambiguous.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;count&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;locator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&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;count&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;1&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;`Expected one match, found &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;count&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&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;locator&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="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3000&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;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fill&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;locator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3000&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;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="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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;history&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;agentFinished&lt;/span&gt; &lt;span class="o"&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;let&lt;/span&gt; &lt;span class="nx"&gt;failure&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="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="nx"&gt;tracing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;screenshots&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;snapshots&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;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;BASE_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;waitUntil&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;domcontentloaded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;MAX_STEPS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// The snapshot is a compact description of the accessible UI.&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;snapshot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;body&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ariaSnapshot&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12000&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;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;chooseAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;history&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;validateAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&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="s2"&gt;`Step &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;step&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;action&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;history&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&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;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;finish&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;agentFinished&lt;/span&gt; &lt;span class="o"&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;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Prevent the agent from navigating out of our local test target.&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;new&lt;/span&gt; &lt;span class="nc"&gt;URL&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="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nx"&gt;origin&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;BASE_URL&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Agent left the approved origin&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="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;applyAction&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;action&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;agentFinished&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;`Agent exceeded &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;MAX_STEPS&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; steps`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Here is the actual test oracle, independent of the model's opinion.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cart&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;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#cart&lt;/span&gt;&lt;span class="dl"&gt;'&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;discount&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;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#discount&lt;/span&gt;&lt;span class="dl"&gt;'&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;receipt&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;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#receipt&lt;/span&gt;&lt;span class="dl"&gt;'&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cart&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Cart: 2 items&lt;/span&gt;&lt;span class="dl"&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;`Wrong cart: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;cart&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;discount&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Discount: $5&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="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;`Wrong discount: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;discount&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;receipt&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Order total: $10&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="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;`Wrong receipt: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;receipt&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PASS: correct cart, discount, and total&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="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;failure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;error&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;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;FAIL:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&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="s1"&gt;failure.png&lt;/span&gt;&lt;span class="dl"&gt;'&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;catch&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="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;agent-history.json&lt;/span&gt;&lt;span class="dl"&gt;'&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;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;history&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&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="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="nx"&gt;tracing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stop&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="s1"&gt;trace.zip&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;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;failure&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exitCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it in a second terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node agent.mjs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On a successful run, the model will choose actions corresponding to adding a notebook, adding a pen, entering &lt;code&gt;SAVE5&lt;/code&gt;, applying it, checking out, and finishing. The exact order and number of model calls can vary. At the end, the script checks three concrete facts: two cart items, a $5 discount, and a $10 receipt.&lt;/p&gt;

&lt;p&gt;If it fails, you'll still get &lt;code&gt;agent-history.json&lt;/code&gt; and &lt;code&gt;trace.zip&lt;/code&gt;; failures also attempt a screenshot. Open the trace with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx playwright show-trace trace.zip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A note about the API: &lt;code&gt;response_format: { type: 'json_object' }&lt;/code&gt; asks the model for JSON, not for a guaranteed valid browser command. That's why &lt;code&gt;validateAction()&lt;/code&gt; still exists. The code uses the raw Playwright library rather than Playwright Test, so the trace contains browser activity but not Playwright Test's assertion metadata.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture is more important than the model
&lt;/h2&gt;

&lt;p&gt;Notice how little authority we gave the LLM.&lt;/p&gt;

&lt;p&gt;It can click a &lt;strong&gt;named button&lt;/strong&gt; or fill a &lt;strong&gt;named textbox&lt;/strong&gt;. That's it. The model can't execute JavaScript, visit another website, delete files, call internal APIs, or decide that a failing assertion should be ignored.&lt;/p&gt;

&lt;p&gt;It also doesn't receive the entire DOM. We use Playwright's &lt;code&gt;locator.ariaSnapshot()&lt;/code&gt; to send a readable view of the accessible interface. This API is available in Playwright 1.49 and later. It's often enough for buttons, links, forms, and page text. It isn't magic: poorly labeled controls and visual-only interfaces remain difficult.&lt;/p&gt;

&lt;p&gt;Three design choices are doing most of the work:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One action at a time.&lt;/strong&gt; If the model proposes a 12-step plan in advance, step four may be wrong because step two opened a dialog. Observe again after every action.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A fixed budget.&lt;/strong&gt; Autonomous doesn't mean infinite. Twelve actions is plenty for our checkout. For a production app, give each scenario a time limit, a step limit, and a clear failure state.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An independent oracle.&lt;/strong&gt; We don't ask, "Did you test checkout successfully?" and accept "Yes". We inspect application state and compare it with expected values. In a real system you might check an order API, a database record, or a known fixture instead of trusting the page alone.&lt;/p&gt;

&lt;p&gt;That last point is where a lot of impressive autonomous testing demos quietly stop being convincing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's break it deliberately
&lt;/h2&gt;

&lt;p&gt;Change this line in &lt;code&gt;index.html&lt;/code&gt;:&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;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;notebookCount&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;penCount&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;discount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to:&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;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;notebookCount&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;penCount&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the checkout ignores the discount. The agent may complete all the clicks and even report that it's finished. The script should still fail because the receipt says &lt;code&gt;$15&lt;/code&gt;, not &lt;code&gt;$10&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That's what you want from a test: a failure that doesn't depend on how confident the agent sounds.&lt;/p&gt;

&lt;p&gt;Put the original line back before continuing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this gets expensive faster than you expect
&lt;/h2&gt;

&lt;p&gt;For our small page, a model call after every action is manageable. For a typical enterprise flow, the numbers look different.&lt;/p&gt;

&lt;p&gt;Say you have a signup journey with 25 browser actions. At one model call per action, that's 25 model requests for one attempt. Run it across four browsers and you've got up to 100 requests, before retries. Add 200 scenarios and you start caring about latency, context size, model availability, and cost.&lt;/p&gt;

&lt;p&gt;And the more interesting the app, the worse the edge cases become:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A button opens a new tab, but the agent is still observing the old one.&lt;/li&gt;
&lt;li&gt;A checkout step runs inside an iframe.&lt;/li&gt;
&lt;li&gt;A file upload requires interacting with a hidden input.&lt;/li&gt;
&lt;li&gt;An email verification step depends on another system.&lt;/li&gt;
&lt;li&gt;A model chooses a plausible but wrong "Continue" button.&lt;/li&gt;
&lt;li&gt;A slow UI update makes a successful action look like a failure, so the agent clicks twice.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These aren't criticisms of Playwright. Playwright gives you APIs for popups, frames, uploads, and waiting. But now &lt;em&gt;your agent&lt;/em&gt; needs policies for all of them, plus recovery logic, reporting, retries, and a way for a human to correct its decisions.&lt;/p&gt;

&lt;p&gt;You can build it. The question is whether maintaining that platform is the best use of your team's time.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd change before using this in CI
&lt;/h2&gt;

&lt;p&gt;I wouldn't point the script above at a production account. It's a learning example with intentionally narrow permissions, not a hardened security boundary. Its origin check happens between steps; real deployments should also restrict network access and use isolated test accounts.&lt;/p&gt;

&lt;p&gt;Before a serious rollout, I'd make a few changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Use deterministic assertions and fixtures.&lt;/strong&gt; Keep the LLM in charge of exploration, not the definition of success.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Limit the accessible environment.&lt;/strong&gt; Run in disposable browser contexts with test data, restricted credentials, and network controls. An application's page content can contain instructions aimed at the model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treat action history as data, not proof.&lt;/strong&gt; Save traces and screenshots, but redact secrets before sending page content to an external model or storing artifacts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Handle tabs, frames, and downloads explicitly.&lt;/strong&gt; Don't assume &lt;code&gt;page&lt;/code&gt; is always the right page or that accessibility snapshots expose every useful target.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep successful paths for review.&lt;/strong&gt; Once an agent discovers a stable checkout flow, consider converting it into an ordinary Playwright test. There is no prize for paying an LLM to rediscover the same five clicks every morning.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The last one is especially useful. Autonomous discovery and deterministic regression testing solve slightly different problems. You don't have to choose only one.&lt;/p&gt;

&lt;h2&gt;
  
  
  When I'd use something other than a homemade agent
&lt;/h2&gt;

&lt;p&gt;If you're learning how browser agents work, this project is worth doing. You'll understand the observation/action loop, the failure modes, and why good test oracles matter.&lt;/p&gt;

&lt;p&gt;If you're responsible for a team's regression suite, the calculation changes.&lt;/p&gt;

&lt;p&gt;There are platforms that already package autonomous browser exploration with test creation and managed execution. &lt;a href="https://endtest.io/" rel="noopener noreferrer"&gt;Endtest&lt;/a&gt; is one example. Its &lt;a href="https://endtest.io/docs/advanced/endtest-bot" rel="noopener noreferrer"&gt;Endtest Bot&lt;/a&gt; is designed to explore a web application and generate test scenarios, while the broader platform handles running and managing web tests. That's much closer to the outcome most QA teams want than maintaining their own model prompts and browser orchestration code.&lt;/p&gt;

&lt;p&gt;I'd evaluate a product like that on the difficult flows, not on a login demo: iframes, multiple tabs, uploads, conditional paths, and what happens when an element changes. Also ask to inspect and edit the generated steps. If you can't understand what an autonomous test did, you have a maintenance problem disguised as an AI feature.&lt;/p&gt;

&lt;p&gt;A custom Playwright agent can still be the better choice if you need full control over models, infrastructure, or unusual browser behavior. You're trading platform fees for engineering time. Neither option is free.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part worth keeping
&lt;/h2&gt;

&lt;p&gt;You can make Playwright autonomous with surprisingly little code. The browser library was never the hard part.&lt;/p&gt;

&lt;p&gt;The hard part is deciding what the agent is allowed to do, how to tell whether it did the right thing, and what evidence you have when it didn't.&lt;/p&gt;

&lt;p&gt;Start with one business-critical flow. Make the failure deterministic. Then add autonomy where it saves you work, rather than where it makes for the flashiest demo.&lt;/p&gt;

&lt;p&gt;That's a much better test automation strategy than building a robot that can click anything and trusting it when it says everything is fine.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;References:&lt;/strong&gt; &lt;a href="https://playwright.dev/docs/aria-snapshots" rel="noopener noreferrer"&gt;Playwright accessibility snapshots&lt;/a&gt;, &lt;a href="https://playwright.dev/docs/api/class-locator" rel="noopener noreferrer"&gt;Playwright locator API&lt;/a&gt;, &lt;a href="https://playwright.dev/docs/api/class-tracing" rel="noopener noreferrer"&gt;Playwright tracing&lt;/a&gt;, &lt;a href="https://playwright.dev/docs/api/class-browsercontext" rel="noopener noreferrer"&gt;Playwright browser contexts&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>playwright</category>
      <category>ai</category>
      <category>javascript</category>
      <category>testing</category>
    </item>
    <item>
      <title>TestDino Is Useless If You Already Use Playwright</title>
      <dc:creator>Simon Gerber</dc:creator>
      <pubDate>Thu, 08 Oct 2026 21:41:17 +0000</pubDate>
      <link>https://dev.to/orbitpickle307/testdino-is-useless-if-you-already-use-playwright-3d6i</link>
      <guid>https://dev.to/orbitpickle307/testdino-is-useless-if-you-already-use-playwright-3d6i</guid>
      <description>&lt;p&gt;I keep seeing TestDino pop up around Playwright.&lt;/p&gt;

&lt;p&gt;The pitch sounds reasonable.&lt;/p&gt;

&lt;p&gt;You already have Playwright tests.&lt;/p&gt;

&lt;p&gt;TestDino sits on top of them and gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;centralized reporting&lt;/li&gt;
&lt;li&gt;flaky-test detection&lt;/li&gt;
&lt;li&gt;failure grouping&lt;/li&gt;
&lt;li&gt;AI analysis&lt;/li&gt;
&lt;li&gt;historical trends&lt;/li&gt;
&lt;li&gt;screenshots and traces&lt;/li&gt;
&lt;li&gt;PR integrations&lt;/li&gt;
&lt;li&gt;an MCP server&lt;/li&gt;
&lt;/ul&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Playwright
    ↓
TestDino
    ↓
More dashboards
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I like testing tools.&lt;/p&gt;

&lt;p&gt;I like Playwright.&lt;/p&gt;

&lt;p&gt;I particularly like tools that remove annoying QA work.&lt;/p&gt;

&lt;p&gt;So I went into this expecting there to be something interesting here.&lt;/p&gt;

&lt;p&gt;And there is.&lt;/p&gt;

&lt;p&gt;Just not enough for me to understand why most Playwright teams would pay for it.&lt;/p&gt;

&lt;p&gt;The more I looked, the more TestDino started to feel like SaaS rent for looking at your own Playwright results.&lt;/p&gt;

&lt;h2&gt;
  
  
  First, what does TestDino actually do?
&lt;/h2&gt;

&lt;p&gt;This is important because the website throws around phrases like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;AI-native test observability&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;and:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;cloud companion for Playwright&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Underneath that language, the product is much easier to explain.&lt;/p&gt;

&lt;p&gt;You install their Playwright reporter:&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="nx"&gt;reporter&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@testdino/playwright&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;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TESTDINO_TOKEN&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;Your Playwright runs get uploaded to TestDino.&lt;/p&gt;

&lt;p&gt;TestDino then stores, organizes, analyzes, and displays the results.&lt;/p&gt;

&lt;p&gt;Its own documentation describes it as a platform for Playwright test reporting, failure analysis, flaky-test detection, integrations, analytics, and historical results.&lt;/p&gt;

&lt;p&gt;That's useful.&lt;/p&gt;

&lt;p&gt;But let's be clear about what you're buying.&lt;/p&gt;

&lt;p&gt;You're not replacing Playwright.&lt;/p&gt;

&lt;p&gt;You're not getting a new browser automation engine.&lt;/p&gt;

&lt;p&gt;You're not getting some magical testing infrastructure that suddenly executes things Playwright couldn't execute.&lt;/p&gt;

&lt;p&gt;You're mostly adding a SaaS layer around Playwright.&lt;/p&gt;

&lt;p&gt;And that's where I start having questions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Playwright already gives me a ridiculous amount of debugging information
&lt;/h2&gt;

&lt;p&gt;Playwright isn't exactly starving us for evidence.&lt;/p&gt;

&lt;p&gt;A failing Playwright test can already give you:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;screenshots
videos
traces
DOM snapshots
network activity
console output
source information
action timelines
retries
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;TestDino's own trace documentation explains how its trace viewer exposes the actions, DOM, network, console, source, and timing information captured by Playwright traces.&lt;/p&gt;

&lt;p&gt;That's nice.&lt;/p&gt;

&lt;p&gt;But I kept having this thought while looking at the product:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How much am I paying TestDino to organize information that Playwright already generated?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The answer isn't "nothing."&lt;/p&gt;

&lt;p&gt;Centralized history is genuinely useful.&lt;/p&gt;

&lt;p&gt;Cross-run flake detection is useful.&lt;/p&gt;

&lt;p&gt;Grouping failures across hundreds of tests is useful.&lt;/p&gt;

&lt;p&gt;But this is a narrower value proposition than the marketing makes it sound.&lt;/p&gt;

&lt;p&gt;If your Playwright suite is 150 tests and your team can already diagnose failures from CI artifacts, TestDino feels like bringing Salesforce into a lemonade stand.&lt;/p&gt;

&lt;h2&gt;
  
  
  And it only works with Playwright
&lt;/h2&gt;

&lt;p&gt;This one is pretty brutal.&lt;/p&gt;

&lt;p&gt;TestDino is explicitly built around Playwright.&lt;/p&gt;

&lt;p&gt;If your company has:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Playwright
+
Selenium
+
Cypress
+
mobile automation
+
API suites
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you don't really have a central testing platform.&lt;/p&gt;

&lt;p&gt;You have a Playwright dashboard.&lt;/p&gt;

&lt;p&gt;Everything else still lives somewhere else.&lt;/p&gt;

&lt;p&gt;That may be completely fine for a young company standardized on Playwright.&lt;/p&gt;

&lt;p&gt;It's much less appealing for a mature QA organization.&lt;/p&gt;

&lt;p&gt;The weird part is that the more sophisticated your testing organization becomes, the more likely you are to have multiple types of automation.&lt;/p&gt;

&lt;p&gt;Which makes TestDino's narrowness increasingly noticeable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pricing looks reasonable until you do the math
&lt;/h2&gt;

&lt;p&gt;At first glance, pricing actually looks pretty good.&lt;/p&gt;

&lt;p&gt;Current published pricing is approximately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Free     $0
Pro      $49/month
Team     $99/month
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;with lower prices if billed annually.&lt;/p&gt;

&lt;p&gt;Free gets 5,000 executions.&lt;/p&gt;

&lt;p&gt;Pro gets 10,000.&lt;/p&gt;

&lt;p&gt;Team gets 40,000.&lt;/p&gt;

&lt;p&gt;Not crazy.&lt;/p&gt;

&lt;p&gt;Then you read the important part.&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;One test across 3 browsers = 3 results.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's where this gets interesting.&lt;/p&gt;

&lt;p&gt;Imagine a fairly modest suite:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;500 tests
×
3 browsers
×
30 daily runs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;45,000 test results/month
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Congratulations.&lt;/p&gt;

&lt;p&gt;You've exceeded the published Team allowance.&lt;/p&gt;

&lt;p&gt;With 500 tests.&lt;/p&gt;

&lt;p&gt;Running once per day.&lt;/p&gt;

&lt;p&gt;That's not remotely unusual for a serious automation team.&lt;/p&gt;

&lt;p&gt;And if you're running the suite:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;per PR
+
nightly
+
staging
+
production
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;those numbers can climb very quickly.&lt;/p&gt;

&lt;p&gt;Usage-based pricing isn't inherently bad.&lt;/p&gt;

&lt;p&gt;But TestDino's value increases when you run lots of tests, while the pricing also increases with how many tests you run.&lt;/p&gt;

&lt;p&gt;That's an awkward incentive.&lt;/p&gt;

&lt;h2&gt;
  
  
  The user limits are weird too
&lt;/h2&gt;

&lt;p&gt;The Free plan allows one user.&lt;/p&gt;

&lt;p&gt;Pro allows three.&lt;/p&gt;

&lt;p&gt;Team allows 30.&lt;/p&gt;

&lt;p&gt;So suppose you're a tiny QA team with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2 QA engineers
2 developers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You may have very low execution volume.&lt;/p&gt;

&lt;p&gt;Doesn't matter.&lt;/p&gt;

&lt;p&gt;If all four need proper access, you're already outside Pro's three-user allowance.&lt;/p&gt;

&lt;p&gt;That jumps you toward Team.&lt;/p&gt;

&lt;p&gt;For what is still fundamentally a Playwright reporting dashboard.&lt;/p&gt;

&lt;p&gt;This is the kind of SaaS packaging that makes me tired.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then there's artifact retention
&lt;/h2&gt;

&lt;p&gt;This part bothers me more.&lt;/p&gt;

&lt;p&gt;According to TestDino's pricing table, Pro and Team have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;21 days artifact retention
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;while analytics may be retained much longer.&lt;/p&gt;

&lt;p&gt;Think about what TestDino is selling.&lt;/p&gt;

&lt;p&gt;Historical context.&lt;/p&gt;

&lt;p&gt;Debugging.&lt;/p&gt;

&lt;p&gt;Failure analysis.&lt;/p&gt;

&lt;p&gt;Understanding recurring issues.&lt;/p&gt;

&lt;p&gt;Now imagine:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Didn't we have this exact checkout failure last month?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Great question.&lt;/p&gt;

&lt;p&gt;The analytics may remember it.&lt;/p&gt;

&lt;p&gt;But the screenshot, video, or artifact you actually want may already be gone.&lt;/p&gt;

&lt;p&gt;Three weeks feels short for a product whose entire purpose is to help me understand historical test failures.&lt;/p&gt;

&lt;p&gt;I can obviously keep my artifacts elsewhere.&lt;/p&gt;

&lt;p&gt;But now we're back to:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why am I paying another service for this?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The pricing page itself confused me
&lt;/h2&gt;

&lt;p&gt;Here's something I didn't expect.&lt;/p&gt;

&lt;p&gt;On the pricing page, the Pro plan says it includes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI classification
AI test analysis
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;as additions over Free.&lt;/p&gt;

&lt;p&gt;But lower on the same page, the FAQ says every plan includes AI failure classification.&lt;/p&gt;

&lt;p&gt;Maybe there's a perfectly reasonable distinction between exactly which AI features are included.&lt;/p&gt;

&lt;p&gt;But I shouldn't have to reverse engineer the pricing page to understand it.&lt;/p&gt;

&lt;p&gt;This is a test analytics company.&lt;/p&gt;

&lt;p&gt;I expect the test results to be clear.&lt;/p&gt;

&lt;p&gt;I'd also appreciate the pricing being clear.&lt;/p&gt;

&lt;h2&gt;
  
  
  Uploading all this debugging data is not a trivial decision
&lt;/h2&gt;

&lt;p&gt;Remember what we're sending.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;screenshots
videos
traces
logs
DOM state
network information
test names
branch information
commit information
failure data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;TestDino advertises ISO 27001, SOC 2 Type II, GDPR support, and EU hosting options, which is reassuring.&lt;/p&gt;

&lt;p&gt;But its pricing page lists data redaction under Enterprise features.&lt;/p&gt;

&lt;p&gt;That makes me uncomfortable.&lt;/p&gt;

&lt;p&gt;For a reporting product ingesting screenshots, traces, logs, and potentially application data, redaction isn't some exotic Fortune 100 feature.&lt;/p&gt;

&lt;p&gt;It can be basic hygiene.&lt;/p&gt;

&lt;p&gt;Maybe your test environment contains nothing sensitive.&lt;/p&gt;

&lt;p&gt;Great.&lt;/p&gt;

&lt;p&gt;But I'd check that assumption before uploading every Playwright artifact your company produces.&lt;/p&gt;

&lt;h2&gt;
  
  
  The AI part doesn't move me much
&lt;/h2&gt;

&lt;p&gt;TestDino groups failures and categorizes issues.&lt;/p&gt;

&lt;p&gt;The product talks about classifications such as timing, environment, network, assertion, bugs, UI changes, and flaky behavior.&lt;/p&gt;

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

&lt;p&gt;But I don't think:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Timeout → probably timing issue
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is enough to justify another platform.&lt;/p&gt;

&lt;p&gt;What I'd want to know is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Was the classification correct?

How often is it wrong?

Can it distinguish a flaky test from a real race condition?

Can it identify the commit that caused the regression?

Can it propose a fix that actually preserves test intent?

Does it save enough investigation time to justify uploading and storing everything elsewhere?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those are much harder questions.&lt;/p&gt;

&lt;p&gt;A dashboard saying:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NETWORK
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;with a nice badge doesn't automatically mean the debugging problem has been solved.&lt;/p&gt;

&lt;h2&gt;
  
  
  The MCP server is probably the most interesting part
&lt;/h2&gt;

&lt;p&gt;This is where I finally found something I genuinely like.&lt;/p&gt;

&lt;p&gt;TestDino exposes test information to AI agents over MCP.&lt;/p&gt;

&lt;p&gt;That means your coding agent can ask things like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;What failed in the latest run?

Which tests are flaky?

Show the historical failures for this test.

What changed around this failure?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;without you copying logs into Claude, Cursor, or another tool manually.&lt;/p&gt;

&lt;p&gt;That's actually interesting.&lt;/p&gt;

&lt;p&gt;I can see real value there.&lt;/p&gt;

&lt;p&gt;But again:&lt;/p&gt;

&lt;p&gt;I'm uploading my test data into a separate SaaS product so an AI can query it.&lt;/p&gt;

&lt;p&gt;That's useful if TestDino becomes the place where my testing history lives.&lt;/p&gt;

&lt;p&gt;It's much less compelling if I already have that information structured elsewhere.&lt;/p&gt;

&lt;p&gt;MCP doesn't magically make mediocre data valuable.&lt;/p&gt;

&lt;p&gt;It just makes it easier for an agent to access it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Public reviews are surprisingly positive
&lt;/h2&gt;

&lt;p&gt;Here's where I have to be fair.&lt;/p&gt;

&lt;p&gt;People who publicly review TestDino mostly seem to like it.&lt;/p&gt;

&lt;p&gt;Reviewers frequently praise:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;easy setup&lt;/li&gt;
&lt;li&gt;clean dashboards&lt;/li&gt;
&lt;li&gt;flaky-test visibility&lt;/li&gt;
&lt;li&gt;centralized reporting&lt;/li&gt;
&lt;li&gt;support&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So maybe I'm the grumpy one here.&lt;/p&gt;

&lt;p&gt;But there are hints of the same concerns I had.&lt;/p&gt;

&lt;p&gt;Some public feedback has called for deeper debugging, more customization, stronger documentation for advanced workflows, better linking between manual and automated tests, and more flexible repository handling.&lt;/p&gt;

&lt;p&gt;Those are not catastrophic problems.&lt;/p&gt;

&lt;p&gt;They just reinforce my overall impression:&lt;/p&gt;

&lt;p&gt;TestDino looks polished at the surface.&lt;/p&gt;

&lt;p&gt;I'm less convinced about depth.&lt;/p&gt;

&lt;h2&gt;
  
  
  An earlier hands-on review found exactly that problem
&lt;/h2&gt;

&lt;p&gt;An independent review from 2025 had a similar experience.&lt;/p&gt;

&lt;p&gt;The reviewer liked the polish, ease of navigation, screenshots, videos, and support.&lt;/p&gt;

&lt;p&gt;But they also complained that dashboard metrics couldn't be drilled into sufficiently, AI insights weren't linked cleanly to the underlying test run or code, and PR information wasn't well connected to test runs.&lt;/p&gt;

&lt;p&gt;To TestDino's credit, its current documentation describes much better PR-to-run navigation, timelines, code diffs, failure clusters, logs, screenshots, and traces.&lt;/p&gt;

&lt;p&gt;So some of those problems appear to have been addressed since that review.&lt;/p&gt;

&lt;p&gt;That's good.&lt;/p&gt;

&lt;p&gt;It also tells you the product is still evolving quickly.&lt;/p&gt;

&lt;p&gt;Whether you interpret that as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Great, they're shipping fast.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;or:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I'm paying to participate in the maturation process.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;is up to you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then I read the Terms of Service
&lt;/h2&gt;

&lt;p&gt;And this may be my favorite part.&lt;/p&gt;

&lt;p&gt;TestDino's current Terms say you may not use the platform to compile a:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;comparison
evaluation
benchmark
competitive assessment
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;without TestDino's prior written consent.&lt;/p&gt;

&lt;p&gt;They say consent will not be unreasonably withheld for a good-faith factual comparison if the party identifies itself before requesting access.&lt;/p&gt;

&lt;p&gt;Read that again.&lt;/p&gt;

&lt;p&gt;A testing tool.&lt;/p&gt;

&lt;p&gt;A product literally designed around measuring whether software behaves correctly.&lt;/p&gt;

&lt;p&gt;Doesn't want you evaluating the product through platform access without asking permission first.&lt;/p&gt;

&lt;p&gt;That's certainly one approach.&lt;/p&gt;

&lt;p&gt;The terms also define competing products and people connected to them as "Restricted Parties" and say they aren't eligible to access the platform without a written agreement.&lt;/p&gt;

&lt;p&gt;I'm not a lawyer, so I'm not going to speculate about how broadly any of that would ultimately apply.&lt;/p&gt;

&lt;p&gt;But as a buyer?&lt;/p&gt;

&lt;p&gt;I don't love it.&lt;/p&gt;

&lt;p&gt;If I'm selecting engineering infrastructure, independent benchmarking is exactly what I want people doing.&lt;/p&gt;

&lt;p&gt;I want:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;test it
break it
compare it
measure it
publish the results
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a vendor wants people to request permission before using the product for an evaluation or benchmark, that's a yellow flag for me all by itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ironically, this makes an "honest hands-on review" harder
&lt;/h2&gt;

&lt;p&gt;This is also why I wouldn't pretend I spent a week hammering the private product and publish invented observations.&lt;/p&gt;

&lt;p&gt;TestDino's publicly available showcase gives a detailed look at streaming results, run summaries, PR coverage, flaky detection, error grouping, test management, test explorer, tags, and imports/exports.&lt;/p&gt;

&lt;p&gt;Its documentation is extensive.&lt;/p&gt;

&lt;p&gt;There are third-party tutorials and reviews.&lt;/p&gt;

&lt;p&gt;But there's a difference between:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I inspected the product's public surfaces, documentation, pricing, demos, independent reviews, and technical architecture.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;and:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I personally ran 10,000 tests through it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I'm not going to pretend those are the same thing.&lt;/p&gt;

&lt;p&gt;Any review that does should immediately lose credibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  So who actually needs TestDino?
&lt;/h2&gt;

&lt;p&gt;I can think of one very specific team.&lt;/p&gt;

&lt;p&gt;You have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;a large Playwright-only test suite

+

lots of CI runs

+

multiple engineers investigating failures

+

persistent flaky-test problems

+

poor historical reporting

+

a desire to expose test history to AI agents
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then yes.&lt;/p&gt;

&lt;p&gt;I can understand TestDino.&lt;/p&gt;

&lt;p&gt;You're buying organization.&lt;/p&gt;

&lt;p&gt;You're buying history.&lt;/p&gt;

&lt;p&gt;You're buying triage.&lt;/p&gt;

&lt;p&gt;You're buying convenience.&lt;/p&gt;

&lt;p&gt;Fine.&lt;/p&gt;

&lt;p&gt;But if you're a small or medium Playwright team already keeping useful CI artifacts?&lt;/p&gt;

&lt;p&gt;I'd have a hard time recommending it.&lt;/p&gt;

&lt;p&gt;You already have a very capable testing framework.&lt;/p&gt;

&lt;p&gt;You already have traces.&lt;/p&gt;

&lt;p&gt;You already have screenshots.&lt;/p&gt;

&lt;p&gt;You already have videos.&lt;/p&gt;

&lt;p&gt;You already have CI history.&lt;/p&gt;

&lt;p&gt;Now you're adding:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;another account
another reporter
another dashboard
another API key
another SaaS bill
another usage limit
another retention policy
another copy of your test data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to make those things look nicer.&lt;/p&gt;

&lt;h2&gt;
  
  
  My biggest problem with TestDino
&lt;/h2&gt;

&lt;p&gt;It's not that TestDino appears completely useless.&lt;/p&gt;

&lt;p&gt;That would actually make the review easier.&lt;/p&gt;

&lt;p&gt;There are useful ideas here.&lt;/p&gt;

&lt;p&gt;The MCP integration is interesting.&lt;/p&gt;

&lt;p&gt;Cross-run flaky-test analytics are valuable.&lt;/p&gt;

&lt;p&gt;Centralized evidence can save time.&lt;/p&gt;

&lt;p&gt;The product looks polished.&lt;/p&gt;

&lt;p&gt;The problem is that the value proposition feels incredibly thin.&lt;/p&gt;

&lt;p&gt;It feels like someone looked at Playwright's reporting output and said:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What if this was a startup?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And now we have pricing tiers, AI classification, usage quotas, retention windows, team-member limits, enterprise redaction, and a Terms of Service section about benchmarking.&lt;/p&gt;

&lt;p&gt;I wanted to find the killer feature.&lt;/p&gt;

&lt;p&gt;The thing where I went:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Oh. That's why I'd install this.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I didn't find it.&lt;/p&gt;

&lt;h2&gt;
  
  
  My verdict
&lt;/h2&gt;

&lt;p&gt;For a large Playwright-only organization drowning in CI results:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Maybe.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For everyone else:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I'd skip it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;My rough score:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UI and polish:        8/10
Setup:                8/10
Reporting:            7/10
AI differentiation:   4/10
Framework coverage:   2/10
Pricing model:        5/10
Artifact retention:   4/10
Need to exist:        3/10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The harshest thing I can say about TestDino isn't that it looks badly built.&lt;/p&gt;

&lt;p&gt;It doesn't.&lt;/p&gt;

&lt;p&gt;It's that after going through everything it offers, I kept coming back to the same question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why isn't this just part of my existing Playwright and CI workflow?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And when a developer tool makes me ask that question this many times, adding another SaaS subscription is probably not the answer.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>playwright</category>
      <category>qa</category>
      <category>automation</category>
    </item>
    <item>
      <title>The Real Test of Test Automation Is What Happens After Something Changes</title>
      <dc:creator>Simon Gerber</dc:creator>
      <pubDate>Wed, 19 Aug 2026 22:03:29 +0000</pubDate>
      <link>https://dev.to/orbitpickle307/the-real-test-of-test-automation-is-what-happens-after-something-changes-2kh9</link>
      <guid>https://dev.to/orbitpickle307/the-real-test-of-test-automation-is-what-happens-after-something-changes-2kh9</guid>
      <description>&lt;p&gt;Test automation demos tend to begin at the nicest possible moment.&lt;/p&gt;

&lt;p&gt;The application is stable.&lt;/p&gt;

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

&lt;p&gt;The test data is ready.&lt;/p&gt;

&lt;p&gt;Someone creates a test, clicks Run, and everything turns green.&lt;/p&gt;

&lt;p&gt;Useful? Sure.&lt;/p&gt;

&lt;p&gt;But that isn't the difficult part.&lt;/p&gt;

&lt;p&gt;The difficult part starts after the system changes.&lt;/p&gt;

&lt;p&gt;A browser gets updated.&lt;/p&gt;

&lt;p&gt;A prompt is modified.&lt;/p&gt;

&lt;p&gt;An AI agent gets access to a new tool.&lt;/p&gt;

&lt;p&gt;A developer changes the upload flow.&lt;/p&gt;

&lt;p&gt;A selector disappears.&lt;/p&gt;

&lt;p&gt;A test gets "healed."&lt;/p&gt;

&lt;p&gt;Now the real questions begin:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which tests should run?&lt;/li&gt;
&lt;li&gt;Who should review what changed?&lt;/li&gt;
&lt;li&gt;Is the failure caused by the product or the environment?&lt;/li&gt;
&lt;li&gt;Did the AI fix the test correctly, or merely make it pass?&lt;/li&gt;
&lt;li&gt;Can somebody reproduce what happened tomorrow?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's where test automation either becomes infrastructure people trust or another dashboard people learn to ignore.&lt;/p&gt;

&lt;h2&gt;
  
  
  Changes should trigger evidence, not hope
&lt;/h2&gt;

&lt;p&gt;This is especially important for AI-powered products.&lt;/p&gt;

&lt;p&gt;Traditional software releases usually have a visible code change.&lt;/p&gt;

&lt;p&gt;AI systems can change behavior because of code, but also because someone changed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a prompt,&lt;/li&gt;
&lt;li&gt;a model,&lt;/li&gt;
&lt;li&gt;a system instruction,&lt;/li&gt;
&lt;li&gt;a retrieval source,&lt;/li&gt;
&lt;li&gt;a tool definition,&lt;/li&gt;
&lt;li&gt;a tool policy,&lt;/li&gt;
&lt;li&gt;an orchestration rule.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Any of those can alter what users experience.&lt;/p&gt;

&lt;p&gt;Yet many teams still treat those changes as if they were configuration tweaks rather than releases.&lt;/p&gt;

&lt;p&gt;That's risky.&lt;/p&gt;

&lt;p&gt;If changing a prompt can alter a customer-facing workflow, then a prompt change should be capable of triggering the same kind of regression checks as a code change.&lt;/p&gt;

&lt;p&gt;One practical example is &lt;a href="https://ai-test-agents.com/how-to-trigger-endtest-runs-from-an-ai-release-pipeline-after-prompt-model-or-tool-policy-changes/" rel="noopener noreferrer"&gt;triggering Endtest runs from an AI release pipeline after prompt, model, or tool-policy changes&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The broader idea matters more than the specific tool:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Attach regression testing to the thing that can change behavior.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If a model switch can change checkout assistance, run checkout tests.&lt;/p&gt;

&lt;p&gt;If a prompt change can alter how an agent uses a CRM, run those workflows.&lt;/p&gt;

&lt;p&gt;If a tool-policy update can change which actions are allowed, verify the important permission boundaries.&lt;/p&gt;

&lt;p&gt;Otherwise you're deploying behavior changes and hoping your existing CI pipeline notices.&lt;/p&gt;

&lt;p&gt;It probably won't.&lt;/p&gt;

&lt;h2&gt;
  
  
  The best automation removes fake complexity
&lt;/h2&gt;

&lt;p&gt;A lot of flaky automation comes from testing the wrong layer.&lt;/p&gt;

&lt;p&gt;File uploads are a good example.&lt;/p&gt;

&lt;p&gt;A human clicks an upload control and sees an operating-system file picker.&lt;/p&gt;

&lt;p&gt;So the first instinct is often to automate the picker.&lt;/p&gt;

&lt;p&gt;But your application doesn't own that dialog.&lt;/p&gt;

&lt;p&gt;The browser doesn't fully own it either.&lt;/p&gt;

&lt;p&gt;The operating system does.&lt;/p&gt;

&lt;p&gt;Trying to automate it as if it were part of your web application introduces a dependency that usually adds no meaningful product coverage.&lt;/p&gt;

&lt;p&gt;In Playwright, a better pattern is to work with the browser's upload primitives directly. This guide on &lt;a href="https://thesdet.com/how-to-test-browser-file-uploads-in-playwright-without-depending-on-native-dialog-flakiness/" rel="noopener noreferrer"&gt;testing browser file uploads in Playwright without native dialog flakiness&lt;/a&gt; shows the basic principle.&lt;/p&gt;

&lt;p&gt;The interesting lesson is broader than Playwright:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't automate an implementation detail simply because a user can see it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Test the behavior your product is responsible for.&lt;/p&gt;

&lt;p&gt;Did the file get selected?&lt;/p&gt;

&lt;p&gt;Did the application validate it?&lt;/p&gt;

&lt;p&gt;Was it uploaded?&lt;/p&gt;

&lt;p&gt;Did the server accept it?&lt;/p&gt;

&lt;p&gt;Did the UI show the correct result?&lt;/p&gt;

&lt;p&gt;Those are product behaviors.&lt;/p&gt;

&lt;p&gt;Whether macOS happened to render a particular file dialog is usually not.&lt;/p&gt;

&lt;p&gt;This sounds obvious when written down.&lt;/p&gt;

&lt;p&gt;It is surprisingly easy to forget when building a test suite.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI-generated tests still need a human operating model
&lt;/h2&gt;

&lt;p&gt;AI can now generate tests, update selectors, suggest fixes, and sometimes recover from failures.&lt;/p&gt;

&lt;p&gt;That can remove a lot of repetitive work.&lt;/p&gt;

&lt;p&gt;But it also creates a new question:&lt;/p&gt;

&lt;p&gt;Who owns the change?&lt;/p&gt;

&lt;p&gt;Imagine an AI system modifies twelve tests after a frontend refactor.&lt;/p&gt;

&lt;p&gt;Maybe all twelve changes are correct.&lt;/p&gt;

&lt;p&gt;Maybe eleven are correct and one quietly changes the intent of an assertion.&lt;/p&gt;

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

&lt;p&gt;The more autonomy a testing platform has, the more important review and approval become.&lt;/p&gt;

&lt;p&gt;A useful evaluation framework is to look at &lt;a href="https://ai-testing-tools.com/what-to-look-for-in-an-ai-testing-platform-for-test-authoring-review-approval-handoffs-and-ongoing-maintenance/" rel="noopener noreferrer"&gt;AI testing platforms through test authoring review, approval handoffs, and ongoing maintenance&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I would ask questions such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can I see exactly what the AI changed?&lt;/li&gt;
&lt;li&gt;Can I approve or reject those changes?&lt;/li&gt;
&lt;li&gt;Is there an audit trail?&lt;/li&gt;
&lt;li&gt;Are generated steps editable?&lt;/li&gt;
&lt;li&gt;Can different team members review tests?&lt;/li&gt;
&lt;li&gt;Can we distinguish an application change from a test change?&lt;/li&gt;
&lt;li&gt;What happens when the AI is uncertain?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is not bureaucracy for its own sake.&lt;/p&gt;

&lt;p&gt;It's the same reason we review code.&lt;/p&gt;

&lt;p&gt;Automation becomes more valuable as more people trust it.&lt;/p&gt;

&lt;p&gt;And trust usually requires visibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sometimes the test is right and the browser changed
&lt;/h2&gt;

&lt;p&gt;One of the fastest ways to waste an afternoon is to assume every browser test failure is an application regression.&lt;/p&gt;

&lt;p&gt;Browsers are software too.&lt;/p&gt;

&lt;p&gt;They ship changes.&lt;/p&gt;

&lt;p&gt;Rendering changes.&lt;/p&gt;

&lt;p&gt;Security behavior changes.&lt;/p&gt;

&lt;p&gt;Timing changes.&lt;/p&gt;

&lt;p&gt;Driver behavior changes.&lt;/p&gt;

&lt;p&gt;Deprecated features disappear.&lt;/p&gt;

&lt;p&gt;A test can fail on Tuesday even though your application hasn't changed since Friday.&lt;/p&gt;

&lt;p&gt;When that happens, the worst debugging sequence is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Assume the app broke.&lt;/li&gt;
&lt;li&gt;Rewrite the test.&lt;/li&gt;
&lt;li&gt;Add waits.&lt;/li&gt;
&lt;li&gt;Add retries.&lt;/li&gt;
&lt;li&gt;Discover three hours later that Chrome updated.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A more disciplined approach is to first establish what changed in the execution environment.&lt;/p&gt;

&lt;p&gt;This guide to &lt;a href="https://playwright-vs-selenium.com/how-to-debug-browser-test-failures-caused-by-browser-engine-drift-not-app-regressions/" rel="noopener noreferrer"&gt;debugging browser test failures caused by browser engine drift rather than app regressions&lt;/a&gt; covers the kinds of signals worth checking.&lt;/p&gt;

&lt;p&gt;At minimum, capture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;browser name,&lt;/li&gt;
&lt;li&gt;browser version,&lt;/li&gt;
&lt;li&gt;operating system,&lt;/li&gt;
&lt;li&gt;automation library version,&lt;/li&gt;
&lt;li&gt;relevant driver/runtime version,&lt;/li&gt;
&lt;li&gt;screenshots,&lt;/li&gt;
&lt;li&gt;console errors,&lt;/li&gt;
&lt;li&gt;network failures.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then compare the failing run with the last known good run.&lt;/p&gt;

&lt;p&gt;If the application commit is identical but the browser changed, that's a useful clue.&lt;/p&gt;

&lt;p&gt;It doesn't prove the browser is responsible.&lt;/p&gt;

&lt;p&gt;But it changes where you look first.&lt;/p&gt;

&lt;p&gt;That can save a lot of expensive guessing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benchmarks should include failure, not just success
&lt;/h2&gt;

&lt;p&gt;Tool evaluations often suffer from the same problem as demos.&lt;/p&gt;

&lt;p&gt;Everything is tested under ideal conditions.&lt;/p&gt;

&lt;p&gt;A team creates five workflows.&lt;/p&gt;

&lt;p&gt;All five pass.&lt;/p&gt;

&lt;p&gt;The tool gets a high score.&lt;/p&gt;

&lt;p&gt;That tells you surprisingly little.&lt;/p&gt;

&lt;p&gt;If I were comparing AI-native browser testing platforms, I would deliberately introduce failure.&lt;/p&gt;

&lt;p&gt;Change an element label.&lt;/p&gt;

&lt;p&gt;Add latency.&lt;/p&gt;

&lt;p&gt;Move a button.&lt;/p&gt;

&lt;p&gt;Modify test data.&lt;/p&gt;

&lt;p&gt;Break an iframe locator.&lt;/p&gt;

&lt;p&gt;Return an unexpected API response.&lt;/p&gt;

&lt;p&gt;Change the browser version.&lt;/p&gt;

&lt;p&gt;Then measure what happens.&lt;/p&gt;

&lt;p&gt;A reproducible framework like this &lt;a href="https://testingradar.com/benchmark-plan-comparing-ai-native-browser-testing-platforms-on-setup-time-flake-rate-and-evidence-quality/" rel="noopener noreferrer"&gt;benchmark plan for AI-native browser testing platforms&lt;/a&gt; is much closer to how these tools should be compared.&lt;/p&gt;

&lt;p&gt;The useful metrics aren't just "time to create first test."&lt;/p&gt;

&lt;p&gt;I'd care about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;setup time,&lt;/li&gt;
&lt;li&gt;repeatability,&lt;/li&gt;
&lt;li&gt;flake rate,&lt;/li&gt;
&lt;li&gt;recovery behavior,&lt;/li&gt;
&lt;li&gt;evidence quality,&lt;/li&gt;
&lt;li&gt;time to diagnose failure,&lt;/li&gt;
&lt;li&gt;human intervention required,&lt;/li&gt;
&lt;li&gt;maintenance after application changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those numbers tell you what living with the tool might actually feel like.&lt;/p&gt;

&lt;p&gt;And that matters because you're not buying the demo.&lt;/p&gt;

&lt;p&gt;You're buying the next two years.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hidden KPI is time-to-understanding
&lt;/h2&gt;

&lt;p&gt;Most QA dashboards measure execution.&lt;/p&gt;

&lt;p&gt;Number of tests.&lt;/p&gt;

&lt;p&gt;Pass rate.&lt;/p&gt;

&lt;p&gt;Failure rate.&lt;/p&gt;

&lt;p&gt;Duration.&lt;/p&gt;

&lt;p&gt;Coverage.&lt;/p&gt;

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

&lt;p&gt;But I think one of the most valuable metrics is rarely shown:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How long does it take a human to understand a failed test?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Imagine two systems.&lt;/p&gt;

&lt;p&gt;System A has a 98.5% pass rate.&lt;/p&gt;

&lt;p&gt;System B has a 99.3% pass rate.&lt;/p&gt;

&lt;p&gt;At first glance, System B looks better.&lt;/p&gt;

&lt;p&gt;But suppose a failure in System A takes three minutes to diagnose because it includes the screenshot, page state, browser version, logs, and exact test change.&lt;/p&gt;

&lt;p&gt;A failure in System B takes 25 minutes because somebody has to rerun it twice and reconstruct what happened.&lt;/p&gt;

&lt;p&gt;Which one is actually cheaper?&lt;/p&gt;

&lt;p&gt;I'd probably take System A.&lt;/p&gt;

&lt;p&gt;Reliability matters.&lt;/p&gt;

&lt;p&gt;But diagnosability compounds.&lt;/p&gt;

&lt;p&gt;Every unclear failure creates another interruption.&lt;/p&gt;

&lt;p&gt;Every interruption pulls somebody away from product work.&lt;/p&gt;

&lt;p&gt;Over months, those small interruptions become a meaningful engineering cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  Treat testing as part of the change system
&lt;/h2&gt;

&lt;p&gt;The strongest testing setups I've seen tend to share a simple property:&lt;/p&gt;

&lt;p&gt;Tests are connected to change.&lt;/p&gt;

&lt;p&gt;Code changes trigger the relevant tests.&lt;/p&gt;

&lt;p&gt;Browser upgrades trigger compatibility checks.&lt;/p&gt;

&lt;p&gt;AI prompt and model changes trigger workflow regression.&lt;/p&gt;

&lt;p&gt;AI-generated test modifications go through review.&lt;/p&gt;

&lt;p&gt;Failures preserve enough evidence to explain themselves.&lt;/p&gt;

&lt;p&gt;Tool evaluations intentionally introduce breakage rather than only measuring happy paths.&lt;/p&gt;

&lt;p&gt;None of this is particularly glamorous.&lt;/p&gt;

&lt;p&gt;It doesn't make for a 30-second product demo.&lt;/p&gt;

&lt;p&gt;But it's the difference between having automated tests and having an automated quality system.&lt;/p&gt;

&lt;p&gt;Creating the test is step one.&lt;/p&gt;

&lt;p&gt;What happens after something changes is the part that determines whether the test is still useful six months later.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>qa</category>
      <category>automation</category>
      <category>ai</category>
    </item>
    <item>
      <title>The Hardest E2E Tests Live at the Boundaries</title>
      <dc:creator>Simon Gerber</dc:creator>
      <pubDate>Sat, 08 Aug 2026 21:28:36 +0000</pubDate>
      <link>https://dev.to/orbitpickle307/the-hardest-e2e-tests-live-at-the-boundaries-2ghc</link>
      <guid>https://dev.to/orbitpickle307/the-hardest-e2e-tests-live-at-the-boundaries-2ghc</guid>
      <description>&lt;p&gt;The easiest automated test in the world looks something like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open page.&lt;/li&gt;
&lt;li&gt;Click button.&lt;/li&gt;
&lt;li&gt;Assert text.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Unfortunately, that’s not where expensive bugs usually live.&lt;/p&gt;

&lt;p&gt;They live at boundaries.&lt;/p&gt;

&lt;p&gt;The moment your application has to interact with another system, another browser context, another identity provider, another clock, or another human team, automation gets much more interesting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Authentication Is Basically a Collection of Edge Cases
&lt;/h2&gt;

&lt;p&gt;“Test login” sounds simple.&lt;/p&gt;

&lt;p&gt;Then you ship a real enterprise application.&lt;/p&gt;

&lt;p&gt;Now you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SSO&lt;/li&gt;
&lt;li&gt;MFA&lt;/li&gt;
&lt;li&gt;OTP codes&lt;/li&gt;
&lt;li&gt;expiring sessions&lt;/li&gt;
&lt;li&gt;idle logout&lt;/li&gt;
&lt;li&gt;refresh tokens&lt;/li&gt;
&lt;li&gt;re-authentication&lt;/li&gt;
&lt;li&gt;multiple tabs&lt;/li&gt;
&lt;li&gt;redirects to identity providers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your clean little login test becomes a distributed systems test wearing a browser costume.&lt;/p&gt;

&lt;p&gt;Session timeout is a perfect example.&lt;/p&gt;

&lt;p&gt;You could literally wait 30 minutes during every test run.&lt;/p&gt;

&lt;p&gt;Please don’t.&lt;/p&gt;

&lt;p&gt;The better approaches in &lt;a href="https://thesdet.com/how-to-test-session-timeout-idle-logout-and-re-authentication-flows-without-breaking-e2e-stability/" rel="noopener noreferrer"&gt;testing session timeout, idle logout, and re-authentication&lt;/a&gt; are about controlling the state instead of making CI experience time at human speed.&lt;/p&gt;

&lt;p&gt;The same goes for evaluating a platform that needs to handle &lt;a href="https://qatoolguide.com/what-to-look-for-in-a-browser-testing-platform-for-mfa-sso-otp-and-expiring-sessions/" rel="noopener noreferrer"&gt;MFA, SSO, OTP, and expiring sessions&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;These aren’t exotic requirements anymore.&lt;/p&gt;

&lt;p&gt;They’re normal SaaS requirements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Payments Are Even Worse
&lt;/h2&gt;

&lt;p&gt;Checkout combines nearly every annoying browser automation problem.&lt;/p&gt;

&lt;p&gt;You have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;payment iframes&lt;/li&gt;
&lt;li&gt;cross-origin content&lt;/li&gt;
&lt;li&gt;redirects&lt;/li&gt;
&lt;li&gt;3DS authentication&lt;/li&gt;
&lt;li&gt;external payment providers&lt;/li&gt;
&lt;li&gt;popups&lt;/li&gt;
&lt;li&gt;asynchronous callbacks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And sometimes the browser leaves your domain entirely.&lt;/p&gt;

&lt;p&gt;That makes comparisons like &lt;a href="https://testautomationreviews.com/endtest-vs-playwright-for-testing-payment-iframes-3ds-flows-and-redirect-based-checkout/" rel="noopener noreferrer"&gt;Endtest vs Playwright for payment iframes, 3DS, and redirect checkout&lt;/a&gt; more meaningful than comparing how each tool handles a simple button click.&lt;/p&gt;

&lt;p&gt;The difficult part of automation is rarely syntax.&lt;/p&gt;

&lt;p&gt;The difficult part is surviving reality.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI-Driven Interfaces Add Another Boundary
&lt;/h2&gt;

&lt;p&gt;Now add AI.&lt;/p&gt;

&lt;p&gt;Traditional applications are approximately deterministic.&lt;/p&gt;

&lt;p&gt;Input A usually produces Output B.&lt;/p&gt;

&lt;p&gt;AI applications introduce probability.&lt;/p&gt;

&lt;p&gt;The interface may be deterministic while the output isn’t.&lt;/p&gt;

&lt;p&gt;So your test strategy has to change.&lt;/p&gt;

&lt;p&gt;An assertion like:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;response === expected_response&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;may become almost useless.&lt;/p&gt;

&lt;p&gt;Instead you care about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;intent&lt;/li&gt;
&lt;li&gt;required facts&lt;/li&gt;
&lt;li&gt;prohibited output&lt;/li&gt;
&lt;li&gt;schema&lt;/li&gt;
&lt;li&gt;confidence&lt;/li&gt;
&lt;li&gt;behavioral drift&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s why &lt;a href="https://aitestingreport.com/how-to-evaluate-ai-output-drift-monitoring-for-production-test-gates/" rel="noopener noreferrer"&gt;monitoring AI output drift in production test gates&lt;/a&gt; is becoming an interesting problem.&lt;/p&gt;

&lt;p&gt;The product might not “break” in the traditional sense.&lt;/p&gt;

&lt;p&gt;It may slowly become worse.&lt;/p&gt;

&lt;p&gt;That’s harder to catch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tools Have Different Costs at These Boundaries
&lt;/h2&gt;

&lt;p&gt;This is also where automation tool comparisons get more nuanced.&lt;/p&gt;

&lt;p&gt;If your application changes aggressively and AI is involved in the UI itself, the question isn’t:&lt;/p&gt;

&lt;p&gt;“Which testing tool has more features?”&lt;/p&gt;

&lt;p&gt;It’s:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which tool creates less operational overhead for the way my team actually ships software?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That’s the interesting part of comparisons such as &lt;a href="https://aitestingcompare.com/endtest-vs-functionize-for-teams-testing-ai-driven-ui-changes-in-fast-moving-web-apps/" rel="noopener noreferrer"&gt;Endtest vs Functionize for AI-driven UI changes&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Two products can theoretically automate the same workflow while creating very different amounts of maintenance.&lt;/p&gt;

&lt;p&gt;And maintenance compounds.&lt;/p&gt;

&lt;p&gt;Saving ten minutes once is irrelevant.&lt;/p&gt;

&lt;p&gt;Saving ten minutes on something your team does 500 times is a product feature.&lt;/p&gt;

&lt;h2&gt;
  
  
  People Are Part of the System Too
&lt;/h2&gt;

&lt;p&gt;There’s one boundary engineers sometimes forget:&lt;/p&gt;

&lt;p&gt;Humans.&lt;/p&gt;

&lt;p&gt;You can have an excellent test suite and still ship slowly because nobody knows who investigates failures.&lt;/p&gt;

&lt;p&gt;Or the offshore QA team discovers problems eight hours after engineering finishes for the day.&lt;/p&gt;

&lt;p&gt;Or the test team can execute a release but doesn’t understand what changed.&lt;/p&gt;

&lt;p&gt;That’s why evaluating &lt;a href="https://automated-testing-services.com/how-to-evaluate-an-offshore-qa-partner-for-time-zone-coverage-overlap-hours-and-release-handoffs/" rel="noopener noreferrer"&gt;offshore QA partners based on overlap hours and release handoffs&lt;/a&gt; isn’t simply procurement trivia.&lt;/p&gt;

&lt;p&gt;Coordination latency is part of your QA system.&lt;/p&gt;

&lt;p&gt;A bug found at 9:05 AM by someone sitting beside the developer is very different from the same bug reported at 1:00 AM through a spreadsheet.&lt;/p&gt;

&lt;h2&gt;
  
  
  This Is Where Automation ROI Is Won
&lt;/h2&gt;

&lt;p&gt;Basic tests are important.&lt;/p&gt;

&lt;p&gt;You should absolutely test that users can log in and buttons work.&lt;/p&gt;

&lt;p&gt;But once those tests exist, I’d spend disproportionate effort on the boundaries:&lt;/p&gt;

&lt;p&gt;Authentication.&lt;/p&gt;

&lt;p&gt;Payments.&lt;/p&gt;

&lt;p&gt;External services.&lt;/p&gt;

&lt;p&gt;Session state.&lt;/p&gt;

&lt;p&gt;AI behavior.&lt;/p&gt;

&lt;p&gt;Redirects.&lt;/p&gt;

&lt;p&gt;Asynchronous state.&lt;/p&gt;

&lt;p&gt;Human handoffs.&lt;/p&gt;

&lt;p&gt;Those are the places where assumptions collide.&lt;/p&gt;

&lt;p&gt;And they’re the places where a good automated test can save you from the kind of bug that doesn’t produce a cute little red unit test.&lt;/p&gt;

&lt;p&gt;It produces a Slack message from a customer.&lt;/p&gt;

&lt;p&gt;Those are considerably more expensive.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>qa</category>
      <category>automation</category>
      <category>saas</category>
    </item>
    <item>
      <title>AI Features Need a Different Kind of Test Suite</title>
      <dc:creator>Simon Gerber</dc:creator>
      <pubDate>Fri, 07 Aug 2026 22:09:02 +0000</pubDate>
      <link>https://dev.to/orbitpickle307/ai-features-need-a-different-kind-of-test-suite-55ho</link>
      <guid>https://dev.to/orbitpickle307/ai-features-need-a-different-kind-of-test-suite-55ho</guid>
      <description>&lt;p&gt;A lot of teams are adding AI to existing products the way SaaS companies added chat widgets ten years ago.&lt;/p&gt;

&lt;p&gt;There’s the normal application.&lt;/p&gt;

&lt;p&gt;Then there’s the AI box.&lt;/p&gt;

&lt;p&gt;You type something into it.&lt;/p&gt;

&lt;p&gt;Magic happens.&lt;/p&gt;

&lt;p&gt;Ship it.&lt;/p&gt;

&lt;p&gt;The problem is that the AI box rarely stays isolated.&lt;/p&gt;

&lt;p&gt;Soon it can fill forms.&lt;/p&gt;

&lt;p&gt;Suggest values.&lt;/p&gt;

&lt;p&gt;Change application state.&lt;/p&gt;

&lt;p&gt;Create records.&lt;/p&gt;

&lt;p&gt;Recommend actions.&lt;/p&gt;

&lt;p&gt;Trigger workflows.&lt;/p&gt;

&lt;p&gt;Hand something off to a human.&lt;/p&gt;

&lt;p&gt;Now you don’t merely have an LLM feature.&lt;/p&gt;

&lt;p&gt;You have an unpredictable participant inside your application.&lt;/p&gt;

&lt;p&gt;And that changes what “testing the UI” means.&lt;/p&gt;

&lt;h2&gt;
  
  
  The dangerous failure is often a believable UI
&lt;/h2&gt;

&lt;p&gt;Traditional UI testing is good at binary failures.&lt;/p&gt;

&lt;p&gt;The button is missing.&lt;/p&gt;

&lt;p&gt;The API returned 500.&lt;/p&gt;

&lt;p&gt;The page didn’t load.&lt;/p&gt;

&lt;p&gt;AI introduces something nastier:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;the interface works perfectly while showing the wrong thing.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Imagine an AI assistant generating an insurance quote.&lt;/p&gt;

&lt;p&gt;The page renders.&lt;/p&gt;

&lt;p&gt;Every element is present.&lt;/p&gt;

&lt;p&gt;The interaction succeeds.&lt;/p&gt;

&lt;p&gt;But the model inferred the wrong coverage level and the frontend confidently displays it.&lt;/p&gt;

&lt;p&gt;From the perspective of a classic browser test, everything worked.&lt;/p&gt;

&lt;p&gt;From the perspective of the business, you may have a serious problem.&lt;/p&gt;

&lt;p&gt;This is why I find the emerging category of tools for &lt;a href="https://ai-testing-tools.com/best-ai-testing-tools-for-detecting-hallucinated-ui-states-in-llm-powered-web-apps/" rel="noopener noreferrer"&gt;detecting hallucinated UI states in LLM-powered applications&lt;/a&gt; interesting.&lt;/p&gt;

&lt;p&gt;AI testing has to care about the semantic relationship between what the model produced and what the application ultimately did.&lt;/p&gt;

&lt;h2&gt;
  
  
  Form assistants are a good example
&lt;/h2&gt;

&lt;p&gt;AI-powered forms look deceptively easy to test.&lt;/p&gt;

&lt;p&gt;Enter a prompt.&lt;/p&gt;

&lt;p&gt;Watch fields populate.&lt;/p&gt;

&lt;p&gt;Assert that the fields contain values.&lt;/p&gt;

&lt;p&gt;But what happens if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the model fills a disabled field?&lt;/li&gt;
&lt;li&gt;it overwrites data the user manually entered?&lt;/li&gt;
&lt;li&gt;server-side validation rejects its suggestion?&lt;/li&gt;
&lt;li&gt;the AI changes a dependent dropdown but not the value below it?&lt;/li&gt;
&lt;li&gt;the user corrects one field and the assistant “helpfully” changes it back?&lt;/li&gt;
&lt;li&gt;the submission fails and the AI retries incorrectly?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now you have three sources of truth:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;the user,&lt;/li&gt;
&lt;li&gt;the model,&lt;/li&gt;
&lt;li&gt;the application.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That’s why testing form assistants requires considerably more than testing whether generated text appeared.&lt;/p&gt;

&lt;p&gt;There’s a practical breakdown in &lt;a href="https://vibiumlabs.com/how-to-test-ai-form-assistants-before-they-corrupt-validation-autofill-or-submission-state/" rel="noopener noreferrer"&gt;how to test AI form assistants before they corrupt validation, autofill, or submission state&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And if you’re evaluating an automation platform specifically for these workflows, this &lt;a href="https://ai-test-agents.com/endtest-reader-guide-for-testing-ai-generated-form-assistants-dynamic-validation-and-recovery-paths/" rel="noopener noreferrer"&gt;guide to testing AI-generated form assistants and recovery paths with Endtest&lt;/a&gt; looks at the tooling side.&lt;/p&gt;

&lt;p&gt;The interesting test cases live at the boundary between AI output and deterministic application rules.&lt;/p&gt;

&lt;p&gt;That boundary deserves disproportionate attention.&lt;/p&gt;

&lt;h2&gt;
  
  
  Streaming responses break traditional waits
&lt;/h2&gt;

&lt;p&gt;Chat interfaces introduce another common failure mode.&lt;/p&gt;

&lt;p&gt;A normal browser test might wait for:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;That works beautifully until responses stream token by token.&lt;/p&gt;

&lt;p&gt;The element exists almost immediately.&lt;/p&gt;

&lt;p&gt;The answer does not.&lt;/p&gt;

&lt;p&gt;So the test reads half a sentence and proceeds.&lt;/p&gt;

&lt;p&gt;You can increase the timeout, but now a fast response waits unnecessarily.&lt;/p&gt;

&lt;p&gt;You can wait for network silence, except modern apps often have analytics, telemetry, or background requests that mean “network silence” never really happens.&lt;/p&gt;

&lt;p&gt;You need an application-level definition of completion.&lt;/p&gt;

&lt;p&gt;Maybe the Stop button disappears.&lt;/p&gt;

&lt;p&gt;Maybe a status changes.&lt;/p&gt;

&lt;p&gt;Maybe a streaming flag becomes false.&lt;/p&gt;

&lt;p&gt;Whatever it is, it should correspond to something the user would recognize as “the answer is finished.”&lt;/p&gt;

&lt;p&gt;There’s a good treatment of this problem in &lt;a href="https://aitestingcompare.com/how-to-test-streaming-ai-responses-in-the-browser-without-flaky-timing-assertions/" rel="noopener noreferrer"&gt;testing streaming AI responses without flaky timing assertions&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Again, the theme is the same:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;waiting longer is not the same as waiting intelligently.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Handoffs are where AI becomes a workflow
&lt;/h2&gt;

&lt;p&gt;The most interesting AI applications aren’t fully autonomous.&lt;/p&gt;

&lt;p&gt;They hand control back and forth between humans and software.&lt;/p&gt;

&lt;p&gt;AI prepares something.&lt;/p&gt;

&lt;p&gt;A human approves it.&lt;/p&gt;

&lt;p&gt;AI continues.&lt;/p&gt;

&lt;p&gt;Someone escalates.&lt;/p&gt;

&lt;p&gt;The workflow gets reassigned.&lt;/p&gt;

&lt;p&gt;An audit event is recorded.&lt;/p&gt;

&lt;p&gt;That creates a much richer test surface than a chat response.&lt;/p&gt;

&lt;p&gt;You have to validate both the model behavior and the surrounding product workflow.&lt;/p&gt;

&lt;p&gt;This review of &lt;a href="https://aitestingtoolreviews.com/endtest-review-for-testing-ai-agent-handoff-flows-approval-steps-and-escalation-ui/" rel="noopener noreferrer"&gt;testing AI agent handoff flows, approvals, and escalation UI with Endtest&lt;/a&gt; is a useful example of the kinds of scenarios worth exercising.&lt;/p&gt;

&lt;p&gt;The key word there is &lt;strong&gt;workflow&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;AI products still need boring deterministic software around the AI.&lt;/p&gt;

&lt;p&gt;Permissions need to work.&lt;/p&gt;

&lt;p&gt;Buttons need to work.&lt;/p&gt;

&lt;p&gt;Approvals need to work.&lt;/p&gt;

&lt;p&gt;Audit logs need to work.&lt;/p&gt;

&lt;p&gt;The model doesn't get to excuse those failures.&lt;/p&gt;

&lt;h2&gt;
  
  
  “97% accurate” tells you almost nothing
&lt;/h2&gt;

&lt;p&gt;AI vendors love accuracy numbers.&lt;/p&gt;

&lt;p&gt;97.2%.&lt;/p&gt;

&lt;p&gt;98.7%.&lt;/p&gt;

&lt;p&gt;99%.&lt;/p&gt;

&lt;p&gt;The problem is that a single percentage can hide almost everything that matters.&lt;/p&gt;

&lt;p&gt;Accurate at what?&lt;/p&gt;

&lt;p&gt;On whose dataset?&lt;/p&gt;

&lt;p&gt;Under which prompt?&lt;/p&gt;

&lt;p&gt;Which languages?&lt;/p&gt;

&lt;p&gt;What happens on ambiguous inputs?&lt;/p&gt;

&lt;p&gt;Was the evaluation performed once six months ago?&lt;/p&gt;

&lt;p&gt;Are failures clustered around one particularly important use case?&lt;/p&gt;

&lt;p&gt;Before treating an AI testing tool's accuracy score as a meaningful comparison, read something like &lt;a href="https://aitestingreviews.com/what-to-check-in-an-ai-testing-tool-before-you-trust-its-reported-accuracy-scores/" rel="noopener noreferrer"&gt;what to check before trusting reported AI testing accuracy scores&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Averages are especially dangerous when the cost of individual errors is asymmetric.&lt;/p&gt;

&lt;p&gt;Being wrong about the color of a generated icon is not equivalent to being wrong about whether a financial transaction succeeded.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI testing needs observability
&lt;/h2&gt;

&lt;p&gt;When a deterministic test fails, you usually ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What happened?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With AI systems, you also need:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why did the system reach that result?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That may require:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;prompt versions,&lt;/li&gt;
&lt;li&gt;model versions,&lt;/li&gt;
&lt;li&gt;retrieved context,&lt;/li&gt;
&lt;li&gt;tool calls,&lt;/li&gt;
&lt;li&gt;token streams,&lt;/li&gt;
&lt;li&gt;traces,&lt;/li&gt;
&lt;li&gt;screenshots,&lt;/li&gt;
&lt;li&gt;browser state,&lt;/li&gt;
&lt;li&gt;retries.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s why AI test observability is becoming its own category.&lt;/p&gt;

&lt;p&gt;There’s a useful overview of &lt;a href="https://aitestingreport.com/how-to-evaluate-ai-test-observability-platforms-for-prompt-replays-traces-and-failure-triage-in-llm-apps/" rel="noopener noreferrer"&gt;evaluating AI test observability platforms for prompt replays, traces, and failure triage&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Without this context, reproducing an AI failure becomes forensic archaeology.&lt;/p&gt;

&lt;h2&gt;
  
  
  And eventually somebody asks about the data
&lt;/h2&gt;

&lt;p&gt;Once you're saving prompts and replaying conversations, another question arrives:&lt;/p&gt;

&lt;p&gt;What exactly are we storing?&lt;/p&gt;

&lt;p&gt;AI test data can contain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;customer information,&lt;/li&gt;
&lt;li&gt;internal documents,&lt;/li&gt;
&lt;li&gt;production prompts,&lt;/li&gt;
&lt;li&gt;retrieved content,&lt;/li&gt;
&lt;li&gt;personal data,&lt;/li&gt;
&lt;li&gt;secrets inadvertently entered by users.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At that point, synthetic data and masking stop being enterprise checkboxes and start becoming part of your testing architecture.&lt;/p&gt;

&lt;p&gt;This &lt;a href="https://testingradar.com/a-market-map-of-ai-test-data-governance-tools-for-teams-evaluating-masking-synthetic-data-and-prompt-safety/" rel="noopener noreferrer"&gt;market map of AI test data governance tools&lt;/a&gt; provides a good overview of that side of the ecosystem.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI doesn’t eliminate deterministic testing
&lt;/h2&gt;

&lt;p&gt;There’s a weird idea floating around that AI will eventually make normal test automation obsolete.&lt;/p&gt;

&lt;p&gt;I suspect the opposite happens.&lt;/p&gt;

&lt;p&gt;As applications become less deterministic, teams will want &lt;strong&gt;more deterministic evidence&lt;/strong&gt; around them.&lt;/p&gt;

&lt;p&gt;Did the correct field change?&lt;/p&gt;

&lt;p&gt;Was the approval actually persisted?&lt;/p&gt;

&lt;p&gt;Was the right user notified?&lt;/p&gt;

&lt;p&gt;Did the application prevent an invalid submission?&lt;/p&gt;

&lt;p&gt;Can we reproduce what happened?&lt;/p&gt;

&lt;p&gt;AI can generate wildly different language while the product still has very specific rules.&lt;/p&gt;

&lt;p&gt;Those rules are where your test suite earns its keep.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>testing</category>
      <category>qa</category>
      <category>automation</category>
    </item>
    <item>
      <title>AI Regression Testing Needs Guardrails</title>
      <dc:creator>Simon Gerber</dc:creator>
      <pubDate>Wed, 05 Aug 2026 19:10:35 +0000</pubDate>
      <link>https://dev.to/orbitpickle307/ai-regression-testing-needs-guardrails-28i0</link>
      <guid>https://dev.to/orbitpickle307/ai-regression-testing-needs-guardrails-28i0</guid>
      <description>&lt;p&gt;Traditional browser testing assumes the product is mostly deterministic.&lt;/p&gt;

&lt;p&gt;Click this button. See that page. Submit this form. Confirm the success message.&lt;/p&gt;

&lt;p&gt;AI products break that comfortable model.&lt;/p&gt;

&lt;p&gt;The wording may change while the result remains correct. A prompt update can improve one workflow and quietly damage another. Retrieval, caching, and tool use can introduce failure paths that are invisible in a simple UI assertion.&lt;/p&gt;

&lt;p&gt;The answer is not to abandon regression testing.&lt;/p&gt;

&lt;p&gt;It is to test the system at several layers and preserve enough evidence to understand why behavior changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rollbacks are a feature, not an emergency procedure
&lt;/h2&gt;

&lt;p&gt;Teams often test the new AI behavior and forget to test the rollback.&lt;/p&gt;

&lt;p&gt;But AI features usually depend on more than one deployable component:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prompt versions&lt;/li&gt;
&lt;li&gt;Model configuration&lt;/li&gt;
&lt;li&gt;Retrieval indexes&lt;/li&gt;
&lt;li&gt;Embedding models&lt;/li&gt;
&lt;li&gt;Caches&lt;/li&gt;
&lt;li&gt;Tool definitions&lt;/li&gt;
&lt;li&gt;Feature flags&lt;/li&gt;
&lt;li&gt;UI behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Rolling back only the prompt may leave the rest of the system in a newer state.&lt;/p&gt;

&lt;p&gt;This guide on &lt;a href="https://ai-test-agents.com/how-to-test-llm-feature-rollbacks-without-breaking-prompt-cache-or-retrieval-paths/" rel="noopener noreferrer"&gt;testing LLM feature rollbacks across prompts, caches, and retrieval paths&lt;/a&gt; explains why rollback testing should be treated as a complete user flow.&lt;/p&gt;

&lt;p&gt;A useful rollback test should confirm that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The intended version is active&lt;/li&gt;
&lt;li&gt;Cached responses do not preserve the broken behavior&lt;/li&gt;
&lt;li&gt;Retrieval uses compatible data&lt;/li&gt;
&lt;li&gt;Tool calls still match the older contract&lt;/li&gt;
&lt;li&gt;The frontend presents the correct controls&lt;/li&gt;
&lt;li&gt;Audit logs identify the active configuration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The worst time to discover a rollback is incomplete is during an incident.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prompt changes are product changes
&lt;/h2&gt;

&lt;p&gt;A one-line prompt adjustment can alter tone, structure, tool selection, refusal behavior, and the data included in a response.&lt;/p&gt;

&lt;p&gt;That makes prompt changes deceptively large.&lt;/p&gt;

&lt;p&gt;This article on &lt;a href="https://thesdet.com/how-to-test-ai-powered-ui-changes-without-turning-every-prompt-tweak-into-a-regression-fire-drill/" rel="noopener noreferrer"&gt;testing AI-powered UI changes without turning every prompt tweak into a regression fire drill&lt;/a&gt; suggests separating what must remain exact from what can vary.&lt;/p&gt;

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

&lt;p&gt;Exact requirements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The correct tool is called&lt;/li&gt;
&lt;li&gt;Restricted data is not shown&lt;/li&gt;
&lt;li&gt;A confirmation step appears before a destructive action&lt;/li&gt;
&lt;li&gt;The user reaches the expected workflow outcome&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Flexible requirements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sentence wording&lt;/li&gt;
&lt;li&gt;Response length within a reasonable range&lt;/li&gt;
&lt;li&gt;Ordering of non-critical explanations&lt;/li&gt;
&lt;li&gt;Stylistic differences&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If everything is asserted exactly, the tests become noisy.&lt;/p&gt;

&lt;p&gt;If nothing is asserted exactly, the tests become meaningless.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multistep agents need traceability
&lt;/h2&gt;

&lt;p&gt;A multistep browser agent may interpret a request, inspect the page, select an element, fill a form, call a tool, and verify the result.&lt;/p&gt;

&lt;p&gt;A final “passed” status hides all of those decisions.&lt;/p&gt;

&lt;p&gt;This guide on &lt;a href="https://ai-testing-tools.com/how-to-evaluate-ai-test-agents-for-multistep-browser-flows-without-sacrificing-debuggability/" rel="noopener noreferrer"&gt;evaluating AI test agents for multistep browser flows without sacrificing debuggability&lt;/a&gt; makes a crucial point: every important decision should leave evidence.&lt;/p&gt;

&lt;p&gt;At minimum, I would want to see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The user instruction&lt;/li&gt;
&lt;li&gt;The interpreted goal&lt;/li&gt;
&lt;li&gt;Each selected action&lt;/li&gt;
&lt;li&gt;The locator or element evidence&lt;/li&gt;
&lt;li&gt;Tool inputs and outputs&lt;/li&gt;
&lt;li&gt;Retries&lt;/li&gt;
&lt;li&gt;Confidence or ambiguity&lt;/li&gt;
&lt;li&gt;The final assertion&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without that trace, a successful run may still be untrustworthy.&lt;/p&gt;

&lt;p&gt;The agent could have completed the wrong workflow correctly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The market is splitting into categories
&lt;/h2&gt;

&lt;p&gt;“AI testing platform” is becoming too broad to be useful.&lt;/p&gt;

&lt;p&gt;Some products focus on generating tests. Others focus on evaluating model output, monitoring production behavior, testing security, or maintaining browser automation.&lt;/p&gt;

&lt;p&gt;This &lt;a href="https://testingradar.com/market-map-of-ai-regression-testing-platforms-for-teams-shipping-frequent-prompt-and-ui-changes/" rel="noopener noreferrer"&gt;market map of AI regression-testing platforms&lt;/a&gt; is helpful because it separates categories by the problem being solved.&lt;/p&gt;

&lt;p&gt;Before comparing vendors, define the job:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do you need browser workflow coverage?&lt;/li&gt;
&lt;li&gt;Prompt regression evaluation?&lt;/li&gt;
&lt;li&gt;Tool-use validation?&lt;/li&gt;
&lt;li&gt;Production monitoring?&lt;/li&gt;
&lt;li&gt;Security testing?&lt;/li&gt;
&lt;li&gt;Human review and governance?&lt;/li&gt;
&lt;li&gt;Cross-browser execution?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A product can be excellent in one category and still be the wrong choice for another.&lt;/p&gt;

&lt;h2&gt;
  
  
  Admin consoles deserve first-class tests
&lt;/h2&gt;

&lt;p&gt;AI products increasingly include internal consoles for traces, overrides, approvals, and audit history.&lt;/p&gt;

&lt;p&gt;These interfaces are not secondary.&lt;/p&gt;

&lt;p&gt;They are how operators understand and control the system when automation behaves badly.&lt;/p&gt;

&lt;p&gt;This practical review of &lt;a href="https://aitestingtoolreviews.com/endtest-review-for-teams-testing-ai-agent-admin-consoles-trace-views-and-human-override-panels/" rel="noopener noreferrer"&gt;using Endtest to validate AI-agent admin consoles, trace views, and override panels&lt;/a&gt; highlights workflows that are easy to overlook:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Filtering traces&lt;/li&gt;
&lt;li&gt;Inspecting tool calls&lt;/li&gt;
&lt;li&gt;Applying human overrides&lt;/li&gt;
&lt;li&gt;Replaying failed runs&lt;/li&gt;
&lt;li&gt;Confirming audit entries&lt;/li&gt;
&lt;li&gt;Restricting actions by role&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A product that can perform powerful autonomous actions needs equally reliable controls.&lt;/p&gt;

&lt;h2&gt;
  
  
  Safety tests must include the browser flow
&lt;/h2&gt;

&lt;p&gt;Prompt injection and data leakage are often treated as model-evaluation problems.&lt;/p&gt;

&lt;p&gt;But the final risk appears in the product workflow.&lt;/p&gt;

&lt;p&gt;A copilot may receive malicious content from a document, call an internal tool, and display restricted information in the browser.&lt;/p&gt;

&lt;p&gt;This guide on &lt;a href="https://aitestingreviews.com/how-to-test-ai-copilots-for-data-leakage-prompt-injection-and-unsafe-tool-use/" rel="noopener noreferrer"&gt;testing AI copilots for data leakage, prompt injection, and unsafe tool use&lt;/a&gt; describes why the test must cover both model behavior and application controls.&lt;/p&gt;

&lt;p&gt;Useful scenarios include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A document instructs the agent to ignore policy&lt;/li&gt;
&lt;li&gt;A user asks for data outside their permissions&lt;/li&gt;
&lt;li&gt;The model attempts a destructive tool call&lt;/li&gt;
&lt;li&gt;Sensitive values appear in logs&lt;/li&gt;
&lt;li&gt;A confirmation is bypassed&lt;/li&gt;
&lt;li&gt;The UI exposes hidden system instructions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The browser is where permissions, confirmations, and evidence meet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the result editable and reviewable
&lt;/h2&gt;

&lt;p&gt;At &lt;a href="https://endtest.io/" rel="noopener noreferrer"&gt;Endtest&lt;/a&gt;, AI Assertions can evaluate outcomes that are difficult to express as exact strings, while the AI Test Creation Agent can accelerate the creation of browser workflows. But the resulting tests still need to be understandable and editable by people.&lt;/p&gt;

&lt;p&gt;That is the guardrail I keep coming back to.&lt;/p&gt;

&lt;p&gt;AI can help create tests.&lt;/p&gt;

&lt;p&gt;AI can help analyze failures.&lt;/p&gt;

&lt;p&gt;AI can even suggest repairs.&lt;/p&gt;

&lt;p&gt;But a human team should still be able to see what changed, why it changed, and how to reverse it.&lt;/p&gt;

&lt;p&gt;The goal is not autonomous testing at any cost.&lt;/p&gt;

&lt;p&gt;The goal is faster confidence without losing control.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>testing</category>
      <category>security</category>
      <category>qa</category>
    </item>
    <item>
      <title>Do Not Hire an AI Test Agent From Its Demo</title>
      <dc:creator>Simon Gerber</dc:creator>
      <pubDate>Tue, 04 Aug 2026 22:22:08 +0000</pubDate>
      <link>https://dev.to/orbitpickle307/do-not-hire-an-ai-test-agent-from-its-demo-28fk</link>
      <guid>https://dev.to/orbitpickle307/do-not-hire-an-ai-test-agent-from-its-demo-28fk</guid>
      <description>&lt;p&gt;AI testing demos are almost unfairly persuasive.&lt;/p&gt;

&lt;p&gt;You describe a workflow in plain English. The agent opens a browser, finds the correct elements, completes the flow, and produces a passing test.&lt;/p&gt;

&lt;p&gt;Five minutes later, it feels as if test maintenance has been solved.&lt;/p&gt;

&lt;p&gt;The problem is that a demo shows the happy path for the agent, not the operating model for your team.&lt;/p&gt;

&lt;p&gt;The real evaluation begins when the application changes, the locator is ambiguous, the environment is partially broken, or the agent makes the wrong decision with high confidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Evaluate the agent like a junior operator
&lt;/h2&gt;

&lt;p&gt;I would not give a new employee broad production access simply because they completed one task successfully in an interview.&lt;/p&gt;

&lt;p&gt;I would first want to know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How they explain their decisions&lt;/li&gt;
&lt;li&gt;What they do when information is missing&lt;/li&gt;
&lt;li&gt;Whether they escalate uncertainty&lt;/li&gt;
&lt;li&gt;How easily their work can be reviewed&lt;/li&gt;
&lt;li&gt;Whether mistakes can be reversed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An AI agent deserves the same scrutiny.&lt;/p&gt;

&lt;p&gt;This guide on &lt;a href="https://ai-testing-tools.com/how-to-evaluate-ai-test-agents-for-self-healing-browser-coverage-without-losing-debuggability/" rel="noopener noreferrer"&gt;evaluating AI test agents without losing browser-test debug visibility&lt;/a&gt; focuses on an essential requirement: automation should become easier to maintain, not harder to inspect.&lt;/p&gt;

&lt;p&gt;An agent that produces a passing result without showing how it arrived there creates a trust problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do not test it only on your cleanest workflow
&lt;/h2&gt;

&lt;p&gt;Vendor demos usually use predictable applications:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stable IDs&lt;/li&gt;
&lt;li&gt;Simple forms&lt;/li&gt;
&lt;li&gt;Obvious button labels&lt;/li&gt;
&lt;li&gt;No iframes&lt;/li&gt;
&lt;li&gt;No duplicate elements&lt;/li&gt;
&lt;li&gt;No delayed events&lt;/li&gt;
&lt;li&gt;No permission prompts&lt;/li&gt;
&lt;li&gt;No uncertain outcomes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your evaluation should do the opposite.&lt;/p&gt;

&lt;p&gt;Give the agent a workflow where two elements have similar labels. Change a piece of copy after the test has been generated. Remove an element. Slow down an API response. Cause the browser to crash. Return an unexpected validation message.&lt;/p&gt;

&lt;p&gt;The article on &lt;a href="https://aitestingreviews.com/how-to-evaluate-ai-test-agents-for-browser-flows-without-trusting-the-demo/" rel="noopener noreferrer"&gt;evaluating browser-flow agents without trusting the demo&lt;/a&gt; offers a useful principle: measure recovery behavior, not just task completion.&lt;/p&gt;

&lt;p&gt;When the agent cannot confidently determine the right action, it should say so.&lt;/p&gt;

&lt;p&gt;A clarifying question is better than an impressive-looking mistake.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measure more than pass rate
&lt;/h2&gt;

&lt;p&gt;Pass rate is a poor standalone metric for AI-generated or AI-maintained tests.&lt;/p&gt;

&lt;p&gt;A test can pass because the product works. It can also pass because the agent selected the wrong element, skipped a failed assertion, modified the workflow, or healed the test into a different scenario.&lt;/p&gt;

&lt;p&gt;This guide to &lt;a href="https://bugbench.com/what-to-measure-in-ai-test-runs-before-you-trust-the-pass-rate/" rel="noopener noreferrer"&gt;measuring AI test runs before trusting their pass rate&lt;/a&gt; suggests looking at the decisions behind the result.&lt;/p&gt;

&lt;p&gt;Useful metrics include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Percentage of runs requiring agent intervention&lt;/li&gt;
&lt;li&gt;Number of locators changed automatically&lt;/li&gt;
&lt;li&gt;Frequency of ambiguous element matches&lt;/li&gt;
&lt;li&gt;Human acceptance rate for suggested changes&lt;/li&gt;
&lt;li&gt;False repair rate&lt;/li&gt;
&lt;li&gt;Time saved during investigation&lt;/li&gt;
&lt;li&gt;Number of silently changed test steps&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I would also measure how frequently the agent requests clarification. A low number is not necessarily good. It may mean the system is guessing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Procurement questions are product questions
&lt;/h2&gt;

&lt;p&gt;AI testing purchases often begin as feature comparisons and end as security reviews.&lt;/p&gt;

&lt;p&gt;That transition should happen earlier.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://aitestingreport.com/ai-testing-procurement-scorecard-how-to-compare-security-governance-and-product-fit-before-buying/" rel="noopener noreferrer"&gt;AI testing procurement scorecard&lt;/a&gt; covers questions that become important once the proof of concept is over:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What application data is sent to the model?&lt;/li&gt;
&lt;li&gt;Are screenshots or page sources retained?&lt;/li&gt;
&lt;li&gt;Can sensitive values be masked?&lt;/li&gt;
&lt;li&gt;Which employees can enable AI features?&lt;/li&gt;
&lt;li&gt;Are model actions logged?&lt;/li&gt;
&lt;li&gt;Can generated changes require approval?&lt;/li&gt;
&lt;li&gt;Where is the data processed?&lt;/li&gt;
&lt;li&gt;What happens when an AI feature is disabled?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An AI feature is not isolated from the rest of the product. Its governance model becomes part of your testing infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test the product on genuinely uncertain UI
&lt;/h2&gt;

&lt;p&gt;AI-powered form assistants and guided checkout flows are good evaluation targets because they combine normal browser interaction with probabilistic behavior.&lt;/p&gt;

&lt;p&gt;The assistant may change its wording while still giving the correct answer. It may suggest different products based on small changes in input. A rigid text assertion can fail even when the experience is acceptable.&lt;/p&gt;

&lt;p&gt;This review of &lt;a href="https://aitestingtoolreviews.com/endtest-review-for-teams-validating-ai-powered-form-assistants-and-guided-checkout-flows/" rel="noopener noreferrer"&gt;Endtest for AI-powered form assistants and guided checkout flows&lt;/a&gt; illustrates where AI Assertions can be more useful than exact string comparisons.&lt;/p&gt;

&lt;p&gt;For example, instead of asserting that the assistant displays one precise sentence, you may want to validate that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The response answers the customer’s question.&lt;/li&gt;
&lt;li&gt;The recommendation respects the selected constraints.&lt;/li&gt;
&lt;li&gt;The assistant does not claim the payment succeeded before confirmation.&lt;/li&gt;
&lt;li&gt;The checkout flow still reaches the expected outcome.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The assertion needs flexibility, but the workflow still needs objective boundaries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Account for the people who will own the tests
&lt;/h2&gt;

&lt;p&gt;A technically impressive agent can still be the wrong product for a team.&lt;/p&gt;

&lt;p&gt;This comparison of &lt;a href="https://testingtoolguide.com/endtest-vs-playwright-for-qa-teams-without-a-dedicated-sdet-setup-ownership-and-maintenance-tradeoffs/" rel="noopener noreferrer"&gt;Endtest and Playwright for QA teams without a dedicated SDET&lt;/a&gt; highlights a factor that feature matrices often ignore: who will operate the system after implementation?&lt;/p&gt;

&lt;p&gt;A developer-led team may prefer code, custom fixtures, and complete framework control.&lt;/p&gt;

&lt;p&gt;A QA-led team may benefit more from readable steps, managed execution, built-in reporting, and an interface where tests can be updated without editing a framework.&lt;/p&gt;

&lt;p&gt;Neither approach is universally correct.&lt;/p&gt;

&lt;p&gt;The mistake is choosing an architecture for the team you hope to hire rather than the team you currently have.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI should compress work, not hide it
&lt;/h2&gt;

&lt;p&gt;At &lt;a href="https://endtest.io/" rel="noopener noreferrer"&gt;Endtest&lt;/a&gt;, features such as the AI Test Creation Agent are intended to accelerate test creation while keeping the resulting test understandable and editable.&lt;/p&gt;

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

&lt;p&gt;The most useful AI systems do not replace the test with a mysterious generated artifact. They compress repetitive work while preserving review, ownership, and control.&lt;/p&gt;

&lt;p&gt;Before adopting an AI test agent, ask one final question:&lt;/p&gt;

&lt;p&gt;When it makes a mistake—and eventually it will—how quickly will my team understand what happened?&lt;/p&gt;

&lt;p&gt;That answer matters far more than the demo.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>testing</category>
      <category>qa</category>
      <category>devops</category>
    </item>
    <item>
      <title>Many “Flaky Tests” Are Really Frontend Architecture Problems</title>
      <dc:creator>Simon Gerber</dc:creator>
      <pubDate>Mon, 03 Aug 2026 21:26:36 +0000</pubDate>
      <link>https://dev.to/orbitpickle307/many-flaky-tests-are-really-frontend-architecture-problems-435j</link>
      <guid>https://dev.to/orbitpickle307/many-flaky-tests-are-really-frontend-architecture-problems-435j</guid>
      <description>&lt;p&gt;Browser tests are often blamed for exposing instability they did not create.&lt;/p&gt;

&lt;p&gt;A test fails after a page loads. Someone adds a wait.&lt;/p&gt;

&lt;p&gt;It fails again. Someone changes the locator.&lt;/p&gt;

&lt;p&gt;It passes locally but fails in CI. Someone adds another retry.&lt;/p&gt;

&lt;p&gt;Six months later, the test contains four waits, three fallback selectors, and a comment warning future developers not to touch anything.&lt;/p&gt;

&lt;p&gt;At that point, the team says the test is flaky.&lt;/p&gt;

&lt;p&gt;Sometimes it is.&lt;/p&gt;

&lt;p&gt;But sometimes the test is accurately revealing that the frontend has become difficult to observe, predict, and operate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hydration mismatches are not harmless console noise
&lt;/h2&gt;

&lt;p&gt;Server-rendered applications can display HTML before the client-side framework takes control.&lt;/p&gt;

&lt;p&gt;That improves perceived performance, but it creates a contract: the client must hydrate a structure compatible with what the server rendered.&lt;/p&gt;

&lt;p&gt;When the structures differ, frameworks may warn, discard parts of the DOM, recreate components, or attach events in unexpected ways.&lt;/p&gt;

&lt;p&gt;A human user may not notice. The page still appears.&lt;/p&gt;

&lt;p&gt;A browser test often notices immediately.&lt;/p&gt;

&lt;p&gt;An element exists, disappears, and is recreated. A click lands during the transition. Text changes after the assertion begins. A locator points to a node that is no longer connected to the document.&lt;/p&gt;

&lt;p&gt;The result looks like test instability, but the root cause is a rendering inconsistency.&lt;/p&gt;

&lt;p&gt;The guide on &lt;a href="https://frontendtester.com/how-to-debug-hydration-mismatches-before-they-break-your-browser-tests/" rel="noopener noreferrer"&gt;debugging hydration mismatches before they break browser tests&lt;/a&gt; explains the common causes and the signals worth capturing.&lt;/p&gt;

&lt;p&gt;A second useful perspective is this breakdown of &lt;a href="https://bughuntersclub.com/how-to-debug-hydration-mismatch-bugs-before-they-turn-into-false-frontend-failures/" rel="noopener noreferrer"&gt;hydration mismatch bugs that turn into false frontend failures&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The lesson is not to make tests tolerate every hydration problem.&lt;/p&gt;

&lt;p&gt;It is to use the test failure as evidence that the interface has unstable intermediate states.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dynamic interfaces need stable contracts
&lt;/h2&gt;

&lt;p&gt;Modern frontends change frequently.&lt;/p&gt;

&lt;p&gt;Design systems evolve. Components are restructured. Text is personalized. Forms add conditional fields. Lists reorder themselves based on live data.&lt;/p&gt;

&lt;p&gt;A test that depends on incidental HTML structure will suffer.&lt;/p&gt;

&lt;p&gt;For example, this locator may work today:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;div:nth-child(3) &amp;gt; div:nth-child(2) &amp;gt; button
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But it does not describe the user’s intent. It describes the current DOM arrangement.&lt;/p&gt;

&lt;p&gt;A stronger contract might be a stable identifier, accessible name, field label, or explicit test attribute.&lt;/p&gt;

&lt;p&gt;The best browser-testing tools cannot completely compensate for a frontend that offers no stable way to identify important elements.&lt;/p&gt;

&lt;p&gt;Still, tools differ significantly in how they handle dynamic states, reusable components, debugging, waiting behavior, and test maintenance.&lt;/p&gt;

&lt;p&gt;This comparison of &lt;a href="https://testautomationreviews.com/best-browser-testing-tools-for-design-systems-and-dynamic-frontends/" rel="noopener noreferrer"&gt;browser testing tools for design systems and dynamic frontends&lt;/a&gt; outlines the capabilities that matter.&lt;/p&gt;

&lt;p&gt;The correct choice is rarely the tool with the longest feature page.&lt;/p&gt;

&lt;p&gt;It is the one whose assumptions match the way your interface actually changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Weekly UI changes alter the tool-selection equation
&lt;/h2&gt;

&lt;p&gt;Many testing evaluations are based on a short proof of concept.&lt;/p&gt;

&lt;p&gt;A team automates five stable flows. Everything passes. The tool appears successful.&lt;/p&gt;

&lt;p&gt;But the real question is what happens after twelve weeks of product development.&lt;/p&gt;

&lt;p&gt;How many tests break when components are refactored?&lt;/p&gt;

&lt;p&gt;How quickly can someone understand and repair them?&lt;/p&gt;

&lt;p&gt;Does changing a shared flow require editing one reusable component or twenty individual scripts?&lt;/p&gt;

&lt;p&gt;Can product managers and QA engineers understand what the test does without reading framework code?&lt;/p&gt;

&lt;p&gt;Teams shipping frequent UI changes should evaluate tools under change, not just under execution.&lt;/p&gt;

&lt;p&gt;This guide to &lt;a href="https://testingradar.com/a-teams-guide-to-ai-testing-platforms-for-teams-shipping-frequent-ui-ui-changes/" rel="noopener noreferrer"&gt;choosing an AI testing platform when the UI changes every week&lt;/a&gt; focuses on that difference.&lt;/p&gt;

&lt;p&gt;A demo proves that a test can be created.&lt;/p&gt;

&lt;p&gt;A change-heavy trial proves whether the testing approach can survive your product roadmap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dynamic forms are a useful stress test
&lt;/h2&gt;

&lt;p&gt;Forms expose many of the weaknesses in browser automation.&lt;/p&gt;

&lt;p&gt;They can contain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Conditional sections&lt;/li&gt;
&lt;li&gt;Client-side validation&lt;/li&gt;
&lt;li&gt;Server-side validation&lt;/li&gt;
&lt;li&gt;Asynchronous suggestions&lt;/li&gt;
&lt;li&gt;Repeating fields&lt;/li&gt;
&lt;li&gt;Custom dropdowns&lt;/li&gt;
&lt;li&gt;Date pickers&lt;/li&gt;
&lt;li&gt;File uploads&lt;/li&gt;
&lt;li&gt;Auto-save behavior&lt;/li&gt;
&lt;li&gt;Values populated from previous answers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A test may need to interact with elements that are visible only after a specific sequence of inputs. A small UI change can alter timing, structure, or labels.&lt;/p&gt;

&lt;p&gt;This makes dynamic forms a useful comparison scenario for different automation approaches.&lt;/p&gt;

&lt;p&gt;The analysis of &lt;a href="https://bugbench.com/endtest-vs-playwright-for-dynamic-form-testing-which-one-breaks-less-when-the-ui-changes/" rel="noopener noreferrer"&gt;Endtest versus Playwright for dynamic-form testing&lt;/a&gt; highlights the underlying tradeoff.&lt;/p&gt;

&lt;p&gt;A code-first framework may offer deep flexibility and direct control. A higher-level platform may reduce the amount of framework code the team must create and maintain.&lt;/p&gt;

&lt;p&gt;Neither advantage is imaginary.&lt;/p&gt;

&lt;p&gt;The correct choice depends on who will own the tests, how customized the application is, how often flows change, and whether the team wants to maintain an automation framework as an internal software project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do not solve every frontend problem in the test layer
&lt;/h2&gt;

&lt;p&gt;There is a temptation to make the test absorb all application uncertainty.&lt;/p&gt;

&lt;p&gt;If the component loads unpredictably, add a longer wait.&lt;/p&gt;

&lt;p&gt;If the DOM changes during hydration, retry the click.&lt;/p&gt;

&lt;p&gt;If the list has no stable identifiers, find the third matching item.&lt;/p&gt;

&lt;p&gt;These changes may make the test pass, but they can hide product weaknesses.&lt;/p&gt;

&lt;p&gt;A better debugging sequence is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Determine whether the application has reached a stable user-visible state.&lt;/li&gt;
&lt;li&gt;Check for hydration, rendering, and console errors.&lt;/li&gt;
&lt;li&gt;Confirm that interactive elements expose stable identities.&lt;/li&gt;
&lt;li&gt;Inspect whether network and state transitions are observable.&lt;/li&gt;
&lt;li&gt;Only then decide whether the test needs a different wait or locator.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Tests should handle legitimate asynchronous behavior.&lt;/p&gt;

&lt;p&gt;They should not be forced to guess what the application is doing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The test is part of the feedback loop
&lt;/h2&gt;

&lt;p&gt;Browser automation is often discussed as a layer placed on top of the product.&lt;/p&gt;

&lt;p&gt;In practice, it also provides feedback about the product’s architecture.&lt;/p&gt;

&lt;p&gt;A UI that is easy to test usually has clearer states, better accessibility, more stable contracts, and more predictable transitions.&lt;/p&gt;

&lt;p&gt;A UI that is extremely difficult to test may be difficult for users and developers too.&lt;/p&gt;

&lt;p&gt;So the next time someone calls a browser test flaky, inspect the test.&lt;/p&gt;

&lt;p&gt;But also inspect the frontend.&lt;/p&gt;

&lt;p&gt;The test may be telling you something important.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>qa</category>
      <category>playwright</category>
      <category>frontend</category>
    </item>
    <item>
      <title>The Hardest Browser Tests Are Systems Tests in Disguise</title>
      <dc:creator>Simon Gerber</dc:creator>
      <pubDate>Wed, 29 Jul 2026 09:15:24 +0000</pubDate>
      <link>https://dev.to/orbitpickle307/the-hardest-browser-tests-are-systems-tests-in-disguise-3fgn</link>
      <guid>https://dev.to/orbitpickle307/the-hardest-browser-tests-are-systems-tests-in-disguise-3fgn</guid>
      <description>&lt;p&gt;A browser test looks simple from the outside.&lt;/p&gt;

&lt;p&gt;Open the page. Enter some data. Click a button. Confirm the result.&lt;/p&gt;

&lt;p&gt;But many important user journeys extend far beyond the browser.&lt;/p&gt;

&lt;p&gt;An SMS must arrive. An email provider must accept a message. A webhook must be processed. A file must be scanned. A background job must finish. An identity provider must redirect the user. A session must survive several transitions.&lt;/p&gt;

&lt;p&gt;At that point, the browser is only one participant in a distributed system.&lt;/p&gt;

&lt;p&gt;Treating the workflow as a sequence of clicks usually creates slow, mysterious tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  OTP tests fail in the spaces between systems
&lt;/h2&gt;

&lt;p&gt;Consider a one-time-password flow.&lt;/p&gt;

&lt;p&gt;The browser requests a code. The application creates the code. A messaging provider accepts it. A webhook reports delivery. The message reaches the device or inbox. The user retrieves the code and enters it before it expires.&lt;/p&gt;

&lt;p&gt;Any of those steps can fail independently.&lt;/p&gt;

&lt;p&gt;A generic error such as “OTP field not found” tells you almost nothing.&lt;/p&gt;

&lt;p&gt;This guide to &lt;a href="https://test-automation-tools.com/what-to-check-in-a-browser-testing-tool-for-webhook-driven-sms-verification-twilio-retries-and-time-sensitive-otp-flows/" rel="noopener noreferrer"&gt;browser-testing tools for webhook-driven SMS verification and time-sensitive OTP flows&lt;/a&gt; explains the capabilities needed to test the complete process.&lt;/p&gt;

&lt;p&gt;The test should preserve timestamps and identifiers across the entire chain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When the code was requested&lt;/li&gt;
&lt;li&gt;The request or transaction ID&lt;/li&gt;
&lt;li&gt;When the provider accepted the message&lt;/li&gt;
&lt;li&gt;Whether a retry occurred&lt;/li&gt;
&lt;li&gt;When the message became retrievable&lt;/li&gt;
&lt;li&gt;Which code was entered&lt;/li&gt;
&lt;li&gt;When the code expired&lt;/li&gt;
&lt;li&gt;What the application returned&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without that information, every timeout looks the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build the harness before expanding the test count
&lt;/h2&gt;

&lt;p&gt;Teams sometimes create one successful email-verification test and immediately duplicate it across dozens of scenarios.&lt;/p&gt;

&lt;p&gt;Then the inbox becomes unreliable, messages are retrieved out of order, test accounts collide, and the whole suite begins to fail intermittently.&lt;/p&gt;

&lt;p&gt;A reliable harness needs isolation.&lt;/p&gt;

&lt;p&gt;This guide to &lt;a href="https://softwaretestingreviews.com/how-to-build-a-reliable-sms-and-email-verification-harness-with-twilio-mailgun-and-imap/" rel="noopener noreferrer"&gt;building an SMS and email verification harness with Twilio, Mailgun, and IMAP&lt;/a&gt; describes the plumbing that tends to be overlooked.&lt;/p&gt;

&lt;p&gt;Each execution should use a unique address or phone-routing strategy. The retrieval logic should match the message using a correlation value, not merely “the latest email.” Messages should be cleaned up or expired predictably.&lt;/p&gt;

&lt;p&gt;Polling should also have boundaries.&lt;/p&gt;

&lt;p&gt;Checking every 100 milliseconds for five minutes does not make the system reliable. It creates traffic and hides a delivery problem behind a long timeout.&lt;/p&gt;

&lt;h2&gt;
  
  
  Upload testing begins before the upload
&lt;/h2&gt;

&lt;p&gt;File-upload tests are often reduced to selecting a file and waiting for a success message.&lt;/p&gt;

&lt;p&gt;Real upload flows contain many more states:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Drag enter&lt;/li&gt;
&lt;li&gt;Drag leave&lt;/li&gt;
&lt;li&gt;Drop&lt;/li&gt;
&lt;li&gt;Local preview&lt;/li&gt;
&lt;li&gt;Client-side validation&lt;/li&gt;
&lt;li&gt;Chunking&lt;/li&gt;
&lt;li&gt;Progress&lt;/li&gt;
&lt;li&gt;Cancellation&lt;/li&gt;
&lt;li&gt;Retry&lt;/li&gt;
&lt;li&gt;Server processing&lt;/li&gt;
&lt;li&gt;Final availability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This article on &lt;a href="https://vibiumlabs.com/how-to-test-drag-and-drop-upload-flows-previews-and-retry-states-in-real-browsers/" rel="noopener noreferrer"&gt;testing drag-and-drop uploads, previews, and retry states in real browsers&lt;/a&gt; shows why the interaction deserves more than one happy-path test.&lt;/p&gt;

&lt;p&gt;A useful suite should include invalid files, oversized files, interrupted uploads, duplicate names, and failures after the bytes reach the server.&lt;/p&gt;

&lt;p&gt;The system may display 100% progress while a background service is still scanning or transforming the file.&lt;/p&gt;

&lt;p&gt;That is not necessarily a bug, but the test must understand which completion state matters to the user.&lt;/p&gt;

&lt;h2&gt;
  
  
  Validation and transport are different layers
&lt;/h2&gt;

&lt;p&gt;When an upload fails, it is useful to know where it failed.&lt;/p&gt;

&lt;p&gt;Client-side validation might reject the file before any request is sent. The browser may send the request and receive a server error. The server may accept the file but fail during processing.&lt;/p&gt;

&lt;p&gt;This practical review of &lt;a href="https://testautomationguide.com/endtest-for-testing-file-uploads-drag-and-drop-and-client-side-validation-workflow-tradeoffs-and-fit/" rel="noopener noreferrer"&gt;using Endtest for file uploads, drag-and-drop, and client-side validation&lt;/a&gt; separates those layers.&lt;/p&gt;

&lt;p&gt;That separation makes failures actionable.&lt;/p&gt;

&lt;p&gt;“Upload failed” is not a diagnosis.&lt;/p&gt;

&lt;p&gt;“Client rejected the MIME type before sending a request” is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Downloads need validation after the click
&lt;/h2&gt;

&lt;p&gt;Download tests have a similar blind spot.&lt;/p&gt;

&lt;p&gt;The browser clicks Export, a file appears, and the test passes.&lt;/p&gt;

&lt;p&gt;But the file might be empty, malformed, truncated, encoded incorrectly, or filled with stale data.&lt;/p&gt;

&lt;p&gt;A better workflow confirms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The expected download started&lt;/li&gt;
&lt;li&gt;The correct filename was used&lt;/li&gt;
&lt;li&gt;The file completed&lt;/li&gt;
&lt;li&gt;The file is not empty&lt;/li&gt;
&lt;li&gt;The format can be parsed&lt;/li&gt;
&lt;li&gt;Required columns or values exist&lt;/li&gt;
&lt;li&gt;Sensitive data was not included unexpectedly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This guide to &lt;a href="https://bughuntersclub.com/how-to-build-reliable-browser-tests-for-upload-download-and-file-validation-flows-without-brittle-setup/" rel="noopener noreferrer"&gt;browser tests for upload, download, and file-validation flows&lt;/a&gt; covers the full lifecycle.&lt;/p&gt;

&lt;p&gt;The key is to avoid making the browser test responsible for every byte-level assertion.&lt;/p&gt;

&lt;p&gt;The browser can trigger the export and capture the artifact. A specialized validation step can inspect the resulting CSV, PDF, image, or archive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recovery paths deserve more attention than checkout completion
&lt;/h2&gt;

&lt;p&gt;Most checkout automation focuses on the straightforward purchase:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add an item.&lt;/li&gt;
&lt;li&gt;Enter payment details.&lt;/li&gt;
&lt;li&gt;Submit the order.&lt;/li&gt;
&lt;li&gt;Confirm success.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Real users experience interruptions.&lt;/p&gt;

&lt;p&gt;The session expires. The payment requires additional authentication. Inventory changes. The browser is refreshed. The user navigates backward. A provider returns an ambiguous result.&lt;/p&gt;

&lt;p&gt;This guide to &lt;a href="https://qatoolguide.com/a-practical-look-at-endtest-for-testing-multi-step-checkout-recovery-session-expiry-and-re-authentication-flows/" rel="noopener noreferrer"&gt;testing multi-step checkout recovery, session expiry, and reauthentication&lt;/a&gt; focuses on the paths that often produce the most expensive bugs.&lt;/p&gt;

&lt;p&gt;Recovery tests should verify more than whether the user can continue.&lt;/p&gt;

&lt;p&gt;They should check that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The cart remains accurate&lt;/li&gt;
&lt;li&gt;Discounts are preserved correctly&lt;/li&gt;
&lt;li&gt;Payment is not submitted twice&lt;/li&gt;
&lt;li&gt;Inventory is not reserved indefinitely&lt;/li&gt;
&lt;li&gt;The user receives a clear status&lt;/li&gt;
&lt;li&gt;An uncertain transaction can be reconciled&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A recovery flow that creates duplicate orders is worse than a checkout flow that fails visibly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Authentication recovery is different from first-time login
&lt;/h2&gt;

&lt;p&gt;Login tests are common. Account-recovery tests are not.&lt;/p&gt;

&lt;p&gt;That imbalance is strange because recovery flows often combine email delivery, expiring tokens, password rules, session invalidation, and security controls.&lt;/p&gt;

&lt;p&gt;This practical look at &lt;a href="https://test-automation-experts.com/a-practical-look-at-endtest-for-teams-testing-authentication-recovery-flows-not-just-first-run-logins/" rel="noopener noreferrer"&gt;testing authentication recovery flows rather than only first-run logins&lt;/a&gt; highlights the scenarios teams frequently miss.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Can a reset link be used twice?&lt;/li&gt;
&lt;li&gt;Does requesting a second link invalidate the first?&lt;/li&gt;
&lt;li&gt;Are existing sessions revoked after a password change?&lt;/li&gt;
&lt;li&gt;What happens when the link expires while the page is open?&lt;/li&gt;
&lt;li&gt;Does the user return to the intended destination after authenticating?&lt;/li&gt;
&lt;li&gt;Can the flow leak whether an account exists?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These tests protect both usability and security.&lt;/p&gt;

&lt;h2&gt;
  
  
  Load changes the frontend too
&lt;/h2&gt;

&lt;p&gt;Load testing is often treated as a backend concern.&lt;/p&gt;

&lt;p&gt;The API slows down, latency rises, and errors appear. But frontend behaviour also changes under load.&lt;/p&gt;

&lt;p&gt;Skeleton states remain visible longer. Polling requests overlap. Buttons are clicked twice because the first response appears stalled. Notifications arrive out of order. A route transition begins before the previous request finishes.&lt;/p&gt;

&lt;p&gt;This guide to &lt;a href="https://testproject.to/what-to-check-in-a-load-testing-tool-for-frontend-heavy-apps-with-spiky-traffic-and-shared-reporting/" rel="noopener noreferrer"&gt;load-testing tools for frontend-heavy applications with spiky traffic&lt;/a&gt; makes a good case for connecting load results with browser evidence.&lt;/p&gt;

&lt;p&gt;A useful strategy is to run a small number of browser journeys while a separate load test creates pressure on the system.&lt;/p&gt;

&lt;p&gt;The browser tests then answer a different question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What does degraded performance actually feel like to a user?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;An API returning in four seconds may be technically within a threshold while still creating a broken interface.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build observability into the workflow
&lt;/h2&gt;

&lt;p&gt;Complex browser tests become reliable when they stop depending exclusively on what is visible on the screen.&lt;/p&gt;

&lt;p&gt;The browser remains important, but it should be connected to evidence from the surrounding system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API responses&lt;/li&gt;
&lt;li&gt;Webhooks&lt;/li&gt;
&lt;li&gt;Message-provider events&lt;/li&gt;
&lt;li&gt;Background-job status&lt;/li&gt;
&lt;li&gt;Stored artifacts&lt;/li&gt;
&lt;li&gt;Correlation IDs&lt;/li&gt;
&lt;li&gt;Server timestamps&lt;/li&gt;
&lt;li&gt;Processing states&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This does not mean bypassing the user experience.&lt;/p&gt;

&lt;p&gt;It means giving the test enough context to explain what happened.&lt;/p&gt;

&lt;p&gt;A screenshot of a spinner is weak evidence.&lt;/p&gt;

&lt;p&gt;A screenshot of the spinner combined with a job ID, queue status, provider response, and elapsed time is a diagnosis.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test the boundaries, not just the clicks
&lt;/h2&gt;

&lt;p&gt;The most valuable browser tests often live at system boundaries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Browser to identity provider&lt;/li&gt;
&lt;li&gt;Application to messaging provider&lt;/li&gt;
&lt;li&gt;Upload client to storage service&lt;/li&gt;
&lt;li&gt;Checkout to payment processor&lt;/li&gt;
&lt;li&gt;Export request to background worker&lt;/li&gt;
&lt;li&gt;Recovery link to session manager&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those boundaries are where ownership becomes unclear and failures become difficult to reproduce.&lt;/p&gt;

&lt;p&gt;They are also where automated evidence provides the greatest leverage.&lt;/p&gt;

&lt;p&gt;When a user journey crosses several systems, do not force the entire explanation into a single timeout message.&lt;/p&gt;

&lt;p&gt;Design the test so that every system leaves a trace.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>qa</category>
      <category>selenium</category>
      <category>playwright</category>
    </item>
    <item>
      <title>Test the User-Visible Contract</title>
      <dc:creator>Simon Gerber</dc:creator>
      <pubDate>Mon, 27 Jul 2026 21:35:53 +0000</pubDate>
      <link>https://dev.to/orbitpickle307/test-the-user-visible-contract-46kf</link>
      <guid>https://dev.to/orbitpickle307/test-the-user-visible-contract-46kf</guid>
      <description>&lt;p&gt;Modern frontends have made a simple idea surprisingly difficult:&lt;/p&gt;

&lt;p&gt;When is the page finished?&lt;/p&gt;

&lt;p&gt;With server-rendered HTML, client hydration, React Server Components, Suspense boundaries, streamed responses, background requests, variable fonts, live regions, and data visualizations, a screen may be usable before it is technically settled.&lt;/p&gt;

&lt;p&gt;It may also continue changing after the user has started interacting with it.&lt;/p&gt;

&lt;p&gt;This creates a trap for browser automation.&lt;/p&gt;

&lt;p&gt;The test observes every intermediate state. The user only cares whether the experience eventually becomes correct and remains usable.&lt;/p&gt;

&lt;p&gt;When those two perspectives are confused, teams spend a lot of time fixing tests that are accurately detecting implementation details nobody promised would be stable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The DOM is an implementation detail more often than we admit
&lt;/h2&gt;

&lt;p&gt;Browser automation naturally gravitates toward the DOM because that is where elements can be located and asserted.&lt;/p&gt;

&lt;p&gt;But the DOM is not the product.&lt;/p&gt;

&lt;p&gt;The user-visible contract is the product.&lt;/p&gt;

&lt;p&gt;For a streamed account page, that contract might be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the heading appears;&lt;/li&gt;
&lt;li&gt;the account summary becomes available;&lt;/li&gt;
&lt;li&gt;controls are enabled when the required data arrives;&lt;/li&gt;
&lt;li&gt;loading states do not block unrelated actions;&lt;/li&gt;
&lt;li&gt;focus does not jump unexpectedly;&lt;/li&gt;
&lt;li&gt;errors are announced accessibly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The exact number of renders is usually irrelevant.&lt;/p&gt;

&lt;p&gt;This is the right lens when deciding &lt;a href="https://softwaretestingreviews.com/what-to-look-for-in-a-browser-testing-tool-for-react-server-components-streaming-ui-and-partial-rerenders/" rel="noopener noreferrer"&gt;what to look for in a browser testing tool for React Server Components, streaming UI, and partial re-renders&lt;/a&gt;. A tool needs to handle elements that appear, disappear, or update without forcing the test author to encode every transient state.&lt;/p&gt;

&lt;p&gt;The more your assertions mirror implementation timing, the more expensive ordinary frontend evolution becomes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Assert outcomes, not rendering choreography
&lt;/h2&gt;

&lt;p&gt;Imagine a product page with three regions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;basic product information is rendered immediately;&lt;/li&gt;
&lt;li&gt;availability streams in;&lt;/li&gt;
&lt;li&gt;recommendations load later.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A brittle test waits for all network activity to stop, checks the entire page, and fails because the recommendation service takes an extra second.&lt;/p&gt;

&lt;p&gt;A useful test checks that the product can be understood and purchased once the required state is available. Recommendations can be validated separately because they represent a different user promise.&lt;/p&gt;

&lt;p&gt;This is the practical distinction in &lt;a href="https://testproject.to/testing-react-server-components-and-partial-re-renders-with-endtest-what-to-assert-and-what-to-ignore/" rel="noopener noreferrer"&gt;testing React Server Components and partial re-renders with Endtest&lt;/a&gt;: identify which transitions are meaningful and which are rendering noise.&lt;/p&gt;

&lt;p&gt;The test should not ignore instability.&lt;/p&gt;

&lt;p&gt;It should classify it.&lt;/p&gt;

&lt;p&gt;A price changing after initial display may be a serious defect. A skeleton being replaced by content is expected. A button briefly disabled while its permissions load may be acceptable—or it may be a product problem if the state is confusing.&lt;/p&gt;

&lt;p&gt;Those are product decisions, not framework defaults.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hydration noise is not automatically harmless
&lt;/h2&gt;

&lt;p&gt;There is a temptation to solve flaky tests by waiting longer.&lt;/p&gt;

&lt;p&gt;Sometimes that works.&lt;/p&gt;

&lt;p&gt;It can also hide real defects.&lt;/p&gt;

&lt;p&gt;A hydration mismatch may only flicker for 100 milliseconds, but if it clears an input, moves focus, changes an accessible name, or causes a button to receive the wrong click, the user impact is real.&lt;/p&gt;

&lt;p&gt;The goal is not to suppress every transient observation. The goal is to distinguish harmless implementation transitions from changes that break the user-visible contract.&lt;/p&gt;

&lt;p&gt;A strong guide to &lt;a href="https://testautomationguide.com/how-to-test-react-server-components-streaming-updates-and-partial-re-renders-without-chasing-hydration-noise/" rel="noopener noreferrer"&gt;testing React Server Components and streaming updates without chasing hydration noise&lt;/a&gt; should therefore include explicit invariants:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;user-entered values persist;&lt;/li&gt;
&lt;li&gt;focus remains predictable;&lt;/li&gt;
&lt;li&gt;interactive elements do not move under the pointer;&lt;/li&gt;
&lt;li&gt;accessible labels remain stable;&lt;/li&gt;
&lt;li&gt;committed actions are not duplicated;&lt;/li&gt;
&lt;li&gt;errors are recoverable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These invariants survive framework upgrades better than assertions about component internals.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fonts can change behaviour, not just appearance
&lt;/h2&gt;

&lt;p&gt;Font loading bugs are a perfect example of why visual details and functional behaviour overlap.&lt;/p&gt;

&lt;p&gt;A fallback font can make a label wider. That pushes a button onto another line. The layout shift moves the target after the test has located it. An emoji fallback changes line height. A variable-font axis produces different measurements in CI than on a developer laptop.&lt;/p&gt;

&lt;p&gt;The test appears flaky.&lt;/p&gt;

&lt;p&gt;The underlying issue is a race between layout and interaction.&lt;/p&gt;

&lt;p&gt;The debugging techniques for &lt;a href="https://frontendtester.com/how-to-debug-browser-tests-that-only-fail-after-font-loading-emoji-fallbacks-or-variable-fonts-change-the-layout/" rel="noopener noreferrer"&gt;browser tests that fail after font loading, emoji fallbacks, or variable fonts change the layout&lt;/a&gt; are useful beyond typography. They reinforce a broader principle: a visible element is not necessarily a stable interaction target.&lt;/p&gt;

&lt;p&gt;Tests should wait for the condition that matters.&lt;/p&gt;

&lt;p&gt;That may be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fonts are ready;&lt;/li&gt;
&lt;li&gt;layout has stopped shifting;&lt;/li&gt;
&lt;li&gt;the element is enabled;&lt;/li&gt;
&lt;li&gt;the target remains in the same position long enough to interact;&lt;/li&gt;
&lt;li&gt;the application exposes an explicit ready state.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;“Visible” is often too weak.&lt;/p&gt;

&lt;p&gt;“Network idle” is often too broad.&lt;/p&gt;

&lt;p&gt;The right condition is usually product-specific.&lt;/p&gt;

&lt;h2&gt;
  
  
  Accessibility exposes hidden timing defects
&lt;/h2&gt;

&lt;p&gt;Sighted users can often infer that a changing region is loading or that a modal has appeared.&lt;/p&gt;

&lt;p&gt;Assistive technology depends on the application communicating those transitions correctly.&lt;/p&gt;

&lt;p&gt;ARIA live regions, focus traps, status messages, and dynamic announcements introduce a second user-visible contract: what the accessibility tree communicates over time.&lt;/p&gt;

&lt;p&gt;This is why &lt;a href="https://testingradar.com/what-to-check-in-an-accessibility-testing-platform-for-aria-live-regions-focus-traps-and-dynamic-content-announcements/" rel="noopener noreferrer"&gt;accessibility testing platforms should handle ARIA live regions, focus traps, and dynamic content announcements&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A page can look correct in a screenshot while being unusable with a keyboard or screen reader.&lt;/p&gt;

&lt;p&gt;Useful browser checks include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;focus moves into a modal and remains trapped appropriately;&lt;/li&gt;
&lt;li&gt;closing the modal returns focus to a sensible element;&lt;/li&gt;
&lt;li&gt;validation errors are announced;&lt;/li&gt;
&lt;li&gt;loading completion is communicated;&lt;/li&gt;
&lt;li&gt;repeated partial renders do not generate a flood of duplicate announcements;&lt;/li&gt;
&lt;li&gt;hidden content is not exposed as interactive.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are temporal behaviours. A static accessibility scan will not catch all of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dynamic charts need semantic assertions
&lt;/h2&gt;

&lt;p&gt;Charts and dashboards create another temptation: screenshot everything.&lt;/p&gt;

&lt;p&gt;Visual comparison can be valuable, but dynamic charts contain variability. Timestamps change. Data labels move. Anti-aliasing differs. Tooltips appear based on pointer position. A heatmap may be correct even when individual pixels are not identical.&lt;/p&gt;

&lt;p&gt;A practical evaluation of &lt;a href="https://bugbench.com/a-practical-look-at-endtest-for-visual-checks-on-dynamic-charts-heatmaps-and-data-rich-dashboards/" rel="noopener noreferrer"&gt;Endtest for visual checks on dynamic charts, heatmaps, and data-rich dashboards&lt;/a&gt; highlights the need to combine visual checks with semantic ones.&lt;/p&gt;

&lt;p&gt;Instead of asking only, “Does this image match?”, ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is the chart present?&lt;/li&gt;
&lt;li&gt;Is the expected series available?&lt;/li&gt;
&lt;li&gt;Does the legend contain the correct labels?&lt;/li&gt;
&lt;li&gt;Does the tooltip expose the expected value?&lt;/li&gt;
&lt;li&gt;Is the empty state correct?&lt;/li&gt;
&lt;li&gt;Can a keyboard user reach the data?&lt;/li&gt;
&lt;li&gt;Are obviously invalid values absent?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The screenshot is evidence. It should not always be the sole assertion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate stable contracts from unstable presentation
&lt;/h2&gt;

&lt;p&gt;A useful test design exercise is to divide page behaviour into three layers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stable business contract
&lt;/h3&gt;

&lt;p&gt;These should rarely change without intentional product work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the user can submit an order;&lt;/li&gt;
&lt;li&gt;permissions restrict access;&lt;/li&gt;
&lt;li&gt;totals are correct;&lt;/li&gt;
&lt;li&gt;errors explain recovery;&lt;/li&gt;
&lt;li&gt;saved data persists.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Interaction contract
&lt;/h3&gt;

&lt;p&gt;These can evolve, but users still depend on them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;focus behaviour;&lt;/li&gt;
&lt;li&gt;enabled and disabled states;&lt;/li&gt;
&lt;li&gt;loading feedback;&lt;/li&gt;
&lt;li&gt;keyboard navigation;&lt;/li&gt;
&lt;li&gt;announcements;&lt;/li&gt;
&lt;li&gt;control labels.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Presentation details
&lt;/h3&gt;

&lt;p&gt;These change frequently:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;spacing;&lt;/li&gt;
&lt;li&gt;animation duration;&lt;/li&gt;
&lt;li&gt;component nesting;&lt;/li&gt;
&lt;li&gt;skeleton shape;&lt;/li&gt;
&lt;li&gt;exact render count;&lt;/li&gt;
&lt;li&gt;decorative icons.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Test heavily at the first layer, selectively at the second, and deliberately at the third.&lt;/p&gt;

&lt;p&gt;The mistake is not testing presentation. Visual regressions matter.&lt;/p&gt;

&lt;p&gt;The mistake is assigning every implementation detail the same release-blocking weight as a broken payment flow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Modern UI testing is an exercise in restraint
&lt;/h2&gt;

&lt;p&gt;Browser tools are becoming more capable. They can observe more states, capture more traces, compare more pixels, and generate more assertions.&lt;/p&gt;

&lt;p&gt;That does not mean every observable state deserves a test.&lt;/p&gt;

&lt;p&gt;The hard work is choosing the few contracts that should remain true while the implementation underneath them changes.&lt;/p&gt;

&lt;p&gt;That is what makes a suite stable without making it shallow.&lt;/p&gt;

&lt;p&gt;Test what the user is promised.&lt;/p&gt;

&lt;p&gt;Observe the rest when it helps you diagnose failures.&lt;/p&gt;

&lt;p&gt;Do not turn every intermediate render into a permanent obligation.&lt;/p&gt;

</description>
      <category>react</category>
      <category>testing</category>
      <category>a11y</category>
      <category>frontend</category>
    </item>
    <item>
      <title>AI Can Write the Test. Your Team Owns the Maintenance</title>
      <dc:creator>Simon Gerber</dc:creator>
      <pubDate>Fri, 24 Jul 2026 22:12:16 +0000</pubDate>
      <link>https://dev.to/orbitpickle307/ai-can-write-the-test-your-team-owns-the-maintenance-4ehn</link>
      <guid>https://dev.to/orbitpickle307/ai-can-write-the-test-your-team-owns-the-maintenance-4ehn</guid>
      <description>&lt;p&gt;AI has made the first hour of test automation dramatically cheaper.&lt;/p&gt;

&lt;p&gt;Describe a workflow, paste a requirement, or point an agent at an application, and it can produce a respectable first draft. The code may include page objects, fixtures, assertions, and comments. It looks like progress because it is progress.&lt;/p&gt;

&lt;p&gt;But creation was never the full cost.&lt;/p&gt;

&lt;p&gt;The expensive part begins after the test joins the repository.&lt;/p&gt;

&lt;p&gt;Now the team must decide whether the assertions are meaningful, whether the selectors are durable, whether the data is safe, whether the test fails for the right reasons, and who will fix it six months later when the interface changes.&lt;/p&gt;

&lt;p&gt;AI lowers the cost of producing tests. It can also increase the number of tests you are responsible for.&lt;/p&gt;

&lt;p&gt;That is the trade-off teams need to understand.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generated code can fail before generated tests even run
&lt;/h2&gt;

&lt;p&gt;AI-generated frontend changes frequently work in a local environment and fail in CI. The problem is not always the quality of the generated code. It is the missing context around it.&lt;/p&gt;

&lt;p&gt;The CI environment may use a different Node version, package lock state, browser build, environment variable set, feature flag, operating system, or resource limit.&lt;/p&gt;

&lt;p&gt;This analysis of &lt;a href="https://aitestingcompare.com/why-ai-generated-frontend-changes-fail-in-ci-even-when-local-runs-pass/" rel="noopener noreferrer"&gt;why AI-generated frontend changes fail in CI even when local runs pass&lt;/a&gt; highlights an important principle: AI tends to optimise for the context it can see.&lt;/p&gt;

&lt;p&gt;If the model sees the component but not the pipeline, it can create a locally valid solution that violates an invisible constraint.&lt;/p&gt;

&lt;p&gt;The same thing happens with generated tests. A model may assume:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a clean database;&lt;/li&gt;
&lt;li&gt;one browser worker;&lt;/li&gt;
&lt;li&gt;stable test IDs;&lt;/li&gt;
&lt;li&gt;immediate API responses;&lt;/li&gt;
&lt;li&gt;permission to create arbitrary users;&lt;/li&gt;
&lt;li&gt;access to secrets that do not exist in CI;&lt;/li&gt;
&lt;li&gt;a desktop viewport;&lt;/li&gt;
&lt;li&gt;English copy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A useful generation prompt should include operational constraints, not only the user journey.&lt;/p&gt;

&lt;p&gt;Tell the model where the test runs, how data is created, what can execute in parallel, which selectors are preferred, and what evidence should be captured on failure.&lt;/p&gt;

&lt;p&gt;The prompt is part of the test architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Claude can write Playwright. That is not the decision
&lt;/h2&gt;

&lt;p&gt;A modern coding model can generate Playwright tests quickly. It can also refactor helpers, interpret error logs, and suggest alternate selectors.&lt;/p&gt;

&lt;p&gt;The interesting question is not whether it can write the code. It can.&lt;/p&gt;

&lt;p&gt;The better question is what changes in maintenance, review, and signal quality after generation becomes abundant.&lt;/p&gt;

&lt;p&gt;The article &lt;a href="https://aitestingreviews.com/when-claude-writes-your-playwright-tests-what-changes-in-maintenance-review-and-signal-quality/" rel="noopener noreferrer"&gt;When Claude Writes Your Playwright Tests&lt;/a&gt; describes the shift well. Reviewers need to evaluate more than syntax.&lt;/p&gt;

&lt;p&gt;A generated test can be perfectly valid TypeScript and still be a poor test because it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;asserts incidental copy;&lt;/li&gt;
&lt;li&gt;uses a selector that matches multiple elements;&lt;/li&gt;
&lt;li&gt;hides an unreliable action behind retries;&lt;/li&gt;
&lt;li&gt;duplicates coverage that already exists;&lt;/li&gt;
&lt;li&gt;skips negative behaviour;&lt;/li&gt;
&lt;li&gt;passes after an optimistic update but before server confirmation;&lt;/li&gt;
&lt;li&gt;creates data it never cleans up.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Code review for generated tests should begin with intent:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What product risk is this test reducing?&lt;/li&gt;
&lt;li&gt;What failure would it catch?&lt;/li&gt;
&lt;li&gt;Why is the browser the right layer?&lt;/li&gt;
&lt;li&gt;Does it fail when the product is broken?&lt;/li&gt;
&lt;li&gt;Will the team understand the failure report?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Only then should the reviewer care about formatting and helper reuse.&lt;/p&gt;

&lt;h2&gt;
  
  
  The test count can become a vanity metric
&lt;/h2&gt;

&lt;p&gt;When generation is expensive, teams are selective. When generation becomes almost free, restraint becomes more important.&lt;/p&gt;

&lt;p&gt;It is easy to produce 200 tests from requirements, tickets, and recorded sessions. The number looks impressive. Then the product changes, 47 tests fail, and nobody knows which failures matter.&lt;/p&gt;

&lt;p&gt;This framework on &lt;a href="https://browserslack.com/what-to-measure-before-you-trust-a-playwright-test-suite-generated-with-claude/" rel="noopener noreferrer"&gt;what to measure before trusting a Playwright test suite generated with Claude&lt;/a&gt; recommends measuring the suite as an operating system rather than a code output.&lt;/p&gt;

&lt;p&gt;Useful metrics include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;percentage of failures that represent real defects;&lt;/li&gt;
&lt;li&gt;median diagnosis time;&lt;/li&gt;
&lt;li&gt;maintenance hours per month;&lt;/li&gt;
&lt;li&gt;repeated failures from the same cause;&lt;/li&gt;
&lt;li&gt;coverage of critical workflows;&lt;/li&gt;
&lt;li&gt;tests that have never failed meaningfully;&lt;/li&gt;
&lt;li&gt;retries required for a green pipeline;&lt;/li&gt;
&lt;li&gt;reviewer effort per generated test.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That final metric matters. A model can produce code in seconds, but a senior engineer may spend 20 minutes proving that the code is safe and useful.&lt;/p&gt;

&lt;p&gt;AI does not eliminate review cost. It moves the work.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI testing platforms also contain human work
&lt;/h2&gt;

&lt;p&gt;A platform may advertise AI-generated tests, self-healing, natural-language instructions, or autonomous maintenance. Those capabilities can reduce effort. They can also introduce new operating tasks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;reviewing generated steps;&lt;/li&gt;
&lt;li&gt;validating prompts;&lt;/li&gt;
&lt;li&gt;inspecting traces;&lt;/li&gt;
&lt;li&gt;approving suggested fixes;&lt;/li&gt;
&lt;li&gt;resolving ambiguous assertions;&lt;/li&gt;
&lt;li&gt;monitoring model usage;&lt;/li&gt;
&lt;li&gt;handling false repairs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The article on &lt;a href="https://testingtoolguide.com/how-to-estimate-the-real-cost-of-an-ai-testing-platform-when-prompt-reviews-traces-and-human-approvals-add-up/" rel="noopener noreferrer"&gt;estimating the real cost of an AI testing platform when prompt reviews, traces, and human approvals add up&lt;/a&gt; offers a healthier cost model.&lt;/p&gt;

&lt;p&gt;Do not compare only subscription price with an open-source licence price.&lt;/p&gt;

&lt;p&gt;Compare the full workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Total cost = platform cost
           + test creation time
           + review time
           + maintenance time
           + infrastructure
           + failure diagnosis
           + training and adoption
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same equation should be used for a home-grown framework.&lt;/p&gt;

&lt;p&gt;“Free” software can be expensive when specialised engineers spend a significant part of every sprint maintaining it. A paid platform can also be expensive when it requires constant human supervision.&lt;/p&gt;

&lt;p&gt;The correct unit is not licence cost. It is cost per trustworthy result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build versus buy is mostly a staffing decision
&lt;/h2&gt;

&lt;p&gt;The comparison between &lt;a href="https://frontendtester.com/endtest-vs-hand-rolled-playwright-for-fast-changing-frontends-maintenance-cost-debugging-time-and-team-adoption/" rel="noopener noreferrer"&gt;Endtest and hand-rolled Playwright for fast-changing frontends&lt;/a&gt; frames the decision around ownership.&lt;/p&gt;

&lt;p&gt;A custom Playwright framework can provide excellent control. Your team can define fixtures, abstractions, reporters, environment management, and CI behaviour exactly as needed.&lt;/p&gt;

&lt;p&gt;But somebody owns all of it.&lt;/p&gt;

&lt;p&gt;That ownership includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;browser and dependency upgrades;&lt;/li&gt;
&lt;li&gt;authentication helpers;&lt;/li&gt;
&lt;li&gt;test-data utilities;&lt;/li&gt;
&lt;li&gt;parallel execution rules;&lt;/li&gt;
&lt;li&gt;retry strategy;&lt;/li&gt;
&lt;li&gt;screenshots, videos, traces, and logs;&lt;/li&gt;
&lt;li&gt;integrations with email, SMS, and storage;&lt;/li&gt;
&lt;li&gt;onboarding documentation;&lt;/li&gt;
&lt;li&gt;support for less technical contributors.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A managed system such as Endtest trades some framework-level freedom for a smaller internal maintenance surface and broader accessibility.&lt;/p&gt;

&lt;p&gt;Neither model is automatically better.&lt;/p&gt;

&lt;p&gt;The mistake is selecting a code-first framework because the initial proof of concept was easy, then discovering that the team unintentionally created an internal product.&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;Do we want to build tests, or do we also want to build and operate a testing platform?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Some organisations should answer yes. Many should not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reliability is different for streaming AI applications
&lt;/h2&gt;

&lt;p&gt;AI applications introduce behaviours that traditional CRUD tests do not encounter as often:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;streamed text arrives incrementally;&lt;/li&gt;
&lt;li&gt;the UI changes while the response is still in progress;&lt;/li&gt;
&lt;li&gt;intermediate state may be incomplete but valid;&lt;/li&gt;
&lt;li&gt;model outputs vary;&lt;/li&gt;
&lt;li&gt;cancellations and retries matter;&lt;/li&gt;
&lt;li&gt;network latency changes the rendering sequence.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A brittle test may wait for exact text, assume a fixed number of updates, or interact before the stream is complete.&lt;/p&gt;

&lt;p&gt;The guide to &lt;a href="https://aitestingreport.com/how-to-benchmark-browser-test-reliability-for-ai-apps-that-stream-responses-and-update-state-incrementally/" rel="noopener noreferrer"&gt;benchmarking browser test reliability for AI apps that stream responses and update state incrementally&lt;/a&gt; recommends testing the state machine rather than the precise output.&lt;/p&gt;

&lt;p&gt;For example, verify that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a response begins within an acceptable period;&lt;/li&gt;
&lt;li&gt;the loading or streaming state is visible;&lt;/li&gt;
&lt;li&gt;content grows rather than being replaced incorrectly;&lt;/li&gt;
&lt;li&gt;controls are enabled or disabled at the right times;&lt;/li&gt;
&lt;li&gt;cancellation stops the stream;&lt;/li&gt;
&lt;li&gt;the final state is persisted correctly;&lt;/li&gt;
&lt;li&gt;an error offers a recovery action.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For non-deterministic content, assert structure and constraints instead of a complete sentence.&lt;/p&gt;

&lt;p&gt;You may check that the response is non-empty, contains required facts, follows a schema, avoids prohibited content, or receives an acceptable evaluation score.&lt;/p&gt;

&lt;p&gt;The more variable the product, the more deliberate the oracle must be.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stable coverage does not require a heavy framework
&lt;/h2&gt;

&lt;p&gt;Some teams need deep framework control. Others mainly need dependable coverage of business-critical journeys without dedicating several engineers to test infrastructure.&lt;/p&gt;

&lt;p&gt;This practical review of &lt;a href="https://test-automation-experts.com/a-practical-look-at-endtest-for-teams-that-need-stable-coverage-without-a-heavy-framework/" rel="noopener noreferrer"&gt;Endtest for teams that need stable coverage without a heavy framework&lt;/a&gt; represents the second group.&lt;/p&gt;

&lt;p&gt;The value proposition is not that code is bad. It is that infrastructure work has an opportunity cost.&lt;/p&gt;

&lt;p&gt;A team that avoids building its own reporting, test editor, execution grid, collaboration layer, and integrations can spend that time improving product coverage.&lt;/p&gt;

&lt;p&gt;The trade-off is accepting the platform's model and limits. That is why a proof of concept should test the awkward workflows, not only login.&lt;/p&gt;

&lt;p&gt;Try:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;multi-step authentication;&lt;/li&gt;
&lt;li&gt;file uploads and downloads;&lt;/li&gt;
&lt;li&gt;dynamic tables;&lt;/li&gt;
&lt;li&gt;cross-domain navigation;&lt;/li&gt;
&lt;li&gt;email and SMS verification;&lt;/li&gt;
&lt;li&gt;failure diagnosis;&lt;/li&gt;
&lt;li&gt;role-based collaboration;&lt;/li&gt;
&lt;li&gt;CI execution under realistic parallelism.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Any tool looks good when the demo is “open page, click button.”&lt;/p&gt;

&lt;h2&gt;
  
  
  Human approval should focus on risk
&lt;/h2&gt;

&lt;p&gt;AI-generated tests should not require a committee meeting. But they should receive review proportional to their impact.&lt;/p&gt;

&lt;p&gt;A low-risk visual check for an internal page may need a quick inspection. A payment, access-control, deletion, or regulatory workflow deserves deeper review.&lt;/p&gt;

&lt;p&gt;A lightweight approval checklist can ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is the workflow worth testing at the browser layer?&lt;/li&gt;
&lt;li&gt;Are secrets and personal data handled safely?&lt;/li&gt;
&lt;li&gt;Can the test run in parallel?&lt;/li&gt;
&lt;li&gt;Are assertions tied to user outcomes?&lt;/li&gt;
&lt;li&gt;Does failure produce enough evidence?&lt;/li&gt;
&lt;li&gt;Is cleanup defined?&lt;/li&gt;
&lt;li&gt;Who owns future maintenance?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This prevents the two common extremes: blindly accepting generated code or making AI assistance so bureaucratic that nobody uses it.&lt;/p&gt;

&lt;p&gt;The goal is not perfect generation.&lt;/p&gt;

&lt;p&gt;The goal is controlled leverage.&lt;/p&gt;

&lt;h2&gt;
  
  
  The new bottleneck is judgement
&lt;/h2&gt;

&lt;p&gt;For years, the bottleneck in browser automation was writing the code.&lt;/p&gt;

&lt;p&gt;AI is removing part of that bottleneck.&lt;/p&gt;

&lt;p&gt;What remains is judgement:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;choosing the right risks;&lt;/li&gt;
&lt;li&gt;deciding the right test layer;&lt;/li&gt;
&lt;li&gt;defining stable assertions;&lt;/li&gt;
&lt;li&gt;controlling test data;&lt;/li&gt;
&lt;li&gt;evaluating failures;&lt;/li&gt;
&lt;li&gt;deleting low-value coverage;&lt;/li&gt;
&lt;li&gt;choosing which infrastructure to own.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is good news. These are more valuable problems than remembering the syntax for a locator.&lt;/p&gt;

&lt;p&gt;But they are not automatic.&lt;/p&gt;

&lt;p&gt;AI can write the test.&lt;/p&gt;

&lt;p&gt;Your team still owns whether anyone should trust it.&lt;/p&gt;

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