|
| 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