Why CLI — and how to drive it

See why a coding agent reaches for `@playwright/cli` over MCP, and run your first agent-driven browser session.

Playwright ships two ways to connect to the agent world. And the guidance from the Playwright team is pretty clear:

If you're using a coding agent or if your tasks are coding, testing, you're using Claude, GitHub Copilot, you probably want to use CLI.

On the other hand, if you are authoring an agentic loop that is performing certain tasks for your scenarios, MCP is still the way to go.

— Pavel Feldman (Creator of Playwright), Playwright CLI vs MCP — a new tool for your coding agent

For coding agents (Claude Code, Copilot, Cursor, etc.) reach for @playwright/cli. Boom! 💥

The number that decides it is token usage. The same task (open playwright.dev, search locators, screenshot the docs in four languages) consumes way fewer tokens using the CLI.

Coding agents
@playwright/cli
tokens used

Snapshots and screenshots save to disk. The agent reads them only when it needs to.

$ playwright-cli open playwright.dev
# snapshot saved to
# .playwright-cli/page-*.yml
Skill-based, headless by default, lean on context.
Agentic loops
@playwright/mcp
tokens used

Every tool result returns into context. Useful when you want the LLM to reason over each step.

await browser_navigate({
  url: "playwright.dev"
});
// returns the full snapshot
// into the model's context
MCP-protocol tools, headed by default, heavy on context.

For the official example, that's roughly 4× the token cost on MCP, because every snapshot and screenshot it returns lands in your model's context whether the agent needs it or not. The CLI saves both to disk and lets the agent decide what to actually read.

That's why we'll focus on the CLI and how you can use it to automate your daily Playwright tasks.

Three pieces make this work, each minimal:

  • The skill teaches your agent the CLI surface. The agent reads commands and conventions from disk, not from your prompt.
  • The CLI is the thin shell between agent and browser. Each command does one thing and echoes the equivalent page.* call.
  • The browser holds session state. It stays alive across commands so the agent doesn't have to track cookies, URLs, or DOM in its own context.

Pull any of the three out and the whole thing collapses. The next two sections show the system in motion — first by hand, then with an agent driving.

Understand the agent flow by doing it

You installed @playwright/cli and the skill in the prep lesson.

Note

This lesson assumes @playwright/cli 0.1.11 or newer. Run playwright-cli --version to confirm — if you're behind, npm install -g @playwright/cli@latest and re-run playwright-cli install in your project.

Open a terminal in your workshop project and point it at the workshop store:

$ playwright-cli open https://www.playwright-workshop.online/

The CLI prints a few lines: the browser pid, the equivalent page.goto() call, the page URL and title, and a path to a snapshot file on disk. Almost no tokens at all.

If you (or your agent) want to understand the page structure, ask the CLI for a snapshot — explicit snapshot calls return the YAML inline rather than saving to disk:

$ playwright-cli snapshot

The YAML lists every interactive element with a stable ref like e3, e15, e21 — that's how the CLI addresses elements without dumping the full DOM into your context. Pick a ref from the snapshot and click it:

# e.g. a product link's ref
$ playwright-cli click e34

The CLI returns the action it ran as Playwright code, the resulting page context and snapshot.

Every CLI command echoes the equivalent page.* call. Drive a flow once and you have the body of a test, line by line. Close the browser when you're done:

$ playwright-cli close
Note

Other ways to target elements

If you'd rather not snapshot first, you can pass any of these instead of a ref. The workshop prefers user-first locators (role, label, text) — they describe what the user sees and survive a redesign:

# Role locator (preferred)
playwright-cli click "getByRole('button', { name: 'Add to cart' })"

# Test id
playwright-cli click "getByTestId('checkout-cta')"

# CSS selector (last resort)
playwright-cli click "#submit-btn"

fill, hover, press_key, and the other interaction commands accept the same forms. Refs are the most token-efficient option though — the snapshot already gave the agent the ref, so it doesn't have to construct a selector at all.


Hand the keyboard to your agent

Now skip ahead — let the agent drive. Open your coding agent in your workshop project and prompt:

Open https://www.playwright-workshop.online/, search for snowboard, click the first product result. Use playwright-cli and run headed so I can watch.

Prompt for your coding agent

Watch what happens.

1. The agent reads the skill. First thing on the wire is a tool call to read .claude/skills/playwright-cli/SKILL.md. That's where it learns open, snapshot, click, fill, close, and the ref system. No model-side guessing about Playwright's API.

Tip

The skill bundles task-specific reference docs

The playwright-cli skill covers the CLI surface, but also ships additional agent references/. The agent pulls these in on-demand when the task calls for it:

  • Running and debugging Playwright testsreferences/playwright-tests.md
  • Test generation from a recorded session → references/test-generation.md
  • Much more ...

Browse .claude/skills/playwright-cli/references/ to see the full set and optimize your prompt so that these agent docs are picked up.

2. The agent opens the browser. It tacks on --headed because the prompt asked. The flag isn't in the skill's quick reference — agents pick it up from CLI conventions or playwright-cli open --help.

playwright-cli open https://www.playwright-workshop.online/ --headed
### Browser `default` opened with pid 43712.
### Ran Playwright code
```js
await page.goto('https://www.playwright-workshop.online');
```
### Page
- Page URL: https://www.playwright-workshop.online
- Page Title: PWT Workshop
### Snapshot
- [Snapshot](.playwright-cli/page-2026-05-04T10-12-44-001Z.yml)

The browser is now alive. The CLI returned a handful of lines. The snapshot landed on disk — not in the agent's context.

3. The agent snapshots, fills, and submits. playwright-cli snapshot returns the YAML inline:

playwright-cli snapshot
### Page
- Page URL: https://www.playwright-workshop.online
- Page Title: PWT Workshop
### Snapshot
```yaml
- generic [active] [ref=e1]:
  - navigation "Main" [ref=e2]:
    - ...
    - searchbox "Search products" [ref=e23]
    - ...
```

The agent scans for searchbox "Search products" [ref=e23] and runs:

playwright-cli fill e23 snowboard
### Ran Playwright code
```js
await page.getByRole('searchbox', { name: 'Search products' }).fill('snowboard');
```
### Page
- Page URL: https://www.playwright-workshop.online
- Page Title: PWT Workshop
### Snapshot
- [Snapshot](.playwright-cli/page-2026-05-04T10-12-47-002Z.yml)
playwright-cli press_key Enter
### Ran Playwright code
```js
await page.keyboard.press('Enter');
```
### Page
- Page URL: https://www.playwright-workshop.online/search?q=snowboard
- Page Title: Search results | PWT Workshop
### Snapshot
- [Snapshot](.playwright-cli/page-2026-05-04T10-12-50-117Z.yml)

4. The agent snapshots again and clicks the first result. Another snapshot surfaces link "All Mountain Snowboard" [ref=e47].

playwright-cli click e47
### Ran Playwright code
```js
await page.getByRole('link', { name: 'All Mountain Snowboard' }).click();
```
### Page
- Page URL: https://www.playwright-workshop.online/product/all-mountain-snowboard
- Page Title: All Mountain Snowboard | PWT Workshop
### Snapshot
- [Snapshot](.playwright-cli/page-2026-05-04T10-12-55-203Z.yml)

One skill read, six CLI commands, two snapshot calls into context. The other four commands each emitted a snapshot file the agent never opened. The browser stayed alive across all of it — which is exactly why the agent didn't choke on context.


Hands on

Have fun!

Exercise 1 of 1

Send the agent through the full checkout

Could you just prompt the agent to complete the entire checkout flow for you? Could it be a conversation? Let's find out!

A few things to play with:

  • Start with a one-liner. When the agent gets stuck, nudge it instead of rewriting the prompt — does the back-and-forth feel natural?
  • After it finishes, open .playwright-cli/ and count the snapshot files. Each one is structure the agent could have inlined into context but skipped.
  • Watch the commands it strings together. Each one echoes a page.* line — that transcript is the body of a checkout test, written for free.

Tell you agent to close the browser (playwright-cli close) when you're done.