Commit 4f62f36
test: Playwright e2e suite (first-run smoke + env-backed welcome) (#976)
* test: add Playwright e2e suite and wire it into CI
Run the existing Playwright `_electron` suite in a new e2e.yml (Node 24 — the
version Electron bundles — under xvfb, with the Electron binary cached and
materialized for the cached-binary case). Add a launch/cleanup harness:
`launchApp` isolates each test with Electron's native `--user-data-dir` (the app
honors it via app.getPath('userData')) and can seed `app-data.json`; `pageByTitle`
selects a window by title since the app opens several views, most title-less, so
`firstWindow()` is ambiguous. Smoke tests: launch, the Welcome view renders a
Welcome-specific element, the multi-view composition is present (guards the
Phase 3 BrowserView->WebContentsView migration), and the app shuts down without
hanging (close in finally; job timeout bounds it).
Note: AI-assisted (Claude Code). Manually verified: 4 smoke tests pass locally
(yarn build && yarn test:e2e); tsc, eslint, prettier clean.
* test: cover the env-backed welcome actions with a seeded Python env
When a Python env is configured the welcome local-server actions become
enabled; pin that. The test seeds `app-data.json` with a `pythonPath` (the real
config seam the app reads) and asserts the "New notebook" action loses its
`disabled` class. Gated on JLAB_TEST_PYTHON_PATH (CI provisions a venv with
jupyterlab and sets it; skipped locally when absent) so the suite stays green
everywhere. Driving the JupyterLab content itself is left to a separate
plain-Playwright suite against the server URL (Phase 3).
Note: AI-assisted (Claude Code). Manually verified: passes locally with
JLAB_TEST_PYTHON_PATH set to a jupyterlab env; skips cleanly when unset.
* ci: pin jupyterlab and scope e2e concurrency per PR
Address review: pin the CI venv to jupyterlab==4.5.7 (the app's target, matching
env_installer/jlab_server.yaml) so env-backed tests don't drift with upstream
releases; key the concurrency group on the PR number instead of head_ref so
same-named branches across forks don't cancel each other's runs.
Note: AI-assisted (Claude Code). Manually verified: prettier clean.
* test: e2e the labview booting a real Jupyter server
With a Python env seeded, clicking "New notebook" boots a real Jupyter server
and the labview WebContentsView navigates to it; assert it reached the local
server URL via waitForWindowByUrl (no driving the JupyterLab DOM through
Electron). The session reads its env from two seams, so the launch helper now
seeds both: app-data.json (pythonPath enables the welcome + userSetPythonEnvs
registers the env so the registry resolves it) and settings.json (pythonPath
the new session uses to boot). Gated on JLAB_TEST_PYTHON_PATH like the other
env-backed test.
Note: AI-assisted (Claude Code). Manually verified: both env-backed tests pass
locally with JLAB_TEST_PYTHON_PATH set (labview reaches http://localhost:PORT/lab
in ~6s); smoke tests still skip them when unset; tsc/eslint/prettier clean.
* ci: drop the electron cache and install.js, just yarn install
The ~/.cache/electron cache made launch nondeterministic: on a cache hit the
postinstall skips extraction (empty dist -> 'Electron failed to install
correctly'), and an explicit install.js step that fixes the hit case corrupts a
fresh install on a miss. Removing both and letting yarn install's postinstall
download + extract fresh each run is deterministic (~10s) and is the config that
passed before the cache was added.
Note: AI-assisted (Claude Code). Manually verified: matches the previously
passing setup; the cache/install.js permutations that failed are removed.
* ci: temp diagnostic for electron install state [skip-review]
Note: AI-assisted (Claude Code). Temporary, reverted once the cause is known.
* ci: diag electron resolution [temp]
Note: AI-assisted (Claude Code). Temporary diagnostic.
* ci: force a clean Electron binary install before e2e
The diagnostic showed node_modules/electron/dist had the resource files but not
the electron binary itself (dist/electron missing), so require('electron')
threw 'Electron failed to install correctly'. yarn install left dist
half-extracted and the postinstall skipped re-extraction because path.txt
existed. Remove dist/path.txt/the download cache and re-run install.js to
download + extract fresh, asserting the binary is present and executable.
Note: AI-assisted (Claude Code). Manually verified: diagnosed from CI logs
(dist/electron 'No such file or directory'); the test -x guard fails loud if the
binary is still missing.
* ci: debug electron install.js [temp]
Note: AI-assisted (Claude Code). Temporary @electron/get debug.
* ci: check electron zip integrity [temp]
Note: AI-assisted (Claude Code). Temporary.
* ci: extract Electron with system unzip (extract-zip drops the binary)
The downloaded electron zip is complete and valid (96MB, integrity OK), but
electron's postinstall via extract-zip leaves node_modules/electron/dist without
the electron binary on this runner, so launch fails. Let install.js fetch the
zip into the cache, then extract it with the system unzip and write path.txt.
Note: AI-assisted (Claude Code). Diagnosed from CI: zip ZIP_OK + 74 files but
dist/electron 'No such file'; extract-zip is the failing step, system unzip is
the workaround.
* ci: tidy the Electron install step comment
Note: AI-assisted (Claude Code). Comment-only cleanup of the merged iteration notes.
* test: simplify the env-backed e2e
Merge the two env-backed cases into one flow (single app launch): with a seeded
Python env, assert the welcome action is enabled and that clicking New notebook
boots a server the labview reaches. Drop the redundant pip upgrade in CI.
Note: AI-assisted (Claude Code). Manually verified: the merged env test passes
with JLAB_TEST_PYTHON_PATH set (~12s); the 4 smoke tests still pass and the env
test skips when unset; tsc/eslint/prettier clean.
* test: drop the hardcoded jupyterlab version from the e2e seed
The app re-detects the env's jupyterlab version on launch, so the seeded
userSetPythonEnvs entry does not need a versions field; pinning it to a literal
duplicated the version with the pip install in the workflow and would go stale on
a jupyterlab bump. The labview test still boots a real server without it.
Note: AI-assisted (Claude Code). Manually verified: the env-backed labview test
passes with the versions field removed (~10s); the 5-test suite stays green;
tsc/eslint/prettier clean.
* test(e2e): track the bundled jupyterlab version automatically
Read the pin from env_installer/jlab_server.yaml so a bump there propagates to
the e2e env without a second edit, with a guard that fails the job if the format
ever changes (so we notice instead of silently installing latest).
Note: AI-assisted (Claude Code). Manually verified: awk extracts '4.5.7' from
the current jlab_server.yaml; prettier clean on the workflow.
* test(e2e): tighten the jupyterlab-pin parse to a semver match
The non-empty check accepted operator-prefixed pins (`>=4.5.7`) that would
make pip fail with a confusing message; require an exact x.y.z so the job fails
with a clear hint pointing back at the env spec.
Note: AI-assisted (Claude Code). Manually verified: the regex accepts 4.5.7 and
rejects empty / operator-prefixed / multi-line inputs.
---------
Co-authored-by: Michał Krassowski <5832902+krassowski@users.noreply.github.com>1 parent aded203 commit 4f62f36
6 files changed
Lines changed: 255 additions & 69 deletions
File tree
- .github/workflows
- test/e2e
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
| 11 | + | |
| 12 | + | |
11 | 13 | | |
12 | 14 | | |
13 | 15 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
7 | | - | |
| 7 | + | |
| 8 | + | |
8 | 9 | | |
9 | 10 | | |
10 | 11 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | | - | |
2 | | - | |
3 | | - | |
4 | | - | |
5 | | - | |
6 | | - | |
7 | | - | |
8 | | - | |
9 | | - | |
10 | | - | |
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
11 | 13 | | |
12 | 14 | | |
13 | | - | |
14 | | - | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
15 | 29 | | |
16 | 30 | | |
17 | | - | |
18 | | - | |
19 | | - | |
20 | | - | |
21 | | - | |
22 | | - | |
23 | | - | |
24 | | - | |
25 | | - | |
26 | | - | |
27 | | - | |
28 | | - | |
29 | | - | |
30 | | - | |
31 | | - | |
32 | | - | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
33 | 46 | | |
34 | 47 | | |
35 | | - | |
36 | | - | |
37 | | - | |
38 | | - | |
39 | | - | |
40 | | - | |
41 | | - | |
42 | | - | |
43 | | - | |
44 | | - | |
45 | | - | |
46 | | - | |
47 | | - | |
48 | | - | |
49 | | - | |
50 | | - | |
51 | | - | |
52 | | - | |
53 | | - | |
54 | | - | |
55 | | - | |
56 | | - | |
57 | | - | |
58 | | - | |
59 | | - | |
60 | | - | |
61 | | - | |
62 | | - | |
63 | | - | |
64 | | - | |
65 | | - | |
66 | | - | |
67 | | - | |
68 | | - | |
69 | | - | |
70 | | - | |
71 | | - | |
72 | | - | |
73 | | - | |
74 | | - | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
75 | 60 | | |
0 commit comments