feat(image): browser testing in the session image - #74
Merged
Conversation
`npx playwright install --with-deps chromium` is the line every project's CI runs and the line a Rainier session cannot: --with-deps is an apt install as root, and a session has no escalation path, a read-only rootfs, and no package archive on its egress allowlist. So a developer who wanted to run their project's browser tests in a session had to discover sixteen missing shared libraries one "error while loading shared libraries" at a time, and then find they could not install any of them. Both halves of that command are now in the image. The shared libraries and fonts are an ordinary build-time apt layer — Playwright's own debian12-x64 chromium dependency set, named in full because the tool that knows it needs root to act on it. The browser is one checksum-pinned Chrome for Testing headless shell beside the rest of the toolchain, laid out exactly where a project's Playwright looks and linked into the workspace browser cache by the image itself, so docker copies the links onto a freshly created volume and a fresh session runs a suite with nothing downloaded and no network at all. What is deliberately NOT installed is Playwright. A global `playwright` on PATH is what a bare `npx playwright` finds, and it would drive a browser revision the project never pinned; the version that runs a project's tests is the one in the project's lockfile, always. The image installs browsers; the project installs Playwright. 1.63.x matches the baseline and downloads nothing; any other version installs its own revision into the workspace cache beside it, which is a documented, predictable ~114 MiB paid once per workspace. Nothing was relaxed to make a browser start. No --privileged, no --cap-add, no seccomp or apparmor unconfined, no host network or IPC, no wider mount, no /dev/shm change, and no --no-sandbox from this image or this driver — Playwright's own chromiumSandbox default is false, and what isolates the browser is the container it already ran in. TestDockerGrantsNoBrowserPrivilege and TestSessionImageDoesNotDisableBrowserSafety fail the build if any of that changes. Chromium's own layer-1 sandbox needs a user namespace that neither docker's default seccomp profile nor the hosted one admits; the smoke reports the observed status rather than asserting it, because which policy a host applies is not an image property. Qualification is in two pieces. The existing smoke gains offline browser checks — root-owned baseline, seeded cache, pinned build, every library resolved, a page rendered and screenshotted at 1280x800 and at 390x844, Arial laid out at Liberation's metrics rather than a fallback's, and no browser process left behind. scripts/session-image-browser-e2e.sh is new and is the only qualification step with a network: it stages a sample project that has never been in the image, `npm ci`s its locked dependencies, then runs its Playwright suite twice on --network none and goes looking for the trace, screenshot and video a deliberate failure has to leave behind. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Numbers off the qualification run rather than estimates: the apt layer is 26 packages and 30,744 KiB, the browser payload is 272,100 KiB, and the whole image goes from 2,416,401,380 bytes in 22 layers to 2,725,950,441 in 28 — +295 MiB for the capability, with what dropping the browser or adding the full Chrome for Testing build would cost beside it. Also the sandbox evidence, which had been described and is now observed: a launch that asks for Chromium's own sandbox exits 133 with 'No usable sandbox!' and renders nothing. Failing closed is the property worth having; a browser that reported no sandbox and drew the page anyway is the one to guard against. And the practical note a suite hits first: a session's http_proxy is the egress proxy and Chromium reads it, so a test web server has to be on loopback, which no_proxy carries. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The sample suite asserts the browser build against its own Playwright's browsers.json, which is the stronger check — it proves the project resolved the match rather than that the script and the Dockerfile agree. The Dockerfile read left behind by the earlier shape was doing nothing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Member
Author
|
Cloud-side half: https://github.com/tokencanopy/rainier-cloud/pull/69 — the sandbox analysis this PR's Neither PR deploys, publishes or changes host policy. |
jiashuoz
marked this pull request as draft
September 9, 2026 17:07
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds a pinned Chromium headless browser, ffmpeg, shared libraries, and fonts to the session image so projects can run their own pinned Playwright tests without root or runtime OS installation. No global Playwright package is installed. The supported baseline uses Playwright 1.63.x; other revisions use the project's writable browser cache.
The root-owned browser payload is linked into the workspace cache.
rainier-browsers linkpreserves project-installed browsers, repairs baseline links, and invalidates completion markers for retired image-local revisions so Playwright can reinstall them. Package-localPLAYWRIGHT_BROWSERS_PATH=0is rejected by the helper instead of being treated as a literal directory.Validation includes native amd64 image qualification, offline image checks, and a self-contained
session-image-browser-e2estep: a fresh locked project, desktop/phone navigation, two offline runs, failure artifacts, and process cleanup. Approximate image growth is 295 MiB uncompressed.This PR no longer checks out, imports, or requires credentials for any Cloud repository. The public
testdata/session-securityfixture is a synthetic, test-only snapshot with pinned hashes and exact namespace rules. It is validated in this repository's CI. Hosted Rainier Cloud owns its deployment profile and qualifies that copy independently; runtime rollout is an integration concern, not a build dependency of core.Chromium's own sandbox remains required. The image and driver never add
--no-sandbox; the sample Playwright project setschromiumSandbox: true. The qualification host currently reaches the sandbox launch and exits 133 under the loaded seccomp/AppArmor policy, so the PR remains blocked on that runtime qualification. No host-policy relaxation, deployment, or image publication is included.