Skip to content

Lite: capture PR screenshots in a container - #15381

Closed
PavelLaptev wants to merge 3 commits into
masterfrom
lite-screenshots-container
Closed

PavelLaptev wants to merge 3 commits into
masterfrom
lite-screenshots-container

Conversation

@PavelLaptev

Copy link
Copy Markdown
Contributor

🧢 Changes

Follows up on #15350, and on @samhh's note there that the plan was a second Playwright config running under Docker for cross-platform screenshot support.

  • apps/lite/e2e/docker/Dockerfile — Playwright's v1.58.2-noble base (Chromium's shared libraries, Xvfb, fonts), plus a Rust toolchain for but and the napi SDK, libxss1, rsync, and a pinned fallback font set. LANG=C.UTF-8, TZ=UTC, and a 1600×1000 Xvfb screen.
  • apps/lite/e2e/docker/run-capture.sh — the entrypoint: mirror the checkout, build, capture.
  • apps/lite/e2e/docker/capture.sh — the host wrapper, capture.sh before / capture.sh after.
  • apps/lite/e2e/playwright.docker.config.ts — the second config. Capture spec only, one worker, two retries, and the Chromium switches.
  • apps/lite/e2e/test.ts — a typed electronArgs option, defaulting to empty, spread into the Electron launch.
  • .github/workflows/lite-screenshots-container-proof.yml — temporary, delete before merge. Its own commit so it drops cleanly.

☕️ Reasoning

The failure that killed CI capture is container-shaped. Every abandoned run logged sandboxed_renderer.bundle.js script failed to run — Chromium's setuid sandbox failing to initialise. A hosted runner gives no way to change that. A container does, which is why the fix is an image rather than another attempt at the same job.

The second thing it buys is commensurability. A before/after pair only means something if both halves were rendered the same way, and font hinting and device scale differ between two contributors' machines before the UI is even considered. With one OS, one font set and one rasteriser, the two halves no longer have to be captured by the same person.

The host checkout is mounted read-only and mirrored into a volume, not built in place. A macOS checkout carries a macOS Electron and macOS native modules under node_modules, plus a macOS target/. *.node is excluded from the mirror and the napi SDK rebuilt inside, so the Linux artefact replaces the macOS one rather than racing it. Nothing in a developer's tree is written to.

The switches are an option on the fixture, not an env var. They live visibly in the config a reader would look in, and the default path stays byte-identical — which is the same reason #15350 dropped the GPU flags rather than keeping them.

The whole screenshots/ directory is mounted, not the one output directory, because the spec clears its own output directory before capturing and rm on a mount point fails with EBUSY.

⚠️ Unverified

None of this has run. There is no container runtime on the machine it was written on — no Docker, Podman, colima or nerdctl. That is what the temporary workflow is for: the claim is specifically about the Linux runners, so a runner is the only place it can be answered.

What has been checked: pnpm -F @gitbutler/lite check passes and --listFiles confirms the new config is genuinely in the program; oxlint, oxfmt, prettier, knip:prod and knip:non-prod are clean.

Two risks in the proving run, both about the runner rather than the design:

  • Disk. A debug build of the workspace plus the ~2GB base image is tight. The workflow reclaims roughly 30GB first; the debug target size is unmeasured.
  • Time. Cold every run — the volumes that make this cheap locally are empty on an ephemeral runner. @gitbutler/but-sdk's build is four cargo invocations, so the SDK compiles twice over. If it times out, trimming the graph variant is the first lever.

📌 Follow-ups

  • Delete lite-screenshots-container-proof.yml before merge.
  • If the proof is green, real before/after capture in CI becomes possible — and it sidesteps the unapply problem entirely: check out the head SHA and capture, then the base SHA and capture, with no but unapply and so no stacked-branch caveat.
  • Lite's host e2e currently fails all three specs with Error: Process failed to launch! on this machine, on master as well as here. Unrelated to this branch, but it blocks the host capture flow.

@PavelLaptev
PavelLaptev force-pushed the lite-screenshots-container branch 4 times, most recently from c350777 to 0a87e68 Compare August 17, 2026 22:37
screenshots.spec.ts emptied its output directory in test.beforeAll. Playwright
restarts the worker after a failure and runs the hook again, so any failing
surface deleted every surface captured before it: a run reporting "3 passed"
could leave nothing on disk at all.

The intent was right — a PNG left by an earlier run must not survive a rerun in
which its own surface failed, or it gets compared as though this run produced
it. Only the hook was wrong. Moved to a globalSetup shared by both configs,
which runs exactly once, and left the mkdir in beforeAll where repeating is
harmless.
@PavelLaptev
PavelLaptev force-pushed the lite-screenshots-container branch from 0a87e68 to 4e8edd8 Compare August 17, 2026 23:02
Capture runs on a developer's machine because it was abandoned on the Linux
runners: three of eight surfaces hung under the bare X server, and every log
carried "sandboxed_renderer.bundle.js script failed to run". That message is
Chromium's setuid sandbox failing to initialise — not something a hosted runner
lets you change, and something a container does.

Adds a second Playwright config and an image that runs the existing catalogue
under Xvfb. The host checkout is mounted read-only and mirrored into a volume,
so a macOS node_modules and target/ are never written to; the Linux Electron,
`but` and napi SDK live in volumes of their own.

The other thing it buys is commensurability. A before/after pair only means
something if both halves were rendered the same way, and font hinting and device
scale differ between two machines before the UI is even considered. With the OS,
font set and rasteriser fixed, the two sides no longer have to be captured by
the same person.

The switches live in the new config rather than the shared launch, so an
ordinary local run still renders the way the developer's machine does.
Delete before this branch merges.

The claim under test is specifically about the Linux runners, and there is no
container runtime on the machine this was written on — so the only place it can
be answered is a runner. Captures one side and uploads it as an artifact; a
surface that hangs fails the job on the spec's own 180s budget. Not a
before/after run.
@PavelLaptev
PavelLaptev force-pushed the lite-screenshots-container branch from 4e8edd8 to 333fd22 Compare August 17, 2026 23:22
@PavelLaptev
PavelLaptev deleted the lite-screenshots-container branch August 30, 2026 15:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant