Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions src/playwright/NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,28 @@ when it cannot find it — it never installs node for you, so your own version p
Debian and Ubuntu only. Playwright installs Chromium's shared libraries with apt and has no
equivalent for other package managers.

## Node versions

The CLI is installed under its own prefix, `/usr/local/share/playwright-cli`, and symlinked to
`/usr/local/bin/playwright-cli`. It is deliberately not a plain `npm install --global`: the node
feature installs node through nvm, whose global root is one directory per node version
(`$NVM_DIR/versions/node/<version>/lib/node_modules`), with `$NVM_DIR/current/bin` on `PATH`. A
`npm install --global` there belongs to whichever version was active at build time, so `nvm install`
or `nvm use` repoints `current` and `playwright-cli` drops off `PATH` for the whole container —
not just the shell that switched.

Switch node freely; one copy of the CLI stays reachable, and it runs on whatever node is active
(the package needs node 18 or newer).

The install stays root-owned and read-only to the remote user. One copy now backs every node
version, so no single user should be able to rewrite what the others execute.

It runs from environments that inherit `containerEnv`, which is how the CLI finds both node and its
browsers. `sudo` is not one of them — it replaces `PATH` with `secure_path` and drops `containerEnv`,
and `sudo playwright-cli` has never worked for this feature. Making it work would mean root
executing an interpreter and a browser out of trees the remote user owns, so it stays unsupported;
run the CLI as the remote user.

## Browsers

Chromium only, plus the headless shell and ffmpeg that come with it. Firefox and WebKit are not
Expand Down
22 changes: 22 additions & 0 deletions src/playwright/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,28 @@ when it cannot find it — it never installs node for you, so your own version p
Debian and Ubuntu only. Playwright installs Chromium's shared libraries with apt and has no
equivalent for other package managers.

## Node versions

The CLI is installed under its own prefix, `/usr/local/share/playwright-cli`, and symlinked to
`/usr/local/bin/playwright-cli`. It is deliberately not a plain `npm install --global`: the node
feature installs node through nvm, whose global root is one directory per node version
(`$NVM_DIR/versions/node/<version>/lib/node_modules`), with `$NVM_DIR/current/bin` on `PATH`. A
`npm install --global` there belongs to whichever version was active at build time, so `nvm install`
or `nvm use` repoints `current` and `playwright-cli` drops off `PATH` for the whole container —
not just the shell that switched.

Switch node freely; one copy of the CLI stays reachable, and it runs on whatever node is active
(the package needs node 18 or newer).

The install stays root-owned and read-only to the remote user. One copy now backs every node
version, so no single user should be able to rewrite what the others execute.

It runs from environments that inherit `containerEnv`, which is how the CLI finds both node and its
browsers. `sudo` is not one of them — it replaces `PATH` with `secure_path` and drops `containerEnv`,
and `sudo playwright-cli` has never worked for this feature. Making it work would mean root
executing an interpreter and a browser out of trees the remote user owns, so it stays unsupported;
run the CLI as the remote user.

## Browsers

Chromium only, plus the headless shell and ffmpeg that come with it. Firefox and WebKit are not
Expand Down
2 changes: 1 addition & 1 deletion src/playwright/devcontainer-feature.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@
"type": "string"
}
},
"version": "1.1.0"
"version": "1.2.0"
}
18 changes: 12 additions & 6 deletions src/playwright/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ set -euo pipefail

VERSION="${VERSION:-latest}"
BROWSERS_PATH="/usr/local/share/ms-playwright"
INSTALL_PATH="/usr/local/share/playwright-cli"

export DEBIAN_FRONTEND=noninteractive
export PLAYWRIGHT_BROWSERS_PATH="${BROWSERS_PATH}"
Expand All @@ -24,15 +25,15 @@ if ! command -v npm >/dev/null 2>&1; then
exit 1
fi

npm_root="$(npm root -g)"
owner="$(stat -c '%u:%g' "${npm_root}")"
owner="$(stat -c '%u:%g' "$(npm root -g)")"

npm install --global "@playwright/cli@${VERSION}"
npm install --global --prefix "${INSTALL_PATH}" "@playwright/cli@${VERSION}"

playwright_core="$(find "${npm_root}" -maxdepth 6 -path '*/playwright-core/cli.js' -print -quit)"
node_modules="${INSTALL_PATH}/lib/node_modules"
playwright_core="$(find "${node_modules}" -maxdepth 6 -path '*/playwright-core/cli.js' -print -quit)"

if [ -z "${playwright_core}" ]; then
echo "playwright-core was not found under ${npm_root}" >&2
echo "playwright-core was not found under ${node_modules}" >&2
exit 1
fi

Expand All @@ -44,5 +45,10 @@ 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}"
ln -sfn "${INSTALL_PATH}/bin/playwright-cli" /usr/local/bin/playwright-cli

chown -R root:root "${INSTALL_PATH}"
chmod -R a+rX,go-w "${INSTALL_PATH}"

chown -R "${owner}" "${BROWSERS_PATH}"
chmod -R a+rX "${BROWSERS_PATH}"
2 changes: 1 addition & 1 deletion test/playwright/scenarios.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"ubuntu": {
"features": {
"ghcr.io/devcontainers/features/common-utils:2": {},
"ghcr.io/devcontainers/features/node:1": {},
"ghcr.io/devcontainers/features/node:2": {},
"playwright": {
"version": "latest"
}
Expand Down
27 changes: 27 additions & 0 deletions test/playwright/ubuntu.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,37 @@ set -e
# shellcheck source=/dev/null
source \
dev-container-features-test-lib

NVM_DIR="${NVM_DIR:-/usr/local/share/nvm}"

outside_nvm() {
case "$(readlink -f "$(command -v playwright-cli)")" in
"${NVM_DIR}"/*) return 1 ;;
esac
}

survives_node_switch() {
# shellcheck source=/dev/null
. "${NVM_DIR}/nvm.sh" &&
nvm install 20 >/dev/null &&
nvm use 20 >/dev/null &&
test "$(node -v | cut -d. -f1)" = v20 &&
playwright-cli --version
}

not_writable_by_remote_user() {
! test -w /usr/local/bin/playwright-cli &&
! test -w /usr/local/share/playwright-cli/bin/playwright-cli
}

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:-}\" = /usr/local/share/ms-playwright"
check 'check if the default browser is chromium' bash -c "test \"${PLAYWRIGHT_MCP_BROWSER:-}\" = chromium"
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"
check 'check if the browser opens with no --browser flag' bash -c "PLAYWRIGHT_CLI_SESSION=featuretest playwright-cli open && PLAYWRIGHT_CLI_SESSION=featuretest playwright-cli close"
check 'check if playwright-cli lives outside the nvm version directories' outside_nvm
check 'check if the shared install is read-only to the remote user' not_writable_by_remote_user
# Keep last: repoints nvm's "current" symlink for every later check.
check 'check if playwright-cli survives a node version switch' survives_node_switch
reportResults
Loading