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 b8a8617
Browse filesBrowse the repository at this point in the historyBrowse files
A replayed trace runs no script. The page a trace recorded is rendered in a sandboxed frame, scripts, inline event handlers, `srcdoc` and `javascript:` URLs are dropped from it on every update, not only the first render, and the dashboard and `show-trace` now send the same Content-Security-Policy as the hosted player. A trace someone else recorded can therefore be opened safely in any of them.
Copy file name to clipboardExpand all lines: CLAUDE.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -105,7 +105,7 @@ They import from `shared` and `core`, never from each other. They aren't importe
105
105
106
106
### Backend and app are framework-agnostic
107
107
108
-
`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. It runs the reader in a module Web Worker loaded from the player's own origin (`app/src/controller/trace-parse.worker.ts`, which `script-src 'self'` already admits), so the tab stays responsive and the landing screen names the phase the open is on; the live build never references the worker. 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. Several zips or a folder open as a run (`controller/trace-run.ts`), in the hosted player and in `show-trace`, which lists them at `TRACE_API.run` for the app to read: each zip is parsed once, one at a time, and its payload kept for when its row is picked. A zip records no test state, so a row's outcome is read from its rows (`utils/trace-summary.ts`: a failed action or step means failed, actions with none failed mean passed, no actions mean unknown); a jest `expect` that records no row can therefore fail a test whose trace reads as passed. Keeping every payload costs memory in proportion to the run; a reader that reads entries on demand would shrink each payload, which is why the run holds the reader's own payload rather than a cache of its own.
108
+
`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. It runs the reader in a module Web Worker loaded from the player's own origin (`app/src/controller/trace-parse.worker.ts`, which `script-src 'self'` already admits), so the tab stays responsive and the landing screen names the phase the open is on; the live build never references the worker. A trace is untrusted input, and its replay must run no script in any mode: the replay iframe is sandboxed (`allow-same-origin` only), `components/browser/replay-safety.ts` drops `<script>`, `on*`, `srcdoc` and `javascript:` URLs on the first render and on every replayed mutation alike, and the CSP (`shared/player-csp.ts`, sent by `vercel.json` and by the backend on its HTML, a test holding the two equal) blocks what the first two miss; the hosted e2e check opens a hostile trace to prove it. 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. Several zips or a folder open as a run (`controller/trace-run.ts`), in the hosted player and in `show-trace`, which lists them at `TRACE_API.run` for the app to read: each zip is parsed once, one at a time, and its payload kept for when its row is picked. A zip records no test state, so a row's outcome is read from its rows (`utils/trace-summary.ts`: a failed action or step means failed, actions with none failed mean passed, no actions mean unknown); a jest `expect` that records no row can therefore fail a test whose trace reads as passed. Keeping every payload costs memory in proportion to the run; a reader that reads entries on demand would shrink each payload, which is why the run holds the reader's own payload rather than a cache of its own.
109
109
110
110
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.
Each zip is read once, as the list fills in, and kept for when it is picked. Anything in the folder that is not a `.zip` (screenshots, videos, the artifacts manifest) is skipped; a zip that cannot be opened stays in the list with the reason, and the rest open normally. A zip records no test state, so the outcome is read from the trace itself: **failed** when an action or step in it failed, **passed** when it holds actions and none failed, **unknown** when it holds none. An assertion library that records no row (a jest `expect`, chai) can fail a test whose trace still reads as passed. One zip opens exactly as before, with no list.
273
273
274
-
**Hosted player: [trace.webdriver.io](https://trace.webdriver.io).** The same player as a web page, for opening a trace without installing anything: pick one or several `.zip` files, or drop them or a folder of them on the page. 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:
274
+
**Hosted player: [trace.webdriver.io](https://trace.webdriver.io).** The same player as a web page, for opening a trace without installing anything: pick one or several `.zip` files, or drop them or a folder of them on the page. 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 trace from someone else is treated as untrusted: its replayed page runs no script, in the hosted player, `show-trace` and the live dashboard alike. 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:
275
275
276
276
```sh
277
277
pnpm player:hosted # builds it and serves it at http://localhost:4173 under the production headers
0 commit comments