PageSourceSearch

https://webdriver.io/assets/js/7811e4c5.cd42be8d.js

js webdriver.io collected 2026-09-24 08:35:14 UTC 29,039 bytes, 1 lines download raw bytes

1"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[28123],{22002:(e,s,r)=>{r.d(s,{R:()=>c,x:()=>a});var t=r(7378);const n={},i=t.createContext(n);function c(e){const s=t.useContext(i);return t.useMemo(function(){return"function"==typeof e?e(s):{...s,...e}},[s,e])}function a(e){let s;return s=e.disableParentContext?"function"==typeof e.components?e.components(n):e.components||n:c(e.components),t.createElement(i.Provider,{value:s},e.children)}},29446:(e,s,r)=>{r.r(s),r.d(s,{assets:()=>o,contentTitle:()=>a,default:()=>h,frontMatter:()=>c,metadata:()=>t,toc:()=>d});const t=JSON.parse('{"id":"devtools/wdio/trace-mode","title":"Trace Mode","description":"Headless capture path \u2014 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-.zip (or a trace-/ 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.","source":"@site/docs/devtools/wdio/TraceMode.md","sourceDirName":"devtools/wdio","slug":"/devtools/wdio/trace-mode","permalink":"/docs/devtools/wdio/trace-mode","draft":false,"unlisted":false,"editUrl":"https://github.com/webdriverio/webdriverio/edit/main/website/docs/devtools/wdio/TraceMode.md","tags":[],"version":"current","frontMatter":{"id":"trace-mode","title":"Trace Mode"},"sidebar":"docs","previous":{"title":"Session Screencast","permalink":"/docs/devtools/wdio/screencast"},"next":{"title":"Trace Player","permalink":"/docs/devtools/trace-player"}}');var n=r(86106),i=r(22002);const c={id:"trace-mode",title:"Trace Mode"},a=void 0,o={},d=[{value:"Enable",id:"enable",level:2},{value:"What&#39;s inside the artifact",id:"whats-inside-the-artifact",level:2},{value:"What counts as an &quot;action&quot;",id:"what-counts-as-an-action",level:3},{value:"Output format \u2014 <code>traceFormat</code>",id:"output-format--traceformat",level:2},{value:"Trace granularity \u2014 <code>traceGranularity</code>",id:"trace-granularity--tracegranularity",level:2},{value:"Retention \u2014 <code>tracePolicy</code>",id:"retention--tracepolicy",level:2},{value:"Dense filmstrip \u2014 <code>filmstrip</code>",id:"dense-filmstrip--filmstrip",level:2},{value:"Per-test screenshot &amp; video \u2014 <code>screenshot</code> / <code>video</code>",id:"per-test-screenshot--video--screenshot--video",level:2},{value:"Artifacts manifest \u2014 <code>emitArtifactsManifest</code>",id:"artifacts-manifest--emitartifactsmanifest",level:2},{value:"Assertions \u2014 <code>captureAssertions</code>",id:"assertions--captureassertions",level:2},{value:"Mobile testing",id:"mobile-testing",level:2},{value:"Viewing the artifact",id:"viewing-the-artifact",level:2},{value:"Learn more",id:"learn-more",level:2}];function l(e){const s={a:"a",blockquote:"blockquote",code:"code",em:"em",h2:"h2",h3:"h3",li:"li",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,i.R)(),...e.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsxs)(s.p,{children:["Headless capture path \u2014 no DevTools UI window opens. At session end the adapter writes trace artifacts into a ",(0,n.jsx)(s.code,{children:"test-results/"})," folder next to your spec / config directory. For ",(0,n.jsx)(s.code,{children:"session"})," / ",(0,n.jsx)(s.code,{children:"spec"})," granularity that's a ",(0,n.jsx)(s.code,{children:"trace-<sessionId>.zip"})," (or a ",(0,n.jsx)(s.code,{children:"trace-<sessionId>/"})," directory); for ",(0,n.jsx)(s.code,{children:"test"})," granularity each test gets its own subfolder (see ",(0,n.jsx)(s.a,{href:"#trace-granularity--tracegranularity",children:"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."]}),"\n",(0,n.jsxs)(s.p,{children:["Trace mode is ",(0,n.jsx)(s.strong,{children:"mutually exclusive with live mode"}),". Pick one per session: humans debugging interactively want live; agents diffing runs or CI bots collecting artifacts want trace."]}),"\n",(0,n.jsx)(s.h2,{id:"enable",children:"Enable"}),"\n",(0,n.jsx)(s.pre,{children:(0,n.jsx)(s.code,{className:"language-ts",children:"// wdio.conf.ts\nservices: [\n  [\n    'devtools',\n    {\n      mode: 'trace',\n      traceFormat: 'zip' // optional; 'zip' (default) | 'ndjson-directory'\n    }\n  ]\n]\n"})}),"\n",(0,n.jsxs)(s.p,{children:["A complete, copy-pasteable reference config ships at ",(0,n.jsx)(s.a,{href:"https://github.com/webdriverio/devtools/blob/main/examples/wdio/wdio.trace.conf.ts",children:(0,n.jsx)(s.code,{children:"examples/wdio/wdio.trace.conf.ts"})}),"."]}),"\n",(0,n.jsxs)(s.p,{children:["Selenium and Nightwatch ship the same trace pipeline \u2014 see their adapter pages for framework-specific enable syntax: ",(0,n.jsx)(s.a,{href:"/docs/devtools/selenium#trace-mode",children:"Selenium"})," \xb7 ",(0,n.jsx)(s.a,{href:"/docs/devtools/nightwatch#trace-mode",children:"Nightwatch"}),"."]}),"\n",(0,n.jsx)(s.h2,{id:"whats-inside-the-artifact",children:"What's inside the artifact"}),"\n",(0,n.jsxs)(s.table,{children:[(0,n.jsx)(s.thead,{children:(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.th,{children:"File"}),(0,n.jsx)(s.th,{children:"Contents"})]})}),(0,n.jsxs)(s.tbody,{children:[(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"trace.trace"})}),(0,n.jsxs)(s.td,{children:["NDJSON ",(0,n.jsx)(s.code,{children:"context-options"})," + ",(0,n.jsx)(s.code,{children:"before"})," / ",(0,n.jsx)(s.code,{children:"after"})," action events; one line per record"]})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"trace.network"})}),(0,n.jsx)(s.td,{children:"HAR-style network entries, one per line"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"transcript.md"})}),(0,n.jsx)(s.td,{children:"Human/LLM-readable Markdown summary with timing, selectors, value annotations"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"resources/page@<id>-<ts>.jpeg"})}),(0,n.jsx)(s.td,{children:"Screenshot taken at each user-facing action"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"resources/page@<id>-<ts>-elements.json"})}),(0,n.jsx)(s.td,{children:"Flat list of interactable elements at that action"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"resources/page@<id>-<ts>-snapshot.txt"})}),(0,n.jsx)(s.td,{children:"Depth-indented accessibility-tree snapshot (AI-friendly)"})]})]})]}),"\n",(0,n.jsx)(s.h3,{id:"what-counts-as-an-action",children:'What c
1ounts as an "action"'}),"\n",(0,n.jsx)(s.p,{children:"Commands are filtered through an allow-list before they produce trace entries. Examples that land in the trace:"}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.code,{children:"url"})," / ",(0,n.jsx)(s.code,{children:"get"})," \u2192 ",(0,n.jsx)(s.code,{children:"Page.navigate"})]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.code,{children:"click"})," \u2192 ",(0,n.jsx)(s.code,{children:"Element.click"})]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.code,{children:"setValue"})," / ",(0,n.jsx)(s.code,{children:"sendKeys"})," \u2192 ",(0,n.jsx)(s.code,{children:"Element.fill"})]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.code,{children:"submit"}),", ",(0,n.jsx)(s.code,{children:"clear"}),", ",(0,n.jsx)(s.code,{children:"selectByVisibleText"}),", \u2026"]}),"\n"]}),"\n",(0,n.jsxs)(s.p,{children:["Internal commands like ",(0,n.jsx)(s.code,{children:"findElement"}),", ",(0,n.jsx)(s.code,{children:"waitUntil"}),", ",(0,n.jsx)(s.code,{children:"executeScript"})," are deliberately excluded \u2014 they don't represent user-facing intent and would noise up the timeline. The full allow-list lives in ",(0,n.jsx)(s.a,{href:"https://github.com/webdriverio/devtools/blob/main/packages/core/src/action-mapping.ts",children:(0,n.jsx)(s.code,{children:"@wdio/devtools-core/action-mapping.ts"})}),"."]}),"\n",(0,n.jsxs)(s.h2,{id:"output-format--traceformat",children:["Output format \u2014 ",(0,n.jsx)(s.code,{children:"traceFormat"})]}),"\n",(0,n.jsx)(s.pre,{children:(0,n.jsx)(s.code,{className:"language-ts",children:"{\n  mode: 'trace',\n  traceFormat: 'zip' | 'ndjson-directory'  // default: 'zip'\n}\n"})}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.strong,{children:(0,n.jsx)(s.code,{children:"zip"})})," (default) \u2014 single archive at ",(0,n.jsx)(s.code,{children:"test-results/trace-<sessionId>.zip"}),"."]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.strong,{children:(0,n.jsx)(s.code,{children:"ndjson-directory"})})," \u2014 same files unpacked into ",(0,n.jsx)(s.code,{children:"test-results/trace-<sessionId>/"}),". One less unzip step for scripted or agentic consumers that want to grep / stream the NDJSON directly."]}),"\n"]}),"\n",(0,n.jsxs)(s.p,{children:["Both formats open in the first-party ",(0,n.jsxs)(s.a,{href:"/docs/devtools/trace-player",children:[(0,n.jsx)(s.code,{children:"show-trace"})," player"]})," and in other compatible trace viewers."]}),"\n",(0,n.jsxs)(s.h2,{id:"trace-granularity--tracegranularity",children:["Trace granularity \u2014 ",(0,n.jsx)(s.code,{children:"traceGranularity"})]}),"\n",(0,n.jsx)(s.p,{children:"How many trace artifacts a run produces:"}),"\n",(0,n.jsx)(s.pre,{children:(0,n.jsx)(s.code,{className:"language-ts",children:"{\n  mode: 'trace',\n  traceGranularity: 'session' | 'spec' | 'test' // default: 'session'\n}\n"})}),"\n",(0,n.jsxs)(s.table,{children:[(0,n.jsx)(s.thead,{children:(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.th,{children:"Value"}),(0,n.jsx)(s.th,{children:"Output"})]})}),(0,n.jsxs)(s.tbody,{children:[(0,n.jsxs)(s.tr,{children:[(0,n.jsxs)(s.td,{children:[(0,n.jsx)(s.code,{children:"session"})," (default)"]}),(0,n.jsxs)(s.td,{children:["One trace per worker/session \u2014 ",(0,n.jsx)(s.code,{children:"test-results/trace-<sessionId>.zip"}),"."]})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"spec"})}),(0,n.jsx)(s.td,{children:"One trace per spec file. Smaller, easier to navigate."})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"test"})}),(0,n.jsxs)(s.td,{children:["One trace ",(0,n.jsx)(s.strong,{children:"per test"}),", each in its own folder: ",(0,n.jsx)(s.code,{children:"test-results/<spec>-<title>-<browser>[-retry<N>]/trace.zip"}),"."]})]})]})]}),"\n",(0,n.jsxs)(s.p,{children:["For ",(0,n.jsx)(s.code,{children:"test"})," granularity the folder name is built from the spec basename, a slug of the test title, the browser, and a ",(0,n.jsx)(s.code,{children:"-retry<N>"})," suffix on retried attempts \u2014 e.g. ",(0,n.jsx)(s.code,{children:"test-results/login_e2e-logs-in-chrome/trace.zip"}),", with a first retry at ",(0,n.jsx)(s.code,{children:"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."]}),"\n",(0,n.jsxs)(s.h2,{id:"retention--tracepolicy",children:["Retention \u2014 ",(0,n.jsx)(s.code,{children:"tracePolicy"})]}),"\n",(0,n.jsxs)(s.p,{children:["By default every trace is kept (",(0,n.jsx)(s.code,{children:"'on'"}),"). To keep only the interesting ones \u2014 ideal with ",(0,n.jsx)(s.code,{children:"traceGranularity: 'test'"}),":"]}),"\n",(0,n.jsx)(s.pre,{children:(0,n.jsx)(s.code,{className:"language-ts",children:"{\n  mode: 'trace',\n  traceGranularity: 'test',\n  tracePolicy: 'retain-on-failure' // default: 'on'\n}\n"})}),"\n",(0,n.jsxs)(s.table,{children:[(0,n.jsx)(s.thead,{children:(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.th,{children:"Policy"}),(0,n.jsx)(s.th,{children:"Keeps the trace when\u2026"})]})}),(0,n.jsxs)(s.tbody,{children:[(0,n.jsxs)(s.tr,{children:[(0,n.jsxs)(s.td,{children:[(0,n.jsx)(s.code,{children:"'on'"})," (default)"]}),(0,n.jsx)(s.td,{children:"Always \u2014 every trace is written."})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"'retain-on-failure'"})}),(0,n.jsxs)(s.td,{children:["The test's ",(0,n.jsx)(s.strong,{children:"final"})," attempt failed. A fail-then-pass retry sequence ends ",(0,n.jsx)(s.code,{children:"passed"}),", so it is ",(0,n.jsx)(s.em,{children:"not"})," kept \u2014 you don't over-retain a flake that eventually went green."]})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"'retain-on-first-failure'"})}),(0,n.jsxs)(s.td,{children:[(0,n.jsx)(s.strong,{children:"Attempt 0"})," failed, regardless of whether a later retry passed."]})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"'on-first-retry'"})}),(0,n.jsx)(s.td,{children:"The test was retried at least once (an attempt 1 exists)."})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"'on-all-retries'"})}),(0,n.jsx)(s.td,{children:"Any retried attempt (attempt \u2265 1) exists."})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"'retain-on-failure-and-retries'"})}),(0,n.jsxs)(s.td,{children:["The final attempt failed ",(0,n.jsx)(s.strong,{children:"or"})," the test was retried."]})]})]})]}),"\n",(0,n.jsxs)(s.p,{children:["A non-retained slice is decided against and never written to disk. The retry-aware policies key on a per-attempt ",(0,n.jsx)(s.strong,{children:"outcome ledger"})," the adapter keeps per retry-stable test id, so ",(0,n.jsx)(s.code,{children:"retain-on-failure"})," and ",(0,n.jsx)(s.code,{children:"retain-on-first-failure"})," evaluate the right attempt. Where a runner doesn't expose per-attempt retry information, every policy except ",(0,n.jsx)(s.code,{children:"retain-on-failure"})," degrades to ",(0,n.jsx)(s.code,{children:"retain-on-failure"}),"; a run with no observed outcomes (e.g. a plain standalone script) fails ",(0,n.jsx)(s.strong,{children:"open"})," and keeps the trace rather than risk dropping one you need."]}),"\n",(0,n.jsxs)(s.blockquote,{children:["\n",(0,n.jsxs)(s.p,{children:["Retry-aware retention is verified end-to-e
1nd for ",(0,n.jsx)(s.strong,{children:"WebdriverIO"})," (mocha / cucumber) and ",(0,n.jsx)(s.strong,{children:"Selenium"})," (mocha). For ",(0,n.jsx)(s.strong,{children:"Nightwatch"}),", ",(0,n.jsx)(s.code,{children:"retain-on-failure"})," works, but the other retry-aware policies degrade to it because Nightwatch's ",(0,n.jsx)(s.code,{children:"--retries"})," re-runs a testcase internally without re-firing the per-test hooks. WDIO's cross-process ",(0,n.jsx)(s.code,{children:"specFileRetries"})," also falls outside the (per-worker) ledger. See the ",(0,n.jsx)(s.a,{href:"/docs/devtools/nightwatch#trace-mode",children:"Nightwatch adapter page"})," for the specifics."]}),"\n"]}),"\n",(0,n.jsxs)(s.h2,{id:"dense-filmstrip--filmstrip",children:["Dense filmstrip \u2014 ",(0,n.jsx)(s.code,{children:"filmstrip"})]}),"\n",(0,n.jsxs)(s.p,{children:[(0,n.jsx)(s.strong,{children:"By default"})," the trace records a ",(0,n.jsx)(s.strong,{children:"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 ",(0,n.jsx)(s.code,{children:"filmstrip: false"})," to record only one frame per action \u2014 a smaller trace with no continuous recorder:"]}),"\n",(0,n.jsx)(s.pre,{children:(0,n.jsx)(s.code,{className:"language-ts",children:"{\n  mode: 'trace',\n  filmstrip: false // opt out \u2014 one frame per action (default is true)\n}\n"})}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsxs)(s.li,{children:["Dense frames are added ",(0,n.jsx)(s.strong,{children:"alongside"})," the per-action frames (which carry the DOM snapshots), so no DOM data is lost \u2014 when dense frames are present they supersede the sparse per-action filmstrip for scrubbing."]}),"\n",(0,n.jsxs)(s.li,{children:["Frames are thinned at export (\u2265100 ms apart) and content-addressed, so identical frames (a static wait) collapse to one resource. The live session buffer is bounded by ",(0,n.jsx)(s.code,{children:"screencast.maxBufferFrames"})," (default 2000)."]}),"\n",(0,n.jsxs)(s.li,{children:["Recording uses the screencast recorder \u2014 CDP push on Chrome/Chromium, screenshot polling elsewhere. On non-Chrome browsers the polling issues many ",(0,n.jsx)(s.code,{children:"takeScreenshot"})," commands; pair with your reporter's step-silencing option (see ",(0,n.jsx)(s.a,{href:"/docs/devtools/allure",children:"Allure Integration"}),")."]}),"\n"]}),"\n",(0,n.jsxs)(s.p,{children:[(0,n.jsx)(s.code,{children:"filmstrip"})," is available on all three adapters (WebdriverIO / Selenium / Nightwatch)."]}),"\n",(0,n.jsxs)(s.h2,{id:"per-test-screenshot--video--screenshot--video",children:["Per-test screenshot & video \u2014 ",(0,n.jsx)(s.code,{children:"screenshot"})," / ",(0,n.jsx)(s.code,{children:"video"})]}),"\n",(0,n.jsxs)(s.p,{children:["At ",(0,n.jsx)(s.code,{children:"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:"]}),"\n",(0,n.jsx)(s.pre,{children:(0,n.jsx)(s.code,{className:"language-ts",children:"{\n  mode: 'trace',\n  traceGranularity: 'test',\n  screenshot: 'only-on-failure', // 'off' (default) | 'on' | 'only-on-failure'\n  video: 'retain-on-failure'     // 'off' (default) | any tracePolicy value\n}\n"})}),"\n",(0,n.jsxs)(s.table,{children:[(0,n.jsx)(s.thead,{children:(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.th,{children:"Option"}),(0,n.jsx)(s.th,{children:"Values"}),(0,n.jsx)(s.th,{children:"Behavior"})]})}),(0,n.jsxs)(s.tbody,{children:[(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"screenshot"})}),(0,n.jsxs)(s.td,{children:[(0,n.jsx)(s.code,{children:"'off'"})," (default) \xb7 ",(0,n.jsx)(s.code,{children:"'on'"})," \xb7 ",(0,n.jsx)(s.code,{children:"'only-on-failure'"})]}),(0,n.jsxs)(s.td,{children:[(0,n.jsx)(s.code,{children:"'on'"})," captures after every test; ",(0,n.jsx)(s.code,{children:"'only-on-failure'"})," only after a failing test. PNG."]})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"video"})}),(0,n.jsxs)(s.td,{children:[(0,n.jsx)(s.code,{children:"'off'"})," (default) \xb7 any ",(0,n.jsx)(s.code,{children:"tracePolicy"})," value"]}),(0,n.jsxs)(s.td,{children:["Records the screencast continuously and keeps each test's slice per the same retention semantics as ",(0,n.jsx)(s.code,{children:"tracePolicy"}),". WebM. Setting a non-",(0,n.jsx)(s.code,{children:"off"})," value starts the recorder on its own \u2014 you don't also need ",(0,n.jsx)(s.code,{children:"filmstrip"})," or ",(0,n.jsx)(s.code,{children:"screencast.enabled"}),"."]})]})]})]}),"\n",(0,n.jsxs)(s.p,{children:["Both are gated to trace mode + ",(0,n.jsx)(s.code,{children:"traceGranularity: 'test'"})," (the per-test scope these attach to). At coarser granularities they no-op."]}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.strong,{children:"WebdriverIO"})," \u2014 ",(0,n.jsx)(s.code,{children:"screenshot"})," / ",(0,n.jsx)(s.code,{children:"video"})," are service options; attached inline to Allure when ",(0,n.jsx)(s.code,{children:"@wdio/allure-reporter"})," is present."]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.strong,{children:"Selenium"})," \u2014 same options on its ",(0,n.jsx)(s.code,{children:"DevToolsOptions"}),"; attached inline to Allure via ",(0,n.jsx)(s.code,{children:"allure-js-commons"})," when an Allure runner adapter is active."]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.strong,{children:"Nightwatch"})," \u2014 ",(0,n.jsx)(s.strong,{children:"produce-only"}),": the files are written to the trace output dir (and listed in the manifest), but not attached inline to Allure \u2014 Nightwatch has no live Allure attach API. See ",(0,n.jsx)(s.a,{href:"/docs/devtools/limitations",children:"Trace Mode Limitations"}),"."]}),"\n"]}),"\n",(0,n.jsxs)(s.blockquote,{children:["\n",(0,n.jsxs)(s.p,{children:[(0,n.jsx)(s.code,{children:"screencast.enabled"})," is the separate ",(0,n.jsx)(s.strong,{children:"live-mode"})," continuous ",(0,n.jsx)(s.code,{children:".webm"})," recording and is ignored in trace mode. In trace mode use ",(0,n.jsx)(s.code,{children:"filmstrip"})," (dense frames into the trace) or per-test ",(0,n.jsx)(s.code,{children:"video"}),"; the screencast tuning fields (",(0,n.jsx)(s.code,{children:"quality"}),", ",(0,n.jsx)(s.code,{children:"maxWidth"}),", ",(0,n.jsx)(s.code,{children:"pollIntervalMs"}),", \u2026) still apply to whichever recorder runs."]}),"\n"]}),"\n",(0,n.jsxs)(s.h2,{id:"artifacts-manifest--emitartifactsmanifest",children:["Artifacts manifest \u2014 ",(0,n.jsx)(s.code,{children:"emitArtifactsManifest"})]}),"\n",(0,n.jsxs)(s.p,{children:["Writes a ",(0,n.jsx)(s.code,{children:"devtools-artifacts-<sessionId>.json"})," next to the trace \u2014 a generic index that reporters and CI consume to discover the produced artifacts (every trace / screenshot / video, plus each test's state):"]}),"\n",(0,n.jsx)(s.pre,{children:(0,n.jsx)(s.code,{className:"language-ts",children:"{\n  mode: 'trace',\n  emitArtifactsManifest: true // default: off;
1 auto-on when Allure is detected\n}\n"})}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.strong,{children:"Off by default."})," It ",(0,n.jsx)(s.strong,{children:"auto-enables"})," when an Allure reporter is detected \u2014 WebdriverIO's ",(0,n.jsx)(s.code,{children:"@wdio/allure-reporter"})," in the config, or an active Selenium ",(0,n.jsx)(s.code,{children:"allure-js-commons"})," runtime."]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.strong,{children:"Nightwatch is opt-in"}),": it has no live Allure signal to auto-detect against (",(0,n.jsx)(s.code,{children:"nightwatch-allure"})," is post-hoc), so it never auto-enables \u2014 set it explicitly if you want the manifest."]}),"\n"]}),"\n",(0,n.jsxs)(s.h2,{id:"assertions--captureassertions",children:["Assertions \u2014 ",(0,n.jsx)(s.code,{children:"captureAssertions"})]}),"\n",(0,n.jsxs)(s.p,{children:["Assertions surface as first-class action rows in the trace (on by default; set ",(0,n.jsx)(s.code,{children:"captureAssertions: false"})," to opt out):"]}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.strong,{children:(0,n.jsx)(s.code,{children:"node:assert"})})," \u2014 captured across all three adapters as ",(0,n.jsx)(s.code,{children:"assert.<method>"})," rows."]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsxs)(s.strong,{children:["WebdriverIO ",(0,n.jsx)(s.code,{children:"expect"})]})," \u2014 passing ",(0,n.jsx)(s.em,{children:"and"})," failing ",(0,n.jsx)(s.code,{children:"expect(...)"})," matchers (",(0,n.jsx)(s.code,{children:"expect($el).toHaveText(...)"}),", ",(0,n.jsx)(s.code,{children:"toBeExisting()"}),", \u2026) appear as ",(0,n.jsx)(s.code,{children:"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."]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsxs)(s.strong,{children:["Nightwatch ",(0,n.jsx)(s.code,{children:"browser.assert.*"})," / ",(0,n.jsx)(s.code,{children:"browser.verify.*"})]})," \u2014 native assertions surface as ",(0,n.jsx)(s.code,{children:"assert.<m>"})," / ",(0,n.jsx)(s.code,{children:"verify.<m>"})," rows."]}),"\n"]}),"\n",(0,n.jsx)(s.p,{children:"Passing assertions render green; failing ones render red with the error message."}),"\n",(0,n.jsx)(s.h2,{id:"mobile-testing",children:"Mobile testing"}),"\n",(0,n.jsxs)(s.p,{children:["Trace mode detects mobile sessions via ",(0,n.jsx)(s.code,{children:"platformName: 'android' | 'ios'"})," (case-insensitive) and adjusts:"]}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.strong,{children:"Mobile web"})," (Chrome on Android, Safari on iOS): same DOM-based snapshot pipeline as desktop."]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.strong,{children:"Native mobile"}),": the page-injected DOM scripts are guarded off; ",(0,n.jsx)(s.code,{children:"getPageSource()"})," is used to grab the Appium XML tree, which feeds the snapshot serializer instead."]}),"\n"]}),"\n",(0,n.jsxs)(s.p,{children:["The trace's ",(0,n.jsx)(s.code,{children:"context-options"})," records ",(0,n.jsx)(s.code,{children:"title: 'android \u2014 <deviceName>'"})," / ",(0,n.jsx)(s.code,{children:"'ios \u2014 <deviceName>'"})," so the viewer labels frames correctly. A reference WDIO config for Android Chrome via Appium ships at ",(0,n.jsx)(s.a,{href:"https://github.com/webdriverio/devtools/blob/main/examples/wdio/wdio.mobile.conf.ts",children:(0,n.jsx)(s.code,{children:"examples/wdio/wdio.mobile.conf.ts"})}),"."]}),"\n",(0,n.jsx)(s.h2,{id:"viewing-the-artifact",children:"Viewing the artifact"}),"\n",(0,n.jsxs)(s.p,{children:["Open a trace in the first-party ",(0,n.jsx)(s.strong,{children:(0,n.jsx)(s.a,{href:"/docs/devtools/trace-player",children:"Trace Player"})})," \u2014 the WebdriverIO DevTools UI in a dedicated read-only player mode:"]}),"\n",(0,n.jsx)(s.pre,{children:(0,n.jsx)(s.code,{className:"language-sh",children:"show-trace trace-<sessionId>.zip          # bin on PATH after install\nnpx show-trace trace-<sessionId>.zip      # or via npx\n"})}
1),"\n",(0,n.jsxs)(s.p,{children:["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 ",(0,n.jsx)(s.code,{children:".zip"})," also opens in other standalone trace viewers and inside an Allure report's embedded viewer. See the ",(0,n.jsx)(s.strong,{children:(0,n.jsx)(s.a,{href:"/docs/devtools/trace-player",children:"Trace Player"})})," page for the full walkthrough, features, and keyboard shortcuts."]}),"\n",(0,n.jsx)(s.h2,{id:"learn-more",children:"Learn more"}),"\n",(0,n.jsxs)(s.p,{children:["The ",(0,n.jsx)(s.code,{children:"show-trace"})," bin shipped by each adapter opens the same archive in the DevTools player, which additionally exposes an ",(0,n.jsx)(s.strong,{children:"A11y tab"}),": the accessibility tree captured per action, where clicking a row copies that element's locator."]}),"\n",(0,n.jsxs)(s.p,{children:["Those locators are written in the recording runner's own dialect, so they paste straight into the framework that produced the trace. An element identified only by its text is ",(0,n.jsx)(s.code,{children:"a*=Logout"})," under WebdriverIO and ",(0,n.jsx)(s.code,{children:'//a[contains(., "Logout")]'})," under Selenium \u2014 captioned with the call that resolves it, ",(0,n.jsx)(s.code,{children:"By.xpath()"}),". Nightwatch prefers a native CSS locator such as ",(0,n.jsx)(s.code,{children:'button[type="submit"]'}),", because it is the only runner that reads a bare selector string under a default CSS strategy, and falls back to XPath (captioned ",(0,n.jsx)(s.code,{children:"useXpath()"})," / ",(0,n.jsx)(s.code,{children:"locateStrategy: 'xpath'"}),") only when no unique CSS locator exists. Every other locator is portable CSS."]}),"\n",(0,n.jsxs)(s.p,{children:["For LLM / agent consumption, read ",(0,n.jsx)(s.code,{children:"transcript.md"})," directly \u2014 it's a tight Markdown rendering of the actions with selectors and values."]}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.strong,{children:(0,n.jsx)(s.a,{href:"/docs/devtools/trace-player",children:"Trace Player"})})," \u2014 the full ",(0,n.jsx)(s.code,{children:"show-trace"})," player walkthrough, features, and keyboard shortcuts."]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.strong,{children:(0,n.jsx)(s.a,{href:"/docs/devtools/allure",children:"Allure Integration"})})," \u2014 how trace / screenshot / video artifacts attach to an Allure report."]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.strong,{children:(0,n.jsx)(s.a,{href:"/docs/devtools/cross-framework",children:"Cross-Framework Support"})})," \u2014 the per-adapter capability matrix (WebdriverIO / Selenium / Nightwatch)."]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.strong,{children:(0,n.jsx)(s.a,{href:"/docs/devtools/limitations",children:"Trace Mode Limitations"})})," \u2014 what trace mode skips and the known per-adapter gaps."]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.strong,{children:(0,n.jsx)(s.a,{href:"/docs/devtools/reference",children:"Configuration Reference"})})," \u2014 every option at a glance."]}),"\n"]})]})}function h(e={}){const{wrapper:s}={...(0,i.R)(),...e.components};return s?(0,n.jsx)(s,{...e,children:(0,n.jsx)(l,{...e})}):l(e)}}}]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.