Skip to content

Trace view - Improve trace step by preserving original user-facing locator presentation #10208

@hi-ogawa

Description

@hi-ogawa

Trace steps should show locator information in the form users recognize from their test code. When possible, the step list should show locator-like text such as getByRole(...), not internal selector syntax or provider-lowered command selectors.

This should make traces easier to scan, easier to connect back to the test source, and less noisy when providers need execution-specific selector formats internally.

Further details and tentative plan 🤖

Current State

Trace-view currently treats the selector available at record time as one value with several jobs: display it in the step list, use it to resolve the highlighted node in the snapshot, and often infer it from the provider command payload. That is fine while the value still resembles the user's locator, but it becomes confusing once a provider transforms the locator into an execution-specific selector.

User locator intent
  page.getByRole('button', { name: 'Save' })
          |
          v
Vitest / ivya selector
  internal:role=button[name="Save"i]
          |
          +--> trace step display
          |
          +--> snapshot target lookup
          |
          +--> provider command input

Those three outputs do not always want the same representation. The step list wants a locator-like presentation. Snapshot lookup wants a selector the browser-side selector engine can resolve at snapshot time. Provider commands want whatever selector shape the provider needs to execute the action.

Playwright usually keeps enough original locator selector information in the command path for a useful formatter. WebDriverIO often resolves the locator to a concrete element first, then dispatches provider commands through an element-specific CSS selector. By that point, the trace step can lose the original locator presentation. The resolved WebDriverIO selector can also include provider-specific shadow DOM syntax such as >>>, which is not valid for current trace snapshot lookup.

Existing TODO-backed symptoms:

  • WebDriverIO shadow DOM marks record lowered selectors and produce selectorResolution: "error".
  • WebDriverIO missing-element click failures can miss the failed action entry and only record lifecycle failure.

Direction

1. Display Formatter Slice

Add a narrow first slice that improves trace step display without changing command execution or snapshot resolution.

Recommended behavior:

  • When a trace entry has selector metadata that can be formatted, store a display-oriented locator label with the trace data.
  • Have the trace viewer prefer the locator label for step display and fall back to the existing selector.
  • Keep existing raw selector behavior for snapshot lookup and diagnostics.

This is promising for Playwright because its trace entries usually still carry enough original selector information. WebDriverIO entries that already lost original locator presentation may still look poor in this first slice; that is acceptable because the slice is intended to improve the easy path without hiding the broader problem.

Collect-side formatting is preferred because the locator formatter already exists in browser-side locator code, while @vitest/ui does not currently depend on ivya.

2. Decouple Trace Metadata From Command Payloads

After the display formatter slice, stop treating provider command arguments as the source of trace display metadata.

At a high level, trace collection should keep these concerns separate:

  • the locator presentation shown to users
  • the selector or target used for snapshot/highlight resolution
  • the selector or target passed to the provider command

These can share values in simple paths, but they should not be the same contract.

Implementation field names such as selectorLabel, locatorLabel, or snapshotSelector are local details to choose during implementation. The product-facing concept is locator presentation, not a specific field name.

Boundaries:

  • Do not make args[0] a stronger trace metadata API.
  • Do not fix this primarily by stripping WebDriverIO >>> in trace snapshot code.
  • Preserve locator presentation before WebDriverIO lowers locators to concrete provider selectors.
  • Keep provider command behavior unchanged unless needed to add missing trace metadata around failures.

3. WebDriverIO Follow-Up

Once display metadata is separated from command payloads, WebDriverIO should carry the original locator trace metadata through its resolved-element action path while still sending the resolved CSS selector to the provider command.

For successful actions, the broad flow should be:

  • original WebdriverIOLocator retains trace display metadata
  • it resolves the element for provider execution
  • the resolved element locator carries both command selector information and trace display metadata
  • command dispatch records trace metadata from the trace metadata path, not from args[0]

For failed actions, WebDriverIO should record a failed action trace entry when locator resolution fails before provider command dispatch starts. From the user's perspective, page.getByRole(...).click() was the action that failed, even if the provider command never ran.

Expected WebDriverIO outcomes:

  • Shadow DOM mark tests should no longer show provider-lowered >>>html > ... text in the step display.
  • Shadow DOM mark tests should move from selectorResolution: "error" to selectorResolution: "matched" when the original locator can resolve in the snapshot.
  • Missing click should include a failed vitest:click action entry before lifecycle failure.
  • That failed action entry should preserve locator presentation and report selectorResolution: "missing" when the original locator parses but finds no target.

Decisions

Store the formatted locator label with trace data. This keeps the viewer simple and avoids adding locator parser/formatter dependencies to @vitest/ui.

Keep direct Element actions out of the first slice. Direct element APIs do not have an original locator presentation to preserve, so userEvent.click(element), screenshot element targets, and screenshot masks should keep existing behavior for now.

Do not change Playwright trace groups as part of this work. The immediate goal is Vitest trace-view step display.

Implementation Sketch

Display Formatter Slice

Introduce the trace target shape early as the conceptual direction, but do not wire every user call site yet:

interface BrowserTraceTarget {
  selector?: string
  selectorLabel?: string
  snapshotSelector?: string
}

For the first slice, use only local trace-recording behavior: format the selector already present on recordBrowserTraceEntry options and store the formatted label. This avoids touching locator action call sites, user-event conversion paths, and provider command plumbing in the same patch.

Add a small formatter helper close to trace recording:

import { asLocator } from 'ivya'

function formatTraceLocator(selector: string | undefined): string | undefined {
  if (!selector) {
    return
  }
  try {
    return asLocator('javascript', selector)
  }
  catch {
    return
  }
}

Add the target shape to trace entries:

export interface BrowserTraceEntry {
  name: string
  kind: BrowserTraceEntryKind
  target?: BrowserTraceTarget
  snapshot: TraceSnapshot
  // ...
}

Populate it in recordBrowserTraceEntry without changing snapshot lookup:

const target = options.target ?? {
  selector: options.selector,
  selectorLabel: formatTraceLocator(options.selector),
}
const snapshot = takeSnapshot(target.snapshotSelector ?? target.selector)
const entry = {
  ...options,
  target,
  startTime: relativeStartTime,
  snapshot,
}

Then render with a fallback in the trace viewer:

{{ step.target?.selectorLabel ?? step.target?.selector ?? step.selector }}

The temporary step.selector fallback keeps old entries and the initial local formatting patch easy to stage. Once all trace entries use target, the legacy top-level selector display path can be removed or kept only as compatibility.

Tests can assert the display field for a small Playwright-backed subset while keeping the existing raw selector assertions. This verifies the quick win without forcing the WebDriverIO behavior change into the same patch.

Later Metadata Split

For the broader provider fix, start passing BrowserTraceTarget explicitly around command dispatch and locator actions.

Expected usage:

  • selector remains the raw/debuggable selector when available.
  • selectorLabel is the display-oriented locator presentation.
  • snapshotSelector is used only for snapshot/highlight lookup and does not need to be persisted unless it proves useful for debugging.
  • Provider command args remain provider command args, not trace metadata.

The exact field names can change during implementation, but the separation should remain.

Code Pointers

Suggested Verification

Run focused browser trace specs after implementation:

CI=true cd test/browser && pnpm test specs/trace.test.ts

If snapshots change broadly, inspect only the trace metadata portions before accepting updates.

Metadata

Metadata

Assignees

No one assigned

    Labels

    feat: browserIssues and PRs related to the browser runner

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions