# 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.

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@latest
```

The 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.

1. Choose **New portal**, place it on the canvas and enter your app's address in its address bar.
2. Connect the agent's terminal to the portal.
3. The agent runs `mao portal navigate <portal> <url>` to open a page.
4. It inspects the page with `info`, `screenshot` or `snapshot`, and interacts with `click`, `fill`, `type`, `key`, `scroll`, `text` or `evaluate`.
5. 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 text` and `mao portal html`.
- `fill` checks 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?

| Option | Setup | What the agent sees | Can you watch it live | Mobile coverage |
| --- | --- | --- | --- | --- |
| Playwright MCP | `claude mcp add`, Node.js 18+ | Accessibility snapshot, screenshots on request | Yes, the browser is visible by default | Device emulation with `--device` |
| Playwright tests | `npm init playwright@latest` in the repo | Test results, reports and the screenshots the test saves | With `--headed` or `--ui`; headless by default | Device emulation: viewport, user agent, touch |
| Puppeteer scripts | `npm i puppeteer` (downloads Chrome) | What the script reads from the page | Headless by default | Device metrics and user agent with `page.emulate()` |
| MAO browser portal | A portal on the canvas, a wire, Chromium installed | Accessibility snapshot, page text, screenshots; password fields masked | Yes, on the canvas beside the agent's terminal | Desktop Chromium; use a device portal for phones |
| MAO device portal | Android SDK tools or a phone; Xcode for iOS | The device screen, screenshots and its accessibility tree | Yes, the screen streams on the canvas | Android emulator or phone, iOS Simulator |

## 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.

1. Start your app's dev server.
2. 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.
3. Give the agent the task and the check in one prompt (below).
4. The agent edits the code, then opens the page in the portal with `mao portal navigate`.
5. It runs `mao portal snapshot` to find the form fields and the pay button, fills each field with `fill` and presses the button with `click`.
6. It runs `mao portal screenshot` on the result, and reads the page with `mao portal text` if it needs the exact error or confirmation message.
7. 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 `adb` and the emulator through `ANDROID_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

### Can Claude Code test my web app in a browser by itself?

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`.

### Does the agent look at screenshots or at the page structure?

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.

### Can I watch the agent test the page?

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.

### Can the agent read my passwords in the browser?

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.

### Can a coding agent test the mobile version of my app?

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](https://github.com/microsoft/playwright-mcp): 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](https://github.com/microsoft/playwright-cli): the install and skills commands, headless by default and `--headed`.
- [Playwright: Installation](https://playwright.dev/docs/intro): `npm init playwright@latest`, `npx playwright test`, `--headed`, `--ui`, the HTML report and the supported browsers.
- [Playwright: Writing tests](https://playwright.dev/docs/writing-tests): `getByRole`, `fill`, `click` and the `toBeVisible` assertion.
- [Playwright: Screenshots](https://playwright.dev/docs/screenshots): `page.screenshot` and `fullPage`.
- [Playwright: Emulation](https://playwright.dev/docs/emulation): device emulation of user agent, screen size, viewport and touch.
- [Puppeteer](https://pptr.dev/): what Puppeteer is, the protocols, headless by default, and the Chrome download on install.
- [Puppeteer: page.emulate()](https://pptr.dev/api/puppeteer.page.emulate): device metrics and user agent emulation.
- [Browser and device portals](https://trymao.dev/portals): what portals do, the Chromium requirement and where MAO looks for it.
- [Open a browser portal](https://trymao.dev/docs/portals-browser-android-ios/browser-portal): placing a portal, its controls and what is kept when it reopens.
- [Let an agent drive an authorized browser](https://trymao.dev/docs/portals-browser-android-ios/browser-portal-cli): the `mao portal` commands and the connection check.
- [Control an Android device portal](https://trymao.dev/docs/portals-browser-android-ios/android-device-portal): Android setup, device commands and streaming.
- [View an iOS Simulator portal](https://trymao.dev/docs/portals-browser-android-ios/ios-simulator-portal): the macOS-only iOS Simulator portal.
- [MAO 0.11.6 release notes](https://trymao.dev/changelog/0.11.6): password and one-time-code masking, `fill` checks and click errors.
- [MAO 0.8.0 release notes](https://trymao.dev/changelog/0.8.0): iOS Simulator support, the accessibility tree on launch, and how MAO finds Chromium and the Android tools.
- [MAO 0.11.0 release notes](https://trymao.dev/changelog/0.11.0): the agent command is `mao`.

Checked on 2026-10-28.

Source: https://trymao.dev/coding-agent-test-web-app-in-browser
