How can a coding agent test my web app in a real browser by itself?
Give the agent a browser it can drive: a Playwright MCP server or the Playwright CLI for quick checks, Playwright or Puppeteer tests for checks you keep, or a MAO browser portal wired to the agent when you want to watch it work through the page beside its terminal.
- Playwright MCP acts on the accessibility tree, not on pixels
- Playwright and Puppeteer run headless unless you ask otherwise
- A MAO portal answers only to the agents wired to it
A coding agent can test your web app by itself once it has a browser it can drive. Add a browser automation server such as Playwright MCP, or the Playwright CLI, for checks the agent makes while it works. Have the agent write and run Playwright or Puppeteer tests for checks you want to keep. In MAO, wire the agent to a browser portal and it drives a live Chromium page with mao portal on the same canvas as its terminal, where you can watch it.
What does an agent need to test a web app by itself?
It needs three things: a way to open and operate a page, a way to read what the page shows, and a way to prove the result. Every option below gives it all three, in different forms.
- Operate: navigate to a URL, click, fill fields, press keys.
- Read: the page's structure (the accessibility tree), its text, or a screenshot.
- Prove: a screenshot or a passing test it can show you.
What changes between the options is where the browser runs, whether you can see it, and whether the check survives the conversation.
How does browser automation over MCP work?
The agent calls browser tools from an MCP server. Playwright MCP is "a Model Context Protocol (MCP) server that provides browser automation capabilities using Playwright", and it works from the page's accessibility tree rather than from pixels.
Add it to Claude Code with one command (it needs Node.js 18 or newer):
claude mcp add playwright npx @playwright/mcp@latestThe agent then has tools such as browser_navigate, browser_snapshot (the page's accessibility snapshot), browser_click, browser_type, browser_fill_form and browser_take_screenshot. Its README notes that the agent cannot act on a screenshot; it acts on the snapshot. The browser opens in a visible window by default, and --headless hides it. --device "iPhone 15" emulates a phone.
By default it keeps a persistent profile, so logged-in state carries over between sessions; --isolated keeps the profile in memory and discards it when the browser closes.
The same README suggests that a coding agent may do better with the Playwright CLI and its skills, because CLI calls use fewer tokens. You install it with npm install -g @playwright/cli@latest and give your agent its skills with playwright-cli install --skills. It runs headless unless you open the browser with --headed.
How does an agent write and run its own browser tests?
Ask the agent to write the check as a test and run it. A test stays in the repository, so the next change gets the same check, and CI can run it too.
With Playwright Test, the agent sets up the project with npm init playwright@latest and runs the tests with npx playwright test. Tests run headless by default; --headed shows the browser and --ui opens Playwright's UI mode. npx playwright show-report opens the HTML report. Playwright runs the tests in Chromium, WebKit and Firefox.
A test the agent might write for a checkout page:
import { test, expect } from '@playwright/test';
test('checkout shows the confirmation', async ({ page }) => {
await page.goto('http://localhost:3000/checkout');
await page.getByRole('textbox', { name: 'Email' }).fill('test@example.com');
await page.getByRole('button', { name: 'Pay' }).click();
await expect(page.getByRole('heading', { name: 'Thank you' })).toBeVisible();
await page.screenshot({ path: 'checkout.png', fullPage: true });
});Puppeteer is the other common library: "a JavaScript library which provides a high-level API to control Chrome or Firefox over the DevTools Protocol" or WebDriver BiDi. npm i puppeteer downloads a compatible Chrome during installation, and it runs headless by default. The agent writes a script against it the same way.
How does a MAO browser portal work?
A browser portal is a live Chromium page that sits on the MAO canvas beside your terminals. Wire an agent's terminal to it and that agent drives the page with mao portal; no other agent can.
- Choose New portal, place it on the canvas and enter your app's address in its address bar.
- Connect the agent's terminal to the portal.
- The agent runs
mao portal navigate <portal> <url>to open a page. - It inspects the page with
info,screenshotorsnapshot, and interacts withclick,fill,type,key,scroll,textorevaluate. - It runs
mao portal close <portal>when it is done.
Every command checks that the wire is still there, so removing the wire takes the browser back. The commands drive a portal that is already open; they do not launch a browser by themselves. The portal needs a Chromium browser, such as Chrome, installed on your machine, and MAO looks for one in Homebrew, /Applications and the usual Windows and Linux install locations.
A few details matter when the agent is testing a form:
- A password or one-time-code field shows the agent only whether it is filled, in snapshots and in
mao portal textandmao portal html. fillchecks what the field holds afterwards, and types the value key by key when a field does not accept it at once.- A click on an element that is hidden, off screen or covered reports an error instead of success.
- Only the active tab is kept when the portal is reopened. Browser profile data, popups and downloads are not included.
How do the options compare?
How does the agent test a change to a checkout page?
Here is the whole loop with a MAO browser portal: the agent changes the checkout page, opens it, fills the form, takes a screenshot and tells you what happened.
- Start your app's dev server.
- Place a browser portal next to the agent's terminal, open the checkout page's local address in it, and connect the agent's terminal to the portal.
- Give the agent the task and the check in one prompt (below).
- The agent edits the code, then opens the page in the portal with
mao portal navigate. - It runs
mao portal snapshotto find the form fields and the pay button, fills each field withfilland presses the button withclick. - It runs
mao portal screenshoton the result, and reads the page withmao portal textif it needs the exact error or confirmation message. - It reports what it changed, what the page showed, and the screenshot. If something broke, it fixes the code and runs the check again.
The prompt:
Move the coupon field above the card details on the checkout page.
Then open the checkout page in the portal you are connected to, fill
the form with test data, pay, and take a screenshot of the result.
Tell me what you changed and whether checkout still reaches the
confirmation screen.The commands the agent runs to open, read and capture the page:
mao portal navigate <portal> http://localhost:3000/checkout
mao portal snapshot <portal>
mao portal screenshot <portal>You watch it happen in the portal on the canvas. With Playwright MCP the loop is the same, with browser_navigate, browser_snapshot, browser_fill_form, browser_click and browser_take_screenshot in place of the mao portal commands. Once the flow works, ask the agent to turn it into a Playwright test so the check stays.
When do you need a device portal instead?
Use a device portal when the thing you are testing runs on a phone, not in a desktop browser. Playwright and Puppeteer emulate a phone's screen size, user agent and (for Playwright) touch inside a desktop browser engine; a MAO device portal holds a real Android emulator, an Android phone attached to your computer, or an iOS Simulator.
- Android: choose New device portal and pick an emulator or attached device. MAO needs the Android SDK tools or an attached device, and finds
adband the emulator throughANDROID_HOME, Homebrew and the SDK's default folders. - iOS: on a Mac with Xcode, a device portal shows an existing iOS Simulator. You create and delete simulators in Xcode. Agents can read the app's accessibility tree when they launch the app with
mao portal launch.
The wired agent inspects the device with info, screenshot or snapshot, and controls it with click, swipe, type, button, launch, terminate, boot and shutdown. The device screen streams while the portal is on screen; an off-screen or collapsed portal stops streaming.
Frequently asked questions
Yes. Add Playwright MCP with claude mcp add playwright npx @playwright/mcp@latest, ask it to write and run Playwright tests, or, in MAO, connect its terminal to a browser portal so it can drive the page with mao portal.
Mostly the structure. Playwright MCP acts on the page's accessibility snapshot and cannot act on a screenshot, and a MAO browser portal's snapshot reads the accessibility tree; both also take screenshots for you to see.
Yes. Playwright MCP opens a visible browser by default, Playwright Test shows the browser with --headed or --ui, and a MAO browser portal shows the page on the canvas next to the agent's terminal.
In a MAO browser portal, no: a password or one-time-code field shows the agent only whether it is filled. Playwright MCP keeps a persistent profile with your logged-in state by default; --isolated keeps the profile in memory and discards it when the browser closes.
Playwright and Puppeteer can emulate a phone's screen size and user agent in a desktop browser. To test on a real Android emulator, an Android phone or an iOS Simulator, wire the agent to a MAO device portal.
Sources
- Playwright MCP README: what Playwright MCP is, the accessibility-tree approach, the Claude Code install command, Node.js 18, the tool names, headed by default,
--headless,--device, the persistent profile and--isolated, and the note on the CLI for coding agents. - Playwright CLI README: the install and skills commands, headless by default and
--headed. - Playwright: Installation:
npm init playwright@latest,npx playwright test,--headed,--ui, the HTML report and the supported browsers. - Playwright: Writing tests:
getByRole,fill,clickand thetoBeVisibleassertion. - Playwright: Screenshots:
page.screenshotandfullPage. - Playwright: Emulation: device emulation of user agent, screen size, viewport and touch.
- Puppeteer: what Puppeteer is, the protocols, headless by default, and the Chrome download on install.
- Puppeteer: page.emulate(): device metrics and user agent emulation.
- Browser and device portals: what portals do, the Chromium requirement and where MAO looks for it.
- Open a browser portal: placing a portal, its controls and what is kept when it reopens.
- Let an agent drive an authorized browser: the
mao portalcommands and the connection check. - Control an Android device portal: Android setup, device commands and streaming.
- View an iOS Simulator portal: the macOS-only iOS Simulator portal.
- MAO 0.11.6 release notes: password and one-time-code masking,
fillchecks and click errors. - MAO 0.8.0 release notes: iOS Simulator support, the accessibility tree on launch, and how MAO finds Chromium and the Android tools.
- MAO 0.11.0 release notes: the agent command is
mao.
Checked on 2026-10-28.
Questions? Ask in the Discord or write to hello@miguer.com.