AI and agentic testing
Agentic testing with Playwright: how AI agents plan, write and repair tests
How AI agents do QA work with Playwright today: the loop, the tools (test agents, MCP, Agent SDK), where agents go wrong and the guardrails that work.
Agentic testing with Playwright means handing the QA work itself to an AI agent that can read your code, drive a real browser and run commands. It reads the specs and the code, plans what to test, writes Playwright tests, runs them, collects evidence for each failure, works out why it failed and proposes a repair. The result is ordinary Playwright Test code in your repository that runs with or without the agent. As of October 2026, the main building blocks are Playwright’s own test agents (planner, generator and healer), Playwright MCP and playwright-cli for browser control, and agent frameworks such as the Claude Agent SDK.
The hard part is not the agent. It is making sure the agent cannot turn a red build green by checking less. Below: the loop, the tools, four ways agents go wrong, the guardrails that work, and a test written the way an agent should write it.
What agentic testing with Playwright is
Codegen records a script of what you clicked, and a chat model writes a test from memory of pages like yours. An agent works in a loop with tools instead: Anthropic’s Agent SDK documentation defines an agent as “an application that completes a task by planning its own steps and calling tools that read files, run commands, or edit code.” For testing, the agent can open the page it is testing, run the test it just wrote, read the failure and try again. The output is the same as in any good Playwright project: plain TypeScript files a developer can review, run with npx playwright test locally or in CI.
The agentic QA workflow, step by step
Whichever tools you choose, the work follows seven steps.
- Read the specs and the code. Tickets, acceptance criteria, business rules, and the fixtures and page objects that already exist. Expected values come from these documents, not from whatever the app shows today.
- Plan. The agent lists the flows and cases to cover, highest risk first, in a plan a person can correct before any test code exists.
- Write. It opens the real page to see the roles, labels and requests behind each action, then writes tests that reuse your helpers.
- Run. It runs the new tests, the type checker and the linter, and fixes its own mistakes first.
- Collect evidence. For every failure: the error, the expected and actual values, a trace or screenshot, and the API response behind it.
- Triage. It decides what each failure is: a product bug, an intended change, a broken test, an environment problem or a flaky test.
- Repair or report. A broken test gets fixed. A product bug gets a bug report, and the test stays red. Anything unclear goes to a person.
Steps 6 and 7 separate agentic testing from a script that “heals” anything red. Until proven otherwise, a failing test is evidence about the product.
Agentic testing tools for Playwright, as of October 2026
Playwright test agents: planner, generator, healer
Playwright introduced three test agents in version 1.56, released on October 6, 2025, and they ship with Playwright itself. In the current docs:
- The planner explores the app and writes a Markdown test plan into
specs/. It first runs a seed test (seed.spec.ts) so your global setup, fixtures and hooks are in place, and uses it as the example for the tests that follow. - The generator turns the plan into test files under
tests/and “verifies selectors and assertions live as it performs the scenarios.” - The healer replays a failing test, inspects the current UI, suggests a patch (a locator update, a wait adjustment, a data fix) and reruns the test “until it passes or until guardrails stop the loop.”
One command adds the agent definitions to a project. The docs list four loops (vscode, claude, codex and opencode) and say to regenerate the definitions whenever you update Playwright:
npx playwright init-agents --loop=claude
Note the healer’s documented output: “A passing test, or a skipped test if the healer believes that functionality is broken.” That is a sensible default, and exactly the change a person should see before it is merged: skipping a checkout test is a release decision.
Playwright MCP and playwright-cli
Both give an agent a real browser. Playwright MCP is a Model Context Protocol server that works “using structured accessibility snapshots” rather than screenshots: the agent sees each element’s role and text, plus a reference it can act on. In Claude Code, Anthropic’s coding tool, you add it with claude mcp add playwright npx @playwright/mcp@latest.
playwright-cli offers the same control through short shell commands and installable skills. Playwright’s docs recommend it for coding agents, because it avoids “loading large tool schemas and verbose accessibility trees into the model context”, and MCP for “specialized agentic loops that benefit from persistent state”, such as exploratory automation.
Two warnings from the MCP docs belong in your own rules: enable the browser_run_code_unsafe tool, which the docs call equivalent to remote code execution, only for trusted clients, and treat tools that a web page registers for agents as untrusted input. Page content is data, never instructions.
The Claude Agent SDK
For your own headless QA agents in CI, you need an agent framework. The Claude Agent SDK provides “the same tools, agent loop, and context management that power Claude Code” as a TypeScript or Python library. What matters for QA work:
- Limits.
maxTurnscaps the tool round trips, andmaxBudgetUsdstops the loop at an estimated spend. - Permissions.
allowedToolsapproves the tools you list,disallowedToolsblocks tools outright, and indontAskmode any call that would ask for approval is denied. - Hooks. A
PreToolUsehook runs before a tool call and can block it, so a rule like “never edit the app’s source” is enforced by code, not only by the prompt. - Subagents. A writer and a reviewer can run with separate tool lists, so the reviewer can be limited to reading.
| Tool | What it gives an agent | Good fit |
|---|---|---|
| Playwright test agents | A planner, a generator and a healer | Starting inside your AI tool |
| Playwright MCP | A browser through accessibility snapshots | Exploration and long sessions |
playwright-cli |
A browser through short commands | Coding agents with tight context |
| Claude Agent SDK | The agent loop, tools, hooks and limits | Your own QA agents in CI |
Where agents go wrong
Tests that check too little
An agent rewarded for a passing test can write one that passes on a broken product: await expect(page.getByText('Order placed')).toBeVisible() stays green when the total is wrong. If the spec says a coupon takes 10% off a $40.00 cart, the test asserts $36.00, exactly. A test that only checks that something appeared is a smoke check and should be named like one.
Fixes that weaken assertions
Asked to repair a red test, the shortest path to green is to check less: set the expected value to whatever the app shows now, swap toHaveText for toContainText, wrap the assertion in a try, add test.skip, raise a timeout or add retries. Each edit turns the run green and leaves the product as it was. A deliberate behavior change is written down first, in a ticket or a spec; if nothing says so, the “fix” may be hiding a bug. Our post on self-healing tests covers what an agent may and may not change.
Hallucinated selectors
A model can write #checkout-submit because many pages have a button like that. Playwright’s best practices recommend locators based on what the user sees, such as roles and labels, over CSS or XPath selectors that break when the DOM changes. Get them by looking: the generator checks selectors against the live page, and MCP or the CLI shows the real roles and names. A locator nobody saw on the real page should not survive review.
Flaky waits
When a test fails on timing, the quick fix is a fixed sleep such as page.waitForTimeout(3000). Playwright already waits for an element to be actionable before each action, and its web first assertions retry until the condition holds or the timeout (5 seconds by default) runs out. A sleep slows every run and can still lose the race on a busy CI machine. Wait instead for what proves the action landed: the response, the URL, the text on screen. Our guide to flaky tests in Playwright covers the other causes.
A Playwright test written the way an agent should write it
The rule under test comes from a spec: a password needs at least 12 characters, and a refused sign-up stays on the form.
import { test, expect } from '@playwright/test';
// Spec SIGNUP-3: a password needs at least 12 characters.
test('sign-up refuses an 11-character password', async ({ page }) => {
await page.goto('/signup'); // baseURL is set under `use` in playwright.config.ts
await page.getByLabel('Email').fill(`new.user.${Date.now()}@example.com`);
await page.getByLabel('Password').fill('elevenchars'); // one below the limit
await page.getByRole('button', { name: 'Create account' }).click();
await expect(page.getByRole('alert')).toHaveText('Password must be at least 12 characters.');
await expect(page, 'a refused sign-up stays on the form').toHaveURL(/\/signup$/);
});
Why this is a test worth accepting from an agent:
- Locators a user would recognize:
getByLabelandgetByRolewith the visible name, read from the real page. - Web first assertions with exact values:
toHaveTextwith the full message from the spec, nottoBeVisibleon any error. - Data on the boundary: eleven characters, one below the limit, so the test fails if the rule drifts either way.
- No sleeps and no retries: Playwright waits for the button to be actionable and retries the assertions.
- A message that says why: the custom message shows in the report, so a reviewer sees the intent.
- Its own data: a fresh email on every run, so parallel tests cannot collide.
And the version to refuse in review:
await page.locator('#root > div > form > button.btn-primary').click();
await page.waitForTimeout(3000);
expect(await page.locator('.error').isVisible()).toBe(true);
It guesses a CSS path, sleeps for three seconds, then checks once that some error is visible, whatever it says.
Guardrails that work
- A person reviews every change. Agents open pull requests; they do not merge them. The reviewer checks that each test proves what its title says, that expected values match the spec and that nothing was dropped. A reviewer agent can do a first pass, but a person approves.
- An automatic check refuses weaker assertions. Compare every changed test with the main branch and fail the build when an assertion is removed, drops from an exact value to a partial match or a presence check, or moves inside a
tryor a condition, or when a skip, a hard wait, a longer timeout or a retry appears. A new expected value passes only when a spec already states it. - Tests are plain code in your repository. They can be read, diffed and reviewed like the rest of your code, and they keep running if you stop using the agents. Markdown plans, the format Playwright’s planner writes, get reviewed the same way.
- Every run happens in CI. Run the suite on every pull request, so an agent’s change is tested like a developer’s; Playwright’s best practices recommend running tests on each commit and pull request. Give the agent a repository token that can only read, and let a job without an agent open the pull request.
How the agentic-qa sample project does it
Our public sample project, agentic-qa, applies these guardrails to a demo shop we built. It shows the method, not a client result.
- Skills and subagents. Each QA workflow is a skill that hands work to subagents with only the tools they need.
test-authorwrites specs and page objects with role and label locators, exact values from the rule documents and a message on everyexpect(ESLint enforces the message).test-reviewercan only read and returnsPASSorNEEDS_REVISION. Explorer agents drive their ownplaywright-clisessions and may not read the app’s source. - Verdict first. The
heal-testsworkflow classifies every failure before anything is edited: product bug, intended change, test defect, environment, flaky or unknown. Only test defects and documented intended changes get healed. A product bug gets a defect report, and its test stays red until the defect is confirmed, then is pinned withtest.fail. - The heal guard.
qa:heal-guardcompares tests and framework code with the base branch. Removed or weakened assertions and any new skip, hard wait, timeout or retry are never allowed. A changed expected value needs a rule, feature or card that states the new behavior and changed after the test did, or a person’s quoted confirmation. A diff of the app’s code never counts, “since a code change is also what a bug looks like.” - CI split. The headless runner uses the Claude Agent SDK with a spend cap, a turn cap, preapproved tools and a
PreToolUsehook that limits what it may write. The agent job’s GitHub token can only read; a separate publish job with no agent reruns the guards on the patch and opens the pull request.
How we run this loop
We run the same loop as a service. Claude agents, built with the Claude Agent SDK, plan, write, run and maintain Playwright tests in your repository, and the tests run in your own CI on every pull request. A QA engineer reviews every change before it is merged, looks at every failure before it becomes a bug report or a test fix, and signs a Go or No-Go call before each release. Shipping stays your decision. The agentic QA page explains how the service is set up.
Sources
- Agents (Playwright Test Agents), playwright.dev
- Playwright release v1.56.0, GitHub
- Playwright MCP, playwright.dev
- Coding agents, playwright.dev
- Best practices, playwright.dev
- Assertions, playwright.dev
- Writing tests, playwright.dev
- Web server, playwright.dev
- Agent SDK overview, Anthropic
- How the agent loop works, Anthropic
Researched and drafted with Claude, Anthropic's AI model, and checked against the sources it links. Spotted an error? Tell us and we will correct it.