Pick one driver, not three
Playwright, Selenium, and Puppeteer all expose click-shaped tools. Connecting two
at once is the single most common cause of an agent grabbing the wrong tool mid-flow. Chrome
DevTools MCP is the exception — it complements Playwright rather than competing, because its
tools are named for observation (list_network_requests, performance_start_trace)
rather than interaction.
01
Playwright MCP
The default pick. Drives Chromium, Firefox and WebKit through
structured accessibility snapshots instead of pixels or brittle CSS selectors.
Official · Microsoft
npx @playwright/mcp
No credentials
Core stack
Why a tester should care
Traditional agent-driven browser automation took a screenshot and asked a vision model where to
click. That is slow, expensive, and non-deterministic. Playwright MCP instead hands the model an
accessibility snapshot: a structured tree of roles, names, and stable element
references. The model then says "click the button with ref e17", which is exact.
The QA consequence is significant. Because the agent is reasoning over roles and accessible names,
the specs it writes naturally reach for getByRole('button', { name: 'Submit claim' })
rather than div.css-1x7k9 > span:nth-child(3). You get accessible-by-construction
locators for free, and a spec that survives a CSS refactor.
Second-order benefit: if the agent can't find an element in the accessibility tree, that is
itself a finding. An unlabelled icon button that the agent cannot address is an unlabelled icon
button your screen-reader users cannot address either.
| Tool group | Representative tools | What you use it for |
| Perception | browser_snapshot, browser_take_screenshot | Get the a11y tree (cheap, precise) or a picture (expensive, for visual bugs) |
| Navigation | browser_navigate, browser_navigate_back, browser_tabs | Move through the app, multi-tab flows, OAuth popups |
| Interaction | browser_click, browser_type, browser_select_option, browser_hover, browser_drag, browser_press_key | Drive the flow; keyboard tools also give you tab-order testing |
| Files & dialogs | browser_file_upload, browser_handle_dialog | Upload flows, native confirm/alert handling |
| Diagnostics | browser_console_messages, browser_network_requests | Catch the silent 500 behind a "nothing happened" bug |
| Escape hatch | browser_evaluate | Arbitrary JS in page context — powerful, and the one to restrict |
| Waiting | browser_wait_for | Wait on text appearing/disappearing rather than sleeping |
Wire it up
Claude Code, one line:
shell
claude mcp add playwright -- npx -y @playwright/mcp@latest --isolated --browser chromium
Or as JSON config, for clients that use an mcpServers block:
mcp config — headed local debugging
{
"mcpServers": {
"playwright": {
"command": "npx",
"args": [
"-y", "@playwright/mcp@latest",
"--browser", "chromium",
"--viewport-size", "1440,900",
"--isolated",
"--allowed-origins", "https://staging.example.com;https://api.staging.example.com",
"--save-trace",
"--output-dir", "./artifacts/playwright-mcp"
]
}
}
}
- --isolated
- Fresh in-memory profile per session. Use it: a shared profile leaks login state between runs and produces phantom passes.
- --storage-state
- The deliberate opposite — load a saved auth state so the agent starts logged in. Point it at a JSON file your normal auth setup already produces.
- --allowed-origins
- Origin allowlist. The strongest single guardrail here: the agent physically cannot navigate to production or to an attacker-controlled link found on a page.
- --device
- Device emulation, e.g.
"iPhone 15". Cheap responsive checks without a device lab.
- --save-trace
- Writes a Playwright trace per session. This is your evidence artifact — attach it to the defect.
- --headless
- For CI. Keep it headed locally: watching the agent drive is how you catch it "succeeding" on the wrong element.
Workflows that actually pay off
1 · Explore-then-codify
The highest-value pattern, and the one most teams skip. Don't ask for a spec first — ask for exploration first.
Navigate to the staging checkout flow and complete a purchase with the test card
4242…. Take a snapshot at each step. Then list every element you interacted with,
the locator you'd use for it, and flag any that had no accessible name.
Now write that flow as a Playwright test in tests/checkout.spec.ts, matching the
fixtures and page-object style already used in tests/login.spec.ts. Run it. If it
fails, fix the test — not the app — and show me the diff.
The second prompt matters more than the first. "Run it, and iterate until green" is what
separates an agent from a code generator: it closes the loop itself, and you review a spec that
has already passed rather than one that merely looks plausible.
2 · Reproducing a bug report into a regression test
Here's the bug report: "On mobile, applying a promo code clears the cart."
Reproduce it with the iPhone 15 device profile against staging. Capture console messages and
network requests around the failure. If it reproduces, write a failing Playwright test that
encodes the correct behaviour and save it under tests/regression/.
You end up with three artifacts from one prompt: a confirmed repro, a network/console trace
that tells the developer why, and the regression test that stops it coming back.
3 · Accessibility sweep as a side effect
Snapshot every page reachable from the main nav. For each, list interactive elements
with a missing or ambiguous accessible name, and any heading-level skips. Output as a markdown
table sorted by page.
4 · Cross-device regression
Run tests/checkout.spec.ts at 1440×900, 768×1024, and the iPhone 15 profile.
Screenshot the order-summary panel in each and tell me where the layout diverges.
Gotchas
- Snapshot size on heavy pages. A dense data grid produces a very large
accessibility tree and can eat the context window in one call. Navigate to a narrower view, or
ask the agent to snapshot after applying a filter.
- Generated specs drift toward the happy path. Agents write the flow they just
walked. Negative cases, boundary values, and error states still need you to ask for them
explicitly — and they are where the bugs live.
browser_evaluate is a hole in your guardrails. Arbitrary JS in page
context can set localStorage, call internal APIs, or bypass the UI entirely. Convenient for
setup, but a test that reaches state via evaluate is not testing the UI. Watch for it
in generated code.
- Non-determinism between runs. The same prompt won't produce the same click
sequence twice. Conversational driving is for exploration; the committed spec is the regression
asset. Never wire "ask the agent to test checkout" into CI.
- Page content is untrusted input. If the agent reads a product review, a support
ticket, or a PDF rendered in the browser, that text is data — not instructions. Prompt-injection
through user-generated content is a live risk for any browser-driving agent, and it matters more
when the same agent also has write access to Jira or the database.
- First run downloads browsers. Budget for it in CI images, or pre-bake with
npx playwright install --with-deps chromium.
Verdict
Install this one first. If you only ever adopt a single MCP server for testing, it is this one,
and the ROI shows up in week one on spec authoring and bug reproduction.
02
Gives the agent the DevTools panels: console, network,
performance traces, and CPU/network emulation.
Official · Chrome DevTools team
npx chrome-devtools-mcp
Chromium only
Why a tester should care
Playwright tells you the test failed. Chrome DevTools MCP tells you the page fired a request that
404'd, blocked the main thread for 1.8 seconds, and shifted layout twice after first paint.
It converts "the page feels slow" — the least actionable bug report in existence — into a trace
with named insights and a number attached.
For a QA-to-AI engineer this is also the most useful server for root-causing your own flakes.
A test that intermittently fails on a click is very often a test racing a slow XHR. The network
panel shows you that in one call.
| Tool group | Representative tools | QA use |
| Performance | performance_start_trace, performance_stop_trace, performance_analyze_insight | Core Web Vitals, long tasks, render-blocking resources — with the trace as evidence |
| Network | list_network_requests, get_network_request | Status codes, payloads, timing, waterfall order |
| Console | list_console_messages | Silent JS errors that never surface in the UI |
| Emulation | emulate_cpu, emulate_network, resize_page | Reproduce the bug that only happens on a mid-tier Android on 3G |
| Interaction | navigate_page, click, fill, take_snapshot | Enough driving to reach the state you want to measure |
Wire it up
shell
claude mcp add chrome-devtools -- npx -y chrome-devtools-mcp@latest
mcp config — attach to an already-running Chrome
{
"mcpServers": {
"chrome-devtools": {
"command": "npx",
"args": [
"-y", "chrome-devtools-mcp@latest",
"--headless=false",
"--isolated=true"
]
}
}
}
It can also attach to an existing Chrome instance started with a remote debugging port, which is
how you point it at a session you've already logged into by hand — useful for SSO-gated apps where
scripting the login is more trouble than it's worth.
Workflows
1 · Performance budget as an acceptance criterion
Record a performance trace of a cold load of the staging dashboard on a 4× CPU
slowdown and Slow 4G. Report LCP, CLS, and TBT, list the top three insights, and name the specific
resources responsible. Then compare against production and tell me if the release regressed.
The comparison is the part that makes it a test rather than a measurement. A single LCP number is
trivia; LCP versus the previous release is a pass/fail.
2 · The "nothing happened" bug
Click Save on the profile form, then show me every network request it triggered with
status and response body, plus any console errors. The UI shows no feedback at all.
3 · Flake root-cause
This test fails ~1 in 8 runs on the click after login. Load the page, list network
requests with timings, and tell me which request the button depends on and how variable its
response time is.
4 · Third-party weight audit
List all network requests by third-party domain, with transfer size and blocking
time. Which ones are render-blocking?
Gotchas
- Traces are big. Ask for analysed insights, not raw trace dumps, or you'll fill
the context window with JSON.
- Numbers move. Local performance measurements vary with whatever else your
machine is doing. Treat single runs as directional; only trust deltas measured back to back
under the same emulation settings.
- Chromium only. No Safari or Firefox story here. Pair with BrowserStack for
real cross-browser performance.
- Overlaps with Playwright. Both can click and navigate. If you run both, tell
the agent explicitly which one owns driving ("use Playwright to reach the state, DevTools only
to measure") or it will thrash between them.
Verdict
Add second, right after Playwright. It's read-only, needs no credentials, and turns vague
performance complaints into filed, numbered defects.
03
Selenium MCP
WebDriver automation for the suite you already have.
Built by Angie Jones; the pragmatic choice when migration isn't on the roadmap.
npx @angiejones/mcp-selenium
Grid-compatible
Why a tester should care
Be honest about the situation this solves. If you have 4,000 Selenium tests in Java, a Grid in
your own data centre, and a compliance process that signed off on that stack, "just migrate to
Playwright" is not advice — it's a two-year project. Selenium MCP lets an agent work
inside that world: prototype a flow, confirm locator behaviour on a real WebDriver
session, and hand you something that fits the existing page objects.
There's a second, less obvious use: the agent as a locator archaeologist. Point it at a page and
ask which of your existing XPaths still resolve. Legacy suites accumulate dead selectors, and
finding them by hand is miserable work.
Deliberately WebDriver-shaped, which is the point — the vocabulary matches what your existing suite
already speaks: start_browser, navigate, find_element,
click_element, send_keys, get_element_text,
hover, double_click, right_click, drag_and_drop,
press_key, upload_file, take_screenshot,
close_session.
Locator strategies are the familiar set: id, css, xpath,
name, tag, class. Note what's missing versus Playwright —
there's no accessibility snapshot, so the agent is working from screenshots and its own guesses at
selectors rather than a structured tree. That is the core trade-off.
Wire it up
shell
claude mcp add selenium -- npx -y @angiejones/mcp-selenium
mcp config
{
"mcpServers": {
"selenium": {
"command": "npx",
"args": ["-y", "@angiejones/mcp-selenium"]
}
}
}
Browser drivers must be resolvable on the host — modern Selenium handles most of this via Selenium
Manager, but a corporate proxy will break that silently. Test start_browser before
assuming the install worked. For Grid, the agent starts a session against your hub URL, so the
same network access rules as your normal suite apply.
Workflows
1 · Locator audit
Here are the 30 XPath locators from CheckoutPage.java. Open staging,
try each one, and report which resolve, which resolve to more than one element, and which fail.
For the failures, suggest a replacement using a stable attribute.
2 · Prototype before you write Java
Walk the guest-checkout flow on staging with Selenium. Record the exact locators
and actions that worked, then output them as a Java page object matching the structure of
LoginPage.java.
3 · Migration triage
Read src/test/java/checkout/. Which tests depend on implicit waits or
Thread.sleep? Rank them by how likely they are to be flaky and estimate migration
effort to Playwright.
Gotchas
- No accessibility tree means guessier locators. The agent is inferring selectors
rather than reading references. Review everything it produces; a locator that worked once may
have matched by accident.
- Session leakage. Agents forget to call
close_session. Orphaned
browsers accumulate, and on a shared Grid that's someone else's outage. Check for stray sessions
after long conversations.
- Community maintenance. Check the repo's commit activity before committing a
team to it. This is not a vendor-supported product with an SLA.
- Don't run alongside Playwright MCP. Tool collision is near-certain.
Verdict
Right tool for a real constraint. If you have a choice and no legacy suite, choose Playwright —
the accessibility-tree difference is not a small one. If you don't have a choice, this is a
genuinely useful bridge and a good way to build the migration business case with data.
04
Puppeteer MCP
Lightweight Chromium driving for headless verification
and scraping-adjacent tasks.
Reference server archived
Chromium only
Read this before installing
The original @modelcontextprotocol/server-puppeteer reference implementation was
moved to the archived section of the MCP servers repository. Several community forks continue it
under various names and quality levels. Nothing about Puppeteer MCP is uniquely capable versus
Playwright MCP — if you are choosing today with no existing Puppeteer code, choose Playwright and
skip this section.
Where it still fits
Three honest cases. First, you already have a Puppeteer codebase and want the agent to speak the
same API. Second, you want a very small footprint for a scheduled headless check — "does the login
page render and return 200" — where Playwright's browser matrix is overkill. Third, content
extraction: pulling rendered HTML out of a JS-heavy page into the agent's context, where Fetch MCP
would only get you the empty shell.
That third case is real and under-appreciated. If you're building test data or a RAG corpus from
pages that render client-side, a headless browser is the only thing that works.
Small by design: puppeteer_navigate, puppeteer_screenshot,
puppeteer_click, puppeteer_fill, puppeteer_select,
puppeteer_hover, puppeteer_evaluate. CSS selectors throughout — no
accessibility-tree reasoning, no built-in tracing, no network panel.
Wire it up
mcp config — verify the package first
{
"mcpServers": {
"puppeteer": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-puppeteer"],
"env": {
"PUPPETEER_LAUNCH_OPTIONS": "{\"headless\": true, \"args\": [\"--no-sandbox\"]}",
"ALLOW_DANGEROUS": "false"
}
}
}
}
--no-sandbox
Common in containers, and it disables a Chromium security boundary. If the agent then navigates
to arbitrary external URLs, you've combined "runs untrusted web content" with "no sandbox". Keep
this flag inside a container you'd be willing to throw away, and don't set
ALLOW_DANGEROUS=true because a tool error suggested it.
Workflows
1 · Post-deploy smoke
For each of these 12 URLs, navigate, wait for network idle, screenshot, and report
any that returned non-200, showed an error boundary, or rendered an empty main region.
2 · Rendered-content extraction for test data
Open each product page in urls.txt, extract the rendered title, price,
and stock status, and write them to fixtures/products.json.
Gotchas
- Maintenance is on you. Archived reference, forks of varying quality. Pin a
version and read the source before it touches anything sensitive.
- Scraping has rules. Respect robots.txt, terms of service, and rate limits. "The
agent did it" is not a defence.
- No trace, no network panel. When something fails you get a screenshot and a
shrug.
Verdict
Skip unless you have existing Puppeteer code or a specific need for a minimal headless renderer.
Playwright MCP does everything here, better, with a maintained upstream.
05
Maestro MCP
Mobile UI automation for Android and iOS, exposed through
a thin gateway over the Maestro CLI.
Vendor · mobile.dev
Ships with Maestro CLI
Android + iOS
Why a tester should care
Mobile automation has historically been the worst developer experience in test engineering:
Appium capability matrices, driver version drift, and a feedback loop measured in minutes. Maestro's
pitch is declarative YAML flows with built-in waiting — no explicit sleeps, no
WebDriverWait ceremony. The MCP server puts an agent in front of that.
The gateway architecture matters practically: the MCP server is a thin layer, and the actual
device interaction is the same Maestro CLI your CI already runs. Flows the agent authors are
ordinary .yaml files you commit — no separate agent-only execution path. That is
exactly the property you want, and it's what makes generated mobile tests trustworthy.
| Group | Representative tools | QA use |
| Device | start_device, list_devices, launch_app, stop_app | Boot a simulator/emulator, install and launch the build |
| Perception | take_screenshot, inspect_view_hierarchy | The hierarchy is the mobile equivalent of the a11y tree — prefer it over screenshots |
| Interaction | tap_on, input_text, back, swipe | Drive the flow |
| Flows | run_flow, run_flow_files, check_flow_syntax | Execute inline YAML or committed flow files; validate before running |
Wire it up
Install the Maestro CLI first — the MCP server is a subcommand of it, not a separate package.
shell — install Maestro (macOS/Linux)
curl -fsSL "https://get.maestro.mobile.dev" | bash
maestro --version
mcp config
{
"mcpServers": {
"maestro": {
"command": "maestro",
"args": ["mcp"]
}
}
}
Prerequisites are the usual mobile ones and they are the real install cost: Android SDK plus a
configured emulator (or a device with USB debugging), and for iOS, Xcode with a booted simulator on
macOS. Windows users testing iOS still need a Mac somewhere — MCP does not change that.
Workflows
1 · Author a flow by doing it
Launch the app on the Pixel 8 emulator, sign in as qa+demo@example.com,
and add an item to the basket. Inspect the view hierarchy at each step and prefer accessibility
ids over text. Then write it as .maestro/basket-add.yaml, check the syntax, and run it
twice to confirm it's stable.
"Run it twice" is not padding. A mobile flow that passes once and fails the second time is the
most common failure mode, usually an animation or a permission dialog on first launch.
2 · Permission and interrupt handling
Run the onboarding flow on a freshly reset emulator. Note every system dialog —
notifications, location, tracking — and add explicit handling for each to the flow so it works on
both a clean install and an upgrade.
3 · Cross-platform parity
Run .maestro/checkout.yaml on both the Android emulator and the iOS
simulator. Where the flow needed platform-specific selectors, list them and tell me whether the
difference is a real UX divergence or just an accessibility-id gap.
That last question is the valuable one — parity testing frequently surfaces accessibility
labelling that one platform team did and the other didn't.
Gotchas
- Environment setup dominates. Ninety percent of the pain is SDKs, emulators, and
signing. The MCP layer is the easy part; budget accordingly.
- Emulators are not devices. They won't reproduce thermal throttling, real network
conditions, biometric hardware, or manufacturer skins. Pair with BrowserStack for the real-device
matrix on anything user-facing.
- Screenshot-driven tapping is fragile. Push the agent to
inspect_view_hierarchy and address elements by accessibility id. Tapping coordinates
derived from a screenshot breaks on the next device size.
- iOS needs macOS. No way around it.
Verdict
The best available answer for conversational mobile testing, mostly because the generated
artifact is a plain committed YAML flow rather than an agent-only script. Adopt if mobile is in
scope; the flows outlive the conversation.
How to choose between them
| If your situation is… | Use | Because |
| Greenfield web UI automation | Playwright | Accessibility-tree locators, tracing, cross-browser, maintained by Microsoft |
| "It's slow" / "it errors silently" | Chrome DevTools | Only one with traces, network waterfall, and CPU/network emulation |
| Large existing WebDriver suite | Selenium | Speaks your suite's vocabulary; no migration required |
| Minimal headless checks or rendered-page extraction | Puppeteer (or just Playwright) | Smaller surface — but check fork maintenance |
| Native Android / iOS | Maestro | Declarative flows with implicit waiting; generated flows are committable |
| Real devices, many browsers, no lab | BrowserStack → | See Test management |
The pairing that works
Playwright + Chrome DevTools, and nothing else in this category. Playwright drives
and asserts; DevTools observes and explains. Together they cover authoring, execution, debugging,
and performance without a single credential between them — which also makes this the safest pair to
hand a junior engineer.