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.
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.
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:
selectorResolution: "error".Direction
1. Display Formatter Slice
Add a narrow first slice that improves trace step display without changing command execution or snapshot resolution.
Recommended behavior:
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/uidoes not currently depend onivya.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:
These can share values in simple paths, but they should not be the same contract.
Implementation field names such as
selectorLabel,locatorLabel, orsnapshotSelectorare local details to choose during implementation. The product-facing concept is locator presentation, not a specific field name.Boundaries:
args[0]a stronger trace metadata API.>>>in trace snapshot code.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:
WebdriverIOLocatorretains trace display metadataargs[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:
>>>html > ...text in the step display.selectorResolution: "error"toselectorResolution: "matched"when the original locator can resolve in the snapshot.vitest:clickaction entry before lifecycle failure.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
Elementactions out of the first slice. Direct element APIs do not have an original locator presentation to preserve, souserEvent.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:
For the first slice, use only local trace-recording behavior: format the selector already present on
recordBrowserTraceEntryoptions 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:
Add the target shape to trace entries:
Populate it in
recordBrowserTraceEntrywithout changing snapshot lookup:Then render with a fallback in the trace viewer:
{{ step.target?.selectorLabel ?? step.target?.selector ?? step.selector }}The temporary
step.selectorfallback keeps old entries and the initial local formatting patch easy to stage. Once all trace entries usetarget, 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
BrowserTraceTargetexplicitly around command dispatch and locator actions.Expected usage:
selectorremains the raw/debuggable selector when available.selectorLabelis the display-oriented locator presentation.snapshotSelectoris used only for snapshot/highlight lookup and does not need to be persisted unless it proves useful for debugging.The exact field names can change during implementation, but the separation should remain.
Code Pointers
Suggested Verification
Run focused browser trace specs after implementation:
If snapshots change broadly, inspect only the trace metadata portions before accepting updates.