You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 0fd97cf
Browse filesBrowse the repository at this point in the historyBrowse files
Host the trace player as a static page with no backend
Opening a trace zip needed the DevTools package installed and `show-trace`
run against it, which is a lot to ask of whoever the zip is sent to. The app
now builds a hosted mode (`vite build --mode hosted`) that opens a zip the
viewer drops or picks and parses it in the browser tab, and `vercel.json`
deploys it the way benchmark.webdriver.io is deployed.
A trace records everything the run showed or typed, so the zip never leaves
the tab: no upload, the trace is not written to browser storage, and the CSP
allows network calls only to the player's own origin. It does allow a
cross-origin `<base>`, styles, fonts and images, which the DOM replay needs
to load the recorded site's own stylesheets.
The reader had to become browser-safe: the disk read moved to
`read-trace-zip.ts`, source paths are hashed with a pure SHA-1 (native
`sha1Hex` stays for resource bytes, ~30x faster), and screenshots are
base64-encoded without `Buffer`. A test runs the reader on a recorded zip
with `Buffer` removed, since an import walk cannot see a global. The ESLint
rule barring the app from `trace` now allows `trace-reader` alone.
Refs #494
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The trace player can now run as a static web page with no backend: built with `--mode hosted`, it opens a trace `.zip` the viewer drops on the page or picks, parsing it in the browser tab without uploading it or writing it to browser storage. This is the build trace.webdriver.io serves. The live dashboard is unchanged; the zip reader it does not use is loaded only when a file is opened.
-`parseTraceZip`/ `readTraceZip`(`trace-reader.ts` and its `trace-reader-*` siblings) — the reverse direction: a `trace.zip`, ours or a foreign one, back into the `TracePlayerData` the player renders. Imported by subpath (`@wdio/devtools-trace/trace-reader`) and kept out of the package index, so adapters that reach `trace` through `core` never inline it.
70
+
-`parseTraceZip` (`trace-reader.ts` and its `trace-reader-*` siblings) — the reverse direction: a `trace.zip`, ours or a foreign one, back into the `TracePlayerData` the player renders. Imported by subpath (`@wdio/devtools-trace/trace-reader`) and kept out of the package index, so adapters that reach `trace` through `core` never inline it. It reaches no Node built-in, because the hosted player runs it in the browser (`tests/trace-reader-browser-safe.test.ts` walks its imports); the disk read is `readTraceZip` in `read-trace-zip.ts`, and source paths are hashed with the pure `portableSha1Hex` so the reader needs no `node:crypto`; resource bytes keep native `sha1Hex`, ~30x faster.
71
71
72
-
Imports from: `shared`. Imported by: `core` (which re-exports it, so adapters reach it unchanged)and `backend`.
72
+
Imports from: `shared`. Imported by: `core` (which re-exports it, so adapters reach it unchanged), `backend`, and `app`, which may import `trace-reader` alone (ESLint enforces it).
Copy file name to clipboardExpand all lines: CLAUDE.md
+2-1Lines changed: 2 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -31,6 +31,7 @@ Run from repo root unless noted.
31
31
|`pnpm dev`| Run all packages in parallel dev mode. |
32
32
|`pnpm smoke:fixtures [--port N]`| Serve the local fixture site the desktop examples drive (a copy of the-internet's login flow: `/`, `/login`, `/secure`, `/logout`; default `http://127.0.0.1:4567`). Every desktop login example reads `DEVTOOLS_EXAMPLE_BASE_URL` (trailing slash ignored); unset, they drive the public site. |
33
33
|`pnpm smoke:assert --expect <name> [--exit-code N] [--log file] <zip-or-dir>...`| Check trace zips against `packages/conformance/smoke/expectations/<name>.json`: the listed rows under each test's own tracing group (or the whole run for an entry with `scope: "run"` and a `reason`), an error row on each deliberately failing test, a retried test's attempt count with its other checks read from its final attempt alone (`attempts`), title fragments such as a failed wait's condition, a call site on every row of a per-test entry (at least one for a run-scoped entry) and every call site inside the example's own `sourceFiles`, the run's exit code, and no wait timeout, in any adapter's wording (`TIMEOUT_PATTERNS` in `smoke/constants.ts`), in the trace's stdout or the run log other than the lines of a test marked `allowTimeoutError`. |
34
+
|`pnpm player:hosted`| Build the hosted trace player (the app with `--mode hosted`, no backend) and serve it at `http://localhost:4173` under the headers `vercel.json` sends from trace.webdriver.io. |
34
35
|`pnpm docs:capabilities`| Render the per-adapter capability tables from `packages/conformance/src/features.ts` into `README.md`; `--website <webdriverio checkout>` also renders the docs-site tables, `--check` fails instead of writing, and `--parity` lists every feature some adapter lacks plus the evidence counts. |
35
36
|`python3 packages/selenium-devtools-py/scripts/changes.py next-version`| The version a Python-adapter release would publish, from the fragments pending in `changes/`. `check --base <ref>` is the CI gate; `apply` is what the release runs. |
36
37
@@ -103,7 +104,7 @@ They import from `shared` and `core`, never from each other. They aren't importe
103
104
104
105
### Backend and app are framework-agnostic
105
106
106
-
`backend` and `app` import from `shared`only (for contracts) and from each other via the WS/HTTP boundary. Neither imports an adapter package.
107
+
`backend` and `app` import from `shared` (for contracts) and from each other via the WS/HTTP boundary. Neither imports an adapter package. `backend` also imports `trace`, to write and read zips; `app` imports only `trace`'s reader, which the hosted player (`trace.webdriver.io`, built with `pnpm player:hosted`, deployed by Vercel from `vercel.json`) runs in the browser to open a dropped zip with no backend at all. That build is `HOSTED_PLAYER` in `app/src/controller/constants.ts`; it never writes the trace to browser storage, and `vercel.json` is also what `vite preview` serves, so its CSP is checked locally before it ships.
107
108
108
109
Framework-specific behavior in the backend is contained in two files: `runner.ts` and `framework-filters.ts`. Both branch on a typed `TestRunnerId` from shared, never on a magic string. The `framework-filters` dispatch is a `switch` over `TestRunnerId` (not a table lookup) so CodeQL's `unvalidated-dynamic-method-call` query trusts the call site.
Copy file name to clipboardExpand all lines: README.md
+6Lines changed: 6 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -262,6 +262,12 @@ The player exposes everything captured in the archive:
262
262
263
263
The `show-trace` bin is exposed by each JavaScript adapter (`@wdio/devtools-service`, `@wdio/nightwatch-devtools`, `@wdio/selenium-devtools`), so `pnpm show-trace <zip>` / `npx show-trace <zip>` work in any project that installs one — no extra dependency.
264
264
265
+
**Hosted player: [trace.webdriver.io](https://trace.webdriver.io).** The same player as a web page, for opening a trace without installing anything: drop a `.zip` on the page or pick one. The archive is parsed in the browser tab and never uploaded, which matters because a trace records everything the run showed or typed. Run controls are absent, as in `show-trace`. A replayed page still loads its own stylesheets and images from the site under test, as it does in `show-trace`; from an `https` page the browser blocks the `http` ones, so a trace of an `http` site can render unstyled there and fully in `show-trace`. To try the hosted build locally:
266
+
267
+
```sh
268
+
pnpm player:hosted # builds it and serves it at http://localhost:4173 under the production headers
269
+
```
270
+
265
271
**Other viewers.** The trace uses a portable NDJSON schema, so the same `.zip` also opens in other compatible standalone trace viewers that read the format, and — because it shares that on-disk format — is what an Allure report's **embedded trace viewer** (Allure ≥ 2.35) reads. See the [backend README](./packages/backend/README.md#trace-serving--show-trace) for the reader details.
Copy file name to clipboardExpand all lines: eslint.config.cjs
+14-2Lines changed: 14 additions & 2 deletions
Original file line number
Diff line number
Diff line change
@@ -12,6 +12,7 @@ module.exports = [
12
12
ignores: [
13
13
'node_modules/**',
14
14
'**/dist/**',
15
+
'**/dist-hosted/**',
15
16
'**/.tsup/**',
16
17
'**/allure-report/**',
17
18
'**/allure-results/**'
@@ -298,6 +299,13 @@ module.exports = [
298
299
'no-restricted-imports': [
299
300
'error',
300
301
{
302
+
paths: [
303
+
{
304
+
name: '@wdio/devtools-trace',
305
+
message:
306
+
'App may import only trace-reader from trace (CLAUDE.md §2.2), which the hosted player runs in the browser. The writers need Node; the backend writes zips with them.'
// The bare name is in `paths`: here it would exclude the directory, which `!` cannot undo.
340
+
group: [
341
+
'@wdio/devtools-trace/*',
342
+
'!@wdio/devtools-trace/trace-reader'
343
+
],
332
344
message:
333
-
'App must not import from trace (CLAUDE.md §2.2). The backend writes and parses zips with it; the app receives a parsed trace over HTTP.'
345
+
'App may import only trace-reader from trace (CLAUDE.md §2.2), which the hosted player runs in the browser. The writers need Node; the backend writes zips with them.'
0 commit comments