{"title":"Playwright MCP Is Not Your Regression Test Suite","excerpt":"A Playwright MCP session confirmed a checkout coupon worked, and the assertion it recorded passed against the correct page and against three broken versions of it. This guide runs @playwright/mcp 0.0.83 and Playwright 1.64.1 on a loopback lab: what the accessibility snapshot shows, why the agent's default browser differs from your CI build, how a verified text becomes a bare visibility check, and why an agent working at its own pace misses a sub-second race. It then encodes the race with a held page.route, proves each test against three hand-written mutants, and sets limits for Playwright's planner, generator and healer agents.","canonicalUrl":"https://automationtester.in/blog/ai-in-testing/playwright-mcp-vs-regression-test-suite","category":{"name":"AI in Testing","slug":"ai-in-testing"},"tags":["playwright","playwright-mcp","model-context-protocol","ai-agents","mutation-testing","flaky-tests","test-design"],"author":{"name":"Shashank Rawlani","url":"https://shashank.rawlani.com","linkedin":"https://www.linkedin.com/in/shashankrawlani/"},"datePublished":"2026-11-04T13:30:00.000Z","dateModified":"2026-11-04T13:30:00.000Z","coverImage":{"url":"https://ik.imagekit.io/automationtester/automationtester-in/blog/covers/v3/playwright-mcp-vs-regression-test-suite.webp","alt":"Dark technical illustration. On the left, a green browser panel holds an indented tree of grey and green pills, each row starting with a small green dot. A dashed grey path leaves its bottom edge and wanders along the bottom of the frame to the right edge. From the browser, two horizontal lanes run right: the upper one solid green, the lower one dashed grey until it meets a narrow dashed green vertical gate, where a ringed green node holds it and a thin green curve drops into that node from the upper lane. Past the gate the lower lane is solid green, and both lanes curve into a tall green panel lined with green pills. On its right edge, three small orange panels each send a straight orange line into a crossed-out orange ring."},"contentHtml":"<div class=\"callout callout-info\"><strong>Quick answer:</strong> Point Playwright MCP at a page when you need to explore it or diagnose it, and keep the regression gate in versioned Playwright tests. The agent works from accessibility snapshots, one tool call at a time, with a settle pause after every action, so it cannot see races shorter than its own pace, and the code it emits from a verification can be weaker than the check it actually ran. Run the server isolated, with its traces and session log saved. Read those for hypotheses. Then write the regression yourself: control the timing with <code>page.route</code>, assert outcomes with web-first assertions, and run each test against deliberate mutations of the page to prove it fails when the behaviour breaks.</div>\n\n<p>An agent with Playwright MCP opens a checkout page, types a coupon, presses Apply and confirms the message \"Coupon SAVE20 applied\". It hands back the code it ran, and that code becomes a test. In the lab behind this article, the test passed 20 times out of 20 against the correct page. It also passed 20 out of 20 against three broken versions: one that dropped a coupon applied before the cart had loaded, one that never subtracted the discount, and one that sent the coupon under the wrong query parameter, so the server rejected it.</p>\n\n<p>The page really did say the coupon was applied, and the session proved only that. A regression suite has to answer whether the total is right whatever order the responses arrive in. This finale of the series places Playwright MCP and Playwright's own test agents on either side of that line. The <a href=\"/blog/ai-in-testing/appium-mcp-exploration-vs-test-suite\">Appium MCP article</a> covers the mobile side; everything below is browser-specific.</p>\n\n<div class=\"callout callout-warning\"><strong>Versions checked on 2026-10-27:</strong> <code>@playwright/mcp</code> 0.0.83 (npm latest, published 2026-09-28), <code>@playwright/test</code> 1.64.1 (2026-10-14), and <code>@modelcontextprotocol/sdk</code> 1.32.2 for the client. The lab ran on Node.js 26.12.1 on macOS arm64, against a static checkout page and a small Node server on 127.0.0.1. The MCP calls were scripted through the official TypeScript client rather than chosen by a model, so the outputs are what the server returns, not what any particular model would do. Playwright MCP ships often and its version is still 0.0.x; re-check the flags below when you upgrade.</div>\n\n<h2 id=\"pwmcp-what-the-model-reads\">What the model reads: a snapshot with refs</h2>\n\n<p>Playwright MCP does not send the model a screenshot to act on. After navigation and most actions it returns an accessibility snapshot: a YAML tree of roles, accessible names and values, with a <code>ref</code> on each node. The model passes a ref back as the <code>target</code> of the next call. This is the snapshot the lab page produced after the coupon was applied:</p>\n\n<pre class=\"language-yaml\"><code class=\"language-yaml\">- main [ref=e2]:\n  - heading \"Checkout\" [level=1] [ref=e3]\n  - paragraph [ref=e4]:\n    - text: \"Order total:\"\n    - status [ref=e5]: $100.00\n  - generic [ref=e6]:\n    - generic [ref=e7]:\n      - text: Coupon code\n      - textbox \"Coupon code\" [ref=e8]: SAVE20\n    - button \"Apply coupon\" [active] [ref=e9]\n  - status [ref=e10]: Coupon SAVE20 applied</code></pre>\n\n<p>The total is an <code>&lt;output&gt;</code> element, which the tree reports as <code>status</code>, so the page has two unnamed elements with that role and a role-only locator would be ambiguous. Refs last only until the page changes; the snapshot documentation says a stale one fails with an instruction to take a new snapshot. That suits an agent and is useless as an identifier in a committed test. Each action also returns the Playwright code it ran, such as <code>await page.getByRole('button', { name: 'Apply coupon' }).click();</code>, and those lines are the raw material for any test the agent writes.</p>\n\n<p>The default server registers 25 tools in 0.0.83, already including <code>browser_evaluate</code> and <code>browser_run_code_unsafe</code>, which run arbitrary script in the page or Playwright code against it. Network mocking, storage, tracing, coordinate clicks, PDF and the verification tools are opt-in through <code>--caps</code>; with every capability on, the lab server listed 72 tools.</p>\n\n<h2 id=\"pwmcp-not-your-ci-browser\">The agent's browser is not your CI browser</h2>\n\n<p><code>@playwright/mcp</code> 0.0.83 does not depend on a released Playwright: its <code>package.json</code> pins <code>playwright</code> 1.64.0-alpha-1790635538000, with Chromium revision 1247 (155.0.8059.12), while <code>@playwright/test</code> 1.64.1 expects revision 1248, Chrome for Testing 156.0.8078.4. And the agent drives neither by default. The MCP configuration code assigns the <code>chrome</code> channel when no browser is named, so it launches the machine's installed Google Chrome. In the lab, both the standalone server and the bundled copy reported \"Google Chrome 155\" from <code>navigator.userAgentData</code>; the test runner's <code>chromium.launch()</code> reported 156.0.8078.4.</p>\n\n<p>Since 1.62, Playwright bundles the server as <code>npx playwright mcp</code>. Run it from the same pinned <code>@playwright/test</code> as your suite with <code>--browser chromium</code>, which maps to Chrome for Testing, and the agent explores on the build a default Chromium project tests on.</p>\n\n<h2 id=\"pwmcp-server-for-evidence\">A server configured to leave evidence</h2>\n\n<p>By default the server keeps a persistent profile in the user's cache directory, keyed by a hash of the client's workspace, so logins survive between sessions. The extension mode goes further and attaches to your own running Chrome. <code>--isolated</code> keeps the profile in memory and discards it when the browser closes, which is the only mode with a starting state you can describe; <code>--storage-state</code> seeds it with a known test user.</p>\n\n<pre class=\"language-json\"><code class=\"language-json\">{\n  \"mcpServers\": {\n    \"checkout-explore\": {\n      \"command\": \"npx\",\n      \"args\": [\n        \"playwright\", \"mcp\",\n        \"--browser\", \"chromium\",\n        \"--isolated\",\n        \"--storage-state\", \"explore/test-user.json\",\n        \"--allowed-origins\", \"https://staging.example.test\",\n        \"--caps\", \"devtools,testing\",\n        \"--save-session\",\n        \"--output-dir\", \"explore/out\",\n        \"--secrets\", \"explore/.env.explore\"\n      ]\n    }\n  }\n}</code></pre>\n\n<p>Two of those flags are weaker than their names suggest. <code>--allowed-origins</code> blocks requests from other origins (in the lab, a navigation to <code>http://localhost:4387/</code> with only the 127.0.0.1 origin allowed failed with <code>net::ERR_BLOCKED_BY_CLIENT</code>), but the CLI help says it is not a security boundary and does not apply to redirects. Treat it as a way to keep the session on topic, and put the real control in the network the browser runs on; the <a href=\"/blog/ai-in-testing/test-mcp-ssrf-redirect-discovery-security\">SSRF part of this series</a> covers why redirect-blind allowlists fail. <code>--secrets</code> replaces matching values in tool responses so the model does not read them, and the configuration schema calls it a convenience, not a security feature.</p>\n\n<p>The page is untrusted input too. Text on a page someone else controls can steer the agent, and with <code>browser_run_code_unsafe</code> in the default set the damage need not stay in the page; the <a href=\"/blog/ai-in-testing/test-mcp-prompt-injection-data-exfiltration\">prompt-injection part</a> shows how to test for that. Explore on an environment you own, with a test account worth nothing, and never against production with write access. The MCP security guidance for local servers adds the other half: run the server sandboxed, with minimal default access to files and network.</p>\n\n<p><code>--save-session</code> writes a Markdown log of every tool call and its result, and <code>--caps=devtools</code> adds tools that record a Playwright trace while the agent works. Those two files are the evidence everything below depends on. If they go to a log store, the <a href=\"/blog/ai-in-testing/mcp-observability-audit-without-secrets\">observability part</a> covers keeping credentials out of them.</p>\n\n<h2 id=\"pwmcp-emitted-assertion\">The emitted assertion is weaker than the check</h2>\n\n<p>With <code>--caps=testing</code>, the agent gets verification tools, and Playwright's MCP documentation says each one records the matching <code>expect(...)</code> line, so that a passing sequence can be assembled into a test. Here is the call from the opening, and what it recorded:</p>\n\n<pre class=\"language-text\"><code class=\"language-text\">&gt;&gt;&gt; browser_verify_text_visible {\"text\":\"Coupon SAVE20 applied\"}\n### Result\nDone\n### Ran Playwright code\n```js\nawait expect(page.getByTestId('coupon-status')).toBeVisible();\n```</code></pre>\n\n<p>The text is gone. The tool did search the page for \"Coupon SAVE20 applied\", but then, in <code>verify.ts</code> at v1.64.1, it normalises the matching locator to the best one it can generate, here the test id of the status paragraph, and records a visibility assertion on that. The recorded line passes for any visible message in that paragraph, including \"Coupon not recognised\". That is why the session test survived all three broken versions.</p>\n\n<pre class=\"language-typescript\"><code class=\"language-typescript\">// recorded by the session: passes whatever the message says\nawait expect(page.getByTestId('coupon-status')).toBeVisible();\n\n// what the check meant: the message and the money\nawait expect(page.getByTestId('coupon-status')).toHaveText('Coupon SAVE20 applied');\nawait expect(page.getByTestId('order-total')).toHaveText('$100.00');</code></pre>\n\n<p>Reading generated specs for assertions that cannot fail is a habit covered in <a href=\"/blog/ai-in-testing/review-ai-generated-playwright-tests-before-merge\">the review checklist for AI-written Playwright tests</a>. The narrower point: \"Done\" in the session and the line it hands you are two different checks, so review the line.</p>\n\n<h2 id=\"pwmcp-reading-the-session\">What the session log shows, and what it hides</h2>\n\n<p>The lab server answers <code>/api/cart</code> after a random delay of up to 400 ms, as a slow cart service would. The saved session log shows the agent arriving while that request was still in flight:</p>\n\n<pre class=\"language-text\"><code class=\"language-text\">### Tool call: browser_navigate\n- Result\n  \"snapshot\": \"... - status [ref=e5]: Loading… ...\"\n\n### Tool call: browser_click\n- Result\n  \"code\": \"await page.getByRole('button', { name: 'Apply coupon' }).click();\",\n  \"snapshot\": \"... - status [ref=e5]: $100.00 ... - status [ref=e10]: Coupon SAVE20 applied\"</code></pre>\n\n<p>By the time the click ran, the cart had answered. Each MCP action waits for the page to settle before returning, 500 ms by default under <code>--timeout-settle</code>, and a real model adds thinking time between calls. At that pace the coupon nearly always lands after the cart, and the agent walks past a bug that needs the opposite order.</p>\n\n<p>The test runner works at machine speed. The session's three action lines plus an assertion on the total, run with <code>--repeat-each=20</code> against the shipped page, failed 17 times and passed 3. The trace of one failure, read with the <code>npx playwright trace</code> commands added for agents in 1.59, shows the order:</p>\n\n<pre class=\"language-text\"><code class=\"language-text\">$ npx playwright trace requests\n     # Start     Method   Status   Name                                            Duration     Size Route\n  ──── ───────── ──────── ──────── ───────────────────────────────────────────── ────────── ──────── ──────────\n    1. 0:00.291  GET      200      127.0.0.1:4388                                      12ms      628\n    2. 0:00.312  GET      200      app.js                                              10ms     1.1K\n    3. 0:00.341  GET      200      cart                                               351ms      171\n    4. 0:00.457  GET      200      coupon?code=SAVE20                                   2ms      183</code></pre>\n\n<p>The coupon answered at about 0.46 s; the cart was pending until about 0.69 s. The MCP session's own trace, opened the same way, has the cart finishing at 0.32 s and the coupon starting at 0.40 s. Same page, opposite orders. The reviewed root cause, written down where someone can dispute it: the shipped code applies the discount only if the subtotal is already known, and never applies it later. The <a href=\"/blog/automation-tutorials/playwright-trace-viewer-root-cause-workflow\">trace-viewer workflow</a> covers reading a trace to that depth.</p>\n\n<p>That candidate is still not the regression test. Failing 17 times in 20, it would look flaky in CI, earn a retry and pass on it. The regression has to produce the bad ordering every time.</p>\n\n<h2 id=\"pwmcp-hold-the-cart\">Hold the cart response until the coupon has answered</h2>\n\n<p>The contract is that the total is right in either order. Encode both orders, and force the dangerous one with a route that holds the cart request until the coupon response has arrived:</p>\n\n<pre class=\"language-typescript\"><code class=\"language-typescript\">import { test, expect } from '@playwright/test';\n\ntest.describe('coupon discount, whatever order the responses arrive in', () =&gt; {\n  test('a coupon applied before the cart loads still discounts the total', async ({ page }) =&gt; {\n    let releaseCart: () =&gt; void = () =&gt; {};\n    const cartHeld = new Promise&lt;void&gt;((resolve) =&gt; { releaseCart = resolve; });\n    await page.route('**/api/cart', async (route) =&gt; {\n      await cartHeld;\n      await route.continue();\n    });\n\n    await page.goto('/');\n    const couponAnswered = page.waitForResponse((r) =&gt; r.url().includes('/api/coupon'));\n    await page.getByRole('textbox', { name: 'Coupon code' }).fill('SAVE20');\n    await page.getByRole('button', { name: 'Apply coupon' }).click();\n    await couponAnswered;\n    releaseCart();\n\n    await expect(page.getByTestId('coupon-status')).toHaveText('Coupon SAVE20 applied');\n    await expect(page.getByTestId('order-total')).toHaveText('$100.00');\n  });\n\n  test('a coupon applied after the cart loads discounts the total', async ({ page }) =&gt; {\n    await page.goto('/');\n    await expect(page.getByTestId('order-total')).toHaveText('$120.00');\n    await page.getByRole('textbox', { name: 'Coupon code' }).fill('SAVE20');\n    await page.getByRole('button', { name: 'Apply coupon' }).click();\n    await expect(page.getByTestId('order-total')).toHaveText('$100.00');\n  });\n});</code></pre>\n\n<p><code>route.continue()</code> lets the real server produce the cart; the test controls when it arrives, not what it says. One gap remains: a received coupon response does not prove the page has handled it before the cart is released. If your page shows the coupon result before the total is known, assert that message before <code>releaseCart()</code> to pin the order completely; as written, this test relies on the response. <code>toHaveText</code> retries until the text matches or the five-second default timeout expires, so no sleep is needed, and the second test waits for <code>$120.00</code> before typing to guarantee the ordinary order.</p>\n\n<p>Against the shipped page the first test fails on every run. The call log shows it is no timing accident: the total went from \"Loading…\" to the undiscounted amount and never showed the right one.</p>\n\n<pre class=\"language-text\"><code class=\"language-text\">Error: expect(locator).toHaveText(expected) failed\n\nLocator:  getByTestId('order-total')\nExpected: \"$100.00\"\nReceived: \"$120.00\"\nTimeout:  5000ms\n\nCall log:\n  - Expect \"toHaveText\" getByTestId('order-total') with timeout 5000ms\n  - waiting for getByTestId('order-total')\n    4 × locator resolved to &lt;output data-testid=\"order-total\"&gt;Loading…&lt;/output&gt;\n      - unexpected value \"Loading…\"\n    10 × locator resolved to &lt;output data-testid=\"order-total\"&gt;$120.00&lt;/output&gt;\n       - unexpected value \"$120.00\"</code></pre>\n\n<h2 id=\"pwmcp-mutants-before-merge\">Run every candidate against mutants before merge</h2>\n\n<p>A test that has only ever passed has not shown it can detect anything. Before it joins the suite, run it against small, deliberate breakages of the code it protects. These three were written by hand, each a plausible way to bring the bug back or break the feature beside it:</p>\n\n<ul>\n<li><strong>M1</strong>, the shipped bug: the discount is applied only if the subtotal is already known.</li>\n<li><strong>M2</strong>: the render ignores the discount.</li>\n<li><strong>M3</strong>: the coupon is sent as <code>?coupon=</code> instead of <code>?code=</code>, so the server rejects it.</li>\n</ul>\n\n<pre class=\"language-bash\"><code class=\"language-bash\">for app in app.js app-race.js app-nodiscount.js app-wrongparam.js; do\n  APP_JS=$app PLAYWRIGHT_JSON_OUTPUT_NAME=results-$app.json \\\n    npx playwright test --repeat-each=20 --reporter=json &gt; /dev/null\ndone</code></pre>\n\n<p>The <code>webServer</code> block in the Playwright config starts the page with <code>APP_JS</code> taken from the environment, so each loop serves one variant. Failures out of 20 runs per cell:</p>\n\n<table>\n<thead><tr><th>Test</th><th>Correct page</th><th>M1 coupon before cart</th><th>M2 no discount</th><th>M3 wrong parameter</th></tr></thead>\n<tbody>\n<tr><td>Session line: status visible</td><td>0</td><td>0</td><td>0</td><td>0</td></tr>\n<tr><td>Session lines plus a total check</td><td>0</td><td>17</td><td>20</td><td>20</td></tr>\n<tr><td>Contract: coupon before cart</td><td>0</td><td>20</td><td>20</td><td>20</td></tr>\n<tr><td>Contract: coupon after cart</td><td>0</td><td>0</td><td>20</td><td>20</td></tr>\n</tbody>\n</table>\n\n<p>By column, each mutant must be killed on every run by at least one test, and only the held-cart test kills M1 reliably. The after-cart test surviving M1 is fine; the pair covers the contract. By row, zeros under every mutant mark a test that cannot fail, whatever its name says. Keep the mutants beside the spec, as files or patches, and rerun the loop before merging any change to it.</p>\n\n<h2 id=\"pwmcp-test-agents\">Playwright's planner, generator and healer</h2>\n\n<p>Playwright 1.56 introduced three test agents. <code>npx playwright init-agents --loop=claude</code> (also <code>vscode</code>, <code>codex</code>, <code>copilot</code> and <code>opencode</code>) writes a planner, a generator and a healer as agent definitions, a seed test, a <code>specs/</code> directory for Markdown plans, and an <code>.mcp.json</code> that starts <code>npx playwright run-test-mcp-server</code>. The planner explores from the seed test and saves a plan, the generator executes each plan step live and writes a test from its log, and the healer runs failing tests and edits them. The documentation asks you to regenerate the definitions on every Playwright upgrade.</p>\n\n<p>The planner and generator belong on the exploration side. Their plan and candidate spec go through the same review and mutants as anything else, and the planner's 1.64.1 tool list includes <code>browser_run_code_unsafe</code>, so the environment rules above apply to it.</p>\n\n<p>The healer is different. Its definition at v1.64.1 lists fixing assertions and expected values among its remedies, tells it not to ask questions and to \"do the most reasonable thing possible to pass the test\", and lets it mark a test <code>test.fixme()</code> when it believes the application is broken. The documentation names the outcome: a passing test, or a skipped one. Pointed at the contract test while M1 is live, each of those moves turns the build green and none fixes the bug.</p>\n\n<p>Use the healer on locators and waits after a redesign, on a branch, with a person reading the diff. A heal that changes an expected value or adds <code>test.fixme</code> is a product decision for whoever owns the behaviour, and no heal lands without a rerun of the mutants.</p>\n\n<h2 id=\"pwmcp-price-of-two-tracks\">The price of keeping two tracks</h2>\n\n<p>Exploration costs tokens: every action returns a snapshot, and the Playwright MCP README now steers coding agents to <code>playwright-cli</code> with skills because tool schemas and accessibility trees are expensive context, keeping MCP for long, stateful exploratory loops. Review costs a person's time per candidate. Mutant runs cost wall-clock time, since every kill waits out a five-second assertion timeout; the 320 runs above took several minutes on a one-page app.</p>\n\n<p>Skip the agent where a page object and a passing contract suite already exist. It earns its cost on a new flow, an unautomated page, or a flaky failure nobody can explain yet, where a session's trace and network log produce a hypothesis faster than reading code.</p>\n\n<h2 id=\"pwmcp-series-close\">Where the series leaves you</h2>\n\n<p>Across eight parts, one rule kept coming back: a model may propose and explore, but the decision rests on something deterministic that someone can read. For servers that meant scripted clients and conformance runs; for authorization, tokens minted to be refused; for injection and SSRF, a scripted attacker and stub servers. For a browser it means a held response and a mutant that the test has to kill.</p>\n\n<h2 id=\"pwmcp-apply-today\">Apply this today</h2>\n\n<p>Pick one flow with a known or suspected timing bug. Start <code>npx playwright mcp</code> from your pinned <code>@playwright/test</code> with <code>--browser chromium --isolated --caps=devtools,testing --save-session</code> and an output directory, against an environment you own. Let the agent or the planner walk the flow with tracing on, and read the session log and trace for the order of requests. Write the held-route test for the dangerous order and a plain one for the ordinary order, then two or three mutants aimed at the root cause, and run the loop.</p>\n\n<p>Keep the evidence in one pull request: session log, trace, a two-line root-cause note, the spec, and a mutant table with at least one 20-of-20 kill per mutant.</p>\n\n<h2 id=\"pwmcp-faq\">Questions about Playwright MCP and regression suites</h2>\n\n<h3 id=\"pwmcp-faq-ci-agent\">Can a Playwright MCP agent run in CI as a smoke check?</h3>\n\n<p>It can run there, headless and isolated, and a pass means a model found some route through the flow on that build. That is too weak for a merge gate: another model version may take another route, and a failure may be the model rather than the product. Use it for triage on a failed build and keep the gate on the versioned suite.</p>\n\n<h3 id=\"pwmcp-faq-assemble\">Is it safe to commit the \"Ran Playwright code\" lines as a test?</h3>\n\n<p>As a starting point, yes. The emitted locators use roles and test ids, as you would. Rewrite the assertions, because a verification can be recorded weaker than the check the tool ran, and add the timing, data and starting state the session never controlled.</p>\n\n<h2 id=\"pwmcp-references\">Primary references</h2>\n\n<ul>\n<li><a href=\"https://github.com/microsoft/playwright-mcp/blob/v0.0.83/README.md\">Playwright MCP README at v0.0.83</a>. The CLI options (isolated, storage state, allowed origins with its not-a-boundary note, secrets, save session, output directory, settle timeout, caps), the user profile modes, the tool list per capability, the security note and the CLI-versus-MCP recommendation.</li>\n<li><a href=\"https://www.npmjs.com/package/@playwright/mcp\">npm registry entry for @playwright/mcp</a>. Version 0.0.83 as latest, its publish date, and the pinned <code>playwright</code> 1.64.0-alpha dependency.</li>\n<li><a href=\"https://playwright.dev/mcp/snapshots\">Playwright MCP documentation: snapshots</a>. The snapshot format, refs as targets, ref lifetime and the stale-ref error.</li>\n<li><a href=\"https://playwright.dev/mcp/tools/assertions\">Playwright MCP documentation: testing and assertions</a>. The verify tools under <code>--caps=testing</code> and the statement that each records an <code>expect(...)</code> line for assembling a test.</li>\n<li><a href=\"https://playwright.dev/mcp/tools/tracing\">Playwright MCP documentation: tracing</a>. <code>browser_start_tracing</code> and <code>browser_stop_tracing</code> under the devtools capability, and where traces are written.</li>\n<li><a href=\"https://github.com/microsoft/playwright/blob/v1.64.1/packages/playwright-core/src/tools/backend/verify.ts\">verify.ts in microsoft/playwright at v1.64.1</a>. The text verification normalising its locator and recording a visibility assertion.</li>\n<li><a href=\"https://github.com/microsoft/playwright/blob/v1.64.1/packages/playwright-core/src/tools/mcp/config.ts\">MCP config.ts in microsoft/playwright at v1.64.1</a>. The <code>chrome</code> channel assigned when no browser is named, <code>chromium</code> mapped to Chrome for Testing, and the 500 ms settle default.</li>\n<li><a href=\"https://playwright.dev/docs/test-agents\">Playwright Test Agents guide</a>. Planner, generator and healer, <code>init-agents</code> loops, seed tests, specs, regenerating on upgrade, and the healer's passing-or-skipped output.</li>\n<li><a href=\"https://github.com/microsoft/playwright/blob/v1.64.1/packages/playwright/src/agents/playwright-test-healer.agent.md\">Healer agent definition at v1.64.1</a>. Its instructions to fix assertions and expected values, not to ask questions, and to mark tests <code>test.fixme()</code>.</li>\n<li><a href=\"https://playwright.dev/docs/release-notes\">Playwright release notes</a>. Test agents in 1.56, the trace CLI for agents in 1.59, and the bundled <code>npx playwright mcp</code> in 1.62.</li>\n<li><a href=\"https://playwright.dev/docs/network\">Playwright network guide</a>. <code>page.route</code>, <code>route.continue()</code> and <code>page.waitForResponse</code>, used to hold and release the cart request.</li>\n<li><a href=\"https://playwright.dev/docs/test-assertions\">Playwright assertions reference</a>. Auto-retrying assertions such as <code>toHaveText</code> and the five-second default timeout.</li>\n<li><a href=\"https://playwright.dev/docs/test-cli\">Playwright Test command line</a>. <code>--repeat-each</code> for the 20-run tallies.</li>\n<li><a href=\"https://playwright.dev/docs/test-webserver\">Playwright web server configuration</a>. Starting the page under test from the config with its own environment.</li>\n<li><a href=\"https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices\">MCP security best practices</a>. Local server compromise, and running local servers sandboxed with minimal default access.</li>\n</ul>\n"}