Skip to content

feat(image): browser testing in the session image - #74

Merged
jiashuoz merged 38 commits into
mainfrom
rainier/browser-testing-image
Sep 10, 2026
Merged

feat(image): browser testing in the session image#74
jiashuoz merged 38 commits into
mainfrom
rainier/browser-testing-image

Conversation

@jiashuoz

@jiashuoz jiashuoz commented Sep 9, 2026

Copy link
Copy Markdown
Member

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 link preserves project-installed browsers, repairs baseline links, and invalidates completion markers for retired image-local revisions so Playwright can reinstall them. Package-local PLAYWRIGHT_BROWSERS_PATH=0 is 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-e2e step: 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-security fixture 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 sets chromiumSandbox: 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.

jiashuoz and others added 3 commits September 9, 2026 16:40
`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>
@jiashuoz

jiashuoz commented Sep 9, 2026

Copy link
Copy Markdown
Member Author

Cloud-side half: https://github.com/tokencanopy/rainier-cloud/pull/69 — the sandbox analysis this PR's docs/session-image.md points at (docs/security/browser-sandbox-in-a-session.md), the measured size delta in the rollout runbook, the one egress host a project on another Playwright version would need, and scripts/browser-session-qualification.sh, which runs that repository's own web/e2e suite inside this image under the hosted seccomp and AppArmor profiles.

Neither PR deploys, publishes or changes host policy.

@jiashuoz
jiashuoz marked this pull request as draft September 9, 2026 17:07
@jiashuoz
jiashuoz marked this pull request as ready for review September 10, 2026 00:47
@jiashuoz
jiashuoz merged commit a378d38 into main Sep 10, 2026
2 checks passed
@jiashuoz
jiashuoz deleted the rainier/browser-testing-image branch September 10, 2026 00:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant