From 02a87d83d63c9736a2bf8818f9df32312392b73e Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 5 Aug 2026 05:03:21 +0000 Subject: [PATCH 1/7] feat: add a playwright feature Installs the Playwright CLI globally and downloads a Chromium build, so containers do not pay for an npm install and a browser download on every start. Browsers land in /usr/local/share/ms-playwright rather than Playwright's default "$HOME/.cache", because sandboxes may replace HOME with a per-pod volume: anything baked into the image's home directory is invisible there. PLAYWRIGHT_BROWSERS_PATH is set through containerEnv so the install and every later run agree on that location. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01MB7zNmdzPYMpwkUWxAjSFt --- src/playwright/README.md | 24 +++++++++ src/playwright/devcontainer-feature.json | 24 +++++++++ src/playwright/install.sh | 64 ++++++++++++++++++++++++ test/playwright/scenarios.json | 13 +++++ test/playwright/ubuntu.sh | 16 ++++++ 5 files changed, 141 insertions(+) create mode 100644 src/playwright/README.md create mode 100644 src/playwright/devcontainer-feature.json create mode 100755 src/playwright/install.sh create mode 100644 test/playwright/scenarios.json create mode 100755 test/playwright/ubuntu.sh diff --git a/src/playwright/README.md b/src/playwright/README.md new file mode 100644 index 0000000..b477b17 --- /dev/null +++ b/src/playwright/README.md @@ -0,0 +1,24 @@ + +# Playwright CLI and Chromium (playwright) + +Installs the Playwright CLI globally and downloads a Chromium build into a system-wide, HOME-independent browsers directory. + +## Example Usage + +```json +"features": { + "ghcr.io/orianna-ai/devcontainer-features/playwright:1": {} +} +``` + +## Options + +| Options Id | Description | Type | Default Value | +|-----|-----|-----|-----| +| version | Version of the @playwright/cli npm package to install globally. | string | latest | + + + +--- + +_Note: This file was auto-generated from the [devcontainer-feature.json](devcontainer-feature.json). Add additional notes to a `NOTES.md`._ diff --git a/src/playwright/devcontainer-feature.json b/src/playwright/devcontainer-feature.json new file mode 100644 index 0000000..78e2f33 --- /dev/null +++ b/src/playwright/devcontainer-feature.json @@ -0,0 +1,24 @@ +{ + "containerEnv": { + "PLAYWRIGHT_BROWSERS_PATH": "/usr/local/share/ms-playwright" + }, + "description": "Installs the Playwright CLI globally and downloads a Chromium build into a system-wide, HOME-independent browsers directory.", + "id": "playwright", + "installsAfter": [ + "ghcr.io/devcontainers/features/common-utils", + "ghcr.io/devcontainers/features/node" + ], + "name": "Playwright CLI and Chromium", + "options": { + "version": { + "default": "latest", + "description": "Version of the @playwright/cli npm package to install globally.", + "proposals": [ + "latest", + "0.1.17" + ], + "type": "string" + } + }, + "version": "1.0.0" +} diff --git a/src/playwright/install.sh b/src/playwright/install.sh new file mode 100755 index 0000000..1c5f09c --- /dev/null +++ b/src/playwright/install.sh @@ -0,0 +1,64 @@ +#!/bin/bash +# Bake the Playwright CLI and a Chromium build into the image. +# +# Installing both at run time puts an npm install and a browser download on the critical path of +# every container start. Doing it here trades image size for startup latency. +# +# Browsers land in a system-wide directory rather than Playwright's default "$HOME/.cache", because +# a sandbox may point HOME at a per-pod volume: anything baked into the image's home directory is +# invisible there. PLAYWRIGHT_BROWSERS_PATH (declared in devcontainer-feature.json, so it is baked +# into the image environment) points both the install below and every later run at that directory. +set -euo pipefail + +VERSION="${VERSION:-latest}" +BROWSERS_PATH="/usr/local/share/ms-playwright" + +export DEBIAN_FRONTEND=noninteractive +export PLAYWRIGHT_BROWSERS_PATH="${BROWSERS_PATH}" + +if ! command -v npm >/dev/null 2>&1; then + export NVM_DIR="${NVM_DIR:-/usr/local/share/nvm}" + + # shellcheck source=/dev/null + [ -s "${NVM_DIR}/nvm.sh" ] && . "${NVM_DIR}/nvm.sh" +fi + +if ! command -v npm >/dev/null 2>&1; then + echo "npm was not found; this feature has to install after the node feature" >&2 + + exit 1 +fi + +npm_root="$(npm root -g)" + +# The node feature hands the global module tree to the remote user, and containers may install more +# global packages as that user at run time. Installing as root would leave root-owned files behind, +# so capture the current ownership and restore it once the install is done. +owner="$(stat -c '%u:%g' "${npm_root}")" + +npm install --global "@playwright/cli@${VERSION}" + +# @playwright/cli only links its own "playwright-cli" binary; the browser installer lives in its +# playwright-core dependency, whose binary npm leaves unlinked. Locate it rather than assuming +# whether npm hoisted the dependency out of the package's own node_modules. +playwright_core="$(find "${npm_root}" -maxdepth 6 -path '*/playwright-core/cli.js' -print -quit)" + +if [ -z "${playwright_core}" ]; then + echo "playwright-core was not found under ${npm_root}" >&2 + + exit 1 +fi + +# install-deps installs Chromium's shared libraries with apt, and an earlier feature may already +# have dropped the package lists. +apt-get update -y + +node "${playwright_core}" install-deps chromium +node "${playwright_core}" install chromium + +rm -rf /var/lib/apt/lists/* + +npm cache clean --force + +chown -R "${owner}" "${npm_root}" "$(npm prefix -g)/bin" "${BROWSERS_PATH}" +chmod -R a+rX "${BROWSERS_PATH}" diff --git a/test/playwright/scenarios.json b/test/playwright/scenarios.json new file mode 100644 index 0000000..4a921e5 --- /dev/null +++ b/test/playwright/scenarios.json @@ -0,0 +1,13 @@ +{ + "ubuntu": { + "features": { + "ghcr.io/devcontainers/features/common-utils:2": {}, + "ghcr.io/devcontainers/features/node:1": {}, + "playwright": { + "version": "latest" + } + }, + "image": "mcr.microsoft.com/devcontainers/base:ubuntu-22.04", + "user": "vscode" + } +} diff --git a/test/playwright/ubuntu.sh b/test/playwright/ubuntu.sh new file mode 100755 index 0000000..c724630 --- /dev/null +++ b/test/playwright/ubuntu.sh @@ -0,0 +1,16 @@ +#!/bin/bash +set -e + +# shellcheck source=/dev/null +source \ + dev-container-features-test-lib + +BROWSERS_PATH="/usr/local/share/ms-playwright" + +check 'check if playwright-cli exists' bash -c "command -v playwright-cli" +check 'check if the browsers path is exported' bash -c "test \"${PLAYWRIGHT_BROWSERS_PATH:-}\" = ${BROWSERS_PATH}" +check 'check if chromium was downloaded' bash -c "ls ${BROWSERS_PATH} | grep -q chromium" +# The checks run as the remote user, so -r and -x prove the browsers survived the chown back to the +# node feature's owner and stay reachable when the home directory is swapped out underneath them. +check 'check if the browsers path is readable by the remote user' bash -c "test -r ${BROWSERS_PATH} && test -x ${BROWSERS_PATH}" +reportResults From d5a77af531d31f4ea7dc9c6bf2c08a6841c93561 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 5 Aug 2026 05:14:13 +0000 Subject: [PATCH 2/7] fix: fail early on non-debian images and document the node requirement The installer only ever installed Chromium's shared libraries with apt, so it died mid-install on a non-Debian base with "apt-get: command not found". Check for apt-get up front instead, before the npm install, and say what the feature supports. Node stays in installsAfter rather than dependsOn: the spec only deduplicates Features whose id and options match exactly, so a dependsOn entry would install a second, separately versioned copy of the node feature for anyone who already pins one. NOTES.md documents the requirement instead, so the generated README shows node alongside playwright. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01MB7zNmdzPYMpwkUWxAjSFt --- src/playwright/NOTES.md | 26 ++++++++++++++++++++++++++ src/playwright/README.md | 26 ++++++++++++++++++++++++++ src/playwright/install.sh | 11 +++++++++-- 3 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 src/playwright/NOTES.md diff --git a/src/playwright/NOTES.md b/src/playwright/NOTES.md new file mode 100644 index 0000000..75504f9 --- /dev/null +++ b/src/playwright/NOTES.md @@ -0,0 +1,26 @@ +## Requirements + +Node has to be selected too — the feature installs `@playwright/cli` with `npm`, and it fails with +an explicit error when `npm` is not on `PATH`. + +```json +"features": { + "ghcr.io/devcontainers/features/node:2": {}, + "ghcr.io/orianna-ai/devcontainer-features/playwright:1": {} +} +``` + +Node is listed in `installsAfter` rather than `dependsOn` so that consumers keep control of their +own Node version. The spec only deduplicates Features whose id *and* options match exactly, so a +`dependsOn` entry would install a second, separately versioned copy of the Node feature for anyone +who already pins one, and whichever copy ran last would win the nvm default. + +Debian and Ubuntu base images only. Playwright installs Chromium's shared libraries with apt and has +no equivalent for other package managers, so the feature fails early on anything else. + +## Browsers path + +Browsers land in `/usr/local/share/ms-playwright` rather than Playwright's default `$HOME/.cache`, +because a sandbox may point `HOME` at a per-pod volume — anything baked into the image's home +directory is invisible there. `PLAYWRIGHT_BROWSERS_PATH` is set through `containerEnv`, so the +install and every later run agree on that location without any further setup. diff --git a/src/playwright/README.md b/src/playwright/README.md index b477b17..d303ca5 100644 --- a/src/playwright/README.md +++ b/src/playwright/README.md @@ -17,6 +17,32 @@ Installs the Playwright CLI globally and downloads a Chromium build into a syste |-----|-----|-----|-----| | version | Version of the @playwright/cli npm package to install globally. | string | latest | +## Requirements + +Node has to be selected too — the feature installs `@playwright/cli` with `npm`, and it fails with +an explicit error when `npm` is not on `PATH`. + +```json +"features": { + "ghcr.io/devcontainers/features/node:2": {}, + "ghcr.io/orianna-ai/devcontainer-features/playwright:1": {} +} +``` + +Node is listed in `installsAfter` rather than `dependsOn` so that consumers keep control of their +own Node version. The spec only deduplicates Features whose id *and* options match exactly, so a +`dependsOn` entry would install a second, separately versioned copy of the Node feature for anyone +who already pins one, and whichever copy ran last would win the nvm default. + +Debian and Ubuntu base images only. Playwright installs Chromium's shared libraries with apt and has +no equivalent for other package managers, so the feature fails early on anything else. + +## Browsers path + +Browsers land in `/usr/local/share/ms-playwright` rather than Playwright's default `$HOME/.cache`, +because a sandbox may point `HOME` at a per-pod volume — anything baked into the image's home +directory is invisible there. `PLAYWRIGHT_BROWSERS_PATH` is set through `containerEnv`, so the +install and every later run agree on that location without any further setup. --- diff --git a/src/playwright/install.sh b/src/playwright/install.sh index 1c5f09c..33ab00c 100755 --- a/src/playwright/install.sh +++ b/src/playwright/install.sh @@ -16,6 +16,14 @@ BROWSERS_PATH="/usr/local/share/ms-playwright" export DEBIAN_FRONTEND=noninteractive export PLAYWRIGHT_BROWSERS_PATH="${BROWSERS_PATH}" +# Playwright only knows how to install Chromium's shared libraries with apt, so this feature is +# Debian and Ubuntu only. Say so before the npm install rather than dying halfway through it. +if ! command -v apt-get >/dev/null 2>&1; then + echo "apt-get was not found; this feature only supports debian and ubuntu base images" >&2 + + exit 1 +fi + if ! command -v npm >/dev/null 2>&1; then export NVM_DIR="${NVM_DIR:-/usr/local/share/nvm}" @@ -49,8 +57,7 @@ if [ -z "${playwright_core}" ]; then exit 1 fi -# install-deps installs Chromium's shared libraries with apt, and an earlier feature may already -# have dropped the package lists. +# An earlier feature may already have dropped the package lists that install-deps needs. apt-get update -y node "${playwright_core}" install-deps chromium From 7790c65ad301b480a4e82e20e909988c5ab66288 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 5 Aug 2026 13:00:49 +0000 Subject: [PATCH 3/7] style: trim the playwright feature to the conventions of its neighbors Drops the comments that restate the code, the blank lines before exit, and the name and description that ran long next to buildbuddy and cloud-sql-proxy. Drops the version proposals too, so the option looks like cloud-sql-proxy's. The test loses its local path variable for the same reason. What stays is the three things a reader cannot get from the code: why browsers go to a system-wide path, why the global module tree is chowned back, and why playwright-core is located rather than invoked by name. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01MB7zNmdzPYMpwkUWxAjSFt --- src/playwright/NOTES.md | 18 +++++++------- src/playwright/README.md | 24 +++++++++---------- src/playwright/devcontainer-feature.json | 10 +++----- src/playwright/install.sh | 30 ++++++------------------ test/playwright/ubuntu.sh | 11 +++------ 5 files changed, 34 insertions(+), 59 deletions(-) diff --git a/src/playwright/NOTES.md b/src/playwright/NOTES.md index 75504f9..47bfa8f 100644 --- a/src/playwright/NOTES.md +++ b/src/playwright/NOTES.md @@ -1,7 +1,7 @@ ## Requirements -Node has to be selected too — the feature installs `@playwright/cli` with `npm`, and it fails with -an explicit error when `npm` is not on `PATH`. +Select the node feature too — this one installs `@playwright/cli` with `npm` and exits with an +explicit error when `npm` is missing. ```json "features": { @@ -10,17 +10,17 @@ an explicit error when `npm` is not on `PATH`. } ``` -Node is listed in `installsAfter` rather than `dependsOn` so that consumers keep control of their -own Node version. The spec only deduplicates Features whose id *and* options match exactly, so a -`dependsOn` entry would install a second, separately versioned copy of the Node feature for anyone -who already pins one, and whichever copy ran last would win the nvm default. +Node is in `installsAfter` rather than `dependsOn` on purpose. Features dedupe only when their id +*and* options match exactly, so a `dependsOn` entry would install a second copy of the node feature +for anyone who already pins their own version, and whichever copy ran last would win the nvm +default. -Debian and Ubuntu base images only. Playwright installs Chromium's shared libraries with apt and has -no equivalent for other package managers, so the feature fails early on anything else. +Debian and Ubuntu only. Playwright installs Chromium's shared libraries with apt and has no +equivalent for other package managers, so the feature fails early on anything else. ## Browsers path Browsers land in `/usr/local/share/ms-playwright` rather than Playwright's default `$HOME/.cache`, because a sandbox may point `HOME` at a per-pod volume — anything baked into the image's home directory is invisible there. `PLAYWRIGHT_BROWSERS_PATH` is set through `containerEnv`, so the -install and every later run agree on that location without any further setup. +install and every later run agree on that location with no further setup. diff --git a/src/playwright/README.md b/src/playwright/README.md index d303ca5..351bdee 100644 --- a/src/playwright/README.md +++ b/src/playwright/README.md @@ -1,7 +1,7 @@ -# Playwright CLI and Chromium (playwright) +# Playwright (playwright) -Installs the Playwright CLI globally and downloads a Chromium build into a system-wide, HOME-independent browsers directory. +Installs the Playwright CLI and Chromium ## Example Usage @@ -15,12 +15,12 @@ Installs the Playwright CLI globally and downloads a Chromium build into a syste | Options Id | Description | Type | Default Value | |-----|-----|-----|-----| -| version | Version of the @playwright/cli npm package to install globally. | string | latest | +| version | Version of the @playwright/cli package to install. | string | latest | ## Requirements -Node has to be selected too — the feature installs `@playwright/cli` with `npm`, and it fails with -an explicit error when `npm` is not on `PATH`. +Select the node feature too — this one installs `@playwright/cli` with `npm` and exits with an +explicit error when `npm` is missing. ```json "features": { @@ -29,20 +29,20 @@ an explicit error when `npm` is not on `PATH`. } ``` -Node is listed in `installsAfter` rather than `dependsOn` so that consumers keep control of their -own Node version. The spec only deduplicates Features whose id *and* options match exactly, so a -`dependsOn` entry would install a second, separately versioned copy of the Node feature for anyone -who already pins one, and whichever copy ran last would win the nvm default. +Node is in `installsAfter` rather than `dependsOn` on purpose. Features dedupe only when their id +*and* options match exactly, so a `dependsOn` entry would install a second copy of the node feature +for anyone who already pins their own version, and whichever copy ran last would win the nvm +default. -Debian and Ubuntu base images only. Playwright installs Chromium's shared libraries with apt and has -no equivalent for other package managers, so the feature fails early on anything else. +Debian and Ubuntu only. Playwright installs Chromium's shared libraries with apt and has no +equivalent for other package managers, so the feature fails early on anything else. ## Browsers path Browsers land in `/usr/local/share/ms-playwright` rather than Playwright's default `$HOME/.cache`, because a sandbox may point `HOME` at a per-pod volume — anything baked into the image's home directory is invisible there. `PLAYWRIGHT_BROWSERS_PATH` is set through `containerEnv`, so the -install and every later run agree on that location without any further setup. +install and every later run agree on that location with no further setup. --- diff --git a/src/playwright/devcontainer-feature.json b/src/playwright/devcontainer-feature.json index 78e2f33..f7ff830 100644 --- a/src/playwright/devcontainer-feature.json +++ b/src/playwright/devcontainer-feature.json @@ -2,21 +2,17 @@ "containerEnv": { "PLAYWRIGHT_BROWSERS_PATH": "/usr/local/share/ms-playwright" }, - "description": "Installs the Playwright CLI globally and downloads a Chromium build into a system-wide, HOME-independent browsers directory.", + "description": "Installs the Playwright CLI and Chromium", "id": "playwright", "installsAfter": [ "ghcr.io/devcontainers/features/common-utils", "ghcr.io/devcontainers/features/node" ], - "name": "Playwright CLI and Chromium", + "name": "Playwright", "options": { "version": { "default": "latest", - "description": "Version of the @playwright/cli npm package to install globally.", - "proposals": [ - "latest", - "0.1.17" - ], + "description": "Version of the @playwright/cli package to install.", "type": "string" } }, diff --git a/src/playwright/install.sh b/src/playwright/install.sh index 33ab00c..b4ba29c 100755 --- a/src/playwright/install.sh +++ b/src/playwright/install.sh @@ -1,13 +1,6 @@ #!/bin/bash -# Bake the Playwright CLI and a Chromium build into the image. -# -# Installing both at run time puts an npm install and a browser download on the critical path of -# every container start. Doing it here trades image size for startup latency. -# -# Browsers land in a system-wide directory rather than Playwright's default "$HOME/.cache", because -# a sandbox may point HOME at a per-pod volume: anything baked into the image's home directory is -# invisible there. PLAYWRIGHT_BROWSERS_PATH (declared in devcontainer-feature.json, so it is baked -# into the image environment) points both the install below and every later run at that directory. +# Browsers go to a system-wide directory rather than Playwright's default "$HOME/.cache": a sandbox +# may point HOME at a per-pod volume, where anything baked into the image's home is invisible. set -euo pipefail VERSION="${VERSION:-latest}" @@ -16,11 +9,8 @@ BROWSERS_PATH="/usr/local/share/ms-playwright" export DEBIAN_FRONTEND=noninteractive export PLAYWRIGHT_BROWSERS_PATH="${BROWSERS_PATH}" -# Playwright only knows how to install Chromium's shared libraries with apt, so this feature is -# Debian and Ubuntu only. Say so before the npm install rather than dying halfway through it. if ! command -v apt-get >/dev/null 2>&1; then echo "apt-get was not found; this feature only supports debian and ubuntu base images" >&2 - exit 1 fi @@ -33,38 +23,32 @@ fi if ! command -v npm >/dev/null 2>&1; then echo "npm was not found; this feature has to install after the node feature" >&2 - exit 1 fi +# The node feature hands the global module tree to the remote user, who installs more global +# packages there at run time. Restore that ownership after installing as root. npm_root="$(npm root -g)" - -# The node feature hands the global module tree to the remote user, and containers may install more -# global packages as that user at run time. Installing as root would leave root-owned files behind, -# so capture the current ownership and restore it once the install is done. owner="$(stat -c '%u:%g' "${npm_root}")" npm install --global "@playwright/cli@${VERSION}" -# @playwright/cli only links its own "playwright-cli" binary; the browser installer lives in its -# playwright-core dependency, whose binary npm leaves unlinked. Locate it rather than assuming -# whether npm hoisted the dependency out of the package's own node_modules. +# @playwright/cli links only its own binary; the browser installer lives in its playwright-core +# dependency, which npm leaves unlinked and may or may not hoist. playwright_core="$(find "${npm_root}" -maxdepth 6 -path '*/playwright-core/cli.js' -print -quit)" if [ -z "${playwright_core}" ]; then echo "playwright-core was not found under ${npm_root}" >&2 - exit 1 fi -# An earlier feature may already have dropped the package lists that install-deps needs. +# install-deps needs package lists that an earlier feature may already have dropped. apt-get update -y node "${playwright_core}" install-deps chromium node "${playwright_core}" install chromium rm -rf /var/lib/apt/lists/* - npm cache clean --force chown -R "${owner}" "${npm_root}" "$(npm prefix -g)/bin" "${BROWSERS_PATH}" diff --git a/test/playwright/ubuntu.sh b/test/playwright/ubuntu.sh index c724630..fffdef3 100755 --- a/test/playwright/ubuntu.sh +++ b/test/playwright/ubuntu.sh @@ -4,13 +4,8 @@ set -e # shellcheck source=/dev/null source \ dev-container-features-test-lib - -BROWSERS_PATH="/usr/local/share/ms-playwright" - check 'check if playwright-cli exists' bash -c "command -v playwright-cli" -check 'check if the browsers path is exported' bash -c "test \"${PLAYWRIGHT_BROWSERS_PATH:-}\" = ${BROWSERS_PATH}" -check 'check if chromium was downloaded' bash -c "ls ${BROWSERS_PATH} | grep -q chromium" -# The checks run as the remote user, so -r and -x prove the browsers survived the chown back to the -# node feature's owner and stay reachable when the home directory is swapped out underneath them. -check 'check if the browsers path is readable by the remote user' bash -c "test -r ${BROWSERS_PATH} && test -x ${BROWSERS_PATH}" +check 'check if the browsers path is exported' bash -c "test \"${PLAYWRIGHT_BROWSERS_PATH:-}\" = /usr/local/share/ms-playwright" +check 'check if chromium was downloaded' bash -c "ls /usr/local/share/ms-playwright | grep -q chromium" +check 'check if the browsers are readable by the remote user' bash -c "test -r /usr/local/share/ms-playwright && test -x /usr/local/share/ms-playwright" reportResults From 7570a01e23f1ac8630fada24a437441e59e273ba Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 5 Aug 2026 13:18:49 +0000 Subject: [PATCH 4/7] style: drop the comments from the playwright installer Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01MB7zNmdzPYMpwkUWxAjSFt --- src/playwright/install.sh | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/playwright/install.sh b/src/playwright/install.sh index b4ba29c..4548c6e 100755 --- a/src/playwright/install.sh +++ b/src/playwright/install.sh @@ -1,6 +1,4 @@ #!/bin/bash -# Browsers go to a system-wide directory rather than Playwright's default "$HOME/.cache": a sandbox -# may point HOME at a per-pod volume, where anything baked into the image's home is invisible. set -euo pipefail VERSION="${VERSION:-latest}" @@ -26,15 +24,11 @@ if ! command -v npm >/dev/null 2>&1; then exit 1 fi -# The node feature hands the global module tree to the remote user, who installs more global -# packages there at run time. Restore that ownership after installing as root. npm_root="$(npm root -g)" owner="$(stat -c '%u:%g' "${npm_root}")" npm install --global "@playwright/cli@${VERSION}" -# @playwright/cli links only its own binary; the browser installer lives in its playwright-core -# dependency, which npm leaves unlinked and may or may not hoist. playwright_core="$(find "${npm_root}" -maxdepth 6 -path '*/playwright-core/cli.js' -print -quit)" if [ -z "${playwright_core}" ]; then @@ -42,7 +36,6 @@ if [ -z "${playwright_core}" ]; then exit 1 fi -# install-deps needs package lists that an earlier feature may already have dropped. apt-get update -y node "${playwright_core}" install-deps chromium From 3ebb56e0852c54a36fa4f50c366ad2e4f4aaa4b7 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 5 Aug 2026 13:20:41 +0000 Subject: [PATCH 5/7] docs: tighten the playwright notes Says what the feature actually installs, which was missing: chromium only, no firefox or webkit. Notes the ownership the install leaves behind, now that install.sh no longer explains it. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01MB7zNmdzPYMpwkUWxAjSFt --- src/playwright/NOTES.md | 27 ++++++++++++++++----------- src/playwright/README.md | 27 ++++++++++++++++----------- 2 files changed, 32 insertions(+), 22 deletions(-) diff --git a/src/playwright/NOTES.md b/src/playwright/NOTES.md index 47bfa8f..33c9473 100644 --- a/src/playwright/NOTES.md +++ b/src/playwright/NOTES.md @@ -1,7 +1,7 @@ ## Requirements -Select the node feature too — this one installs `@playwright/cli` with `npm` and exits with an -explicit error when `npm` is missing. +The feature installs `@playwright/cli` with `npm`, so the node feature has to be selected alongside +it. The install exits with an explicit error when `npm` is missing rather than half-succeeding. ```json "features": { @@ -10,17 +10,22 @@ explicit error when `npm` is missing. } ``` -Node is in `installsAfter` rather than `dependsOn` on purpose. Features dedupe only when their id +Node is in `installsAfter` rather than `dependsOn` deliberately: features dedupe only when their id *and* options match exactly, so a `dependsOn` entry would install a second copy of the node feature -for anyone who already pins their own version, and whichever copy ran last would win the nvm -default. +for anyone who pins their own version, and whichever copy ran last would win the nvm default. Debian and Ubuntu only. Playwright installs Chromium's shared libraries with apt and has no -equivalent for other package managers, so the feature fails early on anything else. +equivalent for other package managers, so the install fails early on anything else. -## Browsers path +## Browsers -Browsers land in `/usr/local/share/ms-playwright` rather than Playwright's default `$HOME/.cache`, -because a sandbox may point `HOME` at a per-pod volume — anything baked into the image's home -directory is invisible there. `PLAYWRIGHT_BROWSERS_PATH` is set through `containerEnv`, so the -install and every later run agree on that location with no further setup. +Chromium only, alongside the headless shell and ffmpeg that Playwright pulls with it. Firefox and +WebKit are not downloaded. + +They land in `/usr/local/share/ms-playwright` rather than Playwright's default `$HOME/.cache`, +because a sandbox may point `HOME` at a per-pod volume, where anything baked into the image's home +directory is invisible. `PLAYWRIGHT_BROWSERS_PATH` is set through `containerEnv`, so the install and +every later run agree on that location with no further setup. + +The directory is left readable and traversable by the remote user, and the global module tree keeps +the ownership the node feature gave it, so installing more global packages at run time still works. diff --git a/src/playwright/README.md b/src/playwright/README.md index 351bdee..6fa8811 100644 --- a/src/playwright/README.md +++ b/src/playwright/README.md @@ -19,8 +19,8 @@ Installs the Playwright CLI and Chromium ## Requirements -Select the node feature too — this one installs `@playwright/cli` with `npm` and exits with an -explicit error when `npm` is missing. +The feature installs `@playwright/cli` with `npm`, so the node feature has to be selected alongside +it. The install exits with an explicit error when `npm` is missing rather than half-succeeding. ```json "features": { @@ -29,20 +29,25 @@ explicit error when `npm` is missing. } ``` -Node is in `installsAfter` rather than `dependsOn` on purpose. Features dedupe only when their id +Node is in `installsAfter` rather than `dependsOn` deliberately: features dedupe only when their id *and* options match exactly, so a `dependsOn` entry would install a second copy of the node feature -for anyone who already pins their own version, and whichever copy ran last would win the nvm -default. +for anyone who pins their own version, and whichever copy ran last would win the nvm default. Debian and Ubuntu only. Playwright installs Chromium's shared libraries with apt and has no -equivalent for other package managers, so the feature fails early on anything else. +equivalent for other package managers, so the install fails early on anything else. -## Browsers path +## Browsers -Browsers land in `/usr/local/share/ms-playwright` rather than Playwright's default `$HOME/.cache`, -because a sandbox may point `HOME` at a per-pod volume — anything baked into the image's home -directory is invisible there. `PLAYWRIGHT_BROWSERS_PATH` is set through `containerEnv`, so the -install and every later run agree on that location with no further setup. +Chromium only, alongside the headless shell and ffmpeg that Playwright pulls with it. Firefox and +WebKit are not downloaded. + +They land in `/usr/local/share/ms-playwright` rather than Playwright's default `$HOME/.cache`, +because a sandbox may point `HOME` at a per-pod volume, where anything baked into the image's home +directory is invisible. `PLAYWRIGHT_BROWSERS_PATH` is set through `containerEnv`, so the install and +every later run agree on that location with no further setup. + +The directory is left readable and traversable by the remote user, and the global module tree keeps +the ownership the node feature gave it, so installing more global packages at run time still works. --- From 71cd8e3ccf118bc5888e2380c1757a21cc0c091f Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 5 Aug 2026 13:23:22 +0000 Subject: [PATCH 6/7] docs: cut the playwright notes down to what a consumer acts on Drops the ownership paragraph and the installsAfter-versus-dependsOn rationale, which explained our implementation rather than telling anyone what to do. The reader keeps the four things they cannot get from the manifest: node is required, debian and ubuntu only, chromium only, and where the browsers live. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01MB7zNmdzPYMpwkUWxAjSFt --- src/playwright/NOTES.md | 24 ++++++++---------------- src/playwright/README.md | 24 ++++++++---------------- 2 files changed, 16 insertions(+), 32 deletions(-) diff --git a/src/playwright/NOTES.md b/src/playwright/NOTES.md index 33c9473..c3ecc5e 100644 --- a/src/playwright/NOTES.md +++ b/src/playwright/NOTES.md @@ -1,7 +1,7 @@ ## Requirements -The feature installs `@playwright/cli` with `npm`, so the node feature has to be selected alongside -it. The install exits with an explicit error when `npm` is missing rather than half-succeeding. +Select the node feature alongside this one. The install uses `npm` and exits with an explicit error +when it cannot find it — it never installs node for you, so your own version pin stays intact. ```json "features": { @@ -10,22 +10,14 @@ it. The install exits with an explicit error when `npm` is missing rather than h } ``` -Node is in `installsAfter` rather than `dependsOn` deliberately: features dedupe only when their id -*and* options match exactly, so a `dependsOn` entry would install a second copy of the node feature -for anyone who pins their own version, and whichever copy ran last would win the nvm default. - Debian and Ubuntu only. Playwright installs Chromium's shared libraries with apt and has no -equivalent for other package managers, so the install fails early on anything else. +equivalent for other package managers. ## Browsers -Chromium only, alongside the headless shell and ffmpeg that Playwright pulls with it. Firefox and -WebKit are not downloaded. - -They land in `/usr/local/share/ms-playwright` rather than Playwright's default `$HOME/.cache`, -because a sandbox may point `HOME` at a per-pod volume, where anything baked into the image's home -directory is invisible. `PLAYWRIGHT_BROWSERS_PATH` is set through `containerEnv`, so the install and -every later run agree on that location with no further setup. +Chromium only, plus the headless shell and ffmpeg that come with it. Firefox and WebKit are not +downloaded. -The directory is left readable and traversable by the remote user, and the global module tree keeps -the ownership the node feature gave it, so installing more global packages at run time still works. +They live in `/usr/local/share/ms-playwright` rather than Playwright's default `$HOME/.cache`, and +`PLAYWRIGHT_BROWSERS_PATH` is set through `containerEnv` to match. Nothing to configure, and it +keeps working when `HOME` is replaced at run time. diff --git a/src/playwright/README.md b/src/playwright/README.md index 6fa8811..ed271a2 100644 --- a/src/playwright/README.md +++ b/src/playwright/README.md @@ -19,8 +19,8 @@ Installs the Playwright CLI and Chromium ## Requirements -The feature installs `@playwright/cli` with `npm`, so the node feature has to be selected alongside -it. The install exits with an explicit error when `npm` is missing rather than half-succeeding. +Select the node feature alongside this one. The install uses `npm` and exits with an explicit error +when it cannot find it — it never installs node for you, so your own version pin stays intact. ```json "features": { @@ -29,25 +29,17 @@ it. The install exits with an explicit error when `npm` is missing rather than h } ``` -Node is in `installsAfter` rather than `dependsOn` deliberately: features dedupe only when their id -*and* options match exactly, so a `dependsOn` entry would install a second copy of the node feature -for anyone who pins their own version, and whichever copy ran last would win the nvm default. - Debian and Ubuntu only. Playwright installs Chromium's shared libraries with apt and has no -equivalent for other package managers, so the install fails early on anything else. +equivalent for other package managers. ## Browsers -Chromium only, alongside the headless shell and ffmpeg that Playwright pulls with it. Firefox and -WebKit are not downloaded. - -They land in `/usr/local/share/ms-playwright` rather than Playwright's default `$HOME/.cache`, -because a sandbox may point `HOME` at a per-pod volume, where anything baked into the image's home -directory is invisible. `PLAYWRIGHT_BROWSERS_PATH` is set through `containerEnv`, so the install and -every later run agree on that location with no further setup. +Chromium only, plus the headless shell and ffmpeg that come with it. Firefox and WebKit are not +downloaded. -The directory is left readable and traversable by the remote user, and the global module tree keeps -the ownership the node feature gave it, so installing more global packages at run time still works. +They live in `/usr/local/share/ms-playwright` rather than Playwright's default `$HOME/.cache`, and +`PLAYWRIGHT_BROWSERS_PATH` is set through `containerEnv` to match. Nothing to configure, and it +keeps working when `HOME` is replaced at run time. --- From 8f52d0cad60d6b404173e0d575ac3d3546a6a0eb Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 5 Aug 2026 13:25:30 +0000 Subject: [PATCH 7/7] docs: say the node requirement in the feature description The generated Example Usage block shows this feature on its own and cannot be edited, so a reader copying it hit the npm error with nothing warning them first. The description renders above that block, so put it there. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01MB7zNmdzPYMpwkUWxAjSFt --- src/playwright/README.md | 2 +- src/playwright/devcontainer-feature.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/playwright/README.md b/src/playwright/README.md index ed271a2..f6faee0 100644 --- a/src/playwright/README.md +++ b/src/playwright/README.md @@ -1,7 +1,7 @@ # Playwright (playwright) -Installs the Playwright CLI and Chromium +Installs the Playwright CLI and Chromium. Requires the node feature. ## Example Usage diff --git a/src/playwright/devcontainer-feature.json b/src/playwright/devcontainer-feature.json index f7ff830..033fe30 100644 --- a/src/playwright/devcontainer-feature.json +++ b/src/playwright/devcontainer-feature.json @@ -2,7 +2,7 @@ "containerEnv": { "PLAYWRIGHT_BROWSERS_PATH": "/usr/local/share/ms-playwright" }, - "description": "Installs the Playwright CLI and Chromium", + "description": "Installs the Playwright CLI and Chromium. Requires the node feature.", "id": "playwright", "installsAfter": [ "ghcr.io/devcontainers/features/common-utils",