Skip to content

Playwright Strategy section: enumerate every consumer that needs executablePath wiring #91

Description

@gatezh

Why this matters

The current Playwright Strategy section in the README does great work for @playwright/test and @playwright/mcp, but its wiring guidance is written as if playwright.config.ts is the only Playwright entry point in a consumer project. It isn't.

Concrete recurrence: a consumer (UtiliTsar, the project I'm filing this from) followed the upstream README end to end — wired playwright.config.ts, set up init-plugins.sh, copied sandbox-fetch-docs and sandbox-playwright skills, set the env vars. The MCP plugin worked, @playwright/test worked. Then the storybook test suite (@vitest/browser-playwright) failed at first run with a misleading message:

Executable doesn't exist at /home/node/.cache/ms-playwright/chromium_headless_shell-1217/chrome-linux/headless_shell

Looks like Playwright was just installed or updated.
Please run the following command to download new browsers:
    npx playwright install

That message points at the wrong fix. Running npx playwright install would re-download the bundled binary the image specifically tries to avoid. The actual fix is: wire launchOptions.executablePath in vitest.config.ts the same way playwright.config.ts already wires it. One line.

The reason this took multiple back-and-forths to find is the README's wording (verbatim):

PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH is a project convention — Playwright does not read it automatically. Each project's playwright.config.ts must honor it:

That sentence enumerates one Playwright entry point. A reasonable consumer reads this, wires their playwright.config.ts, and stops. If their project also uses vitest browser mode (typically via @storybook/addon-vitest for storybook interaction tests, or @vitest/browser for browser-mode unit tests), it silently breaks at first run.

This issue closes that gap by enumerating every Playwright consumer a typical project might have, with copy-paste-ready wiring snippets for each.

What's already documented

The existing Playwright Strategy section is solid for what it covers:

  • The two env vars (PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 + PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/chromium) and the rationale (avoid version coupling between @playwright/mcp and managed-cache binaries; image stays smaller).
  • The convention statement (Playwright doesn't read the env var automatically).
  • The @playwright/mcp story end to end: /usr/local/bin/patch-playwright-mcp rewrites .mcp.json, init-plugins.sh invokes it at postCreateCommand, postStartCommand re-runs it for plugin auto-updates.
  • The debug snippet (cat ~/.claude/plugins/cache/...).

Nothing about the above needs to change. This issue only proposes to expand the consumer wiring part of the section to cover all Playwright entry points — not just @playwright/test.

The gap

The Playwright Strategy section's only consumer wiring example is @playwright/test. A representative monorepo project may have any of these Playwright consumers, none of which the README enumerates:

Consumer Why it shows up Wiring required?
@playwright/mcp bundled with playwright@claude-plugins-official No — image-baked patch script handles it
@playwright/test scripted E2E suite Yes — playwright.config.ts use.launchOptions.executablePath
@vitest/browser-playwright vitest browser mode (used by @storybook/addon-vitest for storybook interaction tests, by @vitest/browser for browser-mode unit tests) Yes — vitest.config.ts playwright({ launchOptions: { … } })
playwright-core / playwright (direct) custom scripts that call chromium.launch(...) themselves (CI scrapers, custom E2E without @playwright/test, codegen) Yes — at every chromium.launch(...) call site
agent-browser (default target only) image npm-installs it No — reads PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH natively

Of these, only @playwright/test is documented. @vitest/browser-playwright is the one this issue is filing against — it's a real consumer, increasingly common via storybook tests, and silent when broken.

Proposed change

Replace the existing Playwright Strategy → consumer wiring snippet in the README (the part that says "Each project's playwright.config.ts must honor it:" plus the single TS code block) with the table + snippets below. Pasteable verbatim.

`PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH` is a project convention — Playwright
does **not** read it automatically. Every Playwright entry point in your
project must honor it. The image's job ends at "the env var points at the
binary"; consumer projects do the wiring.

| Consumer | Where to wire | What to add |
|---|---|---|
| `@playwright/mcp` | nothing (image-baked) | The `/usr/local/bin/patch-playwright-mcp` binary rewrites every cached `.mcp.json` to use the system chromium. See "Playwright MCP plugin" below. |
| `@playwright/test` | `playwright.config.ts` | `use.launchOptions.executablePath` |
| `@vitest/browser-playwright` (incl. `@storybook/addon-vitest`, `@vitest/browser`) | `vitest.config.ts` | `playwright({ launchOptions: { executablePath } })` |
| Direct `playwright-core` / `playwright` use in scripts | every `chromium.launch(...)` site | `chromium.launch({ executablePath })` |
| `agent-browser` (default target only) | nothing | Reads `PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH` natively. |

### `@playwright/test` — `playwright.config.ts`

```ts
import { defineConfig } from "@playwright/test";

export default defineConfig({
  use: {
    launchOptions: {
      executablePath: process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH,
    },
  },
});
```

In a monorepo, repeat this in every `playwright.config.ts` (one per service
that has a Playwright suite). The same env-var value works for all.

### `@vitest/browser-playwright` — `vitest.config.ts`

The provider's options match the Playwright `LaunchOptions` interface
verbatim under the `launchOptions` key (see the
[`PlaywrightProviderOptions` interface](https://github.com/vitest-dev/vitest/blob/main/packages/browser-playwright/src/index.ts)
in `@vitest/browser-playwright`):

```ts
import { defineConfig, mergeConfig } from "vitest/config";
import { playwright } from "@vitest/browser-playwright";
import viteConfig from "./vite.config";

export default mergeConfig(
  viteConfig,
  defineConfig({
    test: {
      browser: {
        enabled: true,
        headless: true,
        provider: playwright({
          launchOptions: {
            executablePath: process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH,
          },
        }),
        instances: [{ browser: "chromium" }],
      },
    },
  }),
);
```

Symptom when this is missing: `Executable doesn't exist at
/home/node/.cache/ms-playwright/chromium_headless_shell-<rev>/chrome-linux/headless_shell`
followed by a "Please run npx playwright install" message. Don't follow
that hint — wire `launchOptions.executablePath` instead.

### Direct `playwright-core` / `playwright` use

For CI scripts, custom E2E that doesn't use `@playwright/test`, or codegen
sites that import `chromium` directly:

```ts
import { chromium } from "playwright-core"; // or "playwright"

const browser = await chromium.launch({
  executablePath: process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH,
  headless: true,
});
```

Optional small wording fixes (Project Setup Guide section)

Two items are currently labeled "Optional" but materially affect Playwright support:

  • "Optional: Claude Code skill for Playwright" — the skill encodes the discovery protocol Claude needs to find a project's playwright.config.ts, the port, etc. Without it Claude has to be hand-held every time. Suggest renaming to "Recommended". Same for sandbox-fetch-docs if the project uses the sandbox variant — without it, Claude can't fetch library docs through the firewall.
  • "Optional: .devcontainer/init-plugins.sh" — required if the project uses Playwright MCP (otherwise the patch binary never runs). Soft call; could stay "Optional" with a parenthetical "(required for Playwright MCP)".

Not blocking on these — feel free to keep the wording as-is if "Optional" is meant in the strict "image works without it" sense. The Playwright-consumer-wiring change above is the load-bearing part.

Verification plan

After the change, a fresh consumer following only the Playwright Strategy section should be able to wire all known Playwright entry points without external help. Concrete check: search the new section for the strings playwright.config.ts, vitest.config.ts, playwright-core, agent-browser, @playwright/mcp — all five should appear at least once. They do in the snippet above.

End-to-end test (in a consumer project): with the wiring applied, run the project's full test stack — playwright test, vitest browser projects, any custom Playwright scripts. None of them should print "Please run npx playwright install" or look for binaries under ~/.cache/ms-playwright/. All three should launch via /usr/bin/chromium.

Migration impact for consumer projects

Purely additive. Projects that already wired playwright.config.ts keep working. Projects that hadn't yet wired vitest.config.ts (this one, before the local fix that prompted this issue) will see the new section and apply it the next time they sync against the README. A one-line change per missing wiring point.

Trade-offs / alternatives considered

  • Make executablePath automatic: rejected, would require a custom Playwright fork or a global JS shim. The "project convention" stance is correct — it's just under-documented for non-@playwright/test consumers.
  • Move some of this guidance into the sandbox-playwright SKILL.md instead of the README: the skill is for Claude; the README is for humans setting up the project. Both audiences need the wiring guidance, but the README is where a human first lands. Keep the canonical version there. Optionally mirror into the skill too — but that's a follow-up.
  • Generate a stub vitest.config.ts template the way the README links to a playwright.config.ts template (it doesn't, actually — the example is inline only): not necessary. The README inline-snippet pattern is fine.

Closes the loop with the previous three issues

This issue is the consumer-wiring completeness pass. After it lands, a fresh consumer should be able to set up Playwright in their project from the README alone, regardless of which Playwright packages they use.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions