website/docs/devtools/wdio/TraceMode.md
Headless capture path — no DevTools UI window opens. At session end the adapter writes trace artifacts into a test-results/ folder next to your spec / config directory. For session / spec granularity that's a trace-<sessionId>.zip (or a trace-<sessionId>/ directory); for test granularity each test gets its own subfolder (see Trace granularity). The artifact is portable and ships everything needed for offline replay, AI-agent diffing, or any consumer that prefers a file over a live UI.
Trace mode is mutually exclusive with live mode. Pick one per session: humans debugging interactively want live; agents diffing runs or CI bots collecting artifacts want trace.
// wdio.conf.ts
services: [
[
'devtools',
{
mode: 'trace',
traceFormat: 'zip' // optional; 'zip' (default) | 'ndjson-directory'
}
]
]
A complete, copy-pasteable reference config ships at examples/wdio/wdio.trace.conf.ts.
Selenium and Nightwatch ship the same trace pipeline — see their adapter pages for framework-specific enable syntax: Selenium · Nightwatch.
| File | Contents |
|---|---|
trace.trace | NDJSON context-options + before / after action events; one line per record |
trace.network | HAR-style network entries, one per line |
transcript.md | Human/LLM-readable Markdown summary with timing, selectors, value annotations |
resources/page@<id>-<ts>.jpeg | Screenshot taken at each user-facing action |
resources/page@<id>-<ts>-elements.json | Flat list of interactable elements at that action |
resources/page@<id>-<ts>-snapshot.txt | Depth-indented accessibility-tree snapshot (AI-friendly) |
Commands are filtered through an allow-list before they produce trace entries. Examples that land in the trace:
url / get → Page.navigateclick → Element.clicksetValue / sendKeys → Element.fillsubmit, clear, selectByVisibleText, …Internal commands like findElement, waitUntil, executeScript are deliberately excluded — they don't represent user-facing intent and would noise up the timeline. The full allow-list lives in @wdio/devtools-core/action-mapping.ts.
traceFormat{
mode: 'trace',
traceFormat: 'zip' | 'ndjson-directory' // default: 'zip'
}
zip (default) — single archive at test-results/trace-<sessionId>.zip.ndjson-directory — same files unpacked into test-results/trace-<sessionId>/. One less unzip step for scripted or agentic consumers that want to grep / stream the NDJSON directly.Both formats open in the first-party show-trace player and in other compatible trace viewers.
traceGranularityHow many trace artifacts a run produces:
{
mode: 'trace',
traceGranularity: 'session' | 'spec' | 'test' // default: 'session'
}
| Value | Output |
|---|---|
session (default) | One trace per worker/session — test-results/trace-<sessionId>.zip. |
spec | One trace per spec file. Smaller, easier to navigate. |
test | One trace per test, each in its own folder: test-results/<spec>-<title>-<browser>[-retry<N>]/trace.zip. |
For test granularity the folder name is built from the spec basename, a slug of the test title, the browser, and a -retry<N> suffix on retried attempts — e.g. test-results/login_e2e-logs-in-chrome/trace.zip, with a first retry at test-results/login_e2e-logs-in-chrome-retry1/trace.zip. Per-test traces are the most navigable and pair best with a retention policy so only the traces you care about are written.
tracePolicyBy default every trace is kept ('on'). To keep only the interesting ones — ideal with traceGranularity: 'test':
{
mode: 'trace',
traceGranularity: 'test',
tracePolicy: 'retain-on-failure' // default: 'on'
}
| Policy | Keeps the trace when… |
|---|---|
'on' (default) | Always — every trace is written. |
'retain-on-failure' | The test's final attempt failed. A fail-then-pass retry sequence ends passed, so it is not kept — you don't over-retain a flake that eventually went green. |
'retain-on-first-failure' | Attempt 0 failed, regardless of whether a later retry passed. |
'on-first-retry' | The test was retried at least once (an attempt 1 exists). |
'on-all-retries' | Any retried attempt (attempt ≥ 1) exists. |
'retain-on-failure-and-retries' | The final attempt failed or the test was retried. |
A non-retained slice is decided against and never written to disk. The retry-aware policies key on a per-attempt outcome ledger the adapter keeps per retry-stable test id, so retain-on-failure and retain-on-first-failure evaluate the right attempt. Where a runner doesn't expose per-attempt retry information, every policy except retain-on-failure degrades to retain-on-failure; a run with no observed outcomes (e.g. a plain standalone script) fails open and keeps the trace rather than risk dropping one you need.
Retry-aware retention is verified end-to-end for WebdriverIO (mocha / cucumber) and Selenium (mocha). For Nightwatch,
retain-on-failureworks, but the other retry-aware policies degrade to it because Nightwatch's--retriesre-runs a testcase internally without re-firing the per-test hooks. WDIO's cross-processspecFileRetriesalso falls outside the (per-worker) ledger. See the Nightwatch adapter page for the specifics.
filmstripBy default the trace records a dense, continuous screencast so the player scrubs smooth playback rather than jumping frame-to-frame. The dense frames sit alongside the per-action frames (which carry the DOM snapshots). Set filmstrip: false to record only one frame per action — a smaller trace with no continuous recorder:
{
mode: 'trace',
filmstrip: false // opt out — one frame per action (default is true)
}
screencast.maxBufferFrames (default 2000).takeScreenshot commands; pair with your reporter's step-silencing option (see Allure Integration).filmstrip is available on all three adapters (WebdriverIO / Selenium / Nightwatch).
screenshot / videoAt traceGranularity: 'test' each test can also produce a standalone screenshot and/or a per-test video slice, mirroring the familiar screenshot/video-on-failure ergonomics:
{
mode: 'trace',
traceGranularity: 'test',
screenshot: 'only-on-failure', // 'off' (default) | 'on' | 'only-on-failure'
video: 'retain-on-failure' // 'off' (default) | any tracePolicy value
}
| Option | Values | Behavior |
|---|---|---|
screenshot | 'off' (default) · 'on' · 'only-on-failure' | 'on' captures after every test; 'only-on-failure' only after a failing test. PNG. |
video | 'off' (default) · any tracePolicy value | Records the screencast continuously and keeps each test's slice per the same retention semantics as tracePolicy. WebM. Setting a non-off value starts the recorder on its own — you don't also need filmstrip or screencast.enabled. |
Both are gated to trace mode + traceGranularity: 'test' (the per-test scope these attach to). At coarser granularities they no-op.
screenshot / video are service options; attached inline to Allure when @wdio/allure-reporter is present.DevToolsOptions; attached inline to Allure via allure-js-commons when an Allure runner adapter is active.
screencast.enabledis the separate live-mode continuous.webmrecording and is ignored in trace mode. In trace mode usefilmstrip(dense frames into the trace) or per-testvideo; the screencast tuning fields (quality,maxWidth,pollIntervalMs, …) still apply to whichever recorder runs.
emitArtifactsManifestWrites a devtools-artifacts-<sessionId>.json next to the trace — a generic index that reporters and CI consume to discover the produced artifacts (every trace / screenshot / video, plus each test's state):
{
mode: 'trace',
emitArtifactsManifest: true // default: off; auto-on when Allure is detected
}
@wdio/allure-reporter in the config, or an active Selenium allure-js-commons runtime.nightwatch-allure is post-hoc), so it never auto-enables — set it explicitly if you want the manifest.captureAssertionsAssertions surface as first-class action rows in the trace (on by default; set captureAssertions: false to opt out):
node:assert — captured across all three adapters as assert.<method> rows.expect — passing and failing expect(...) matchers (expect($el).toHaveText(...), toBeExisting(), …) appear as expect.<matcher> rows carrying the expected value, the element's source location, and a snapshot; the matcher's internal polling commands are suppressed so only the assertion shows.browser.assert.* / browser.verify.* — native assertions surface as assert.<m> / verify.<m> rows.Passing assertions render green; failing ones render red with the error message.
Trace mode detects mobile sessions via platformName: 'android' | 'ios' (case-insensitive) and adjusts:
getPageSource() is used to grab the Appium XML tree, which feeds the snapshot serializer instead.The trace's context-options records title: 'android — <deviceName>' / 'ios — <deviceName>' so the viewer labels frames correctly. A reference WDIO config for Android Chrome via Appium ships at examples/wdio/wdio.mobile.conf.ts.
Open a trace in the first-party Trace Player — the WebdriverIO DevTools UI in a dedicated read-only player mode:
show-trace trace-<sessionId>.zip # bin on PATH after install
npx show-trace trace-<sessionId>.zip # or via npx
The player gives you DOM time-travel, the A11y tab and pick-locator overlay, the Transcript tab with Copy-for-LLM, the Errors / Console / Network / Source dock tabs, and a scrubbable timeline. The same portable .zip also opens in other standalone trace viewers and inside an Allure report's embedded viewer. See the Trace Player page for the full walkthrough, features, and keyboard shortcuts.
show-trace player walkthrough, features, and keyboard shortcuts.