Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
90 commits
Select commit Hold shift + click to select a range
0cb322c
Migrated baseline constants into shared package
vishnuv688 May 29, 2026
4f101cc
Resolved import across all the packages
vishnuv688 May 29, 2026
cc57c89
Resolved import across all the packages - 2
vishnuv688 May 29, 2026
815cb30
Created core package
vishnuv688 May 29, 2026
a9f5ae6
core: extract console capture, UID helpers, and Chrome log-level mapp…
vishnuv688 May 29, 2026
6f9c196
core: extract chromeLogLevelToLogLevel, isPortInUse, findFreePort, an…
vishnuv688 May 29, 2026
cd7c1ad
core: extract isUserCodeFrame, normalizeFilePath, and getCallSourceFr…
vishnuv688 May 29, 2026
53883e5
shared: add typed HTTP/WS contracts for the baseline endpoints; backe…
vishnuv688 May 29, 2026
0317899
shared: add typed HTTP/WS contracts for the baseline endpoints; backe…
vishnuv688 May 29, 2026
59f069f
shared: tighten ConsoleLog.source to a named LogSource union; core LO…
vishnuv688 May 29, 2026
c899daa
service: extend @wdio/reporter module augmentation with Cucumber pick…
vishnuv688 May 29, 2026
6579fd8
core: extract serializeError helper; shared: add WS_PATHS constant; a…
vishnuv688 May 29, 2026
9c9b9a4
chore: move all demo projects under examples/ and add demo:wdio/demo:…
vishnuv688 May 29, 2026
a1fe61d
core: extract TestReporterBase shared by nightwatch + selenium report…
vishnuv688 Jun 1, 2026
c576f1f
selenium: fix cucumber rerun lifecycle — defer WS close to onTestRunC…
vishnuv688 Jun 1, 2026
91f6800
backend: split runner.ts into framework-filters and bin-resolver modules
vishnuv688 Jun 1, 2026
cca884c
docs: drop backend/index.ts and runner.ts from CLAUDE.md god-files list
vishnuv688 Jun 1, 2026
d07e867
docs: update CLAUDE.md §7 to note explorer.ts pay-down (670 → 506)
vishnuv688 Jun 1, 2026
0f9961b
refactor: pay down §7 god-files — extract pure logic into focused mod…
vishnuv688 Jun 1, 2026
a9deadf
app(controller): add 19 unit tests for suite-merge module (canonical-…
vishnuv688 Jun 1, 2026
380864d
service(utils): extract Cucumber step-definition discovery into utils…
vishnuv688 Jun 1, 2026
f3c7656
app(browser): extract snapshot component styles into snapshot-styles …
vishnuv688 Jun 1, 2026
9e6768b
service(utils): extract source-mapping (mapTestToSource/mapSuiteToSou…
vishnuv688 Jun 1, 2026
8c438ca
service(vite.config): include -prefixed imports in the bundle scope s…
vishnuv688 Jun 1, 2026
2f4c2ff
app(controller): extract markAllRunning/markSpecificRunning tree tran…
vishnuv688 Jun 1, 2026
be8cd0d
chore: code extraction
vishnuv688 Jun 1, 2026
d87a45e
shared: hoist REUSE_ENV (rerun handshake env-var names) into shared/r…
vishnuv688 Jun 1, 2026
be25fb9
shared: hoist WS_SCOPE (control-frame scope names) into shared/routes…
vishnuv688 Jun 1, 2026
c784f40
test: add 27 unit tests covering mark-running tree transforms, run-de…
vishnuv688 Jun 1, 2026
ee61023
nightwatch(browserProxy): eliminate 9 casts via single dynamic-recor…
vishnuv688 Jun 1, 2026
1165384
chore: eliminate casts from production sources
vishnuv688 Jun 1, 2026
598018c
core: add errorMessage(value) helper; replace 54 (err as Error).messa…
vishnuv688 Jun 1, 2026
db6e7a4
core: hoist sources Map + captureSource(filePath) into SessionCapture…
vishnuv688 Jun 1, 2026
9a5c807
core: hoist trace-payload state fields and processTracePayload() into…
vishnuv688 Jun 1, 2026
5471ce9
core: dedupe sendCommand override; extract loadInjectableScript + pol…
vishnuv688 Jun 2, 2026
f173f30
core: extract ScreencastRecorderBase, video-encoder, finalize-screenc…
vishnuv688 Jun 2, 2026
ad2499b
docs: update READMEs for cross-adapter screencast and preserve-and-re…
vishnuv688 Jun 2, 2026
02c5fd6
chore: Extract suiteManager + testManager into core
vishnuv688 Jun 2, 2026
73984a8
chore: Promote nightwatch to BiDi network + console capture parity
vishnuv688 Jun 2, 2026
140e62e
chore: Promote service + selenium to performance-API-capture parity (…
vishnuv688 Jun 2, 2026
a40f5dd
chore: Promote service + nightwatch to node:assert-patching parity
vishnuv688 Jun 2, 2026
8cb3081
chore: Extract captureBrowserLogs duplication (selenium + nightwatch)
vishnuv688 Jun 2, 2026
4dcd9cb
core: extract findTestDefinitions / findTestLineInFile to a shared te…
vishnuv688 Jun 2, 2026
159afdb
shared: hoist TIMING_BASE + DEFAULTS_BASE; adapters spread + override…
vishnuv688 Jun 2, 2026
f2e6c06
shared: add Node-safe TraceMutation; tighten unknown[] boundaries to …
vishnuv688 Jun 2, 2026
d212523
nightwatch: extract cucumberResultToTestState + closeOpenSteps into h…
vishnuv688 Jun 2, 2026
30708a2
nightwatch: reset #bidiAttachAttempted on browser session change
vishnuv688 Jun 2, 2026
10082d6
test: trim ceremony tests; backfill bidi.ts + worker-message-handler;…
vishnuv688 Jun 2, 2026
43cfd5a
docs: refresh CLAUDE.md §7 debt list + selenium/service/root README u…
vishnuv688 Jun 2, 2026
6d0554a
refactor: bump max-lines to 500, fix ESLint config ordering, three go…
vishnuv688 Jun 2, 2026
2e3ef03
refactor: four more focused god-file extractions (compare/selenium/ni…
vishnuv688 Jun 2, 2026
0468fe4
test(script): backfill NetworkRequestCollector to 93% lines (was 15%)
vishnuv688 Jun 2, 2026
a5f6084
Bump safe deps (patches + minors) across all package.json
vishnuv688 Jun 2, 2026
ff99f00
chore: Extract selenium cucumber/jest/driverPatcher; Extract core bid…
vishnuv688 Jun 2, 2026
0bb7003
chore: Extract core video-encoder.ts encodeToVideo; Extract script co…
vishnuv688 Jun 2, 2026
6701726
nightwatch: Extract nightwatch plugin methods
vishnuv688 Jun 2, 2026
35e7f7f
refactor: extract handleCommandExecution, tryRegisterMochaHooks, back…
vishnuv688 Jun 2, 2026
8f14dd0
refactor: extract 18 long methods across backend, nightwatch, seleniu…
vishnuv688 Jun 2, 2026
49a2b98
refactor: extract long methods in worker-message-handler, DataManager…
vishnuv688 Jun 2, 2026
d3753fa
refactor: extract pure helpers from ast-locations, run-detection, sui…
vishnuv688 Jun 2, 2026
5f694af
refactor: split long render methods and controller helpers; eliminate…
vishnuv688 Jun 2, 2026
b89555d
refactor(nightwatch): extract cucumber, test, session, and run lifecy…
vishnuv688 Jun 3, 2026
d703f9d
refactor(selenium): extract session and test management lifecycles to…
vishnuv688 Jun 3, 2026
a76e215
refactor: consolidate per-lifecycle ctx factories into single PluginI…
vishnuv688 Jun 3, 2026
b3ba1f2
refactor(selenium): extract onCommand and configSummary helpers; brin…
vishnuv688 Jun 3, 2026
67d8611
test(service): backfill reporter (37→87%) and ast-locations (27→82%) …
vishnuv688 Jun 3, 2026
b58301d
test(service): backfill ast-locations, source-mapping, step-defs cove…
vishnuv688 Jun 3, 2026
1bf8943
test(coverage): backfill standalone (0→81%) and bidi handlers (43→76%)
vishnuv688 Jun 3, 2026
b7040ed
test(coverage): backfill service/utils (70→90%) and driverPatcher (66…
vishnuv688 Jun 3, 2026
9587aa6
refactor(types): tighten any → unknown across script/backend/service …
vishnuv688 Jun 3, 2026
1830b8c
refactor(types): tighten any → unknown across shared/core/script/serv…
vishnuv688 Jun 3, 2026
61280bd
chore: All no-explicit-any warnings eliminated repo-wide
vishnuv688 Jun 3, 2026
8f07f14
refactor(selenium): type cucumber lifecycle args with explicit Gherki…
vishnuv688 Jun 3, 2026
1ccd45f
docs(claude.md): refresh §7 debt list against current state
vishnuv688 Jun 3, 2026
f95495e
test(script): cover logger.ts at 100%; test(nightwatch): cover Sessio…
vishnuv688 Jun 3, 2026
7c75971
refactor(app/compare): extract renderDetailBlock module; refactor(app…
vishnuv688 Jun 3, 2026
16d546d
test(core): add loadInjectableScript IIFE-wrap assertion to script-lo…
vishnuv688 Jun 3, 2026
b7d018a
test(backend/baselineStore): cover clear(uid), getPair, and state rol…
vishnuv688 Jun 3, 2026
1eb26cd
test(nightwatch/session): cover takeScreenshotViaHttp via in-process …
vishnuv688 Jun 3, 2026
5f2eb58
test(backend): cover framework-filters at 100%
vishnuv688 Jun 3, 2026
5f15162
test(selenium/session): cover injectScript/captureTrace via direct stub
vishnuv688 Jun 3, 2026
98917ea
fix(backend/runner): close CodeQL unvalidated-dynamic-method-call on …
vishnuv688 Jun 3, 2026
effee13
fix(adapters): unify screencast/output-dir resolution in core
vishnuv688 Jun 3, 2026
b2cc901
fix: Test case faliue and CodeQL fix
vishnuv688 Jun 3, 2026
84d7241
security: Added eslint-plugin-security for regex related issues
vishnuv688 Jun 3, 2026
9d05da5
refactor(shared): move SocketMessage to shared/ws.ts; refactor(shared…
vishnuv688 Jun 3, 2026
98abea7
fix(core): declare implicit @wdio/devtools-script dependency
vishnuv688 Jun 3, 2026
c530d01
chore(deps): bump cucumber 11→13, jest 29→30, mocha 10→11, vite-plugi…
vishnuv688 Jun 3, 2026
9cc2961
fix(selenium): onBeforeQuit must do PER-DRIVER cleanup, not session-end
vishnuv688 Jun 3, 2026
0ab9ae2
Update package.json
vishnuv688 Jun 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<!--
This template encodes the rules in CLAUDE.md and ARCHITECTURE.md.
Delete sections that genuinely don't apply, but don't delete sections to avoid answering them.
-->

## What

<!-- One or two sentences. What does this PR change? -->

## Why

<!-- The motivation. Not "what" again — the reason this change exists. Link issue if any. -->

## How

<!-- The approach. Anything non-obvious about the implementation. -->

---

## Architecture self-check

> Required for every non-trivial PR. If a box is unchecked, explain why.

- [ ] **No new duplication.** This PR does not add a type, constant, enum, or contract that already exists in another package. (If it consolidates one, note which item from `CLAUDE.md` §7 is being resolved.)
- [ ] **No cross-adapter imports.** No code in `service`, `nightwatch-devtools`, or `selenium-devtools` imports from another adapter.
- [ ] **No adapter imports in `backend` / `app`.** Neither package reaches into adapter internals.
- [ ] **Typed contracts at boundaries.** Any new `fetch(...)`, `ws.send(...)`, or HTTP route has a typed request/response shape in `shared` (or in `service` types if `shared` doesn't exist yet, with a TODO to move).
- [ ] **No `if (framework === '...')` outside an adapter.** Framework branching uses a typed `FrameworkId`.
- [ ] **No new `any` at package boundaries.** Internal `any` is acceptable only at a documented framework-edge with a one-line comment.

### Multi-adapter changes

- [ ] This PR touches **more than one** adapter package.

> If checked: **why isn't this in `core`?** Answer here:
>
> _<your answer>_

---

## Debt scoreboard

> List the `CLAUDE.md` §7 debt items this PR resolves, partially resolves, or extends. Delete this section only if the PR genuinely affects no debt items.

- Resolved: _<item, or "none">_
- Partially resolved: _<item, or "none">_
- New debt introduced: _<item, or "none — and explain why if any>_

If new debt is introduced, it must be added to `CLAUDE.md` §7 in this PR.

---

## Testing

- [ ] Unit tests for new logic in `shared` / `core` (required per `CLAUDE.md` §4).
- [ ] Regression test for any bug fix (required per `CLAUDE.md` §4).
- [ ] `pnpm build` passes.
- [ ] `pnpm test` passes.
- [ ] `pnpm lint` passes.
- [ ] For UI/runtime changes: verified in `example/` (or `example` for the framework I changed).

If any required item is skipped, say so here with the reason:

_<your note, or "n/a">_

---

## Screenshots / recordings (UI changes only)

<!-- Drop them in here. -->
235 changes: 235 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
# Architecture

A descriptive map of how the pieces fit together. For conventions and coding standards, see [CLAUDE.md](./CLAUDE.md).

---

## At a glance

A devtools dashboard for end-to-end browser tests. Three test frameworks (WebdriverIO, Nightwatch, Selenium) push the same normalized event stream through a single backend into a single browser UI.

```
[user's test framework]
[adapter] thin: framework-specific hooks + driver patching
[core] framework-agnostic capture/reporting library
▼ (WS frames typed by shared)
[backend] Fastify + WS gateway + baseline store + rerun spawner
▼ (WS + HTTP, both typed by shared)
[app] Lit browser UI, framework-agnostic
```

A separate piece, **`packages/script`**, is injected into the browser under test (not Node) to capture DOM mutations from the page's own JS context. It communicates back through the adapter, not directly to the backend.

---

## Packages

The workspace is a pnpm monorepo. Two of the packages (`shared`, `core`) are workspace-internal — they're marked `"private": true` and never published; consumers bundle their code into their own `dist/`.

### `packages/shared`

Types, constants, enums, HTTP/WS contract definitions. Pure TypeScript, no runtime dependencies on any other package in the monorepo. Workspace-internal; inlined into every consumer at build time.

Contains the canonical definitions for:

- Domain types: `CommandLog`, `ConsoleLog`, `NetworkRequest`, `TraceMutation`, `Metadata`, `TraceLog`, `TraceType`, `TestStats`, `SuiteStats`, `TestStatus`, `TestError`, `ReporterError`, `PreservedAttempt`, `PreservedStep`, `PerformanceData`, `DocumentInfo`, `Viewport`, `ScreencastInfo`, `ScreencastFrame`, `ScreencastOptions`, `LogLevel`, `LogSource`.
- WS wire format: `SocketMessage<T>`, `WsMessageScope`, `WsPayloadFor<T>`, `ClearExecutionDataWsPayload`, `ReplaceCommandWsPayload`.
- Routing/scope constants: `WS_PATHS`, `WS_SCOPE`, `BASELINE_WS_SCOPE`, `TESTS_API`, `BASELINE_API`.
- Process-control env vars: `REUSE_ENV`, `RUNNER_ENV`.
- Defaults: `TIMING_BASE`, `DEFAULTS_BASE`, `SCREENCAST_DEFAULTS`.
- File patterns: `SPEC_FILE_RE`, `FEATURE_FILE_RE` (the latter Cucumber-only).
- Test-runner identification: `TestRunnerId = 'mocha' | 'jasmine' | 'cucumber' | 'nightwatch' | 'nightwatch-cucumber' | 'selenium-webdriver'`.

Imports from: nothing. Imported by: every other package.

### `packages/core`

Framework-agnostic capture and reporting library. Workspace-internal; inlined into each adapter at build time.

Contains:

- `SessionCapturerBase` — orchestrates per-session capture (console/stream patching, WS connection, command-id bookkeeping, upstream-send guard with `onUpstreamDrop` hook).
- `TestReporterBase` — common reporter behavior, extended by Nightwatch + Selenium reporters (Service uses `@wdio/reporter` from WDIO directly).
- `ScreencastRecorderBase` — frame buffer + polling fallback shared by all three adapters.
- `resolveAdapterOutputDir` — the dir-resolution helper that picks where screencast/trace files land (test-file dir → config dir → cwd, with a `node_modules/` skip).
- Pure helpers: `assert-patcher`, `bidi` (`attachBidiHandlers`, `loadSeleniumSubmodule`, `arrayHeadersToObject`), `console` (`stripAnsi`, `detectLogLevel`, `createConsoleLogEntry`, `mapChromeBrowserLogs`, `chromeLogLevelToLogLevel`), `error` (`serializeError`, `errorMessage`), `finalize-screencast`, `net` (`isPortInUse`, `findFreePort`, `getRequestType`), `performance-capture` (`CAPTURE_PERFORMANCE_SCRIPT`, `applyPerformanceData`), `retry-tracker`, `script-loader` (`loadInjectableScript`, `pollUntilReady`), `stack` (`isUserCodeFrame`, `normalizeFilePath`, `getCallSourceFromStack`), `suite-helpers`, `test-discovery` (`findTestDefinitions`, `extractTestMetadata`), `uid` (`generateStableUid`, `deterministicUid`, `resetSignatureCounters`), `video-encoder` (`encodeToVideo`).

Imports from: `shared`. Imported by: all three adapter packages.

### `packages/service` — WebdriverIO adapter

WebdriverIO-specific glue.

Contains: WDIO service hooks (`beforeCommand`, `afterCommand`, `beforeTest`, `afterTest`, `beforeSession`, `afterSession`, `onPrepare`, `onComplete`), a reporter that extends WDIO's `Reporters.ReporterEntry`, the BiDi listener wiring (`bidi-listeners.ts`), launcher entry point, cucumber step-definition AST scanning, and the standalone runner (`standalone.ts`).

Imports from: `@wdio/types`, `@wdio/reporter`, `@wdio/logger`, `@wdio/protocols`, `webdriverio`, `core`, `shared`.

### `packages/nightwatch-devtools` — Nightwatch adapter

Nightwatch-specific glue.

Contains:

- The `NightwatchDevToolsPlugin` class + factory in `index.ts`.
- Lifecycle modules: `run-lifecycle.ts`, `test-lifecycle.ts`, `cucumber-lifecycle.ts`, `session-init.ts`, `event-hub.ts`.
- `BrowserProxy` (in `helpers/`) that wraps Nightwatch's browser API and forwards each command into the session capturer.
- A `SessionCapturer` subclass + a Nightwatch-flavored `SuiteManager` / `TestManager`.
- BiDi opt-in support (gated on `bidi: true` in plugin options + the `webSocketUrl: true` capability).
- Cucumber wiring: `cucumberHooks.cjs` (registered via the Cucumber `require` option), feature-file scanning, step-definition resolution.
- A perf-log → NetworkRequest parser (`helpers/perfLogs.ts`) for the CDP perf-log path when BiDi isn't attached.

Imports from: `@wdio/logger`, `core`, `shared`. Does not import: other adapter packages, `backend`, `app`.

### `packages/selenium-devtools` — Selenium adapter

Selenium-webdriver-specific glue.

Contains:

- `driverPatcher.ts` — wraps `selenium-webdriver`'s `WebDriver` / `WebElement` / `Builder` prototypes with command capture.
- Per-runner hooks for Mocha, Jest, Jasmine, Vitest, and Cucumber (`runnerHooks/*.ts`).
- Native BiDi via `selenium-webdriver/bidi`.
- Driver-launch + dashboard-launch helpers, detached-backend mode, process-hook shutdown.
- `SessionCapturer` subclass + Selenium-flavored `SuiteManager` / `TestManager`.

Imports from: `core`, `shared`, `selenium-webdriver` (peer). Does not import: other adapter packages, `backend`, `app`.

### `packages/backend`

The server adapters connect to and the app talks to.

Contains:

- Fastify HTTP server.
- WebSocket gateway: one connection per adapter worker, one per app client.
- Baseline store (in-memory) for preserve-and-rerun; reuses `shared` types directly via thin `*Like` aliases (`baseline/types.ts`).
- Test runner spawner (`runner.ts`) — spawns the user's `wdio` / `nightwatch` / `selenium` binary with rerun filters.
- Framework-specific CLI args live in `framework-filters.ts` — a `switch` over `TestRunnerId` returning the right `FilterBuilder`. (The switch shape is deliberate: CodeQL trusts compile-time-known callable selection, table dispatch trips its `unvalidated-dynamic-method-call` query.)
- Bin resolver (`bin-resolver.ts`) — finds the WDIO/Nightwatch CLI in the user's `node_modules/` or `npx` cache.
- Worker-message handler (`worker-message-handler.ts`) — dispatches messages from spawned workers (config/sessionId/videoPath/...).

Framework awareness lives only in `runner.ts` and `framework-filters.ts`, always through `TestRunnerId`, never magic strings.

Imports from: `shared`. Does not import: any adapter package, `app`, or `core` (the backend doesn't capture; core is for capturers).

### `packages/app`

The browser UI.

Contains:

- Lit web components (sidebar/explorer, workbench/compare, workbench/console, workbench/network, workbench/snapshot, etc.).
- WebSocket client for the live event stream.
- Context providers (`@lit/context`) for each data stream.
- `DataManagerController` — orchestrates the WS connection and the 11 context providers (one per scope).
- Pure helpers: suite-merge logic, mark-running logic, run-detection logic, context-update transforms (`contextUpdates.ts`), runner-capability derivations (`runnerCapabilities.ts`).

Imports from: `shared`. Does not import: any adapter package, `backend` directly (only via WS/HTTP), `core`.

### `packages/script`

Browser-injected runtime — runs **inside the page under test**, not in Node.

Contains: DOM mutation observers, page-side trace collection, a small logger. It's loaded into the page via `loadInjectableScript()` (which reads the built `dist/script.js`) and communicates back through the WebDriver bridge (`executeScript` / `getLog`), not directly to the backend.

The execution environment is the browser, not Node, so this package cannot import from `core` (Node-only) or from non-browser-safe parts of `shared`.

### `examples/`

Per-framework demo projects used for manual verification.

- `examples/wdio/` — WebdriverIO with Mocha (default). Run via `pnpm demo:wdio`.
- `examples/nightwatch/` — Nightwatch (both vanilla and Cucumber). Run via `pnpm demo:nightwatch`.
- `examples/selenium/` — Selenium with subdirs for `mocha-test/`, `jest-test/`, `cucumber-test/`, `jasmine-test/`, `vitest-test/`. `pnpm demo:selenium` runs mocha; `pnpm --filter @wdio/selenium-devtools example:<runner>` runs the others.

---

## Data flow

### A test run, end to end

1. The user runs their normal command (`wdio run …`, `nightwatch test`, `mocha + selenium`, ...).
2. The framework loads its adapter via service/plugin config.
3. The adapter constructs a `SessionCapturer` (subclass of `core`'s `SessionCapturerBase`). The base class opens a WS connection to the backend, patches `console.*`, intercepts stdout/stderr, and installs the upstream-send guard.
4. The framework fires lifecycle hooks (suite/test start, command, etc.). The adapter translates each into a `core` call.
5. `core` builds the typed event per `shared` schema and pushes it through the WS.
6. `backend` receives the event, optionally persists it (baseline store, video registry), and broadcasts to every connected app client.
7. `app` updates its Lit components reactively via the context providers.

### Preserve-and-rerun

1. User clicks "📌 Preserve & Rerun" on a failed test in the dashboard.
2. App POSTs to `/api/baseline/preserve` (typed contract in `shared`).
3. Backend snapshots the failing attempt into the baseline store, then spawns a rerun via `runner.ts`.
4. The rerun goes through the normal flow above.
5. App receives both attempts and renders the side-by-side compare view.

### Rerun mechanics

`backend/src/runner.ts` is the only place outside an adapter that knows about specific frameworks. It uses `TestRunnerId` from shared and dispatches via `framework-filters.ts`'s `switch`:

- `cucumber`: `--spec <feature[:line]>` and/or `--cucumberOpts.name <regex>`.
- `mocha`/`jasmine`: `--spec <file>` + `--mochaOpts.grep`/`--jasmineOpts.grep`.
- `nightwatch`: positional spec file + optional `--testcase <name>`.
- `nightwatch-cucumber`: `--name <regex>` (feature files via `feature_path` config).
- Unknown/missing: spec-only fallback.

Everywhere else in the system, events are framework-agnostic.

---

## Boundaries

Every data crossing between packages goes through a typed contract in `shared`:

| Boundary | Direction | Transport | Lives in |
|---|---|---|---|
| Adapter → backend | One-way events (command, console, network, mutation, …) | WebSocket frames | `shared/ws.ts` (`SocketMessage<T>`) |
| App → backend | Preserve, clear, run, stop, get-baseline | HTTP (Fastify) | `shared/baseline.ts`, `shared/runner.ts` |
| Backend → app | Live event broadcast + API responses | WebSocket + HTTP | `shared/ws.ts`, `shared/baseline.ts` |
| Backend → spawned worker | Run config, rerun env, video paths | Env vars + IPC | `shared/runner.ts` (`REUSE_ENV`, `RUNNER_ENV`) |
| Script → adapter | Mutation events, trace data | `executeScript` return values + `getLog` channel | Implicit in adapter — script's payload shape is consumed by core's `processTracePayload` |

New events or HTTP routes start with a `shared` change. The other packages then import the contract.

---

## Where things live

The repo has converged on a clear ownership story. When in doubt, the top-down decision tree is:

- A type, constant, enum, schema, or contract used by more than one package → **`shared`**.
- Capture, parsing, normalization, sourcemap, UID, reporter, screencast, or WS-framing logic that doesn't depend on a specific framework's API → **`core`**.
- A specific framework's hook, driver patch, or runner integration → the matching **adapter** package. Adapter code calls `core` for the actual work and only owns the hook registration.
- A backend HTTP route, WS handler, or rerun behavior → **`backend`**, with the contract added to `shared` first.
- UI → **`app`**, consuming `shared` contracts only.
- Code that runs inside the browser under test → **`script`**.

A few cross-cutting conventions follow from this layout:

- Adapter packages don't import each other. Anything two adapters would both want lives in `core`.
- Backend doesn't import adapter packages, and adapter packages don't import backend or app.
- The script package is a leaf — adapters load its built bundle as a string and inject it; they don't import from it at runtime.
- `shared` and `core` are private workspace packages. Consumers bundle them. The bundler config has to inline them (not externalize) or the published artifact won't resolve — see the build-config notes in `CLAUDE.md`.

---

## Current state

The architecture above is the actual state of the repo. Where it diverges from the ideal, the divergences are tracked in [CLAUDE.md §7](./CLAUDE.md#known-debt).

Notable in-place pieces worth knowing about:

- `replaceCommand` has two semantics across adapters — Selenium mutates the existing entry in place (preserves `_id`/`id` continuity for chained calls); Nightwatch splices and reissues with a new `_id`. Both call the same `core/suite-helpers` factories; the storage strategy stays adapter-specific because the runner integrations differ.
- `patchNodeAssert` is wired only in `selenium-devtools` (Selenium's primary assertion style is `node:assert`). The shared helper lives in `core/assert-patcher`; Service and Nightwatch can opt in via a one-line call when they need to, but it's not auto-enabled because both communities lean on chai/expect.
- BiDi is auto-attached in Service and Selenium. Nightwatch is opt-in via `bidi: true` and requires `webSocketUrl: true` in capabilities — historically Nightwatch users haven't all enabled BiDi by default.
- Performance API capture (`CAPTURE_PERFORMANCE_SCRIPT`) is identical across all three adapters; each wires it into its own afterCommand-equivalent path.
- Output directory for screencast videos and trace files is resolved through `core/resolveAdapterOutputDir` — adapters feed `userConfiguredDir` (WDIO honors `wdio.conf.ts`'s `outputDir`/`rootDir`), `testFilePath` (Selenium/Nightwatch), and `configPath` (Nightwatch), and the helper picks the first writable, non-`node_modules/` candidate.

For per-package implementation details, see each package's `README.md`
Loading
Loading