Code name:
playwright-dev
OS-level dependencies for headless Chromium, Firefox, and WebKit, a browser-binary cache shared across rebuilds via a Docker named volume, and the official Playwright Test VS Code extension — pre-configured so npx playwright test and npx playwright install just work, without re-downloading browsers on every rebuild.
Also included automatically: repairs broken host paths in your git config and restores your SSH commit-signing key on every attach, on both local and cloud containers, with nothing to set up on your end — see
helpers4-commonfor how it works.
Headless browser automation (Playwright, and anything built on Chromium's DevTools Protocol — including WebAuthn.addVirtualAuthenticator, useful for testing passkey/WebAuthn flows without physical hardware) needs a real browser binary plus a long list of OS shared libraries. npx playwright install --with-deps can fetch both, but:
- it needs
sudo/root at test-run time to apt-install system packages, - it re-downloads the browser binaries into
~/.cache/ms-playwrighton every fresh container, since that path isn't normally persisted.
This feature moves both steps into the devcontainer lifecycle: OS packages are installed once at image build time (via the official playwright install-deps, not a hand-maintained apt list that would drift across base-image OS versions), and the browser binaries are downloaded once into a named volume that survives rebuilds — the same shape as the pnpm-store feature.
It deliberately does not install the playwright npm package itself — that stays a devDependency of the consuming project, so the CLI version always matches the project's own Playwright version instead of drifting from a separately-installed global one.
Microsoft publishes mcr.microsoft.com/playwright:v<version>-<os>, a Docker image with browsers and OS deps already baked in — zero install time, nothing to cache. If you don't need to compose with other helpers4 features on top of your own base image, using that image directly as "image" is simpler than this feature. The tradeoff is that it pins your whole devcontainer to Microsoft's base image and Playwright version, instead of adding browser support to whatever base image and feature set (typescript-dev, vite-plus, pnpm-store, …) you're already using.
Add this feature to your devcontainer.json:
{
"features": {
"ghcr.io/helpers4/devcontainer/playwright-dev:1": {}
}
}This will:
- Install the OS packages required to run Chromium, Firefox, and WebKit headless
- Set
PLAYWRIGHT_BROWSERS_PATHto a Docker-volume-backed path shared across rebuilds - Download the browser binaries into that volume on first container start
- Install the official Playwright Test VS Code extension, pre-configured
If your project only needs Chromium (e.g. CDP-based WebAuthn testing), skip the Firefox/WebKit dependencies to keep the image smaller:
{
"features": {
"ghcr.io/helpers4/devcontainer/playwright-dev:1": {
"browsers": "chromium"
}
}
}| Option | Type | Default | Description |
|---|---|---|---|
browsers |
string (chromium | firefox | webkit | all) |
all |
Which browser engine(s) to install OS-level dependencies for, and to pre-download into the shared cache. |
installDeps |
boolean | true |
Install the OS packages required to run the selected browser(s) headless, via playwright install-deps. Disable if the base image already provides them. |
| Editor | Status | ID |
|---|---|---|
| VS Code | ✅ | ms-playwright.playwright |
| Cursor | ✅ | ms-playwright.playwright (same registry as VS Code) |
| WebStorm / IntelliJ IDEA (2023.3+) | ℹ️ | Playwright tests are recognized natively via the built-in Test Automation plugin — not something this feature installs, nothing to configure here. |
| Zed | 🔜 | no standard devcontainer customization format yet |
Binaries live in a Docker named volume (helpers4-playwright-browsers-${localEnv:USER}) mounted at /usr/local/share/playwright-browsers, exposed to every shell via PLAYWRIGHT_BROWSERS_PATH. The volume name includes ${localEnv:USER}, so it's shared across every local devcontainer for the same host OS user — not just rebuilds of one project — the same reasoning as pnpm-store: browser binaries are downloaded, versioned artifacts with no credentials attached, so sharing them across your own projects has none of the cross-project bleed risk that keeps an AI tool's credentials volume (claude-dev, mistral-dev) scoped per devcontainer instead.
A postCreateCommand guard script takes ownership of the volume at container creation (with --shared, since a second, concurrently-running project can resolve a different container UID) and downloads the browsers only if they haven't been fetched yet for the current Playwright version + browsers selection — the version is part of the completion marker specifically because the cache is now shared: two projects pinning different Playwright versions can need different browser revisions, and only the marker (not Playwright's own revision-namespaced cache layout) would otherwise get that wrong.
# Confirm what's cached
ls "$PLAYWRIGHT_BROWSERS_PATH"- Test explorer: run/debug Playwright tests from the sidebar
- Pick locators, record new tests (codegen), and view traces inline
Settings applied:
{
"playwright.reuseBrowser": true,
"playwright.showTrace": true
}Chromium's DevTools Protocol exposes WebAuthn.addVirtualAuthenticator — no extra software authenticator needed. With Playwright:
const client = await context.newCDPSession(page);
await client.send("WebAuthn.enable");
await client.send("WebAuthn.addVirtualAuthenticator", {
options: {
protocol: "ctap2",
transport: "internal",
hasResidentKey: true,
hasUserVerification: true,
isUserVerified: true,
},
});{
"features": {
"ghcr.io/helpers4/devcontainer/playwright-dev:1": {},
"ghcr.io/helpers4/devcontainer/typescript-dev:1": {},
"ghcr.io/helpers4/devcontainer/vite-plus:1": {},
"ghcr.io/helpers4/devcontainer/pnpm-store:1": {}
}
}The guard script needs network access at postCreateCommand time. Re-run it manually, or install directly:
npx playwright installplaywright install-deps only knows the apt package names for the Debian/Ubuntu versions Playwright officially supports. Pin your base image to one of those, or set installDeps: false and install the equivalent packages yourself.
- Playwright: https://playwright.dev/
- VS Code extension: https://marketplace.visualstudio.com/items?itemName=ms-playwright.playwright
- CDP WebAuthn domain: https://chromedevtools.github.io/devtools-protocol/tot/WebAuthn/
- v1.3.1: Code-review follow-up on v1.3.0's cache-sharing change — a failed Playwright
version lookup (offline, transient npx/registry hiccup) fell back to a generic
.h4-installed-unknown-*marker; on the now-shared volume, a different project hitting the same failure could match that marker and wrongly skip installing a browser revision it doesn't actually have. A failed lookup no longer participates in the marker shortcut at all — it just re-runs the (idempotent, revision-aware) install for that one start instead. - v1.3.0: The browser-cache volume is now keyed by
${localEnv:USER}instead of${devcontainerId}— shared across every local devcontainer for this host OS user instead of exclusive to one project, avoiding redundant downloads across your own projects. Safe unlike an AI tool's credentials volume: browser binaries carry no identity/permissions surface to leak between projects.h4_ensure_volume_writableis now called with--shared. The download completion marker is now also scoped by the resolved Playwright version (previously just thebrowsersselection) — necessary now that the cache is shared, since two projects can pin different Playwright versions needing different browser revisions; without this, whichever project populated the shared cache first would cause a different project's version to wrongly skip its own download. - v1.2.3: Internal refactor, no behavior change — the browser-cache ownership logic
(chown to the current user when needed) now calls
helpers4-common'sh4_ensure_volume_writableinstead of carrying its own inline copy. - v1.2.2: Documentation only, no functional change — the previous wording sweep made the
JSON
descriptionfield far too long, shifting focus away from the feature itself onto the self-heal side benefit. Shortened to 5 words and kept generic (no implementation detail like "git config"), matching the original's brevity and level of detail. - v1.2.1: Documentation only, no functional change — the self-heal callout above (and the
JSON
descriptionfield) led with internal jargon ("helpers4's self-heal") instead of the actual benefit; reworded to lead with what it does, with the full mechanism staying inhelpers4-common's own README. - v1.2.0: Documentation only, no functional change — mentions that
helpers4-common's automatic git-config self-heal (see above) now comes along with this feature. - v1.1.0: Switched from an inline copy of
helpers4-common's bootstrap (user detection, apt helpers) to a directdependsOnon thehelpers4-commonfeature — no behavior change, just a single source of truth for that logic instead of a copy every feature had to keep in sync. - v1.0.1: Dropped the
@latestpin from bothnpx playwrightcalls — it was forcing the newest registry release instead of the project's own pinned version. Addedghcr.io/devcontainers/features/node:1todependsOn:typescript-devalone doesn't install Node, so thenpm not foundguard wasn't actually covered. The browser-cache guard now writes a completion marker instead of trusting a non-empty directory, so an interrupted download doesn't get stuck as a permanently broken cache. Added an "IDE support" table and documented themcr.microsoft.com/playwrightprebuilt-image alternative. - v1.0.0: Initial release.
LGPL-3.0 - See LICENSE file for details