You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 3662ef8
Browse filesBrowse the repository at this point in the historyBrowse files
docs(claude-code): document executablePath wiring for every Playwright consumer (#91)
- README Playwright Strategy: replace the single playwright.config.ts
snippet with a per-consumer wiring matrix (table) covering @playwright/
test, @vitest/browser-playwright (incl. @storybook/addon-vitest),
direct playwright-core / playwright use, and agent-browser. Each gets
its own pasteable snippet. Includes the misleading "npx playwright
install" symptom + correct fix for the vitest case.
- Dockerfile (default target): add AGENT_BROWSER_EXECUTABLE_PATH=/usr/
bin/chromium so agent-browser shares the system chromium. agent-browser
is a Rust CLI with its own env-var family; PLAYWRIGHT_* vars are
ignored. Eliminates auto-detection ambiguity and prevents a Chrome-for-
Testing download on first run (which would fail in firewalled setups
and waste bandwidth elsewhere).
- README skills section: rename "Optional: Claude Code skill for
Playwright" to "Recommended:" — the wiring matrix is now documented,
but the skill encodes how Claude Code drives those entry points.
- sandbox-playwright SKILL.md: add @vitest/browser-playwright and direct
playwright-core to the path-picker table; expand discovery step #1 to
find vitest.config.* alongside playwright.config.* (RTK-safe single-
predicate finds); add the "don't run npx playwright install" red flag
+ fix instruction; broaden the maintainer footer to enumerate the full
env-var family (including AGENT_BROWSER_EXECUTABLE_PATH).
- CI verify: assert AGENT_BROWSER_EXECUTABLE_PATH is set to /usr/bin/
chromium in the published default image (not added to sandbox — agent-
browser isn't installed there).
-**`@playwright/test` CLI** — the project's scripted E2E suite.
16
+
-**`@vitest/browser-playwright`** — vitest browser mode (used by `@storybook/addon-vitest` for storybook interaction tests, by `@vitest/browser` for browser-mode unit tests).
17
+
-**Direct `playwright-core` / `playwright`** — custom scripts that import `chromium` and call `chromium.launch(...)` themselves (CI scrapers, codegen).
16
18
17
19
Don't install anything. Don't spawn a dev server — the user keeps one running on a port they chose.
18
20
19
-
## Two paths — pick the right one
21
+
## Pick the right path
20
22
21
23
| Goal | Use | How |
22
24
|---|---|---|
23
25
| Ad-hoc: "does this button work?", "take a screenshot" |**MCP plugin**|`ToolSearch` → `browser_navigate` → `browser_snapshot` / `browser_click`|
24
-
| Reproducible test suite |**Project CLI**|`bun run test:e2e` (whatever the script in the project's `package.json` is) |
26
+
| Reproducible E2E suite |**`@playwright/test` CLI**|`bun run test:e2e` (whatever the script in the project's `package.json` is) |
27
+
| Storybook interaction tests / vitest browser tests |**`@vitest/browser-playwright`**| The project's `vitest`/`storybook` test script (e.g. `bun run test`, `bun run test-storybook`). Config lives in `vitest.config.ts`, NOT `playwright.config.ts`. |
28
+
| Custom script with `chromium.launch(...)`|**Direct**| Run the script as the project does. Requires `executablePath` wired at the call site (image-set env var). |
25
29
26
30
## Discovery first — never hardcode
27
31
28
32
Every concrete fact in this section must come from the project at invocation time, not from this skill's prose. Stale skills are worse than missing ones.
29
33
30
-
1.**Find the Playwright config.** RTK rewrites `find` and rejects compound predicates (`-not`, `! -path`), so use plain `find` and filter with `grep` — works whether RTK is in the loop or not.
34
+
1.**Find the relevant config.** RTK rewrites `find` and rejects compound predicates (`-not`, `! -path`, also `\( … -o … \)` grouping), so use plain single-predicate`find` and filter with `grep` — works whether RTK is in the loop or not.
- 0 matches → tell the user there's no e2e suite. Stop.
35
-
- 1 match → use it.
36
-
- 2+ matches → ask which one (a monorepo can ship one config per service, e.g. one for the React app, one for the marketing site).
41
+
- 0 `playwright.config.*` matches AND the user asked for E2E → tell them there's no Playwright E2E suite. Stop.
42
+
- 0 `vitest.config.*` matches with `test.browser.enabled = true` AND the user asked for storybook/vitest browser tests → tell them browser-mode isn't configured. Stop.
43
+
- 2+ matches of the same kind → ask which one (a monorepo can ship one config per service, e.g. one for the React app, one for the marketing site).
44
+
- Note: a plain unit-test `vitest.config.*` (no `test.browser.enabled`) doesn't drive Playwright — read the file to confirm browser mode is on before assuming.
37
45
38
46
2.**Read the config.** Note `testDir`, the entries in `projects[]` (with their `testMatch` / `testIgnore` patterns — they are usually regexes, not filename allowlists), and `webServer` (its `url` and `reuseExistingServer`).
39
47
@@ -83,6 +91,7 @@ All tools are registered with the prefix `mcp__plugin_playwright_playwright__`.
83
91
|`bun add @playwright/test` or any "reinstall Playwright" attempt | Already installed. Reinstalling risks an unintended version bump and violates the "no unauthorized installs" rule in the project's CLAUDE.md. |
84
92
| Spawn your own `bun run dev`| The user keeps one running; Playwright's `webServer` reuses it (`reuseExistingServer: true`). A duplicate port-collides. |
85
93
| Hardcode a port (`5173`, `5179`, etc.) in URLs, examples, or docs | Discover it per Step 3. Different projects use different env-var conventions (`APP_PORT`, `PORT`, `VITE_PORT`, …) and the user may override the default. |
94
+
| Run `npx playwright install` when a Vitest/Storybook test fails with `Executable doesn't exist at /home/node/.cache/ms-playwright/...`| The image deliberately ships system Chromium at `/usr/bin/chromium` and sets `PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1`. The fix is to wire `launchOptions.executablePath` in `vitest.config.ts` (`provider: playwright({ launchOptions: { executablePath: process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH } })`), not to download a Playwright-managed binary. |
86
95
| Call `mcp__plugin_playwright_playwright__browser_*` without `ToolSearch` first | Deferred tools — raw invocation returns `InputValidationError` because the parameter schema hasn't been loaded. |
87
96
| Navigate to non-localhost, non-GitHub, non-npm URLs and expect them to work | The devcontainer firewall (see `.claude/rules/devcontainer.md`) blocks arbitrary external domains. Localhost is fine. |
88
97
| Chain actions without re-observing | Page state is implicit. Re-`browser_snapshot` or `browser_wait_for` between interactions. |
@@ -92,14 +101,22 @@ All tools are registered with the prefix `mcp__plugin_playwright_playwright__`.
92
101
93
102
- "Playwright isn't available in this sandbox / devcontainer"
94
103
- "I need to install Playwright first"
104
+
- "Let me run `npx playwright install`" (the bundled binary is deliberately not used; wire `launchOptions.executablePath` instead)
95
105
- "Let me start a dev server on :<some-port>" — you don't know the user's port
96
106
- "I'll use `WebFetch` to check the rendered page instead"
97
107
98
108
All mean: do the discovery in §"Discovery first", then follow the interactive or scripted workflow above. If the dev server isn't running, ask the user to start it — don't start one yourself.
99
109
100
-
## How the MCP plugin is wired here
110
+
## How Playwright is wired here
101
111
102
-
For maintainers reading this: the upstream devcontainer image (`gatezh/devcontainers` claude-code, both `default` and `sandbox` targets) ships system chromium at `/usr/bin/chromium` and sets`PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1` + `PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/chromium`. Playwright doesn't read that env var on its own — it's a project convention — so:
112
+
For maintainers reading this: the upstream devcontainer image (`gatezh/devcontainers` claude-code, both `default` and `sandbox` targets) ships system chromium at `/usr/bin/chromium` and sets:
103
113
104
-
- The project's `playwright.config.ts` files wire it via `launchOptions.executablePath: process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH` so `playwright test` (or whatever the project's test script wraps) finds the binary.
105
-
-`@playwright/mcp` defaults to the `chrome` channel (Google Chrome stable at `/opt/google/chrome/chrome`), which isn't installed. The bundled `.mcp.json` is rewritten by `/usr/local/bin/patch-playwright-mcp` (baked into the image) to launch with `--browser chromium --executable-path /usr/bin/chromium --no-sandbox --headless`. It runs from `init-plugins.sh` (postCreate) AND from `postStartCommand` so plugin auto-updates between sessions don't leave fresh cache dirs unpatched. See gatezh/devcontainers#85, #87.
114
+
-`PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1` — keeps `npm install` from downloading bundled browsers.
115
+
-`PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/chromium` — project convention; Playwright doesn't read it automatically, so each consumer of `@playwright/test`, `@vitest/browser-playwright`, or direct `playwright-core` use must pass it explicitly through `launchOptions.executablePath`.
116
+
-`AGENT_BROWSER_EXECUTABLE_PATH=/usr/bin/chromium` (default target only) — `agent-browser` is a Rust CLI with its own env-var family; without this it would auto-detect or attempt a Chrome-for-Testing download on first use.
117
+
118
+
Beyond those env vars:
119
+
120
+
- The project's `playwright.config.ts` files wire `launchOptions.executablePath: process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH` so `playwright test` finds the binary.
121
+
- The project's `vitest.config.ts` (if it uses `@vitest/browser-playwright`, which `@storybook/addon-vitest` does) wires `provider: playwright({ launchOptions: { executablePath: process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH } })`.
122
+
-`@playwright/mcp` defaults to the `chrome` channel (Google Chrome stable at `/opt/google/chrome/chrome`), which isn't installed. The bundled `.mcp.json` is rewritten by `/usr/local/bin/patch-playwright-mcp` (baked into the image) to launch with `--browser chromium --executable-path /usr/bin/chromium --no-sandbox --headless`. It runs from `init-plugins.sh` (postCreate) AND from `postStartCommand` so plugin auto-updates between sessions don't leave fresh cache dirs unpatched. See gatezh/devcontainers#85, #87, #91.
Copy file name to clipboardExpand all lines: claude-code/README.md
+75-5Lines changed: 75 additions & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -120,9 +120,9 @@ The sandbox firewall blocks vendor doc sites, so Claude Code can't `WebFetch` or
120
120
121
121
Copy `.claude/skills/sandbox-fetch-docs/` into your project's `.claude/skills/` directory so Claude Code picks it up automatically.
122
122
123
-
### Optional: Claude Code skill for Playwright
123
+
### Recommended: Claude Code skill for Playwright
124
124
125
-
Both image variants ship system chromium and the `/usr/local/bin/patch-playwright-mcp` binary, so Playwright MCP and `@playwright/test` work out of the box once a project's `playwright.config.ts` wires `launchOptions.executablePath` (see [Playwright Strategy](#playwright-strategy)). The image includes a [sandbox-playwright](.claude/skills/sandbox-playwright/SKILL.md) skill that teaches Claude Code how to drive both paths — discovery-driven, with no hardcoded project ports, service names, or test layouts.
125
+
Both image variants ship system chromium and the `/usr/local/bin/patch-playwright-mcp` binary, so Playwright MCP and `@playwright/test`(and `@vitest/browser-playwright`, and direct `playwright-core` calls) work out of the box once each project entry point wires `launchOptions.executablePath`— see [Playwright Strategy](#playwright-strategy) for the full per-consumer wiring matrix. The image includes a [sandbox-playwright](.claude/skills/sandbox-playwright/SKILL.md) skill that teaches Claude Code how to drive these paths — discovery-driven, with no hardcoded project ports, service names, or test layouts.
126
126
127
127
Copy `.claude/skills/sandbox-playwright/` into your project's `.claude/skills/` directory so Claude Code picks it up automatically.
AGENT_BROWSER_EXECUTABLE_PATH=/usr/bin/chromium # default target only
243
244
```
244
245
245
246
`PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH` is a project convention — Playwright
246
-
does **not** read it automatically. Each project's `playwright.config.ts`
247
-
must honor it:
247
+
does **not**read it automatically. Every Playwright entry point in your
248
+
project must honor it. The image's job ends at "the env var points at the
249
+
binary"; consumer projects do the wiring.
250
+
251
+
| Consumer | Where to wire | What to add |
252
+
|---|---|---|
253
+
| `@playwright/mcp` | nothing (image-baked) | `/usr/local/bin/patch-playwright-mcp` rewrites every cached `.mcp.json` to use the system chromium. See [Playwright MCP plugin](#playwright-mcp-plugin) below. |
| Direct `playwright-core` / `playwright` use in scripts | every `chromium.launch(...)` site | `chromium.launch({ executablePath })` |
257
+
| `agent-browser` (default target only) | nothing | Image sets `AGENT_BROWSER_EXECUTABLE_PATH=/usr/bin/chromium`. Independent of the `PLAYWRIGHT_*` convention — agent-browser is a Rust CLI with its own env-var family. |
0 commit comments