Skip to content

Drive the app end to end, in CI - #7

Merged
timbogdanov merged 1 commit into
mainfrom
feat/e2e-harness
Aug 19, 2026
Merged

Drive the app end to end, in CI#7
timbogdanov merged 1 commit into
mainfrom
feat/e2e-harness

Conversation

@timbogdanov

Copy link
Copy Markdown
Owner

Phase 3 of the v0.2.0 plan — proof.

The suite was strong on pure functions, and CI already launched the app and asserted it booted. Nothing drew a stroke, saved a file, reopened it and checked the result.

That gap is not hypothetical. v0.1.0 shipped a macOS build that could not be opened at all, because every check ran against a build nobody had downloaded. This phase deliberately lands before the two large features, on the reasoning that a check written afterwards tends to confirm what already happened.

What it does

tests/e2e/drive.mjs drives the real window through 25 checks — real mouse input through the browser's own event pipeline, the real native save path, the real document round-trip — in the existing smoke job, which already installs xvfb and gives Chromium its sandbox.

It also pins the properties the last two phases added, in the running app rather than in a sandbox:

  • the canvas stays paper and ink while the preview carries the duotone;
  • dragging the map view moves the territory into negative world coordinates and still renders.

Two traps, recorded so nobody pays twice

Both cost real time to find, and both are documented at the top of the file:

  • The renderer's CSP has no unsafe-eval, so every evaluate() must be handed a function. A string gets eval'd, the page blocks it, and the failure reads as a mysterious undefined rather than as a CSP error.
  • The canvas calls setPointerCapture(), which throws on a synthetic PointerEvent. Drawing has to go through page.mouse.

Skip, don't fail

It exits 2 — "could not run", not "found a problem" — when electron or a display is missing, so verify-all.sh reports a loud skip on a bare checkout while CI treats it as a hard gate. Verified both paths: 25/25 locally, exit 2 with a bogus --app.

Screenshots upload on failure as well as success, since a failed run is when a picture is worth most.

playwright-core is the first devDependency beyond the build toolchain. Nothing ships with it — the packaged asar is still electron/, app/, package.json and LICENSE.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UCrKKTD2mwHx8Fm1EnJiQ3

The suite was strong on pure functions and CI already launched the app and
asserted it booted. Nothing drew a stroke, saved a file, reopened it and checked
the result. That gap is not hypothetical: v0.1.0 shipped a macOS build that could
not be opened at all, because every check ran against a build nobody had
downloaded.

tests/e2e/drive.mjs drives the real window through 25 checks — real mouse input
through the browser's own event pipeline, the real native save path, the real
document round-trip — and runs in the existing smoke job, which already installs
xvfb and gives Chromium its sandbox.

It also pins the two properties the last two phases added, in the running app
rather than in a sandbox: the canvas stays paper and ink while the preview
carries the duotone, and dragging the map view moves the territory into negative
world coordinates and still renders.

Two things it had to get right, both of which cost real time to find and are
recorded at the top of the file so the next person does not pay again:

  * The renderer's CSP has no 'unsafe-eval', so every evaluate() must be handed a
    function. A string gets eval'd, the page blocks it, and the failure reads as
    a mysterious undefined rather than as a CSP error.
  * The canvas calls setPointerCapture(), which throws on a synthetic
    PointerEvent, so drawing has to go through page.mouse.

It exits 2 — "could not run" rather than "found a problem" — when electron or a
display is missing, so verify-all.sh reports a skip on a bare checkout while CI
treats it as a hard gate. Screenshots upload on failure as well as success, since
a failed run is when a picture is worth most.

playwright-core is the first devDependency beyond the build toolchain. Nothing
ships with it: the packaged asar is still electron/, app/, package.json and
LICENSE.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UCrKKTD2mwHx8Fm1EnJiQ3
@timbogdanov
timbogdanov merged commit 7fac99f into main Aug 19, 2026
5 checks passed
@timbogdanov
timbogdanov deleted the feat/e2e-harness branch August 19, 2026 01:53
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