Skip to content

Open a trace from a URL in the hosted player, and deep-link into it - #534

Open
vishnuv688 wants to merge 1 commit into
mainfrom
feat/500-trace-url
Open

vishnuv688 wants to merge 1 commit into
mainfrom
feat/500-trace-url

Conversation

@vishnuv688

Copy link
Copy Markdown
Member

What & why

Closes #500. Part of #494.

The hosted player could only open a trace someone picked or dropped by hand, so a CI job or a bug report could not link to one, let alone to the failing step. Now:

  • ?trace=<url> opens a zip from any https host that sends Access-Control-Allow-Origin (or from the player's own origin).
  • #action=<callId>&tab=<tab> opens the trace on that action with that bottom tab open, in the hosted player and show-trace. The address follows the viewer's picks, so the address bar always links to what is on screen.

Example: https://trace.webdriver.io/?trace=https%3A%2F%2Fci.example%2Ftrace.zip#action=call@12&tab=network

Type of change

  • Bugfix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Polish (an improvement to an existing feature)
  • Breaking change (existing behavior changes for users)
  • Documentation
  • Internal (build, CI, dependencies, tooling)

Packages touched

  • shared (types and contracts)
  • core (framework-agnostic capture/reporting)
  • elements (published element/snapshot API — @wdio/elements)
  • service (WebdriverIO adapter)
  • nightwatch-devtools (Nightwatch adapter)
  • selenium-devtools (Selenium adapter)
  • selenium-devtools-py (Selenium Python adapter)
  • backend (server)
  • app (UI)
  • script (page-injected runtime)
  • trace (Trace mode)

Plus conformance (the hosted-player check) and vercel.json. backend has no source change; it bundles the trace reader, which now keeps callId, and that is what gives show-trace deep links. selenium-devtools-py only regenerates its wire types for the new optional CommandLog.callId (patch fragment); its behaviour is unchanged.

Capability matrix

  • packages/conformance/src/features.ts updated, and tables regenerated with pnpm docs:capabilities, if this adds or changes user-facing behaviour

A player feature, not an adapter capability; the tables are unchanged.

Notes for reviewers

The trust decision (recorded on #500): the hosted player's connect-src gains https: so it can fetch the zip. hostedPlayerCsp() in shared/player-csp.ts is the single source; vercel.json must equal it and a test checks it differs from playerCsp() in connect-src only. The backend (show-trace, live dashboard) keeps its policy and ignores ?trace=. An opened trace still never leaves the tab: the only fetch is the download itself.

The download (app/src/controller/trace-download.ts):

  • The URL must be https or the player's own origin. javascript:, data:, blob:, file:, other-origin http: and URLs carrying credentials are refused before any request.
  • credentials: 'omit', referrerPolicy: 'no-referrer', and the final URL after redirects is checked again.
  • Streams with a 512 MB cap and a 30 s idle timeout, and stops at the first bytes if the body is not a zip. Every refusal aborts the request.
  • Each failure has its own message; a CORS refusal says the host must send Access-Control-Allow-Origin. Errors name the host and path only, never a signed URL's query.
  • A URL with its own query can be pasted as it is when trace is the last parameter, or percent-encoded anywhere.

Deep links:

  • The action id is the zip's own callId, kept by the reader on a new optional CommandLog.callId (old zips get one too).
  • Only controller/trace-link.ts reads or writes the address. The link is applied once, to the trace the page opened with; opening another trace by hand clears it, so a second trace starts on its own first action. A run of several traces links the tab only, since the hash names no run entry.
  • Writes use replaceState (no history entries). A pasted link in an open tab is followed through hashchange. The live dashboard never touches the address.

Behaviour change: opening a trace no longer switches the dock to Log on its own; it opens on the last-used tab, so a linked tab holds. A viewer's pick still opens Log.

Scheduled hosted-player check: it now compares production's CSP to hostedPlayerCsp() directive by directive, so it will report drift until production is deployed from a main that includes this PR. Production does not exist yet (hosting is still being set up), so this is the policy the first deploy must serve.

Review: three independent rounds. The first found one blocking bug (a stale hash steered every later trace opened in the same tab) plus ten more; the second confirmed the fixes and found six small edges; all are fixed, each with a test that fails without it. No security findings: scheme tricks, redirects, exfiltration, injection and the CSP scope were checked.

Testing

  • Frozen install, pnpm build, pnpm typecheck, pnpm test (3704 passed, 1 skipped), pnpm test:ui (31 of 31), pnpm lint, pnpm docs:capabilities --check are green; no workspace import leaks in any bundle.
  • The hosted-player end-to-end check gains a step that opens a zip by ?trace= with #action=…&tab=network, asserts that action and tab are shown, and that a pick is written back to the address. It passes locally.
  • Python: wire, contract and mirror tests pass. Two test_instrumentation.py tests fail locally with and without this PR.

Needs checking by hand

  1. pnpm player:hosted, copy a zip into packages/app/dist-hosted/t.zip, open http://localhost:4173/?trace=/t.zip#action=call@3&tab=network: the download phase shows, then the trace opens on that action with Network open. Picks and tab changes update the address without adding back-button entries.
  2. Drop a second zip in the same tab: it opens on its own first action, and the address no longer names the first trace.
  3. Errors: ?trace=http://example.com/t.zip, ?trace=javascript:alert(1), a 404 URL, ?trace=/index.html and an empty ?trace= each show their own message.
  4. Cross-origin: serve a zip over https with CORS (e.g. npx http-server <dir> --cors -S -C cert.pem -K key.pem -p 8443) and open it by ?trace=; without --cors the CORS message shows.
  5. show-trace <zip> with #action=call@2&tab=console appended opens there, and the address follows picks.
  6. The live dashboard (pnpm demo:wdio) leaves the address unchanged.

Not in this PR: the docs-site mirror.

Screenshots / recordings

To be added: a ?trace= deep link opening on the Network tab.

🤖 Generated with Claude Code

CI jobs and bug reports could not link to a trace: the hosted player
only opened files picked or dropped by hand. It now opens
?trace=<https url> from any host that sends CORS headers, without
credentials, refusing other schemes, redirects off https, a stalled
host, a body over 512 MB and anything that is not a zip. Its CSP gains
connect-src https: for that fetch; the backend's policy is unchanged.

#action=<callId>&tab=<tab> opens a trace on an action and a dock tab,
in the hosted player and show-trace, and the address follows the
viewer's picks with replaceState. The link applies only to the trace it
came with, and a run of several traces links the tab only. CommandLog
gains an optional callId, kept by the trace reader. Opening a trace no
longer moves the dock to Log, so a linked tab holds.

Closes #500

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Hosted player: open a trace from a URL, and deep-link into it

1 participant