Skip to content

Commit 127c0ed

Browse files
committed
Add reload-matrix harness for ground-truth dev reload measurement
1 parent 5e15051 commit 127c0ed

7 files changed

Lines changed: 1577 additions & 0 deletions

File tree

scripts/reload-matrix/README.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# reload-matrix
2+
3+
Ground-truth reload measurement for Extension.js dev. Each scenario runs the
4+
real dev pipeline end-to-end against a real fixture in `examples/`,
5+
attaches a passive CDP observer to the launched Chrome, performs a controlled
6+
file edit, and counts the actual lifecycle events the user would see.
7+
8+
The matrix is the executable counterpart of `RELOAD_MATRIX.md` at the root of
9+
this workspace. When the documented expectation drifts from runtime reality,
10+
the matrix flips to FAIL with the specific cell.
11+
12+
## Run modes
13+
14+
| Mode | What it tests | When to run |
15+
| ----------------- | ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
16+
| `local` (default) | The CLI built from `programs/extension/dist/cli.cjs` in this monorepo. | Inner-loop while iterating on reload-pipeline fixes. |
17+
| `remote` | The published `extension@<tag>` from npm, fetched via `npx`. | Publish gate — verifies the canary that was just uploaded behaves the same as local. Catches packaging bugs (missing files, broken bin entry, wrong cross-package deps). |
18+
19+
```sh
20+
# from _FUTURE/examples
21+
pnpm test:reload-matrix # local mode, 3 reps per scenario
22+
pnpm test:reload-matrix:smoke # one scenario, full event timeline printed
23+
pnpm test:reload-matrix:remote # remote mode against extension@canary
24+
25+
# environment knobs
26+
RELOAD_MATRIX_REPEAT=5 pnpm test:reload-matrix
27+
RELOAD_MATRIX_FILTER=locales pnpm test:reload-matrix
28+
RELOAD_MATRIX_TAG=next pnpm test:reload-matrix:remote
29+
```
30+
31+
## What it measures
32+
33+
For every scenario the harness reports two counts attributed to the user
34+
extension origin (companion-extension noise is filtered out):
35+
36+
- **`sw`**: service-worker restarts. Computed as `min(serviceWorkerCreated, serviceWorkerDestroyed)` for the user's `chrome-extension://<id>/` origin between the first edit and the post-edit quiescence point.
37+
- **`nav`**: extension-page navigations. The number of `Page.frameNavigated` events for the user origin during the same window.
38+
39+
These are the signals that map 1:1 to what the user sees:
40+
41+
- `sw=1 nav=0` — the extension reloaded silently (action button blink, popup closes if open).
42+
- `sw=0 nav=1` — an open extension page refreshed via livereload; SW state preserved.
43+
- `sw=0 nav=0` — no visible disruption (HMR applied a module update, or no open page existed).
44+
- `sw=N nav=M` with N or M unexpectedly high — the bug we're hunting.
45+
46+
## Adding a scenario
47+
48+
Edit `scenarios.mjs`. Each row is a JS object with:
49+
50+
- `name`: short label.
51+
- `fixturePath`: absolute path; resolve via `resolveTemplateFixture('<template>')`.
52+
- `openPages`: array of relative URLs inside the extension (e.g. `'action/index.html'`) the harness opens before the edit. Use this to put pages in the user-observable state for the scenario.
53+
- `edits`: sequence of `{relativePath, transform, waitMsAfter}` records. `transform(current)` returns the new file contents.
54+
- `expected`: any combination of:
55+
- `serviceWorkerRestarts: number` — exact count.
56+
- `serviceWorkerRestartsAtMost: number` — upper bound, for cases Chrome may legitimately exceed by 1 under memory pressure.
57+
- `extensionPageNavigations: number` — exact count.
58+
- `extensionPageNavigationsAtMost: number` — upper bound.
59+
60+
Cross-reference the new row against `RELOAD_MATRIX.md`. If the doc's cell is
61+
wrong, update it in the same commit so the doc and the runtime match.
62+
63+
## How it works
64+
65+
1. The fixture is copied to a temp directory so edits never leak back into the repo.
66+
2. `dev` is spawned as a child process; stdout is parsed for the CDP debug port and the dev banner.
67+
3. `cdp-observer.mjs` connects a second CDP client to the same browser via `/json/version` and subscribes to `Target.setAutoAttach`. It is passive — it never sends commands that mutate state.
68+
4. After a startup quiescence window, the harness records the event count baseline, opens any requested pages, performs edits, and waits for a post-edit quiescence window.
69+
5. Events are bucketed by extension origin and the user origin is identified by the `Extension ID <id>` line in the dev banner.
70+
6. Cleanup: dev process killed (SIGTERM → SIGKILL after 4s), observer WebSocket closed, temp directory removed.
71+
72+
## Troubleshooting
73+
74+
- **"Local CLI build not found"**: run `pnpm --dir programs/extension run compile` from the monorepo root.
75+
- **"Timed out waiting for CDP debug port"**: dev failed to launch. Run `node scripts/reload-matrix/run-smoke.mjs` to surface the dev process stdout/stderr.
76+
- **Flaky scenarios on Linux CI**: bump `RELOAD_MATRIX_REPEAT` and watch for genuine non-determinism vs. timing slack. The harness's quiescence windows are conservative but not infinite.

0 commit comments

Comments
 (0)