@wdio/nightwatch-devtools 1.8.2 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +78 -16
- package/dist/{bidi-WNZOXX5X.js → bidi-C67DYUCA.js} +4 -2
- package/dist/{bidi-WNZOXX5X.js.map → bidi-C67DYUCA.js.map} +1 -1
- package/dist/{chunk-DM4FCAIJ.js → chunk-DRZC45YG.js} +961 -251
- package/dist/chunk-DRZC45YG.js.map +1 -0
- package/dist/index.js +727 -285
- package/dist/index.js.map +1 -1
- package/package.json +10 -7
- package/dist/chunk-DM4FCAIJ.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../shared/src/runner.ts","../../core/src/error.ts","../../trace/src/trace-retention.ts","../../core/src/attempt-tracker.ts","../../core/src/artifacts-manifest.ts","../../core/src/spec-trace-helpers.ts","../../trace/src/trace-exporter.ts","../../shared/src/trace-actions.ts","../../shared/src/action-mapping.ts","../../shared/src/browser-schemes.ts","../../shared/src/console.ts","../../shared/src/device.ts","../../shared/src/locator-dialect.ts","../../shared/src/element-scripts.ts","../../shared/src/page-scripts.ts","../../shared/src/command-tree.ts","../../shared/src/command-failure.ts","../../shared/src/image.ts","../../shared/src/request-type.ts","../../shared/src/routes.ts","../../shared/src/snapshot-format.ts","../../shared/src/trace-file.ts","../../shared/src/types.ts","../../trace/src/trace-console.ts","../../trace/src/trace-snapshots.ts","../../trace/src/screencast-trace.ts","../../trace/src/trace-frame-snapshots.ts","../../trace/src/sha1.ts","../../trace/src/trace-sources.ts","../../trace/src/trace-hierarchy.ts","../../trace/src/trace-action-events.ts","../../trace/src/trace-har.ts","../../trace/src/trace-zip-writer.ts","../../trace/src/trace-mutations.ts","../../trace/src/trace-transcript.ts","../../core/src/uid.ts","../../core/src/artifact-naming.ts","../../core/src/trace-finalizer.ts","../../core/src/bidi.ts","../../core/src/net.ts","../../core/src/output-dir.ts","../../trace/src/a11y-snapshot.ts","../../core/src/locators/xml-parsing.ts","../../core/src/locators/xpath-index.ts","../../core/src/locators/constants.ts","../../core/src/locators/locator-generation.ts","../../core/src/element-snapshot.ts","../../core/src/locators/element-filter.ts","../../core/src/locators/index.ts","../../core/src/with-timeout.ts","../../core/src/action-snapshot.ts","../../core/src/allure-artifacts.ts","../../core/src/screenshot-artifact.ts","../../core/src/video-slice.ts","../../core/src/video-encoder.ts","../../core/src/webdriver-http.ts","../../core/src/appium-interceptor.ts","../../core/src/viewport.ts","../../core/src/assert-patcher.ts","../../core/src/stack.ts","../../core/src/script-loader.ts","../../core/src/bidi-preload.ts","../../core/src/terminal-throttle.ts","../../core/src/finalize-screencast.ts","../../core/src/input-dispatch.ts","../../core/src/performance-capture.ts","../../core/src/read-value-locators.ts","../../core/src/retry-tracker.ts","../../core/src/run-id.ts","../../core/src/screencast.ts","../../core/src/browser-focus.ts","../../core/src/session-capturer.ts","../../core/src/suite-helpers.ts","../../core/src/test-discovery.ts","../../core/src/test-reporter.ts"],"sourcesContent":["/**\n * HTTP contracts for the runner endpoints. Imported by the backend route\n * handlers and the app's fetch callers — keeps the body shape in lockstep\n * across the wire instead of relying on `Record<string, unknown>`.\n */\n\nexport const TESTS_API = {\n run: '/api/tests/run',\n stop: '/api/tests/stop'\n} as const\n\n/**\n * Environment variables the backend's rerun spawner sets on the child\n * process: how the adapter (service/nightwatch/selenium/python) detects the\n * reuse-mode handshake and connects to the existing dashboard backend instead\n * of starting a new one, plus what the child cannot work out for itself.\n * `LAUNCH_COMMAND` is that second kind — a child is spawned with one test\n * named on its command line, so an adapter deriving its Run-all command from\n * its own invocation would republish \"run that one test\" as the command for\n * running everything, and its arguments no longer say what it was narrowed\n * from. Single source of truth — typos in any leg of the handshake silently\n * break reruns, so every package on both sides references this object.\n */\nexport const REUSE_ENV = {\n REUSE: 'DEVTOOLS_APP_REUSE',\n HOST: 'DEVTOOLS_APP_HOST',\n PORT: 'DEVTOOLS_APP_PORT',\n RERUN_LABEL: 'DEVTOOLS_RERUN_LABEL',\n RERUN_ENTRY_TYPE: 'DEVTOOLS_RERUN_ENTRY_TYPE',\n LAUNCH_COMMAND: 'DEVTOOLS_RERUN_LAUNCH_COMMAND'\n} as const\n\n/**\n * Environment variables the WDIO service writes during `onPrepare` (config\n * path it detected, initial --spec args) so the backend's rerun spawner can\n * relaunch with the same config. Also covers DEVTOOLS_RUNNER_CWD which the\n * backend reads to know which directory to spawn the child in. Bin-override\n * vars (DEVTOOLS_WDIO_BIN, DEVTOOLS_NIGHTWATCH_BIN) live here too — they're\n * test-rig overrides that backend's bin-resolver respects. RUN_ID is stamped\n * before any worker forks so every worker of one run inherits it and reports\n * the same run identity (see `WORKER_WS_QUERY.runId`).\n */\nexport const RUNNER_ENV = {\n RUN_ID: 'DEVTOOLS_RUN_ID',\n WDIO_CONFIG: 'DEVTOOLS_WDIO_CONFIG',\n NIGHTWATCH_CONFIG: 'DEVTOOLS_NIGHTWATCH_CONFIG',\n WDIO_INITIAL_SPECS: 'DEVTOOLS_WDIO_INITIAL_SPECS',\n // The selection the Nightwatch run was launched with (specs, --tag, --env, ...), without --config; see encodeInitialArgs.\n NIGHTWATCH_INITIAL_ARGS: 'DEVTOOLS_NIGHTWATCH_INITIAL_ARGS',\n RUNNER_CWD: 'DEVTOOLS_RUNNER_CWD',\n WDIO_BIN: 'DEVTOOLS_WDIO_BIN',\n NIGHTWATCH_BIN: 'DEVTOOLS_NIGHTWATCH_BIN'\n} as const\n\n/** The value of `RUNNER_ENV.NIGHTWATCH_INITIAL_ARGS`: a JSON array of strings. */\nexport function encodeInitialArgs(args: readonly string[]): string {\n return JSON.stringify(args)\n}\n\n/** The arguments `encodeInitialArgs` wrote, or undefined for an absent or malformed value. */\nexport function parseInitialArgs(\n raw: string | undefined\n): string[] | undefined {\n if (!raw) {\n return undefined\n }\n let parsed: unknown\n try {\n parsed = JSON.parse(raw)\n } catch {\n return undefined\n }\n return Array.isArray(parsed) && parsed.every((a) => typeof a === 'string')\n ? parsed\n : undefined\n}\n\n/**\n * Slots an adapter leaves in its `rerunCommand`, which the backend fills in\n * when the dashboard reruns one entry. The two differ in what they select by,\n * and the difference is not cosmetic:\n *\n * - `testName` is a NAME PATTERN. It is regex-escaped on substitution because\n * every runner that consumes it filters by regex (mocha `--grep`, jest\n * `--testNamePattern`, cucumber `--name`).\n * - `testId` is an EXACT id, substituted verbatim and shell-quoted. pytest\n * selects by nodeid (`file.py::Class::test`), matched literally, so the\n * escaping `testName` needs would corrupt it — measured, `pytest\n * 'test_thing\\.py::test_a'` collects nothing.\n *\n * A template carries one or the other; `testId` is filled from the entry's\n * `uid`, `testName` from its label.\n */\nexport const RERUN_SLOT = {\n testName: '{{testName}}',\n testId: '{{testId}}'\n} as const\n\n/** POST /api/tests/run body. */\nexport interface RunnerRequestBody {\n uid: string\n entryType: 'suite' | 'test'\n specFile?: string\n fullTitle?: string\n label?: string\n callSource?: string\n runAll?: boolean\n framework?: string\n configFile?: string\n lineNumber?: number\n devtoolsHost?: string\n devtoolsPort?: number\n featureFile?: string\n featureLine?: number\n suiteType?: string\n rerunCommand?: string\n launchCommand?: string\n preserveBaseline?: boolean\n}\n\n/** 200 response from /api/tests/run and /api/tests/stop. */\nexport interface RunnerOkResponse {\n ok: true\n}\n\n/** 4xx response shape from runner endpoints. */\nexport interface RunnerErrorResponse {\n error: string\n}\n","import type { SerializedError } from '@wdio/devtools-shared'\n\nexport type { SerializedError }\n\n/**\n * Coerce an unknown value (caught exception, framework-supplied error\n * object, string, etc.) into an Error instance. Used at adapter command\n * boundaries where caught values can be anything — Error subclasses,\n * thrown strings, framework objects with a `.message` — and downstream\n * code wants a stable `Error` to inspect and serialize.\n */\nexport function toError(value: unknown): Error {\n if (value instanceof Error) {\n return value\n }\n if (\n value !== null &&\n typeof value === 'object' &&\n typeof (value as { message?: unknown }).message === 'string'\n ) {\n const e = new Error((value as { message: string }).message)\n const name = (value as { name?: unknown }).name\n if (typeof name === 'string') {\n e.name = name\n }\n return e\n }\n try {\n return new Error(String(value))\n } catch {\n // `String()` throws for a null-prototype object.\n return new Error(Object.prototype.toString.call(value))\n }\n}\n\n/**\n * Extract a printable message from a caught value. Equivalent to reading\n * `.message` on an Error, but degrades cleanly when the thrown value is a\n * string, a plain object, undefined, or anything else — `(err as Error).message`\n * silently returns `undefined` in those cases and yields useless log output.\n */\nexport function errorMessage(value: unknown): string {\n if (value instanceof Error) {\n return value.message\n }\n if (typeof value === 'string') {\n return value\n }\n if (\n value !== null &&\n typeof value === 'object' &&\n typeof (value as { message?: unknown }).message === 'string'\n ) {\n return (value as { message: string }).message\n }\n if (value === undefined || value === null) {\n return 'unknown error'\n }\n try {\n return String(value)\n } catch {\n return 'unknown error'\n }\n}\n\n/**\n * True when an error message signals the WebDriver session is gone or\n * unreachable — expected when deferred/async capture work races session\n * teardown. Adapters use it to swallow post-quit errors instead of logging noise.\n */\nexport function isSessionGoneError(message: string): boolean {\n return (\n message.includes('ECONNREFUSED') ||\n message.includes('no such session') ||\n message.includes('invalid session id')\n )\n}\n\n/**\n * Normalize an Error to a plain object so its fields survive `JSON.stringify`\n * over the WS bridge. Error instances have `message`/`name`/`stack` as\n * non-enumerable, which `JSON.stringify` would drop.\n *\n * Returns `undefined` when the input is undefined so callers can pass through\n * possibly-undefined values without an extra branch.\n */\nexport function serializeError(\n error: Error | undefined\n): SerializedError | undefined {\n if (!error) {\n return undefined\n }\n return { name: error.name, message: error.message, stack: error.stack }\n}\n","import type {\n DevToolsMode,\n TestStatus,\n TraceRetentionPolicy\n} from '@wdio/devtools-shared'\n\n/**\n * Pure retention policy evaluation for trace mode. Adapters collect the\n * observed test outcomes for a trace scope (session/spec/test) and ask\n * whether the written trace should be kept.\n */\n\n/** Every policy the evaluator recognizes. An unknown string (from a JS config\n * that slipped past the type) is treated as `on` — fail open, never silently\n * drop a trace the user might need. */\nconst KNOWN_POLICIES = new Set<TraceRetentionPolicy>([\n 'on',\n 'retain-on-failure',\n 'retain-on-first-failure',\n 'on-first-retry',\n 'on-all-retries',\n 'retain-on-failure-and-retries'\n])\n\nexport interface TestOutcome {\n /** Retry-stable test identity. Outcomes sharing a uid are one test's attempts\n * (attempt 0, 1, …); an outcome with no uid is its own single-attempt group,\n * so a flat/uid-less feed evaluates exactly as one-outcome-per-test. */\n uid?: string\n state?: TestStatus\n attempt?: number\n}\n\nexport interface RetentionInput {\n outcomes: Iterable<TestOutcome>\n /** Whether the adapter can distinguish retries (per-outcome `attempt`). */\n attemptInfoAvailable: boolean\n}\n\nexport interface RetentionDecision {\n retain: boolean\n /** Set when a retry-aware policy fell back to `retain-on-failure` because attempt info was unavailable. */\n degradedToFailure?: boolean\n /** Set when no outcomes were observed (standalone scripts) — retained rather than risk losing a needed trace. */\n failOpen?: boolean\n}\n\nexport function shouldRetainTrace(\n policy: TraceRetentionPolicy | undefined,\n input: RetentionInput\n): RetentionDecision {\n if (policy === undefined || policy === 'on' || !KNOWN_POLICIES.has(policy)) {\n return { retain: true }\n }\n const outcomes = Array.from(input.outcomes)\n if (outcomes.length === 0) {\n return { retain: true, failOpen: true }\n }\n const anyFailed = outcomes.some((o) => o.state === 'failed')\n if (!input.attemptInfoAvailable && policy !== 'retain-on-failure') {\n return { retain: anyFailed, degradedToFailure: true }\n }\n // Group a test's attempts so failure policies key on the RIGHT attempt: the\n // *final* attempt (retain-on-failure — a fail-then-pass ends passed) vs the\n // *first* attempt (retain-on-first-failure). A uid-less feed makes every\n // outcome its own group, so this reduces to the flat one-outcome-per-test\n // logic and is byte-identical for callers that don't supply per-attempt uids.\n const groups = groupByTest(outcomes)\n switch (policy) {\n case 'retain-on-failure':\n return { retain: groups.some((g) => finalAttempt(g).state === 'failed') }\n case 'retain-on-first-failure':\n return {\n retain: groups.some((g) => firstAttempt(g)?.state === 'failed')\n }\n case 'on-first-retry':\n return { retain: groups.some((g) => g.some((o) => o.attempt === 1)) }\n case 'on-all-retries':\n return { retain: groups.some((g) => g.some((o) => attemptOf(o) >= 1)) }\n case 'retain-on-failure-and-retries':\n return {\n retain: groups.some(\n (g) =>\n finalAttempt(g).state === 'failed' ||\n g.some((o) => attemptOf(o) >= 1)\n )\n }\n }\n}\n\nconst attemptOf = (o: TestOutcome): number => o.attempt ?? 0\n\n/** Group outcomes by `uid`; a uid-less outcome becomes its own singleton group\n * (preserving flat, one-outcome-per-test evaluation for callers without uids). */\nfunction groupByTest(outcomes: TestOutcome[]): TestOutcome[][] {\n const byUid = new Map<string, TestOutcome[]>()\n const groups: TestOutcome[][] = []\n for (const outcome of outcomes) {\n if (outcome.uid === undefined) {\n groups.push([outcome])\n continue\n }\n const existing = byUid.get(outcome.uid)\n if (existing) {\n existing.push(outcome)\n } else {\n const group = [outcome]\n byUid.set(outcome.uid, group)\n groups.push(group)\n }\n }\n return groups\n}\n\n/** The highest-numbered attempt's outcome — the test's final result. */\nfunction finalAttempt(group: TestOutcome[]): TestOutcome {\n return group.reduce((best, o) => (attemptOf(o) >= attemptOf(best) ? o : best))\n}\n\n/** The attempt-0 outcome, if the group recorded one. */\nfunction firstAttempt(group: TestOutcome[]): TestOutcome | undefined {\n return group.find((o) => attemptOf(o) === 0)\n}\n\n/**\n * Warning text when a retention policy is configured outside trace mode, where\n * it has no effect (the finalizer no-ops in live mode). Returns undefined when\n * there is nothing to warn about, so each adapter can `if (msg) log.warn(msg)`.\n */\nexport function tracePolicyModeWarning(\n policy: TraceRetentionPolicy | undefined,\n mode: DevToolsMode | undefined\n): string | undefined {\n if (policy === undefined || mode === 'trace') {\n return undefined\n }\n return 'tracePolicy only applies in trace mode; ignoring it because mode is not \"trace\".'\n}\n","import type { TestStatus } from '@wdio/devtools-shared'\nimport type { TestOutcome } from '@wdio/devtools-trace/trace-retention'\n\n/**\n * Framework-agnostic per-test attempt ledger. Every supported runner re-enters\n * its per-test start hook when a test is retried, so recording the same uid\n * again appends a new attempt slot (0-based: first run is attempt 0, first retry\n * is attempt 1). Once the outcome is known the adapter stamps it onto the slot.\n * This is the primary, runner-independent retry signal feeding the retry-aware\n * trace policies — the finalizer reads the scoped views to evaluate retention\n * per test/spec/session with real per-attempt outcomes (see trace-retention.ts).\n */\nexport interface RetryOutcomeView {\n /** Every attempt of every test — for session-scope retention. */\n all(): TestOutcome[]\n /** Every attempt of the tests recorded against `specFile` — for spec scope. */\n forSpec(specFile: string): TestOutcome[]\n /** A single test's attempts; pass `attempt` to scope to one attempt's slice. */\n forTest(uid: string, attempt?: number): TestOutcome[]\n}\n\nexport class TestAttemptTracker implements RetryOutcomeView {\n #ledger = new Map<string, { specFile?: string; attempts: TestOutcome[] }>()\n #sawRetry = false\n\n /** Record a starting test; returns its attempt number (0 first, +1 per rerun).\n * `specFile` (optional) enables spec-scoped retention lookups. */\n recordStart(uid: string, specFile?: string): number {\n const entry = this.#ledger.get(uid)\n if (entry && entry.attempts.length > 0) {\n this.#sawRetry = true\n // A retry only follows a failure. Some runners (e.g. Mocha through a\n // --require plugin) can't observe the failed attempt's state from their\n // outcome hook and report it as passed — the retry starting IS the\n // reliable failure signal, so stamp the prior attempt failed here.\n entry.attempts[entry.attempts.length - 1].state = 'failed'\n }\n const attempt = entry ? entry.attempts.length : 0\n const slot: TestOutcome = { uid, attempt }\n if (entry) {\n entry.attempts.push(slot)\n if (specFile !== undefined) {\n entry.specFile = specFile\n }\n } else {\n this.#ledger.set(uid, { specFile, attempts: [slot] })\n }\n return attempt\n }\n\n /** Stamp the resolved state onto uid's most recent attempt slot once the\n * outcome is known. `attempt` overrides the slot's number when the adapter\n * resolved a more authoritative value (e.g. WDIO's result.retries). */\n recordOutcome(\n uid: string,\n state: TestStatus | undefined,\n attempt?: number\n ): void {\n const attempts = this.#ledger.get(uid)?.attempts\n const slot = attempts?.[attempts.length - 1]\n if (!slot) {\n return\n }\n slot.state = state\n if (attempt !== undefined) {\n slot.attempt = attempt\n }\n }\n\n /** Latest attempt recorded for `uid`, or undefined if it never started. */\n attemptFor(uid: string): number | undefined {\n const entry = this.#ledger.get(uid)\n return entry ? entry.attempts.length - 1 : undefined\n }\n\n /** True once any test has started more than once (a retry occurred). */\n get sawRetry(): boolean {\n return this.#sawRetry\n }\n\n all(): TestOutcome[] {\n const out: TestOutcome[] = []\n for (const entry of this.#ledger.values()) {\n out.push(...entry.attempts)\n }\n return out\n }\n\n forSpec(specFile: string): TestOutcome[] {\n const out: TestOutcome[] = []\n for (const entry of this.#ledger.values()) {\n if (entry.specFile === specFile) {\n out.push(...entry.attempts)\n }\n }\n return out\n }\n\n forTest(uid: string, attempt?: number): TestOutcome[] {\n const attempts = this.#ledger.get(uid)?.attempts ?? []\n return attempt === undefined\n ? attempts\n : attempts.filter((a) => a.attempt === attempt)\n }\n\n /** Clear all state — used at session boundaries and reuse-mode reconnects. */\n reset(): void {\n this.#ledger.clear()\n this.#sawRetry = false\n }\n}\n","/**\n * The generic artifacts manifest: a single JSON side-file\n * (`devtools-artifacts-<sessionId>.json`) written next to the trace/video\n * artifacts at end-of-run. It enumerates every artifact a trace-mode session\n * produced — including the ones a retention policy decided against\n * (`retained: false`) — alongside each test's final state and attempt. Any\n * ecosystem reporter (Allure, a CI collector, a custom dashboard) can read this\n * one file to discover what to attach and how each test fared, instead of\n * re-deriving it from framework internals. The WDIO Allure glue consumes it\n * directly; Selenium/Nightwatch document reader recipes against it.\n */\n\nimport fs from 'node:fs/promises'\nimport path from 'node:path'\nimport type {\n TestMetadataEntry,\n TestMetadataMap,\n TraceFormat\n} from '@wdio/devtools-shared'\nimport type { TraceArtifact } from './trace-finalizer.js'\n\n/** One test's outcome as recorded at export time. Field types track\n * TestMetadataEntry in shared; adds the keying uid, drops trace-only ancestry. */\nexport interface ArtifactManifestTest extends Pick<\n TestMetadataEntry,\n 'title' | 'specFile' | 'state' | 'attempt'\n> {\n uid: string\n}\n\nexport interface ArtifactsManifest {\n sessionId: string\n format: TraceFormat\n artifacts: TraceArtifact[]\n tests: ArtifactManifestTest[]\n}\n\n/** Deterministic manifest filename for a session (used by writers and readers). */\nexport function artifactsManifestFilename(sessionId: string): string {\n return `devtools-artifacts-${sessionId}.json`\n}\n\n/** Assemble the manifest from the artifacts collected across the run and the\n * test-metadata map. Pure — the write is separate so it stays unit-testable. */\nexport function buildArtifactsManifest(input: {\n sessionId: string\n format: TraceFormat\n artifacts: readonly TraceArtifact[]\n testMetadata: TestMetadataMap\n}): ArtifactsManifest {\n const tests: ArtifactManifestTest[] = []\n for (const [uid, meta] of input.testMetadata) {\n tests.push({\n uid,\n title: meta.title,\n specFile: meta.specFile,\n state: meta.state,\n attempt: meta.attempt\n })\n }\n return {\n sessionId: input.sessionId,\n format: input.format,\n artifacts: [...input.artifacts],\n tests\n }\n}\n\n/** Write the manifest to `outputDir`, returning its absolute path. */\nexport async function writeArtifactsManifest(\n outputDir: string,\n manifest: ArtifactsManifest\n): Promise<string> {\n await fs.mkdir(outputDir, { recursive: true })\n const filePath = path.join(\n outputDir,\n artifactsManifestFilename(manifest.sessionId)\n )\n await fs.writeFile(filePath, JSON.stringify(manifest, null, 2), 'utf8')\n return filePath\n}\n","/**\n * Shared helpers for per-spec trace partitioning.\n *\n * The three adapters (service, nightwatch, selenium) all use the same\n * index-range tracking and spec-name sanitization logic. This module\n * is the single source of truth — adapters import from here.\n */\n\nimport path from 'node:path'\nimport type {\n ActionSnapshot,\n CommandLog,\n ScreencastFrame,\n TestMetadataMap,\n TraceFormat,\n TraceGranularity\n} from '@wdio/devtools-shared'\nimport type { TraceCapturer } from '@wdio/devtools-trace/trace-exporter'\nimport { writeTraceZip } from '@wdio/devtools-trace/trace-exporter'\nimport { deterministicUid, isStepUidOf } from './uid.js'\nimport { trimChar } from './artifact-naming.js'\n\n// ─── SpecRange ────────────────────────────────────────────────────────────────\n\n/** Index ranges into a SessionCapturer's flat arrays for one trace slice\n * (a spec file, or a single test under `test` granularity). */\nexport interface SpecRange {\n specFile: string\n /** Dedupe/identity key: spec path for spec slices; testUid for test slices,\n * or `${testUid}-retry${n}` so each retried attempt is its own slice. */\n key: string\n /** Present only for test-granularity slices; the base (non-retry) testUid. */\n testUid?: string\n commandStartIdx: number\n consoleStartIdx: number\n networkStartIdx: number\n mutationStartIdx: number\n traceLogStartIdx: number\n /** How many in-flight captures the adapter had recorded when this slice\n * opened — the same shape as the index fields above, over the capture list\n * rather than a data array. A flush waits for the captures AFTER this point\n * and no others: those are its own, while an earlier one belongs to a slice\n * that has already had its wait. Absent for an adapter that records no\n * boundaries or awaits its captures inline (the WDIO service), where the\n * wait is a no-op either way. */\n captureStartIdx?: number\n}\n\n/**\n * The slice range to eager-flush for a just-ended test. When the caller knows\n * the test's uid (e.g. WDIO's `afterTest`), reverse-scan for it — retries push\n * a new range under the same uid, and the next test's boundary may already be\n * recorded, so the last range isn't reliably this test's. When the uid isn't\n * independently known (Nightwatch/Selenium discover it from the range itself),\n * the last recorded range is the just-ended test's. Undefined when there is no\n * range to flush. One helper for what the three adapters each open-coded.\n */\nexport function findFlushableRange(\n ranges: readonly SpecRange[],\n testUid?: string\n): SpecRange | undefined {\n if (testUid !== undefined) {\n for (let i = ranges.length - 1; i >= 0; i--) {\n if (ranges[i]!.testUid === testUid) {\n return ranges[i]\n }\n }\n return undefined\n }\n return ranges[ranges.length - 1]\n}\n\n// ─── Spec name sanitization ───────────────────────────────────────────────────\n\n/**\n * Sanitize a spec file path into a directory-safe identifier.\n * Strips directory, extension, and unsafe characters.\n * Falls back to `'unknown-spec'` when the result is empty.\n */\nexport function sanitizeSpecName(specFile: string): string {\n const cleaned = trimChar(\n specFile\n .replace(/^.*[/\\\\]/, '')\n .replace(/\\.[^.]+$/, '')\n .replace(/[^a-zA-Z0-9_-]/g, '_'),\n '_'\n )\n return cleaned || 'unknown-spec'\n}\n\n// ─── Spec session ID ──────────────────────────────────────────────────────────\n\n/**\n * Build a collision-safe spec-level session ID from a spec file path and\n * the parent session ID. Includes an 8-char base36 hash of the full spec\n * path so two specs with the same basename in different directories don't\n * collide.\n */\nexport function buildSpecSessionId(\n specFile: string,\n sessionId: string\n): string {\n const base = sanitizeSpecName(specFile)\n const hash = deterministicUid(specFile).split('-').pop()!.slice(0, 8)\n return `${base}-${hash}-${sessionId.slice(0, 8)}`\n}\n\n// ─── Test slice session ID ──────────────────────────────────────────────────\n\n/**\n * Build a collision-safe test-level session ID from the slice's spec file, its\n * identity `key` (testUid, or `${testUid}-retry${n}` for retries), and the\n * parent session ID. Hashing the key keeps retries and sibling tests in the\n * same spec from colliding on filename, while the spec basename keeps the name\n * human-readable.\n */\nexport function buildTestSliceSessionId(\n specFile: string,\n key: string,\n sessionId: string\n): string {\n const base = sanitizeSpecName(specFile)\n const hash = deterministicUid(key).split('-').pop()!.slice(0, 8)\n return `${base}-${hash}-${sessionId.slice(0, 8)}`\n}\n\n// ─── Test slice output folder ────────────────────────────────────────────────\n\n/** Max slug length so a title doesn't blow past filesystem path limits. */\nconst MAX_SLUG_LENGTH = 60\n\n/** Lowercase, collapse runs of non-alphanumerics to `-`, trim edge dashes,\n * and cap length (trimming a dash the cut may leave behind). */\nfunction slugify(text: string): string {\n const collapsed = trimChar(\n text.toLowerCase().replace(/[^a-z0-9]+/g, '-'),\n '-'\n )\n // Trim again after the length cap in case it sliced mid-dash-run.\n return trimChar(collapsed.slice(0, MAX_SLUG_LENGTH), '-')\n}\n\n/**\n * Build the per-test output folder name: `<spec>-<title>-<browser>[-retryN]`.\n * The spec basename is sanitized via {@link sanitizeSpecName}; title and browser\n * are slugified. An empty title falls back to a short hash of `key`, and a\n * `${uid}-retry${n}` key appends a `-retry<N>` suffix so retries don't collide.\n */\nexport function buildTestSliceFolder(\n specFile: string,\n testTitle: string | undefined,\n browser: string | undefined,\n key: string\n): string {\n const specBase = sanitizeSpecName(path.basename(specFile))\n // A short hash of the slice key disambiguates two tests that share a title\n // (Scenario Outline examples with a placeholder-free name, or duplicate `it`\n // titles) — without it their folders collide and one trace overwrites the\n // other. Mirrors buildTestSliceSessionId, which hashes the key for the id.\n const keyHash = deterministicUid(key).split('-').pop()!.slice(0, 8)\n const titleSlug = slugify(testTitle ?? '') || keyHash\n const browserSlug = slugify(browser ?? '') || 'browser'\n const retryMatch = key.match(/-retry(\\d+)$/)\n const retrySuffix = retryMatch ? `-retry${retryMatch[1]}` : ''\n return `${specBase}-${titleSlug}-${browserSlug}-${keyHash}${retrySuffix}`\n}\n\n// ─── TraceCapturer slice ─────────────────────────────────────────────────────\n\n/**\n * Build a TraceCapturer that contains only the data for one spec file,\n * sliced from the parent capturer's flat arrays using index ranges.\n * The returned `sources` is a shallow clone so mutations to the parent's\n * source map after this call don't affect the spec-level capturer.\n */\nexport function buildSpecCapturer(\n capturer: TraceCapturer,\n range: SpecRange,\n nextRange?: SpecRange\n): TraceCapturer {\n const end = nextRange\n ? {\n commands: nextRange.commandStartIdx,\n console: nextRange.consoleStartIdx,\n network: nextRange.networkStartIdx,\n mutations: nextRange.mutationStartIdx,\n traceLogs: nextRange.traceLogStartIdx\n }\n : undefined\n\n return {\n mutations: capturer.mutations.slice(range.mutationStartIdx, end?.mutations),\n traceLogs: capturer.traceLogs.slice(range.traceLogStartIdx, end?.traceLogs),\n consoleLogs: capturer.consoleLogs.slice(\n range.consoleStartIdx,\n end?.console\n ),\n networkRequests: capturer.networkRequests.slice(\n range.networkStartIdx,\n end?.network\n ),\n commandsLog: capturer.commandsLog.slice(\n range.commandStartIdx,\n end?.commands\n ),\n sources: new Map(capturer.sources),\n metadata: capturer.metadata,\n startWallTime: capturer.startWallTime\n }\n}\n\n// ─── Test metadata filtering ──────────────────────────────────────────────────\n\n/**\n * Filter a full `testUid → metadata` map to only include entries whose\n * `specFile` matches the given value. Used when flushing a per-spec trace\n * to attach only that spec's test titles as tracingGroup names.\n */\nexport function filterTestMetadataBySpec(\n allMetadata: TestMetadataMap,\n specFile: string\n): TestMetadataMap {\n const filtered: TestMetadataMap = new Map()\n for (const [uid, meta] of allMetadata) {\n if (meta.specFile === specFile) {\n filtered.set(uid, meta)\n }\n }\n return filtered\n}\n\n/**\n * Filter a full `testUid → metadata` map down to a single test's entry and its\n * step entries. The per-test analog of {@link filterTestMetadataBySpec}. The\n * steps have to come along: `buildGroupPath` names each step group from this\n * map and falls back to the raw uid when the entry is missing, so dropping them\n * renders a scenario's steps as `stable-…:step:1` instead of their Gherkin text.\n */\nexport function filterTestMetadataByUid(\n allMetadata: TestMetadataMap,\n testUid: string\n): TestMetadataMap {\n const filtered: TestMetadataMap = new Map()\n const entry = allMetadata.get(testUid)\n if (entry) {\n filtered.set(testUid, entry)\n }\n for (const [uid, meta] of allMetadata) {\n if (isStepUidOf(uid, testUid)) {\n filtered.set(uid, meta)\n }\n }\n return filtered\n}\n\n// ─── Spec boundary recording ──────────────────────────────────────────────────\n\n/**\n * Minimal context needed by `recordSliceBoundary` to detect spec-file / test\n * transitions and capture array index ranges. `flushedSpecs` holds already-\n * flushed slice keys (spec paths or test keys), shared with the finalizer.\n */\nexport interface SpecBoundaryContext {\n specRanges: SpecRange[]\n flushedSpecs: Set<string>\n capturer: {\n commandsLog: ArrayLike<unknown>\n consoleLogs: ArrayLike<unknown>\n networkRequests: ArrayLike<unknown>\n mutations: ArrayLike<unknown>\n traceLogs: ArrayLike<unknown>\n }\n /** The adapter's live in-flight capture list, stamped onto each range as\n * `captureStartIdx`. Live by reference: it grows as the run does. */\n pendingCaptures?: ArrayLike<unknown>\n}\n\n/** Push a new slice range and return the previous (unflushed) range to flush.\n * `suppressSameKey` skips recording when the incoming key matches the last\n * range's — used for spec granularity so consecutive tests in one file share\n * a slice; test granularity records every attempt (retries included). */\nfunction pushSliceRange(\n ctx: SpecBoundaryContext,\n specFile: string,\n key: string,\n testUid: string | undefined,\n suppressSameKey: boolean\n): SpecRange | null {\n const lastRange = ctx.specRanges[ctx.specRanges.length - 1]\n if (suppressSameKey && lastRange && lastRange.key === key) {\n return null\n }\n const prevRange =\n lastRange && !ctx.flushedSpecs.has(lastRange.key) ? lastRange : null\n\n ctx.specRanges.push({\n specFile,\n key,\n testUid,\n commandStartIdx: ctx.capturer.commandsLog.length,\n consoleStartIdx: ctx.capturer.consoleLogs.length,\n networkStartIdx: ctx.capturer.networkRequests.length,\n mutationStartIdx: ctx.capturer.mutations.length,\n traceLogStartIdx: ctx.capturer.traceLogs.length,\n captureStartIdx: ctx.pendingCaptures?.length ?? 0\n })\n\n return prevRange\n}\n\n/**\n * Record a trace-slice boundary. For `spec` granularity, a new slice starts\n * when the spec file changes (existing behavior). For `test` granularity, a\n * new slice starts on every recorded test — including retries: a repeated\n * `testUid` is keyed `${testUid}-retry${n}` so each attempt is its own slice.\n * Returns the previous, not-yet-flushed range so the caller can flush it, or\n * `null` when nothing needs flushing (same spec, missing testUid, or a\n * non-sliced granularity).\n */\nexport function recordSliceBoundary(\n ctx: SpecBoundaryContext,\n granularity: TraceGranularity | undefined,\n specFile: string,\n testUid?: string\n): SpecRange | null {\n if (granularity === 'spec') {\n return pushSliceRange(ctx, specFile, specFile, undefined, true)\n }\n if (granularity === 'test' && testUid !== undefined) {\n const priorAttempts = ctx.specRanges.filter(\n (r) => r.testUid === testUid\n ).length\n const key =\n priorAttempts === 0 ? testUid : `${testUid}-retry${priorAttempts}`\n return pushSliceRange(ctx, specFile, key, testUid, false)\n }\n return null\n}\n\n/**\n * Record a spec-file boundary. Thin back-compat wrapper over\n * {@link recordSliceBoundary}; behavior is unchanged for `spec` granularity\n * and returns `null` for every other granularity.\n */\nexport function recordSpecBoundary(\n ctx: SpecBoundaryContext,\n specFile: string,\n traceGranularity: TraceGranularity | undefined\n): SpecRange | null {\n return recordSliceBoundary(ctx, traceGranularity, specFile)\n}\n\n// ─── Spec trace I/O ────────────────────────────────────────────────────────────\n\n/**\n * Pre-resolved inputs for `writeSpecTrace`. The caller gathers\n * adapter-specific state (session ID, output directory, test metadata …)\n * and passes it in; this function does the framework-agnostic slicing,\n * session-ID derivation, filtering, and disk I/O.\n */\nexport interface WriteSpecTraceInput {\n range: SpecRange\n nextRange?: SpecRange\n /** Shape-compatible with `buildSpecCapturer`'s first parameter. */\n capturer: Parameters<typeof buildSpecCapturer>[0]\n actionSnapshots: ActionSnapshot[]\n /** Full session frame buffer; windowed to this slice's wall-clock span. */\n screencastFrames?: readonly ScreencastFrame[]\n sessionId: string\n outputDir: string\n format?: TraceFormat\n /** Full test-metadata map (all specs); filtered to `range.specFile` internally. */\n testMetadata: TestMetadataMap\n capabilities?: unknown\n}\n\n/** Timestamped items inside a slice's wall-clock window `[start, end)`. An\n * undefined `start` (a slice with nothing to anchor on) yields nothing; an\n * undefined `end` (the last slice) is open-ended, so a trailing assertion-wait\n * stays with its test. Used for both action snapshots and screencast frames so\n * they partition on the same reliable boundary as the sliced commands. */\nfunction sliceByWindow<T extends { timestamp: number }>(\n items: readonly T[],\n start: number | undefined,\n end: number | undefined\n): T[] {\n if (start === undefined) {\n return []\n }\n return items.filter(\n (i) => i.timestamp >= start && (end === undefined || i.timestamp < end)\n )\n}\n\n/** Slice the parent capturer/snapshots for one range and write the artifact\n * under `sliceSessionId` with the pre-filtered `testMetadata`. Shared by the\n * spec and test write paths so both slice identically. `overrides` lets the\n * test path redirect into a named folder with a fixed `trace` file stem. */\nasync function writeSliceTrace(\n input: WriteSpecTraceInput,\n sliceSessionId: string,\n testMetadata: TestMetadataMap,\n overrides: { outputDir?: string; fileStem?: string } = {}\n): Promise<string> {\n const sliceCapturer = buildSpecCapturer(\n input.capturer,\n input.range,\n input.nextRange\n )\n\n // Anchor the slice on its own commands, which buildSpecCapturer index-slices\n // reliably — parent-array index lookups desync under reloadSession and would\n // sweep an earlier test's snapshots/frames in. Snapshots and frames are then\n // windowed by that wall-clock span, and the slice is rebased to its own start\n // (start === wallTime) so a per-test trace begins at 0, not the session\n // offset. `windowEnd` is the next slice's start; open for the last. Anchor on\n // the command's `startTime` (invocation), not `timestamp` (completion), so the\n // frames captured *during* the first action — e.g. the page load of an opening\n // `url()` — fall inside this slice rather than being dropped or bleeding into\n // the previous one.\n const commandStart = (c: CommandLog | undefined): number | undefined =>\n c ? (c.startTime ?? c.timestamp) : undefined\n // A command-less slice (assertion-only test) has no command to anchor on; fall\n // back to the earliest of its own index-sliced console/network timestamps so\n // its snapshots/frames still window+rebase rather than drop to the session\n // offset. Empty of both keeps today's undefined → no-window behavior.\n // Both are epoch ms (Date.now), matching the command anchor and the frame/\n // snapshot clock — NOT NetworkRequest.startTime, which is performance.now\n // (relative ms) and would anchor the window ~54,000 years off.\n const fallbackStart = (): number | undefined => {\n const times = [\n ...sliceCapturer.consoleLogs.map((c) => c.timestamp),\n ...sliceCapturer.networkRequests.map((n) => n.timestamp)\n ]\n return times.length ? Math.min(...times) : undefined\n }\n const windowStart =\n commandStart(sliceCapturer.commandsLog[0]) ?? fallbackStart()\n const windowEnd = input.nextRange\n ? commandStart(input.capturer.commandsLog[input.nextRange.commandStartIdx])\n : undefined\n\n const sliceSnapshots = sliceByWindow(\n input.actionSnapshots,\n windowStart,\n windowEnd\n )\n const sliceFrames = input.screencastFrames\n ? sliceByWindow(input.screencastFrames, windowStart, windowEnd)\n : undefined\n\n if (windowStart !== undefined) {\n sliceCapturer.startWallTime = windowStart\n }\n\n return writeTraceZip(sliceCapturer, {\n outputDir: overrides.outputDir ?? input.outputDir,\n sessionId: sliceSessionId,\n fileStem: overrides.fileStem,\n capabilities: input.capabilities,\n actionSnapshots: sliceSnapshots.length > 0 ? sliceSnapshots : undefined,\n screencastFrames: sliceFrames?.length ? sliceFrames : undefined,\n format: input.format,\n testMetadata\n })\n}\n\n/**\n * Write a standalone trace artifact (zip or ndjson-directory) for a single\n * spec file. This is the shared I/O path — all three adapters delegate to it\n * from their own `flushSpecTrace` wrappers.\n */\nexport async function writeSpecTrace(\n input: WriteSpecTraceInput\n): Promise<string> {\n return writeSliceTrace(\n input,\n buildSpecSessionId(input.range.specFile, input.sessionId),\n filterTestMetadataBySpec(input.testMetadata, input.range.specFile)\n )\n}\n\n/**\n * Write a standalone trace artifact for a single test slice into its own\n * folder: `<outputDir>/<specBasename>-<titleSlug>-<browserSlug>[-retryN]/trace.zip`.\n * Reuses {@link WriteSpecTraceInput}; the folder is the slice's external\n * identity (title/browser/retry), while {@link buildTestSliceSessionId} names\n * the sessionId embedded inside the archive.\n */\nexport async function writeTestSliceTrace(\n input: WriteSpecTraceInput\n): Promise<string> {\n const testUid = input.range.testUid ?? input.range.key\n const title = input.testMetadata.get(testUid)?.title\n // capabilities is framework-typed unknown; read only browserName here.\n const browserName = (\n input.capabilities as { browserName?: string } | undefined\n )?.browserName\n const folder = buildTestSliceFolder(\n input.range.specFile,\n title,\n browserName,\n input.range.key\n )\n return writeSliceTrace(\n input,\n buildTestSliceSessionId(\n input.range.specFile,\n input.range.key,\n input.sessionId\n ),\n filterTestMetadataByUid(input.testMetadata, testUid),\n { outputDir: path.join(input.outputDir, folder), fileStem: 'trace' }\n )\n}\n","// Converts a captured TraceLog into a trace.zip Buffer.\n// Stays runner-agnostic so the three adapters can call this directly.\n\nimport fs from 'node:fs/promises'\nimport path from 'node:path'\nimport { TRACE_ZIP_ENTRIES } from '@wdio/devtools-shared'\nimport type {\n ActionSnapshot,\n CommandLog,\n ConsoleLog,\n DeviceInfo,\n Metadata,\n NetworkRequest,\n ScreencastFrame,\n TestMetadataMap,\n TestRunnerId,\n TraceFormat,\n TraceLog,\n TraceMutation\n} from '@wdio/devtools-shared'\nimport {\n deviceFromCapabilities,\n mapCommandToAction\n} from '@wdio/devtools-shared'\nimport {\n buildConsoleEvents,\n type ConsoleEvent,\n type StdioEvent\n} from './trace-console.js'\nimport {\n buildFilmstripEvents,\n buildSnapshotResources,\n type ScreencastFrameEvent\n} from './trace-snapshots.js'\nimport { buildDenseScreencast } from './screencast-trace.js'\nimport {\n buildImageFrameSnapshots,\n FrameSnapshotIndex,\n type FrameSnapshotEvent\n} from './trace-frame-snapshots.js'\nimport { buildActionEvents, type ActionEvent } from './trace-action-events.js'\nimport { buildSourceResources } from './trace-sources.js'\nimport { networkRequestToHar } from './trace-har.js'\nimport { buildTraceZip, type TraceZipResource } from './trace-zip-writer.js'\nimport { buildMutationsNdjson } from './trace-mutations.js'\nimport { generateTranscript } from './trace-transcript.js'\nimport { sha1Hex } from './sha1.js'\n\n// Transcript building moved to its own module; re-exported here because this is\n// the name the package barrel and downstream adapters already import.\nexport { generateTranscript }\n\nconst TRACE_VERSION = 8\nconst LIBRARY_NAME = '@wdio/devtools-core'\nconst LIBRARY_VERSION = '1.0.0'\n\n/** Response bodies above this size are not embedded in the trace. */\nconst MAX_BODY_RESOURCE_BYTES = 1024 * 1024\n/** Per-trace ceiling on total embedded response-body bytes. */\nconst MAX_TOTAL_BODY_RESOURCE_BYTES = 20 * 1024 * 1024\n\nexport interface NetworkBodyCaps {\n maxBodyBytes: number\n maxTotalBytes: number\n}\n\nexport interface NetworkBodyResources {\n resources: TraceZipResource[]\n sha1ByRequestId: Map<string, string>\n}\n\n/** Content-addressed `resources/<sha1>` entries for captured response bodies. */\nexport function buildNetworkBodyResources(\n requests: NetworkRequest[],\n caps: NetworkBodyCaps = {\n maxBodyBytes: MAX_BODY_RESOURCE_BYTES,\n maxTotalBytes: MAX_TOTAL_BODY_RESOURCE_BYTES\n }\n): NetworkBodyResources {\n const resources: TraceZipResource[] = []\n const sha1ByRequestId = new Map<string, string>()\n const stored = new Set<string>()\n let totalBytes = 0\n // Cap skips are silent by design; a warn hook would slot into these branches.\n for (const request of requests) {\n if (request.responseBody === undefined) {\n continue\n }\n const data = Buffer.from(request.responseBody, 'utf8')\n if (data.byteLength > caps.maxBodyBytes) {\n continue\n }\n const sha1 = sha1Hex(data)\n if (stored.has(sha1)) {\n sha1ByRequestId.set(request.id, sha1)\n continue\n }\n if (totalBytes + data.byteLength > caps.maxTotalBytes) {\n continue\n }\n stored.add(sha1)\n totalBytes += data.byteLength\n resources.push({ resourceName: sha1, data })\n sha1ByRequestId.set(request.id, sha1)\n }\n return { resources, sha1ByRequestId }\n}\n\ninterface ContextOptionsEvent {\n version: number\n type: 'context-options'\n origin: 'library'\n libraryName: string\n libraryVersion: string\n browserName: string\n platform: string\n wallTime: number\n monotonicTime: number\n sdkLanguage: string\n title: string\n contextId: string\n options: { viewport: { width: number; height: number } }\n /** Extension field: the runner that recorded this zip, so the player can tell\n * the user how to resolve a captured locator in their own framework. Absent\n * when the capture didn't identify itself, and in foreign zips. */\n runner?: TestRunnerId\n /** Extension field: the device this was recorded on. `browserName` is\n * normalized to `chromium` for a native session and `platform` names the\n * HOST OS, so without this the device survived only as prose in `title`.\n * Absent for a desktop capture and in foreign zips. */\n device?: DeviceInfo\n}\n\ntype TraceEvent =\n | ContextOptionsEvent\n | ActionEvent\n | ScreencastFrameEvent\n | ConsoleEvent\n | StdioEvent\n | FrameSnapshotEvent\n\nfunction shortId(sessionId?: string): string {\n return (sessionId ?? Math.random().toString(36).slice(2, 10)).slice(0, 8)\n}\n\nfunction allocateTraceIds(sessionId?: string): {\n contextId: string\n pageId: string\n} {\n const idPrefix = shortId(sessionId)\n return { contextId: `context@${idPrefix}`, pageId: `page@${idPrefix}` }\n}\n\n/**\n * A native session's `browserName` stays normalized to `chromium` because a\n * standard trace viewer keys its own behaviour off that field and knows no\n * mobile platform. That normalization is why the device needs a field of its\n * own: with `device` on the event, the name no longer has to carry the fact.\n */\nfunction resolveContextNaming(caps: Record<string, unknown> | undefined): {\n browserName: string\n title: string\n device?: DeviceInfo\n} {\n const device = deviceFromCapabilities(caps)\n if (device) {\n // Title unchanged from before the `device` field existed; it is prose for a\n // foreign viewer, and the typed field is what our own consumers read.\n return {\n browserName: 'chromium',\n title: device.name\n ? `${device.platform} — ${device.name}`\n : device.platform,\n device\n }\n }\n const browserName =\n typeof caps?.browserName === 'string' ? caps.browserName : 'chromium'\n return { browserName, title: browserName }\n}\n\nfunction buildContextOptions(\n trace: TraceLog,\n contextId: string,\n wallTime: number\n): ContextOptionsEvent {\n const caps = trace.metadata.capabilities as\n Record<string, unknown> | undefined\n const { browserName, title, device } = resolveContextNaming(caps)\n const viewport = trace.metadata.viewport ?? { width: 1280, height: 720 }\n return {\n version: TRACE_VERSION,\n type: 'context-options',\n origin: 'library',\n libraryName: LIBRARY_NAME,\n libraryVersion: LIBRARY_VERSION,\n browserName,\n platform:\n process.platform === 'darwin'\n ? 'darwin'\n : process.platform === 'win32'\n ? 'windows'\n : 'linux',\n wallTime,\n monotonicTime: 0,\n sdkLanguage: 'javascript',\n title,\n contextId,\n options: {\n viewport: { width: viewport.width, height: viewport.height }\n },\n ...(trace.metadata.runner ? { runner: trace.metadata.runner } : {}),\n // A capture states its own device; the metadata's is preferred over one\n // re-derived here, because an adapter may know the device from a source\n // its capabilities never carried.\n ...((trace.metadata.device ?? device)\n ? { device: trace.metadata.device ?? device }\n : {})\n }\n}\n\nfunction buildNetworkNdjson(\n requests: NetworkRequest[],\n wallTime: number,\n pageId: string,\n sha1ByRequestId: Map<string, string>\n): Buffer {\n if (!requests.length) {\n return Buffer.alloc(0)\n }\n const lines = requests.map((r) => {\n const entry = networkRequestToHar(r, {\n bodySha1: sha1ByRequestId.get(r.id)\n }) as unknown as Record<string, unknown>\n entry.snapshot = {\n ...(entry.snapshot as Record<string, unknown>),\n // Monotonic offset so the viewer positions bars on the timeline.\n _monotonicTime: Math.max(0, r.timestamp - wallTime),\n // Browsing context ID so the viewer associates requests with the page.\n _frameref: pageId\n }\n return JSON.stringify(entry)\n })\n return Buffer.from(lines.join('\\n'), 'utf8')\n}\n\n/**\n * Build a trace.zip buffer from the captured TraceLog.\n * Filters commands through ACTION_MAP and renames to trace vocabulary;\n * network entries become HAR resource-snapshots; per-action screenshots,\n * element JSON, and snapshot text are written under `resources/`.\n */\n/** Chronological sort key — see `compareEvents` for the tie-breaker rationale. */\nfunction eventTime(e: TraceEvent): number {\n switch (e.type) {\n case 'context-options':\n return -Infinity\n case 'before':\n return e.startTime\n case 'after':\n return e.endTime\n case 'screencast-frame':\n return e.timestamp\n case 'frame-snapshot':\n return e.snapshot.timestamp\n case 'console':\n return e.time\n case 'stdout':\n case 'stderr':\n return e.timestamp\n }\n}\n\n/** At the same timestamp T: an action's `after` ends first, then the\n * snapshot captured at the action boundary, then console output observed\n * at the boundary, then the next action's `before`. Matches the viewer's\n * expectation that the screencast frame shows the state between the\n * previous action's completion and the next one's start. */\nfunction eventOrder(e: TraceEvent): number {\n switch (e.type) {\n case 'context-options':\n return 0\n case 'after':\n return 1\n case 'screencast-frame':\n case 'frame-snapshot':\n return 2\n case 'console':\n case 'stdout':\n case 'stderr':\n return 3\n case 'before':\n return 4\n }\n}\n\nfunction compareEvents(a: TraceEvent, b: TraceEvent): number {\n const dt = eventTime(a) - eventTime(b)\n return dt !== 0 ? dt : eventOrder(a) - eventOrder(b)\n}\n\ninterface TraceBundle {\n traceNdjson: string\n networkNdjson: Buffer\n transcriptMd: string\n mutationsNdjson: Buffer\n resources: TraceZipResource[]\n}\n\nfunction buildEventStream(\n trace: TraceLog,\n ctxOptions: ContextOptionsEvent,\n pageId: string,\n wallTime: number,\n testMetadata?: TestMetadataMap,\n denseFrameEvents: ScreencastFrameEvent[] = []\n): TraceEvent[] {\n const viewport = trace.metadata.viewport ?? { width: 1280, height: 720 }\n const snapshots = trace.actionSnapshots ?? []\n const snapshotIndex = new FrameSnapshotIndex(snapshots)\n // Dense frames supersede the sparse per-action filmstrip; per-action DOM is\n // carried by the frame-snapshot events, so sparse frames would only duplicate.\n const filmstrip =\n denseFrameEvents.length > 0\n ? denseFrameEvents\n : buildFilmstripEvents(snapshots, pageId, wallTime, viewport)\n const events: TraceEvent[] = [\n ctxOptions,\n ...filmstrip,\n ...buildActionEvents(\n trace.commands,\n pageId,\n wallTime,\n testMetadata,\n snapshotIndex\n ),\n ...buildImageFrameSnapshots(\n snapshotIndex.refs(),\n pageId,\n wallTime,\n viewport\n ),\n ...buildConsoleEvents(trace.consoleLogs, pageId, wallTime)\n ]\n events.sort(compareEvents)\n return events\n}\n\nfunction buildTraceBundle(\n trace: TraceLog,\n opts: {\n sessionId?: string\n wallTimeOverride?: number\n testMetadata?: TestMetadataMap\n } = {}\n): TraceBundle {\n // wallTime anchors monotonic offsets at the first captured command so\n // subsequent actions render at positive deltas in the trace viewer.\n const firstCommandTs = trace.commands[0]?.timestamp\n const wallTime = opts.wallTimeOverride ?? firstCommandTs ?? Date.now()\n const { contextId, pageId } = allocateTraceIds(opts.sessionId)\n const ctxOptions = buildContextOptions(trace, contextId, wallTime)\n const dense = buildDenseScreencast(\n trace.screencastFrames ?? [],\n pageId,\n wallTime,\n trace.metadata.viewport ?? { width: 1280, height: 720 }\n )\n const events = buildEventStream(\n trace,\n ctxOptions,\n pageId,\n wallTime,\n opts.testMetadata,\n dense.events\n )\n const networkBodies = buildNetworkBodyResources(trace.networkRequests)\n return {\n traceNdjson: events.map((e) => JSON.stringify(e)).join('\\n') + '\\n',\n networkNdjson: buildNetworkNdjson(\n trace.networkRequests,\n wallTime,\n pageId,\n networkBodies.sha1ByRequestId\n ),\n transcriptMd: generateTranscript(\n trace.commands,\n wallTime,\n ctxOptions.title\n ),\n mutationsNdjson: buildMutationsNdjson(trace.mutations ?? []).ndjson,\n resources: [\n ...buildSnapshotResources(trace.actionSnapshots ?? [], pageId),\n ...dense.resources,\n ...buildSourceResources(trace.sources),\n ...networkBodies.resources\n ]\n }\n}\n\nexport async function exportTraceZip(\n trace: TraceLog,\n opts: {\n sessionId?: string\n wallTimeOverride?: number\n testMetadata?: TestMetadataMap\n } = {}\n): Promise<Buffer> {\n const bundle = buildTraceBundle(trace, opts)\n return buildTraceZip({\n traceNdjson: bundle.traceNdjson,\n networkNdjson: bundle.networkNdjson,\n resources: bundle.resources,\n transcriptMd: bundle.transcriptMd,\n mutationsNdjson: bundle.mutationsNdjson\n })\n}\n\nasync function exportTraceDirectory(\n trace: TraceLog,\n targetDir: string,\n opts: {\n sessionId?: string\n wallTimeOverride?: number\n testMetadata?: TestMetadataMap\n } = {}\n): Promise<void> {\n const bundle = buildTraceBundle(trace, opts)\n const entry = (name: string) => path.join(targetDir, name)\n const {\n trace: T,\n network,\n mutations,\n transcript,\n resourcesDir\n } = TRACE_ZIP_ENTRIES\n await fs.mkdir(entry(resourcesDir), { recursive: true })\n await Promise.all([\n fs.writeFile(entry(T), bundle.traceNdjson),\n fs.writeFile(entry(transcript), bundle.transcriptMd, 'utf8'),\n bundle.networkNdjson.length\n ? fs.writeFile(entry(network), bundle.networkNdjson)\n : Promise.resolve(),\n bundle.mutationsNdjson.length\n ? fs.writeFile(entry(mutations), bundle.mutationsNdjson)\n : Promise.resolve(),\n ...bundle.resources.map((r) =>\n fs.writeFile(\n path.join(targetDir, TRACE_ZIP_ENTRIES.resourcesDir, r.resourceName),\n r.data\n )\n )\n ])\n}\n\n/** Minimum capturer surface needed to assemble a TraceLog. */\nexport interface TraceCapturer {\n mutations: TraceMutation[]\n traceLogs: string[]\n consoleLogs: ConsoleLog[]\n networkRequests: NetworkRequest[]\n commandsLog: CommandLog[]\n sources: Map<string, string>\n metadata?: Metadata\n startWallTime?: number\n}\n\nexport interface WriteTraceZipOptions {\n outputDir: string\n sessionId: string\n capabilities?: unknown\n /**\n * Per-action snapshots from a Phase-3-style hook. When omitted, snapshots\n * are synthesized from CommandLog entries that carry a screenshot so the\n * viewer still renders thumbnails for adapters without an action hook.\n */\n actionSnapshots?: ActionSnapshot[]\n /** Dense screencast frames for the filmstrip. Thinned + content-addressed at\n * export time; adapters pass the slice's windowed frames (or all, session\n * scope). Omitted → no dense filmstrip (byte-stable with today's output). */\n screencastFrames?: readonly ScreencastFrame[]\n /** Output layout — `zip` (default) writes a single archive, `directory`\n * unpacks the same files into `trace-<id>/`. */\n format?: TraceFormat\n /** Test metadata keyed by testUid for Tracing.tracingGroup events. */\n testMetadata?: TestMetadataMap\n /** Base name for the artifact (zip file stem / directory name). Defaults to\n * `trace-<sessionId>`; per-test slices pass `'trace'` inside a named folder. */\n fileStem?: string\n}\n\n/**\n * Build a TraceLog from a SessionCapturer-shaped source and write the trace\n * artifact (zip file or directory). Returns the absolute path written.\n */\nexport async function writeTraceZip(\n capturer: TraceCapturer,\n opts: WriteTraceZipOptions\n): Promise<string> {\n const baseMetadata = capturer.metadata ?? ({} as Metadata)\n const actionSnapshots =\n opts.actionSnapshots ??\n synthesizeSnapshotsFromCommands(capturer.commandsLog)\n const traceLog: TraceLog = {\n mutations: capturer.mutations,\n logs: capturer.traceLogs,\n consoleLogs: capturer.consoleLogs,\n networkRequests: capturer.networkRequests,\n metadata: {\n ...baseMetadata,\n ...(opts.capabilities\n ? { capabilities: opts.capabilities as Metadata['capabilities'] }\n : {})\n },\n commands: capturer.commandsLog,\n sources: Object.fromEntries(capturer.sources),\n ...(actionSnapshots.length ? { actionSnapshots } : {}),\n ...(opts.screencastFrames?.length\n ? { screencastFrames: [...opts.screencastFrames] }\n : {})\n }\n await fs.mkdir(opts.outputDir, { recursive: true })\n const exportOpts = {\n sessionId: opts.sessionId,\n wallTimeOverride: capturer.startWallTime,\n testMetadata: opts.testMetadata\n }\n const stem = opts.fileStem ?? `trace-${opts.sessionId}`\n if (opts.format === 'ndjson-directory') {\n const dir = path.join(opts.outputDir, stem)\n await fs.mkdir(dir, { recursive: true })\n await exportTraceDirectory(traceLog, dir, exportOpts)\n return dir\n }\n const zip = await exportTraceZip(traceLog, exportOpts)\n const zipPath = path.join(opts.outputDir, `${stem}.zip`)\n await fs.writeFile(zipPath, zip)\n return zipPath\n}\n\nfunction synthesizeSnapshotsFromCommands(\n commands: CommandLog[]\n): ActionSnapshot[] {\n return commands\n .filter((c) => c.screenshot && mapCommandToAction(c.command))\n .map((c) => ({\n timestamp: c.timestamp,\n command: c.command,\n screenshot: c.screenshot\n }))\n}\n","// The trace action vocabulary: maps runner-native command names to the trace\n// class/method pair. Single source of truth for both the exporter (core, forward\n// lookup) and the reader (backend, reverse lookup) so the two cannot drift.\n\nexport interface TraceAction {\n class: string\n method: string\n}\n\n/** Whether an action is a wait: every runner's waits map onto a `waitFor*` method. */\nexport function isWaitAction(action: TraceAction | null | undefined): boolean {\n return action?.method.startsWith('waitFor') ?? false\n}\n\n/** Trace action class assertion commands map to. */\nexport const ASSERT_ACTION_CLASS = 'Assert'\n\n/** node:assert methods the core assert patcher wraps; the reader derives its\n * `Assert.<m>` → `assert.<m>` reverse entries from the same list. */\nexport const TRACKED_ASSERT_METHODS = [\n 'equal',\n 'strictEqual',\n 'deepEqual',\n 'deepStrictEqual',\n 'notEqual',\n 'notStrictEqual',\n 'notDeepEqual',\n 'notDeepStrictEqual',\n 'ok',\n 'fail',\n 'throws',\n 'doesNotThrow',\n 'rejects',\n 'doesNotReject',\n 'match',\n 'doesNotMatch'\n] as const\n\n// assert.<m> (node:assert), verify.<m> (nightwatch soft variants), and\n// expect.<m> (synthesized failing-matcher entries) all render as Assert.\n// Only FAILING expect-webdriverio matchers reach the command log today (via the\n// reporter); recording passing matchers needs a per-adapter capture change.\nconst ASSERT_COMMAND_RE = /^(?:assert|verify|expect)\\.(\\w+)$/\n\nexport function mapAssertCommand(command: string): TraceAction | null {\n const match = ASSERT_COMMAND_RE.exec(command)\n return match ? { class: ASSERT_ACTION_CLASS, method: match[1] } : null\n}\n\nexport const ACTION_MAP: Record<string, TraceAction> = {\n // WDIO browser-level\n url: { class: 'Page', method: 'navigate' },\n navigateTo: { class: 'Page', method: 'navigate' },\n back: { class: 'Page', method: 'goBack' },\n forward: { class: 'Page', method: 'goForward' },\n refresh: { class: 'Page', method: 'reload' },\n newWindow: { class: 'Page', method: 'goto' },\n // Selenium WebDriver navigation (driver.get, driver.navigate().to/back/forward/refresh)\n get: { class: 'Page', method: 'navigate' },\n to: { class: 'Page', method: 'navigate' },\n // WDIO element-level\n click: { class: 'Element', method: 'click' },\n doubleClick: { class: 'Element', method: 'dblclick' },\n setValue: { class: 'Element', method: 'fill' },\n addValue: { class: 'Element', method: 'fill' },\n selectByVisibleText: { class: 'Element', method: 'selectOption' },\n moveTo: { class: 'Element', method: 'hover' },\n scrollIntoView: { class: 'Element', method: 'scrollIntoViewIfNeeded' },\n dragAndDrop: { class: 'Element', method: 'dragTo' },\n // Selenium WebElement actions\n sendKeys: { class: 'Element', method: 'fill' },\n clear: { class: 'Element', method: 'clear' },\n submit: { class: 'Element', method: 'submit' },\n // Cross-runner\n keys: { class: 'Keyboard', method: 'press' },\n execute: { class: 'Page', method: 'evaluate' },\n executeAsync: { class: 'Page', method: 'evaluate' },\n switchToFrame: { class: 'Frame', method: 'goto' },\n touchAction: { class: 'Element', method: 'tap' },\n // WDIO element reads — surfaced so query steps appear in the timeline the way\n // locator queries do in standard trace viewers. Adapters already capture\n // these; only the export allow-list kept them out.\n getText: { class: 'Element', method: 'getText' },\n getValue: { class: 'Element', method: 'getValue' },\n getAttribute: { class: 'Element', method: 'getAttribute' },\n getProperty: { class: 'Element', method: 'getProperty' },\n getCSSProperty: { class: 'Element', method: 'getCSSProperty' },\n getTagName: { class: 'Element', method: 'getTagName' },\n getLocation: { class: 'Element', method: 'getLocation' },\n getSize: { class: 'Element', method: 'getSize' },\n isDisplayed: { class: 'Element', method: 'isDisplayed' },\n isExisting: { class: 'Element', method: 'isExisting' },\n isEnabled: { class: 'Element', method: 'isEnabled' },\n isSelected: { class: 'Element', method: 'isSelected' },\n isClickable: { class: 'Element', method: 'isClickable' },\n isFocused: { class: 'Element', method: 'isFocused' },\n // Explicit user-facing waits (not the internal polling loops behind them).\n waitForDisplayed: { class: 'Element', method: 'waitForDisplayed' },\n waitForExist: { class: 'Element', method: 'waitForExist' },\n waitForEnabled: { class: 'Element', method: 'waitForEnabled' },\n waitForClickable: { class: 'Element', method: 'waitForClickable' },\n waitUntil: { class: 'Browser', method: 'waitForFunction' },\n // Nightwatch's wait family, normalized onto the WDIO names above. These are\n // written by the user and are the commands a Nightwatch test most often fails\n // on, so leaving them out of the allow-list meant the failing step produced no\n // row at all — and with no row, its error had nowhere to attach and neither the\n // action list nor the Errors tab showed the failure.\n waitForElementVisible: { class: 'Element', method: 'waitForDisplayed' },\n waitForElementNotVisible: { class: 'Element', method: 'waitForHidden' },\n waitForElementPresent: { class: 'Element', method: 'waitForExist' },\n waitForElementNotPresent: { class: 'Element', method: 'waitForNotExist' },\n // A Selenium wait on an element locator (`until.elementLocated`, `presence_of_element_located`), named by its adapter.\n waitForElementLocated: { class: 'Element', method: 'waitForExist' },\n // WDIO page/browser reads\n getTitle: { class: 'Page', method: 'getTitle' },\n getUrl: { class: 'Page', method: 'getUrl' },\n getPageSource: { class: 'Page', method: 'getPageSource' },\n // Selenium read aliases — normalized onto the WDIO names above so both runners\n // read identically. getText/getAttribute/getTagName/isDisplayed/isEnabled/\n // isSelected share the command name across runners and need no alias.\n getCssValue: { class: 'Element', method: 'getCSSProperty' },\n getRect: { class: 'Element', method: 'getRect' },\n getCurrentUrl: { class: 'Page', method: 'getUrl' },\n // A Selenium wait on any condition other than an element locator.\n wait: { class: 'Browser', method: 'waitForFunction' },\n // Raw W3C protocol names. The JS adapters wrap a client library and report\n // its method names, but an adapter that patches the protocol chokepoint —\n // the Python one, and any future language binding — reports what WebDriver\n // itself calls the command. Unmapped commands are dropped from a trace\n // outright (`trace-action-events.ts` skips them), so without these a Python\n // run exported 7 actions where the live dashboard had shown ~60: `get`,\n // `getCurrentUrl` and `getTitle` survived only because those three names\n // happen to be shared with the vocabulary above.\n //\n // `findElement`/`findElements` are deliberately absent, matching the JS\n // adapters: locating an element is plumbing for the action that follows, not\n // an action a reader wants a row for. So is `w3cExecuteScript`: selenium\n // implements `is_displayed()` and the expected-condition waits by running its\n // own JS atoms through it, so mapping it filled the trace with\n // `Page.evaluate(\"/* isDisplayed */ …\")` rows the test never wrote. The JS\n // Selenium adapter's `executeScript` is unmapped for the same reason, and a\n // user's own `execute_script` is the cost both pay.\n clickElement: { class: 'Element', method: 'click' },\n sendKeysToElement: { class: 'Element', method: 'fill' },\n clearElement: { class: 'Element', method: 'clear' },\n getElementText: { class: 'Element', method: 'getText' },\n getElementAttribute: { class: 'Element', method: 'getAttribute' },\n getElementProperty: { class: 'Element', method: 'getProperty' },\n getElementValueOfCssProperty: { class: 'Element', method: 'getCSSProperty' },\n getElementTagName: { class: 'Element', method: 'getTagName' },\n getElementRect: { class: 'Element', method: 'getRect' },\n isElementEnabled: { class: 'Element', method: 'isEnabled' },\n isElementSelected: { class: 'Element', method: 'isSelected' },\n goBack: { class: 'Page', method: 'goBack' },\n goForward: { class: 'Page', method: 'goForward' },\n switchToWindow: { class: 'Page', method: 'switchToWindow' },\n switchToParentFrame: { class: 'Frame', method: 'goto' }\n}\n\n/** Trace methods (ACTION_MAP values) that act at a point on the page — the\n * exporter emits a hit `point` for these so the player can mark where the\n * action landed. `fill` is included (typing focuses the element first). */\nexport const POINTABLE_METHODS: ReadonlySet<string> = new Set([\n 'click',\n 'dblclick',\n 'hover',\n 'tap',\n 'dragTo',\n 'scrollIntoViewIfNeeded',\n 'selectOption',\n 'fill'\n])\n\n/** Runner command names (native or trace method) that type into an element —\n * drives the timeline's keyboard glyph vs the pointer glyph. */\nconst KEYBOARD_COMMANDS: ReadonlySet<string> = new Set([\n 'setValue',\n 'sendKeys',\n 'addValue',\n 'clearValue',\n 'clear',\n 'keys',\n 'fill',\n 'press'\n])\n\nexport function isKeyboardCommand(command: string): boolean {\n return KEYBOARD_COMMANDS.has(command)\n}\n\n/** Runner-native pointer commands the trace vocabulary deliberately doesn't\n * name — Nightwatch's low-level mouse API has no meaningful trace method, so\n * adding it to ACTION_MAP would put noise in the timeline. Listed here because\n * they still dispatch, and a command that dispatches unrecognized keeps the\n * screenshot poll running straight through it. */\nconst UNMAPPED_POINTER_COMMANDS: ReadonlySet<string> = new Set([\n 'moveToElement',\n 'mouseButtonClick',\n 'mouseButtonDown',\n 'mouseButtonUp',\n 'rightClick',\n 'submitForm'\n])\n\n/**\n * Whether a runner command makes the driver dispatch trusted input into the\n * page. Derived from the vocabulary above rather than declared as a second list:\n * a command qualifies when it types (`isKeyboardCommand`) or when its trace\n * method acts at a point (`POINTABLE_METHODS`). A command wrongly classified as\n * non-dispatching is the costly direction — it leaves the screencast poll racing\n * the dispatch — so the unmapped pointer commands above are named explicitly.\n */\nexport function isInputDispatchingCommand(command: string): boolean {\n if (isKeyboardCommand(command) || UNMAPPED_POINTER_COMMANDS.has(command)) {\n return true\n }\n const action = ACTION_MAP[command]\n return action !== undefined && POINTABLE_METHODS.has(action.method)\n}\n","// Helpers over the trace action vocabulary, beside the ACTION_MAP they read —\n// here rather than in `core` because both the capture side and the trace\n// transforms need them, and the transforms must not depend on adapter logic. Ported from Vince Graics' PR #209 (`@wdio/tracing-service`); the\n// existing devtools UI uses its own denylist (`INTERNAL_COMMANDS`) — this map\n// is for the trace.zip exporter to filter + rename in one step.\n\nimport {\n ACTION_MAP,\n ASSERT_ACTION_CLASS,\n isWaitAction,\n mapAssertCommand,\n type TraceAction\n} from './trace-actions.js'\n\nexport type { TraceAction }\nexport { ASSERT_ACTION_CLASS, mapAssertCommand }\n\n// Excluded by design:\n// clearValue — WDIO fires it inside setValue (duplicate events). `addValue`\n// was excluded for the same reason and is now mapped: the nested call never\n// reaches the command log (the service logs only a command matching the top\n// of its stack), and a DIRECT addValue — idiomatic on Appium — was\n// producing no action row at all.\n// executeScript — Selenium's `until` polling fires it ~50ms; also recurses\n// because @wdio/elements uses executeScript inside captureActionSnapshot.\n// WDIO's user-facing `execute`/`executeAsync` are still captured.\n// $ / $$ / findElement(s) / getElement(s) — locator resolution fires on every\n// element access; high-frequency internal machinery, not a timeline step.\n// Passing expect-webdriverio matchers — never reach the command log (only\n// failures do, via the reporter); surfacing them is a per-adapter change.\n\nexport function mapCommandToAction(command: string): TraceAction | null {\n return ACTION_MAP[command] ?? mapAssertCommand(command)\n}\n\n/** Whether a runner command is a wait the trace renders as one row. */\nexport function isWaitCommand(command: string): boolean {\n return isWaitAction(mapCommandToAction(command))\n}\n\nconst ASSERT_TITLE_VALUE_MAX = 40\n\nfunction formatAssertValue(value: unknown): string {\n let text: string\n if (typeof value === 'object' && value !== null) {\n try {\n text = JSON.stringify(value)\n } catch {\n text = String(value)\n }\n } else {\n text = typeof value === 'string' ? JSON.stringify(value) : String(value)\n }\n return text.length > ASSERT_TITLE_VALUE_MAX\n ? `${text.slice(0, ASSERT_TITLE_VALUE_MAX - 1)}…`\n : text\n}\n\n// The label mirrors the CALL the user wrote — its positional args\n// (`assert.equal(a, b)`, `titleContains('x')`). The normalized actual/expected\n// params drive the result diff, NOT the label: a single-arg assert like\n// titleContains passes only the expected, so labelling it with the derived\n// actual would misrepresent the call. Fall back to actual/expected only when\n// no args survived (reader round-trips that dropped them).\nfunction formatAssertTitle(\n action: TraceAction,\n args: unknown[],\n params?: Record<string, unknown>,\n command?: string\n): string {\n const values =\n args.length > 0 ? args.slice(0, 2) : [params?.actual, params?.expected]\n const label = values\n .filter((value) => value !== undefined)\n .map(formatAssertValue)\n .join(', ')\n return `${command ?? `assert.${action.method}`}(${label})`\n}\n\nexport function formatActionTitle(\n action: TraceAction,\n args: unknown[],\n params?: Record<string, unknown>,\n command?: string\n): string {\n if (action.class === ASSERT_ACTION_CLASS) {\n return formatAssertTitle(action, args, params, command)\n }\n const firstArg = args[0] ?? params?.selector\n if (firstArg === undefined) {\n return `${action.class}.${action.method}()`\n }\n const label =\n typeof firstArg === 'object' ? JSON.stringify(firstArg) : String(firstArg)\n return `${action.class}.${action.method}(\"${label}\")`\n}\n\n/**\n * Methods where the first positional argument should render as value= in the\n * transcript line (e.g. setValue, selectByVisibleText).\n */\nexport const FILL_METHODS = new Set(['fill', 'selectOption'])\n","/** The browser's own pages, which no server answers for: start, error and\n * new-tab pages, extensions, devtools. Chrome's family is matched by prefix. */\nexport const BROWSER_INTERNAL_SCHEMES: ReadonlySet<string> = new Set([\n 'about',\n 'chrome',\n 'chrome-error',\n 'chrome-extension',\n 'chrome-native',\n 'chrome-search',\n 'chrome-untrusted',\n 'devtools',\n 'edge',\n 'moz-extension',\n 'safari-web-extension'\n])\n\nconst SCHEME = /^([a-z][a-z0-9+.-]*):/\n\n/** The scheme a URL names, lower-cased; a relative URL names none. */\nexport function schemeOf(url: string): string | undefined {\n return SCHEME.exec(url.toLowerCase())?.[1]\n}\n\nexport function isBrowserInternalUrl(url: string): boolean {\n const scheme = schemeOf(url)\n return (\n scheme !== undefined &&\n (BROWSER_INTERNAL_SCHEMES.has(scheme) || scheme.startsWith('chrome-'))\n )\n}\n","import type { ConsoleLog, LogLevel, LogSource } from './types.js'\n\n/**\n * Console methods we intercept to forward test/runner-process output into the\n * UI Console tab.\n */\nexport const CONSOLE_METHODS = ['log', 'info', 'warn', 'error'] as const\n\n/**\n * Strips ANSI escape sequences (colour codes, cursor moves, etc.) from\n * terminal output so the UI Console renders plain text. The pattern accepts\n * any trailing letter, not just `m`, so cursor/style sequences are handled\n * too.\n */\nexport const ANSI_REGEX = /\\x1b\\[[?]?[0-9;]*[A-Za-z]/g\n\nexport function stripAnsi(text: string): string {\n return text.replace(ANSI_REGEX, '')\n}\n\n/**\n * Log-level detection patterns, applied in priority order (highest to\n * lowest). The first matching pattern wins.\n */\nexport const LOG_LEVEL_PATTERNS: ReadonlyArray<{\n level: 'trace' | 'debug' | 'info' | 'warn' | 'error'\n pattern: RegExp\n}> = [\n { level: 'trace', pattern: /\\btrace\\b/i },\n { level: 'debug', pattern: /\\bdebug\\b/i },\n { level: 'info', pattern: /\\binfo\\b/i },\n { level: 'warn', pattern: /\\bwarn(ing)?\\b/i },\n { level: 'error', pattern: /\\berror\\b/i }\n] as const\n\n/** Visual indicators that suggest error-level logs in unstructured output. */\nexport const ERROR_INDICATORS = ['✗', 'failed', 'failure'] as const\n\n/**\n * Matches the leading Braille spinner glyphs that runners (Nightwatch CLI,\n * Selenium tooling) emit for in-place progress updates. Adapters skip lines\n * that match this so the dashboard's Console tab isn't flooded with frames.\n */\nexport const SPINNER_RE = /^[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]/u\n\n/**\n * Filter out terminal/stream lines that would feed back into the WS bridge\n * and cause an infinite forwarding loop: pino JSON output, [SESSION] markers,\n * backend logger lines, Jest console framing, and bare stack-frame lines.\n *\n * Adapters call this from their stream-patch before forwarding lines to the\n * UI Console tab. Combine with SPINNER_RE for full noise filtering.\n */\nexport function isInternalStreamLine(line: string): boolean {\n const t = line.trim()\n if (t.startsWith('{\"') || t.startsWith('[SESSION]')) {\n return true\n }\n if (t.includes('@wdio/devtools-backend')) {\n return true\n }\n if (/^console\\.(log|info|warn|error|debug|trace)$/.test(t)) {\n return true\n }\n if (/^at\\s.+:\\d+:\\d+\\)?$/.test(t)) {\n return true\n }\n return false\n}\n\n/** Enum-style accessor for the canonical LogSource values from shared. */\nexport const LOG_SOURCES = {\n BROWSER: 'browser',\n TEST: 'test',\n TERMINAL: 'terminal'\n} as const satisfies Record<string, LogSource>\n\n/**\n * Classify a line of unstructured terminal output by scanning for log-level\n * keywords. Falls back to `'log'` when no pattern matches.\n */\nexport function detectLogLevel(text: string): LogLevel {\n const normalised = stripAnsi(text).toLowerCase()\n for (const { level, pattern } of LOG_LEVEL_PATTERNS) {\n if (pattern.test(normalised)) {\n return level\n }\n }\n if (ERROR_INDICATORS.some((i) => normalised.includes(i.toLowerCase()))) {\n return 'error'\n }\n return 'log'\n}\n\n/** Build a ConsoleLog entry tagged with the supplied source. */\nexport function createConsoleLogEntry(\n type: LogLevel,\n args: unknown[],\n source: LogSource = LOG_SOURCES.TEST\n): ConsoleLog {\n return { timestamp: Date.now(), type, args, source }\n}\n\n/**\n * Map raw Chrome browser-log entries (the shape returned by both\n * `driver.manage().logs().get('browser')` in selenium-webdriver and\n * `browser.getLog('browser')` in nightwatch) into the dashboard's typed\n * ConsoleLog shape, tagged as source='browser'. Each entry's Chrome level\n * (`SEVERE` / `WARNING` / `INFO` / `DEBUG`) is normalised through\n * {@link chromeLogLevelToLogLevel}.\n */\nexport function mapChromeBrowserLogs(\n entries: Array<{ level: unknown; message: string; timestamp: number }>\n): ConsoleLog[] {\n return entries.map((entry) => ({\n timestamp: entry.timestamp,\n type: chromeLogLevelToLogLevel(\n entry.level as string | { value?: number; name?: string }\n ),\n args: [entry.message],\n source: LOG_SOURCES.BROWSER\n }))\n}\n\n/**\n * Map a Chrome DevTools log-level string (or `{name, value}` object) to our\n * `LogLevel` union. Used by CDP/BiDi consumers that surface browser-side\n * console output through SEVERE/WARNING/INFO/DEBUG severity names.\n */\nexport function chromeLogLevelToLogLevel(\n level: string | { value?: number; name?: string }\n): LogLevel {\n const levelName = (\n typeof level === 'object' ? (level?.name ?? '') : (level ?? '')\n ).toUpperCase()\n switch (levelName) {\n case 'SEVERE':\n return 'error'\n case 'WARNING':\n return 'warn'\n case 'INFO':\n return 'info'\n case 'DEBUG':\n return 'debug'\n default:\n return 'log'\n }\n}\n","// The device a capture was recorded on. A native mobile session reports this\n// only through its capabilities, and every consumer that wants to know \"was\n// this a phone?\" reads it through `deviceFromCapabilities` rather than\n// re-deriving it from a heuristic.\n\n/** Native platforms a session can run on. */\nexport const NATIVE_PLATFORMS = ['android', 'ios'] as const\n\nexport type NativePlatform = (typeof NATIVE_PLATFORMS)[number]\n\nexport function isNativePlatform(value: unknown): value is NativePlatform {\n return NATIVE_PLATFORMS.includes(value as NativePlatform)\n}\n\n/** The device a capture was recorded on, as far as the session could tell. */\nexport interface DeviceInfo {\n platform: NativePlatform\n /** Friendly model name (`Pixel 7`, `iPhone 17`); absent when the session\n * reported only a hardware serial. */\n name?: string\n /** OS version the session reported (`18.1`, `16`). */\n version?: string\n}\n\n/**\n * Capability keys holding a device's name, in the order that reads correctly on\n * real hardware:\n *\n * - a local run puts the requested name in `appium:deviceName`\n * - a device cloud + Android reports the hardware serial as BOTH `deviceName`\n * and `udid`, and the friendly name only in `deviceModel`\n * - iOS reports a friendly `deviceName` with `udid` separate\n *\n * so `deviceModel` has to be preferred over `deviceName`, and any candidate\n * that merely repeats the serial is rejected — which is what keeps the cloud's\n * `deviceName` from winning.\n */\nconst DEVICE_NAME_KEYS = ['appium:deviceName', 'deviceModel', 'deviceName']\nconst SERIAL_KEYS = ['udid', 'appium:udid']\nconst PLATFORM_VERSION_KEYS = ['appium:platformVersion', 'platformVersion']\n\nfunction capString(\n caps: Record<string, unknown>,\n key: string\n): string | undefined {\n const value = caps[key]\n return typeof value === 'string' && value.trim() ? value : undefined\n}\n\n/** A capability read from the bag or from one level of vendor options. A device\n * cloud commonly states `platformName` and `browserName` only inside its own\n * bag (`bstack:options`), which is why WDIO's mobile detection reads there\n * too; scanning one level needs no list of vendors to keep current.\n *\n * Every caller passes MATCHED capabilities — what the session answered with —\n * so a request-shaped bag's `firstMatch` array is deliberately not scanned: the\n * server merges `alwaysMatch` with the ONE entry it chose, and reading a\n * browser out of any entry would claim one the session never got. */\nfunction deepCapString(\n caps: Record<string, unknown>,\n key: string\n): string | undefined {\n const own = capString(caps, key)\n if (own) {\n return own\n }\n for (const nested of Object.values(caps)) {\n if (nested && typeof nested === 'object' && !Array.isArray(nested)) {\n const value = capString(nested as Record<string, unknown>, key)\n if (value) {\n return value\n }\n }\n }\n return undefined\n}\n\n/** Whether the capabilities name an Appium automation. Separate from naming a\n * DEVICE: a Mac2, WinAppDriver or tvOS session has no document either, and\n * `NATIVE_PLATFORMS` deliberately excludes them because that list chooses a\n * device frame, which is a display concern. */\nfunction namesAnAutomation(caps: Record<string, unknown>): boolean {\n return Boolean(\n deepCapString(caps, 'appium:automationName') ??\n deepCapString(caps, 'automationName')\n )\n}\n\n// An array is not a bag: read as one, its entries would pass for vendor options.\nfunction isCapabilityBag(value: unknown): value is Record<string, unknown> {\n return Boolean(value) && typeof value === 'object' && !Array.isArray(value)\n}\n\nfunction firstCapString(\n caps: Record<string, unknown>,\n keys: string[],\n reject: (value: string) => boolean = () => false\n): string | undefined {\n for (const key of keys) {\n const value = deepCapString(caps, key)\n if (value && !reject(value)) {\n return value\n }\n }\n return undefined\n}\n\n/**\n * Read the device out of a session's capabilities, or undefined when the\n * session was not a native mobile one. The single reader for this fact: a\n * capture states it on the way out (`context-options.device`) and the trace\n * reader narrows it on the way back in, so no consumer has to guess.\n */\nexport function deviceFromCapabilities(\n capabilities: unknown\n): DeviceInfo | undefined {\n if (!isCapabilityBag(capabilities)) {\n return undefined\n }\n const caps = capabilities\n const platform = deepCapString(caps, 'platformName')?.toLowerCase()\n if (!isNativePlatform(platform)) {\n return undefined\n }\n const serials = SERIAL_KEYS.map((key) => deepCapString(caps, key)).filter(\n (value): value is string => value !== undefined\n )\n const name = firstCapString(caps, DEVICE_NAME_KEYS, (value) =>\n serials.includes(value)\n )\n const version = firstCapString(caps, PLATFORM_VERSION_KEYS)\n return {\n platform,\n ...(name ? { name } : {}),\n ...(version ? { version } : {})\n }\n}\n\n/**\n * Whether a session had no web document to run page script in — it drove an\n * app rather than a browser. The question every adapter has to answer before a\n * DOM drain, a page-script probe or a viewport read, since each of those is a\n * round trip that can only fail on a native session.\n *\n * A device alone does not answer it: an Appium session driving Chrome or Safari\n * runs on a phone and has a real page. So the browser it names is the\n * discriminator — mobile web must state one, a native app states none.\n *\n * Capabilities rather than a driver flag, because capabilities are the one\n * thing all four adapters have.\n *\n * A device is not required — an Appium automation is enough. Answering \"web\"\n * for a document-less session is NOT the cheap direction: the service's\n * post-action settle reads a page tag, treats the failure as a navigation, and\n * then polls a probe that can only fail for its full 8 s timeout, per action.\n * So a Mac2 or tvOS session, which `NATIVE_PLATFORMS` excludes because that\n * list chooses a device frame, has to answer true here too.\n *\n * Answers from CAPABILITIES alone, so a hybrid app switched into a webview is\n * still \"native\" here — that session does have a document, and only a runtime\n * context read knows it. `sessionHasDocument` is the context-aware answer and\n * is what a capture guard should ask. A bag this cannot read at all answers\n * false, which is the expensive direction; in practice every adapter reads\n * capabilities straight off its own session.\n */\nexport function isNativeAppSession(capabilities: unknown): boolean {\n if (!isCapabilityBag(capabilities)) {\n return false\n }\n const caps = capabilities\n if (!deviceFromCapabilities(caps) && !namesAnAutomation(caps)) {\n return false\n }\n return !deepCapString(caps, 'browserName')\n}\n\n/** Appium's name for the context a native app runs in. Every other context it\n * reports is a webview, conventionally `WEBVIEW_<something>`. */\nexport const NATIVE_APP_CONTEXT = 'NATIVE_APP'\n\n/**\n * Whether an Appium context is a webview, i.e. one that has a document.\n *\n * Anything that is not the native context counts, rather than matching\n * `WEBVIEW_` — the prefix is a convention, and a driver naming its webview\n * differently would otherwise be read as native and have its DOM capture\n * skipped. An unknown context (nothing has been switched to yet) is not a\n * webview: a hybrid session starts in the native one.\n */\nexport function isWebviewContext(context: unknown): boolean {\n return typeof context === 'string' && context.length > 0\n ? context !== NATIVE_APP_CONTEXT\n : false\n}\n\n/**\n * Whether the session has a web document to run page script in RIGHT NOW.\n *\n * The question every capture guard actually wants. `isNativeAppSession` answers\n * from the startup bag and cannot change, so a hybrid app that switches into a\n * webview kept being treated as native and its webview portion carried no DOM.\n *\n * `context` is what the adapter last observed; undefined means it has not seen\n * a context switch, which for a native session is the native context it\n * started in.\n */\nexport function sessionHasDocument(\n capabilities: unknown,\n context?: string\n): boolean {\n return !isNativeAppSession(capabilities) || isWebviewContext(context)\n}\n\n/**\n * Narrow a `device` read back off a trace's `context-options`. The field is\n * untrusted — a foreign zip may carry anything under that name, and one of ours\n * from before the field existed carries nothing — so a reader narrows here\n * rather than casting.\n */\nexport function isDeviceInfo(value: unknown): value is DeviceInfo {\n if (!value || typeof value !== 'object') {\n return false\n }\n const candidate = value as Record<string, unknown>\n return (\n isNativePlatform(candidate.platform) &&\n (candidate.name === undefined || typeof candidate.name === 'string') &&\n (candidate.version === undefined || typeof candidate.version === 'string')\n )\n}\n\n/** `iPhone 17 (ios 18.1)` — one label for a device, so every consumer that\n * shows it spells it the same way. */\nexport function deviceLabel(device: DeviceInfo): string {\n const version = device.version ? ` ${device.version}` : ''\n return device.name\n ? `${device.name} (${device.platform}${version})`\n : `${device.platform}${version}`\n}\n","// Which locator each runner is best handed: the grammar its text form is\n// written in, where that text form sits in the capture's branch order, and what\n// it needs told when the grammar is XPath. The one fact table behind both sides\n// — the page capture reads it to decide what to emit, the player reads it to\n// caption what it hands the user.\n//\n// WebdriverIO has its own `tag*=text` form and auto-detects a leading `//`;\n// Selenium and Nightwatch have no text form, and neither auto-detects XPath —\n// Selenium takes it via `By.xpath`, Nightwatch via its `'xpath'` strategy.\n\nimport type { TestRunnerId } from './types.js'\n\n/** Text-locator grammar a runner resolves natively. Only the capture's text\n * branch varies by it; every other branch is portable CSS. */\nexport type TextLocatorDialect = 'webdriverio' | 'xpath'\n\n/** Where the text branch sits among the capture's branches. `first` prefers the\n * readable text form; `fallback` reaches it only when no unique CSS locator\n * exists, still ahead of the meaningless positional path. */\nexport type TextBranchOrder = 'first' | 'fallback'\n\nexport interface LocatorDialect {\n text: TextLocatorDialect\n textBranch: TextBranchOrder\n /** The call that makes the runner treat a captured XPath locator as XPath.\n * Undefined where naming one would be wrong (WebdriverIO needs none) or a\n * guess (an unidentified recorder). */\n xpathHint?: string\n}\n\n/** An unidentified recorder gets XPath text first: it is the portable form and\n * the readable one, so it is the safe answer rather than a guess. */\nexport function locatorDialect(\n runner: TestRunnerId | undefined\n): LocatorDialect {\n switch (runner) {\n case 'mocha':\n case 'jasmine':\n case 'cucumber':\n return { text: 'webdriverio', textBranch: 'first' }\n // Every Selenium locator is wrapped in a `By.…`, so `By.xpath` asks no more\n // of the user than `By.css` — the text form stays first.\n case 'selenium-webdriver':\n return { text: 'xpath', textBranch: 'first', xpathHint: 'By.xpath()' }\n // The only runner where a bare locator string is read under a default CSS\n // strategy, so it is the only one where XPath costs the user something.\n case 'nightwatch':\n case 'nightwatch-cucumber':\n return {\n text: 'xpath',\n textBranch: 'fallback',\n xpathHint: \"useXpath() or locateStrategy: 'xpath'\"\n }\n case undefined:\n return { text: 'xpath', textBranch: 'first' }\n }\n}\n\n/** A captured locator is portable CSS, or XPath where the element's own text\n * identifies it */\nexport function isXPathLocator(locator: string): boolean {\n // No CSS selector can open with `/` or `(`, so this never claims one that\n // querySelector could have parsed. Covers `//a[…]`, `/html/…`, `./a`, `.//a`\n // and the indexed `(//a[…])[2]`.\n return /^\\(*\\.{0,2}\\//.test(locator)\n}\n\n/** Tag name an XPath locator is scoped to, for a consumer that reports the tag\n * it matched. Undefined for CSS and for a tag-less expression (`//*[…]`). */\nexport function xpathLocatorTag(locator: string): string | undefined {\n return /^\\/\\/([a-z][a-z0-9-]*)\\[/.exec(locator)?.[1]\n}\n\n/** WebdriverIO's text-locator forms — `tag*=Contains`, `tag=Exact`, tag optional. */\nconst TEXT_LOCATOR_RE = /^([a-z][a-z0-9-]*)?(\\*?)=(.+)$/i\n\n/** A text locator in either dialect, decomposed. `tag` is `*` when the form\n * names none; `partial` marks a contains-match rather than an exact one.\n * Undefined for every non-text locator, including the `concat()`-stitched\n * literal that text carrying both quote kinds produces — that one is left to\n * exact comparison rather than parsed back. */\nfunction textLocatorParts(\n locator: string\n): { tag: string; text: string; partial: boolean } | undefined {\n const xpath =\n /^\\/\\/([a-z][a-z0-9-]*|\\*)\\[contains\\(\\.,\\s*([\"'])(.*)\\2\\)\\]$/i.exec(\n locator\n )\n if (xpath) {\n return { tag: xpath[1]!.toLowerCase(), text: xpath[3]!, partial: true }\n }\n const wdio = TEXT_LOCATOR_RE.exec(locator)\n if (!wdio) {\n return undefined\n }\n return {\n tag: wdio[1]?.toLowerCase() ?? '*',\n text: wdio[3]!,\n partial: wdio[2] === '*'\n }\n}\n\n/** Whether a locator the page capture generated and one a test wrote denote the\n * same element. Both sides are decomposed from either dialect, because which of\n * the two the capture emits depends on the runner while what a test writes does\n * not — a `===` drops the input point whenever they differ. */\nexport function locatorsMatch(captured: string, written: string): boolean {\n if (captured === written) {\n return true\n }\n const target = textLocatorParts(captured)\n const test = textLocatorParts(written)\n if (!target || !test) {\n return false\n }\n if (target.tag !== '*' && test.tag !== '*' && target.tag !== test.tag) {\n return false\n }\n // The capture stores the element's whole trimmed text, so a contains-match has\n // to be a substring of it while an exact one names it in full.\n return test.partial\n ? target.text.includes(test.text)\n : target.text === test.text\n}\n","/**\n * Browser-injectable script strings for element extraction.\n *\n * Each function returns a self-contained JavaScript string designed to run\n * inside a browser page via `browser.execute(script)`. The scripts have no\n * external dependencies and must be ES5-compatible.\n *\n * WDIO-dependent wrappers that call `browser.execute(script)` live in\n * `@wdio/elements` — these are just the script bodies.\n */\n\nimport { locatorDialect } from './locator-dialect.js'\nimport type { LocatorDialect, TextLocatorDialect } from './locator-dialect.js'\nimport type { TestRunnerId } from './types.js'\n\n/**\n * HTTP contract for the page-side element scripts.\n *\n * Same reasoning as `COLLECTOR_API`: these are browser-injectable source\n * strings, and an adapter that cannot import this package — the Python one —\n * has no other way to reach them. Serving them keeps the version matched by\n * construction instead of ported per language, which is what left a Python\n * trace with no A11y tree at all.\n *\n * Generated rather than static, because the scripts bake in the runner's\n * locator dialect: a WDIO run wants `a*=Logout`, a protocol-level one wants\n * XPath, and the caller cannot patch that into a served string afterwards.\n */\nexport const ELEMENT_SCRIPTS_API = {\n get: '/api/element-scripts'\n} as const\n\n/** `Content-Type` for {@link ELEMENT_SCRIPTS_API}. A JSON envelope rather than\n * raw source, because there are two scripts and a caller needs both. */\nexport const ELEMENT_SCRIPTS_CONTENT_TYPE = 'application/json; charset=utf-8'\n\n/** Body of {@link ELEMENT_SCRIPTS_API} — one self-contained expression per\n * script, both built by {@link buildElementScripts}. */\nexport interface ElementScriptsResponse {\n accessibilityTree: string\n elements: string\n}\n\n/** Fields a captured element record is read for: `trace-action-events` matches\n * on `selector` and draws `after.point` from `boundingBox`, and the rest is\n * context for a reader. `value` and `href` are deliberately absent — nothing\n * in the repo reads either off a captured record, and a trace zip is a\n * portable artifact, so shipping an unread OTP or signed url in every\n * `*-elements.json` is cost with no consumer. `@wdio/elements` still returns\n * the full `BrowserElementInfo` from its own live call. */\nconst CAPTURED_ELEMENT_FIELDS = [\n 'tagName',\n 'name',\n 'type',\n 'selector',\n 'isInViewport',\n 'boundingBox'\n] as const\n\n/** The pair every caller wants: both scripts in the form an action snapshot\n * reads them, so the route and the in-process adapters cannot drift apart on\n * which arguments that is. */\nexport function buildElementScripts(\n runner?: TestRunnerId\n): ElementScriptsResponse {\n const projection = CAPTURED_ELEMENT_FIELDS.map(\n (field) => `${field}: e.${field}`\n ).join(', ')\n return {\n accessibilityTree: accessibilityTreeScript(true, runner),\n // `.map` rather than a flag on elementsScript: the projection is a property\n // of what a TRACE keeps, not of how the page is walked.\n elements: `(${elementsScript(true, true, runner)}).map(function (e) { return { ${projection} } })`\n }\n}\n\n/** Shared by both injected scripts below — the same visibility gate decides\n * which elements each one reports. */\nconst IS_VISIBLE_SCRIPT = `\n function isVisible(el) {\n if (typeof el.checkVisibility === 'function') {\n return el.checkVisibility({ opacityProperty: true, visibilityProperty: true, contentVisibilityAuto: true })\n }\n var style = window.getComputedStyle(el)\n return style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0' && el.offsetWidth > 0 && el.offsetHeight > 0\n }\n`\n\n/** XPath 1.0 has no string escape, so a value carrying both quote kinds is\n * stitched from single-kind literals; a literal double quote can only enter the\n * expression as its own single-quoted token. */\nconst XPATH_TEXT_LITERAL_SCRIPT = `\n function xpathTextLiteral(value) {\n if (value.indexOf('\"') === -1) { return '\"' + value + '\"' }\n if (value.indexOf(\"'\") === -1) { return \"'\" + value + \"'\" }\n var quoteToken = \"'\" + '\"' + \"'\"\n var parts = value.split('\"')\n var pieces = []\n for (var p = 0; p < parts.length; p++) {\n if (parts[p]) { pieces.push('\"' + parts[p] + '\"') }\n if (p < parts.length - 1) { pieces.push(quoteToken) }\n }\n // concat() takes at least two arguments — a value that is nothing but\n // double quotes yields one piece and needs no concat.\n return pieces.length > 1 ? 'concat(' + pieces.join(', ') + ')' : pieces[0]\n }\n`\n\n/** The meaning-bearing CSS branches: portable across all runners, so they are\n * dialect-independent. Null when none of them identifies the element uniquely,\n * which is what lets a caller order them against the text branch. */\nconst SEMANTIC_CSS_SELECTOR_SCRIPT = `\n function semanticCssSelector(element, tag) {\n var ariaLabel = element.getAttribute('aria-label')\n if (ariaLabel && ariaLabel.length <= 200) {\n var sel = '[aria-label=\"' + CSS.escape(ariaLabel) + '\"]'\n if (document.querySelectorAll(sel).length === 1) { return sel }\n }\n var testId = element.getAttribute('data-testid')\n if (testId) {\n var testSel = '[data-testid=\"' + CSS.escape(testId) + '\"]'\n if (document.querySelectorAll(testSel).length === 1) { return testSel }\n }\n if (element.id) {\n var idSel = '#' + CSS.escape(element.id)\n if (document.querySelectorAll(idSel).length === 1) { return idSel }\n }\n var nameAttr = element.getAttribute('name')\n if (nameAttr) {\n var nameSel = tag + '[name=\"' + CSS.escape(nameAttr) + '\"]'\n if (document.querySelectorAll(nameSel).length === 1) { return nameSel }\n }\n var typeAttr = element.getAttribute('type')\n if (typeAttr) {\n var typeSel = tag + '[type=\"' + CSS.escape(typeAttr) + '\"]'\n if (document.querySelectorAll(typeSel).length === 1) { return typeSel }\n }\n if (element.className && typeof element.className === 'string') {\n var classes = element.className.trim().split(/\\\\s+/).filter(Boolean)\n for (var i = 0; i < classes.length; i++) {\n var clsSel = tag + '.' + CSS.escape(classes[i])\n if (document.querySelectorAll(clsSel).length === 1) { return clsSel }\n }\n if (classes.length >= 2) {\n var twoClsSel = tag + classes.slice(0, 2).map(function(c) { return '.' + CSS.escape(c) }).join('')\n if (document.querySelectorAll(twoClsSel).length === 1) { return twoClsSel }\n }\n }\n return null\n }\n`\n\n/** Last resort: a positional `:nth-of-type` path, which always resolves but\n * carries no meaning — hence every other branch getting first refusal. */\nconst POSITIONAL_SELECTOR_SCRIPT = `\n function positionalSelector(element) {\n var current = element\n var path = []\n while (current && current !== document.documentElement) {\n var seg = current.tagName.toLowerCase()\n if (current.id) { path.unshift('#' + CSS.escape(current.id)); break }\n var parent = current.parentElement\n if (parent) {\n var siblings = Array.from(parent.children).filter(function(c) { return c.tagName === current.tagName })\n if (siblings.length > 1) { seg += ':nth-of-type(' + (siblings.indexOf(current) + 1) + ')' }\n }\n path.unshift(seg)\n current = current.parentElement\n if (path.length >= 4) { break }\n }\n return path.join(' > ')\n }\n`\n\n/** The text branch's return expression. WebdriverIO's `tag*=text` compiles\n * internally to XPath with `\"` quoting, so a text carrying a double quote would\n * yield a broken expression — those keep the XPath form, which it also resolves. */\nfunction textLocatorExpression(dialect: TextLocatorDialect): string {\n const xpath = \"'//' + tag + '[contains(., ' + xpathTextLiteral(text) + ')]'\"\n return dialect === 'webdriverio'\n ? `text.indexOf('\"') === -1 ? tag + '*=' + text : ${xpath}`\n : xpath\n}\n\n/** Identify the element by its own text, in `dialect`'s grammar. Null when the\n * text neither exists nor singles it out, so it composes with the CSS branches\n * in either order. */\nfunction textSelectorScript(dialect: TextLocatorDialect): string {\n return `\n function textSelector(element, tag) {\n var text = (element.textContent || '').trim().replace(/\\\\s+/g, ' ')\n if (!text || text.length > 120) { return null }\n var sameTagElements = document.querySelectorAll(tag)\n var matchCount = 0\n sameTagElements.forEach(function(el) { if (el.textContent.includes(text)) { matchCount++ } })\n // The DOM predicate this counts is exactly XPath's\n // \\`//tag[contains(., text)]\\`, so a single match here is a unique match\n // there — the emitted expression carries the uniqueness just checked.\n if (matchCount !== 1) { return null }\n return ${textLocatorExpression(dialect)}\n }\n`\n}\n\n/** Shared by both injected scripts below, so one grammar produces the locator in\n * `-snapshot.txt` and `-elements.json`. The dialect decides both what the text\n * branch emits and where it sits; the positional path stays last either way. */\nfunction getSelectorScript(dialect: LocatorDialect): string {\n const preferred =\n dialect.textBranch === 'first'\n ? ['textSelector(element, tag)', 'semanticCssSelector(element, tag)']\n : ['semanticCssSelector(element, tag)', 'textSelector(element, tag)']\n return `\n ${XPATH_TEXT_LITERAL_SCRIPT}\n ${SEMANTIC_CSS_SELECTOR_SCRIPT}\n ${POSITIONAL_SELECTOR_SCRIPT}\n ${textSelectorScript(dialect.text)}\n\n function getSelector(element) {\n var tag = element.tagName.toLowerCase()\n return ${preferred[0]} || ${preferred[1]} || positionalSelector(element)\n }\n`\n}\n\n/**\n * Accessibility tree walk — returns a flat array of AccessibilityNode.\n *\n * Walks the DOM from `document.body`, assigning semantic roles (button, link,\n * textbox, heading, img, statictext, …) based on tag name, ARIA attributes,\n * and visibility. Each node carries a unique locator, in `runner`'s own text\n * dialect — omit it for the portable XPath form every runner resolves.\n */\nexport function accessibilityTreeScript(\n inViewportOnly: boolean,\n runner?: TestRunnerId\n): string {\n return `(function () {\n var INPUT_TYPE_ROLES = {\n text: 'textbox', search: 'searchbox', email: 'textbox', url: 'textbox',\n tel: 'textbox', password: 'textbox', number: 'spinbutton',\n checkbox: 'checkbox', radio: 'radio', range: 'slider',\n submit: 'button', reset: 'button', image: 'button', file: 'button', color: 'button'\n }\n\n var CONTAINER_ROLES = new Set([\n 'navigation', 'banner', 'contentinfo', 'complementary', 'main',\n 'form', 'region', 'group', 'list', 'listitem', 'table', 'row', 'rowgroup', 'generic'\n ])\n\n function getRole(el) {\n var explicit = el.getAttribute('role')\n if (explicit) { return explicit.split(' ')[0] }\n var tag = el.tagName.toLowerCase()\n switch (tag) {\n case 'button': return 'button'\n case 'a': return el.hasAttribute('href') ? 'link' : null\n case 'input': {\n var type = (el.getAttribute('type') || 'text').toLowerCase()\n if (type === 'hidden') { return null }\n return INPUT_TYPE_ROLES[type] || 'textbox'\n }\n case 'select': return 'combobox'\n case 'textarea': return 'textbox'\n case 'h1': case 'h2': case 'h3': case 'h4': case 'h5': case 'h6': return 'heading'\n case 'img': return 'img'\n case 'nav': return 'navigation'\n case 'main': return 'main'\n case 'header': return !el.closest('article,aside,main,nav,section') ? 'banner' : null\n case 'footer': return !el.closest('article,aside,main,nav,section') ? 'contentinfo' : null\n case 'aside': return 'complementary'\n case 'dialog': return 'dialog'\n case 'form': return 'form'\n case 'section': return el.hasAttribute('aria-label') || el.hasAttribute('aria-labelledby') ? 'region' : null\n case 'summary': return 'button'\n case 'details': return 'group'\n case 'progress': return 'progressbar'\n case 'meter': return 'meter'\n case 'ul': case 'ol': return 'list'\n case 'li': return 'listitem'\n case 'table': return 'table'\n }\n if (el.contentEditable === 'true') { return 'textbox' }\n if (el.hasAttribute('tabindex') && parseInt(el.getAttribute('tabindex') || '-1', 10) >= 0) { return 'generic' }\n if (getDirectText(el)) { return 'statictext' }\n return null\n }\n\n function getAccessibleName(el, role) {\n var ariaLabel = el.getAttribute('aria-label')\n if (ariaLabel) { return ariaLabel.trim() }\n var labelledBy = el.getAttribute('aria-labelledby')\n if (labelledBy) {\n var texts = labelledBy.split(/\\\\s+/).map(function(id) { return (document.getElementById(id)?.textContent || '').trim() }).filter(Boolean)\n if (texts.length > 0) { return texts.join(' ').slice(0, 200) }\n }\n var tag = el.tagName.toLowerCase()\n if (tag === 'img' || (tag === 'input' && el.getAttribute('type') === 'image')) {\n var alt = el.getAttribute('alt')\n if (alt !== null) { return alt.trim() }\n }\n if (['input', 'select', 'textarea'].indexOf(tag) !== -1) {\n var id = el.getAttribute('id')\n if (id) {\n var label = document.querySelector('label[for=\"' + CSS.escape(id) + '\"]')\n if (label) { return (label.textContent || '').trim() }\n }\n var parentLabel = el.closest('label')\n if (parentLabel) {\n var clone = parentLabel.cloneNode(true)\n clone.querySelectorAll('input,select,textarea').forEach(function(n) { n.remove() })\n var lt = (clone.textContent || '').trim()\n if (lt) { return lt }\n }\n }\n var ph = el.getAttribute('placeholder')\n if (ph) { return ph.trim() }\n var title = el.getAttribute('title')\n if (title) { return title.trim() }\n var childImg = el.querySelector('img')\n if (childImg) {\n var imgAlt = childImg.getAttribute('alt')\n if (imgAlt) { return imgAlt.trim() }\n }\n if (role && CONTAINER_ROLES.has(role)) { return '' }\n return ((el.textContent || '').trim().replace(/\\\\s+/g, ' ') || '').slice(0, 200)\n }\n\n ${getSelectorScript(locatorDialect(runner))}\n\n function getDirectText(el) {\n var text = ''\n for (var i = 0; i < el.childNodes.length; i++) {\n if (el.childNodes[i].nodeType === 3) { text += el.childNodes[i].textContent }\n }\n return text.trim().replace(/\\\\s+/g, ' ')\n }\n\n ${IS_VISIBLE_SCRIPT}\n\n function isInViewport(el) {\n var rect = el.getBoundingClientRect()\n return rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth)\n }\n\n function getLevel(el) {\n var m = el.tagName.toLowerCase().match(/^h([1-6])$/)\n if (m) { return parseInt(m[1], 10) }\n var ariaLevel = el.getAttribute('aria-level')\n if (ariaLevel) { return parseInt(ariaLevel, 10) }\n return undefined\n }\n\n function getState(el) {\n var inputEl = el\n var isCheckable = ['input', 'menuitemcheckbox', 'menuitemradio'].indexOf(el.tagName.toLowerCase()) !== -1 || ['checkbox', 'radio', 'switch'].indexOf(el.getAttribute('role') || '') !== -1\n return {\n disabled: el.getAttribute('aria-disabled') === 'true' || inputEl.disabled ? 'true' : '',\n checked: isCheckable && inputEl.checked ? 'true' : el.getAttribute('aria-checked') || '',\n expanded: el.getAttribute('aria-expanded') || '',\n selected: el.getAttribute('aria-selected') || '',\n pressed: el.getAttribute('aria-pressed') || '',\n required: inputEl.required || el.getAttribute('aria-required') === 'true' ? 'true' : '',\n readonly: inputEl.readOnly || el.getAttribute('aria-readonly') === 'true' ? 'true' : ''\n }\n }\n\n var result = []\n\n function walk(el, depth) {\n if (depth > 200) { return }\n if (!isVisible(el)) { return }\n var role = getRole(el)\n var inViewport = isInViewport(el)\n if (!role) {\n for (var i = 0; i < el.children.length; i++) { walk(el.children[i], depth + 1) }\n return\n }\n if (${inViewportOnly} && !inViewport) {\n for (var i = 0; i < el.children.length; i++) { walk(el.children[i], depth + 1) }\n return\n }\n var name = getAccessibleName(el, role)\n var selector = getSelector(el)\n var node = { role: role, name: name, selector: selector, depth: depth, level: getLevel(el) ?? '', isInViewport: inViewport }\n var state = getState(el)\n for (var k in state) { node[k] = state[k] }\n result.push(node)\n for (var i = 0; i < el.children.length; i++) { walk(el.children[i], depth + 1) }\n }\n\n for (var i = 0; i < document.body.children.length; i++) { walk(document.body.children[i], 0) }\n return result\n })()`\n}\n\n/**\n * Interactable element query — returns a flat array of BrowserElementInfo.\n *\n * Uses `querySelectorAll` with a broad interactable-selector list, then\n * filters by visibility and (optionally) viewport containment. Each element\n * gets a computed accessible name and a unique locator, in `runner`'s own text\n * dialect — omit it for the portable XPath form every runner resolves.\n */\nexport function elementsScript(\n includeBounds: boolean,\n inViewportOnly: boolean,\n runner?: TestRunnerId\n): string {\n return `(function () {\n var interactableSelectors = [\n 'a[href]', 'button', 'input:not([type=\"hidden\"])', 'select', 'textarea',\n '[role=\"button\"]', '[role=\"link\"]', '[role=\"checkbox\"]', '[role=\"radio\"]',\n '[role=\"tab\"]', '[role=\"menuitem\"]', '[role=\"combobox\"]', '[role=\"option\"]',\n '[role=\"switch\"]', '[role=\"slider\"]', '[role=\"textbox\"]', '[role=\"searchbox\"]',\n '[role=\"spinbutton\"]', '[contenteditable=\"true\"]', '[tabindex]:not([tabindex=\"-1\"])'\n ].join(',')\n\n ${IS_VISIBLE_SCRIPT}\n\n function getAccessibleName(el) {\n var ariaLabel = el.getAttribute('aria-label')\n if (ariaLabel) { return ariaLabel.trim() }\n var labelledBy = el.getAttribute('aria-labelledby')\n if (labelledBy) {\n var texts = labelledBy.split(/\\\\s+/).map(function(id) { return (document.getElementById(id)?.textContent || '').trim() }).filter(Boolean)\n if (texts.length > 0) { return texts.join(' ').slice(0, 200) }\n }\n var tag = el.tagName.toLowerCase()\n if (tag === 'img' || (tag === 'input' && el.getAttribute('type') === 'image')) {\n var alt = el.getAttribute('alt')\n if (alt !== null) { return alt.trim() }\n }\n if (['input', 'select', 'textarea'].indexOf(tag) !== -1) {\n var id = el.getAttribute('id')\n if (id) {\n var label = document.querySelector('label[for=\"' + CSS.escape(id) + '\"]')\n if (label) { return (label.textContent || '').trim() }\n }\n var parentLabel = el.closest('label')\n if (parentLabel) {\n var clone = parentLabel.cloneNode(true)\n clone.querySelectorAll('input,select,textarea').forEach(function(n) { n.remove() })\n var lt = (clone.textContent || '').trim()\n if (lt) { return lt }\n }\n }\n var ph = el.getAttribute('placeholder')\n if (ph) { return ph.trim() }\n var title = el.getAttribute('title')\n if (title) { return title.trim() }\n return ((el.textContent || '').trim().replace(/\\\\s+/g, ' ') || '').slice(0, 200)\n }\n\n ${getSelectorScript(locatorDialect(runner))}\n\n var elements = []\n var seen = new Set()\n\n document.querySelectorAll(interactableSelectors).forEach(function(el) {\n if (seen.has(el)) { return }\n seen.add(el)\n var htmlEl = el\n if (!isVisible(htmlEl)) { return }\n var inputEl = htmlEl\n var rect = htmlEl.getBoundingClientRect()\n var isInVp = rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth)\n if (${inViewportOnly} && !isInVp) { return }\n var elType = htmlEl.getAttribute('type') || ''\n var entry = {\n tagName: htmlEl.tagName.toLowerCase(),\n name: getAccessibleName(htmlEl),\n type: elType,\n // A trace zip is a portable artifact, and nothing downstream reads a\n // password's value — the a11y name comes from the label, not the field.\n value: elType.toLowerCase() === 'password' ? '' : inputEl.value || '',\n href: htmlEl.getAttribute('href') || '',\n selector: getSelector(htmlEl),\n isInViewport: isInVp\n }\n ${includeBounds ? 'entry.boundingBox = { x: rect.x + window.scrollX, y: rect.y + window.scrollY, width: rect.width, height: rect.height }' : ''}\n elements.push(entry)\n })\n return elements\n })()`\n}\n","/**\n * Page-side scripts every adapter runs against the collector and the page's\n * Performance API: the readiness probe, the `<script>` injection, the drain and\n * the navigation timing read.\n *\n * Page code with no Node dependency, so it lives here rather than in core, and\n * the backend serves it for the adapter that cannot import this package. A\n * copy per language drifted: the JS performance script lacked the outer\n * `return` the Python copy had, so no JS adapter attached load timings to a\n * navigation row.\n */\n\n/**\n * HTTP contract for {@link PageScriptsResponse}.\n *\n * Its own route rather than a field of `ELEMENT_SCRIPTS_API`: those are\n * generated per runner for the A11y tab and fetched only when it is on, while\n * these are static and every run needs them for DOM replay and navigation\n * timings. One envelope would make every caller pass a runner it does not use\n * and tie the collector's lifecycle to an optional feature.\n */\nexport const PAGE_SCRIPTS_API = {\n get: '/api/page-scripts'\n} as const\n\n/** `Content-Type` for {@link PAGE_SCRIPTS_API}: a JSON envelope, since a\n * caller needs all four scripts. */\nexport const PAGE_SCRIPTS_CONTENT_TYPE = 'application/json; charset=utf-8'\n\n/** Browser-side expression that is true once the injected collector has\n * initialised, so the collector's global name lives in one place. */\nexport const COLLECTOR_READY_EXPRESSION =\n 'typeof window.wdioTraceCollector !== \"undefined\"'\n\n/** Script body answering {@link COLLECTOR_READY_EXPRESSION}. */\nexport const COLLECTOR_READY_SCRIPT = `return ${COLLECTOR_READY_EXPRESSION};`\n\n/** Appends a `<script>` whose body is `arguments[0]` (the collector wrapped for\n * a script element) and answers whether the collector came up; an appended\n * script runs synchronously, so it usually has. */\nexport const COLLECTOR_INJECT_SCRIPT = `var s=document.createElement('script');s.textContent=arguments[0];document.head.appendChild(s);${COLLECTOR_READY_SCRIPT}`\n\n/**\n * Script body that drains the collector in one round trip, atomic against a\n * navigation replacing the document mid-check.\n *\n * `forceAnchor` forces a full-DOM anchor of the CURRENT document before\n * draining. The collector's initial anchor is scheduled asynchronously (after\n * `waitForBody`), so a drain issued right after injecting on a navigation\n * destination beats it and returns nothing, and the destination's buffer then\n * dies with the page. The WDIO service drains the outgoing document without\n * forcing before a page transition, which is the one caller of `false`.\n */\nexport function collectorDrainExpression(forceAnchor = false): string {\n const call = forceAnchor\n ? 'window.wdioTraceCollector.captureCurrentDom();'\n : ''\n return `if (!(${COLLECTOR_READY_EXPRESSION})) { return null; } ${call} return window.wdioTraceCollector.getTraceData();`\n}\n\n/**\n * Captures Performance API data, cookies and document info from the page under\n * test. A string so the browser-only types (PerformanceEntry, Document) never\n * reach the Node-side type-checker. Returns the bag core's\n * `applyPerformanceData` shapes onto a command row.\n */\nexport const CAPTURE_PERFORMANCE_SCRIPT = `\n return (function() {\n const performance = window.performance;\n const navigation = performance.getEntriesByType?.('navigation')?.[0];\n const resources = performance.getEntriesByType?.('resource') || [];\n\n return {\n navigation: navigation ? {\n url: window.location.href,\n timing: {\n loadTime: navigation.loadEventEnd - navigation.fetchStart,\n domReady: navigation.domContentLoadedEventEnd - navigation.fetchStart,\n responseTime: navigation.responseEnd - navigation.requestStart,\n dnsLookup: navigation.domainLookupEnd - navigation.domainLookupStart,\n tcpConnection: navigation.connectEnd - navigation.connectStart,\n serverResponse: navigation.responseEnd - navigation.responseStart\n }\n } : undefined,\n resources: resources.map(function(resource) {\n return {\n url: resource.name,\n duration: resource.duration,\n size: resource.transferSize || 0,\n type: resource.initiatorType,\n startTime: resource.startTime,\n responseEnd: resource.responseEnd\n };\n }),\n cookies: (function() {\n try { return document.cookie; } catch (e) { return ''; }\n })(),\n documentInfo: {\n url: window.location.href,\n title: document.title,\n headers: {\n userAgent: navigator.userAgent,\n language: navigator.language,\n platform: navigator.platform\n },\n documentInfo: {\n readyState: document.readyState,\n referrer: document.referrer,\n characterSet: document.characterSet\n }\n }\n };\n })()\n`\n\n/** Body of {@link PAGE_SCRIPTS_API}: each a script body for an\n * `executeScript`-style call. The drain is the forced-anchor one, which is\n * what every adapter outside the WDIO service runs. */\nexport interface PageScriptsResponse {\n collectorReady: string\n collectorInject: string\n collectorDrain: string\n capturePerformance: string\n}\n\n/** The envelope the route serves, built from the constants above so it cannot\n * differ from what the JS adapters run. */\nexport function buildPageScripts(): PageScriptsResponse {\n return {\n collectorReady: COLLECTOR_READY_SCRIPT,\n collectorInject: COLLECTOR_INJECT_SCRIPT,\n collectorDrain: collectorDrainExpression(true),\n capturePerformance: CAPTURE_PERFORMANCE_SCRIPT\n }\n}\n","import type { CommandLog } from './types.js'\n\n/** A row and the rows its execution issued, each level in list order. */\nexport interface CommandNode<T extends CommandLog = CommandLog> {\n command: T\n children: CommandNode<T>[]\n}\n\n/** Nests rows under their `parentId`; a row whose parent is absent stays a root until it arrives, and a loop is rooted. */\nexport function nestCommands<T extends CommandLog>(\n commands: readonly T[]\n): CommandNode<T>[] {\n const nodes = commands.map((command) => ({\n command,\n children: [] as CommandNode<T>[]\n }))\n const byId = new Map<number, CommandNode<T>>()\n for (const node of nodes) {\n const { id } = node.command\n if (typeof id === 'number' && !byId.has(id)) {\n byId.set(id, node)\n }\n }\n const parentOf = (node: CommandNode<T>) => {\n const { parentId } = node.command\n return typeof parentId === 'number' ? byId.get(parentId) : undefined\n }\n const roots: CommandNode<T>[] = []\n for (const node of nodes) {\n const parent = parentOf(node)\n if (parent && !closesCycle(node, parentOf)) {\n parent.children.push(node)\n } else {\n roots.push(node)\n }\n }\n return roots\n}\n\nfunction closesCycle<T extends CommandLog>(\n node: CommandNode<T>,\n parentOf: (node: CommandNode<T>) => CommandNode<T> | undefined\n): boolean {\n const seen = new Set<CommandNode<T>>()\n for (let at = parentOf(node); at && !seen.has(at); at = parentOf(at)) {\n if (at === node) {\n return true\n }\n seen.add(at)\n }\n return false\n}\n","// One definition of a failed command row, shared by the trace exporter and the conformance comparison.\n\nimport { mapCommandToAction } from './action-mapping.js'\nimport { nestCommands, type CommandNode } from './command-tree.js'\nimport {\n ASSERT_ACTION_CLASS,\n isWaitAction,\n type TraceAction\n} from './trace-actions.js'\nimport type { CollapsedAssertResult, CommandLog } from './types.js'\n\n/** The collapsed `{passed, actual, expected}` result an assertion row carries, when it carries one. */\nexport function collapsedAssertResult(\n result: unknown\n): CollapsedAssertResult | undefined {\n if (typeof result === 'object' && result !== null && 'passed' in result) {\n return result as CollapsedAssertResult\n }\n return undefined\n}\n\n/** Why a row failed: its `error`, a collapsed result with `passed: false`, or a wait that resolved `false`; undefined when it did not fail. */\nexport function commandFailure(\n cmd: CommandLog,\n action: TraceAction\n): { message: string } | undefined {\n if (cmd.error) {\n return errorFailure(cmd.error)\n }\n const collapsed = collapsedAssertResult(cmd.result)\n if (collapsed && collapsed.passed === false) {\n const isAssert = action.class === ASSERT_ACTION_CLASS\n return {\n message: String(\n collapsed.message ??\n (isAssert ? 'Assertion failed' : `${cmd.command} failed`)\n )\n }\n }\n // Nightwatch hands a timed-out waitForElement* callback only `false`; scoped to waits since a boolean read may legitimately be false.\n if (cmd.result === false && isWaitAction(action)) {\n const target = cmd.args?.find((a) => typeof a === 'string')\n return {\n message: target\n ? `${cmd.command} timed out waiting for ${String(target)}`\n : `${cmd.command} timed out`\n }\n }\n return undefined\n}\n\nfunction errorFailure(error: NonNullable<CommandLog['error']>): {\n message: string\n} {\n return { message: (error as { message?: string }).message ?? String(error) }\n}\n\nfunction rowFailure(cmd: CommandLog): { message: string } | undefined {\n const action = mapCommandToAction(cmd.command)\n if (action) {\n return commandFailure(cmd, action)\n }\n return cmd.error ? errorFailure(cmd.error) : undefined\n}\n\n/** Failed rows issued, at any depth, under a row that has not failed (a wait that passed, or is still running): polls it retried past. */\nexport function retriedRows<T extends CommandLog>(\n commands: readonly T[]\n): Set<T> {\n const retried = new Set<T>()\n const walk = (nodes: CommandNode<T>[], underPassing: boolean) => {\n for (const { command, children } of nodes) {\n const failed = Boolean(rowFailure(command))\n if (underPassing && failed) {\n retried.add(command)\n }\n walk(children, underPassing || !failed)\n }\n }\n walk(nestCommands(commands), false)\n return retried\n}\n\n/** Why a row counts as failed: its own failure, unless a row that issued it passed, which makes it an expected poll. */\nexport function effectiveFailure<T extends CommandLog>(\n cmd: T,\n retried: ReadonlySet<T>\n): { message: string } | undefined {\n return retried.has(cmd) ? undefined : rowFailure(cmd)\n}\n","/** Base64 magic of a PNG signature — the 8-byte header encodes deterministically. */\nconst PNG_BASE64_MAGIC = 'iVBOR'\nconst PNG_SIGNATURE = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]\nconst PNG_IHDR_WIDTH_OFFSET = 16\nconst PNG_IHDR_END = 24\nconst JPEG_SOI = [0xff, 0xd8]\n/** A frame header names the image's size; DHT/JPG/DAC share the 0xC0-0xCF range. */\nconst JPEG_SOF_EXCLUDED = [0xc4, 0xc8, 0xcc]\n/** Start of scan — the entropy-coded data begins, so no header follows. */\nconst JPEG_SOS = 0xda\n/** Prefixes decoded in turn, in bytes. A capture is megabytes and only its\n * header is ever read, so the small window serves every PNG (its size sits at\n * a fixed offset 16 bytes in) and almost every JPEG. The large one is for a\n * JPEG carrying metadata ahead of its frame header — EXIF and an embedded\n * thumbnail put it 35 KB in on a device-farm capture, and each APPn segment\n * may be 64 KB. */\nconst HEADER_WINDOWS = [4096, 1_048_576]\n\nexport interface ImageSize {\n width: number\n height: number\n}\n\n/** Detect image mime from a base64 string's magic bytes — trace screenshots may\n * be PNG (polling capture) or JPEG (CDP), and the zip names both `.jpeg`. */\nexport function imageMime(base64: string): string {\n return base64.startsWith(PNG_BASE64_MAGIC) ? 'image/png' : 'image/jpeg'\n}\n\n/** Intrinsic pixel size read out of a base64 PNG or JPEG header, or null when\n * the bytes name none. A capture is fitted by this rather than by the metadata\n * viewport: the two disagree on both mobile platforms — Android reports the\n * window without the nav bar, iOS reports points, not pixels. */\nexport function imageDimensions(base64: string): ImageSize | null {\n for (const window of HEADER_WINDOWS) {\n const bytes = headerBytes(base64, window)\n const size = pngSize(bytes) ?? jpegSize(bytes)\n // A zero from a corrupt or truncated header is not a size — a caller\n // deriving a ratio from it gets a 0/0 that collapses whatever it shapes.\n if (size && size.width > 0 && size.height > 0) {\n return size\n }\n // A prefix short of the window it asked for is the whole image: a wider\n // one would decode the same bytes again.\n if (bytes.length < window) {\n break\n }\n }\n return null\n}\n\n/** The first `limit` bytes of a base64 string, sliced to a whole number of\n * base64 groups so the decode never straddles one. Input that is not base64 at\n * all (a `data:` url, a truncated frame) names no size — the null contract. */\nfunction headerBytes(base64: string, limit: number): Uint8Array {\n const prefix = base64.slice(0, Math.ceil(limit / 3) * 4)\n const groups = prefix.slice(0, prefix.length - (prefix.length % 4))\n let binary = ''\n try {\n binary = atob(groups)\n } catch {\n return new Uint8Array()\n }\n const bytes = new Uint8Array(binary.length)\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i)\n }\n return bytes\n}\n\nfunction pngSize(bytes: Uint8Array): ImageSize | null {\n if (\n bytes.length < PNG_IHDR_END ||\n PNG_SIGNATURE.some((byte, i) => bytes[i] !== byte)\n ) {\n return null\n }\n return {\n width: u32(bytes, PNG_IHDR_WIDTH_OFFSET),\n height: u32(bytes, PNG_IHDR_WIDTH_OFFSET + 4)\n }\n}\n\n/** Walk the segment chain to the frame header. JPEG puts the size there rather\n * than at a fixed offset, and any number of metadata segments may precede it. */\nfunction jpegSize(bytes: Uint8Array): ImageSize | null {\n if (JPEG_SOI.some((byte, i) => bytes[i] !== byte)) {\n return null\n }\n let at = JPEG_SOI.length\n while (at + 8 < bytes.length) {\n // 0xFF repeats as segment padding, so advance to the marker byte itself.\n if (bytes[at] !== 0xff) {\n return null\n }\n const marker = bytes[at + 1]\n if (marker === 0xff) {\n at++\n continue\n }\n if (isSofMarker(marker)) {\n return { height: u16(bytes, at + 5), width: u16(bytes, at + 7) }\n }\n if (marker === JPEG_SOS) {\n return null\n }\n // Every marker reachable here carries a length: the walk starts past SOI\n // and stops at SOS, so the length-less ones (SOI, EOI, RST) are behind it.\n at += 2 + u16(bytes, at + 2)\n }\n return null\n}\n\nfunction isSofMarker(marker: number): boolean {\n return marker >= 0xc0 && marker <= 0xcf && !JPEG_SOF_EXCLUDED.includes(marker)\n}\n\nfunction u16(bytes: Uint8Array, at: number): number {\n return (bytes[at] << 8) | bytes[at + 1]\n}\n\nfunction u32(bytes: Uint8Array, at: number): number {\n return (\n ((bytes[at] << 24) |\n (bytes[at + 1] << 16) |\n (bytes[at + 2] << 8) |\n bytes[at + 3]) >>>\n 0\n )\n}\n","import type { RequestType } from './types.js'\n\n/**\n * Classify an HTTP request into a {@link RequestType}, preferring the response\n * `mimeType` and falling back to URL extension heuristics. The categories are\n * enumerated once, in `REQUEST_TYPES`, so this text can't drift from them. A\n * shape it can't place returns `'xhr'`, never the vocabulary's `'other'`,\n * which is reserved for producers that classify nothing at all.\n *\n * A pure leaf module, importable from any runtime: core's driver-side capture\n * and the page collector's Resource Timing capture are both classified here,\n * so a content-type or extension is read by one table rather than two.\n */\nexport function getRequestType(url: string, mimeType?: string): RequestType {\n const contentType = mimeType?.toLowerCase() ?? ''\n const urlLower = url.toLowerCase()\n if (contentType.includes('text/html')) {\n return 'document'\n }\n if (contentType.includes('text/css')) {\n return 'stylesheet'\n }\n if (\n contentType.includes('javascript') ||\n contentType.includes('ecmascript')\n ) {\n return 'script'\n }\n if (contentType.includes('image/')) {\n return 'image'\n }\n if (contentType.includes('font/') || contentType.includes('woff')) {\n return 'font'\n }\n if (contentType.includes('application/json')) {\n return 'fetch'\n }\n if (urlLower.endsWith('.html') || urlLower.endsWith('.htm')) {\n return 'document'\n }\n if (urlLower.endsWith('.css')) {\n return 'stylesheet'\n }\n if (urlLower.endsWith('.js') || urlLower.endsWith('.mjs')) {\n return 'script'\n }\n if (/\\.(png|jpg|jpeg|gif|svg|webp|ico)$/.test(urlLower)) {\n return 'image'\n }\n if (/\\.(woff|woff2|ttf|eot|otf)$/.test(urlLower)) {\n return 'font'\n }\n return 'xhr'\n}\n","/**\n * WebSocket upgrade paths on the backend's Fastify server. Each adapter opens\n * one socket at `worker`; one or more browser tabs subscribe at `client`.\n *\n * The HTTP API endpoints under `/api/baseline/*` live in `./baseline.ts`.\n */\nexport const WS_PATHS = {\n /** Adapter session upgrade endpoint. One socket per running adapter. */\n worker: '/worker',\n /** App/UI client upgrade endpoint. Multiple browser tabs may connect. */\n client: '/client'\n} as const\n\n/**\n * Query params on the worker upgrade. Both answer the same question for the\n * backend — does this socket continue the current run, or start a new one? —\n * which decides whether accumulated run state is kept or wiped. A run opens\n * one worker socket per spec file, so treating every connect as a new run\n * drops earlier specs' data and Preserve & Rerun then has nothing to diff.\n */\nexport const WORKER_WS_QUERY = {\n /** `1` when the adapter reopened this socket mid-run (e.g. after a session\n * change from `browser.end()` or `reloadSession()`). */\n reconnect: 'reconnect',\n /** Identity of the run this worker belongs to; shared by every worker the\n * same run forks. */\n runId: 'runId'\n} as const\n\n/**\n * Control-frame scopes exchanged over the worker↔backend↔client WS channels.\n * `BASELINE_WS_SCOPE` (in `./baseline.ts`) covers the baseline-specific\n * scopes; this object covers the runtime control frames. Single source of\n * truth — typos previously caused silent breakage when one end of the wire\n * sent a string the other end didn't recognize.\n */\nexport const WS_SCOPE = {\n /** Backend → worker: a dashboard client has subscribed. Wakes up the\n * adapter's `await UI ready` gate before tests start. */\n clientConnected: 'clientConnected',\n /** Backend → worker: the last dashboard tab closed. Triggers the\n * interactive shutdown flow (close WS, exit, pkill the dashboard). */\n clientDisconnected: 'clientDisconnected',\n /** Worker → backend → clients (or app-local): wipe the visualization data\n * for a specific test/suite uid (or the whole tree). */\n clearExecutionData: 'clearExecutionData',\n /** Worker → backend: clear-by-test-uid request (drives clearExecutionData). */\n clearCommands: 'clearCommands',\n /** Backend → clients: signal that the active run was stopped by the UI. */\n testStopped: 'testStopped',\n /** Worker → backend → clients: swap an earlier captured command for an\n * updated entry (used when a retry coalesces commands). */\n replaceCommand: 'replaceCommand',\n /** Worker → backend: register the config file path so reruns can spawn\n * with the same config. */\n config: 'config'\n} as const\n\nexport type WsScope = (typeof WS_SCOPE)[keyof typeof WS_SCOPE]\n","// Single source of truth for the accessibility-snapshot text format (the\n// `-snapshot.txt` trace resource). The producer (core's element-snapshot\n// serializers) and the consumer (the app's A11y-tree parser) both reference\n// these, so the written and parsed grammar can't drift. Keep one canonical form\n// per concept — see CLAUDE.md § \"One source of truth per concept\".\n\n/** Indent step per tree depth (the header sits at one unit; nodes at depth+1). */\nexport const SNAPSHOT_INDENT_UNIT = ' '\n\n/** Prefix of the web page-header line (`[Page: <title> — <url>]`). */\nexport const SNAPSHOT_PAGE_HEADER = '[Page'\n\n/** Native-mobile platforms the mobile serializer writes a header for. */\nexport const SNAPSHOT_NATIVE_PLATFORMS = ['android', 'ios'] as const\n\nexport type SnapshotNativePlatform = (typeof SNAPSHOT_NATIVE_PLATFORMS)[number]\n\n/** Prefix of the native-mobile header line — bare `[android]` when the capture\n * knew neither device nor viewport (what the per-action capture passes), else\n * `[android — <device> (<width>×<height>)]`. */\nexport function snapshotNativeHeader(platform: SnapshotNativePlatform): string {\n return `[${platform}`\n}\n\n/** Whether a line is a snapshot's header rather than a tree node. Every\n * serializer opens with exactly one — `[Page …]` for web, `[<platform> …]` for\n * native — so both forms live in this one predicate and a consumer that only\n * knew the web form cannot render a native header as a node. */\nexport function isSnapshotHeaderLine(line: string): boolean {\n return (\n line.startsWith(SNAPSHOT_PAGE_HEADER) ||\n SNAPSHOT_NATIVE_PLATFORMS.some((platform) =>\n line.startsWith(snapshotNativeHeader(platform))\n )\n )\n}\n\n/** Separator between a node and its captured locator (rendered space-padded). */\nexport const SNAPSHOT_LOCATOR_DELIM = '→'\n\n/** Marks an inferred purpose before the locator (`<role> ∈ \"<purpose>\"`). */\nexport const SNAPSHOT_PURPOSE_TOKEN = '∈'\n\n/**\n * Roles that can be interacted with — rendered with `→ selector`.\n * Structural roles (heading, img, form, nav, …) are intentionally excluded.\n */\nexport const INTERACTIVE_ROLES = new Set([\n 'button',\n 'link',\n 'textbox',\n 'checkbox',\n 'radio',\n 'combobox',\n 'slider',\n 'searchbox',\n 'spinbutton',\n 'switch',\n 'tab',\n 'menuitem',\n 'option'\n])\n\n/** The fields of a node this module's helpers read. Deliberately narrower than\n * `AccessibilityNode` so core's mobile `SnapshotNode` satisfies it too — both\n * snapshot pipelines share these helpers. */\nexport interface SnapshotFormatNode {\n role: string\n name: string\n depth: number\n}\n\n/**\n * Returns true when `nodes[index]` is a statictext whose accessible name\n * is already echoed by its immediate interactive parent — such a node\n * adds no information and should be suppressed from the output.\n */\nexport function isStatictextEchoedByParent(\n nodes: SnapshotFormatNode[],\n index: number\n): boolean {\n const node = nodes[index]!\n if (node.role !== 'statictext' || !node.name) {\n return false\n }\n for (let j = index - 1; j >= 0; j--) {\n if (nodes[j]!.depth < node.depth) {\n const parent = nodes[j]!\n if (\n INTERACTIVE_ROLES.has(parent.role) &&\n parent.name &&\n parent.name.includes(node.name)\n ) {\n return true\n }\n break\n }\n }\n return false\n}\n","// The trace.zip file layout — the one definition of how a trace is packaged.\n//\n// `core` writes these entries and `backend` reads them, and the two packages\n// cannot import each other (backend depends on shared only). Before this module\n// both sides carried their own copies of these strings, so a rename in the\n// writer left every reader test green and the mismatch only surfaced when\n// someone opened a real trace. Import from here on both sides instead.\n//\n// The *event interfaces* deliberately stay separate: `core`'s are strict (the\n// writer always emits `pageId`, `apiName`, `params`, `title`) while the reader's\n// are permissive, because it also parses traces this repo did not write — see\n// `backend/src/trace-reader-types.ts`. Strict producer, lenient consumer; only\n// the names below have to agree.\n\n/** Entry names inside a trace.zip, and the directory prefix for blobs. Also the\n * filenames used by the unpacked `ndjson-directory` format. */\nexport const TRACE_ZIP_ENTRIES = {\n /** NDJSON action events. */\n trace: 'trace.trace',\n /** NDJSON HAR resource snapshots. Omitted when nothing was captured. */\n network: 'trace.network',\n /** NDJSON DOM mutation stream. Omitted when nothing was captured. */\n mutations: 'trace.mutations',\n /** Human/LLM-readable Markdown run transcript. */\n transcript: 'transcript.md',\n /** Screenshots, element snapshots and network bodies live under here. */\n resourcesDir: 'resources'\n} as const\n\n/** Stream suffixes the reader matches on. A zip may carry several streams (one\n * per page/session), so entries are matched by suffix rather than exact name;\n * `.stacks` is a sidecar the writer does not emit but foreign traces do. */\nexport const TRACE_STREAM_SUFFIXES = {\n trace: '.trace',\n network: '.network',\n mutations: '.mutations',\n stacks: '.stacks'\n} as const\n\n/** `type` discriminants of the NDJSON events in a `.trace` stream. */\nexport const TRACE_EVENT_TYPES = {\n before: 'before',\n after: 'after',\n frameSnapshot: 'frame-snapshot',\n screencastFrame: 'screencast-frame',\n contextOptions: 'context-options',\n console: 'console',\n stdout: 'stdout',\n stderr: 'stderr'\n} as const\n\nexport type TraceEventType =\n (typeof TRACE_EVENT_TYPES)[keyof typeof TRACE_EVENT_TYPES]\n","// Canonical type definitions shared across @wdio/devtools-* packages.\n//\n// Adapters (service, nightwatch-devtools, selenium-devtools) produce events of\n// these shapes. The backend stores and forwards them. The app consumes them.\n// See ARCHITECTURE.md §2 and CLAUDE.md §2.1.\n\nimport type { DeviceInfo } from './device.js'\n\nexport const LOG_LEVELS = [\n 'trace',\n 'debug',\n 'log',\n 'info',\n 'warn',\n 'error'\n] as const\nexport type LogLevel = (typeof LOG_LEVELS)[number]\n\n/** Where a captured ConsoleLog entry originated. */\nexport type LogSource = 'browser' | 'test' | 'terminal'\n\nexport enum TraceType {\n Standalone = 'standalone',\n Testrunner = 'testrunner'\n}\n\nexport type TestStatus = 'passed' | 'failed' | 'skipped' | 'pending' | 'running'\n\n/** `live` opens the DevTools UI window; `trace` skips it and lets a downstream exporter consume captured state. */\nexport type DevToolsMode = 'live' | 'trace'\n\n/** `zip` (default) writes a single `trace-<id>.zip`; `ndjson-directory` writes\n * the same `trace.trace` + `trace.network` + `resources/` layout unpacked\n * into `trace-<id>/`. Both open in any standard trace viewer — the unpacked\n * form skips the unzip step for agentic / scripted consumers. */\nexport type TraceFormat = 'zip' | 'ndjson-directory'\n\n/** `session` (default) writes one trace per worker session; `spec` writes one\n * trace per spec file, keyed on the spec's filename; `test` writes one trace\n * per test. Only applies in trace mode. */\nexport type TraceGranularity = 'session' | 'spec' | 'test'\n\n/** Retention policy for written traces. Only applies in trace mode; `on` is\n * the current always-write behavior (there is no `off` — that's simply not\n * using trace mode). */\nexport type TraceRetentionPolicy =\n | 'on'\n | 'retain-on-failure'\n | 'retain-on-first-failure'\n | 'on-first-retry'\n | 'on-all-retries'\n | 'retain-on-failure-and-retries'\n\n/** Per-test screenshot capture policy, mirroring Playwright's `screenshot`\n * option. `only-on-failure` shoots after a failing test; `on` after every\n * test; `off` (default) never. Only applies in trace mode. */\nexport type TraceScreenshotPolicy = 'off' | 'on' | 'only-on-failure'\n\n/** Per-test video capture policy. `off` (default) records nothing; any other\n * value records the screencast and keeps each test's video slice per the same\n * retention semantics as `tracePolicy`. Only applies in trace mode at\n * `traceGranularity: 'test'` (the per-test scope videos attach to). */\nexport type TraceVideoPolicy = 'off' | TraceRetentionPolicy\n\n/** One node in a test's ancestor chain, outermost first. */\nexport interface TestAncestor {\n uid: string\n title: string\n kind: 'feature' | 'scenario' | 'suite' | 'test' | 'step' | 'hook'\n}\n\n/** Per-test metadata for Tracing.tracingGroup events in trace output. */\nexport interface TestMetadataEntry {\n title: string\n specFile: string\n state?: TestStatus\n attempt?: number\n ancestry?: TestAncestor[]\n}\n\n/** Test metadata keyed by testUid — maps stable test IDs to human-readable\n * title + specFile for Tracing.tracingGroup events in trace output. */\nexport type TestMetadataMap = Map<string, TestMetadataEntry>\n\n/**\n * Normalized assertion result an adapter may attach to `CommandLog.result` for\n * an assertion command. The trace exporter's assert-param builder prefers this\n * over the positional `[actual, expected]` arg convention — correct for\n * frameworks whose asserts pass only an expected value (a matcher like\n * `titleContains('x')`), where args[0] is the expected, not the actual.\n * Cross-package contract: adapters produce it, core's exporter consumes it.\n */\nexport interface CollapsedAssertResult {\n passed: boolean\n actual?: unknown\n expected?: unknown\n message?: string\n}\n\n/**\n * Enum-style accessor for the canonical TestStatus values. Adapter code uses\n * this for readable comparisons (`state === TEST_STATE.PASSED`). The app's\n * sidebar has a parallel `TestState` accessor with the same values; that's a\n * naming holdover (PascalCase enum-style) — both can coexist.\n */\nexport const TEST_STATE = {\n PENDING: 'pending',\n RUNNING: 'running',\n PASSED: 'passed',\n FAILED: 'failed',\n SKIPPED: 'skipped'\n} as const satisfies Record<string, TestStatus>\n\n/**\n * Identifier sent by each adapter on RunnerRequestBody.framework. Used by the\n * backend's runner to pick rerun CLI args. This is technically the *test\n * runner* identifier rather than the higher-level framework (wdio/nightwatch/\n * selenium) — wdio's runner can be mocha/jasmine/cucumber, nightwatch can be\n * vanilla or cucumber, selenium adapters report 'selenium-webdriver'.\n */\nexport const TEST_RUNNER_IDS = [\n 'mocha',\n 'jasmine',\n 'cucumber',\n 'nightwatch',\n 'nightwatch-cucumber',\n 'selenium-webdriver'\n] as const\nexport type TestRunnerId = (typeof TEST_RUNNER_IDS)[number]\n\n/**\n * Narrow an untrusted value onto {@link TestRunnerId}. Needed wherever the id\n * arrives from outside this build — a framework's own config value, or a trace\n * zip recorded by another version — since the static type can't vouch for it.\n */\nexport function isTestRunnerId(value: unknown): value is TestRunnerId {\n return (\n typeof value === 'string' &&\n (TEST_RUNNER_IDS as readonly string[]).includes(value)\n )\n}\n\n// ─── Inner event payloads ───────────────────────────────────────────────────\n\nexport interface PerformanceData {\n navigation?: {\n url: string\n timing: {\n loadTime?: number\n domReady?: number\n responseTime?: number\n dnsLookup?: number\n tcpConnection?: number\n serverResponse?: number\n }\n }\n resources?: Array<{\n url: string\n duration: number\n size: number\n type: string\n startTime: number\n responseEnd: number\n }>\n}\n\nexport interface DocumentInfo {\n url: string\n title: string\n headers: { userAgent: string; language: string; platform: string }\n documentInfo: { readyState: string; referrer: string; characterSet: string }\n}\n\nexport interface CommandLog {\n command: string\n args: unknown[]\n /** Optional display label (e.g. trace-player's `Element.fill(\"x\")`). Falls\n * back to `command` when absent. */\n title?: string\n result?: unknown\n error?: Error | SerializedError\n timestamp: number\n /** Wall-clock ms when the command was invoked (before execution). */\n startTime?: number\n /** Monotonic issue order, stamped when the test issued the command. Breaks\n * ties the millisecond clock can't: a deferred row (a Nightwatch native\n * assert, finalized in a batch at test-end) is appended long after the driver\n * row it was issued before, so an equal `startTime` otherwise resolved in\n * insertion order and put the assert after the command that followed it. */\n sequence?: number\n callSource?: string\n screenshot?: string\n testUid?: string\n performance?: PerformanceData\n cookies?: string\n documentInfo?: DocumentInfo\n /** Stable row identity: a `replaceCommand` locates its row by it, and a child names its parent by it. */\n id?: number\n /** The `id` of the row whose execution issued this one (a wait's condition reads, an element wait's polls). The\n * parent is sent first, while it is still running, so a live list can nest the child on arrival; a row whose\n * parent is absent from the list renders at the top level (`nestCommands`). */\n parentId?: number\n /** Cucumber step this command ran under — nests below testUid in the trace's\n * group tree (Feature → Scenario → Step). Set by the adapter step hooks. */\n stepUid?: string\n /** Depth-indented accessibility-tree text for the page state at this command\n * (from the per-action `-snapshot.txt` resource). Reconstructed by the trace\n * reader; drives the player's A11y tab. */\n snapshotText?: string\n /** Page-coordinate hit point for pointer actions — the centre of the matched\n * element at capture time. Synthesized in the exporter; drives the snapshot\n * click marker and the timeline pointer glyph. */\n point?: { x: number; y: number }\n /** Locator of the element this command acted on. Survives assertion folding\n * (where `args` becomes the expected value), so the player's element overlay\n * can box an assertion target like `#flash`. Carried through the trace as the\n * `locator` action param — kept distinct from the `selector` param so it\n * never feeds `paramsToArgs`. */\n selector?: string\n}\n\n/**\n * Payload broadcast under the WS scope `'replaceCommand'`. Tells the UI to\n * swap an existing CommandLog in-place — used when an adapter reconciles a\n * preliminary entry with the actual final result (e.g. selenium's\n * driverPatcher emits a placeholder, then replaces it once the command\n * resolves).\n */\nexport interface ReplaceCommandWsPayload {\n oldTimestamp: number\n command: CommandLog\n}\n\nexport interface ConsoleLog {\n type: LogLevel\n args: unknown[]\n timestamp: number\n source?: LogSource\n}\n\n/**\n * Every word a captured `NetworkRequest.type` can carry. The capture side\n * classifies once (shared's `getRequestType` for driver-observed traffic and\n * Resource Timing entries, the page collector for its patched\n * `fetch`/`XMLHttpRequest`) and the app only\n * translates these words into display buckets. Adding a category here is the\n * compile-time trigger that forces every consumer's mapping to handle it.\n * `other` is the residual for traffic no producer could classify (a failed\n * request that never got a content-type, a foreign trace's unknown mime).\n */\nexport const REQUEST_TYPES = [\n 'document',\n 'stylesheet',\n 'script',\n 'image',\n 'font',\n 'fetch',\n 'xhr',\n 'other'\n] as const\nexport type RequestType = (typeof REQUEST_TYPES)[number]\n\n/**\n * Narrow an untrusted value onto {@link RequestType}. Needed wherever a type\n * arrives from outside this build — a trace file written by an older version,\n * or an adapter payload off the wire — since the static type can't vouch for it.\n */\nexport function isRequestType(value: unknown): value is RequestType {\n return (\n typeof value === 'string' &&\n (REQUEST_TYPES as readonly string[]).includes(value)\n )\n}\n\n/**\n * Stated only by the one network source that carries no headers, no body and\n * (outside Chromium) no status: the document's Resource Timing buffer, which\n * is all a browser without BiDi exposes. Every other source carries them and\n * states nothing; a consumer reads the mark to name the gap rather than render\n * the request as still in flight or failed.\n */\nexport const NETWORK_CAPTURE_SOURCE = {\n RESOURCE_TIMING: 'resource-timing'\n} as const\nexport type NetworkCaptureSource =\n (typeof NETWORK_CAPTURE_SOURCE)[keyof typeof NETWORK_CAPTURE_SOURCE]\n\n/** Phase durations in ms, HAR's phases. A phase the browser withheld is\n * absent. An interface rather than a mapped type because the wire-schema\n * generator reads property declarations. */\nexport interface NetworkTimings {\n blocked?: number\n dns?: number\n connect?: number\n ssl?: number\n send?: number\n wait?: number\n receive?: number\n}\n\n/** The phases, in HAR's order, for a consumer that iterates them. */\nexport const NETWORK_TIMING_PHASES = [\n 'blocked',\n 'dns',\n 'connect',\n 'ssl',\n 'send',\n 'wait',\n 'receive'\n] as const satisfies readonly (keyof NetworkTimings)[]\nexport type NetworkTimingPhase = (typeof NETWORK_TIMING_PHASES)[number]\n\nexport interface NetworkRequest {\n id: string\n url: string\n /** Empty when the source does not expose it: Resource Timing names no\n * method, so only a parser-initiated load (an image, a script) is known to\n * be a GET. */\n method: string\n headers?: Record<string, string>\n cookies?: unknown[]\n status?: number\n statusText?: string\n timestamp: number\n startTime: number\n endTime?: number\n time?: number\n type: RequestType\n initiator?: string\n requestHeaders?: Record<string, string>\n responseHeaders?: Record<string, string>\n navigation?: string\n redirectChain?: unknown[]\n children?: NetworkRequest[]\n response?: {\n fromCache: boolean\n headers: Record<string, string>\n mimeType: string\n status: number\n }\n error?: string\n requestBody?: string\n responseBody?: string\n size?: number\n timings?: NetworkTimings\n source?: NetworkCaptureSource\n}\n\n// ─── Trace and metadata ─────────────────────────────────────────────────────\n\nexport interface Viewport {\n width: number\n height: number\n offsetLeft: number\n offsetTop: number\n scale: number\n}\n\nexport interface ScreencastInfo {\n sessionId?: string\n videoPath?: string\n videoFile?: string\n frameCount?: number\n duration?: number\n startTime?: number\n}\n\n/** Single captured screencast frame — base64 image + capture timestamp (ms). */\nexport interface ScreencastFrame {\n /** Base64-encoded image data — JPEG/PNG from CDP push mode or PNG from browser.takeScreenshot() in polling mode. */\n data: string\n /** Unix timestamp in milliseconds. */\n timestamp: number\n}\n\n/**\n * Screencast recorder configuration. Used by every adapter — the base recorder\n * in `@wdio/devtools-core` consumes this shape; per-adapter wrappers extend it\n * (e.g. WDIO's CDP fast-path opts).\n */\nexport interface ScreencastOptions {\n /** Enable screencast recording for this session (default: false). */\n enabled?: boolean\n /**\n * Image format for individual frames (default: 'jpeg').\n * - Chrome/Chromium (CDP mode): controls the format Chrome sends over CDP.\n * - Other browsers (polling mode): screenshots are always PNG; ignored.\n * Does NOT affect the output video container, which is always WebM.\n */\n captureFormat?: 'jpeg' | 'png'\n /** JPEG quality 0–100 (default: 70). CDP mode + 'jpeg' only. */\n quality?: number\n /** Max frame width in pixels Chrome sends over CDP (default: 1280). */\n maxWidth?: number\n /** Max frame height in pixels Chrome sends over CDP (default: 720). */\n maxHeight?: number\n /**\n * Screenshot polling interval in milliseconds for non-Chrome browsers\n * (default: 200 ms ≈ 5 fps). Lower = smoother, more WebDriver round-trips.\n */\n pollIntervalMs?: number\n /** Cap on frames held in memory (default: 2000 ≈ several minutes at ~5 fps); the buffer is decimated in place past this. */\n maxBufferFrames?: number\n}\n\n/** Defaults applied to ScreencastOptions when not specified by the user. */\nexport const SCREENCAST_DEFAULTS: Required<ScreencastOptions> = {\n enabled: false,\n captureFormat: 'jpeg',\n quality: 70,\n maxWidth: 1280,\n maxHeight: 720,\n pollIntervalMs: 200,\n maxBufferFrames: 2000\n}\n\n/**\n * Options every framework adapter accepts. Each adapter's own options interface\n * extends this and adds only its framework-specific fields (e.g. WDIO's\n * devtoolsCapabilities, Selenium's openUi, Nightwatch's bidi).\n */\nexport interface BaseDevToolsOptions {\n /** Port to launch the application on (default: random). */\n port?: number\n /** Hostname to launch the application on. @default localhost */\n hostname?: string\n /** Screencast recording options. When enabled, a continuous video of the\n * browser session is recorded and saved as a .webm file. */\n screencast?: ScreencastOptions\n /** Capture node:assert assertions (and framework `expect` matchers where\n * supported) as first-class commands. Default true. */\n captureAssertions?: boolean\n /** `live` (default) launches the DevTools UI; `trace` skips it. */\n mode?: DevToolsMode\n /** Trace output layout — `zip` (default) writes a single archive,\n * `ndjson-directory` unpacks into `trace-<id>/`. Only applies in trace mode. */\n traceFormat?: TraceFormat\n /** Trace output granularity — `session` (default) writes one trace per\n * worker session; `spec` writes one per spec file. Only applies in trace mode. */\n traceGranularity?: TraceGranularity\n /** Trace retention policy — gates which traces are kept (e.g.\n * `retain-on-failure`). Default `on` (keep all). Only applies in trace mode. */\n tracePolicy?: TraceRetentionPolicy\n /** Record a dense, continuous screencast filmstrip into the trace for\n * scrubbable playback in the trace player — not just one frame per action.\n * The dense frames are added alongside the per-action frames (which carry the\n * DOM snapshots). Runs the screencast recorder (CDP push on Chrome, polling\n * elsewhere). Default true. Set `false` to record only one frame per action\n * (smaller trace, no continuous recorder). Only applies in trace mode. */\n filmstrip?: boolean\n /** Write the `devtools-artifacts-<sessionId>.json` manifest next to the trace\n * — the generic index reporters/CI consume to discover produced artifacts.\n * Off by default; auto-enabled when an Allure reporter is detected (WDIO:\n * `@wdio/allure-reporter` in config; Selenium: an active `allure-js-commons`\n * runtime). Nightwatch has no live Allure signal, so it stays opt-in there.\n * Only applies in trace mode. */\n emitArtifactsManifest?: boolean\n}\n\n/** Minimal Cucumber pickle-step shape — only the fields the adapters read.\n * Cucumber's own types vary across versions, so we pin just these. */\nexport interface CucumberPickleStep {\n text?: string\n astNodeIds?: string[]\n location?: { line?: number }\n}\n\n/** Minimal Cucumber pickle shape — only the fields the adapters read. `steps`\n * is present only where the adapter walks step boundaries (Nightwatch). */\nexport interface CucumberPickle {\n name?: string\n uri?: string\n location?: { line?: number }\n astNodeIds?: string[]\n steps?: CucumberPickleStep[]\n}\n\n/** Which run controls the dashboard offers for a session. */\nexport interface RunCapabilities {\n canRunSuites: boolean\n canRunTests: boolean\n canRunAll: boolean\n}\n\n/** What an adapter puts in `Metadata.options` for the run controls; the backend spawns these commands. */\nexport interface RunnerOptions {\n framework?: string\n configFile?: string\n configFilePath?: string\n runCapabilities?: Partial<RunCapabilities>\n rerunCommand?: string\n launchCommand?: string\n}\n\nexport interface Metadata {\n type: TraceType\n url?: string\n options?: unknown\n capabilities?: unknown\n viewport?: Viewport\n sessionId?: string\n testEnv?: string\n host?: string\n modulePath?: string\n desiredCapabilities?: Record<string, unknown>\n /** Runner that recorded this stream. Drives the locator dialect the page\n * capture emits and the player's locator hint; undefined for a trace zip\n * recorded before the field existed, or by a foreign tool. */\n runner?: TestRunnerId\n /** The device this was recorded on, when the session was a native mobile one.\n * Read from the session's capabilities through `deviceFromCapabilities`;\n * undefined for a desktop capture, and for a zip recorded before the field\n * existed or by a foreign tool. */\n device?: DeviceInfo\n}\n\n/** Captured metadata keyed by browser `sessionId` — lets the UI keep each\n * session's metadata instead of overwriting on a new session. Record (not Map)\n * so it stays JSON-serializable across the WS boundary and in contexts. */\nexport type MetadataBySession = Record<string, Metadata>\n\n/**\n * Node-safe shape of a captured DOM mutation. The browser-side script\n * (packages/script) extends this with the real `MutationRecordType` union\n * via the global `TraceMutation` declaration there; this Node version uses\n * a plain string literal type so the shape can flow through shared without\n * dragging the DOM lib into shared's compilation.\n *\n * `addedNodes` / `removedNodes` are opaque payloads here — the browser side\n * stringifies / serializes them via SimplifiedVNode.\n */\nexport interface TraceMutation {\n type: 'attributes' | 'characterData' | 'childList'\n attributeName?: string\n attributeNamespace?: string\n attributeValue?: string\n newTextContent?: string\n oldValue?: string\n addedNodes: unknown[]\n target?: string\n removedNodes: string[]\n previousSibling?: string\n nextSibling?: string\n timestamp: number\n url?: string\n}\n\n/**\n * Trailing sentinel line in a `trace.mutations` NDJSON stream, marking that\n * `dropped` late mutations were discarded under the size cap. A distinct shape\n * (not a `TraceMutation.type`) so it never collides with a real mutation. This\n * is the writer↔reader contract shared by core's exporter and the backend\n * reader, so both agree on the key.\n */\nexport interface MutationsTruncationMarker {\n __truncated__: true\n dropped: number\n}\n\n/** True when an NDJSON entry is the truncation sentinel, not a mutation. */\nexport function isMutationsTruncationMarker(\n entry: unknown\n): entry is MutationsTruncationMarker {\n return (\n typeof entry === 'object' &&\n entry !== null &&\n (entry as { __truncated__?: unknown }).__truncated__ === true\n )\n}\n\n/**\n * Captured at each user-facing action boundary in `trace` mode. Feeds the\n * downstream trace.zip exporter (Phase 4). `screenshot` is base64-encoded JPEG.\n */\nexport interface ActionSnapshot {\n /** The key every reader joins on. A capture is stamped, once the exporter\n * has run, with the action whose RESULT it is — which is why `command`\n * below is not that action's name. */\n timestamp: number\n /** A label, not a key: the action the capture was taken for, which mid-run is\n * the one it PRECEDES (the capture is its state before it ran), while the\n * timestamp names the action it follows. No reader may select on it — the\n * one value with meaning is core's `FINAL_SNAPSHOT_COMMAND`, which marks a\n * frame carrying no result to show. */\n command: string\n url?: string\n title?: string\n screenshot?: string\n elements?: unknown[]\n snapshotText?: string\n /** Raw accessibility nodes, for an adapter that captured the tree but cannot\n * serialize it — the serializer is TypeScript. The exporter turns this into\n * `snapshotText`, which is what the A11y tab parses; a sender that already\n * has `snapshotText` never sets it. */\n accessibilityTree?: AccessibilityNode[]\n}\n\n/** One node of what `accessibilityTreeScript` evaluates to in the page. */\nexport interface AccessibilityNode {\n role: string\n name: string\n selector: string\n depth: number\n level: number | string\n disabled: string\n checked: string\n expanded: string\n selected: string\n pressed: string\n required: string\n readonly: string\n /** Whether the element's bounding rect intersects the viewport. */\n isInViewport?: boolean\n}\n\n/** One element of what `elementsScript` evaluates to in the page. */\nexport interface BrowserElementInfo {\n tagName: string\n name: string // computed accessible name (ARIA spec)\n type: string\n value: string\n href: string\n selector: string\n isInViewport: boolean\n boundingBox?: { x: number; y: number; width: number; height: number }\n}\n\nexport interface GetBrowserElementsOptions {\n includeBounds?: boolean\n /** Only return elements whose bounding rect intersects the viewport (default true). */\n inViewportOnly?: boolean\n}\n\nexport interface TraceLog {\n mutations: TraceMutation[]\n logs: string[]\n consoleLogs: ConsoleLog[]\n networkRequests: NetworkRequest[]\n metadata: Metadata\n commands: CommandLog[]\n sources: Record<string, string>\n suites?: Record<string, SuiteStats>[]\n screencast?: ScreencastInfo\n config?: { configFile?: string }\n /** Per-action snapshots captured in `mode: 'trace'` for the trace.zip exporter. */\n actionSnapshots?: ActionSnapshot[]\n /** Dense screencast frames (continuous recorder buffer) for the trace-mode\n * filmstrip. Present only when `filmstrip` is enabled; thinned + content-\n * addressed at export time. */\n screencastFrames?: ScreencastFrame[]\n}\n\n// ─── Preserve-and-rerun ─────────────────────────────────────────────────────\n\nexport interface TestError {\n message?: string\n name?: string\n stack?: string\n /** expect-webdriverio surfaces these directly on the error. */\n expected?: unknown\n actual?: unknown\n /** expect-webdriverio also bundles them under matcherResult. */\n matcherResult?: {\n expected?: unknown\n actual?: unknown\n message?: string\n }\n}\n\nexport interface PreservedStep {\n uid: string\n title?: string\n fullTitle?: string\n start?: number\n end?: number\n state?: TestStatus\n error?: TestError\n}\n\nexport interface PreservedAttempt {\n testUid: string\n scope: 'test' | 'suite'\n capturedAt: number\n window: { start: number; end: number }\n test: {\n title?: string\n fullTitle?: string\n file?: string\n callSource?: string\n start?: number\n end?: number\n duration?: number\n state?: TestStatus\n error?: TestError\n }\n steps?: PreservedStep[]\n commands: CommandLog[]\n consoleLogs: ConsoleLog[]\n networkRequests: NetworkRequest[]\n /** See note on TraceLog.mutations. */\n mutations: unknown[]\n sources: Record<string, string>\n}\n\n// ─── Test reporter stats (nightwatch + selenium adapters) ───────────────────\n\n/**\n * Serialized form of an `Error` — a plain object that survives\n * `JSON.stringify` over the WS bridge.\n */\nexport interface SerializedError {\n name: string\n message: string\n stack?: string\n}\n\n/**\n * An error payload as it flows through capture: the raw `Error` instance at\n * capture time, or its serialized form once it has crossed the WS bridge.\n */\nexport type ReporterError = Error | SerializedError\n\nexport interface TestStats {\n uid: string\n cid: string\n title: string\n fullTitle: string\n parent: string\n state: TestStatus\n start: Date\n end: Date | null\n type: 'test'\n file: string\n retries: number\n _duration: number\n error?: ReporterError\n hooks?: unknown[]\n callSource?: string\n order?: number\n}\n\nexport interface SuiteStats {\n uid: string\n cid: string\n title: string\n fullTitle: string\n type: 'suite'\n file: string\n start: Date\n state?: TestStatus\n end?: Date | null\n tests: (string | TestStats)[]\n suites: SuiteStats[]\n hooks: unknown[]\n _duration: number\n parent?: string\n callSource?: string\n order?: number\n /** Cucumber-only: the .feature file path. Distinct from `file` because the\n * root suite's `file` stays at cwd to keep its stable UID; rerun payloads\n * use this to drive feature-level filtering. */\n featureFile?: string\n}\n","// Maps captured ConsoleLog entries into trace-event vocabulary: browser\n// console entries become `console` events; test/terminal output becomes\n// `stdout`/`stderr` events (which carry no location semantics — matching\n// what we capture for Node-side lines).\n\nimport type { ConsoleLog, LogSource } from '@wdio/devtools-shared'\n\nexport interface ConsoleEvent {\n type: 'console'\n time: number\n pageId?: string\n messageType: string\n text: string\n args?: { preview: string; value: unknown }[]\n location: { url: string; lineNumber: number; columnNumber: number }\n}\n\nexport interface StdioEvent {\n type: 'stdout' | 'stderr'\n timestamp: number\n text?: string\n /** Extension field: test-vs-terminal origin; foreign viewers ignore it. */\n source?: Extract<LogSource, 'test' | 'terminal'>\n}\n\n// Caps pathological runs (console.log in a loop) so the trace stays openable.\nconst MAX_CONSOLE_EVENTS = 10_000\n\n/** Trace vocabulary uses 'warning'; 'trace' maps to the nearest severity, 'debug'. */\nfunction toTraceLevel(level: ConsoleLog['type']): string {\n if (level === 'warn') {\n return 'warning'\n }\n if (level === 'trace') {\n return 'debug'\n }\n return level\n}\n\nfunction previewArg(arg: unknown): string {\n if (typeof arg === 'string') {\n return arg\n }\n try {\n return JSON.stringify(arg) ?? String(arg)\n } catch {\n return String(arg)\n }\n}\n\nexport function buildConsoleEvents(\n logs: ConsoleLog[],\n pageId: string,\n wallTime: number\n): (ConsoleEvent | StdioEvent)[] {\n const capped = logs.slice(0, MAX_CONSOLE_EVENTS)\n const events: (ConsoleEvent | StdioEvent)[] = capped.map((log) => {\n const time = Math.max(0, log.timestamp - wallTime)\n const text = log.args.map(previewArg).join(' ')\n // Untagged entries predate source tagging; they came from the page.\n if (log.source === 'browser' || log.source === undefined) {\n return {\n type: 'console',\n time,\n pageId,\n messageType: toTraceLevel(log.type),\n text,\n args: log.args.map((arg) => ({ preview: previewArg(arg), value: arg })),\n // Location isn't captured at the patch site; the required field ships zeroed.\n location: { url: '', lineNumber: 0, columnNumber: 0 }\n } satisfies ConsoleEvent\n }\n return {\n type: log.type === 'error' || log.type === 'warn' ? 'stderr' : 'stdout',\n timestamp: time,\n text,\n source: log.source\n } satisfies StdioEvent\n })\n if (logs.length > MAX_CONSOLE_EVENTS) {\n const last = capped[capped.length - 1]\n events.push({\n type: 'stderr',\n timestamp: last ? Math.max(0, last.timestamp - wallTime) : 0,\n text: `[devtools] console truncated: dropped ${logs.length - MAX_CONSOLE_EVENTS} entries`\n })\n }\n return events\n}\n","// Per-action snapshot resources and the screencast-frame filmstrip derived\n// from them. Split from trace-exporter.ts; the exporter composes these into\n// the trace event stream.\n\nimport type { ActionSnapshot } from '@wdio/devtools-shared'\nimport type { TraceZipResource } from './trace-zip-writer.js'\n\nexport interface ScreencastFrameEvent {\n type: 'screencast-frame'\n pageId: string\n sha1: string\n elements?: string\n snapshot?: string\n width: number\n height: number\n timestamp: number\n}\n\n// The one merge rule for same-timestamp snapshots: richest wins per field,\n// first-seen wins for identity (command/url/title, which no export path reads).\n// Merging per field matters because the real screenshot and the real elements\n// can land on different captures of the same action.\nfunction mergeSnapshotInto(\n existing: ActionSnapshot,\n snap: ActionSnapshot\n): void {\n if ((snap.screenshot?.length ?? 0) > (existing.screenshot?.length ?? 0)) {\n existing.screenshot = snap.screenshot\n }\n if (!existing.elements?.length && snap.elements?.length) {\n existing.elements = snap.elements\n }\n if (!existing.snapshotText && snap.snapshotText) {\n existing.snapshotText = snap.snapshotText\n }\n}\n\n// Two snapshots at the same timestamp (a real post-action capture and a blank\n// end-of-scenario one) map to the same `${pageId}-${ts}` resource name, so a\n// last-wins write lets a blank frame clobber the real one. Collapse to one per\n// timestamp so the resource writer never sees a collision.\nfunction collapseByTimestamp(snapshots: ActionSnapshot[]): ActionSnapshot[] {\n const byTs = new Map<number, ActionSnapshot>()\n const order: number[] = []\n for (const snap of snapshots) {\n const existing = byTs.get(snap.timestamp)\n if (!existing) {\n byTs.set(snap.timestamp, { ...snap })\n order.push(snap.timestamp)\n continue\n }\n mergeSnapshotInto(existing, snap)\n }\n return order.map((ts) => byTs.get(ts)!)\n}\n\n/** Insert a snapshot, or merge it into the one already holding its timestamp.\n * Applies the collapse rule at capture time so the accumulator holds one record\n * per timestamp: a mid-run slice flush reads this array live, before export\n * would have collapsed it, and would otherwise emit a blank duplicate. */\nexport function upsertRichestSnapshot(\n snapshots: ActionSnapshot[],\n snap: ActionSnapshot\n): void {\n const idx = snapshots.findIndex((s) => s.timestamp === snap.timestamp)\n if (idx === -1) {\n snapshots.push(snap)\n return\n }\n mergeSnapshotInto(snapshots[idx]!, snap)\n}\n\nexport function buildSnapshotResources(\n rawSnapshots: ActionSnapshot[],\n pageId: string\n): TraceZipResource[] {\n const snapshots = collapseByTimestamp(rawSnapshots)\n const out: TraceZipResource[] = []\n for (const snap of snapshots) {\n const base = `${pageId}-${snap.timestamp}`\n if (snap.screenshot) {\n out.push({\n resourceName: `${base}.jpeg`,\n data: Buffer.from(snap.screenshot, 'base64')\n })\n }\n if (snap.elements && snap.elements.length) {\n out.push({\n resourceName: `${base}-elements.json`,\n data: Buffer.from(JSON.stringify(snap.elements), 'utf8')\n })\n }\n if (snap.snapshotText) {\n out.push({\n resourceName: `${base}-snapshot.txt`,\n data: Buffer.from(snap.snapshotText, 'utf8')\n })\n }\n }\n return out\n}\n\nfunction frameForSnapshot(\n snap: ActionSnapshot,\n pageId: string,\n timestamp: number,\n viewport: { width: number; height: number }\n): ScreencastFrameEvent {\n const base = `${pageId}-${snap.timestamp}`\n const frame: ScreencastFrameEvent = {\n type: 'screencast-frame',\n pageId,\n sha1: `${base}.jpeg`,\n width: viewport.width,\n height: viewport.height,\n timestamp\n }\n if (snap.elements && snap.elements.length) {\n frame.elements = `${base}-elements.json`\n }\n if (snap.snapshotText) {\n frame.snapshot = `${base}-snapshot.txt`\n }\n return frame\n}\n\n/** Filmstrip events; the first snapshot is re-anchored to t=0 for pre-interaction state. */\nexport function buildFilmstripEvents(\n rawSnapshots: ActionSnapshot[],\n pageId: string,\n wallTime: number,\n viewport: { width: number; height: number }\n): ScreencastFrameEvent[] {\n const snapshots = collapseByTimestamp(rawSnapshots)\n const firstSnap = snapshots.find((s) => s.screenshot)\n const events: ScreencastFrameEvent[] = []\n if (firstSnap) {\n events.push(frameForSnapshot(firstSnap, pageId, 0, viewport))\n }\n for (const snap of snapshots) {\n if (snap === firstSnap || !snap.screenshot) {\n continue\n }\n events.push(\n frameForSnapshot(\n snap,\n pageId,\n Math.max(0, snap.timestamp - wallTime),\n viewport\n )\n )\n }\n return events\n}\n","/**\n * Dense screencast filmstrip for trace mode. Turns the recorder's continuous\n * frame buffer into `screencast-frame` events plus content-addressed image\n * resources, so the trace player scrubs smooth playback instead of one frame\n * per action.\n *\n * Two independent bounds keep the cost in check: thinning caps the number of\n * emitted events (min inter-frame gap + drop consecutive byte-identical frames\n * + even downsample to a hard cap), and content-addressing dedupes the image\n * bytes (identical frames — a static wait polled repeatedly — collapse to one\n * resource). Framework-agnostic: adapters feed `recorder.frames`; the recorder\n * itself (CDP push on Chrome, screenshot polling elsewhere) lives per-adapter.\n */\n\nimport { createHash } from 'node:crypto'\nimport type { ScreencastFrame } from '@wdio/devtools-shared'\nimport type { TraceZipResource } from './trace-zip-writer.js'\nimport type { ScreencastFrameEvent } from './trace-snapshots.js'\n\nexport interface DenseScreencastOptions {\n /** Hard cap on emitted frame events (default 600). */\n maxFrames?: number\n /** Minimum ms between kept frames (default 100 ≈ 10 fps). */\n minFrameIntervalMs?: number\n}\n\nconst DEFAULT_MAX_FRAMES = 600\nconst DEFAULT_MIN_INTERVAL_MS = 100\n\n/**\n * A run's whole filmstrip: the frames drained from sessions that have already\n * ended plus the one still recording. Both sources are always needed — a\n * mid-run per-spec/per-test flush fires before the live recorder is drained, so\n * its frames exist only on the recorder, while at the final write the recorder\n * is already gone. Reading either alone silently truncates the filmstrip.\n */\nexport function accumulatedScreencastFrames(\n drained: readonly ScreencastFrame[],\n recording: { readonly frames: readonly ScreencastFrame[] } | undefined\n): ScreencastFrame[] {\n return [...drained, ...(recording?.frames ?? [])]\n}\n\n/**\n * Thin a continuous frame buffer down to a bounded event set. Keeps a frame\n * only when it is at least `minFrameIntervalMs` after the last kept frame and\n * its bytes differ from the last kept frame (a static run collapses to its\n * first frame — the player holds it until the next change, which is the big win\n * for polling mode where every poll re-shoots an unchanged page). A final even\n * downsample enforces the hard cap while always keeping the first and last\n * frames so the timeline still spans the full run.\n */\nexport function thinScreencastFrames(\n frames: readonly ScreencastFrame[],\n options: DenseScreencastOptions = {}\n): ScreencastFrame[] {\n const minInterval = options.minFrameIntervalMs ?? DEFAULT_MIN_INTERVAL_MS\n const maxFrames = options.maxFrames ?? DEFAULT_MAX_FRAMES\n\n const kept: ScreencastFrame[] = []\n let lastKeptTs = -Infinity\n let lastData: string | undefined\n for (const frame of frames) {\n if (frame.timestamp - lastKeptTs < minInterval) {\n continue\n }\n if (frame.data === lastData) {\n continue\n }\n kept.push(frame)\n lastKeptTs = frame.timestamp\n lastData = frame.data\n }\n\n if (kept.length <= maxFrames) {\n return kept\n }\n if (maxFrames <= 1) {\n return kept.length ? [kept[0]!] : []\n }\n const out: ScreencastFrame[] = []\n const step = (kept.length - 1) / (maxFrames - 1)\n for (let i = 0; i < maxFrames; i++) {\n out.push(kept[Math.round(i * step)]!)\n }\n return out\n}\n\n/**\n * Build the dense filmstrip: `screencast-frame` events (offsets rebased against\n * `wallTime`, matching the sparse filmstrip) and their image resources named by\n * content hash so byte-identical frames share a single resource. Adapters that\n * don't record frames pass an empty array and get an empty result — byte-stable\n * with today's output.\n */\nexport function buildDenseScreencast(\n frames: readonly ScreencastFrame[],\n pageId: string,\n wallTime: number,\n viewport: { width: number; height: number },\n options: DenseScreencastOptions = {}\n): { events: ScreencastFrameEvent[]; resources: TraceZipResource[] } {\n const events: ScreencastFrameEvent[] = []\n const resources: TraceZipResource[] = []\n const seen = new Set<string>()\n for (const frame of thinScreencastFrames(frames, options)) {\n // One malformed frame must not cost the run its whole trace: a screenshot\n // probe that answered a driver error object instead of base64 reached here\n // and `Buffer.from` threw inside the export, losing every other event too.\n if (typeof frame.data !== 'string' || !frame.data) {\n continue\n }\n const data = Buffer.from(frame.data, 'base64')\n const sha1 = createHash('sha1').update(data).digest('hex')\n const resourceName = `${sha1}.jpeg`\n if (!seen.has(sha1)) {\n seen.add(sha1)\n resources.push({ resourceName, data })\n }\n events.push({\n type: 'screencast-frame',\n pageId,\n sha1: resourceName,\n width: viewport.width,\n height: viewport.height,\n timestamp: Math.max(0, frame.timestamp - wallTime)\n })\n }\n return { events, resources }\n}\n","// Image-backed frame-snapshot synthesis: each action screenshot becomes a\n// minimal DOM document so standard trace viewers render the action pane.\n// Compatibility shim until real DOM snapshots are captured.\n\nimport { imageMime, type ActionSnapshot } from '@wdio/devtools-shared'\n\nconst SNAPSHOT_DOCTYPE = 'html'\nconst FALLBACK_FRAME_URL = 'about:blank'\nconst BODY_STYLE = 'margin:0'\nconst IMAGE_STYLE = 'display:block;width:100vw;height:100vh;object-fit:contain'\n\n/** Serialized DOM node: text, or a [TAG, attributes, ...children] tuple. */\nexport type FrameSnapshotNode =\n string | [string, Record<string, string>, ...FrameSnapshotNode[]]\n\nexport interface FrameSnapshotResourceOverride {\n url: string\n sha1?: string\n ref?: number\n}\n\nexport interface FrameSnapshot {\n callId: string\n snapshotName: string\n pageId: string\n frameId: string\n frameUrl: string\n doctype?: string\n html: FrameSnapshotNode\n viewport: { width: number; height: number }\n timestamp: number\n wallTime: number\n collectionTime: number\n resourceOverrides: FrameSnapshotResourceOverride[]\n isMainFrame: boolean\n}\n\nexport interface FrameSnapshotEvent {\n type: 'frame-snapshot'\n snapshot: FrameSnapshot\n}\n\nexport interface FrameSnapshotRef {\n callId: string\n snapshotName: string\n snapshot: ActionSnapshot\n}\n\n/** Correlates captured screenshots to callIds as the exporter assigns them. */\nexport class FrameSnapshotIndex {\n #byTimestamp = new Map<number, ActionSnapshot>()\n // Element rects by timestamp, indexed independently of the screenshot gate so\n // A8 input points resolve even when the frame carries no screenshot.\n #elementsByTimestamp = new Map<number, unknown[]>()\n #refs: FrameSnapshotRef[] = []\n #lastName?: string\n\n constructor(snapshots: ActionSnapshot[]) {\n for (const snap of snapshots) {\n if (snap.elements) {\n const currentEls = this.#elementsByTimestamp.get(snap.timestamp)\n if (!currentEls || snap.elements.length > currentEls.length) {\n this.#elementsByTimestamp.set(snap.timestamp, snap.elements)\n }\n }\n if (!snap.screenshot) {\n continue\n }\n const current = this.#byTimestamp.get(snap.timestamp)\n // Same-timestamp duplicates keep the richest capture (dedupe parity).\n if (\n !current ||\n snap.screenshot.length > (current.screenshot?.length ?? 0)\n ) {\n this.#byTimestamp.set(snap.timestamp, snap)\n }\n }\n }\n\n /** Captured element rects at a command's completion timestamp, if any. */\n elementsAt(timestamp: number): unknown[] | undefined {\n return this.#elementsByTimestamp.get(timestamp)\n }\n\n /** Snapshot name representing the page state before the next action. */\n beforeName(): string | undefined {\n return this.#lastName\n }\n\n /**\n * The capture describing the page when this command completed: the one at its\n * exact timestamp, else the most recent one before it.\n *\n * Non-consuming, and falls back rather than returning nothing, because several\n * actions legitimately share one page state. Nightwatch emits its native\n * assertion rows in a batch whose execution windows collapse onto the same\n * instant, so handing the capture to whichever claimed first left the rest of\n * the batch with no DOM, no a11y tree and no screenshot in the action pane.\n * And an assertion reads the page rather than changing it, so it takes no\n * capture of its own and correctly inherits the preceding action's.\n */\n claimAfter(timestamp: number, callId: string): string | undefined {\n const snap =\n this.#byTimestamp.get(timestamp) ?? this.#latestAtOrBefore(timestamp)\n if (!snap) {\n return undefined\n }\n const snapshotName = `after@${callId}`\n this.#refs.push({ callId, snapshotName, snapshot: snap })\n this.#lastName = snapshotName\n return snapshotName\n }\n\n #latestAtOrBefore(timestamp: number): ActionSnapshot | undefined {\n let best: ActionSnapshot | undefined\n for (const [ts, snap] of this.#byTimestamp) {\n if (ts <= timestamp && (!best || ts > best.timestamp)) {\n best = snap\n }\n }\n return best\n }\n\n refs(): FrameSnapshotRef[] {\n return this.#refs\n }\n}\n\nfunction frameIdForPage(pageId: string): string {\n const suffix = pageId.startsWith('page@')\n ? pageId.slice('page@'.length)\n : pageId\n return `frame@${suffix}`\n}\n\nfunction imageDocument(snap: ActionSnapshot): FrameSnapshotNode {\n const screenshot = snap.screenshot ?? ''\n return [\n 'HTML',\n {},\n ['HEAD', {}, ['BASE', { href: snap.url ?? FALLBACK_FRAME_URL }]],\n [\n 'BODY',\n { style: BODY_STYLE },\n [\n 'IMG',\n {\n src: `data:${imageMime(screenshot)};base64,${screenshot}`,\n style: IMAGE_STYLE\n }\n ]\n ]\n ]\n}\n\n/** One frame-snapshot event per claimed screenshot, viewer-shape exact. */\nexport function buildImageFrameSnapshots(\n refs: FrameSnapshotRef[],\n pageId: string,\n wallTime: number,\n viewport: { width: number; height: number }\n): FrameSnapshotEvent[] {\n const frameId = frameIdForPage(pageId)\n return refs.map((ref) => ({\n type: 'frame-snapshot' as const,\n snapshot: {\n callId: ref.callId,\n snapshotName: ref.snapshotName,\n pageId,\n frameId,\n frameUrl: ref.snapshot.url ?? FALLBACK_FRAME_URL,\n doctype: SNAPSHOT_DOCTYPE,\n html: imageDocument(ref.snapshot),\n viewport: { width: viewport.width, height: viewport.height },\n timestamp: Math.max(0, ref.snapshot.timestamp - wallTime),\n wallTime: ref.snapshot.timestamp,\n collectionTime: 0,\n resourceOverrides: [],\n isMainFrame: true\n }\n }))\n}\n","import { createHash } from 'node:crypto'\n\n/** Hex SHA-1 used for content-addressed trace resources. */\nexport function sha1Hex(data: Buffer | string): string {\n return createHash('sha1').update(data).digest('hex')\n}\n","// Source files and per-action call stacks for the trace: sources are written\n// as path-addressed `src@<sha1(path)>.txt` resources, and each action's\n// captured `<file>:<line>[:<column>]` callSource becomes an inline stack frame.\n\nimport { sha1Hex } from './sha1.js'\nimport type { TraceZipResource } from './trace-zip-writer.js'\n\nexport interface StackFrame {\n file: string\n line: number\n column: number\n}\n\n// Test files are small; anything bigger is generated/bundled and not worth shipping.\nconst MAX_SOURCE_BYTES = 2 * 1024 * 1024\n\nexport function sourceResourceName(filePath: string): string {\n return `src@${sha1Hex(filePath)}.txt`\n}\n\n// Splits one trailing `:<digits>` segment; `sep <= 0` keeps Windows drive\n// letters (`C:...`) and colon-less paths intact.\nfunction splitNumericSuffix(value: string): { rest: string; num?: number } {\n const sep = value.lastIndexOf(':')\n if (sep <= 0) {\n return { rest: value }\n }\n const digits = value.slice(sep + 1)\n if (!/^\\d+$/.test(digits)) {\n return { rest: value }\n }\n return { rest: value.slice(0, sep), num: Number(digits) }\n}\n\n/** Inline stack from a captured `<file>:<line>[:<column>]` callSource. */\nexport function callSourceToStack(\n callSource?: string\n): StackFrame[] | undefined {\n if (!callSource) {\n return undefined\n }\n const last = splitNumericSuffix(callSource)\n if (last.num === undefined) {\n return [{ file: callSource, line: 0, column: 0 }]\n }\n const prev = splitNumericSuffix(last.rest)\n if (prev.num === undefined) {\n return [{ file: last.rest, line: last.num, column: 0 }]\n }\n return [{ file: prev.rest, line: prev.num, column: last.num }]\n}\n\nexport function buildSourceResources(\n sources: Record<string, string>\n): TraceZipResource[] {\n const out: TraceZipResource[] = []\n for (const [filePath, text] of Object.entries(sources)) {\n const data = Buffer.from(text, 'utf8')\n if (data.byteLength > MAX_SOURCE_BYTES) {\n continue\n }\n out.push({ resourceName: sourceResourceName(filePath), data })\n }\n return out\n}\n","// The ordered open-group path for a command — outermost first — that the action\n// exporter's group stack turns into balanced nested Tracing.tracingGroup\n// markers. The path is the test's ancestry (feature/scenario/suite, from the\n// suite-metadata walk) + the test itself + its step, when a stepUid is set.\n\nimport type { CommandLog, TestMetadataMap } from '@wdio/devtools-shared'\n\nexport interface GroupNode {\n uid: string\n title: string\n}\n\n/** A node's own name, with an enclosing group's title stripped off the front.\n * Some runners report a test's FULL title (`<suite> <test>`), so nesting it\n * under its own suite renders the suite name twice. Display only — the entry's\n * stored title still keys artifacts and slices. Falls back to the full title\n * when stripping would leave nothing, so a test named after its suite keeps a\n * label. */\nfunction ownTitle(title: string, parent: GroupNode | undefined): string {\n if (!parent || !title.startsWith(parent.title)) {\n return title\n }\n return title.slice(parent.title.length).trim() || title\n}\n\nexport function buildGroupPath(\n cmd: CommandLog,\n testMetadata?: TestMetadataMap\n): GroupNode[] {\n const path: GroupNode[] = []\n if (cmd.testUid) {\n const entry = testMetadata?.get(cmd.testUid)\n for (const ancestor of entry?.ancestry ?? []) {\n path.push({\n uid: ancestor.uid,\n title: ownTitle(ancestor.title, path[path.length - 1])\n })\n }\n path.push({\n uid: cmd.testUid,\n title: ownTitle(entry?.title ?? cmd.testUid, path[path.length - 1])\n })\n }\n if (cmd.stepUid) {\n const stepEntry = testMetadata?.get(cmd.stepUid)\n path.push({\n uid: cmd.stepUid,\n title: ownTitle(stepEntry?.title ?? cmd.stepUid, path[path.length - 1])\n })\n }\n return path\n}\n","// Builds the before/after action events of the exported trace stream,\n// including tracingGroup test boundaries and frame-snapshot ref stamping.\n\nimport type {\n CommandLog,\n CommandNode,\n TestMetadataMap\n} from '@wdio/devtools-shared'\nimport {\n collapsedAssertResult,\n commandFailure,\n locatorsMatch,\n nestCommands,\n retriedRows,\n POINTABLE_METHODS\n} from '@wdio/devtools-shared'\nimport {\n ASSERT_ACTION_CLASS,\n formatActionTitle,\n mapCommandToAction,\n FILL_METHODS,\n type TraceAction\n} from '@wdio/devtools-shared'\nimport { callSourceToStack, type StackFrame } from './trace-sources.js'\nimport type { FrameSnapshotIndex } from './trace-frame-snapshots.js'\nimport { buildGroupPath } from './trace-hierarchy.js'\n\nexport interface BeforeEvent {\n type: 'before'\n callId: string\n startTime: number\n class: string\n method: string\n pageId: string\n params: Record<string, unknown>\n title: string\n /** Trace-viewer API name (e.g. 'page.goBack', 'element.click'). */\n apiName: string\n /** CallId of the action whose execution issued this one, else of the Tracing.tracingGroup that wraps it (if any). */\n parentId?: string\n /** User-code frame the command was issued from, when captured. */\n stack?: StackFrame[]\n /** Frame-snapshot name rendered as the action's before state. */\n beforeSnapshot?: string\n}\n\nexport interface AfterEvent {\n type: 'after'\n callId: string\n endTime: number\n error?: { message: string }\n /** A poll's error under a wait that passed: kept for the player, never read as a failed action. */\n retriedError?: { message: string }\n /** Command return value (e.g. the text getText resolved to). */\n result?: unknown\n /** CallId of the Tracing.tracingGroup that wraps this action (if any). */\n parentId?: string\n /** Frame-snapshot name rendered as the action's after state. */\n afterSnapshot?: string\n /** Page-coordinate hit point (centre of the matched element) for pointer\n * actions — drives the player's click marker + timeline pointer glyph. */\n point?: { x: number; y: number }\n}\n\n// Serialized command results over this size are dropped from the trace — a\n// huge execute() return shouldn't bloat every action line.\nconst MAX_RESULT_BYTES = 64 * 1024\n\n/** JSON-safe command result within the size cap; undefined when absent,\n * oversized, or not serializable. */\nfunction serializableResult(result: unknown): unknown {\n if (result === undefined) {\n return undefined\n }\n try {\n const json = JSON.stringify(result)\n if (json === undefined || json.length > MAX_RESULT_BYTES) {\n return undefined\n }\n return JSON.parse(json)\n } catch {\n return undefined\n }\n}\n\nexport type ActionEvent = BeforeEvent | AfterEvent\n\ninterface ActionStream {\n events: ActionEvent[]\n prevEndMs: number\n callCounter: number\n /** Currently-open tracingGroup path, outermost first (feature→scenario→step).\n * Actions nest under the innermost entry. */\n openGroups: { uid: string; callId: string }[]\n}\n\n// Assert params: a collapsed result's actual/expected win over the positional\n// args, which are only right for node:assert-style `[actual, expected]` calls.\n// Otherwise node:assert positional order (actual, expected, message?),\n// plus a numeric echo of the raw args so the reader's paramsToArgs inverse\n// reconstructs the original arg list without assert-specific knowledge.\nfunction buildAssertParams(cmd: CommandLog): Record<string, unknown> {\n const params: Record<string, unknown> = Object.fromEntries(\n cmd.args.map((arg, index) => [String(index), arg])\n )\n const [actual, expected, message] = cmd.args\n const collapsed = collapsedAssertResult(cmd.result)\n const semantic = {\n actual: collapsed?.actual ?? actual,\n expected: collapsed?.expected ?? expected,\n message: collapsed?.message ?? message\n }\n for (const [key, value] of Object.entries(semantic)) {\n if (value !== undefined) {\n params[key] = value\n }\n }\n return params\n}\n\n// Semantic params from positional args (selector/value/url), falling back to\n// index keys; the reader's paramsToArgs is the inverse.\nfunction buildActionParams(\n action: TraceAction,\n rawArgs: unknown[]\n): Record<string, unknown> {\n const isValueMethod = FILL_METHODS.has(action.method)\n if (action.class === 'Element' && isValueMethod && rawArgs.length >= 2) {\n return { selector: rawArgs[0], value: rawArgs[1] }\n }\n if (action.class === 'Element' && isValueMethod && rawArgs.length === 1) {\n return { value: rawArgs[0] }\n }\n if (\n action.class === 'Element' &&\n rawArgs.length === 1 &&\n typeof rawArgs[0] === 'string'\n ) {\n return { selector: rawArgs[0] }\n }\n if (rawArgs.length === 1 && typeof rawArgs[0] === 'string') {\n return { url: rawArgs[0] }\n }\n return Object.fromEntries(rawArgs.map((a, i) => [String(i), a]))\n}\n\nfunction closeGroupsFrom(stream: ActionStream, from: number): void {\n // Innermost-first so nested after events stay balanced.\n for (let i = stream.openGroups.length - 1; i >= from; i--) {\n stream.events.push({\n type: 'after',\n callId: stream.openGroups[i].callId,\n endTime: stream.prevEndMs\n })\n }\n stream.openGroups.length = Math.max(0, from)\n}\n\n// Diff the command's desired group path (feature→scenario→step, or just the\n// test) against the open stack: close the diverged tail, then open the new tail\n// with parentId chaining so the reader nests them. Actions then reference the\n// innermost open group. A path of length ≤1 with no ancestry/step reproduces\n// the previous single-group-per-test output.\nfunction syncGroups(\n stream: ActionStream,\n cmd: CommandLog,\n pageId: string,\n wallTime: number,\n testMetadata?: TestMetadataMap\n): void {\n const desired = buildGroupPath(cmd, testMetadata)\n let common = 0\n while (\n common < stream.openGroups.length &&\n common < desired.length &&\n stream.openGroups[common].uid === desired[common].uid\n ) {\n common++\n }\n closeGroupsFrom(stream, common)\n for (let i = common; i < desired.length; i++) {\n stream.callCounter++\n const callId = `call@${stream.callCounter}`\n const parentId = stream.openGroups[i - 1]?.callId\n const groupBefore: BeforeEvent = {\n type: 'before',\n callId,\n startTime: Math.max(\n stream.prevEndMs,\n (cmd.startTime ?? cmd.timestamp) - wallTime\n ),\n class: 'Tracing',\n method: 'tracingGroup',\n pageId,\n params: { name: desired[i].title },\n title: desired[i].title,\n apiName: 'tracing.tracingGroup'\n }\n if (parentId) {\n groupBefore.parentId = parentId\n }\n stream.events.push(groupBefore)\n stream.openGroups.push({ uid: desired[i].uid, callId })\n }\n}\n\nfunction buildParamsAndTitle(\n action: TraceAction,\n cmd: CommandLog\n): { params: Record<string, unknown>; title: string } {\n const isAssert = action.class === ASSERT_ACTION_CLASS\n const params = isAssert\n ? buildAssertParams(cmd)\n : buildActionParams(action, cmd.args)\n // The element this command targeted, under a key distinct from `selector` so\n // it never feeds `paramsToArgs` — lets the reader restore CommandLog.selector\n // for the player's overlay even when args carry an assertion's expected value.\n if (cmd.selector && params.locator === undefined) {\n params.locator = cmd.selector\n }\n return {\n params,\n title: formatActionTitle(\n action,\n cmd.args,\n params,\n isAssert ? cmd.command : undefined\n )\n }\n}\n\n/** Centre of the element a pointer action matched, from the captured element\n * rects at the command's completion. Undefined for non-pointer actions, a\n * non-string selector, or a selector absent from the captured elements (e.g. a\n * WDIO text/xpath locator `getSelector` didn't reproduce). A runner that acts\n * through a resolved element handle leaves nothing selector-shaped in `args`\n * (Selenium: the handle is the receiver, the args hold only the typed value), so\n * the row's recorded locator is read first. */\nfunction resolveActionPoint(\n action: TraceAction,\n cmd: CommandLog,\n snapshotIndex?: FrameSnapshotIndex\n): { x: number; y: number } | undefined {\n if (action.class !== 'Element' || !POINTABLE_METHODS.has(action.method)) {\n return undefined\n }\n const selector = cmd.selector ?? cmd.args?.[0]\n if (typeof selector !== 'string') {\n return undefined\n }\n const elements = snapshotIndex?.elementsAt(cmd.timestamp)\n for (const el of elements ?? []) {\n // Narrow the unknown element record at the boundary (element-scripts shape).\n const e = el as {\n selector?: string\n boundingBox?: { x: number; y: number; width: number; height: number }\n }\n if (e.selector && locatorsMatch(e.selector, selector) && e.boundingBox) {\n const bb = e.boundingBox\n return { x: bb.x + bb.width / 2, y: bb.y + bb.height / 2 }\n }\n }\n return undefined\n}\n\nfunction buildAfterEvent(\n cmd: CommandLog,\n action: TraceAction,\n callId: string,\n endMs: number,\n ctx: EmitContext\n): AfterEvent {\n const { snapshotIndex } = ctx\n const afterEvent: AfterEvent = { type: 'after', callId, endTime: endMs }\n const error = commandFailure(cmd, action)\n if (error && ctx.retried.has(cmd)) {\n afterEvent.retriedError = error\n } else if (error) {\n afterEvent.error = error\n }\n const result = serializableResult(cmd.result)\n if (result !== undefined) {\n afterEvent.result = result\n }\n const afterName = snapshotIndex?.claimAfter(cmd.timestamp, callId)\n if (afterName) {\n afterEvent.afterSnapshot = afterName\n }\n const point = resolveActionPoint(action, cmd, snapshotIndex)\n if (point) {\n afterEvent.point = point\n }\n return afterEvent\n}\n\nfunction pushBefore(\n stream: ActionStream,\n cmd: CommandLog,\n action: TraceAction,\n pageId: string,\n wallTime: number,\n parentCallId: string | undefined,\n snapshotIndex?: FrameSnapshotIndex\n): { callId: string; startMs: number } {\n stream.callCounter++\n const callId = `call@${stream.callCounter}`\n // Command invocation timestamp, falling back to completion when absent.\n const rawStartMs = (cmd.startTime ?? cmd.timestamp) - wallTime\n // Floor at prevEndMs to prevent visual overlap with the previous action.\n const startMs = Math.max(stream.prevEndMs, rawStartMs)\n const { params, title } = buildParamsAndTitle(action, cmd)\n const beforeEvent: BeforeEvent = {\n type: 'before',\n callId,\n startTime: startMs,\n class: action.class,\n method: action.method,\n pageId,\n params,\n title,\n apiName: `${action.class.toLowerCase()}.${action.method}`,\n parentId:\n parentCallId ?? stream.openGroups[stream.openGroups.length - 1]?.callId\n }\n const stack = callSourceToStack(cmd.callSource)\n if (stack) {\n beforeEvent.stack = stack\n }\n const beforeName = snapshotIndex?.beforeName()\n if (beforeName) {\n beforeEvent.beforeSnapshot = beforeName\n }\n stream.events.push(beforeEvent)\n return { callId, startMs }\n}\n\ninterface EmitContext {\n pageId: string\n wallTime: number\n snapshotIndex?: FrameSnapshotIndex\n retried: ReadonlySet<CommandLog>\n}\n\n// A row's before, then the rows it issued (nested under its callId), then its after: the after closes once the last\n// child has, so the player draws the children inside the parent's span. A row the vocabulary drops passes its\n// children up to the level it would have held.\nfunction emitNode(\n stream: ActionStream,\n node: CommandNode,\n ctx: EmitContext,\n parentCallId?: string\n): void {\n const cmd = node.command\n const action = mapCommandToAction(cmd.command)\n if (!action) {\n for (const child of node.children) {\n emitNode(stream, child, ctx, parentCallId)\n }\n return\n }\n const { callId, startMs } = pushBefore(\n stream,\n cmd,\n action,\n ctx.pageId,\n ctx.wallTime,\n parentCallId,\n ctx.snapshotIndex\n )\n if (node.children.length) {\n stream.prevEndMs = startMs\n for (const child of node.children) {\n emitNode(stream, child, ctx, callId)\n }\n }\n // +1ms minimum duration so an `after` never precedes its parsed `before`.\n const endMs = Math.max(\n startMs + 1,\n cmd.timestamp - ctx.wallTime,\n node.children.length ? stream.prevEndMs : 0\n )\n stream.events.push(buildAfterEvent(cmd, action, callId, endMs, ctx))\n stream.prevEndMs = endMs\n}\n\nexport function buildActionEvents(\n commands: CommandLog[],\n pageId: string,\n wallTime: number,\n testMetadata?: TestMetadataMap,\n snapshotIndex?: FrameSnapshotIndex\n): ActionEvent[] {\n const stream: ActionStream = {\n events: [],\n prevEndMs: 0,\n callCounter: 0,\n openGroups: []\n }\n // Process in chronological order, not insertion order. Deferred rows (e.g.\n // Nightwatch native asserts, finalized in one batch at test-end) are appended\n // late but carry their real call-time `startTime`; since pushBefore floors\n // each start at the running prevEndMs, out-of-order input would clamp those\n // late rows to the end of the timeline (clustering them after the last real\n // command). A stable sort by start time restores true positions. `sequence`\n // then breaks ties the millisecond clock can't: a deferred row issued\n // microseconds before the command that follows it reads as the same\n // `startTime`, and insertion order put it after — which is how an assert that\n // ran on the secure page landed below the logout click it preceded. Each\n // command owns its own before/after pair, so pairing is unaffected.\n const ordered = [...commands].sort(\n (a, b) =>\n (a.startTime ?? a.timestamp) - (b.startTime ?? b.timestamp) ||\n (a.sequence ?? 0) - (b.sequence ?? 0)\n )\n const ctx: EmitContext = {\n pageId,\n wallTime,\n snapshotIndex,\n retried: retriedRows(ordered)\n }\n for (const node of nestCommands(ordered)) {\n if (hasAction(node)) {\n syncGroups(stream, node.command, pageId, wallTime, testMetadata)\n }\n emitNode(stream, node, ctx)\n }\n closeGroupsFrom(stream, 0)\n return stream.events\n}\n\n// A dropped row with no mapped descendant emits nothing, so it must not open or close a test group either.\nfunction hasAction(node: CommandNode): boolean {\n return (\n Boolean(mapCommandToAction(node.command.command)) ||\n node.children.some(hasAction)\n )\n}\n","// Convert the existing NetworkRequest shape into trace format\n// `resource-snapshot` NDJSON entries (HAR-flavoured) for trace.zip.\n\nimport type {\n NetworkCaptureSource,\n NetworkRequest,\n NetworkTimings,\n RequestType\n} from '@wdio/devtools-shared'\n\n/** Bodies below this inline as plain `text`; larger ones rely on `_sha1` only. */\nconst INLINE_BODY_TEXT_MAX_BYTES = 8 * 1024\n\n/** HAR's own marker for a phase that does not apply. */\nconst HAR_PHASE_NOT_APPLICABLE = -1\n\nexport interface HarTimings {\n blocked?: number\n dns?: number\n connect?: number\n ssl?: number\n send: number\n wait: number\n receive: number\n}\n\nexport interface HarResponseContent {\n size: number\n mimeType: string\n /** Inline body fallback for small textual bodies. */\n text?: string\n /** Content-addressed body resource name under `resources/`. */\n _sha1?: string\n}\n\nexport interface ResourceSnapshotEntry {\n type: 'resource-snapshot'\n snapshot: {\n startedDateTime: string\n time: number\n request: {\n method: string\n url: string\n httpVersion: string\n cookies: unknown[]\n headers: { name: string; value: string }[]\n queryString: { name: string; value: string }[]\n headersSize: number\n bodySize: number\n }\n response: {\n status: number\n statusText: string\n httpVersion: string\n cookies: unknown[]\n headers: { name: string; value: string }[]\n content: HarResponseContent\n redirectURL: string\n headersSize: number\n bodySize: number\n }\n cache: Record<string, never>\n timings: HarTimings\n /** The capture's own classification, which `content.mimeType` cannot\n * carry for a source that saw no response headers. */\n _resourceType: RequestType\n _source?: NetworkCaptureSource\n }\n}\n\n/** The phases a capture measured, in HAR's shape; a capture that measured\n * none reports the whole duration as `wait`, which is all it knows. */\nfunction toHarTimings(\n timings: NetworkTimings | undefined,\n duration: number\n): HarTimings {\n if (!timings) {\n return { send: 0, wait: Math.max(0, duration), receive: 0 }\n }\n return {\n blocked: timings.blocked ?? HAR_PHASE_NOT_APPLICABLE,\n dns: timings.dns ?? HAR_PHASE_NOT_APPLICABLE,\n connect: timings.connect ?? HAR_PHASE_NOT_APPLICABLE,\n ssl: timings.ssl ?? HAR_PHASE_NOT_APPLICABLE,\n send: timings.send ?? 0,\n wait: timings.wait ?? 0,\n receive: timings.receive ?? 0\n }\n}\n\nfunction toHeaderArray(\n h: Record<string, string> | undefined\n): { name: string; value: string }[] {\n if (!h) {\n return []\n }\n return Object.entries(h).map(([name, value]) => ({ name, value }))\n}\n\nfunction toQueryString(url: string): { name: string; value: string }[] {\n try {\n const u = new URL(url)\n const out: { name: string; value: string }[] = []\n u.searchParams.forEach((value, name) => out.push({ name, value }))\n return out\n } catch {\n return []\n }\n}\n\nfunction toHarContent(\n entry: NetworkRequest,\n mimeType: string,\n bodySha1: string | undefined\n): HarResponseContent {\n const content: HarResponseContent = { size: entry.size ?? 0, mimeType }\n const body = entry.responseBody\n if (body === undefined) {\n return content\n }\n if (Buffer.byteLength(body, 'utf8') < INLINE_BODY_TEXT_MAX_BYTES) {\n content.text = body\n }\n if (bodySha1) {\n content._sha1 = bodySha1\n }\n return content\n}\n\nexport function networkRequestToHar(\n entry: NetworkRequest,\n opts: { bodySha1?: string } = {}\n): ResourceSnapshotEntry {\n const startedDateTime = new Date(entry.timestamp).toISOString()\n const duration =\n entry.time ?? (entry.endTime ?? entry.startTime) - entry.startTime\n const status = entry.response?.status ?? entry.status ?? 0\n const mimeType = entry.response?.mimeType ?? ''\n const responseHeaders = entry.response?.headers ?? entry.responseHeaders\n return {\n type: 'resource-snapshot',\n snapshot: {\n startedDateTime,\n time: Math.max(0, duration),\n request: {\n method: entry.method,\n url: entry.url,\n httpVersion: 'HTTP/1.1',\n cookies: [],\n headers: toHeaderArray(entry.requestHeaders ?? entry.headers),\n queryString: toQueryString(entry.url),\n headersSize: -1,\n bodySize: entry.requestBody ? entry.requestBody.length : -1\n },\n response: {\n status,\n statusText: entry.statusText ?? '',\n httpVersion: 'HTTP/1.1',\n cookies: [],\n headers: toHeaderArray(responseHeaders),\n content: toHarContent(entry, mimeType, opts.bodySha1),\n redirectURL: '',\n headersSize: -1,\n bodySize: entry.size ?? -1\n },\n cache: {},\n timings: toHarTimings(entry.timings, duration),\n _resourceType: entry.type,\n ...(entry.source ? { _source: entry.source } : {})\n }\n }\n}\n","// Thin yazl wrapper that packages a trace into a single Buffer.\n// Ported from Vince Graics' PR #209.\n\nimport { TRACE_ZIP_ENTRIES } from '@wdio/devtools-shared'\nimport yazl from 'yazl'\n\nexport interface TraceZipResource {\n /** Path inside the zip, e.g. `resources/page@xxx-12345.jpeg`. */\n resourceName: string\n data: Buffer\n}\n\nexport interface TraceZipInputs {\n /** NDJSON action events (one JSON object per line). */\n traceNdjson: string\n /** NDJSON HAR resource-snapshot entries. Empty buffer when omitted. */\n networkNdjson: Buffer\n /** Human/LLM-readable Markdown transcript. */\n transcriptMd?: string\n /** NDJSON DOM mutation stream (one mutation per line). Omitted/empty → no entry. */\n mutationsNdjson?: Buffer\n /** Files written under `resources/` — typically screenshots + element snapshots. */\n resources: TraceZipResource[]\n}\n\nexport function buildTraceZip(inputs: TraceZipInputs): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n const zipFile = new yazl.ZipFile()\n zipFile.addBuffer(\n Buffer.from(inputs.traceNdjson, 'utf8'),\n TRACE_ZIP_ENTRIES.trace\n )\n zipFile.addBuffer(inputs.networkNdjson, TRACE_ZIP_ENTRIES.network)\n if (inputs.transcriptMd) {\n zipFile.addBuffer(\n Buffer.from(inputs.transcriptMd, 'utf8'),\n TRACE_ZIP_ENTRIES.transcript\n )\n }\n if (inputs.mutationsNdjson?.length) {\n zipFile.addBuffer(inputs.mutationsNdjson, TRACE_ZIP_ENTRIES.mutations)\n }\n for (const resource of inputs.resources) {\n zipFile.addBuffer(\n resource.data,\n `${TRACE_ZIP_ENTRIES.resourcesDir}/${resource.resourceName}`\n )\n }\n zipFile.end()\n const chunks: Buffer[] = []\n zipFile.outputStream.on('data', (chunk: Buffer) => chunks.push(chunk))\n zipFile.outputStream.on('end', () => resolve(Buffer.concat(chunks)))\n zipFile.outputStream.on('error', reject)\n })\n}\n","// Serialize the captured DOM mutation stream into the trace zip's\n// `trace.mutations` NDJSON entry so the offline player can replay DOM\n// time-travel. Standard trace viewers ignore the unknown entry, so it's\n// compat-safe. Keep-earliest under the byte cap — the initial full-DOM\n// childList and early diffs survive so replay-from-start stays intact; only\n// late mutations drop, and a trailing sentinel records how many.\n\nimport type {\n MutationsTruncationMarker,\n TraceMutation\n} from '@wdio/devtools-shared'\n\n/** Ceiling on the serialized `trace.mutations` payload — keeps archives bounded\n * on mutation-heavy SPAs. Late mutations drop first (replay-from-start holds). */\nexport const MAX_MUTATIONS_NDJSON_BYTES = 50 * 1024 * 1024\n\n/** A full-document anchor rather than an observed diff. `url` is the reliable\n * discriminator: only the collector's `captureCurrentDom` sets it, and neither\n * the MutationObserver serializer nor the synthetic field-state records do. */\nfunction isDomAnchor(mutation: TraceMutation): boolean {\n return mutation.type === 'childList' && mutation.url !== undefined\n}\n\n/**\n * Re-stamp the full-document anchors in a freshly drained batch onto the action\n * that produced the document.\n *\n * An anchor carries the document's own birth time, which lands a few ms *after*\n * the command that navigated there — so the navigating action's row, which ends\n * the moment the command was logged, still resolves to the PREVIOUS page's DOM.\n *\n * `resolve` maps an anchor's own timestamp to the action it belongs to. Deriving\n * it from the anchor rather than from whichever drain collected it is what makes\n * this race-free: several drains compete for a fresh collector's buffer (the\n * first `getTraceData` empties it) and the winner is routinely a later command's\n * post-DOM drain, so trusting the collecting drain mis-credited the anchor.\n *\n * `floor` is the newest timestamp already in the accumulated stream. An anchor is\n * never pulled ahead of mutations belonging to the document it replaces — replay\n * would then apply those stale refs to the new tree — and never pushed later than\n * the page stamped it, so an overshooting attribution is simply ignored.\n */\nexport function reattributeDomAnchors(\n batch: TraceMutation[],\n resolve: (anchorOwnTime: number) => number | undefined,\n floor = 0\n): void {\n let lowerBound = floor\n for (const mutation of batch) {\n if (isDomAnchor(mutation)) {\n const attributed = resolve(mutation.timestamp)\n if (attributed !== undefined) {\n const target = Math.max(attributed, lowerBound)\n if (target < mutation.timestamp) {\n mutation.timestamp = target\n }\n }\n }\n lowerBound = Math.max(lowerBound, mutation.timestamp)\n }\n}\n\nexport interface MutationsNdjsonResult {\n /** NDJSON payload (one mutation per line, optional trailing marker). Empty\n * buffer when there are no mutations. */\n ndjson: Buffer\n truncated: boolean\n /** Count actually written (excludes the dropped tail and the marker line). */\n written: number\n}\n\n/**\n * Serialize mutations to NDJSON under `cap` bytes, keeping the earliest. The\n * first mutation is always emitted (even if it alone exceeds the cap) so the\n * initial full-DOM snapshot is never lost; when any are dropped a\n * `MutationsTruncationMarker` line is appended.\n */\nexport function buildMutationsNdjson(\n mutations: readonly TraceMutation[],\n cap: number = MAX_MUTATIONS_NDJSON_BYTES\n): MutationsNdjsonResult {\n if (!mutations.length) {\n return { ndjson: Buffer.alloc(0), truncated: false, written: 0 }\n }\n const lines: string[] = []\n let bytes = 0\n for (const mutation of mutations) {\n const line = JSON.stringify(mutation)\n // +1 for the '\\n' that will join this line to the previous one.\n const add = Buffer.byteLength(line, 'utf8') + (lines.length ? 1 : 0)\n if (lines.length > 0 && bytes + add > cap) {\n break\n }\n lines.push(line)\n bytes += add\n }\n const written = lines.length\n const dropped = mutations.length - written\n if (dropped > 0) {\n const marker: MutationsTruncationMarker = { __truncated__: true, dropped }\n lines.push(JSON.stringify(marker))\n }\n return {\n ndjson: Buffer.from(lines.join('\\n'), 'utf8'),\n truncated: dropped > 0,\n written\n }\n}\n","// Builds the trace's `transcript.md` — a Markdown step list read by humans and\n// fed to an LLM. Runner-agnostic; the exporter writes whatever this returns.\n\nimport type { CommandLog } from '@wdio/devtools-shared'\nimport {\n formatActionTitle,\n mapCommandToAction,\n retriedRows,\n FILL_METHODS,\n type TraceAction\n} from '@wdio/devtools-shared'\nimport { stripAnsi } from '@wdio/devtools-shared'\n\n/** Render `text` as one numbered markdown list item, indenting every line after\n * the first to the marker's content column (`'1. '` → 3, `'10. '` → 4 — an\n * ordered list's continuation indent tracks the marker width, unlike a\n * bullet's fixed two). A step interpolates captured strings that may hold\n * newlines — a framework error routinely does (expect-webdriverio puts\n * `Expected:` and `Received:` on their own lines) and a typed value can too —\n * and an unindented tail leaves the list, reading as top-level prose\n * unattributed to the step that produced it. */\nfunction asNumberedItem(marker: string, text: string): string {\n const indent = ' '.repeat(marker.length)\n const [head, ...tail] = text.split('\\n')\n return [\n `${marker}${head}`,\n // A whitespace-only line stays empty: indenting it would only add trailing\n // whitespace, and a blank line inside an indented item is still inside it.\n ...tail.map((line) => (line.trim() ? `${indent}${line}` : ''))\n ].join('\\n')\n}\n\nfunction errorMessage(error: NonNullable<CommandLog['error']>): string {\n const raw =\n typeof error === 'object' && error !== null && 'message' in error\n ? String((error as { message: unknown }).message)\n : String(error)\n // Runner errors carry terminal colour (node's AssertionError diff is\n // colour-coded), which is noise in a document read by a model.\n return stripAnsi(raw).trim()\n}\n\n/** Commands that map to a trace action, in invocation order. */\nfunction capturedSteps(\n commands: CommandLog[]\n): { entry: CommandLog; action: TraceAction }[] {\n // Sort by invocation time so batched commands land at their real timeline\n // positions — Nightwatch buffers native asserts and emits them at test-end,\n // so raw order clusters all asserts after the navigations. The Actions tree\n // stays correct because buildActionEvents applies the same sort; mirror it\n // here so the transcript matches execution order. Stable + a no-op for\n // already-ordered WDIO/Selenium command logs.\n const ordered = [...commands].sort(\n (a, b) =>\n (a.startTime ?? a.timestamp) - (b.startTime ?? b.timestamp) ||\n (a.sequence ?? 0) - (b.sequence ?? 0)\n )\n const captured: { entry: CommandLog; action: TraceAction }[] = []\n for (const c of ordered) {\n const action = mapCommandToAction(String(c.command))\n if (action) {\n captured.push({ entry: c, action })\n }\n }\n return captured\n}\n\n/**\n * Generate a human/LLM-readable Markdown transcript from captured commands.\n */\nexport function generateTranscript(\n commands: CommandLog[],\n startWallTime: number,\n title?: string\n): string {\n const wallTimeISO = new Date(startWallTime).toISOString()\n const lines: string[] = [`# ${title ?? 'Session'} — ${wallTimeISO}`, '']\n const retried = retriedRows(commands)\n\n capturedSteps(commands).forEach(({ entry, action }, idx) => {\n const rawArgs = entry.args as unknown[]\n const parts: string[] = [stripAnsi(formatActionTitle(action, rawArgs))]\n\n if (FILL_METHODS.has(action.method) && rawArgs) {\n const valueIdx = rawArgs.length >= 2 ? 1 : 0\n if (rawArgs[valueIdx] !== undefined) {\n parts.push(`value=\"${stripAnsi(String(rawArgs[valueIdx]))}\"`)\n }\n }\n\n if (entry.error) {\n const label = retried.has(entry) ? 'RETRIED' : 'ERROR'\n parts.push(`${label}: ${errorMessage(entry.error)}`)\n }\n\n lines.push(asNumberedItem(`${idx + 1}. `, parts.join(' ')))\n })\n\n return lines.join('\\n')\n}\n","// Stable UID generation for tests and suites. The hash function is a tiny\n// djb2-style char-code accumulator that produces compact base36 strings.\n// \"Stable\" means: the same input produces the same output across runs.\n\n/**\n * Hash arbitrary string parts into a stable, deterministic UID. Calling this\n * multiple times with the same inputs always returns the same value — no\n * counter, no hidden state. Use for entities that must map to the same UID\n * across retries (Cucumber scenarios, feature steps, etc.).\n */\nexport function deterministicUid(...parts: string[]): string {\n const hash = parts\n .join('::')\n .split('')\n .reduce((acc, char) => ((acc << 5) - acc + char.charCodeAt(0)) | 0, 0)\n return `stable-${Math.abs(hash).toString(36)}`\n}\n\nconst STEP_UID_SEPARATOR = ':step:'\n\n/**\n * Key for one step (a Cucumber `Given`/`When`/`Then`) inside a test, derived\n * from the test's own uid plus a per-test index — repeated step text can't\n * collide. Derived rather than hashed so a step's owning test is recoverable\n * from the key alone, which is what {@link isStepUidOf} relies on.\n */\nexport function stepMetadataUid(testUid: string, index: number): string {\n return `${testUid}${STEP_UID_SEPARATOR}${index}`\n}\n\n/** True when `uid` is a step of `testUid`. Anchored on the full test uid plus\n * the separator, so a sibling test whose uid merely starts with the same\n * characters doesn't match. */\nexport function isStepUidOf(uid: string, testUid: string): boolean {\n return uid.startsWith(`${testUid}${STEP_UID_SEPARATOR}`)\n}\n\n// Counter for disambiguating repeated (file, name) signatures within a single\n// test run. Cleared by resetSignatureCounters() between runs.\nconst signatureCounters = new Map<string, number>()\n\n/**\n * Generate a UID from a (file, name) pair, disambiguating repeated calls with\n * the same inputs via an in-run counter. Use for test/suite identity where\n * the same file::name combo may legitimately appear multiple times in one run\n * (e.g. parameterised tests). For entities that must produce the same UID on\n * every retry (Cucumber scenarios), use {@link deterministicUid} instead.\n */\nexport function generateStableUid(file: string, name: string): string {\n const signature = `${file}::${name}`\n const count = signatureCounters.get(signature) ?? 0\n signatureCounters.set(signature, count + 1)\n const input = count > 0 ? `${signature}::${count}` : signature\n return deterministicUid(input)\n}\n\n/** Reset the signature counter map. Call at the start of each test run. */\nexport function resetSignatureCounters(): void {\n signatureCounters.clear()\n}\n","/**\n * Filename helpers for per-test artifacts (screenshots, videos). Shared so the\n * screenshot and video writers slug a test uid identically.\n */\n\n/** Strip leading and trailing occurrences of `char` via a linear scan. A regex\n * like `/^c+|c+$/` is O(n²) on long runs of `c` (the anchored `+` is retried\n * at each position) — flagged by CodeQL's js/polynomial-redos — so this avoids\n * regex entirely. `char` must be a single character. */\nexport function trimChar(value: string, char: string): string {\n let start = 0\n let end = value.length\n while (start < end && value[start] === char) {\n start++\n }\n while (end > start && value[end - 1] === char) {\n end--\n }\n return value.slice(start, end)\n}\n\n/** File-safe slug of a value: keep alphanumerics, `_` and `-`; collapse the\n * rest to a single `-`; trim leading/trailing dashes. */\nexport function fileSlug(value: string): string {\n return trimChar(value.replace(/[^a-zA-Z0-9_-]+/g, '-'), '-')\n}\n","/**\n * Framework-agnostic orchestration of the trace-mode export at end-of-run:\n * session vs per-spec fan-out, the spec-granularity-without-boundaries\n * fallback, retention gating, and per-write error isolation. The three\n * adapters assemble a TraceExportContext from their own state and call\n * finalizeTraceExport — all sequencing lives here.\n */\n\nimport type {\n ActionSnapshot,\n DevToolsMode,\n ScreencastFrame,\n TestMetadataMap,\n TraceFormat,\n TraceGranularity,\n TraceRetentionPolicy\n} from '@wdio/devtools-shared'\nimport { errorMessage } from './error.js'\nimport {\n buildArtifactsManifest,\n writeArtifactsManifest\n} from './artifacts-manifest.js'\nimport {\n filterTestMetadataBySpec,\n filterTestMetadataByUid,\n writeSpecTrace,\n writeTestSliceTrace,\n type SpecRange\n} from './spec-trace-helpers.js'\nimport {\n shouldRetainTrace,\n type TestOutcome\n} from '@wdio/devtools-trace/trace-retention'\nimport type { RetryOutcomeView } from './attempt-tracker.js'\nimport {\n writeTraceZip,\n type TraceCapturer\n} from '@wdio/devtools-trace/trace-exporter'\n\n/** One artifact produced (or, when `retained` is false, decided-against) by a\n * trace-mode run — a trace slice, a screencast video, or a per-test\n * screenshot. */\nexport interface TraceArtifact {\n kind: 'trace' | 'video' | 'screenshot'\n path: string\n scope: 'session' | 'spec' | 'test'\n /** specFile for spec scope, testUid for test scope. */\n key?: string\n testUids: string[]\n /** false = decided-not-to-write (reported, not written). Always true today. */\n retained: boolean\n}\n\nexport interface TraceExportContext {\n mode?: DevToolsMode\n /** undefined → treated as `on` (always retain) by the retention evaluator. */\n policy?: TraceRetentionPolicy\n /** True when the adapter fed real per-test attempt numbers (B4); retry-aware\n * policies degrade to retain-on-failure when this is false. */\n attemptInfoAvailable?: boolean\n /** Per-attempt outcome ledger. When present, retention is evaluated against\n * real per-attempt outcomes (scoped per session/spec/test) instead of the\n * collapsed final-attempt state in `testMetadata` — this is what lets\n * retain-on-first-failure see a failed-then-passed first attempt and stops\n * retain-on-failure over-retaining it. Absent → falls back to `testMetadata`\n * (unchanged behavior). */\n outcomes?: RetryOutcomeView\n granularity?: TraceGranularity\n format?: TraceFormat\n capturer: TraceCapturer\n actionSnapshots?: ActionSnapshot[]\n /** Continuous screencast frame buffer for the dense filmstrip (filmstrip\n * option on). Session write uses all; slice writes window per test. */\n screencastFrames?: readonly ScreencastFrame[]\n sessionId: string\n capabilities?: unknown\n testMetadata: TestMetadataMap\n /** Recorded spec boundaries; empty for session granularity. */\n ranges: SpecRange[]\n /** Spec-file dedupe set shared with the adapter's boundary flushes. */\n flushed: Set<string>\n /** Adapters keep their differing dir logic; range is set for spec writes. */\n resolveOutputDir: (range?: SpecRange) => string\n /** Everything finalize must settle before writing: the adapters' in-flight\n * captures AND their eager slice-write promises (selenium/nightwatch). */\n awaitPending?: Promise<unknown>[]\n /** The captures alone, live by reference and in record order, so an eager\n * flush can settle the ones from `SpecRange.captureStartIdx` on — its own —\n * without waiting on another slice's. */\n pendingCaptures?: readonly Promise<unknown>[]\n /** Session-wide work every slice depends on whatever its window: the metadata\n * read (viewport, device, capabilities), captured once at session start and\n * stamped on every trace written after it. Scoping cannot cover this — it\n * belongs to no slice — and a slice written before it resolves carries the\n * fallback viewport, which finalize cannot correct once the artifact exists.\n * Only the WDIO service defers it; the others await theirs inline. */\n metadataCapture?: Promise<unknown>\n log?: (level: 'info' | 'warn', msg: string) => void\n onArtifact?: (a: TraceArtifact) => void\n /** When true, finalize writes a `devtools-artifacts-<sessionId>.json` manifest\n * enumerating every artifact (retained or not) plus per-test states, for\n * ecosystem reporters (Allure, CI collectors) to consume. */\n emitManifest?: boolean\n /** Every artifact seen via `onArtifact` across the run (including eager\n * mid-run test-slice flushes, which the end-of-run fan-out dedupes away).\n * Pass the same list the adapter accumulates so the manifest is complete;\n * finalize falls back to its own fan-out results when this is omitted. */\n collectedArtifacts?: readonly TraceArtifact[]\n}\n\nconst SPEC_WITHOUT_BOUNDARIES_WARNING =\n 'traceGranularity is \"spec\" but no spec boundaries were detected ' +\n '(the runner may not expose per-test hooks). Falling back to ' +\n 'session-level trace.'\n\nconst TEST_WITHOUT_BOUNDARIES_WARNING =\n 'traceGranularity is \"test\" but no test boundaries were detected ' +\n '(the runner may not expose per-test hooks). Falling back to ' +\n 'session-level trace.'\n\n/** Above this many slices, warn to pair granularity with a retention policy. */\nconst SLICE_COUNT_WARN_THRESHOLD = 200\n\n/** Cap on how long finalize waits for still-in-flight snapshot captures. A\n * fire-and-forget capture against a tearing-down session can hang forever\n * (never resolves nor rejects); blocking the export on it would deadlock the\n * whole run (finalize never returns → the runner never tears the session down\n * → the capture stays stuck). Past this bound we write whatever snapshots\n * resolved in time — the same \"flush from what exists\" rule slices use. */\nexport const PENDING_SETTLE_TIMEOUT_MS = 5000\n\n/**\n * `Promise.allSettled` bounded by a timeout. Resolves when every pending\n * capture settles OR the cap elapses, whichever comes first — a stuck capture\n * can never hang the export. Returns whether it timed out so the caller can\n * warn. The timer is unref'd so it can't itself keep the process alive.\n */\nexport async function settlePending(\n pending: Promise<unknown>[],\n timeoutMs: number\n): Promise<boolean> {\n let timer: ReturnType<typeof setTimeout> | undefined\n const timedOut = new Promise<true>((resolve) => {\n timer = setTimeout(() => resolve(true), timeoutMs)\n timer.unref?.()\n })\n const settled = Promise.allSettled(pending).then(() => false)\n const result = await Promise.race([settled, timedOut])\n if (timer) {\n clearTimeout(timer)\n }\n return result\n}\n\n/**\n * Settle in-flight work under the timeout cap, warning if the bound elapses.\n * Never throws; never hangs.\n *\n * `sliceFrom` is an eager per-slice flush, and it waits on exactly its OWN\n * captures: the ones the adapter recorded after this slice opened. A capture\n * from an earlier slice is that slice's business and has already had its wait,\n * so a single wedged driver read cannot tax every later slice — and nothing of\n * this slice's own is skipped, which guessing by \"have we awaited this promise\n * before\" could not promise.\n *\n * Finalize passes no index and waits on everything, deliberately: the full list\n * also carries the adapters' eager slice-WRITE promises, and the artifacts\n * manifest is written from what they produce, so skipping one there would let\n * the manifest be written while a slice still is.\n */\nasync function awaitPendingCaptures(\n ctx: TraceExportContext,\n sliceFrom?: number\n): Promise<void> {\n // Session-wide prerequisites ride along with both: they belong to no slice,\n // and re-awaiting a settled one is free.\n const sessionWide = ctx.metadataCapture ? [ctx.metadataCapture] : []\n const pending =\n sliceFrom === undefined\n ? [...sessionWide, ...(ctx.awaitPending ?? [])]\n : [\n ...sessionWide,\n ...Array.from(ctx.pendingCaptures ?? []).slice(sliceFrom)\n ]\n if (!pending.length) {\n return\n }\n const timedOut = await settlePending(pending, PENDING_SETTLE_TIMEOUT_MS)\n if (timedOut) {\n ctx.log?.(\n 'warn',\n `One or more of ${pending.length} snapshot capture(s) did not ` +\n `settle within ${PENDING_SETTLE_TIMEOUT_MS}ms; writing trace with the ` +\n 'snapshots captured so far.'\n )\n }\n}\n\n/** The range that bounds `range`, read from the adapter's LIVE list — the next\n * test's boundary is appended to it as that test starts, so a flush that waited\n * sees a bound that did not exist when it began. Identity lookup: every caller\n * passes a range it took from this same array. */\nfunction successorOf(\n ctx: TraceExportContext,\n range: SpecRange\n): SpecRange | undefined {\n const index = ctx.ranges.indexOf(range)\n return index === -1 ? undefined : ctx.ranges[index + 1]\n}\n\nfunction sliceCountWarning(count: number): string {\n return (\n `traceGranularity produced ${count} trace slices. Consider pairing it ` +\n 'with a retention policy (e.g. tracePolicy: \"retain-on-failure\") to ' +\n 'avoid writing hundreds of trace archives.'\n )\n}\n\n/** Project a metadata slice onto the retention evaluator's outcome shape. */\nfunction toOutcomes(metadata: TestMetadataMap): TestOutcome[] {\n return Array.from(metadata.values(), (m) => ({\n state: m.state,\n attempt: m.attempt\n }))\n}\n\n/**\n * Evaluate the retention policy for one trace slice. Adapters that feed real\n * per-test attempt numbers also set `attemptInfoAvailable`; when they don't,\n * retry-aware policies degrade to retain-on-failure (see trace-retention.ts).\n */\nfunction shouldRetain(\n ctx: TraceExportContext,\n metadata: TestMetadataMap,\n ledgerOutcomes?: TestOutcome[]\n): boolean {\n // An empty scoped ledger view (adapter fed outcomes but not for this scope)\n // falls back to metadata rather than fail-opening the evaluator into retaining\n // everything — only a genuinely empty metadata slice fails open.\n return shouldRetainTrace(ctx.policy, {\n outcomes: ledgerOutcomes?.length ? ledgerOutcomes : toOutcomes(metadata),\n attemptInfoAvailable: ctx.attemptInfoAvailable ?? false\n }).retain\n}\n\n/** Attempt number encoded in a test-slice key (`<uid>-retry<n>`); 0 when the\n * key is the base uid (the first attempt / no retry suffix). */\nfunction attemptFromKey(key: string): number {\n const match = /-retry(\\d+)$/.exec(key)\n return match ? Number(match[1]) : 0\n}\n\n/** Scope the per-attempt ledger to a slice: a test slice sees only its own\n * attempt (so a passing retry isn't retained on first-failure); a spec slice\n * sees every attempt of its tests. */\nfunction sliceOutcomeView(\n ctx: TraceExportContext,\n range: SpecRange,\n isTestSlice: boolean\n): TestOutcome[] | undefined {\n if (!ctx.outcomes) {\n return undefined\n }\n return isTestSlice\n ? ctx.outcomes.forTest(range.testUid!, attemptFromKey(range.key))\n : ctx.outcomes.forSpec(range.specFile)\n}\n\n/**\n * Policy-aware single-range flush: dedupes via `ctx.flushed` on the slice\n * `key`, applies the retention decision, and delegates the byte-level\n * slicing/naming to `writeSpecTrace` (spec slices) or `writeTestSliceTrace`\n * (test slices, distinguished by `range.testUid`). Returns the artifact, or\n * undefined when the range was already flushed.\n */\nexport async function flushRangeTrace(\n ctx: TraceExportContext,\n range: SpecRange,\n nextRange?: SpecRange\n): Promise<TraceArtifact | undefined> {\n // Eager mid-run flushes reach here directly, bypassing finalizeTraceExport's\n // gate, so enforce trace-mode here too — a live run must not write slices.\n if (ctx.mode !== 'trace') {\n return undefined\n }\n if (ctx.flushed.has(range.key)) {\n return undefined\n }\n ctx.flushed.add(range.key)\n\n // A slice is written from whatever the capturer holds RIGHT NOW, and the\n // adapters' DOM captures are fire-and-forget, so an eager flush has to settle\n // them or it writes a slice describing a page nobody has read yet. Cheap\n // where those captures already resolved (a desktop drain is milliseconds),\n // and load-bearing where they cannot: on Appium every page-side call is a\n // round trip of hundreds of ms to seconds, so the per-test slice reliably\n // beat the collector and carried no `trace.mutations` at all. Bounded by the\n // same timeout the finalizer uses, so a stuck capture delays a test hook\n // rather than hanging it. Scoped to this slice's own captures; finalize\n // waits on everything.\n await awaitPendingCaptures(ctx, range.captureStartIdx ?? 0)\n\n const isTestSlice = range.testUid !== undefined\n const sliceMetadata = isTestSlice\n ? filterTestMetadataByUid(ctx.testMetadata, range.testUid!)\n : filterTestMetadataBySpec(ctx.testMetadata, range.specFile)\n const outcomes = sliceOutcomeView(ctx, range, isTestSlice)\n const artifact: TraceArtifact = {\n kind: 'trace',\n path: '',\n scope: isTestSlice ? 'test' : 'spec',\n key: range.key,\n testUids: Array.from(sliceMetadata.keys()),\n retained: shouldRetain(ctx, sliceMetadata, outcomes)\n }\n if (!artifact.retained) {\n ctx.onArtifact?.(artifact)\n return artifact\n }\n\n const writeSlice = isTestSlice ? writeTestSliceTrace : writeSpecTrace\n artifact.path = await writeSlice({\n range,\n // Re-resolved AFTER the wait, which can span the start of the next test:\n // Nightwatch — and Selenium's cucumber hook — start the flush without\n // awaiting it, and an eager flush is open-ended, so that test's commands\n // and mutations would land in this test's trace. Its boundary appears in\n // `ctx.ranges` as it starts, which closes this slice on exactly the bound\n // that now exists. Freezing indexes before the wait cannot serve: the\n // captures being waited for append to those same arrays.\n nextRange: nextRange ?? successorOf(ctx, range),\n capturer: ctx.capturer,\n actionSnapshots: ctx.actionSnapshots ?? [],\n screencastFrames: ctx.screencastFrames,\n sessionId: ctx.sessionId,\n outputDir: ctx.resolveOutputDir(range),\n format: ctx.format,\n testMetadata: ctx.testMetadata,\n capabilities: ctx.capabilities\n })\n ctx.log?.(\n 'info',\n `Trace for ${isTestSlice ? 'test' : 'spec'} \"${range.key}\" saved to ${artifact.path}`\n )\n ctx.onArtifact?.(artifact)\n return artifact\n}\n\n/**\n * Flush one slice via {@link flushRangeTrace}, logging the shared spec/test\n * error string on failure so a failed boundary flush can't abort the next test.\n * All three adapters wrapped this identically; the label + identity are derived\n * from `range.testUid` (test slice → `test \"<key>\"`, else `spec \"<specFile>\"`),\n * so each call site keeps its exact message. Errors are logged and swallowed\n * (resolves `undefined`), so callers `await` it when the write must land before\n * a retry overwrites metadata, or fire-and-forget (`void`, or tracked in an\n * in-flight list) otherwise. Callers keep their own find-current-range strategy\n * and pass the resolved range in.\n */\nexport async function flushRangeLogged(\n ctx: TraceExportContext,\n range: SpecRange\n): Promise<TraceArtifact | undefined> {\n try {\n return await flushRangeTrace(ctx, range)\n } catch (err) {\n const label =\n range.testUid !== undefined\n ? `test \"${range.key}\"`\n : `spec \"${range.specFile}\"`\n ctx.log?.(\n 'warn',\n `Failed to flush trace for ${label}: ${errorMessage(err)}`\n )\n return undefined\n }\n}\n\nasync function writeSessionTrace(\n ctx: TraceExportContext\n): Promise<TraceArtifact | undefined> {\n const snapshots = ctx.actionSnapshots ?? []\n const artifact: TraceArtifact = {\n kind: 'trace',\n path: '',\n scope: 'session',\n testUids: Array.from(ctx.testMetadata.keys()),\n retained: shouldRetain(ctx, ctx.testMetadata, ctx.outcomes?.all())\n }\n if (!artifact.retained) {\n ctx.onArtifact?.(artifact)\n return artifact\n }\n\n artifact.path = await writeTraceZip(ctx.capturer, {\n outputDir: ctx.resolveOutputDir(),\n sessionId: ctx.sessionId,\n capabilities: ctx.capabilities,\n actionSnapshots: snapshots.length ? snapshots : undefined,\n screencastFrames: ctx.screencastFrames,\n format: ctx.format,\n testMetadata: ctx.testMetadata\n })\n ctx.log?.('info', `Trace saved to ${artifact.path}`)\n ctx.onArtifact?.(artifact)\n return artifact\n}\n\n/** Run one write, logging and swallowing its error so siblings still write. */\nasync function safely(\n ctx: TraceExportContext,\n write: () => Promise<TraceArtifact | undefined>\n): Promise<TraceArtifact | undefined> {\n try {\n return await write()\n } catch (err) {\n ctx.log?.('warn', `trace write failed: ${errorMessage(err)}`)\n return undefined\n }\n}\n\nasync function flushAllRanges(\n ctx: TraceExportContext\n): Promise<TraceArtifact[]> {\n const artifacts: TraceArtifact[] = []\n // Bound each slice by the next range's start indices; the final range (no\n // nextRange) runs to the end of the arrays. Without this, every slice would\n // run to the end and each test slice would swallow all later tests.\n for (let i = 0; i < ctx.ranges.length; i++) {\n const range = ctx.ranges[i]!\n const nextRange = ctx.ranges[i + 1]\n const artifact = await safely(ctx, () =>\n flushRangeTrace(ctx, range, nextRange)\n )\n if (artifact) {\n artifacts.push(artifact)\n }\n }\n return artifacts\n}\n\n/**\n * Entry point for the after/end-of-run hook. No-op outside trace mode. Settles\n * in-flight captures and eager flushes, then fans out to per-spec, per-test, or\n * session writes and finally the artifacts manifest. `spec`/`test` granularity\n * with no recorded boundaries warns and falls back to a single session-level\n * trace.\n */\nexport async function finalizeTraceExport(\n ctx: TraceExportContext\n): Promise<TraceArtifact[]> {\n if (ctx.mode !== 'trace') {\n return []\n }\n // Settle in-flight snapshot captures AND the adapters' tracked fire-and-forget\n // eager flushes (Selenium/Nightwatch put both in awaitPending) before writing\n // anything: every eager-flushed range is a synchronous dedupe-hit in the\n // fan-out below, so this is the one place that awaits those eager-flush\n // promises. Without it the last eager write can race teardown and its artifact\n // can miss the manifest. A no-op for WDIO (awaits eager flushes inline).\n await awaitPendingCaptures(ctx)\n const artifacts = await fanOutTraceWrites(ctx)\n await maybeWriteManifest(ctx, artifacts)\n return artifacts\n}\n\n/** Session vs per-slice fan-out (with the no-boundaries fallback). */\nasync function fanOutTraceWrites(\n ctx: TraceExportContext\n): Promise<TraceArtifact[]> {\n const sliced = ctx.granularity === 'spec' || ctx.granularity === 'test'\n if (sliced && ctx.ranges.length > 0) {\n if (ctx.ranges.length > SLICE_COUNT_WARN_THRESHOLD) {\n ctx.log?.('warn', sliceCountWarning(ctx.ranges.length))\n }\n return flushAllRanges(ctx)\n }\n if (ctx.granularity === 'spec') {\n ctx.log?.('warn', SPEC_WITHOUT_BOUNDARIES_WARNING)\n } else if (ctx.granularity === 'test') {\n ctx.log?.('warn', TEST_WITHOUT_BOUNDARIES_WARNING)\n }\n const artifact = await safely(ctx, () => writeSessionTrace(ctx))\n return artifact ? [artifact] : []\n}\n\n/** Write the artifacts manifest when enabled, preferring the adapter's full\n * collected list (includes eager slices) over this pass's fan-out results.\n * Never throws — a manifest failure must not abort the run. */\nasync function maybeWriteManifest(\n ctx: TraceExportContext,\n fanOutArtifacts: readonly TraceArtifact[]\n): Promise<void> {\n if (!ctx.emitManifest) {\n return\n }\n const artifacts = ctx.collectedArtifacts ?? fanOutArtifacts\n try {\n const path = await writeArtifactsManifest(\n ctx.resolveOutputDir(),\n buildArtifactsManifest({\n sessionId: ctx.sessionId,\n format: ctx.format ?? 'zip',\n artifacts,\n testMetadata: ctx.testMetadata\n })\n )\n ctx.log?.('info', `Artifacts manifest written to ${path}`)\n } catch (err) {\n ctx.log?.(\n 'warn',\n `Failed to write artifacts manifest: ${errorMessage(err)}`\n )\n }\n}\n","import { createRequire } from 'node:module'\nimport type {\n ConsoleLog,\n LogLevel,\n NetworkRequest\n} from '@wdio/devtools-shared'\nimport {\n LOG_SOURCES,\n chromeLogLevelToLogLevel,\n type LogSource\n} from '@wdio/devtools-shared'\nimport { errorMessage } from './error.js'\nimport { getRequestType } from './net.js'\n\n/**\n * Generic sinks the BiDi handlers push into. Each adapter wires these to its\n * own SessionCapturer state — selenium's `buildBidiSinks` is the canonical\n * example; nightwatch can mirror the pattern when it wires up BiDi.\n */\nexport interface BidiHandlerSinks {\n pushConsoleLog: (entry: ConsoleLog) => void\n pushNetworkRequest: (entry: NetworkRequest) => void\n replaceNetworkRequest: (id: string, entry: NetworkRequest) => void\n}\n\n/**\n * Resolve a `selenium-webdriver/<subpath>` module from the user's install\n * (preferred) or the package's local install (fallback). Returns `null` if\n * neither resolves — caller should treat as \"BiDi not available on this\n * runtime\" and degrade gracefully.\n *\n * Used by both selenium-devtools and (when wired up) nightwatch-devtools —\n * both ship selenium-webdriver-style drivers under the hood.\n */\nexport function loadSeleniumSubmodule<T = unknown>(subpath: string): T | null {\n try {\n const userRequire = createRequire(`${process.cwd()}/`)\n return userRequire(`selenium-webdriver/${subpath}`) as T\n } catch {\n try {\n const localRequire = createRequire(import.meta.url)\n return localRequire(`selenium-webdriver/${subpath}`) as T\n } catch {\n return null\n }\n }\n}\n\ntype BidiLogger = (level: 'info' | 'warn', message: string) => void\ntype InspectorFactory = (driver: unknown) => Promise<unknown>\n\ninterface LogInspector {\n onConsoleEntry: (cb: (entry: unknown) => void) => Promise<void>\n onJavascriptException: (cb: (exc: unknown) => void) => Promise<void>\n}\n\ninterface NetworkInspector {\n beforeRequestSent: (cb: (e: unknown) => void) => Promise<void>\n responseCompleted: (cb: (e: unknown) => void) => Promise<void>\n fetchError?: (cb: (e: unknown) => void) => Promise<void>\n /** The connection the inspector subscribed on, for one without the hook. */\n bidi?: {\n subscribe: (events: string | string[]) => Promise<unknown>\n socket?: {\n on: (event: 'message', listener: (raw: unknown) => void) => void\n }\n }\n}\n\nexport function handleBidiConsoleEntry(\n rawEntry: unknown,\n sinks: BidiHandlerSinks,\n log: BidiLogger\n): void {\n const entry = rawEntry as {\n level?: string\n type?: string\n text?: string\n message?: string\n timestamp?: number\n }\n try {\n const level = (entry?.level ?? entry?.type ?? 'info').toString()\n const text = entry?.text ?? entry?.message ?? ''\n sinks.pushConsoleLog({\n timestamp: Number(entry?.timestamp) || Date.now(),\n type: chromeLogLevelToLogLevel(level) as LogLevel,\n args: [text],\n source: LOG_SOURCES.BROWSER as LogSource\n })\n } catch (err) {\n log('warn', `onConsoleEntry handler threw: ${errorMessage(err)}`)\n }\n}\n\nexport function handleBidiJsException(\n rawExc: unknown,\n sinks: BidiHandlerSinks,\n log: BidiLogger\n): void {\n const exception = rawExc as { text?: string; message?: string }\n try {\n const text = exception?.text ?? exception?.message ?? String(rawExc)\n const trimmed = String(text).replace(/\\s+/g, ' ').slice(0, 200)\n log(\n 'warn',\n `🐛 JS error in page: ${trimmed}${String(text).length > 200 ? '…' : ''}`\n )\n sinks.pushConsoleLog({\n timestamp: Date.now(),\n type: 'error',\n args: [text],\n source: LOG_SOURCES.BROWSER as LogSource\n })\n } catch (err) {\n log('warn', `onJavascriptException handler threw: ${errorMessage(err)}`)\n }\n}\n\nasync function attachLogInspector(\n driver: unknown,\n factory: InspectorFactory,\n sinks: BidiHandlerSinks,\n log: BidiLogger\n): Promise<boolean> {\n try {\n const inspector = (await factory(driver)) as LogInspector\n await inspector.onConsoleEntry((e) => handleBidiConsoleEntry(e, sinks, log))\n await inspector.onJavascriptException((e) =>\n handleBidiJsException(e, sinks, log)\n )\n log('info', '✓ BiDi LogInspector attached (console + JS exceptions)')\n return true\n } catch (err) {\n log('warn', `BiDi LogInspector attach failed: ${errorMessage(err)}`)\n return false\n }\n}\n\n/** Subset of WebDriver BiDi `FetchTimingInfo` (selenium exposes these via\n * getters). Values are ms offsets from the request baseline `requestTime`\n * (usually 0), so `responseEnd - requestTime` is the request duration. */\ninterface FetchTimings {\n requestTime?: number\n responseEnd?: number\n}\n\nconst isFiniteNumber = (value: unknown): value is number =>\n typeof value === 'number' && Number.isFinite(value)\n\ninterface BeforeRequestSentEvent {\n request?: {\n request?: string\n url?: string\n method?: string\n headers?: unknown\n timings?: FetchTimings\n }\n id?: string\n timestamp?: number\n}\n\ninterface ResponseCompletedEvent {\n request?: { request?: string; timings?: FetchTimings }\n id?: string\n timestamp?: number\n response?: {\n status?: number\n statusText?: string\n headers?: unknown\n mimeType?: string\n bytesReceived?: number\n }\n}\n\n/** Request duration (ms) from the browser's FetchTimingInfo, plus a derived\n * endTime anchored to `startTime`. The browser timings are immune to BiDi\n * events arriving batched in one tick (which collapses the event-level\n * `timestamp`/`Date.now()` to a single value and yields 0-duration requests).\n * Falls back to the timestamp diff only when timings are unavailable. */\nfunction bidiResponseTiming(\n timings: FetchTimings | undefined,\n startTime: number,\n timestamp?: number\n): { endTime: number; time: number } {\n if (\n timings &&\n isFiniteNumber(timings.requestTime) &&\n isFiniteNumber(timings.responseEnd) &&\n timings.responseEnd > timings.requestTime\n ) {\n const time = Math.round(timings.responseEnd - timings.requestTime)\n return { endTime: startTime + time, time }\n }\n const endTime = Number(timestamp ?? Date.now())\n return { endTime, time: Math.max(0, endTime - startTime) }\n}\n\n/** Rows a session has seen, by request id: a later event for the same request\n * (a redirect hop, a late failure) continues the row rather than adding one. */\nconst MAX_TRACKED_REQUESTS = 5000\n\nfunction remember(\n rows: Map<string, NetworkRequest>,\n id: string,\n entry: NetworkRequest\n): void {\n rows.set(id, entry)\n if (rows.size <= MAX_TRACKED_REQUESTS) {\n return\n }\n // A row still open keeps its place: its answer has somewhere to land.\n for (const [key, row] of rows) {\n if (row.status !== undefined) {\n rows.delete(key)\n return\n }\n }\n rows.delete(rows.keys().next().value as string)\n}\n\nexport function handleBidiRequestSent(\n rawEvent: unknown,\n rows: Map<string, NetworkRequest>,\n sinks: BidiHandlerSinks,\n log: BidiLogger\n): void {\n const event = rawEvent as BeforeRequestSentEvent\n try {\n const requestId = String(event?.request?.request ?? event?.id ?? '')\n // selenium-webdriver's NetworkInspector hands every network event to every\n // subscriber's callback, so response and failure events reach this\n // handler too; taken as requests they pushed or reopened rows.\n const shape = rawEvent as { response?: unknown; errorText?: unknown }\n if (!requestId || shape?.response || shape?.errorText) {\n return\n }\n const previous = rows.get(requestId)\n const entry: NetworkRequest = {\n id: requestId,\n url: event?.request?.url ?? '',\n method: event?.request?.method ?? 'GET',\n requestHeaders: arrayHeadersToObject(event?.request?.headers),\n // A redirect hop re-sends the request under the same id: one row, which\n // keeps the clock of the hop it follows and reopens for the next answer.\n timestamp: previous?.timestamp ?? Date.now(),\n startTime: previous?.startTime ?? Number(event?.timestamp ?? Date.now()),\n type: getRequestType(event?.request?.url ?? '')\n }\n remember(rows, requestId, entry)\n if (previous) {\n sinks.replaceNetworkRequest(requestId, entry)\n } else {\n sinks.pushNetworkRequest(entry)\n }\n } catch (err) {\n log('warn', `beforeRequestSent threw: ${errorMessage(err)}`)\n }\n}\n\nexport function handleBidiResponseCompleted(\n rawEvent: unknown,\n rows: Map<string, NetworkRequest>,\n sinks: BidiHandlerSinks,\n log: BidiLogger\n): void {\n const event = rawEvent as ResponseCompletedEvent\n try {\n const requestId = String(event?.request?.request ?? event?.id ?? '')\n const previous = rows.get(requestId)\n // A request event delivered here (see handleBidiRequestSent) carries no\n // response; finalizing on it emptied the row.\n if (!previous || !event?.response) {\n return\n }\n const { endTime, time } = bidiResponseTiming(\n event?.request?.timings,\n previous.startTime,\n event?.timestamp\n )\n const finalized: NetworkRequest = {\n ...previous,\n status: Number(event?.response?.status) || previous.status,\n statusText: event?.response?.statusText ?? previous.statusText,\n responseHeaders: arrayHeadersToObject(event?.response?.headers),\n type: getRequestType(previous.url, event?.response?.mimeType),\n endTime,\n time,\n size: Number(event?.response?.bytesReceived) || undefined\n }\n remember(rows, requestId, finalized)\n sinks.replaceNetworkRequest(requestId, finalized)\n } catch (err) {\n log('warn', `responseCompleted threw: ${errorMessage(err)}`)\n }\n}\n\ninterface FetchErrorEvent {\n request?: { request?: string; timings?: FetchTimings }\n id?: string\n timestamp?: number\n errorText?: string\n}\n\n/** A request the browser gave up on: closed as a failure, as the WDIO service\n * closes its own, so it never sits in the Network tab as pending. */\nexport function handleBidiFetchError(\n rawEvent: unknown,\n rows: Map<string, NetworkRequest>,\n sinks: BidiHandlerSinks,\n log: BidiLogger\n): void {\n const event = rawEvent as FetchErrorEvent\n try {\n const requestId = String(event?.request?.request ?? event?.id ?? '')\n const previous = rows.get(requestId)\n // The inspector delivers every network event here too; only a failure\n // carries `errorText`.\n if (!previous || typeof event?.errorText !== 'string') {\n return\n }\n const { endTime, time } = bidiResponseTiming(\n event?.request?.timings,\n previous.startTime,\n event?.timestamp\n )\n const errorText = event?.errorText ?? 'Failed'\n const finalized: NetworkRequest = {\n ...previous,\n status: 0,\n statusText: errorText,\n error: errorText,\n endTime,\n time\n }\n remember(rows, requestId, finalized)\n sinks.replaceNetworkRequest(requestId, finalized)\n } catch (err) {\n log('warn', `fetchError threw: ${errorMessage(err)}`)\n }\n}\n\n/** A failed fetch closes its row, or it would stay pending forever: through\n * the inspector's own hook, or off the socket for an inspector without one. */\nasync function subscribeFetchErrors(\n inspector: NetworkInspector,\n rows: Map<string, NetworkRequest>,\n sinks: BidiHandlerSinks,\n log: BidiLogger\n): Promise<void> {\n if (typeof inspector.fetchError === 'function') {\n await inspector.fetchError((e) => handleBidiFetchError(e, rows, sinks, log))\n return\n }\n const bidi = inspector.bidi\n if (typeof bidi?.subscribe !== 'function' || !bidi.socket?.on) {\n return\n }\n await bidi.subscribe('network.fetchError')\n bidi.socket.on('message', (raw) => {\n try {\n const { method, params } = JSON.parse(String(raw)) as {\n method?: string\n params?: unknown\n }\n if (method === 'network.fetchError') {\n handleBidiFetchError(params, rows, sinks, log)\n }\n } catch {\n /* a frame that is not JSON is not an event */\n }\n })\n}\n\nexport async function attachNetworkInspector(\n driver: unknown,\n factory: InspectorFactory,\n sinks: BidiHandlerSinks,\n log: BidiLogger\n): Promise<boolean> {\n try {\n const inspector = (await factory(driver)) as NetworkInspector\n const rows = new Map<string, NetworkRequest>()\n await inspector.beforeRequestSent((e) =>\n handleBidiRequestSent(e, rows, sinks, log)\n )\n await inspector.responseCompleted((e) =>\n handleBidiResponseCompleted(e, rows, sinks, log)\n )\n try {\n await subscribeFetchErrors(inspector, rows, sinks, log)\n } catch (err) {\n // The inspector is attached either way; only failed requests stay pending.\n log('warn', `BiDi network.fetchError unavailable: ${errorMessage(err)}`)\n }\n log('info', '✓ BiDi NetworkInspector attached (request + response)')\n return true\n } catch (err) {\n log('warn', `BiDi NetworkInspector attach failed: ${errorMessage(err)}`)\n return false\n }\n}\n\n/**\n * Attach the selenium-webdriver BiDi LogInspector + NetworkInspector to a\n * driver and route their events into the given sinks. Returns `true` when at\n * least one inspector connected — caller can disable the equivalent\n * script-injection collectors to avoid duplicates.\n *\n * Tolerant of older / non-BiDi runtimes: if either submodule fails to load\n * or the inspector factory throws (driver session doesn't have webSocketUrl\n * capability set, etc.), the corresponding stream is silently skipped and\n * the function returns false.\n *\n * @param onLog Optional callback for adapter-side logging. Receives ('info' |\n * 'warn', message) on lifecycle events. Default: silent — adapters wire their\n * own logger when they want visibility into BiDi attach state.\n */\nexport async function attachBidiHandlers(\n driver: unknown,\n sinks: BidiHandlerSinks,\n onLog?: (level: 'info' | 'warn', message: string) => void\n): Promise<boolean> {\n const log: BidiLogger = (level, message) => onLog?.(level, message)\n const logFactory =\n loadSeleniumSubmodule<InspectorFactory>('bidi/logInspector')\n const networkFactory = loadSeleniumSubmodule<InspectorFactory>(\n 'bidi/networkInspector'\n )\n\n let attached = 0\n if (typeof logFactory === 'function') {\n if (await attachLogInspector(driver, logFactory, sinks, log)) {\n attached++\n }\n } else {\n log('info', 'selenium-webdriver/bidi/logInspector not available — skipping')\n }\n if (typeof networkFactory === 'function') {\n if (await attachNetworkInspector(driver, networkFactory, sinks, log)) {\n attached++\n }\n } else {\n log(\n 'info',\n 'selenium-webdriver/bidi/networkInspector not available — skipping'\n )\n }\n return attached > 0\n}\n\n/**\n * Flatten BiDi's `Array<{name, value:{value|type}}>` header shape to a\n * lowercased `Record<string, string>`. Exported so adapter-side helpers can\n * reuse it for their own header normalization.\n */\nexport function arrayHeadersToObject(\n headers: unknown\n): Record<string, string> | undefined {\n if (!Array.isArray(headers)) {\n return undefined\n }\n const out: Record<string, string> = {}\n for (const h of headers as Array<{\n name?: string\n value?: string | { value?: string; type?: string }\n }>) {\n const name = String(h?.name ?? '').toLowerCase()\n if (!name) {\n continue\n }\n const v = h?.value\n out[name] =\n typeof v === 'string'\n ? v\n : typeof (v as { value?: string })?.value === 'string'\n ? (v as { value: string }).value\n : JSON.stringify(v ?? '')\n }\n return out\n}\n","import * as net from 'node:net'\n\n/** Kept exported here so importers predating the split keep resolving; the\n * implementation is the browser-safe leaf `request-type.ts`. */\nexport { getRequestType } from './request-type.js'\n\n/**\n * Return true if the given TCP port on `hostname` cannot be bound for\n * listening (already in use, or otherwise unavailable).\n */\nexport function isPortInUse(port: number, hostname: string): Promise<boolean> {\n return new Promise((resolve) => {\n const server = net.createServer()\n server.once('error', () => resolve(true))\n server.once('listening', () => server.close(() => resolve(false)))\n server.listen(port, hostname)\n })\n}\n\n/**\n * Walk upward from `startPort` until a free port is found and return it.\n * Silent: callers that want to log retries should wrap this themselves.\n */\nexport async function findFreePort(\n startPort: number,\n hostname: string\n): Promise<number> {\n let port = startPort\n while (await isPortInUse(port, hostname)) {\n port++\n }\n return port\n}\n","import fs from 'node:fs'\nimport path from 'node:path'\n\nexport interface ResolveAdapterOutputDirInput {\n /**\n * Honored as-is if set — used by adapters that expose a user-facing\n * `outputDir` option (e.g. WDIO). Skips all other resolution steps.\n */\n userConfiguredDir?: string\n /**\n * Absolute path to the current test file. When known, the video / trace\n * lands in the same folder as the spec the user just ran. This is the\n * preferred location across adapters.\n */\n testFilePath?: string\n /**\n * Absolute path to the resolved framework config file (wdio.conf.ts,\n * nightwatch.conf.cjs, etc.). Used as a fallback when the test file\n * isn't known.\n */\n configPath?: string\n /** Last-resort fallback. Defaults to `process.cwd()`. */\n fallbackDir?: string\n}\n\nconst NODE_MODULES_SEGMENT = `${path.sep}node_modules${path.sep}`\n\n/** All run output is grouped under this subfolder. */\nconst OUTPUT_SUBDIR = 'test-results'\n\nfunction isWritable(dir: string): boolean {\n try {\n fs.accessSync(dir, fs.constants.W_OK)\n return true\n } catch {\n return false\n }\n}\n\n/**\n * Resolve the directory where an adapter should write output files\n * (screencast .webm, trace JSON, etc.). Every artifact is grouped under a\n * single `test-results/` subfolder so a run's output is\n * self-contained regardless of where the base directory resolves to.\n *\n * The base directory is resolved by priority:\n * 1. `userConfiguredDir` — explicit opt-in, honored as-is.\n * 2. `dirname(testFilePath)` — same folder as the spec that just ran.\n * 3. `dirname(configPath)` — fallback to the framework config dir.\n * 4. `fallbackDir` (default `process.cwd()`).\n *\n * Any candidate inside a `node_modules/` segment is skipped — this can\n * happen in symlinked workspaces where the test file resolves through a\n * linked dependency. Each candidate must also be writable; non-writable\n * dirs fall through to the next.\n *\n * Shared by all three adapters (service / nightwatch-devtools /\n * selenium-devtools) so the output location stays consistent regardless\n * of where the user invoked the runner from. See CLAUDE.md §2.2.\n */\nexport function resolveAdapterOutputDir(\n input: ResolveAdapterOutputDirInput = {}\n): string {\n return path.join(resolveBaseDir(input), OUTPUT_SUBDIR)\n}\n\nfunction resolveBaseDir(input: ResolveAdapterOutputDirInput): string {\n const fallback = input.fallbackDir ?? process.cwd()\n // userConfiguredDir bypasses the node_modules and writability filters\n // because the user opted into it explicitly — surprising overrides are\n // worse than failing loudly here.\n if (input.userConfiguredDir) {\n return input.userConfiguredDir\n }\n const candidates: string[] = []\n if (input.testFilePath) {\n candidates.push(path.dirname(input.testFilePath))\n }\n if (input.configPath) {\n candidates.push(path.dirname(input.configPath))\n }\n candidates.push(fallback)\n for (const dir of candidates) {\n if (!dir) {\n continue\n }\n // Trailing separator so a spec directly inside node_modules is caught too.\n if (`${dir}${path.sep}`.includes(NODE_MODULES_SEGMENT)) {\n continue\n }\n if (isWritable(dir)) {\n return dir\n }\n }\n return fallback\n}\n","/**\n * Accessibility tree → the indented text the trace's A11y tab parses.\n *\n * A pure transform over nodes a page-side script produced: no driver, no\n * framework, no DOM. It lives here rather than in `core` because the backend\n * builds the trace for an adapter that cannot run the transforms itself, and\n * §2.2 bars the backend from importing core — the same reason the rest of this\n * package moved. The JS adapters reach it through core's re-export unchanged.\n *\n * Only the WEB serializer is here. The mobile one shares helpers with the\n * locator generation that stays in `core`, and no adapter exporting through the\n * backend drives native mobile.\n */\n\nimport {\n INTERACTIVE_ROLES,\n isStatictextEchoedByParent,\n SNAPSHOT_INDENT_UNIT,\n SNAPSHOT_LOCATOR_DELIM,\n SNAPSHOT_PAGE_HEADER,\n SNAPSHOT_PURPOSE_TOKEN\n} from '@wdio/devtools-shared'\nimport type { AccessibilityNode } from '@wdio/devtools-shared'\n\nexport interface WebSnapshotOptions {\n inViewportOnly?: boolean\n}\n\n/**\n * Walk backwards from `index` to find the nearest ancestor or preceding\n * structural sibling with a non-empty name. Same-depth nodes are only\n * used when they are structural (img, heading, statictext, …) — never\n * another interactive element.\n */\nfunction inferPurpose(\n nodes: AccessibilityNode[],\n index: number\n): string | undefined {\n const myDepth = nodes[index].depth\n for (let i = index - 1; i >= 0; i--) {\n if (nodes[i].depth <= myDepth && nodes[i].name) {\n // Same-depth sibling: only structural elements count\n if (nodes[i].depth === myDepth && INTERACTIVE_ROLES.has(nodes[i].role)) {\n continue\n }\n return nodes[i].name\n }\n }\n return undefined\n}\n\n/**\n * Serialize a web accessibility tree into a depth-indented text snapshot.\n *\n * @param nodes Flat ordered node list from getBrowserAccessibilityTree()\n * @param context Optional page context for the header line\n * @param options {@link WebSnapshotOptions}\n */\nexport function serializeWebSnapshot(\n nodes: AccessibilityNode[],\n context?: { url?: string; title?: string },\n options: WebSnapshotOptions = {}\n): string {\n const { inViewportOnly = true } = options\n\n let header = SNAPSHOT_PAGE_HEADER\n if (context?.title) {\n header += `: ${context.title}`\n }\n if (context?.url) {\n header += ` — ${context.url}`\n }\n header += ']'\n\n const lines: string[] = [header]\n\n for (let i = 0; i < nodes.length; i++) {\n const node = nodes[i]\n\n // When viewport filtering is on, skip nodes that are known to be off-screen.\n // Nodes from a tree captured with inViewportOnly=false will have\n // isInViewport populated; nodes from a pre-filtered tree all have\n // isInViewport=true (or undefined for pre-existing data).\n if (inViewportOnly && node.isInViewport === false) {\n continue\n }\n\n const indent = SNAPSHOT_INDENT_UNIT.repeat(node.depth + 1) // +1 indents everything under the header\n const isInteractive = INTERACTIVE_ROLES.has(node.role)\n\n if (isStatictextEchoedByParent(nodes, i)) {\n continue\n }\n\n // Heading gets level suffix: heading[2]\n const roleLabel =\n node.role === 'heading' && node.level\n ? `heading[${node.level}]`\n : node.role\n\n if (isInteractive) {\n // No selector → agent can't act on this node; skip entirely\n if (!node.selector) {\n continue\n }\n const purpose = inferPurpose(nodes, i)\n if (node.name) {\n // Show parent context when available — disambiguates\n // duplicate selectors like six \"Add to Wishlist\" buttons.\n lines.push(\n purpose\n ? `${indent}${roleLabel} \"${node.name}\" ${SNAPSHOT_PURPOSE_TOKEN} \"${purpose}\" ${SNAPSHOT_LOCATOR_DELIM} ${node.selector}`\n : `${indent}${roleLabel} \"${node.name}\" ${SNAPSHOT_LOCATOR_DELIM} ${node.selector}`\n )\n } else if (purpose) {\n lines.push(\n `${indent}${roleLabel} ${SNAPSHOT_PURPOSE_TOKEN} \"${purpose}\" ${SNAPSHOT_LOCATOR_DELIM} ${node.selector}`\n )\n } else {\n lines.push(\n `${indent}${roleLabel} ${SNAPSHOT_LOCATOR_DELIM} ${node.selector}`\n )\n }\n } else {\n // Container / structural: show role + name when present, no selector\n lines.push(\n node.name\n ? `${indent}${roleLabel} \"${node.name}\"`\n : `${indent}${roleLabel}`\n )\n }\n }\n\n return lines.join('\\n')\n}\n","/**\n * XML parsing utilities for mobile element source\n */\n\nimport { DOMParser } from '@xmldom/xmldom'\nimport type {\n Document as XMLDocument,\n Element as XMLElement,\n Node as XMLNode\n} from '@xmldom/xmldom'\nimport xpath from 'xpath'\n\nimport { selectIndexed } from './xpath-index.js'\nimport type {\n ElementAttributes,\n JSONElement,\n Bounds,\n UniquenessResult\n} from './types.js'\n\n/**\n * Get child nodes that are elements (not text nodes, comments, etc.)\n */\nfunction childNodesOf(node: XMLNode): XMLNode[] {\n const children: XMLNode[] = []\n if (node.childNodes) {\n for (let i = 0; i < node.childNodes.length; i++) {\n const child = node.childNodes.item(i)\n if (child?.nodeType === 1) {\n children.push(child)\n }\n }\n }\n return children\n}\n\n/**\n * Recursively translate DOM node to JSONElement\n */\nfunction translateRecursively(\n domNode: XMLNode,\n parentPath: string = '',\n index: number | null = null\n): JSONElement {\n const attributes: ElementAttributes = {}\n\n const element = domNode as XMLElement\n if (element.attributes) {\n for (let attrIdx = 0; attrIdx < element.attributes.length; attrIdx++) {\n const attr = element.attributes.item(attrIdx)\n if (attr) {\n attributes[attr.name] = attr.value.replace(/(\\n)/gm, '\\\\n')\n }\n }\n }\n\n const path =\n index === null ? '' : `${parentPath ? parentPath + '.' : ''}${index}`\n\n return {\n children: childNodesOf(domNode).map((childNode, childIndex) =>\n translateRecursively(childNode as XMLNode, path, childIndex)\n ),\n tagName: domNode.nodeName,\n attributes,\n path\n }\n}\n\n/**\n * Compare two nodes for equality by platform-specific attributes\n * (reference equality via === may fail when nodes come from different traversals)\n */\nfunction isSameElement(node1: XMLNode, node2: XMLNode): boolean {\n if (node1.nodeType !== 1 || node2.nodeType !== 1) {\n return false\n }\n const el1 = node1 as XMLElement\n const el2 = node2 as XMLElement\n\n if (el1.nodeName !== el2.nodeName) {\n return false\n }\n\n // For Android, compare by bounds (unique per element)\n const bounds1 = el1.getAttribute('bounds')\n const bounds2 = el2.getAttribute('bounds')\n if (bounds1 && bounds2) {\n return bounds1 === bounds2\n }\n\n // For iOS, compare by x, y, width, height\n const x1 = el1.getAttribute('x')\n const y1 = el1.getAttribute('y')\n const x2 = el2.getAttribute('x')\n const y2 = el2.getAttribute('y')\n if (x1 && y1 && x2 && y2) {\n return (\n x1 === x2 &&\n y1 === y2 &&\n el1.getAttribute('width') === el2.getAttribute('width') &&\n el1.getAttribute('height') === el2.getAttribute('height')\n )\n }\n\n return false\n}\n\n/**\n * Sanitize page source XML that may contain HTML artifacts (WebView content).\n *\n * Handles two classes of invalid XML that xmldom's strict parser rejects:\n * 1. Bare ampersands — `&` not part of a valid XML entity (e.g. ` `,\n * `©`, bare `&` in URLs) → replaced with `&`.\n * 2. HTML void elements without explicit self-closure (`<link>`, `<meta>`,\n * `<br>`, `<img>`, `<input>`, `<hr>`, `<source>`, `<area>`, `<base>`,\n * `<col>`, `<embed>`, `<track>`, `<wbr>`) → closed with `/>`.\n */\nfunction sanitizePageSource(sourceXML: string): string {\n // Pass 1: escape bare ampersands — & not followed by a known XML entity.\n let sanitized = sourceXML.replace(\n /&(?!(?:amp|lt|gt|quot|apos|#\\d+|#x[0-9a-fA-F]+);)/g,\n '&'\n )\n\n // Pass 2: self-close known HTML void elements.\n const VOID_ELEMENTS = [\n 'link',\n 'meta',\n 'br',\n 'hr',\n 'img',\n 'input',\n 'source',\n 'area',\n 'base',\n 'col',\n 'embed',\n 'track',\n 'wbr'\n ]\n\n for (const tag of VOID_ELEMENTS) {\n // <tagname ...> → <tagname .../> (not already self-closed)\n sanitized = sanitized.replace(\n // eslint-disable-next-line security/detect-non-literal-regexp -- tag from hardcoded list\n new RegExp(`<${tag}(\\\\s[^>]*?[^/])>`, 'gi'),\n `<${tag}$1/>`\n )\n // <tagname> → <tagname/> (no attributes)\n sanitized = sanitized.replace(\n // eslint-disable-next-line security/detect-non-literal-regexp -- tag from hardcoded list\n new RegExp(`<${tag}>`, 'gi'),\n `<${tag}/>`\n )\n }\n\n return sanitized\n}\n\n/**\n * Convert XML page source to JSON tree structure\n */\nexport function xmlToJSON(sourceXML: string): JSONElement | null {\n try {\n const sanitized = sanitizePageSource(sourceXML)\n const parser = new DOMParser()\n const sourceDoc = parser.parseFromString(sanitized, 'text/xml')\n\n // xmldom 0.9+ throws ParseError for fatal errors (caught below); this catches non-fatal cases\n const parseErrors = sourceDoc.getElementsByTagName('parsererror')\n if (parseErrors.length > 0) {\n console.error(\n '[xmlToJSON] XML parsing error:',\n parseErrors[0].textContent\n )\n return null\n }\n\n const children = childNodesOf(sourceDoc)\n const firstChild =\n children[0] ||\n (sourceDoc.documentElement\n ? childNodesOf(sourceDoc.documentElement)[0]\n : null)\n\n return firstChild\n ? translateRecursively(firstChild)\n : { children: [], tagName: '', attributes: {}, path: '' }\n } catch (e) {\n console.error('[xmlToJSON] Failed to parse XML:', e)\n return null\n }\n}\n\n/**\n * Parse XML source to DOM Document for XPath evaluation\n */\nexport function xmlToDOM(sourceXML: string): XMLDocument | null {\n try {\n const sanitized = sanitizePageSource(sourceXML)\n const parser = new DOMParser()\n const doc = parser.parseFromString(sanitized, 'text/xml')\n\n // xmldom 0.9+ throws ParseError for fatal errors (caught below); this catches non-fatal cases\n const parseErrors = doc.getElementsByTagName('parsererror')\n if (parseErrors.length > 0) {\n console.error('[xmlToDOM] XML parsing error:', parseErrors[0].textContent)\n return null\n }\n\n return doc\n } catch (e) {\n console.error('[xmlToDOM] Failed to parse XML:', e)\n return null\n }\n}\n\n/**\n * Execute XPath query on DOM document\n */\nexport function evaluateXPath(doc: XMLDocument, xpathExpr: string): XMLNode[] {\n try {\n const indexed = selectIndexed(doc, xpathExpr)\n if (indexed) {\n return indexed\n }\n // @xmldom/xmldom 0.9+ types don't satisfy global Node; xpath still works at runtime\n const nodes = xpath.select(xpathExpr, doc as unknown as Node)\n if (Array.isArray(nodes)) {\n return nodes as unknown as XMLNode[]\n }\n return []\n } catch (e) {\n console.error(`[evaluateXPath] Failed to evaluate \"${xpathExpr}\":`, e)\n return []\n }\n}\n\n/**\n * Check if an XPath selector is unique and get index if not\n */\nexport function checkXPathUniqueness(\n doc: XMLDocument,\n xpathExpr: string,\n targetNode?: XMLNode\n): UniquenessResult {\n try {\n const nodes = evaluateXPath(doc, xpathExpr)\n const totalMatches = nodes.length\n\n if (totalMatches === 0) {\n return { isUnique: false }\n }\n\n if (totalMatches === 1) {\n return { isUnique: true }\n }\n\n // Not unique - find index of target node if provided\n if (targetNode) {\n for (let i = 0; i < nodes.length; i++) {\n if (nodes[i] === targetNode || isSameElement(nodes[i], targetNode)) {\n return {\n isUnique: false,\n index: i + 1, // 1-based index for XPath\n totalMatches\n }\n }\n }\n }\n\n return { isUnique: false, totalMatches }\n } catch (e) {\n console.error(`[checkXPathUniqueness] Error checking \"${xpathExpr}\":`, e)\n return { isUnique: false }\n }\n}\n\n/**\n * Find DOM node by JSONElement path (e.g., \"0.2.1\")\n */\nexport function findDOMNodeByPath(\n doc: XMLDocument,\n path: string\n): XMLNode | null {\n if (!path) {\n return doc.documentElement\n }\n\n const indices = path.split('.').map(Number)\n let current: XMLNode | null = doc.documentElement\n\n for (const index of indices) {\n if (!current) {\n return null\n }\n\n const children: XMLNode[] = []\n if (current.childNodes) {\n for (let i = 0; i < current.childNodes.length; i++) {\n const child = current.childNodes.item(i)\n if (child?.nodeType === 1) {\n children.push(child)\n }\n }\n }\n\n current = children[index] || null\n }\n\n return current\n}\n\n/**\n * Parse Android bounds string \"[x1,y1][x2,y2]\" to coordinates\n */\nexport function parseAndroidBounds(bounds: string): Bounds {\n const match = bounds.match(/\\[(\\d+),(\\d+)\\]\\[(\\d+),(\\d+)\\]/)\n if (!match) {\n return { x: 0, y: 0, width: 0, height: 0 }\n }\n\n const x1 = parseInt(match[1], 10)\n const y1 = parseInt(match[2], 10)\n const x2 = parseInt(match[3], 10)\n const y2 = parseInt(match[4], 10)\n\n return {\n x: x1,\n y: y1,\n width: x2 - x1,\n height: y2 - y1\n }\n}\n\n/**\n * Parse iOS element bounds from individual x, y, width, height attributes\n */\nexport function parseIOSBounds(attributes: ElementAttributes): Bounds {\n return {\n x: parseInt(attributes.x || '0', 10),\n y: parseInt(attributes.y || '0', 10),\n width: parseInt(attributes.width || '0', 10),\n height: parseInt(attributes.height || '0', 10)\n }\n}\n\n/**\n * Flatten JSON element tree to array (depth-first)\n */\nexport function flattenElementTree(root: JSONElement): JSONElement[] {\n const result: JSONElement[] = []\n\n function traverse(element: JSONElement) {\n result.push(element)\n for (const child of element.children) {\n traverse(child)\n }\n }\n\n traverse(root)\n return result\n}\n\n/**\n * Count occurrences of an attribute value in the source XML\n */\nexport function countAttributeOccurrences(\n sourceXML: string,\n attribute: string,\n value: string\n): number {\n const escapedValue = value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n const pattern = new RegExp(`${attribute}=[\"']${escapedValue}[\"']`, 'g')\n const matches = sourceXML.match(pattern)\n return matches ? matches.length : 0\n}\n\n/**\n * Check if an attribute value is unique in the source (fast regex-based check)\n */\nexport function isAttributeUnique(\n sourceXML: string,\n attribute: string,\n value: string\n): boolean {\n return countAttributeOccurrences(sourceXML, attribute, value) === 1\n}\n","import type { Element as XMLElement } from '@xmldom/xmldom'\n\nimport type { XMLDocument, XMLNode } from './types.js'\n\n/** A page source's elements by tag and attribute value, built once per walk. */\ninterface XPathIndex {\n doc: XMLDocument\n all: XMLElement[]\n byTag: Map<string, XMLElement[]>\n byAttr: Map<string, Map<string, XMLElement[]>>\n}\n\ninterface Condition {\n name: string\n value: string\n}\n\ninterface ParsedQuery {\n tag: string\n conditions: Condition[]\n}\n\nconst NAME = /[A-Za-z_][\\w.-]*/y\n\n/** The index for the walk in progress; scoped so a document edited later is never answered from it. */\nlet active: XPathIndex | null = null\n\n/** `//*` or `//Tag`, optionally `[@a=\"x\" and @b='y']`; null for anything else. */\nexport function parseSimpleXPath(expr: string): ParsedQuery | null {\n if (!expr.startsWith('//')) {\n return null\n }\n let pos = 2\n let tag: string\n if (expr[pos] === '*') {\n tag = '*'\n pos++\n } else {\n NAME.lastIndex = pos\n const m = NAME.exec(expr)\n if (!m) {\n return null\n }\n tag = m[0]\n pos = NAME.lastIndex\n }\n if (pos === expr.length) {\n return { tag, conditions: [] }\n }\n if (expr[pos] !== '[' || !expr.endsWith(']')) {\n return null\n }\n const conditions = parseConditions(expr.slice(pos + 1, -1))\n return conditions ? { tag, conditions } : null\n}\n\nfunction parseConditions(body: string): Condition[] | null {\n const conditions: Condition[] = []\n let pos = 0\n for (;;) {\n if (body[pos] !== '@') {\n return null\n }\n NAME.lastIndex = pos + 1\n const m = NAME.exec(body)\n if (!m || body[NAME.lastIndex] !== '=') {\n return null\n }\n const quote = body[NAME.lastIndex + 1]\n if (quote !== '\"' && quote !== \"'\") {\n return null\n }\n const start = NAME.lastIndex + 2\n const end = body.indexOf(quote, start)\n if (end === -1) {\n return null\n }\n conditions.push({ name: m[0], value: body.slice(start, end) })\n pos = end + 1\n if (pos === body.length) {\n return conditions\n }\n if (!body.startsWith(' and ', pos)) {\n return null\n }\n pos += 5\n }\n}\n\nfunction append<K, V>(map: Map<K, V[]>, key: K, value: V): void {\n const list = map.get(key)\n if (list) {\n list.push(value)\n } else {\n map.set(key, [value])\n }\n}\n\n/** Null for a namespaced document: XPath name tests resolve namespaces, the index does not. */\nfunction buildIndex(doc: XMLDocument): XPathIndex | null {\n const index: XPathIndex = {\n doc,\n all: [],\n byTag: new Map(),\n byAttr: new Map()\n }\n const stack: XMLElement[] = doc.documentElement ? [doc.documentElement] : []\n const ordered: XMLElement[] = []\n while (stack.length > 0) {\n const el = stack.pop()!\n if (el.namespaceURI || el.nodeName.includes(':')) {\n return null\n }\n ordered.push(el)\n for (let i = el.childNodes.length - 1; i >= 0; i--) {\n const child = el.childNodes.item(i)\n if (child?.nodeType === 1) {\n stack.push(child as XMLElement)\n }\n }\n }\n for (const el of ordered) {\n index.all.push(el)\n append(index.byTag, el.nodeName, el)\n for (let i = 0; i < el.attributes.length; i++) {\n const { name, value } = el.attributes.item(i)!\n if (name === 'xmlns' || name.includes(':')) {\n return null\n }\n let byValue = index.byAttr.get(name)\n if (!byValue) {\n byValue = new Map()\n index.byAttr.set(name, byValue)\n }\n append(byValue, value, el)\n }\n }\n return index\n}\n\n/** Runs `walk` with `doc` indexed, so simple uniqueness checks are lookups instead of full scans. */\nexport function withXPathIndex<T>(doc: XMLDocument | null, walk: () => T): T {\n const previous = active\n active = doc ? buildIndex(doc) : null\n try {\n return walk()\n } finally {\n active = previous\n }\n}\n\n/** The nodes a simple XPath selects, in document order; null when the index cannot answer. */\nexport function selectIndexed(\n doc: XMLDocument,\n expr: string\n): XMLNode[] | null {\n if (active?.doc !== doc) {\n return null\n }\n const query = parseSimpleXPath(expr)\n if (!query) {\n return null\n }\n const [first, ...rest] = query.conditions\n const candidates = first\n ? (active.byAttr.get(first.name)?.get(first.value) ?? [])\n : query.tag === '*'\n ? active.all\n : (active.byTag.get(query.tag) ?? [])\n return candidates.filter(\n (el) =>\n (query.tag === '*' || el.nodeName === query.tag) &&\n rest.every(\n (c) => el.hasAttribute(c.name) && el.getAttribute(c.name) === c.value\n )\n )\n}\n","/**\n * Platform-specific element tag constants for mobile automation\n */\n\nexport const ANDROID_INTERACTABLE_TAGS = [\n // Input elements\n 'android.widget.EditText',\n 'android.widget.AutoCompleteTextView',\n 'android.widget.MultiAutoCompleteTextView',\n 'android.widget.SearchView',\n\n // Button-like elements\n 'android.widget.Button',\n 'android.widget.ImageButton',\n 'android.widget.ToggleButton',\n 'android.widget.CompoundButton',\n 'android.widget.RadioButton',\n 'android.widget.CheckBox',\n 'android.widget.Switch',\n 'android.widget.FloatingActionButton',\n 'com.google.android.material.button.MaterialButton',\n 'com.google.android.material.floatingactionbutton.FloatingActionButton',\n\n // Text elements (often tappable)\n 'android.widget.TextView',\n 'android.widget.CheckedTextView',\n\n // Image elements (often tappable)\n 'android.widget.ImageView',\n 'android.widget.QuickContactBadge',\n\n // Selection elements\n 'android.widget.Spinner',\n 'android.widget.SeekBar',\n 'android.widget.RatingBar',\n 'android.widget.ProgressBar',\n 'android.widget.DatePicker',\n 'android.widget.TimePicker',\n 'android.widget.NumberPicker',\n\n // List/grid items\n 'android.widget.AdapterView'\n]\n\nexport const ANDROID_LAYOUT_CONTAINERS = [\n // Core ViewGroup classes\n 'android.view.ViewGroup',\n 'android.view.View',\n 'android.widget.FrameLayout',\n 'android.widget.LinearLayout',\n 'android.widget.RelativeLayout',\n 'android.widget.GridLayout',\n 'android.widget.TableLayout',\n 'android.widget.TableRow',\n 'android.widget.AbsoluteLayout',\n\n // AndroidX layout classes\n 'androidx.constraintlayout.widget.ConstraintLayout',\n 'androidx.coordinatorlayout.widget.CoordinatorLayout',\n 'androidx.appcompat.widget.LinearLayoutCompat',\n 'androidx.cardview.widget.CardView',\n 'androidx.appcompat.widget.ContentFrameLayout',\n 'androidx.appcompat.widget.FitWindowsFrameLayout',\n\n // Scrolling containers\n 'android.widget.ScrollView',\n 'android.widget.HorizontalScrollView',\n 'android.widget.NestedScrollView',\n 'androidx.core.widget.NestedScrollView',\n 'androidx.recyclerview.widget.RecyclerView',\n 'android.widget.ListView',\n 'android.widget.GridView',\n 'android.widget.AbsListView',\n\n // App chrome / system elements\n 'android.widget.ActionBarContainer',\n 'android.widget.ActionBarOverlayLayout',\n 'android.view.ViewStub',\n 'androidx.appcompat.widget.ActionBarContainer',\n 'androidx.appcompat.widget.ActionBarContextView',\n 'androidx.appcompat.widget.ActionBarOverlayLayout',\n\n // Decor views\n 'com.android.internal.policy.DecorView',\n 'android.widget.DecorView'\n]\n\nexport const IOS_INTERACTABLE_TAGS = [\n // Input elements\n 'XCUIElementTypeTextField',\n 'XCUIElementTypeSecureTextField',\n 'XCUIElementTypeTextView',\n 'XCUIElementTypeSearchField',\n\n // Button-like elements\n 'XCUIElementTypeButton',\n 'XCUIElementTypeLink',\n\n // Text elements (often tappable)\n 'XCUIElementTypeStaticText',\n\n // Image elements\n 'XCUIElementTypeImage',\n 'XCUIElementTypeIcon',\n\n // Selection elements\n 'XCUIElementTypeSwitch',\n 'XCUIElementTypeSlider',\n 'XCUIElementTypeStepper',\n 'XCUIElementTypeSegmentedControl',\n 'XCUIElementTypePicker',\n 'XCUIElementTypePickerWheel',\n 'XCUIElementTypeDatePicker',\n 'XCUIElementTypePageIndicator',\n\n // Table/list items\n 'XCUIElementTypeCell',\n 'XCUIElementTypeMenuItem',\n 'XCUIElementTypeMenuBarItem',\n\n // Toggle elements\n 'XCUIElementTypeCheckBox',\n 'XCUIElementTypeRadioButton',\n 'XCUIElementTypeToggle',\n\n // Other interactive\n 'XCUIElementTypeKey',\n 'XCUIElementTypeKeyboard',\n 'XCUIElementTypeAlert',\n 'XCUIElementTypeSheet'\n]\n\nexport const IOS_LAYOUT_CONTAINERS = [\n // Generic containers\n 'XCUIElementTypeOther',\n 'XCUIElementTypeGroup',\n 'XCUIElementTypeLayoutItem',\n\n // Scroll containers\n 'XCUIElementTypeScrollView',\n 'XCUIElementTypeTable',\n 'XCUIElementTypeCollectionView',\n 'XCUIElementTypeScrollBar',\n\n // Navigation chrome\n 'XCUIElementTypeNavigationBar',\n 'XCUIElementTypeTabBar',\n 'XCUIElementTypeToolbar',\n 'XCUIElementTypeStatusBar',\n 'XCUIElementTypeMenuBar',\n\n // Windows and views\n 'XCUIElementTypeWindow',\n 'XCUIElementTypeSheet',\n 'XCUIElementTypeDrawer',\n 'XCUIElementTypeDialog',\n 'XCUIElementTypePopover',\n 'XCUIElementTypePopUpButton',\n\n // Outline elements\n 'XCUIElementTypeOutline',\n 'XCUIElementTypeOutlineRow',\n 'XCUIElementTypeBrowser',\n 'XCUIElementTypeSplitGroup',\n 'XCUIElementTypeSplitter',\n\n // Application root\n 'XCUIElementTypeApplication'\n]\n","/**\n * Locator strategy generation for mobile elements\n */\n\nimport type {\n JSONElement,\n LocatorStrategy,\n LocatorContext,\n UniquenessResult,\n XMLNode,\n XMLDocument\n} from './types.js'\nimport type { Element as XMLElement } from '@xmldom/xmldom'\nimport {\n checkXPathUniqueness,\n evaluateXPath,\n isAttributeUnique\n} from './xml-parsing.js'\n\n/**\n * Check if a string value is valid for use in a locator\n */\nfunction isValidValue(value: string | undefined): value is string {\n return (\n value !== undefined &&\n value !== null &&\n value !== 'null' &&\n value.trim() !== ''\n )\n}\n\n/**\n * Escape special characters in text for use in selectors\n */\nexport function escapeText(text: string): string {\n return text\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/\"/g, '\\\\\"')\n .replace(/\\n/g, '\\\\n')\n .replace(/\\r/g, '\\\\r')\n .replace(/\\t/g, '\\\\t')\n}\n\n/**\n * Escape value for use in XPath expressions\n */\nfunction escapeXPathValue(value: string): string {\n if (!value.includes(\"'\")) {\n return `'${value}'`\n }\n if (!value.includes('\"')) {\n return `\"${value}\"`\n }\n const parts: string[] = []\n let current = ''\n for (const char of value) {\n if (char === \"'\") {\n if (current) {\n parts.push(`'${current}'`)\n }\n parts.push('\"\\'\"')\n current = ''\n } else {\n current += char\n }\n }\n if (current) {\n parts.push(`'${current}'`)\n }\n return `concat(${parts.join(',')})`\n}\n\n/**\n * Wrap non-unique XPath with index\n */\nfunction generateIndexedXPath(baseXPath: string, index: number): string {\n return `(${baseXPath})[${index}]`\n}\n\n/**\n * Add .instance(n) for UiAutomator (0-based)\n */\nfunction generateIndexedUiAutomator(\n baseSelector: string,\n index: number\n): string {\n return `${baseSelector}.instance(${index - 1})`\n}\n\n/**\n * Check uniqueness, falling back to regex if no DOM available\n */\nfunction checkUniqueness(\n ctx: LocatorContext,\n xpath: string,\n targetNode?: XMLNode\n): UniquenessResult {\n if (ctx.parsedDOM) {\n return checkXPathUniqueness(ctx.parsedDOM, xpath, targetNode)\n }\n\n // Bounded quantifiers ({1,200} for attr names, {1,2000} for values) prevent\n // polynomial-time backtracking on near-misses like `//[@<` repeated — CodeQL's\n // js/polynomial-redos query flagged the unbounded form. The bounds are well\n // above any realistic XPath attribute name/value length.\n const match = xpath.match(/\\/\\/\\*\\[@([^=]{1,200})=\"([^\"]{1,2000})\"\\]/)\n if (match) {\n const [, attr, value] = match\n return { isUnique: isAttributeUnique(ctx.sourceXML, attr, value) }\n }\n return { isUnique: false }\n}\n\n/**\n * Get sibling index (1-based) among same-tag siblings\n */\nfunction getSiblingIndex(element: XMLElement): number {\n const parent = element.parentNode\n if (!parent) {\n return 1\n }\n\n const tagName = element.nodeName\n let index = 0\n\n for (let i = 0; i < parent.childNodes.length; i++) {\n const child = parent.childNodes.item(i)\n if (child?.nodeType === 1 && child.nodeName === tagName) {\n index++\n if (child === element) {\n return index\n }\n }\n }\n\n return 1\n}\n\n/**\n * Count siblings with same tag name\n */\nfunction countSiblings(element: XMLElement): number {\n const parent = element.parentNode\n if (!parent) {\n return 1\n }\n\n const tagName = element.nodeName\n let count = 0\n\n for (let i = 0; i < parent.childNodes.length; i++) {\n const child = parent.childNodes.item(i)\n if (child?.nodeType === 1 && child.nodeName === tagName) {\n count++\n }\n }\n\n return count\n}\n\n/**\n * Find unique attribute for element in XPath format\n */\nfunction findUniqueAttribute(\n element: XMLElement,\n ctx: LocatorContext\n): string | null {\n const attrs = ctx.isAndroid\n ? ['resource-id', 'content-desc', 'text']\n : ['name', 'label', 'value']\n\n for (const attr of attrs) {\n const value = element.getAttribute(attr)\n if (value && value.trim()) {\n const xpath = `//*[@${attr}=${escapeXPathValue(value)}]`\n const result = ctx.parsedDOM\n ? checkXPathUniqueness(ctx.parsedDOM, xpath)\n : { isUnique: isAttributeUnique(ctx.sourceXML, attr, value) }\n\n if (result.isUnique) {\n return `@${attr}=${escapeXPathValue(value)}`\n }\n }\n }\n\n return null\n}\n\n/**\n * Build hierarchical XPath by traversing up the DOM tree\n */\nfunction buildHierarchicalXPath(\n ctx: LocatorContext,\n element: XMLElement,\n maxDepth: number = 3\n): string | null {\n if (!ctx.parsedDOM) {\n return null\n }\n\n const pathParts: string[] = []\n let current: XMLElement | null = element\n let depth = 0\n\n while (current && depth < maxDepth) {\n const tagName = current.nodeName\n const uniqueAttr = findUniqueAttribute(current, ctx)\n\n if (uniqueAttr) {\n pathParts.unshift(`//${tagName}[${uniqueAttr}]`)\n break\n } else {\n const siblingIndex = getSiblingIndex(current)\n const siblingCount = countSiblings(current)\n\n if (siblingCount > 1) {\n pathParts.unshift(`${tagName}[${siblingIndex}]`)\n } else {\n pathParts.unshift(tagName)\n }\n }\n\n const parent = current.parentNode as XMLElement | null\n current = parent && parent.nodeType === 1 ? parent : null\n depth++\n }\n\n if (pathParts.length === 0) {\n return null\n }\n\n let result = pathParts[0]\n for (let i = 1; i < pathParts.length; i++) {\n result += '/' + pathParts[i]\n }\n\n if (!result.startsWith('//')) {\n result = '//' + result\n }\n\n return result\n}\n\n/**\n * Add XPath locator with uniqueness checking and fallbacks\n */\nfunction addXPathLocator(\n results: [LocatorStrategy, string][],\n xpath: string,\n ctx: LocatorContext,\n targetNode?: XMLNode\n): void {\n const uniqueness = checkUniqueness(ctx, xpath, targetNode)\n if (uniqueness.isUnique) {\n results.push(['xpath', xpath])\n } else if (uniqueness.index) {\n results.push(['xpath', generateIndexedXPath(xpath, uniqueness.index)])\n } else {\n if (targetNode && ctx.parsedDOM) {\n // @xmldom/xmldom 0.9+ XMLNode doesn't satisfy global Node; safe at runtime\n const hierarchical = buildHierarchicalXPath(\n ctx,\n targetNode as unknown as XMLElement\n )\n if (hierarchical) {\n results.push(['xpath', hierarchical])\n }\n }\n results.push(['xpath', xpath])\n }\n}\n\n/**\n * Check if element is within UiAutomator scope\n */\nfunction isInUiAutomatorScope(\n element: JSONElement,\n doc: XMLDocument | null\n): boolean {\n if (!doc) {\n return true\n }\n\n const hierarchyNodes = evaluateXPath(doc, '/hierarchy/*')\n if (hierarchyNodes.length === 0) {\n return true\n }\n\n const lastIndex = hierarchyNodes.length\n const pathParts = element.path.split('.')\n if (pathParts.length === 0 || pathParts[0] === '') {\n return true\n }\n\n const firstIndex = parseInt(pathParts[0], 10)\n return firstIndex === lastIndex - 1\n}\n\n/**\n * Build Android UiAutomator selector with multiple attributes\n */\nfunction buildUiAutomatorSelector(element: JSONElement): string | null {\n const attrs = element.attributes\n const parts: string[] = []\n\n if (isValidValue(attrs['resource-id'])) {\n parts.push(`resourceId(\"${attrs['resource-id']}\")`)\n }\n if (isValidValue(attrs.text) && attrs.text!.length < 100) {\n parts.push(`text(\"${escapeText(attrs.text!)}\")`)\n }\n if (isValidValue(attrs['content-desc'])) {\n parts.push(`description(\"${attrs['content-desc']}\")`)\n }\n if (isValidValue(attrs.class)) {\n parts.push(`className(\"${attrs.class}\")`)\n }\n\n if (parts.length === 0) {\n return null\n }\n\n return `android=new UiSelector().${parts.join('.')}`\n}\n\n/**\n * Build iOS predicate string with multiple conditions\n */\nfunction buildPredicateString(element: JSONElement): string | null {\n const attrs = element.attributes\n const conditions: string[] = []\n\n if (isValidValue(attrs.name)) {\n conditions.push(`name == \"${escapeText(attrs.name!)}\"`)\n }\n if (isValidValue(attrs.label)) {\n conditions.push(`label == \"${escapeText(attrs.label!)}\"`)\n }\n if (isValidValue(attrs.value)) {\n conditions.push(`value == \"${escapeText(attrs.value!)}\"`)\n }\n if (attrs.visible === 'true') {\n conditions.push('visible == 1')\n }\n if (attrs.enabled === 'true') {\n conditions.push('enabled == 1')\n }\n\n if (conditions.length === 0) {\n return null\n }\n\n return `-ios predicate string:${conditions.join(' AND ')}`\n}\n\n/**\n * Build iOS class chain selector\n */\nfunction buildClassChain(element: JSONElement): string | null {\n const attrs = element.attributes\n const tagName = element.tagName\n\n if (!tagName.startsWith('XCUI')) {\n return null\n }\n\n let selector = `**/${tagName}`\n\n if (isValidValue(attrs.label)) {\n selector += `[\\`label == \"${escapeText(attrs.label!)}\"\\`]`\n } else if (isValidValue(attrs.name)) {\n selector += `[\\`name == \"${escapeText(attrs.name!)}\"\\`]`\n }\n\n return `-ios class chain:${selector}`\n}\n\n/**\n * Build XPath for element with unique identification\n */\nfunction buildXPath(\n element: JSONElement,\n _sourceXML: string,\n isAndroid: boolean\n): string | null {\n const attrs = element.attributes\n const tagName = element.tagName\n const conditions: string[] = []\n\n if (isAndroid) {\n if (isValidValue(attrs['resource-id'])) {\n conditions.push(`@resource-id=\"${attrs['resource-id']}\"`)\n }\n if (isValidValue(attrs['content-desc'])) {\n conditions.push(`@content-desc=\"${attrs['content-desc']}\"`)\n }\n if (isValidValue(attrs.text) && attrs.text!.length < 100) {\n conditions.push(`@text=\"${escapeText(attrs.text!)}\"`)\n }\n } else {\n if (isValidValue(attrs.name)) {\n conditions.push(`@name=\"${attrs.name}\"`)\n }\n if (isValidValue(attrs.label)) {\n conditions.push(`@label=\"${attrs.label}\"`)\n }\n if (isValidValue(attrs.value)) {\n conditions.push(`@value=\"${attrs.value}\"`)\n }\n }\n\n if (conditions.length === 0) {\n return `//${tagName}`\n }\n\n return `//${tagName}[${conditions.join(' and ')}]`\n}\n\n/**\n * Get simple locators based on single attributes\n */\nfunction getSimpleSuggestedLocators(\n element: JSONElement,\n ctx: LocatorContext,\n automationName: string,\n inUiAutomatorScope: boolean,\n targetNode?: XMLNode\n): [LocatorStrategy, string][] {\n const results: [LocatorStrategy, string][] = []\n const isAndroid = automationName.toLowerCase().includes('uiautomator')\n const attrs = element.attributes\n\n if (isAndroid) {\n // Resource ID\n const resourceId = attrs['resource-id']\n if (isValidValue(resourceId)) {\n const xpath = `//*[@resource-id=\"${resourceId}\"]`\n const uniqueness = checkUniqueness(ctx, xpath, targetNode)\n\n if (uniqueness.isUnique && inUiAutomatorScope) {\n results.push([\n 'id',\n `android=new UiSelector().resourceId(\"${resourceId}\")`\n ])\n } else if (uniqueness.index && inUiAutomatorScope) {\n const base = `android=new UiSelector().resourceId(\"${resourceId}\")`\n results.push(['id', generateIndexedUiAutomator(base, uniqueness.index)])\n }\n }\n\n // Content Description\n const contentDesc = attrs['content-desc']\n if (isValidValue(contentDesc)) {\n const xpath = `//*[@content-desc=\"${contentDesc}\"]`\n const uniqueness = checkUniqueness(ctx, xpath, targetNode)\n\n if (uniqueness.isUnique) {\n results.push(['accessibility-id', `~${contentDesc}`])\n }\n }\n\n // Text\n const text = attrs.text\n if (isValidValue(text) && text.length < 100) {\n const xpath = `//*[@text=\"${escapeText(text)}\"]`\n const uniqueness = checkUniqueness(ctx, xpath, targetNode)\n\n if (uniqueness.isUnique && inUiAutomatorScope) {\n results.push([\n 'text',\n `android=new UiSelector().text(\"${escapeText(text)}\")`\n ])\n } else if (uniqueness.index && inUiAutomatorScope) {\n const base = `android=new UiSelector().text(\"${escapeText(text)}\")`\n results.push([\n 'text',\n generateIndexedUiAutomator(base, uniqueness.index)\n ])\n }\n }\n } else {\n // iOS: Accessibility ID (name)\n const name = attrs.name\n if (isValidValue(name)) {\n const xpath = `//*[@name=\"${name}\"]`\n const uniqueness = checkUniqueness(ctx, xpath, targetNode)\n\n if (uniqueness.isUnique) {\n results.push(['accessibility-id', `~${name}`])\n }\n }\n\n // iOS: Label\n const label = attrs.label\n if (isValidValue(label) && label !== attrs.name) {\n const xpath = `//*[@label=\"${escapeText(label)}\"]`\n const uniqueness = checkUniqueness(ctx, xpath, targetNode)\n\n if (uniqueness.isUnique) {\n results.push([\n 'predicate-string',\n `-ios predicate string:label == \"${escapeText(label)}\"`\n ])\n }\n }\n\n // iOS: Value\n const value = attrs.value\n if (isValidValue(value)) {\n const xpath = `//*[@value=\"${escapeText(value)}\"]`\n const uniqueness = checkUniqueness(ctx, xpath, targetNode)\n\n if (uniqueness.isUnique) {\n results.push([\n 'predicate-string',\n `-ios predicate string:value == \"${escapeText(value)}\"`\n ])\n }\n }\n }\n\n return results\n}\n\n/**\n * Get complex locators (combinations, XPath, etc.)\n */\nfunction getComplexSuggestedLocators(\n element: JSONElement,\n ctx: LocatorContext,\n automationName: string,\n inUiAutomatorScope: boolean,\n targetNode?: XMLNode\n): [LocatorStrategy, string][] {\n const results: [LocatorStrategy, string][] = []\n const isAndroid = automationName.toLowerCase().includes('uiautomator')\n\n if (isAndroid) {\n if (inUiAutomatorScope) {\n const uiAutomator = buildUiAutomatorSelector(element)\n if (uiAutomator) {\n results.push(['uiautomator', uiAutomator])\n }\n }\n\n const xpath = buildXPath(element, ctx.sourceXML, true)\n if (xpath) {\n addXPathLocator(results, xpath, ctx, targetNode)\n }\n\n if (inUiAutomatorScope && isValidValue(element.attributes.class)) {\n results.push([\n 'class-name',\n `android=new UiSelector().className(\"${element.attributes.class}\")`\n ])\n }\n } else {\n const predicate = buildPredicateString(element)\n if (predicate) {\n results.push(['predicate-string', predicate])\n }\n\n const classChain = buildClassChain(element)\n if (classChain) {\n results.push(['class-chain', classChain])\n }\n\n const xpath = buildXPath(element, ctx.sourceXML, false)\n if (xpath) {\n addXPathLocator(results, xpath, ctx, targetNode)\n }\n\n const type = element.tagName\n if (type.startsWith('XCUIElementType')) {\n results.push(['class-name', `-ios class chain:**/${type}`])\n }\n }\n\n return results\n}\n\n/**\n * Get all suggested locators for an element\n */\nexport function getSuggestedLocators(\n element: JSONElement,\n sourceXML: string,\n automationName: string,\n ctx?: LocatorContext,\n targetNode?: XMLNode\n): [LocatorStrategy, string][] {\n const locatorCtx = ctx ?? {\n sourceXML,\n parsedDOM: null,\n isAndroid: automationName.toLowerCase().includes('uiautomator')\n }\n\n const inUiAutomatorScope = locatorCtx.isAndroid\n ? isInUiAutomatorScope(element, locatorCtx.parsedDOM)\n : true\n\n const simpleLocators = getSimpleSuggestedLocators(\n element,\n locatorCtx,\n automationName,\n inUiAutomatorScope,\n targetNode\n )\n const complexLocators = getComplexSuggestedLocators(\n element,\n locatorCtx,\n automationName,\n inUiAutomatorScope,\n targetNode\n )\n\n const seen = new Set<string>()\n const results: [LocatorStrategy, string][] = []\n\n for (const locator of [...simpleLocators, ...complexLocators]) {\n if (!seen.has(locator[1])) {\n seen.add(locator[1])\n results.push(locator)\n }\n }\n\n return results\n}\n\n/**\n * Get the best (first priority) locator for an element\n */\nexport function getBestLocator(\n element: JSONElement,\n sourceXML: string,\n automationName: string\n): string | null {\n const locators = getSuggestedLocators(element, sourceXML, automationName)\n return locators.length > 0 ? locators[0][1] : null\n}\n\n/**\n * Convert locator array to object format\n */\nexport function locatorsToObject(\n locators: [LocatorStrategy, string][]\n): Record<string, string> {\n const result: Record<string, string> = {}\n for (const [strategy, value] of locators) {\n if (!result[strategy]) {\n result[strategy] = value\n }\n }\n return result\n}\n","/**\n * AI-readable snapshot serializers\n *\n * Converts accessibility trees and mobile element trees into depth-indented\n * text files that LLMs can consume without any parsing.\n */\n\nimport type {\n AccessibilityNode,\n SnapshotNode,\n SnapshotElement,\n SnapshotResult\n} from './element-types.js'\n\n// The web serializer lives in `trace` because the backend builds the trace for\n// an adapter that cannot, and §2.2 bars the backend from importing core.\nexport {\n serializeWebSnapshot,\n type WebSnapshotOptions\n} from '@wdio/devtools-trace/a11y-snapshot'\n\nimport {\n SNAPSHOT_INDENT_UNIT,\n INTERACTIVE_ROLES,\n isStatictextEchoedByParent,\n SNAPSHOT_LOCATOR_DELIM,\n SNAPSHOT_PURPOSE_TOKEN,\n xpathLocatorTag\n} from '@wdio/devtools-shared'\n\nimport type { JSONElement } from './locators/types.js'\nimport { parseAndroidBounds, parseIOSBounds } from './locators/xml-parsing.js'\nimport {\n ANDROID_INTERACTABLE_TAGS,\n IOS_INTERACTABLE_TAGS\n} from './locators/constants.js'\nimport { getSuggestedLocators } from './locators/locator-generation.js'\n\n// ---------------------------------------------------------------------------\n// Mobile snapshot helpers\n// ---------------------------------------------------------------------------\n\n/** Shorten fully-qualified Android/iOS class names to the last segment. */\nfunction simplifyTag(tagName: string): string {\n const dot = tagName.lastIndexOf('.')\n if (dot !== -1) {\n return tagName.slice(dot + 1)\n }\n return tagName.replace(/^XCUIElementType/, '')\n}\n\n// ---------------------------------------------------------------------------\n// Mobile role classification — maps raw Android/iOS class names to semantic\n// roles so the snapshot reads like the web version (button, textbox, img, …).\n// ---------------------------------------------------------------------------\n\nconst ANDROID_ROLE_MAP: Record<string, string> = {\n 'android.widget.Button': 'button',\n 'android.widget.ImageButton': 'button',\n 'android.widget.ToggleButton': 'button',\n 'android.widget.FloatingActionButton': 'button',\n 'com.google.android.material.button.MaterialButton': 'button',\n 'com.google.android.material.floatingactionbutton.FloatingActionButton':\n 'button',\n 'android.widget.EditText': 'textbox',\n 'android.widget.AutoCompleteTextView': 'textbox',\n 'android.widget.MultiAutoCompleteTextView': 'textbox',\n 'android.widget.SearchView': 'searchbox',\n 'android.widget.ImageView': 'img',\n 'android.widget.QuickContactBadge': 'img',\n 'android.widget.CheckBox': 'checkbox',\n 'android.widget.RadioButton': 'radio',\n 'android.widget.Switch': 'switch',\n 'android.widget.Spinner': 'combobox',\n 'android.widget.SeekBar': 'slider',\n 'android.widget.RatingBar': 'slider',\n 'android.widget.ProgressBar': 'progressbar',\n 'android.widget.TextView': 'statictext',\n 'android.widget.CheckedTextView': 'statictext',\n 'android.widget.RecyclerView': 'list',\n 'android.widget.ListView': 'list',\n 'android.widget.GridView': 'list',\n 'android.webkit.WebView': 'webview'\n}\n\nconst IOS_ROLE_MAP: Record<string, string> = {\n XCUIElementTypeButton: 'button',\n XCUIElementTypeLink: 'link',\n XCUIElementTypeTextField: 'textbox',\n XCUIElementTypeSecureTextField: 'textbox',\n XCUIElementTypeTextView: 'textbox',\n XCUIElementTypeSearchField: 'searchbox',\n XCUIElementTypeImage: 'img',\n XCUIElementTypeIcon: 'img',\n XCUIElementTypeSwitch: 'switch',\n XCUIElementTypeSlider: 'slider',\n XCUIElementTypeStepper: 'slider',\n XCUIElementTypeCheckBox: 'checkbox',\n XCUIElementTypeRadioButton: 'radio',\n XCUIElementTypePicker: 'combobox',\n XCUIElementTypePickerWheel: 'combobox',\n XCUIElementTypeDatePicker: 'combobox',\n XCUIElementTypeSegmentedControl: 'combobox',\n XCUIElementTypeStaticText: 'statictext',\n XCUIElementTypeCell: 'listitem',\n XCUIElementTypeTable: 'list',\n XCUIElementTypeCollectionView: 'list'\n}\n\nfunction classifyMobileRole(\n tagName: string,\n platform: 'android' | 'ios'\n): string {\n if (platform === 'android') {\n return ANDROID_ROLE_MAP[tagName] || simplifyTag(tagName)\n }\n return IOS_ROLE_MAP[tagName] || simplifyTag(tagName)\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Clickable container whose label lives on a child TextView. */\nfunction getFirstChildText(element: JSONElement): string | undefined {\n // Breadth-first: direct children checked before grandchildren.\n // This prefers a direct sibling label over a deeply nested one.\n const queue: JSONElement[] = [...(element.children || [])]\n while (queue.length > 0) {\n const el = queue.shift()!\n const text = el.attributes?.text?.trim()\n if (text) {\n return text\n }\n if (el.children) {\n queue.push(...el.children)\n }\n }\n return undefined\n}\n\n// ---------------------------------------------------------------------------\n// Locator generation\n// ---------------------------------------------------------------------------\n\nfunction getBestAndroidLocator(\n attrs: JSONElement['attributes']\n): string | undefined {\n // Pre-computed by the full locator pipeline (generateAllElementLocators).\n // Takes priority over the simplified fallback logic below.\n if (attrs._selector) {\n return attrs._selector\n }\n // ~ prefix = accessibility-id shorthand in WebdriverIO ($('~foo'))\n if (attrs['content-desc']) {\n return `~${attrs['content-desc']}`\n }\n if (attrs['resource-id']) {\n return `id:${attrs['resource-id']}`\n }\n if (attrs.text) {\n return `~${attrs.text}`\n }\n // Fallback: class-based locator (only useful with :nth-of-type or index)\n if (attrs.class) {\n return `class:${simplifyTag(attrs.class)}`\n }\n return undefined\n}\n\nfunction getBestIOSLocator(\n attrs: JSONElement['attributes']\n): string | undefined {\n // Pre-computed by the full locator pipeline.\n if (attrs._selector) {\n return attrs._selector\n }\n // ~ prefix = accessibility-id shorthand (maps to `name` on iOS)\n if (attrs.name) {\n return `~${attrs.name}`\n }\n if (attrs.label) {\n return `~${attrs.label}`\n }\n if (attrs.value) {\n return `~${attrs.value}`\n }\n // Fallback: class-based locator\n if (attrs.type) {\n return `class:${simplifyTag(attrs.type)}`\n }\n return undefined\n}\n\n// ---------------------------------------------------------------------------\n// Identity\n// ---------------------------------------------------------------------------\n\nfunction getMobileNodeIdentity(\n attrs: JSONElement['attributes'],\n platform: 'android' | 'ios'\n): string {\n if (platform === 'android') {\n const contentDesc = attrs['content-desc']\n if (contentDesc) {\n return contentDesc\n }\n if (attrs.text) {\n return attrs.text\n }\n // Fall back to the last segment of the resource-id (e.g. \"search_action_bar\")\n const rid = attrs['resource-id']\n if (rid) {\n const slash = rid.lastIndexOf('/')\n return slash !== -1 ? rid.slice(slash + 1) : rid\n }\n return ''\n }\n return attrs.name || attrs.label || attrs.value || attrs.text || ''\n}\n\n// ---------------------------------------------------------------------------\n// Interactivity\n// ---------------------------------------------------------------------------\n\nconst ANDROID_INTERACTABLE_SET = new Set(ANDROID_INTERACTABLE_TAGS)\nconst IOS_INTERACTABLE_SET = new Set(IOS_INTERACTABLE_TAGS)\n\n/** An element is *explicitly* interactive when it carries a click/focus/check\n * attribute — as opposed to being interactive only because its tag is in the\n * interactable-tag list. Explicit parents should carry the → selector, not\n * their tag-interactive children. */\nfunction isExplicitlyInteractive(\n attrs: JSONElement['attributes'],\n platform: 'android' | 'ios'\n): boolean {\n if (platform === 'android') {\n return (\n attrs.clickable === 'true' ||\n attrs.focusable === 'true' ||\n attrs.checkable === 'true' ||\n attrs['long-clickable'] === 'true'\n )\n }\n return attrs.accessible === 'true'\n}\n\nfunction isMobileInteractive(\n element: JSONElement,\n platform: 'android' | 'ios'\n): boolean {\n const attrs = element.attributes\n if (platform === 'android') {\n if (ANDROID_INTERACTABLE_SET.has(element.tagName)) {\n return true\n }\n return (\n attrs.clickable === 'true' ||\n attrs['long-clickable'] === 'true' ||\n attrs.focusable === 'true' ||\n attrs.checkable === 'true'\n )\n }\n if (IOS_INTERACTABLE_SET.has(element.tagName)) {\n return true\n }\n return attrs.accessible === 'true'\n}\n\n// ---------------------------------------------------------------------------\n// Viewport\n// ---------------------------------------------------------------------------\n\ninterface WalkMobileOptions {\n inViewportOnly: boolean\n viewport: { width: number; height: number }\n /** Raw page-source XML. When provided, the full locator pipeline is used. */\n sourceXML?: string\n /** 'uiautomator2' or 'xcuitest'. Required when sourceXML is set. */\n automationName?: string\n}\n\nfunction isMobileInViewport(\n element: JSONElement,\n platform: 'android' | 'ios',\n viewport: { width: number; height: number }\n): boolean {\n const bounds =\n platform === 'android'\n ? parseAndroidBounds(element.attributes.bounds || '')\n : parseIOSBounds(element.attributes)\n\n if (bounds.width === 0 && bounds.height === 0) {\n return true\n }\n\n return (\n bounds.x >= 0 &&\n bounds.y >= 0 &&\n bounds.width > 0 &&\n bounds.height > 0 &&\n bounds.x + bounds.width <= viewport.width &&\n bounds.y + bounds.height <= viewport.height\n )\n}\n\n// ---------------------------------------------------------------------------\n// Flat-node representation (mirrors AccessibilityNode so both pipelines share\n// inferPurpose, dedup, and rendering logic).\n// ---------------------------------------------------------------------------\n\ninterface MobileFlatNode {\n role: string\n name: string\n selector: string\n depth: number\n isInteractive: boolean\n /** True when the element has clickable/focusable/checkable — the intended tap target. */\n isExplicitInteractive: boolean\n isInViewport: boolean\n tagName: string\n}\n\n/**\n * First pass: walk the JSONElement tree, apply viewport filtering and\n * collect every node into a flat array with semantic roles and selectors.\n */\nfunction collectMobileNodes(\n element: JSONElement,\n platform: 'android' | 'ios',\n depth: number,\n nodes: MobileFlatNode[],\n walkOpts: WalkMobileOptions\n): void {\n const attrs = element.attributes\n const role = classifyMobileRole(element.tagName, platform)\n let name = getMobileNodeIdentity(attrs, platform)\n const explicit = isExplicitlyInteractive(attrs, platform)\n const interactive = isMobileInteractive(element, platform)\n const inViewport = isMobileInViewport(element, platform, walkOpts.viewport)\n\n // Viewport filtering\n if (walkOpts.inViewportOnly) {\n if (interactive && !inViewport) {\n // Skip this node but still recurse (scroll children may be in view).\n for (const child of element.children || []) {\n collectMobileNodes(child, platform, depth + 1, nodes, walkOpts)\n }\n return\n }\n if (!interactive && !inViewport) {\n // Collapse off-screen container to a placeholder.\n nodes.push({\n role: 'generic',\n name: name ? `${role} \"${name}\"` : role,\n selector: '',\n depth,\n isInteractive: false,\n isExplicitInteractive: false,\n isInViewport: false,\n tagName: element.tagName\n })\n return\n }\n }\n\n // Generate a selector for every interactive element.\n // Use the full locator pipeline when source XML is available;\n // otherwise fall back to the simplified attribute-based heuristics.\n let locator = ''\n if (interactive) {\n if (walkOpts.sourceXML && walkOpts.automationName) {\n // Full pipeline: accessible-id, id, text, uiautomator, xpath, class-name\n const suggested = getSuggestedLocators(\n element,\n walkOpts.sourceXML,\n walkOpts.automationName,\n {\n sourceXML: walkOpts.sourceXML,\n parsedDOM: null,\n isAndroid: platform === 'android'\n }\n )\n if (suggested.length > 0) {\n locator = suggested[0][1] // first = best priority\n }\n }\n if (!locator) {\n // Simplified fallback\n locator =\n (platform === 'android'\n ? getBestAndroidLocator(attrs)\n : getBestIOSLocator(attrs)) ?? ''\n }\n\n // When the only locator is class-based and the element has no name,\n // pull a label from a child — common in Android native apps where a\n // clickable container row's label lives on a child TextView. We\n // enrich the name rather than replacing the selector so the locator\n // still targets the correct (parent) element.\n if (\n !name &&\n platform === 'android' &&\n (locator.startsWith('class:') ||\n locator.startsWith('android=new UiSelector().className(\"'))\n ) {\n const childText = getFirstChildText(element)\n if (childText && childText.length < 100) {\n name = childText\n }\n }\n }\n\n nodes.push({\n role,\n name,\n selector: locator,\n depth,\n isInteractive: interactive,\n isExplicitInteractive: explicit,\n isInViewport: inViewport,\n tagName: element.tagName\n })\n\n for (const child of element.children || []) {\n collectMobileNodes(child, platform, depth + 1, nodes, walkOpts)\n }\n}\n\n// ---------------------------------------------------------------------------\n// Context inference — shared with the web pipeline.\n// Same-depth structural siblings (img, statictext, heading, …) provide\n// context for following interactive nodes.\n// ---------------------------------------------------------------------------\n\nconst MOBILE_STRUCTURAL_ROLES = new Set([\n 'img',\n 'heading',\n 'list',\n 'listitem',\n 'webview',\n 'progressbar',\n 'slider',\n 'switch',\n 'generic'\n])\n\nfunction mobileInferPurpose(\n nodes: MobileFlatNode[],\n index: number\n): string | undefined {\n const myDepth = nodes[index].depth\n for (let i = index - 1; i >= 0; i--) {\n if (nodes[i].depth <= myDepth && nodes[i].name) {\n if (\n nodes[i].depth === myDepth &&\n !MOBILE_STRUCTURAL_ROLES.has(nodes[i].role)\n ) {\n continue\n }\n return nodes[i].name\n }\n }\n return undefined\n}\n\n// ---------------------------------------------------------------------------\n// When a tag-only-interactive child (e.g. a statictext TextView) sits\n// directly under an explicitly-interactive parent (e.g. a clickable\n// LinearLayout row), the *parent* should carry the → selector — the\n// child is just a label. Suppress the child's interactivity so the\n// parent renders as the actionable element.\n// ---------------------------------------------------------------------------\n\nfunction suppressTagOnlyChildren(nodes: MobileFlatNode[]): void {\n for (let i = 0; i < nodes.length; i++) {\n const node = nodes[i]\n if (!node.isInteractive || node.isExplicitInteractive) {\n continue\n }\n // Walk up through ALL ancestors looking for an explicitly-interactive\n // parent. The immediate depth-1 parent may just be a layout wrapper;\n // the real clickable row could be 2-3 levels up.\n for (let j = i - 1; j >= 0; j--) {\n if (nodes[j].depth < node.depth) {\n if (nodes[j].isExplicitInteractive) {\n node.isInteractive = false\n break // found — suppress and stop\n }\n // keep looking upward through the ancestor chain\n }\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Render pass: flat nodes into lines with ∈ context, dedup, noise filter,\n// and class-instance indexing.\n// ---------------------------------------------------------------------------\n\n/** Layout roles that carry no semantic meaning by themselves. */\nconst NOISY_ROLES = new Set([\n 'FrameLayout',\n 'LinearLayout',\n 'ViewGroup',\n 'RelativeLayout',\n 'View',\n 'CardView',\n 'ConstraintLayout',\n 'ScrollView'\n])\n\n/**\n * Pre-count selector occurrences so we can attach .instance(N) suffixes\n * to duplicate selectors.\n */\nfunction countSelectors(nodes: MobileFlatNode[]): Map<string, number> {\n const counts = new Map<string, number>()\n for (const node of nodes) {\n if (node.selector) {\n counts.set(node.selector, (counts.get(node.selector) ?? 0) + 1)\n }\n }\n return counts\n}\n\nfunction renderMobileNodes(nodes: MobileFlatNode[]): string[] {\n const lines: string[] = []\n const selectorCounts = countSelectors(nodes)\n const selectorIndex = new Map<string, number>()\n\n for (let i = 0; i < nodes.length; i++) {\n const node = nodes[i]\n const indent = SNAPSHOT_INDENT_UNIT.repeat(node.depth + 1)\n\n // Collapse anonymous layout containers at depth ≥ 2.\n // Keep depth 0-1 structural chrome and any named container.\n if (\n NOISY_ROLES.has(node.role) &&\n !node.name &&\n node.depth > 1 &&\n !node.isInteractive\n ) {\n continue\n }\n\n // Off-screen containers rendered as collapsed placeholders\n if (node.isInViewport === false && !node.isInteractive) {\n lines.push(`${indent}⋯ ${node.name} (off-screen)`)\n continue\n }\n\n // Dedup: skip statictext whose text is echoed by the parent interactive element\n if (node.role === 'statictext' && node.name) {\n let echoedByParent = false\n for (let j = i - 1; j >= 0; j--) {\n if (nodes[j].depth < node.depth) {\n if (\n nodes[j].isInteractive &&\n nodes[j].name &&\n nodes[j].name.includes(node.name)\n ) {\n echoedByParent = true\n }\n break\n }\n }\n if (echoedByParent) {\n continue\n }\n }\n\n if (node.isInteractive && node.selector) {\n // Append .instance(N) when the same selector repeats\n let selector = node.selector\n const total = selectorCounts.get(selector) ?? 1\n if (total > 1) {\n const idx = selectorIndex.get(selector) ?? 0\n selectorIndex.set(selector, idx + 1)\n selector = `${selector}.instance(${idx})`\n }\n\n const purpose = mobileInferPurpose(nodes, i)\n if (node.name) {\n lines.push(\n purpose\n ? `${indent}${node.role} \"${node.name}\" ${SNAPSHOT_PURPOSE_TOKEN} \"${purpose}\" ${SNAPSHOT_LOCATOR_DELIM} ${selector}`\n : `${indent}${node.role} \"${node.name}\" ${SNAPSHOT_LOCATOR_DELIM} ${selector}`\n )\n } else if (purpose) {\n lines.push(\n `${indent}${node.role} ${SNAPSHOT_PURPOSE_TOKEN} \"${purpose}\" ${SNAPSHOT_LOCATOR_DELIM} ${selector}`\n )\n } else {\n lines.push(\n `${indent}${node.role} ${SNAPSHOT_LOCATOR_DELIM} ${selector}`\n )\n }\n } else {\n // Container / structural / non-locatable\n lines.push(\n node.name\n ? `${indent}${node.role} \"${node.name}\"`\n : `${indent}${node.role}`\n )\n }\n }\n\n return lines\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\nexport interface MobileSnapshotOptions {\n /** Only include elements whose bounds intersect the viewport (default true). */\n inViewportOnly?: boolean\n /**\n * Raw XML page source string. When provided the full locator pipeline\n * (getSuggestedLocators) runs on every interactive node, producing the same\n * selectors that getElements() returns. Omit to use simplified heuristics.\n */\n sourceXML?: string\n}\n\n/**\n * Serialize a mobile element tree into a depth-indented text snapshot.\n *\n * @param root Root JSONElement from the page source XML parse\n * @param context Platform, optional device name, viewport, and source XML.\n * Include `sourceXML` to use the full locator pipeline.\n * @param options {@link MobileSnapshotOptions}\n */\nexport function serializeMobileSnapshot(\n root: JSONElement,\n context: {\n platform: 'android' | 'ios'\n deviceName?: string\n viewport?: { width: number; height: number }\n /** Raw page-source XML. When set, selectors match getElements() output. */\n sourceXML?: string\n },\n options: MobileSnapshotOptions = {}\n): string {\n const { platform, deviceName, viewport, sourceXML } = context\n const { inViewportOnly = true } = options\n\n // Auto-detect source XML stashed by getMobileVisibleElementsWithTree\n const effectiveXML = sourceXML || root.attributes._sourceXML\n\n const effectiveViewport = viewport ?? { width: 9999, height: 9999 }\n const automationName = platform === 'android' ? 'uiautomator2' : 'xcuitest'\n\n let header = `[${platform}`\n if (deviceName) {\n header += ` — ${deviceName}`\n }\n if (viewport) {\n header += ` (${viewport.width}×${viewport.height})`\n }\n header += ']'\n\n const nodes: MobileFlatNode[] = []\n collectMobileNodes(root, platform, 0, nodes, {\n inViewportOnly,\n viewport: effectiveViewport,\n sourceXML: effectiveXML,\n automationName: effectiveXML ? automationName : undefined\n })\n\n // Let explicitly-interactive parents carry the → selector\n suppressTagOnlyChildren(nodes)\n\n const lines = renderMobileNodes(nodes)\n return [header, ...lines].join('\\n')\n}\n\n// ---------------------------------------------------------------------------\n// Unified snapshot formatter — web + mobile share the same render pass.\n// ---------------------------------------------------------------------------\n\n/** Derive a tag name from a locator prefix (e.g. `//button[contains(., \"Submit\")]`\n * or \"button*=Submit\" → \"button\"). */\nfunction extractTagFromSelector(selector: string, fallback: string): string {\n const xpathTag = xpathLocatorTag(selector)\n if (xpathTag) {\n return xpathTag\n }\n // Matches tag name followed by a CSS selector combinator or operator.\n // Supports hyphenated custom elements (my-component) and pseudo-classes (:nth-of-type).\n const match = selector.match(\n /^([a-z][a-z0-9]*(?:-[a-z][a-z0-9]*)*)[*.#\\[:=(^$~]/\n )\n if (match) {\n return match[1]\n }\n const spaceMatch = selector.match(/^([a-z][a-z0-9]*(?:-[a-z][a-z0-9]*)*)\\s/)\n if (spaceMatch) {\n return spaceMatch[1]\n }\n return fallback\n}\n\n/** Walk backwards to find the nearest structural container name for ∈ context. */\nfunction findContextName(\n nodes: SnapshotNode[],\n index: number\n): string | undefined {\n const myDepth = nodes[index].depth\n for (let i = index - 1; i >= 0; i--) {\n if (nodes[i].depth <= myDepth && nodes[i].name) {\n if (nodes[i].depth === myDepth && nodes[i].isInteractive) {\n continue\n }\n // Suppressed tag-interactive nodes (isInteractive=false but role is a\n // mobile-interactable label like 'statictext') shouldn't provide context\n // at same depth — their name is label text, not a container identity.\n // Mirrors mobileInferPurpose which skips same-depth nodes not in\n // MOBILE_STRUCTURAL_ROLES (which excludes 'statictext').\n if (nodes[i].depth === myDepth && nodes[i].role === 'statictext') {\n continue\n }\n return nodes[i].name\n }\n }\n return undefined\n}\n\n/**\n * Core formatter — converts a flat SnapshotNode[] into a text tree with\n * eN virtual IDs and an elements map for selector resolution.\n *\n * Platform-agnostic: both web and mobile pipelines feed into this function.\n */\nexport function buildSnapshot(\n header: string,\n nodes: SnapshotNode[]\n): SnapshotResult {\n const selectorCounts = new Map<string, number>()\n for (const node of nodes) {\n if (node.isInteractive && node.selector) {\n selectorCounts.set(\n node.selector,\n (selectorCounts.get(node.selector) ?? 0) + 1\n )\n }\n }\n\n const lines: string[] = [header]\n const elements: Record<string, SnapshotElement> = {}\n const selectorIndex = new Map<string, number>()\n let counter = 1\n\n for (let i = 0; i < nodes.length; i++) {\n const node = nodes[i]\n const indent = ' '.repeat(node.depth + 1)\n\n if (node.isInteractive && node.selector) {\n let selector = node.selector\n const total = selectorCounts.get(selector) ?? 1\n if (total > 1) {\n const idx = selectorIndex.get(selector) ?? 0\n selectorIndex.set(selector, idx + 1)\n selector = `${selector}.instance(${idx})`\n }\n\n const eId = `e${counter++}`\n\n const roleLabel =\n node.role === 'heading' && node.level\n ? `heading[${node.level}]`\n : node.role\n\n const context = findContextName(nodes, i)\n if (node.name && context) {\n lines.push(\n `${indent}${eId} ${roleLabel} \"${node.name}\" ∈ \"${context}\" → ${selector}`\n )\n } else if (node.name) {\n lines.push(\n `${indent}${eId} ${roleLabel} \"${node.name}\" → ${selector}`\n )\n } else if (context) {\n lines.push(\n `${indent}${eId} ${roleLabel} ∈ \"${context}\" → ${selector}`\n )\n } else {\n lines.push(`${indent}${eId} ${roleLabel} → ${selector}`)\n }\n\n elements[eId] = {\n selector: node.selector,\n ...(selector !== node.selector ? { qualifiedSelector: selector } : {}),\n tagName: node.tagName,\n role: node.role,\n text: node.name\n }\n } else {\n const roleLabel =\n node.role === 'heading' && node.level\n ? `heading[${node.level}]`\n : node.role\n lines.push(\n node.name\n ? `${indent}${roleLabel} \"${node.name}\"`\n : `${indent}${roleLabel}`\n )\n }\n }\n\n return { text: lines.join('\\n'), elements }\n}\n\n// ---------------------------------------------------------------------------\n// Web adapter — AccessibilityNode[] → SnapshotNode[]\n// ---------------------------------------------------------------------------\n\nexport function accessibilityNodesToSnapshotNodes(\n nodes: AccessibilityNode[],\n options?: { inViewportOnly?: boolean }\n): SnapshotNode[] {\n const { inViewportOnly = true } = options ?? {}\n\n const result: SnapshotNode[] = []\n\n for (let i = 0; i < nodes.length; i++) {\n const node = nodes[i]\n\n if (inViewportOnly && node.isInViewport === false) {\n continue\n }\n\n const isInteractive = INTERACTIVE_ROLES.has(node.role)\n\n if (isStatictextEchoedByParent(nodes, i)) {\n continue\n }\n\n // Interactive nodes without a selector can't be acted on — skip them\n // so they don't leak as non-actionable entries in the text tree.\n // Matches the guard in serializeWebSnapshot (line 141).\n if (isInteractive && !node.selector) {\n continue\n }\n\n const tagName =\n isInteractive && node.selector\n ? extractTagFromSelector(node.selector, node.role)\n : node.role\n\n result.push({\n role: node.role,\n name: node.name,\n selector: node.selector,\n depth: node.depth,\n isInteractive,\n tagName,\n level: node.level || undefined\n })\n }\n\n return result\n}\n\n// ---------------------------------------------------------------------------\n// Mobile adapter — JSONElement tree → SnapshotNode[]\n// ---------------------------------------------------------------------------\n\nexport function jsonElementToSnapshotNodes(\n root: JSONElement,\n platform: 'android' | 'ios',\n options?: {\n inViewportOnly?: boolean\n viewport?: { width: number; height: number }\n sourceXML?: string\n }\n): SnapshotNode[] {\n const { inViewportOnly = true } = options ?? {}\n const effectiveViewport = options?.viewport ?? { width: 9999, height: 9999 }\n const automationName = platform === 'android' ? 'uiautomator2' : 'xcuitest'\n const effectiveXML = options?.sourceXML || root.attributes._sourceXML\n\n const mobileNodes: MobileFlatNode[] = []\n collectMobileNodes(root, platform, 0, mobileNodes, {\n inViewportOnly,\n viewport: effectiveViewport,\n sourceXML: effectiveXML,\n automationName: effectiveXML ? automationName : undefined\n })\n\n suppressTagOnlyChildren(mobileNodes)\n\n return mobileNodes.map((m) => ({\n role: m.role,\n name: m.name,\n selector: m.selector,\n depth: m.depth,\n isInteractive: m.isInteractive,\n tagName: m.tagName,\n level: undefined\n }))\n}\n","/**\n * Element filtering logic for mobile automation\n */\n\nimport type { JSONElement, FilterOptions } from './types.js'\nimport {\n ANDROID_INTERACTABLE_TAGS,\n IOS_INTERACTABLE_TAGS,\n ANDROID_LAYOUT_CONTAINERS,\n IOS_LAYOUT_CONTAINERS\n} from './constants.js'\n\n/**\n * Check if element tag matches any in the list (handles partial matches)\n */\nfunction matchesTagList(tagName: string, tagList: string[]): boolean {\n if (tagList.includes(tagName)) {\n return true\n }\n\n for (const tag of tagList) {\n if (tagName.endsWith(tag) || tagName.includes(tag)) {\n return true\n }\n }\n\n return false\n}\n\n/**\n * Check if element matches tag name filters\n */\nfunction matchesTagFilters(\n element: JSONElement,\n includeTagNames: string[],\n excludeTagNames: string[]\n): boolean {\n if (\n includeTagNames.length > 0 &&\n !matchesTagList(element.tagName, includeTagNames)\n ) {\n return false\n }\n\n if (matchesTagList(element.tagName, excludeTagNames)) {\n return false\n }\n\n return true\n}\n\n/**\n * Check if element matches attribute-based filters\n */\nfunction matchesAttributeFilters(\n element: JSONElement,\n requireAttributes: string[],\n minAttributeCount: number\n): boolean {\n if (requireAttributes.length > 0) {\n const hasRequiredAttr = requireAttributes.some(\n (attr) => element.attributes?.[attr]\n )\n if (!hasRequiredAttr) {\n return false\n }\n }\n\n if (element.attributes && minAttributeCount > 0) {\n const attrCount = Object.values(element.attributes).filter(\n (v) => v !== undefined && v !== null && v !== ''\n ).length\n if (attrCount < minAttributeCount) {\n return false\n }\n }\n\n return true\n}\n\n/**\n * Check if element is interactable based on platform\n */\nexport function isInteractableElement(\n element: JSONElement,\n _isNative: boolean,\n automationName: string\n): boolean {\n const isAndroid = automationName.toLowerCase().includes('uiautomator')\n const interactableTags = isAndroid\n ? ANDROID_INTERACTABLE_TAGS\n : IOS_INTERACTABLE_TAGS\n\n if (matchesTagList(element.tagName, interactableTags)) {\n return true\n }\n\n if (isAndroid) {\n if (\n element.attributes?.clickable === 'true' ||\n element.attributes?.focusable === 'true' ||\n element.attributes?.checkable === 'true' ||\n element.attributes?.['long-clickable'] === 'true'\n ) {\n return true\n }\n }\n\n if (!isAndroid) {\n if (element.attributes?.accessible === 'true') {\n return true\n }\n }\n\n return false\n}\n\n/**\n * Check if element is a layout container\n */\nexport function isLayoutContainer(\n element: JSONElement,\n platform: 'android' | 'ios'\n): boolean {\n const containerList =\n platform === 'android' ? ANDROID_LAYOUT_CONTAINERS : IOS_LAYOUT_CONTAINERS\n return matchesTagList(element.tagName, containerList)\n}\n\n/**\n * Check if element has meaningful content (text, accessibility info)\n */\nexport function hasMeaningfulContent(\n element: JSONElement,\n platform: 'android' | 'ios'\n): boolean {\n const attrs = element.attributes\n\n if (attrs.text && attrs.text.trim() !== '' && attrs.text !== 'null') {\n return true\n }\n\n if (platform === 'android') {\n if (\n attrs['content-desc'] &&\n attrs['content-desc'].trim() !== '' &&\n attrs['content-desc'] !== 'null'\n ) {\n return true\n }\n } else {\n if (attrs.label && attrs.label.trim() !== '' && attrs.label !== 'null') {\n return true\n }\n if (attrs.name && attrs.name.trim() !== '' && attrs.name !== 'null') {\n return true\n }\n }\n\n return false\n}\n\n/**\n * Determine if an element should be included based on all filter criteria\n */\nexport function shouldIncludeElement(\n element: JSONElement,\n filters: FilterOptions,\n isNative: boolean,\n automationName: string\n): boolean {\n const {\n includeTagNames = [],\n excludeTagNames = ['hierarchy'],\n requireAttributes = [],\n minAttributeCount = 0,\n fetchableOnly = false,\n clickableOnly = false,\n visibleOnly = true\n } = filters\n\n if (!matchesTagFilters(element, includeTagNames, excludeTagNames)) {\n if (element.attributes?.clickable !== 'true') {\n return false\n }\n }\n\n if (!matchesAttributeFilters(element, requireAttributes, minAttributeCount)) {\n return false\n }\n\n if (clickableOnly && element.attributes?.clickable !== 'true') {\n return false\n }\n\n if (visibleOnly) {\n const isAndroid = automationName.toLowerCase().includes('uiautomator')\n if (isAndroid && element.attributes?.displayed === 'false') {\n return false\n }\n if (!isAndroid && element.attributes?.visible === 'false') {\n return false\n }\n }\n\n if (\n fetchableOnly &&\n !isInteractableElement(element, isNative, automationName)\n ) {\n return false\n }\n\n return true\n}\n\n/**\n * Get default filter options for a platform\n */\nexport function getDefaultFilters(\n platform: 'android' | 'ios',\n includeContainers: boolean = false\n): FilterOptions {\n const layoutContainers =\n platform === 'android' ? ANDROID_LAYOUT_CONTAINERS : IOS_LAYOUT_CONTAINERS\n\n return {\n excludeTagNames: includeContainers\n ? ['hierarchy']\n : ['hierarchy', ...layoutContainers],\n fetchableOnly: !includeContainers,\n visibleOnly: true,\n clickableOnly: false\n }\n}\n","/**\n * Mobile element locator generation\n *\n * Main orchestrator module that coordinates XML parsing, element filtering,\n * and locator generation for mobile automation.\n */\n\n// Types\nexport type {\n ElementAttributes,\n JSONElement,\n Bounds,\n FilterOptions,\n UniquenessResult,\n LocatorStrategy,\n LocatorContext,\n ElementWithLocators,\n GenerateLocatorsOptions\n} from './types.js'\n\n// Constants\nexport {\n ANDROID_INTERACTABLE_TAGS,\n IOS_INTERACTABLE_TAGS,\n ANDROID_LAYOUT_CONTAINERS,\n IOS_LAYOUT_CONTAINERS\n} from './constants.js'\n\n// XML Parsing\nexport {\n xmlToJSON,\n xmlToDOM,\n evaluateXPath,\n checkXPathUniqueness,\n findDOMNodeByPath,\n parseAndroidBounds,\n parseIOSBounds,\n flattenElementTree,\n countAttributeOccurrences,\n isAttributeUnique\n} from './xml-parsing.js'\nimport { withXPathIndex } from './xpath-index.js'\n\n// Element Filtering\nexport {\n isInteractableElement,\n isLayoutContainer,\n hasMeaningfulContent,\n shouldIncludeElement,\n getDefaultFilters\n} from './element-filter.js'\n\n// Locator Generation\nexport {\n getSuggestedLocators,\n getBestLocator,\n locatorsToObject\n} from './locator-generation.js'\n\nimport type {\n JSONElement,\n FilterOptions,\n LocatorStrategy,\n ElementWithLocators,\n GenerateLocatorsOptions,\n XMLDocument\n} from './types.js'\n\nimport {\n xmlToJSON,\n xmlToDOM,\n parseAndroidBounds,\n parseIOSBounds,\n findDOMNodeByPath\n} from './xml-parsing.js'\nimport {\n shouldIncludeElement,\n isLayoutContainer,\n hasMeaningfulContent\n} from './element-filter.js'\nimport { getSuggestedLocators, locatorsToObject } from './locator-generation.js'\n\ninterface ProcessingContext {\n sourceXML: string\n platform: 'android' | 'ios'\n automationName: string\n isNative: boolean\n viewportSize: { width: number; height: number }\n filters: FilterOptions\n inViewportOnly: boolean\n results: ElementWithLocators[]\n parsedDOM: XMLDocument | null\n}\n\n/**\n * Parse element bounds based on platform\n */\nfunction parseBounds(\n element: JSONElement,\n platform: 'android' | 'ios'\n): { x: number; y: number; width: number; height: number } {\n return platform === 'android'\n ? parseAndroidBounds(element.attributes.bounds || '')\n : parseIOSBounds(element.attributes)\n}\n\n/**\n * Check if bounds are within viewport\n */\nfunction isWithinViewport(\n bounds: { x: number; y: number; width: number; height: number },\n viewport: { width: number; height: number }\n): boolean {\n return (\n bounds.x >= 0 &&\n bounds.y >= 0 &&\n bounds.width > 0 &&\n bounds.height > 0 &&\n bounds.x + bounds.width <= viewport.width &&\n bounds.y + bounds.height <= viewport.height\n )\n}\n\n/**\n * Transform JSONElement to ElementWithLocators\n */\nfunction transformElement(\n element: JSONElement,\n locators: [LocatorStrategy, string][],\n ctx: ProcessingContext\n): ElementWithLocators {\n const attrs = element.attributes\n const bounds = parseBounds(element, ctx.platform)\n\n return {\n tagName: element.tagName,\n locators: locatorsToObject(locators),\n text: attrs.text || attrs.label || '',\n contentDesc: attrs['content-desc'] || '',\n resourceId: attrs['resource-id'] || '',\n accessibilityId: attrs.name || attrs['content-desc'] || '',\n label: attrs.label || '',\n value: attrs.value || '',\n className: attrs.class || element.tagName,\n clickable:\n attrs.clickable === 'true' ||\n attrs.accessible === 'true' ||\n attrs['long-clickable'] === 'true',\n enabled: attrs.enabled !== 'false',\n displayed:\n ctx.platform === 'android'\n ? attrs.displayed !== 'false'\n : attrs.visible !== 'false',\n bounds,\n isInViewport: isWithinViewport(bounds, ctx.viewportSize)\n }\n}\n\n/**\n * Check if element should be processed\n */\nfunction shouldProcess(element: JSONElement, ctx: ProcessingContext): boolean {\n if (\n shouldIncludeElement(element, ctx.filters, ctx.isNative, ctx.automationName)\n ) {\n return true\n }\n return (\n isLayoutContainer(element, ctx.platform) &&\n hasMeaningfulContent(element, ctx.platform)\n )\n}\n\n/**\n * Process a single element and add to results if valid\n */\nfunction processElement(element: JSONElement, ctx: ProcessingContext): void {\n if (!shouldProcess(element, ctx)) {\n return\n }\n\n // Skip off-screen elements early when viewport filtering is on —\n // avoids expensive locator generation for elements the caller doesn't want.\n if (ctx.inViewportOnly) {\n const b = parseBounds(element, ctx.platform)\n if (!isWithinViewport(b, ctx.viewportSize)) {\n return\n }\n }\n\n try {\n const targetNode = ctx.parsedDOM\n ? findDOMNodeByPath(ctx.parsedDOM, element.path)\n : undefined\n\n const locators = getSuggestedLocators(\n element,\n ctx.sourceXML,\n ctx.automationName,\n {\n sourceXML: ctx.sourceXML,\n parsedDOM: ctx.parsedDOM,\n isAndroid: ctx.platform === 'android'\n },\n targetNode || undefined\n )\n if (locators.length === 0) {\n return\n }\n\n // Stash the best locator on the tree node so serializeMobileSnapshot\n // can reuse the full locator pipeline instead of recomputing.\n element.attributes._selector = locators[0][1]\n\n const transformed = transformElement(element, locators, ctx)\n if (Object.keys(transformed.locators).length === 0) {\n return\n }\n\n ctx.results.push(transformed)\n } catch (error) {\n // Core is logger-free; console.error provides the required\n // \"enough detail to debug\" per the error-handling convention.\n // A single bad element never fails the entire page-source walk.\n console.error(`[processElement] Error at path ${element.path}:`, error)\n }\n}\n\n/**\n * Recursively traverse and process element tree\n */\nfunction traverseTree(\n element: JSONElement | null,\n ctx: ProcessingContext\n): void {\n if (!element) {\n return\n }\n\n processElement(element, ctx)\n\n for (const child of element.children || []) {\n traverseTree(child, ctx)\n }\n}\n\n/**\n * Generate locators for all elements from page source XML\n */\nexport function generateAllElementLocators(\n sourceXML: string,\n options: GenerateLocatorsOptions\n): ElementWithLocators[] {\n const sourceJSON = xmlToJSON(sourceXML)\n\n if (!sourceJSON) {\n // Core is logger-free; console.error is the only signal that XML\n // parsing failed — the caller receives an empty result silently otherwise.\n console.error(\n '[generateAllElementLocators] Failed to parse page source XML'\n )\n return []\n }\n\n const parsedDOM = xmlToDOM(sourceXML)\n\n const ctx: ProcessingContext = {\n sourceXML,\n platform: options.platform,\n automationName:\n options.platform === 'android' ? 'uiautomator2' : 'xcuitest',\n isNative: options.isNative ?? true,\n viewportSize: options.viewportSize ?? { width: 9999, height: 9999 },\n filters: options.filters ?? {},\n inViewportOnly: options.inViewportOnly ?? true,\n results: [],\n parsedDOM\n }\n\n withXPathIndex(parsedDOM, () => traverseTree(sourceJSON, ctx))\n\n return ctx.results\n}\n","// Resolves `promise` if it settles before `ms`; otherwise resolves to the\n// supplied fallback value. Used by per-action snapshot capture to guard\n// against hung in-page scripts (heavy pages, infinite-render loops) without\n// stalling the user's test.\n\nexport function withTimeout<T>(\n promise: Promise<T>,\n ms: number,\n fallback: T\n): Promise<T> {\n let timer: ReturnType<typeof setTimeout> | undefined\n const timeout = new Promise<T>((resolve) => {\n timer = setTimeout(() => resolve(fallback), ms)\n })\n // .finally on the race result clears the timer in BOTH the resolved AND\n // rejected branch — earlier code only cleared on fulfillment, leaving a\n // dangling timer per failed probe that delayed process exit by up to `ms`.\n return Promise.race([promise, timeout]).finally(() => {\n if (timer) {\n clearTimeout(timer)\n }\n })\n}\n\n/** Default ceiling for a single in-page snapshot probe. */\nexport const SNAPSHOT_PROBE_TIMEOUT_MS = 2500\n\n/** Ceiling for a driver round-trip probe (screenshot, url, title). Larger than\n * the in-page ceiling because encoding a full-page screenshot legitimately\n * takes longer than reading the DOM, but still inside the adapters' 5s\n * settle window so a hung probe can't strand the whole capture. */\nexport const SNAPSHOT_DRIVER_PROBE_TIMEOUT_MS = 4000\n","// Adapter-agnostic per-action snapshot capture. Each adapter wires its own\n// `runScript`, `takeScreenshot`, etc. shim so the actual capture pipeline\n// (timeouts, fallbacks, snapshot serialization) lives in one place.\n\nimport { buildElementScripts } from '@wdio/devtools-shared/element-scripts'\nimport {\n serializeWebSnapshot,\n serializeMobileSnapshot\n} from './element-snapshot.js'\nimport type { AccessibilityNode, BrowserElementInfo } from './element-types.js'\nimport { xmlToJSON } from './locators/xml-parsing.js'\nimport {\n generateAllElementLocators,\n getDefaultFilters\n} from './locators/index.js'\nimport {\n SNAPSHOT_DRIVER_PROBE_TIMEOUT_MS,\n SNAPSHOT_PROBE_TIMEOUT_MS,\n withTimeout\n} from './with-timeout.js'\nimport type { ActionSnapshot, TestRunnerId } from '@wdio/devtools-shared'\n\nexport type ScriptRunner = (scriptSrc: string) => Promise<unknown>\n\nexport interface CaptureActionSnapshotInput {\n command: string\n /** The logged command's timestamp. Export claims snapshots by exact equality\n * (`claimAfter`/`elementsAt`) and slices them by command time, so a snapshot\n * stamped with its own capture time binds to no action at all. */\n timestamp?: number\n /** Browser script runner — omit on native mobile where Appium can't execute JS. */\n runScript?: ScriptRunner\n takeScreenshot?: () => Promise<string | null | undefined>\n getUrl?: () => Promise<string | null | undefined>\n getTitle?: () => Promise<string | null | undefined>\n /** Page-source XML fetcher for native mobile — used instead of runScript. */\n getPageSource?: () => Promise<string | undefined>\n /** Platform identifier for mobile snapshot formatting ('android' | 'ios'). */\n platform?: 'android' | 'ios'\n /** Runner this capture belongs to — decides which text-locator dialect the\n * injected scripts emit. Omitted → the portable XPath form. */\n runner?: TestRunnerId\n}\n\nasync function runWith<T>(\n runScript: ScriptRunner | undefined,\n scriptSrc: string,\n fallback: T\n): Promise<T> {\n if (!runScript) {\n return fallback\n }\n\n return withTimeout(\n // A driver can answer `null` rather than reject (no-such-session, a script\n // error swallowed by the transport). Passing that through hands the\n // serializers a non-array where they expect one, which throws and loses the\n // WHOLE snapshot — screenshot, url and all — not just the probe.\n runScript(scriptSrc).then((r) =>\n r === null || r === undefined ? fallback : (r as T)\n ),\n SNAPSHOT_PROBE_TIMEOUT_MS,\n fallback\n ).catch(() => fallback)\n}\n\n/**\n * Run one driver-side probe under a timeout, resolving to `undefined` on a\n * timeout, a rejection, or a synchronous throw.\n *\n * Every probe is guarded, not just the in-page scripts: a framework whose\n * url/title/screenshot readers go through its own command queue blocks\n * indefinitely when called from inside a command hook, and an unguarded probe\n * in this `Promise.all` stranded the entire capture — observed as 10 of 14\n * Nightwatch captures never settling, so those actions reached the trace with\n * no DOM, no a11y tree and no element rects at all.\n */\nfunction probe<T>(\n read: (() => Promise<T | null | undefined>) | undefined\n): Promise<T | undefined> {\n if (!read) {\n return Promise.resolve(undefined)\n }\n // `Promise.resolve().then(read)` so a probe that throws synchronously becomes\n // a rejection this catch can absorb, rather than escaping the Promise.all.\n return withTimeout(\n Promise.resolve()\n .then(read)\n .then((value) => value ?? undefined),\n SNAPSHOT_DRIVER_PROBE_TIMEOUT_MS,\n undefined\n ).catch(() => undefined)\n}\n\n/** Native-mobile snapshot text + locators, derived from the page-source XML. */\nfunction fromPageSource(\n pageSource: string,\n platform: 'android' | 'ios'\n): { snapshotText: string; elements: unknown[] } {\n const jsonTree = xmlToJSON(pageSource)\n let snapshotText = `[${platform}]`\n if (jsonTree) {\n jsonTree.attributes._sourceXML = pageSource\n snapshotText = serializeMobileSnapshot(jsonTree, {\n platform,\n sourceXML: pageSource\n })\n }\n try {\n const filters = getDefaultFilters(platform, false)\n return {\n snapshotText,\n elements: generateAllElementLocators(pageSource, {\n platform,\n viewportSize: { width: 9999, height: 9999 },\n filters,\n inViewportOnly: false\n })\n }\n } catch {\n // Non-fatal — snapshot text is the primary deliverable.\n return { snapshotText, elements: [] }\n }\n}\n\nexport async function captureActionSnapshot(\n input: CaptureActionSnapshotInput\n): Promise<ActionSnapshot | null> {\n try {\n const timestamp = input.timestamp ?? Date.now()\n const isNativeMobile = !input.runScript && !!input.getPageSource\n // The same pair the backend serves the Python adapter, so the two cannot\n // capture different things. `elements` carries bounds — the per-action\n // element rects drive A8 input points.\n const scripts = buildElementScripts(input.runner)\n\n // Probe order is load-bearing, not cosmetic. A driver serialises requests\n // per session, so `Promise.all` starting them together still has them served\n // in the order issued — and the screenshot is by far the slowest. Issued\n // first, it delayed the cheap reads behind it by hundreds of ms, long enough\n // for the next command to navigate: a fill on the login page reported the\n // page its submit had already reached. Cheapest-and-most-order-sensitive\n // first, screenshot last; the screenshot is served no later than before.\n const [url, title, pageSource, tree, elements, shot] = await Promise.all([\n probe(input.getUrl),\n probe(input.getTitle),\n isNativeMobile ? probe(input.getPageSource) : undefined,\n runWith<AccessibilityNode[]>(\n input.runScript,\n scripts.accessibilityTree,\n []\n ),\n runWith<BrowserElementInfo[]>(input.runScript, scripts.elements, []),\n probe(input.takeScreenshot)\n ])\n\n const mobile =\n isNativeMobile && pageSource\n ? fromPageSource(pageSource, input.platform ?? 'android')\n : undefined\n\n return {\n timestamp,\n command: input.command,\n url,\n title,\n screenshot: shot ?? undefined,\n elements: mobile?.elements ?? elements,\n snapshotText:\n mobile?.snapshotText ?? serializeWebSnapshot(tree, { url, title })\n }\n } catch {\n return null\n }\n}\n","// Framework-agnostic capture-and-attach of per-test trace artifacts to Allure.\n// The *produce* half (screenshot/video write, retention decision) already lives\n// in core; this module adds the orchestration + the attach, behind a pluggable\n// `AllureAttachSink` so each adapter supplies only its reporter binding:\n// - WDIO service → @wdio/allure-reporter's addAttachment\n// - Selenium → allure-js-commons' attachment() (runtime-agnostic)\n// - Nightwatch → no sink (produce-only; its Allure path is post-hoc, with no\n// live attach API), so artifacts land in the manifest only.\n// A missing/undefined sink means \"produce but don't attach\" — never an error.\n\nimport fs from 'node:fs/promises'\nimport { basename } from 'node:path'\nimport type {\n ActionSnapshot,\n DevToolsMode,\n ScreencastFrame,\n TraceGranularity,\n TraceScreenshotPolicy,\n TraceVideoPolicy\n} from '@wdio/devtools-shared'\nimport {\n shouldCaptureScreenshot,\n writeScreenshotArtifact\n} from './screenshot-artifact.js'\nimport { encodePerTestVideo, sliceFramesFrom } from './video-slice.js'\nimport {\n shouldRetainTrace,\n type TestOutcome\n} from '@wdio/devtools-trace/trace-retention'\nimport type { TraceArtifact } from './trace-finalizer.js'\n\n/** Adapter-supplied binding to the active Allure reporter. Attaches one file to\n * the currently-executing test. Undefined = attach unsupported (produce-only). */\nexport type AllureAttachSink = (\n name: string,\n content: Buffer,\n contentType: string\n) => void | Promise<void>\n\ntype LogFn = (level: 'info' | 'warn', message: string) => void\n\n// Trace stays a plain zip download (the viewer is the user's choice, not a\n// third-party viewer Allure would open for a trace-specific content type);\n// video/image render inline.\nconst CONTENT_TYPE_BY_KIND: Record<TraceArtifact['kind'], string> = {\n trace: 'application/zip',\n video: 'video/webm',\n screenshot: 'image/png'\n}\n\n/**\n * Read a retained artifact and hand it to the sink for attachment to the current\n * Allure test. No-op when the artifact wasn't retained, has no path, the sink is\n * absent, or the path is a directory (the ndjson-directory trace format). Never\n * throws — a missing/unreadable artifact must not reject the caller's hook.\n */\nexport async function attachTraceArtifact(\n artifact: TraceArtifact,\n sink: AllureAttachSink | undefined,\n onLog?: LogFn\n): Promise<void> {\n if (!sink || !artifact.retained || !artifact.path) {\n return\n }\n try {\n const stat = await fs.stat(artifact.path)\n if (!stat.isFile()) {\n return\n }\n const content = await fs.readFile(artifact.path)\n await sink(\n basename(artifact.path),\n content,\n CONTENT_TYPE_BY_KIND[artifact.kind]\n )\n } catch (err) {\n onLog?.(\n 'warn',\n `Allure attach skipped for ${artifact.path}: ${String(err)}`\n )\n }\n}\n\n/** A snapshot's command for a session that ran no action at all, so the frame\n * carries no result to show and may be a blank post-teardown page. Written by\n * the service's per-scenario finalize, skipped by `lastRenderedScreenshot`.\n * Shared rather than repeated: a rename on one side would silently stop the\n * skip from matching and start attaching those frames as test screenshots. */\nexport const FINAL_SNAPSHOT_COMMAND = '__final__'\n\n/**\n * The base64 of the last rendered action snapshot for the current test, skipping\n * a `FINAL_SNAPSHOT_COMMAND` frame — which a session that ran no action at all\n * produces, so it carries no result to show and may be a blank post-teardown\n * page. Scoped to `>= startWallTime` so a test that captured nothing doesn't\n * borrow the previous test's frame. Reused as the per-test screenshot —\n * reload-immune and one fewer WebDriver command than a fresh end-of-test\n * capture.\n */\nexport function lastRenderedScreenshot(\n snapshots: readonly ActionSnapshot[],\n startWallTime: number\n): string | undefined {\n for (let i = snapshots.length - 1; i >= 0; i--) {\n const snap = snapshots[i]!\n if (snap.timestamp < startWallTime) {\n return undefined\n }\n if (snap.command !== FINAL_SNAPSHOT_COMMAND && snap.screenshot) {\n return snap.screenshot\n }\n }\n return undefined\n}\n\n/**\n * Write the per-test screenshot (per policy) from an already-captured base64\n * frame, record it in the manifest, and attach it via the sink. No-op outside\n * `test`-granularity trace mode, without a uid/session/frame, or when the policy\n * declines. Gated to `test` granularity so the whole per-test inline story\n * (trace + screenshot + video) is one rule.\n */\nexport async function captureAndAttachScreenshot(input: {\n mode: DevToolsMode | undefined\n granularity: TraceGranularity | undefined\n policy: TraceScreenshotPolicy | undefined\n failed: boolean\n /** Base64 of the last rendered action snapshot (reload-immune). */\n screenshotBase64: string | undefined\n sessionId: string | undefined\n outputDir: string\n testUid: string | undefined\n attach: AllureAttachSink | undefined\n onArtifact: (artifact: TraceArtifact) => void\n onLog?: LogFn\n}): Promise<void> {\n const { mode, granularity, policy, failed, screenshotBase64, sessionId } =\n input\n if (\n mode !== 'trace' ||\n granularity !== 'test' ||\n !screenshotBase64 ||\n !sessionId ||\n !input.testUid ||\n !shouldCaptureScreenshot(policy, failed)\n ) {\n return\n }\n const artifact = await writeScreenshotArtifact({\n outputDir: input.outputDir,\n testUid: input.testUid,\n sessionId,\n base64: screenshotBase64\n })\n input.onArtifact(artifact)\n await attachTraceArtifact(artifact, input.attach, input.onLog)\n}\n\n/**\n * Slice the continuous screencast to this test's window, encode a `.webm`,\n * record it in the manifest, and attach it via the sink — when trace mode +\n * `test` granularity + a non-`off` `video` policy retains this test's outcome.\n * Empty outcomes (test never started) skip rather than fail-open onto the\n * previous test's frames.\n */\nexport async function captureAndAttachVideo(input: {\n mode: DevToolsMode | undefined\n granularity: TraceGranularity | undefined\n policy: TraceVideoPolicy | undefined\n frames: readonly ScreencastFrame[] | undefined\n startWallTime: number\n outcomes: TestOutcome[]\n attempt: number | undefined\n outputDir: string\n testUid: string | undefined\n sessionId: string | undefined\n captureFormat?: 'jpeg' | 'png'\n attach: AllureAttachSink | undefined\n onArtifact: (artifact: TraceArtifact) => void\n onLog?: LogFn\n}): Promise<void> {\n const { mode, granularity, policy, frames, testUid, sessionId } = input\n if (\n mode !== 'trace' ||\n granularity !== 'test' ||\n !policy ||\n policy === 'off' ||\n !frames ||\n !testUid ||\n !sessionId ||\n input.outcomes.length === 0\n ) {\n return\n }\n if (\n !shouldRetainTrace(policy, {\n outcomes: input.outcomes,\n attemptInfoAvailable: true\n }).retain\n ) {\n return\n }\n const artifact = await encodePerTestVideo({\n frames: sliceFramesFrom(frames, input.startWallTime),\n outputDir: input.outputDir,\n testUid,\n sessionId,\n attempt: input.attempt,\n captureFormat: input.captureFormat,\n onLog: input.onLog\n })\n if (!artifact) {\n return\n }\n input.onArtifact(artifact)\n await attachTraceArtifact(artifact, input.attach, input.onLog)\n}\n","/**\n * Per-test screenshot artifact: the policy decision and the file write, kept\n * framework-agnostic so every adapter feeds it the same way (the adapter only\n * supplies the base64 capture from its own driver). Mirrors Playwright's\n * `screenshot` option — `on` after every test, `only-on-failure` after a\n * failing one — and produces a TraceArtifact the manifest and the Allure glue\n * consume like any other artifact.\n */\n\nimport fs from 'node:fs/promises'\nimport path from 'node:path'\nimport type { TraceScreenshotPolicy } from '@wdio/devtools-shared'\nimport { fileSlug } from './artifact-naming.js'\nimport type { TraceArtifact } from './trace-finalizer.js'\n\n/** Whether a per-test screenshot should be captured for this outcome. */\nexport function shouldCaptureScreenshot(\n policy: TraceScreenshotPolicy | undefined,\n failed: boolean\n): boolean {\n if (policy === 'on') {\n return true\n }\n if (policy === 'only-on-failure') {\n return failed\n }\n return false\n}\n\n/**\n * Write a base64 PNG (from the driver's screenshot command) next to the trace\n * output and return the artifact describing it. Retained is always true — a\n * screenshot is only ever written when the policy already decided to capture.\n */\nexport async function writeScreenshotArtifact(input: {\n outputDir: string\n testUid: string\n sessionId: string\n base64: string\n}): Promise<TraceArtifact> {\n await fs.mkdir(input.outputDir, { recursive: true })\n const filename = `screenshot-${fileSlug(input.testUid)}-${input.sessionId.slice(0, 8)}.png`\n const filePath = path.join(input.outputDir, filename)\n await fs.writeFile(filePath, Buffer.from(input.base64, 'base64'))\n return {\n kind: 'screenshot',\n path: filePath,\n scope: 'test',\n key: input.testUid,\n testUids: [input.testUid],\n retained: true\n }\n}\n","/**\n * Per-test video: slice the continuous screencast frame buffer to one test's\n * wall-clock window and encode that slice to a `.webm`. Framework-agnostic —\n * the adapter supplies the recorder's frames and the test's start time; the\n * recorder itself (CDP push on Chrome, screenshot polling elsewhere) lives in\n * each adapter. Best-effort: too few frames or a missing ffmpeg yields no\n * artifact rather than an error, so video never aborts a run.\n */\n\nimport fs from 'node:fs/promises'\nimport path from 'node:path'\nimport type { ScreencastFrame } from '@wdio/devtools-shared'\nimport { fileSlug } from './artifact-naming.js'\nimport { encodeToVideo } from './video-encoder.js'\nimport { errorMessage } from './error.js'\nimport type { TraceArtifact } from './trace-finalizer.js'\n\n/** Frames captured at or after `startWallTime` (ms) — one test's window, since\n * the buffer runs continuously and the next test's frames arrive later. */\nexport function sliceFramesFrom(\n frames: readonly ScreencastFrame[],\n startWallTime: number\n): ScreencastFrame[] {\n return frames.filter((f) => f.timestamp >= startWallTime)\n}\n\n/**\n * Encode a test's frame slice to `<outputDir>/video-<uid>-<session>.webm` and\n * return the artifact. Returns undefined when there are too few frames to make\n * a video or when encoding fails (e.g. fluent-ffmpeg absent) — video is\n * best-effort and must never abort the run.\n */\nexport async function encodePerTestVideo(input: {\n frames: readonly ScreencastFrame[]\n outputDir: string\n testUid: string\n sessionId: string\n /** 0-based attempt; a `-retry<n>` suffix for n>0 keeps retries from\n * overwriting each other's video (mirrors the trace slice's retry keys). */\n attempt?: number\n captureFormat?: 'jpeg' | 'png'\n minFrames?: number\n onLog?: (level: 'info' | 'warn', message: string) => void\n}): Promise<TraceArtifact | undefined> {\n const minFrames = input.minFrames ?? 2\n if (input.frames.length < minFrames) {\n return undefined\n }\n await fs.mkdir(input.outputDir, { recursive: true })\n const retrySuffix = input.attempt ? `-retry${input.attempt}` : ''\n const filename = `video-${fileSlug(input.testUid)}-${input.sessionId.slice(0, 8)}${retrySuffix}.webm`\n const filePath = path.join(input.outputDir, filename)\n try {\n await encodeToVideo([...input.frames], filePath, {\n captureFormat: input.captureFormat\n })\n } catch (err) {\n input.onLog?.('warn', `Per-test video encode failed: ${errorMessage(err)}`)\n return undefined\n }\n return {\n kind: 'video',\n path: filePath,\n scope: 'test',\n key: input.testUid,\n testUids: [input.testUid],\n retained: true\n }\n}\n","// VP8/WebM encoder for screencast frames. Loads fluent-ffmpeg lazily via\n// createRequire so the dep stays optional — adapters that ship screencast\n// support are expected to list fluent-ffmpeg in their own dependencies.\n\nimport fs from 'node:fs/promises'\nimport path from 'node:path'\nimport os from 'node:os'\nimport { createRequire } from 'node:module'\n\nimport type { ScreencastFrame, ScreencastOptions } from '@wdio/devtools-shared'\n\nconst require = createRequire(import.meta.url)\n\n/**\n * Encode an array of CDP screencast frames into a .webm file using ffmpeg\n * (via fluent-ffmpeg) and the VP8 codec (libvpx).\n *\n * Strategy:\n * 1. Write each frame as a JPEG (or PNG) file in a temp directory.\n * 2. Write an ffconcat manifest that assigns each frame its exact display\n * duration based on the inter-frame timestamp delta. Variable-frame-rate\n * output reflects real timing even across long command pauses.\n * 3. Run ffmpeg with the concat demuxer → libvpx (VP8) → .webm output.\n * Force CFR at 10fps — VFR WebMs don't write Cues reliably, so the\n * dashboard `<video>` can't read duration/seek without it.\n * 4. Clean up the temp directory regardless of success or failure.\n *\n * @throws If no frames are provided, fluent-ffmpeg is not installed, or\n * the ffmpeg binary is not found on PATH.\n */\n// fluent-ffmpeg is loaded lazily and its types aren't worth pinning — we\n// only call a tiny chained builder API on it. `unknown` at the boundary\n// + a single cast in the runner keeps the surface honest.\ntype FfmpegBuilder = {\n input(path: string): FfmpegBuilder\n inputOptions(opts: string[]): FfmpegBuilder\n videoCodec(name: string): FfmpegBuilder\n outputOptions(opts: string[]): FfmpegBuilder\n output(path: string): FfmpegBuilder\n on(event: 'end', cb: () => void): FfmpegBuilder\n on(event: 'error', cb: (err: Error) => void): FfmpegBuilder\n run(): void\n}\ntype FfmpegFactory = () => FfmpegBuilder\n\nfunction loadFfmpeg(): FfmpegFactory {\n try {\n return require('fluent-ffmpeg') as FfmpegFactory\n } catch {\n throw new Error(\n 'VideoEncoder: fluent-ffmpeg is required for screencast encoding. ' +\n 'Install it with: npm install fluent-ffmpeg'\n )\n }\n}\n\nasync function writeFramesAndManifest(\n frames: ScreencastFrame[],\n tmpDir: string,\n ext: string\n): Promise<string> {\n const manifestLines: string[] = ['ffconcat version 1.0']\n for (let i = 0; i < frames.length; i++) {\n const frameName = `frame-${String(i).padStart(6, '0')}.${ext}`\n const framePath = path.join(tmpDir, frameName)\n await fs.writeFile(framePath, Buffer.from(frames[i].data, 'base64'))\n const nextTs = frames[i + 1]?.timestamp ?? frames[i].timestamp + 100\n const durationSecs = Math.max((nextTs - frames[i].timestamp) / 1000, 0.01)\n manifestLines.push(`file '${framePath}'`)\n manifestLines.push(`duration ${durationSecs.toFixed(6)}`)\n }\n // The last frame needs to appear twice in the manifest — ffconcat ignores\n // the final `duration` directive without a trailing `file` line.\n const lastFramePath = path.join(\n tmpDir,\n `frame-${String(frames.length - 1).padStart(6, '0')}.${ext}`\n )\n manifestLines.push(`file '${lastFramePath}'`)\n const manifestPath = path.join(tmpDir, 'manifest.txt')\n await fs.writeFile(manifestPath, manifestLines.join('\\n'))\n return manifestPath\n}\n\nfunction classifyFfmpegError(err: Error): Error {\n const msg = err.message || ''\n if (\n msg.includes('Cannot find ffmpeg') ||\n msg.includes('ENOENT') ||\n msg.includes('spawn') ||\n msg.includes('not found')\n ) {\n return new Error(\n 'VideoEncoder: ffmpeg binary not found on PATH. ' +\n 'Install ffmpeg: https://ffmpeg.org/download.html'\n )\n }\n return new Error(`VideoEncoder: ffmpeg error — ${msg}`)\n}\n\nfunction runFfmpeg(\n ffmpeg: FfmpegFactory,\n manifestPath: string,\n outputPath: string\n): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n ffmpeg()\n .input(manifestPath)\n .inputOptions(['-f', 'concat', '-safe', '0'])\n .videoCodec('libvpx')\n .outputOptions([\n '-b:v',\n '1M',\n '-pix_fmt',\n 'yuv420p',\n '-vsync',\n 'cfr',\n '-r',\n '10',\n // Force a keyframe every frame. VP8 can only seek to keyframes, and\n // libvpx's default GOP places a single keyframe on near-static\n // screencast content — so scrubber/marker seeks snap back to the\n // start. At 10fps over short recordings, all-intra keeps seeking\n // frame-accurate at an acceptable size.\n '-g',\n '1',\n '-keyint_min',\n '1',\n '-auto-alt-ref',\n '0',\n '-disposition:v',\n 'default'\n ])\n .output(outputPath)\n .on('end', () => resolve())\n .on('error', (err: Error) => reject(classifyFfmpegError(err)))\n .run()\n })\n}\n\nexport async function encodeToVideo(\n frames: ScreencastFrame[],\n outputPath: string,\n options: Pick<ScreencastOptions, 'captureFormat'> = {}\n): Promise<void> {\n if (frames.length === 0) {\n throw new Error('VideoEncoder: no frames to encode')\n }\n const ffmpeg = loadFfmpeg()\n const ext = options.captureFormat === 'png' ? 'png' : 'jpg'\n const tmpDir = await fs.mkdtemp(\n path.join(os.tmpdir(), 'devtools-screencast-')\n )\n try {\n const manifestPath = await writeFramesAndManifest(frames, tmpDir, ext)\n await runFfmpeg(ffmpeg, manifestPath, outputPath)\n } finally {\n await fs.rm(tmpDir, { recursive: true, force: true }).catch(() => {\n /* tmp cleanup is best-effort */\n })\n }\n}\n","// Direct WebDriver HTTP transport for the capture probes.\n//\n// A probe issued from inside an adapter's own command hook re-enters the driver\n// while the command it is meant to observe is still in flight. Where the driver\n// serialises per session that probe enqueues behind that command and neither\n// one resolves: Nightwatch's command queue does it always, Appium does it for a\n// mobile-web session (#374, measured at 6m13s of timeouts against 1.6s without\n// capture attached). Going straight to the driver's HTTP endpoint bypasses the\n// queue the client library owns, which is the only escape that does not change\n// the ordering guarantee the pre-action snapshot depends on.\n\nimport http from 'node:http'\nimport https from 'node:https'\nimport { errorMessage } from './error.js'\n\n/** Ceiling on a single driver request — a driver that stops answering must not\n * hold a capture open longer than the adapter's settle window. */\nexport const WEBDRIVER_REQUEST_TIMEOUT_MS = 5000\n\n/** Where a driver is reachable, and what it needs to answer. Resolved by each\n * adapter from its own client, since no two expose it the same way. */\nexport interface WebDriverAddress {\n hostname: string\n port: number\n /** Defaults to http. A cloud grid is https, and a plain-http transport\n * reaches it as a connection error rather than an auth failure. */\n protocol?: string\n /** Base prefix ahead of `/session`, e.g. `/wd/hub`. WDIO's default is `/`. */\n path?: string\n /** Basic-auth pair for a cloud grid; sent only when both are present. */\n user?: string\n key?: string\n headers?: Record<string, string>\n /** Adapter's logger. Core carries no logging dependency of its own. */\n onWarn?: (message: string) => void\n}\n\n/** `/wd/hub` and `/` both reach us; only the former belongs in a URL. */\nfunction basePrefix(path: string | undefined): string {\n if (!path || path === '/') {\n return ''\n }\n return path.endsWith('/') ? path.slice(0, -1) : path\n}\n\n/** An IPv6 literal has to be bracketed in a URL, or its own colons read as the\n * port separator and the endpoint is unparseable — which would take a driver\n * that is perfectly reachable and silently disable every direct probe. */\nfunction formatHost(hostname: string): string {\n const isIpv6Literal = hostname.includes(':') && !hostname.startsWith('[')\n return isIpv6Literal ? `[${hostname}]` : hostname\n}\n\nexport function sessionEndpoint(\n address: WebDriverAddress,\n sessionId: string,\n path: string\n): string {\n const protocol = address.protocol ?? 'http'\n const prefix = basePrefix(address.path)\n const host = formatHost(address.hostname)\n return `${protocol}://${host}:${address.port}${prefix}/session/${sessionId}/${path}`\n}\n\nfunction authHeaders(address: WebDriverAddress): Record<string, string> {\n if (!address.user || !address.key) {\n return {}\n }\n const encoded = Buffer.from(`${address.user}:${address.key}`).toString(\n 'base64'\n )\n return { authorization: `Basic ${encoded}` }\n}\n\n/** A W3C error payload: `value` carries `error`/`message` instead of the\n * command's result. Shape-checked rather than status-checked because\n * chromedriver answers some failures with a 200. */\nfunction isWebdriverError(value: unknown): boolean {\n return (\n typeof value === 'object' &&\n value !== null &&\n !Array.isArray(value) &&\n typeof (value as { error?: unknown }).error === 'string'\n )\n}\n\nfunction requestHeaders(\n address: WebDriverAddress,\n payload: string | undefined\n): Record<string, string> {\n return {\n ...authHeaders(address),\n ...address.headers,\n ...(payload\n ? {\n 'content-type': 'application/json',\n 'content-length': String(Buffer.byteLength(payload))\n }\n : {})\n }\n}\n\nfunction resolveFromResponse<T>(\n res: http.IncomingMessage,\n endpoint: string,\n warn: (message: string) => void,\n resolve: (value: T | null) => void\n): void {\n let raw = ''\n let settled = false\n // Every path below has to land exactly once. `end` and `close` both fire on\n // a healthy response, and a reset fires `error` before either.\n const settle = (value: T | null) => {\n if (!settled) {\n settled = true\n resolve(value)\n }\n }\n res.on('data', (chunk: string | Buffer) => {\n raw += chunk\n })\n res.on('end', () => {\n try {\n const value = JSON.parse(raw).value\n // A W3C error answers 200-shaped JSON whose `value` is\n // `{error, message, stacktrace}` — an OBJECT where the caller expects\n // its payload. Casting that through as `T` put an error object into a\n // screencast frame's `data`, and the run's whole trace was then lost to\n // `Buffer.from(object)` at export.\n settle(isWebdriverError(value) ? null : ((value as T) ?? null))\n } catch {\n warn(`Failed to parse response from ${endpoint}`)\n settle(null)\n }\n })\n // A stream `error` with no listener is thrown, which would take the process\n // down over a probe; a reset after headers would otherwise never settle.\n res.on('error', (err: Error) => {\n warn(`Response failed (${endpoint}): ${errorMessage(err)}`)\n settle(null)\n })\n // Fires after `end` on a healthy response, where `settle` is already spent.\n // Reaching it first means the response was truncated.\n res.on('close', () => settle(null))\n}\n\n/** Resolves the W3C `value` field, or null on any transport/parse/timeout\n * failure — a probe is best-effort and never fails the user's test. */\nexport function webdriverRequest<T>(\n address: WebDriverAddress,\n endpoint: string,\n method: 'GET' | 'POST',\n body?: unknown\n): Promise<T | null> {\n const payload = body === undefined ? undefined : JSON.stringify(body)\n const transport = endpoint.startsWith('https:') ? https : http\n const warn = address.onWarn ?? (() => {})\n return new Promise((resolve) => {\n // `request` throws SYNCHRONOUSLY on an endpoint it cannot parse (a bare\n // IPv6 hostname is the reachable case). Unguarded that rejects the promise\n // instead of resolving null, and the rejection surfaces in the command\n // hook as a failure of the user's command rather than a skipped probe.\n let req: http.ClientRequest\n try {\n req = transport.request(\n endpoint,\n { method, headers: requestHeaders(address, payload) },\n (res) => resolveFromResponse<T>(res, endpoint, warn, resolve)\n )\n } catch (err) {\n warn(`Request could not be issued (${endpoint}): ${errorMessage(err)}`)\n resolve(null)\n return\n }\n req.on('error', (err) => {\n warn(`Request failed (${endpoint}): ${errorMessage(err)}`)\n resolve(null)\n })\n req.setTimeout(WEBDRIVER_REQUEST_TIMEOUT_MS, () => {\n warn(`Request timed out (${endpoint})`)\n req.destroy()\n resolve(null)\n })\n if (payload) {\n req.write(payload)\n }\n req.end()\n })\n}\n\n/** GET `/session/:id/<path>`. Null when the session is gone or the call fails. */\nexport function webdriverGet<T>(\n address: WebDriverAddress,\n sessionId: string,\n path: string\n): Promise<T | null> {\n return webdriverRequest<T>(\n address,\n sessionEndpoint(address, sessionId, path),\n 'GET'\n )\n}\n\n/** POST `/session/:id/<path>`. Null when the session is gone or the call fails. */\nexport function webdriverPost<T>(\n address: WebDriverAddress,\n sessionId: string,\n path: string,\n body: unknown\n): Promise<T | null> {\n return webdriverRequest<T>(\n address,\n sessionEndpoint(address, sessionId, path),\n 'POST',\n body\n )\n}\n\n/** Run a script in the page, outside whatever queue the client library owns.\n * `body` is a function body, matching what `browser.execute` accepts. */\nexport function webdriverExecute<T>(\n address: WebDriverAddress,\n sessionId: string,\n body: string,\n args: unknown[] = []\n): Promise<T | null> {\n return webdriverPost<T>(address, sessionId, 'execute/sync', {\n script: body,\n args\n })\n}\n","// Native app network, read from the appium-interceptor plugin's proxy: a native app has no document and Appium serves no BiDi network events.\n\nimport {\n getRequestType,\n isNativeAppSession,\n type NetworkRequest\n} from '@wdio/devtools-shared'\nimport { errorMessage } from './error.js'\n\nconst INTERCEPT_CAPABILITY = 'appium:intercept'\nconst APPIUM_OPTIONS = 'appium:options'\n\n/** Per-command drains are throttled to this: each read returns the listener's whole history, bodies included. */\nexport const NATIVE_NETWORK_DRAIN_INTERVAL_MS = 2000\n\n/** Runs an Appium execute method (`interceptor: …`) outside the client's own command path, so it never renders as an action row. */\nexport type InterceptorExecute = (\n script: string,\n args: unknown[]\n) => Promise<unknown>\n\nexport type NativeNetworkLog = (level: 'info' | 'warn', message: string) => void\n\n/** One entry of the plugin's `interceptor: getInterceptedData`. */\nexport interface InterceptedRequest {\n url: string\n method: string\n statusCode: number\n requestHeaders?: Record<string, string | string[]>\n requestBody?: unknown\n responseHeaders?: Record<string, string | string[]>\n responseBody?: unknown\n}\n\nexport function flattenInterceptedHeaders(\n headers: InterceptedRequest['requestHeaders']\n): Record<string, string> {\n const flat: Record<string, string> = {}\n for (const [name, value] of Object.entries(headers ?? {})) {\n // The plugin files the response status line as a header (\"http/1.1 201 created\").\n if (/\\s/.test(name)) {\n continue\n }\n flat[name.toLowerCase()] = Array.isArray(value) ? value.join(', ') : value\n }\n return flat\n}\n\nexport function interceptedBodyText(body: unknown): string | undefined {\n if (body === undefined || body === null || body === '') {\n return undefined\n }\n return typeof body === 'string' ? body : JSON.stringify(body)\n}\n\n/** The plugin records no timing, so a row is stamped when it was first read. */\nexport function interceptedToNetworkRequest(\n entry: InterceptedRequest,\n id: string,\n observedAt: { timestamp: number; startTime: number }\n): NetworkRequest {\n const responseHeaders = flattenInterceptedHeaders(entry.responseHeaders)\n const responseBody = interceptedBodyText(entry.responseBody)\n return {\n id,\n url: entry.url,\n method: entry.method,\n status: entry.statusCode,\n type: getRequestType(entry.url, responseHeaders['content-type']),\n timestamp: observedAt.timestamp,\n startTime: observedAt.startTime,\n endTime: observedAt.startTime,\n requestHeaders: flattenInterceptedHeaders(entry.requestHeaders),\n responseHeaders,\n response: {\n fromCache: false,\n headers: responseHeaders,\n mimeType: responseHeaders['content-type']?.split(';')[0]?.trim() ?? '',\n status: entry.statusCode\n },\n requestBody: interceptedBodyText(entry.requestBody),\n responseBody,\n size:\n responseBody === undefined ? undefined : Buffer.byteLength(responseBody)\n }\n}\n\nexport class InterceptorNetworkPoller {\n #execute: InterceptorExecute\n #listenerId?: string\n #seen = 0\n // Drains are chained: two overlapping reads would both list the entries past the same `#seen`.\n #tail: Promise<NetworkRequest[]> = Promise.resolve([])\n\n constructor(execute: InterceptorExecute) {\n this.#execute = execute\n }\n\n async start(): Promise<boolean> {\n const id = await this.#execute('interceptor: startListening', [{}])\n this.#listenerId = typeof id === 'string' ? id : undefined\n return this.#listenerId !== undefined\n }\n\n drain(): Promise<NetworkRequest[]> {\n // A failed read is reported to its own caller; the next drain still runs.\n this.#tail = this.#tail.catch(() => []).then(() => this.#readFresh())\n return this.#tail\n }\n\n async #readFresh(): Promise<NetworkRequest[]> {\n const listenerId = this.#listenerId\n if (!listenerId) {\n return []\n }\n const data = await this.#execute('interceptor: getInterceptedData', [\n { id: listenerId }\n ])\n // A transport that answers failures with null would otherwise read as \"nothing new\".\n if (!Array.isArray(data)) {\n throw new Error('the plugin returned no intercepted data')\n }\n if (data.length <= this.#seen) {\n return []\n }\n const observedAt = { timestamp: Date.now(), startTime: performance.now() }\n const fresh = (data as InterceptedRequest[])\n .slice(this.#seen)\n .map((entry, offset) =>\n interceptedToNetworkRequest(\n entry,\n `interceptor-${listenerId}-${this.#seen + offset}`,\n observedAt\n )\n )\n this.#seen = data.length\n return fresh\n }\n}\n\n/** Whether one capability set asks for the proxy, directly or grouped under `appium:options`. */\nfunction asksForIntercept(caps: unknown): boolean {\n if (!caps || typeof caps !== 'object') {\n return false\n }\n const map = caps as { get?: (key: string) => unknown } & Record<\n string,\n unknown\n >\n const read = (key: string) =>\n typeof map.get === 'function' ? map.get(key) : map[key]\n if (read(INTERCEPT_CAPABILITY) === true) {\n return true\n }\n const grouped = read(APPIUM_OPTIONS)\n return (\n !!grouped &&\n typeof grouped === 'object' &&\n (grouped as Record<string, unknown>).intercept === true\n )\n}\n\n/** Whether requested capabilities ask appium-interceptor for its proxy: flat, W3C-wrapped, or a selenium `Capabilities` map. */\nexport function requestsInterceptedNetwork(capabilities: unknown): boolean {\n if (asksForIntercept(capabilities)) {\n return true\n }\n if (!capabilities || typeof capabilities !== 'object') {\n return false\n }\n const { alwaysMatch, firstMatch } = capabilities as {\n alwaysMatch?: unknown\n firstMatch?: unknown\n }\n return [alwaysMatch, ...(Array.isArray(firstMatch) ? firstMatch : [])].some(\n asksForIntercept\n )\n}\n\n/** Where a session's intercepted requests come from, resolved by each adapter from its own client. */\nexport interface InterceptedNetworkTarget {\n requested: boolean\n capabilities: unknown\n /** Undefined when the adapter cannot reach the driver outside its own command path. */\n execute: InterceptorExecute | undefined\n /** Why `execute` is undefined, in the adapter's own terms. */\n unavailableReason?: string\n}\n\nexport interface NetworkRequestSink {\n recordNetworkRequests(requests: NetworkRequest[]): void\n}\n\n/** Per-session lifecycle every adapter shares: start a listener per session, drain it into the capturer. */\nexport class NativeNetworkCapture {\n #sink: NetworkRequestSink\n #onLog: NativeNetworkLog\n #now: () => number\n #lastDrainAt = Number.NEGATIVE_INFINITY\n #poller?: InterceptorNetworkPoller\n // A start superseded by a later one (session replaced mid-handshake) must not install its poller.\n #generation = 0\n\n constructor(\n sink: NetworkRequestSink,\n onLog: NativeNetworkLog,\n now: () => number = Date.now\n ) {\n this.#sink = sink\n this.#onLog = onLog\n this.#now = now\n }\n\n /** Called per session: a listener dies with its session. */\n async start(target: InterceptedNetworkTarget | undefined): Promise<void> {\n const generation = ++this.#generation\n this.#poller = undefined\n if (!target?.requested || !isNativeAppSession(target.capabilities)) {\n return\n }\n if (!target.execute) {\n this.#onLog(\n 'warn',\n `${INTERCEPT_CAPABILITY} is set but ${target.unavailableReason ?? 'the driver cannot be reached outside the command path'}; native network is not captured`\n )\n return\n }\n const poller = new InterceptorNetworkPoller(target.execute)\n const started = await poller.start().catch((err: unknown) => {\n this.#onLog(\n 'warn',\n `appium-interceptor listener failed to start: ${errorMessage(err)}`\n )\n return false\n })\n if (generation !== this.#generation) {\n return\n }\n if (!started) {\n this.#onLog(\n 'warn',\n `${INTERCEPT_CAPABILITY} is set but the interceptor listener did not start; is Appium running with --use-plugins=appium-interceptor?`\n )\n return\n }\n this.#onLog('info', 'Native network capture active via appium-interceptor')\n this.#poller = poller\n this.#lastDrainAt = Number.NEGATIVE_INFINITY\n }\n\n /** Whether a listener is running, so a caller can skip a drain that cannot find anything. */\n get active(): boolean {\n return this.#poller !== undefined\n }\n\n /** At session end, so a drain after the driver quit does not reach a dead session. */\n stop(): void {\n this.#generation++\n this.#poller = undefined\n }\n\n /** Lists what the proxy recorded since the last call. A test or session boundary must not pass `throttle`. */\n async forward(options: { throttle?: boolean } = {}): Promise<void> {\n const poller = this.#poller\n if (!poller) {\n return\n }\n const now = this.#now()\n if (\n options.throttle &&\n now - this.#lastDrainAt < NATIVE_NETWORK_DRAIN_INTERVAL_MS\n ) {\n return\n }\n this.#lastDrainAt = now\n const requests = await poller.drain().catch((err: unknown) => {\n this.#onLog(\n 'warn',\n `Reading appium-interceptor data failed: ${errorMessage(err)}`\n )\n return []\n })\n if (requests.length > 0) {\n this.#sink.recordNetworkRequests(requests)\n }\n }\n}\n","// The geometry a trace is replayed at.\n//\n// `metadata.viewport` is not descriptive: `trace-exporter` falls back to a\n// hard-coded 1280x720 when it is absent, and the player lays the DOM-replay\n// iframe out at whatever it finds. An adapter that publishes none therefore\n// frames every run — desktop included — at proportions it never had.\n//\n// Two reads, because only one of them exists at a time: a page measures itself\n// through `visualViewport`, and a native app has no page to ask, so the\n// device's own window is the only answer. `isNativeAppSession` settles which.\n\nimport { isNativeAppSession, type Viewport } from '@wdio/devtools-shared'\n\n/**\n * A function BODY, which is what every driver's script endpoint takes.\n *\n * Reads the fields one by one rather than returning `window.visualViewport`\n * itself: it is a host object, and a driver that serializes it structurally\n * hands back `{}` — the richer read then looks like a successful empty one.\n */\nexport const VISUAL_VIEWPORT_SCRIPT = `\n var v = window.visualViewport\n if (!v) { return null }\n return {\n width: v.width,\n height: v.height,\n offsetLeft: v.offsetLeft,\n offsetTop: v.offsetTop,\n scale: v.scale\n }\n`\n\nexport interface ViewportProbes {\n /** Runs a function body in the page. Omitted when the adapter has no way to. */\n runScript?: (body: string) => Promise<unknown>\n /** The device or OS window — the native session's only measurable surface. */\n getWindowSize?: () => Promise<unknown>\n onWarn?: (message: string) => void\n}\n\nconst size = (value: unknown): number | undefined =>\n typeof value === 'number' && Number.isFinite(value) && value > 0\n ? value\n : undefined\n\n/** A window rect carries no scale or offset — a native app is not scrolled or\n * pinch-zoomed, so the neutral values are the true ones rather than padding. */\nfunction fromWindow(raw: unknown): Viewport | undefined {\n const rect = raw as { width?: unknown; height?: unknown } | undefined\n const width = size(rect?.width)\n const height = size(rect?.height)\n return width && height\n ? { width, height, offsetLeft: 0, offsetTop: 0, scale: 1 }\n : undefined\n}\n\nfunction fromVisualViewport(raw: unknown): Viewport | undefined {\n const v = raw as Record<string, unknown> | undefined\n const width = size(v?.width)\n const height = size(v?.height)\n if (!width || !height) {\n return undefined\n }\n return {\n width,\n height,\n offsetLeft: typeof v?.offsetLeft === 'number' ? v.offsetLeft : 0,\n offsetTop: typeof v?.offsetTop === 'number' ? v.offsetTop : 0,\n scale: size(v?.scale) ?? 1\n }\n}\n\n/**\n * The session's viewport, or undefined when it cannot be read.\n *\n * Degrades rather than throwing: a capture without geometry is still worth\n * keeping, and this runs while the session is being brought up.\n */\nexport async function resolveViewport(\n capabilities: unknown,\n probes: ViewportProbes\n): Promise<Viewport | undefined> {\n const native = isNativeAppSession(capabilities)\n try {\n if (native) {\n return probes.getWindowSize\n ? fromWindow(await probes.getWindowSize())\n : undefined\n }\n return probes.runScript\n ? fromVisualViewport(await probes.runScript(VISUAL_VIEWPORT_SCRIPT))\n : undefined\n } catch (err) {\n probes.onWarn?.(\n `Could not resolve the session viewport: ${(err as Error).message}`\n )\n return undefined\n }\n}\n","import { createRequire } from 'node:module'\nimport {\n TRACKED_ASSERT_METHODS,\n type CollapsedAssertResult,\n type CommandLog\n} from '@wdio/devtools-shared'\nimport { getCallSourceFromStack, isAssertFromUserCode } from './stack.js'\nimport { toError } from './error.js'\nimport { stripAnsi } from '@wdio/devtools-shared'\n\nexport { TRACKED_ASSERT_METHODS }\n\nconst require = createRequire(import.meta.url)\n\n/** Per-process guard so a second `patchNodeAssert()` call is a no-op. */\nexport const ASSERT_PATCHED_SYMBOL = Symbol.for(\n '@wdio/devtools-core/assert-patched'\n)\n\n/**\n * Minimum shape `patchNodeAssert` emits. Adapters that need extra bookkeeping\n * (selenium adds `fromElement` and `rawResult`) wrap the callback to extend\n * the object before forwarding to their own `onCommand` sink.\n */\nexport interface CapturedAssert {\n command: string\n args: unknown[]\n /** `'passed'` for a passing assert; a `CollapsedAssertResult` for a failing\n * node:assert (its clean `actual`/`expected` props), so the trace shows\n * labelled rows rather than node's ANSI-stripped char-diff. */\n result: 'passed' | CollapsedAssertResult | undefined\n error: Error | undefined\n callSource: string | undefined\n timestamp: number\n /** Locator of the element the assertion was about, when the adapter can name\n * one from the raw args (see `patchNodeAssert`'s `resolveAssertTarget`).\n * Lands on `CommandLog.selector` so the player's overlay boxes the target the\n * way it does for a folded matcher row. */\n selector?: string\n}\n\n/**\n * JSON-safe stringify of an assert argument. Non-serialisable inputs degrade\n * gracefully: functions → '[Function]', RegExp → `/.../i`, cyclic objects →\n * `String(value)`. Exported so adapters can mirror the shape if they wrap.\n */\nexport function safeSerializeAssertArg(value: unknown): unknown {\n if (value === null || value === undefined) {\n return value\n }\n if (value instanceof RegExp) {\n return value.toString()\n }\n if (typeof value === 'function') {\n return '[Function]'\n }\n if (typeof value === 'object') {\n try {\n return JSON.parse(JSON.stringify(value))\n } catch {\n return String(value)\n }\n }\n return value\n}\n\ninterface NodeAssertionError {\n actual?: unknown\n expected?: unknown\n generatedMessage?: boolean\n}\n\n/** Human-readable rendering of an assert value for the message body. */\nfunction displayAssertValue(value: unknown): string {\n if (typeof value === 'string') {\n return `'${value}'`\n }\n try {\n return JSON.stringify(value) ?? String(value)\n } catch {\n return String(value)\n }\n}\n\n/**\n * Turn a thrown assert error into the captured shape. node:assert's\n * AssertionError carries clean `actual`/`expected` props plus, when it\n * auto-generated the message, a per-character COLORED diff whose body becomes\n * unreadable noise once ANSI is stripped (`'ExampleThis DIs Nomt...'`). Pull the\n * clean values into a CollapsedAssertResult AND rebuild an auto-generated\n * message (and its echo in the stack) as a value-bearing `Expected:/Received:`\n * block, so every consumer — the trace's Errors tab, the runner console, and\n * allure-mocha's error box (which strips the diff's ANSI to mush) — shows\n * readable values. A user-supplied message and errors without `actual`/\n * `expected` (e.g. `assert.ok`, a thrown non-assert error) pass through.\n */\nfunction describeAssertFailure(err: unknown): {\n result: CollapsedAssertResult | undefined\n error: Error\n} {\n const error = toError(err)\n const e = (typeof err === 'object' && err ? err : {}) as NodeAssertionError\n if (!('actual' in e) && !('expected' in e)) {\n return { result: undefined, error }\n }\n const rawMessage = stripAnsi(error.message)\n const cleanMessage = e.generatedMessage\n ? `${rawMessage.split('\\n')[0] ?? 'Assertion failed'}\\n\\n` +\n `Expected: ${displayAssertValue(e.expected)}\\n` +\n `Received: ${displayAssertValue(e.actual)}`\n : rawMessage\n error.message = cleanMessage\n if (error.stack) {\n error.stack = stripAnsi(error.stack).replace(rawMessage, cleanMessage)\n }\n return {\n result: {\n passed: false,\n actual: safeSerializeAssertArg(e.actual),\n expected: safeSerializeAssertArg(e.expected)\n },\n error\n }\n}\n\nfunction makeAssertEmitters(\n methodName: string,\n args: unknown[],\n onCommand: (cmd: CapturedAssert) => void,\n callerStack: string | undefined,\n resolveAssertTarget?: (args: unknown[]) => string | undefined\n): { passed: () => void; failed: (err: unknown) => void } {\n const callInfo = getCallSourceFromStack(callerStack)\n // Drop asserts the user's test didn't fire directly: either no user-code frame\n // on the stack at all, or the assert's immediate caller is a dependency (a\n // framework/library assert firing during a user operation) — both are noise.\n // callerStack is captured in patchedAssert so the frame offsets line up.\n if (callInfo.filePath === undefined || !isAssertFromUserCode(callerStack)) {\n return { passed: () => {}, failed: () => {} }\n }\n const startedAt = Date.now()\n // Resolved from the RAW args: sanitizing loses the object identity an element\n // handle is recognised by. After the user-code gate, so a dropped assert\n // doesn't pay for it.\n const selector = resolveAssertTarget?.(args)\n const sanitizedArgs = args.map(safeSerializeAssertArg)\n const emit = (result: CapturedAssert['result'], error: Error | undefined) => {\n const cmd: CapturedAssert = {\n command: `assert.${methodName}`,\n args: sanitizedArgs,\n result,\n error,\n callSource: callInfo.callSource,\n timestamp: startedAt\n }\n if (selector) {\n cmd.selector = selector\n }\n onCommand(cmd)\n }\n return {\n passed: () => emit('passed', undefined),\n failed: (err: unknown) => {\n const { result, error } = describeAssertFailure(err)\n emit(result, error)\n }\n }\n}\n\nfunction makePatchedAssertMethod(\n methodName: string,\n assertObj: Record<string | symbol, unknown>,\n original: (...a: unknown[]) => unknown,\n onCommand: (cmd: CapturedAssert) => void,\n resolveAssertTarget?: (args: unknown[]) => string | undefined\n): (...args: unknown[]) => unknown {\n return function patchedAssert(this: unknown, ...args: unknown[]) {\n // Captured HERE so frames[0] is this wrapper and frames[1] is the assert's\n // caller — a fixed offset isAssertFromUserCode reads (minification-robust).\n const callerStack = new Error().stack\n const { passed, failed } = makeAssertEmitters(\n methodName,\n args,\n onCommand,\n callerStack,\n resolveAssertTarget\n )\n let result: unknown\n // Node's internalMatch dispatches on `fn === assert.match` (Node ≤20), so\n // a wrapper installed on that property silently inverts `match` into\n // `doesNotMatch`. Restore the original binding for the call so identity\n // checks inside node:assert see the real method.\n assertObj[methodName] = original\n try {\n result = original.apply(this, args)\n } catch (err) {\n failed(err)\n throw err\n } finally {\n assertObj[methodName] = patchedAssert\n }\n // Async assert methods (rejects/doesNotReject) return a Promise.\n const maybe = result as { then?: unknown } | null | undefined\n if (maybe && typeof maybe.then === 'function') {\n return (result as Promise<unknown>).then(\n (v) => {\n passed()\n return v\n },\n (err) => {\n failed(err)\n throw err\n }\n )\n }\n passed()\n return result\n }\n}\n\n/**\n * Convert a `CapturedAssert` into the shared `CommandLog` shape adapters push\n * into their session capturer. Asserts are effectively instantaneous, so the\n * capture timestamp doubles as `startTime`.\n */\nexport function capturedAssertToCommandLog(\n cmd: CapturedAssert,\n testUid?: string\n): CommandLog {\n const entry: CommandLog = {\n command: cmd.command,\n args: cmd.args,\n result: cmd.result,\n timestamp: cmd.timestamp,\n startTime: cmd.timestamp\n }\n if (cmd.error) {\n entry.error = {\n name: cmd.error.name,\n message: cmd.error.message,\n stack: cmd.error.stack\n }\n }\n if (cmd.callSource) {\n entry.callSource = cmd.callSource\n }\n if (cmd.selector) {\n entry.selector = cmd.selector\n }\n if (testUid) {\n entry.testUid = testUid\n }\n return entry\n}\n\n/**\n * Params any adapter's matcher tap produces. `prefix` selects the command\n * namespace (`expect` / `assert` / `verify`), all of which map to an `Assert`\n * action via the shared action map. Adapters do the thin framework-specific\n * extraction (matcher name, args, pass flag, message); this conversion is the\n * single shared path — the generic counterpart to `capturedAssertToCommandLog`\n * for libraries that aren't node:assert (expect-webdriverio, chai, Nightwatch).\n */\nexport interface MatcherAssertion {\n prefix?: string\n method: string\n args?: unknown[]\n passed: boolean\n message?: string | (() => string)\n callSource?: string\n /** Explicit display label for the action row. Falls back to `command` when\n * absent — set it when the framework carries a richer human message than\n * `prefix.method` (e.g. Nightwatch's \"Testing if the page title contains …\"). */\n title?: string\n}\n\nexport function matcherAssertionToCommandLog(\n input: MatcherAssertion,\n testUid?: string\n): CommandLog {\n const command = `${input.prefix ?? 'expect'}.${input.method}`\n const message =\n typeof input.message === 'function' ? input.message() : input.message\n const entry = capturedAssertToCommandLog(\n {\n command,\n args: (input.args ?? []).map(safeSerializeAssertArg),\n result: input.passed ? 'passed' : undefined,\n error: input.passed\n ? undefined\n : new Error(stripAnsi(message ?? `${command} failed`)),\n callSource: input.callSource,\n timestamp: Date.now()\n },\n testUid\n )\n if (input.title) {\n entry.title = input.title\n }\n return entry\n}\n\n/** Wrap every tracked method on one assert namespace object in place; returns\n * how many were patched. Used for both `assert.*` and `assert.strict.*`. */\nfunction patchAssertNamespace(\n nsObj: Record<string | symbol, unknown>,\n onCommand: (cmd: CapturedAssert) => void,\n resolveAssertTarget?: (args: unknown[]) => string | undefined\n): number {\n let count = 0\n for (const methodName of TRACKED_ASSERT_METHODS) {\n const original = nsObj[methodName]\n if (typeof original !== 'function') {\n continue\n }\n nsObj[methodName] = makePatchedAssertMethod(\n methodName,\n nsObj,\n original as (...a: unknown[]) => unknown,\n onCommand,\n resolveAssertTarget\n )\n count++\n }\n return count\n}\n\n/**\n * Patch `node:assert` so each tracked method emits a `CapturedAssert` to the\n * supplied hook. Idempotent across calls (guarded by `ASSERT_PATCHED_SYMBOL`).\n * Returns `true` on success, `false` when node:assert can't be resolved\n * (rare — browser-only Node-incompatible runtimes).\n *\n * Wraps both the function-form (`assert(...)`) and the namespace methods\n * (`assert.equal(...)`). User code that imported the methods BEFORE this\n * patcher loaded keeps stale references — adapters should import node:assert\n * from their main entry before user test files load.\n *\n * @param onCommand Callback invoked once per assert call (sync OR async).\n * Receives the captured shape; do NOT throw — the wrapper\n * re-throws the original assert error after the callback.\n * @param onLog Optional logger for lifecycle events. Default: silent.\n * @param resolveAssertTarget\n * Optional adapter hook naming the element an assert was about,\n * from its raw args. node:assert takes values, not elements, so\n * only the adapter that captured the reads can make the link;\n * the result rides along as `CapturedAssert.selector`.\n */\nexport function patchNodeAssert(\n onCommand: (cmd: CapturedAssert) => void,\n onLog?: (level: 'info' | 'warn', message: string) => void,\n resolveAssertTarget?: (args: unknown[]) => string | undefined\n): boolean {\n const log = (level: 'info' | 'warn', message: string) =>\n onLog?.(level, message)\n\n let assertModule: unknown\n try {\n assertModule = require('node:assert')\n } catch {\n log('warn', 'node:assert not available — skipping assertion capture')\n return false\n }\n\n // Node's `assert` is a function with methods on it — cast once for the\n // symbol + dynamic method access we do here.\n const assertObj = assertModule as Record<string | symbol, unknown>\n if (assertObj[ASSERT_PATCHED_SYMBOL]) {\n return true\n }\n assertObj[ASSERT_PATCHED_SYMBOL] = true\n\n let patched = patchAssertNamespace(assertObj, onCommand, resolveAssertTarget)\n // `import { strict as assert }` and `assert.strict.*` reference a separate\n // object with its own method identities; patch it too so strict-mode\n // assertions are captured the same as the default ones. `assert.strict` is a\n // callable (strict-mode `assert()`) with the methods hung off it, so it's a\n // function, not a plain object.\n const strict = assertObj.strict\n if (strict && (typeof strict === 'object' || typeof strict === 'function')) {\n patched += patchAssertNamespace(\n strict as Record<string | symbol, unknown>,\n onCommand,\n resolveAssertTarget\n )\n }\n\n log('info', `Patched ${patched} node:assert method(s)`)\n return true\n}\n","import { existsSync, readFileSync } from 'node:fs'\nimport path from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { parse as parseStackTrace } from 'stacktrace-parser'\n\n/** The root `package.json` name that identifies this monorepo's checkout. */\nconst WORKSPACE_ROOT_NAME = 'devtools-monorepo'\n\n/** This monorepo's `packages/` dir when `fromDir` is inside its checkout; undefined for an installed or user-bundled copy. */\nexport function findOwnPackagesDir(fromDir: string): string | undefined {\n for (let dir = fromDir; ; dir = path.dirname(dir)) {\n if (existsSync(path.join(dir, 'pnpm-workspace.yaml'))) {\n return readPackageName(dir) === WORKSPACE_ROOT_NAME\n ? path.join(dir, 'packages')\n : undefined\n }\n if (path.dirname(dir) === dir) {\n return undefined\n }\n }\n}\n\nfunction readPackageName(dir: string): string | undefined {\n try {\n const pkg = JSON.parse(\n readFileSync(path.join(dir, 'package.json'), 'utf8')\n ) as { name?: unknown }\n return typeof pkg.name === 'string' ? pkg.name : undefined\n } catch {\n return undefined\n }\n}\n\nlet ownPackagesDir: string | null | undefined\n\nfunction resolveOwnPackagesDir(): string | undefined {\n if (ownPackagesDir === undefined) {\n try {\n ownPackagesDir =\n findOwnPackagesDir(path.dirname(fileURLToPath(import.meta.url))) ?? null\n } catch {\n ownPackagesDir = null\n }\n }\n return ownPackagesDir ?? undefined\n}\n\n/** A file under `<packagesDir>/<package>/src/`, which a source-mapped stack (tsx, vitest) names in place of dist/. */\nexport function isPackageSource(\n file: string,\n packagesDir: string | undefined\n): boolean {\n if (!packagesDir) {\n return false\n }\n try {\n const filePath = file.startsWith('file://') ? fileURLToPath(file) : file\n const relative = path.relative(packagesDir, filePath)\n if (relative.startsWith('..') || path.isAbsolute(relative)) {\n return false\n }\n return relative.split(path.sep)[1] === 'src'\n } catch {\n return false\n }\n}\n\n/**\n * Return true if a stack frame belongs to user code (not dependencies, Node\n * internals, build output, this monorepo's own sources, or a generic\n * `index.js` entry point).\n */\nexport function isUserCodeFrame(frame: {\n file?: string | null\n}): frame is { file: string } {\n const { file } = frame\n return !!(\n file &&\n !file.includes('/node_modules/') &&\n !file.includes('<anonymous>') &&\n !file.includes('node:internal') &&\n !file.includes('/dist/') &&\n !file.endsWith('/index.js') &&\n !isPackageSource(file, resolveOwnPackagesDir())\n )\n}\n\n/**\n * Strip the `file://` protocol, any trailing `:line:col` suffix, and\n * percent-decode the result. Node's ESM stack traces use file:// URLs which\n * URL-encode spaces — without decoding, `fs.readFile` hits ENOENT on any\n * path that contains one. Falls back to the literal path if decoding fails.\n */\nexport function normalizeFilePath(filePath: string): string {\n const stripped = filePath.replace(/^file:\\/\\//, '').split(':')[0]\n try {\n return decodeURIComponent(stripped)\n } catch {\n return stripped\n }\n}\n\n/**\n * True when a tracked assert was called DIRECTLY by user code. `stack` MUST be\n * captured inside the `patchedAssert` wrapper (`new Error().stack` on its first\n * line), so frames[0] is the wrapper and frames[1] is whoever called the\n * assert. A non-user immediate caller means a framework/dependency assert fired\n * *during* a user operation (which `getCallSourceFromStack` would otherwise\n * mis-attribute to the far-off user frame and surface as a noisy row) — drop it.\n * Uses the fixed frame offset rather than the wrapper's name, so it survives a\n * bundler minifying/renaming the wrapper. No stack → keep (never lose a real\n * assert to an absent stack).\n */\nexport function isAssertFromUserCode(stack: string | undefined): boolean {\n if (!stack) {\n return true\n }\n const caller = parseStackTrace(stack)[1]\n return !!caller && isUserCodeFrame(caller)\n}\n\n/** A user call site, or none at all when no frame on the stack is user code. */\nexport type CallSourceInfo =\n | { filePath: string; callSource: string }\n | { filePath: undefined; callSource: undefined }\n\nconst NO_CALL_SOURCE: CallSourceInfo = {\n filePath: undefined,\n callSource: undefined\n}\n\n/**\n * Capture `{ filePath, callSource }` for the first user-code frame on the\n * stack. `callSource` is `<file>:<line>` for the UI's source-location displays;\n * both are undefined when no user frame can be found. `stack` defaults to the\n * live stack; callers with a pre-captured stack (e.g. the assert wrapper) pass\n * it so the frame offsets line up.\n */\nexport function getCallSourceFromStack(\n stack: string | undefined = new Error().stack\n): CallSourceInfo {\n if (!stack) {\n return NO_CALL_SOURCE\n }\n try {\n const frame = parseStackTrace(stack).find(isUserCodeFrame)\n if (!frame?.file) {\n return NO_CALL_SOURCE\n }\n const filePath = normalizeFilePath(frame.file)\n return { filePath, callSource: `${filePath}:${frame.lineNumber ?? 0}` }\n } catch {\n return NO_CALL_SOURCE\n }\n}\n","import fs from 'node:fs/promises'\nimport path from 'node:path'\nimport { createRequire } from 'node:module'\n\nimport { isBrowserInternalUrl, schemeOf } from '@wdio/devtools-shared'\n\nimport { errorMessage } from './error.js'\n\nconst require = createRequire(import.meta.url)\n\nexport {\n COLLECTOR_READY_EXPRESSION,\n collectorDrainExpression\n} from '@wdio/devtools-shared/page-scripts'\n\n/**\n * Where the collector bundle sits relative to the RESOLVED package entry.\n *\n * Two shapes, because the entry is not always the built one. The package's own\n * `exports` points at `dist/script.js`, so the bundle is its neighbour — but\n * the repo tsconfig maps `@wdio/devtools-script` to `packages/script/src/\n * index.ts`, and every resolver that honours those paths lands there instead.\n * That includes `tsx`/`ts-node`, which is how `wdio run <conf>.ts` loads a\n * config: reading `script.js` beside the entry then ENOENTs on\n * `packages/script/src/script.js`, and because the callers only warn, DOM\n * capture is silently lost for the whole run.\n */\nexport function collectorSourceCandidates(entry: string): string[] {\n const dir = path.dirname(entry)\n return [\n path.join(dir, 'script.js'),\n path.join(dir, '..', 'dist', 'script.js')\n ]\n}\n\n/** The collector bundle's raw source. Callers wrap it for their own injection\n * mechanism — an async IIFE for a `<script>` body, a function declaration for a\n * BiDi preload script. */\nexport async function loadCollectorSource(): Promise<string> {\n const candidates = collectorSourceCandidates(\n require.resolve('@wdio/devtools-script')\n )\n const failures: string[] = []\n for (const candidate of candidates) {\n try {\n return await fs.readFile(candidate, 'utf-8')\n } catch (err) {\n // Not this shape — record it and try the next, so a genuine failure\n // reports every place that was looked at rather than only the last.\n failures.push(`${candidate} (${errorMessage(err)})`)\n }\n }\n throw new Error(\n `collector bundle not found — is @wdio/devtools-script built? Looked at: ${failures.join('; ')}`\n )\n}\n\n/**\n * The collector bundle wrapped in an async IIFE so its top-level `await` works\n * inside a regular `<script>` element body. Used for instrumenting the document\n * that is loaded RIGHT NOW; documents created later are covered by the BiDi\n * preload script (`registerCollectorPreload`), which takes the raw source.\n */\nexport async function loadInjectableScript(): Promise<string> {\n return `(async function() { ${await loadCollectorSource()} })()`\n}\n\n/** The browser's own pages and inline documents carry no test-relevant DOM. */\nfunction isInstrumentableDocument(url: string | undefined): boolean {\n return (\n Boolean(url) && !isBrowserInternalUrl(url!) && schemeOf(url!) !== 'data'\n )\n}\n\n/**\n * Drain the page-side collector, re-injecting it into the CURRENT document and\n * retrying once when it isn't there.\n *\n * Both injection mechanisms (BiDi preload script, `<script>` append) only cover\n * documents created AFTER the injection, so a document that loads across a\n * session swap — `browser.reloadSession()`, nightwatch cucumber's per-scenario\n * re-navigation — carries no collector. Without one the drain returns nothing\n * and the dashboard replays the PREVIOUS document's DOM for every action on the\n * new page (empty forms, stale screenshots) with no error anywhere. Re-injecting\n * into the live document costs one round trip and its startup anchors the\n * current DOM, so the replay recovers from the next action onward.\n *\n * `drain` must resolve to the collector payload, or a falsy value when the\n * collector isn't reachable — the same `typeof === 'undefined' ? null` atomic\n * probe every adapter already uses. `currentUrl` is only consulted on a miss,\n * to keep recovery off documents that aren't worth instrumenting.\n */\nexport async function drainCollectorWithRecovery<T>(opts: {\n drain: () => Promise<T | null>\n injectIntoCurrentDocument: () => Promise<void>\n currentUrl?: () => Promise<string | undefined>\n log?: (level: 'info' | 'warn', message: string) => void\n}): Promise<T | null> {\n const payload = await opts.drain()\n if (payload) {\n return payload\n }\n if (opts.currentUrl) {\n const url = await opts.currentUrl().catch(() => undefined)\n if (!isInstrumentableDocument(url)) {\n // A session's pre-navigation page has no collector by design; anchoring it\n // would add a phantom \"document loaded\" row to every run. An unreadable\n // url means the session is gone — nothing to recover into either.\n return null\n }\n }\n opts.log?.('info', 'Collector missing on the current document, re-injecting')\n try {\n await opts.injectIntoCurrentDocument()\n } catch (err) {\n // Best-effort: a failed recovery must not also lose the caller's drain.\n opts.log?.('warn', `Collector re-injection failed: ${errorMessage(err)}`)\n return null\n }\n const recovered = await opts.drain()\n if (!recovered) {\n opts.log?.(\n 'warn',\n 'Collector still unreachable after re-injection — DOM capture is stale for this document'\n )\n }\n return recovered\n}\n\n/**\n * Poll a readiness check until it returns true, or the attempts run out.\n * Defaults to 5 × 200ms = up to 1 second total — chosen empirically to cover\n * the async IIFE init time across browsers we test against.\n */\nexport async function pollUntilReady(\n check: () => Promise<boolean>,\n opts: { attempts?: number; intervalMs?: number; checkFirst?: boolean } = {}\n): Promise<boolean> {\n const attempts = opts.attempts ?? 5\n const intervalMs = opts.intervalMs ?? 200\n // An appended script runs synchronously; sleeping first cost each navigation 200 ms.\n if (opts.checkFirst && (await check())) {\n return true\n }\n for (let i = 0; i < attempts; i++) {\n await new Promise((resolve) => setTimeout(resolve, intervalMs))\n if (await check()) {\n return true\n }\n }\n return false\n}\n","// Document-start injection of the collector via a BiDi preload script.\n//\n// The `<script>`-append injection in `script-loader.ts` only ever instruments\n// the document that is loaded at the time it runs, and a `<script>` dies with\n// its document. So a navigation always produces a document we learn about\n// afterwards, and everything built on that — when to re-inject, when to drain,\n// which action to credit the new DOM to — is reconstruction, and races. A\n// preload script registered globally runs in EVERY document before any of its\n// script does, so each one instruments itself and anchors its own DOM at its own\n// `performance.timeOrigin`. Nothing to detect, nothing to attribute.\n\nimport { loadSeleniumSubmodule } from './bidi.js'\nimport { errorMessage } from './error.js'\nimport { loadCollectorSource } from './script-loader.js'\n\ninterface ScriptManager {\n addPreloadScript: (\n functionDeclaration: string,\n argumentValueList?: unknown[],\n sandbox?: string | null\n ) => Promise<number>\n}\n\n/** `selenium-webdriver/bidi/scriptManager`'s default export. Cast at this\n * boundary — the module ships no types. */\ntype ScriptManagerFactory = (\n browsingContextIds: unknown,\n driver: unknown\n) => Promise<ScriptManager>\n\n/**\n * Register the collector to run at document-start in every document of this\n * session. Returns false when BiDi isn't available, so the caller can fall back\n * to per-document `<script>` injection.\n *\n * Requires the session to have been created with `webSocketUrl: true`; the\n * script manager throws without it.\n */\nexport async function registerCollectorPreload(\n driver: unknown,\n log?: (level: 'info' | 'warn', message: string) => void\n): Promise<boolean> {\n const factory =\n loadSeleniumSubmodule<ScriptManagerFactory>('bidi/scriptManager')\n if (!factory) {\n return false\n }\n try {\n // No browsing-context id, which registers the script GLOBALLY — contexts\n // created later are covered too. Scoping it to the current context would\n // miss exactly the documents this exists to catch.\n const manager = await factory(undefined, driver)\n // Raw source, not the IIFE-wrapped form: a preload script is a function\n // declaration, so the bundle's top-level await works in the body directly.\n await manager.addPreloadScript(\n `async () => { ${await loadCollectorSource()} }`\n )\n log?.('info', '✓ Collector registered at document-start (BiDi preload)')\n return true\n } catch (err) {\n log?.(\n 'warn',\n `BiDi preload unavailable, falling back to per-document injection: ${errorMessage(err)}`\n )\n return false\n }\n}\n","/**\n * Rate-limits high-frequency identical terminal (stdout/stderr) lines so a\n * polling framework that reprints the same line every ~100ms doesn't flood the\n * trace's console lane. The motivating case: WDIO's logger writes a\n * `COMMAND`/`RESULT` frame for every WebDriver command, so an `expect` that\n * polls for its full 10s timeout emits hundreds of near-identical lines.\n *\n * Only terminal-source capture goes through this — user `console.*` is\n * captured as `source: 'test'` and never throttled, so real user output is\n * untouched. Distinct lines always pass immediately; a repeat is emitted at\n * most once per window.\n */\n\n/**\n * A repeated identical terminal line is emitted at most once per this window\n * (ms). Sized so a 100ms poll collapses ~10:1 while a human-paced reprint of\n * the same line (>1s apart) still shows every time.\n */\nexport const TERMINAL_REPEAT_WINDOW_MS = 1000\n\n/**\n * Leading ISO-8601 timestamp emitted by most structured loggers (`@wdio/logger`'s\n * `%t %l %n:` template, pino, winston, …). It's the only volatile part of\n * otherwise-identical successive log frames, so it's stripped from the throttle\n * key — never from the emitted text.\n */\nconst LEADING_ISO_TIMESTAMP_RE =\n /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d\\d\\d)?Z {1,4}/\n\n/** Key a terminal line for repeat-detection: drop a leading ISO timestamp so\n * successive log frames of the same message collapse. */\nexport function terminalRepeatKey(line: string): string {\n return line.replace(LEADING_ISO_TIMESTAMP_RE, '')\n}\n\nexport class TerminalLineThrottle {\n #lastEmitted = new Map<string, number>()\n readonly #windowMs: number\n\n constructor(windowMs: number = TERMINAL_REPEAT_WINDOW_MS) {\n this.#windowMs = windowMs\n }\n\n /**\n * True if the line should be emitted, false if it's a within-window repeat of\n * a line already emitted. The window is anchored to the last *emit*, not the\n * last occurrence, so a sustained stream of one line still emits once per\n * window instead of going silent after the first. Expired keys are pruned on\n * each emit, so state stays bounded by the number of distinct lines seen\n * within the window.\n */\n shouldEmit(line: string, now: number = Date.now()): boolean {\n const key = terminalRepeatKey(line)\n const last = this.#lastEmitted.get(key)\n if (last !== undefined && now - last < this.#windowMs) {\n return false\n }\n this.#lastEmitted.set(key, now)\n this.#prune(now)\n return true\n }\n\n #prune(now: number): void {\n for (const [key, ts] of this.#lastEmitted) {\n if (now - ts >= this.#windowMs) {\n this.#lastEmitted.delete(key)\n }\n }\n }\n}\n","import fs from 'node:fs'\nimport os from 'node:os'\nimport path from 'node:path'\nimport type { ScreencastInfo } from '@wdio/devtools-shared'\n\nimport type { ScreencastRecorderBase } from './screencast.js'\nimport { errorMessage } from './error.js'\nimport { encodeToVideo } from './video-encoder.js'\n\nexport interface FinalizeScreencastInput {\n recorder: ScreencastRecorderBase\n sessionId: string\n /** Filename without the .webm suffix (e.g. 'wdio-video', 'selenium-video'). */\n filenamePrefix: string\n /** Preferred output dir; falls back to cwd, then os.tmpdir() if unwritable. */\n outputDir?: string\n /** Skip encoding when the recorder collected fewer frames than this. */\n minFrames?: number\n captureFormat?: 'jpeg' | 'png'\n /** Forward the encoded-video metadata to the dashboard. */\n sendUpstream: (scope: string, data: ScreencastInfo) => void\n /** Optional hook for adapter-side logging on each lifecycle step. */\n onLog?: (level: 'info' | 'warn', message: string) => void\n}\n\n/**\n * Stop the recorder, encode its frames to a `.webm` (preferred dir → cwd →\n * tmpdir), and forward the metadata to the dashboard. All errors are caught\n * and reported via `onLog` — screencast is best-effort and must not abort the\n * run on stop/encode failure.\n *\n * Shared across all three adapters: each one provides only the recorder\n * subclass, the filename prefix, and a sendUpstream binding to its\n * SessionCapturer.\n */\nexport async function finalizeScreencast({\n recorder,\n sessionId,\n filenamePrefix,\n outputDir,\n minFrames = 1,\n captureFormat,\n sendUpstream,\n onLog\n}: FinalizeScreencastInput): Promise<void> {\n const log = (level: 'info' | 'warn', message: string) =>\n onLog?.(level, message)\n\n try {\n await recorder.stop()\n } catch (err) {\n log('warn', `Screencast stop failed: ${errorMessage(err)}`)\n return\n }\n\n const frames = recorder.frames\n if (frames.length < minFrames) {\n return\n }\n\n const fileName = `${filenamePrefix}-${sessionId}.webm`\n const candidate = outputDir || process.cwd()\n let videoPath = path.join(candidate, fileName)\n try {\n // Create the (test-results) dir if absent, then confirm it's writable;\n // fall back to tmpdir on any failure so a bad path never aborts the run.\n fs.mkdirSync(candidate, { recursive: true })\n fs.accessSync(candidate, fs.constants.W_OK)\n } catch {\n videoPath = path.join(os.tmpdir(), fileName)\n }\n\n try {\n await encodeToVideo(frames, videoPath, { captureFormat })\n log('info', `📹 Screencast video: ${videoPath}`)\n sendUpstream('screencast', {\n sessionId,\n videoPath,\n videoFile: fileName,\n frameCount: frames.length,\n duration: recorder.duration,\n startTime: frames[0]?.timestamp\n })\n } catch (err) {\n log('warn', `Screencast encode failed: ${errorMessage(err)}`)\n }\n}\n","import { isInputDispatchingCommand } from '@wdio/devtools-shared'\n\n// A screenshot landing inside chromedriver's click — after it computed the\n// element's coordinates, before it dispatches at them — makes the click report\n// success while activating nothing (CLAUDE.md § Known debt has the measurements).\n// One instance per adapter bundle, not per session: both halves are module-scoped\n// and must be imported from the same bundle to see the same gate.\n\n/** Bound on one command's suppression: a failing Nightwatch test discards its\n * command queue, so the callback that closes a window may never fire. */\nexport const INPUT_DISPATCH_MAX_HOLD_MS = 3000\n\nconst NOOP = (): void => {}\n\nexport class InputDispatchGate {\n readonly #openedAt = new Map<number, number>()\n #nextToken = 0\n\n constructor(private readonly maxHoldMs = INPUT_DISPATCH_MAX_HOLD_MS) {}\n\n /**\n * Open a suppression window for `command` and return its closer. Always\n * returns a callable so call sites need no branch — for a command that\n * dispatches no input the closer is a no-op and nothing is tracked.\n */\n open(command: string): () => void {\n if (!isInputDispatchingCommand(command)) {\n return NOOP\n }\n const token = this.#nextToken++\n this.#openedAt.set(token, Date.now())\n return () => {\n this.#openedAt.delete(token)\n }\n }\n\n /**\n * Whether an unexpired window is open. Expired windows are dropped here, so a\n * closer that never runs costs one bounded stall rather than a permanent one\n * and cannot leak entries for the rest of the run.\n */\n isOpen(): boolean {\n const cutoff = Date.now() - this.maxHoldMs\n for (const [token, openedAt] of this.#openedAt) {\n if (openedAt <= cutoff) {\n this.#openedAt.delete(token)\n }\n }\n return this.#openedAt.size > 0\n }\n}\n\nconst sharedGate = new InputDispatchGate()\n\n/** Adapter side: open the window around a command. Returns its closer. */\nexport function beginInputDispatch(command: string): () => void {\n return sharedGate.open(command)\n}\n\n/** Recorder side: is a driver command dispatching input right now? */\nexport function isInputDispatchInFlight(): boolean {\n return sharedGate.isOpen()\n}\n","import type {\n CommandLog,\n DocumentInfo,\n PerformanceData\n} from '@wdio/devtools-shared'\n\nexport { CAPTURE_PERFORMANCE_SCRIPT } from '@wdio/devtools-shared/page-scripts'\n\n/** Untyped bag returned by shared's `CAPTURE_PERFORMANCE_SCRIPT`. */\nexport interface CapturedPerformancePayload {\n navigation?: PerformanceData['navigation']\n resources?: PerformanceData['resources']\n cookies?: string\n documentInfo?: DocumentInfo\n}\n\n/**\n * Apply a captured performance payload onto a CommandLog entry in-place,\n * setting `performance`, `cookies`, `documentInfo`, and a synthesized `result`\n * matching nightwatch's existing dashboard shape. Returns `true` if anything\n * was applied — caller can branch on this to skip further work.\n */\n/** A duration for the row's summary: the Performance API reports milliseconds at sub-microsecond precision, which reads as noise. */\nexport function formatMilliseconds(ms: number | undefined): string | undefined {\n return typeof ms === 'number' && Number.isFinite(ms)\n ? `${ms.toFixed(2)} ms`\n : undefined\n}\n\nexport function applyPerformanceData(\n command: CommandLog,\n payload: CapturedPerformancePayload | undefined,\n navigatedUrl?: string\n): boolean {\n if (!payload || !payload.navigation) {\n return false\n }\n command.performance = {\n navigation: payload.navigation,\n resources: payload.resources\n }\n command.cookies = payload.cookies\n command.documentInfo = payload.documentInfo\n command.result = {\n url: navigatedUrl,\n loadTime: formatMilliseconds(payload.navigation?.timing?.loadTime),\n resources: payload.resources,\n resourceCount: payload.resources?.length,\n cookies: payload.cookies,\n title: payload.documentInfo?.title\n }\n return true\n}\n","// Value → locator provenance: which element's read produced a given value.\n//\n// node:assert takes VALUES, not elements — `assert.equal(await el.getText(), …)`\n// names no element — so an assertion row can only name its target if something\n// remembers which element each read value came from. Adapters feed this from\n// their command path (where a read's result and its selector are both visible)\n// and read it back when a patched assert fires; the answer lands on\n// `CommandLog.selector` so the player's overlay boxes the assertion's subject.\n//\n// Framework-agnostic by construction: it stores primitives and selector strings\n// only. The complementary handle → locator map (an assert taking the element\n// object itself) is Selenium-specific and stays in that adapter.\n\n/** Bounds the registry. A run reads far more values than any assertion looks\n * back over, and only the recent ones can be an assert's subject. */\nconst MAX_READ_VALUES = 200\n\n/** Values longer than this are never an assertion's subject, and keying a map on\n * one costs real memory — a page source or a base64 screenshot is a command\n * result too. */\nconst MAX_READ_VALUE_LENGTH = 256\n\n// Value → the locator of the element whose read produced it, or null when the\n// most recent producer was a driver-level read that no element owns. The null is\n// load-bearing: a page-title assertion would otherwise inherit the locator of an\n// element that happened to have the same text (measured on the selenium mocha\n// example, where `h1` and the document title are both \"Example Domain\").\nconst selectorByReadValue = new Map<unknown, string | null>()\n\n// Only primitives: an object result is compared by identity, which no assertion\n// on a read value relies on, and holding one would pin it for the run.\nfunction isRememberableValue(value: unknown): boolean {\n if (typeof value === 'string') {\n return value.length > 0 && value.length <= MAX_READ_VALUE_LENGTH\n }\n return typeof value === 'number' || typeof value === 'boolean'\n}\n\n/** Attribute a command result to the element it was read from, so an assertion\n * on that value can name the element. Pass `selector: undefined` for a\n * driver-level read — that records the value as belonging to no element rather\n * than leaving an older element's claim standing. */\nexport function rememberReadValue(\n value: unknown,\n selector: string | undefined\n): void {\n if (!isRememberableValue(value)) {\n return\n }\n // Delete before set so map order is recency order: the newest producer of a\n // value wins the lookup, and the least recent is what eviction drops.\n selectorByReadValue.delete(value)\n selectorByReadValue.set(value, selector ?? null)\n if (selectorByReadValue.size > MAX_READ_VALUES) {\n const oldest = selectorByReadValue.keys().next()\n if (!oldest.done) {\n selectorByReadValue.delete(oldest.value)\n }\n }\n}\n\n/** Called at each test start: an earlier test's read would otherwise box its element for a later `assert.ok(true)`. */\nexport function clearReadValues(): void {\n selectorByReadValue.clear()\n}\n\n/** The element locator this value was read from — undefined when an element read\n * wasn't its most recent producer. */\nexport function selectorForReadValue(value: unknown): string | undefined {\n return selectorByReadValue.get(value) ?? undefined\n}\n\n/**\n * The element an assert was about, from its RAW args (sanitizing loses the\n * object identity a handle is recognised by). The first arg whose producer is\n * known decides, since node:assert puts the subject first. `fromHandle` lets an\n * adapter that also tracks element handles answer for an element passed\n * straight to the assert; adapters whose asserts only ever see values omit it.\n */\nexport function resolveAssertTargetFromArgs(\n args: unknown[],\n fromHandle?: (arg: unknown) => string | undefined\n): string | undefined {\n for (const arg of args) {\n const handle = fromHandle?.(arg)\n if (handle) {\n return handle\n }\n // A value no element owns ends the search: `notEqual(title, 'Welcome')` must not box an element that once read 'Welcome'.\n if (selectorByReadValue.has(arg)) {\n return selectorByReadValue.get(arg) ?? undefined\n }\n }\n return undefined\n}\n","/**\n * Tiny state holder for command-retry detection. Both the selenium and\n * nightwatch adapters need exactly this same pattern: compute a stable\n * signature for the incoming command, compare it to the last one we\n * captured, and treat a match as \"the framework is retrying — replace the\n * previous entry instead of pushing a new one\".\n *\n * The signature is JSON-stringified `{command, args, src: callSource}`. Test\n * boundaries (new test, new scenario) call `reset()` to drop the last\n * signature so a deliberate re-run of the same call counts as a fresh\n * command, not a retry.\n */\nexport class RetryTracker {\n #lastSig: string | null = null\n #lastId: number | null = null\n\n /** Build the canonical signature used for retry-equality checks. */\n static signature(\n command: string,\n args: unknown,\n callSource?: string\n ): string {\n return JSON.stringify({ command, args, src: callSource ?? null })\n }\n\n /** True when the incoming signature matches the last captured one AND we\n * have an id to replace (otherwise there's nothing to replace yet). */\n isRetry(sig: string): boolean {\n return sig === this.#lastSig && this.#lastId !== null\n }\n\n /** The id of the last captured command, if any (for the replace-in-place\n * flow). */\n get lastId(): number | null {\n return this.#lastId\n }\n\n /** Record a fresh capture — sets both sig and id together. */\n recordCapture(sig: string, id: number | null): void {\n this.#lastSig = sig\n this.#lastId = id\n }\n\n /** Record only the id (used by adapters that compute the sig but defer the\n * id assignment to after an async capture call). */\n setLastId(id: number | null): void {\n this.#lastId = id\n }\n\n /** Stage the sig before an async capture so the next call already sees the\n * signature change (prevents stale-sig matches on rapid back-to-back\n * commands). Pair with {@link setLastId} once the capture resolves. */\n setLastSig(sig: string): void {\n this.#lastSig = sig\n }\n\n /** Reset at test/scenario boundaries so the next capture is \"fresh\". */\n reset(): void {\n this.#lastSig = null\n this.#lastId = null\n }\n}\n","/**\n * Identity for one test run, shared by every process that reports into it.\n * The backend compares it across worker connects to tell \"next spec of this\n * run\" (keep the accumulated state) from \"a new run\" (wipe it).\n */\n\nimport { randomUUID } from 'node:crypto'\nimport { RUNNER_ENV } from '@wdio/devtools-shared'\n\n/**\n * This run's id, generating and publishing one on first call. Runners fork\n * workers with the launcher's environment, so a launcher that calls this\n * before the run starts gives every worker the same id; a worker that gets\n * here first can only speak for itself, and each sibling reads as its own run.\n */\nexport function resolveRunId(): string {\n const existing = process.env[RUNNER_ENV.RUN_ID]\n if (existing) {\n return existing\n }\n const runId = randomUUID()\n process.env[RUNNER_ENV.RUN_ID] = runId\n return runId\n}\n","import type { ScreencastFrame, ScreencastOptions } from '@wdio/devtools-shared'\nimport { SCREENCAST_DEFAULTS } from '@wdio/devtools-shared'\nimport { isInputDispatchInFlight } from './input-dispatch.js'\nimport { withTimeout } from './with-timeout.js'\n\n/** Ceiling for one awaited driver primitive in the screencast start/stop\n * handshake. The queue serialises start against stop, so a primitive that\n * never settles parks teardown behind it for good. */\nexport const SCREENCAST_HANDSHAKE_TIMEOUT_MS = 5000\n\nconst FIRST_SHOT_TIMEOUT = Symbol('first-shot-timeout')\n\n/**\n * Shared screencast scaffolding consumed by every adapter (service, selenium,\n * nightwatch). Owns the frame buffer, public API (start/stop/setStartMarker,\n * frames/duration/isRecording getters) and the polling fallback. Subclasses\n * provide framework-specific driver access:\n *\n * - `takeScreenshot()` — required. Used by the polling path.\n * - `tryStartCdp() / tryStopCdp()` — optional CDP push-mode override.\n * Default returns false → falls through to polling.\n *\n * Adapters that have a stable CDP escape hatch (WDIO via getPuppeteer,\n * Selenium via createCDPConnection) override the CDP hooks. Nightwatch\n * inherits the polling-only default — works on every browser Nightwatch\n * supports without extra plumbing.\n */\nexport abstract class ScreencastRecorderBase<TDriver = unknown> {\n protected buffer: ScreencastFrame[] = []\n protected options: Required<ScreencastOptions>\n protected driver?: TDriver\n #pollTimer: ReturnType<typeof setInterval> | undefined\n #pollInFlight = false\n /** Bumped by start and stop alike. A shot that outlives its loop compares\n * against this: its frame belongs to the old recording, and the latch it\n * holds is not the successor's to clear. */\n #pollGeneration = 0\n #isRecording = false\n #cdpActive = false\n #startIndex = 0\n #startMarkerSet = false\n #queue: Promise<void> = Promise.resolve()\n\n constructor(options: ScreencastOptions = {}) {\n this.options = { ...SCREENCAST_DEFAULTS, ...options }\n }\n\n /**\n * Start recording. Tries the CDP fast-path first (if the subclass overrode\n * `tryStartCdp`); falls back to screenshot polling otherwise. Safe to call\n * even if the browser doesn't support screenshots — failures are logged and\n * recording is simply skipped.\n */\n async start(driver: TDriver): Promise<void> {\n return this.#enqueue(() => this.#startInner(driver))\n }\n\n /**\n * Stop recording and release resources. Safe to call even if start() was\n * never called or failed.\n */\n async stop(): Promise<void> {\n return this.#enqueue(() => this.#stopInner())\n }\n\n /**\n * Serialise start and stop against each other, so a stop always runs against\n * a start that has finished arming. Unserialised, a stop landing mid-handshake\n * observes nothing armed and returns, leaving what the handshake armed with no\n * owner — and a second start in that window overwrites the session the first\n * is about to claim, because both CDP overrides hold theirs in a single field.\n *\n * Every awaited driver primitive in start/stop is ceilinged at\n * SCREENCAST_HANDSHAKE_TIMEOUT_MS, so a queued op always settles and a driver\n * that wedges cannot park teardown behind it.\n */\n #enqueue(op: () => Promise<void>): Promise<void> {\n const run = this.#queue.then(op, op)\n this.#queue = run.then(\n () => undefined,\n () => undefined\n )\n return run\n }\n\n async #startInner(driver: TDriver): Promise<void> {\n if (this.#isRecording) {\n return\n }\n const generation = ++this.#pollGeneration\n this.driver = driver\n if (await this.tryStartCdp()) {\n this.#cdpActive = true\n this.#isRecording = true\n return\n }\n await this.#startPolling(generation)\n }\n\n async #stopInner(): Promise<void> {\n // Bumped before the early return: a shot issued by a loop this stop is\n // ending must not land in the next recording, and the latch it holds is not\n // the successor's to clear.\n this.#pollGeneration++\n if (!this.#isRecording) {\n return\n }\n if (this.#cdpActive) {\n await this.tryStopCdp()\n this.#cdpActive = false\n } else if (this.#pollTimer !== undefined) {\n this.#stopPolling()\n }\n this.#isRecording = false\n }\n\n /**\n * Mark the current frame position as the start of meaningful recording.\n * Frames captured before this call (blank browser, pre-navigation pauses)\n * are excluded from `frames`. Idempotent — only the first call takes effect.\n */\n setStartMarker(): void {\n if (!this.#startMarkerSet) {\n this.#startMarkerSet = true\n this.#startIndex = this.buffer.length\n }\n }\n\n /** Frames to encode — everything from the first meaningful action onwards. */\n get frames(): ScreencastFrame[] {\n return this.buffer.slice(this.#startIndex)\n }\n\n /** Duration in ms between first and last captured frame. Zero if <2 frames. */\n get duration(): number {\n const f = this.frames\n if (f.length < 2) {\n return 0\n }\n return f[f.length - 1].timestamp - f[0].timestamp\n }\n\n get isRecording(): boolean {\n return this.#isRecording\n }\n\n // ─── Subclass hooks ──────────────────────────────────────────────────────\n\n /**\n * Capture a single screenshot via the framework's driver API. Used by the\n * polling fallback. Return `null` to indicate a transient failure (loop\n * continues); throw to abort polling entirely.\n */\n protected abstract takeScreenshot(): Promise<string | null>\n\n /**\n * Try to start CDP push-mode recording. Return `true` on success. Default\n * returns `false` → caller falls back to polling. Subclasses that wire CDP\n * push themselves (WDIO via Puppeteer, Selenium via createCDPConnection)\n * override and push frames into `this.frames` directly when CDP fires.\n */\n protected async tryStartCdp(): Promise<boolean> {\n return false\n }\n\n /** Stop the CDP push-mode session started by `tryStartCdp`. */\n protected async tryStopCdp(): Promise<void> {\n // no-op\n }\n\n /**\n * Helper for CDP subclasses: push a frame onto the buffer with the right\n * timestamp normalization (CDP gives seconds-as-float; we store ms).\n */\n protected pushCdpFrame(data: string, timestampSeconds?: number): void {\n const timestamp =\n typeof timestampSeconds === 'number'\n ? Math.round(timestampSeconds * 1000)\n : Date.now()\n this.#appendFrame({ data, timestamp })\n }\n\n /**\n * Append a frame, decimating the buffer in place once it exceeds\n * `maxBufferFrames` so a long session can't grow it without bound.\n */\n #appendFrame(frame: ScreencastFrame): void {\n this.buffer.push(frame)\n // Decimation always keeps the first and last frame, so 2 is the hard floor;\n // clamp so a nonsensical sub-2 cap is honored as 2 rather than never firing.\n if (this.buffer.length > Math.max(2, this.options.maxBufferFrames)) {\n this.#decimateBuffer()\n }\n }\n\n /**\n * Halve the buffer by keeping every other frame plus the first and last, so\n * memory is bounded while the temporal spread survives (never tail-truncated).\n * `#startIndex` is remapped to the count of surviving pre-marker frames, which\n * keeps `frames`/`duration` excluding pre-marker frames across decimation.\n */\n #decimateBuffer(): void {\n const lastIndex = this.buffer.length - 1\n const kept: ScreencastFrame[] = []\n let preMarkerKept = 0\n for (let i = 0; i < this.buffer.length; i++) {\n if (i % 2 === 0 || i === lastIndex) {\n if (i < this.#startIndex) {\n preMarkerKept++\n }\n kept.push(this.buffer[i])\n }\n }\n this.buffer = kept\n this.#startIndex = preMarkerKept\n }\n\n /** Whether `setStartMarker` (or `markStartAtLatest`) has fired yet. */\n protected get hasStartMarker(): boolean {\n return this.#startMarkerSet\n }\n\n /**\n * Anchor the start marker to the most recently pushed frame. Used by\n * subclasses that detect the first content-bearing frame heuristically\n * (e.g. selenium's blank-frame-byte-size threshold) and want to skip the\n * preceding about:blank dead-air without waiting for an explicit caller.\n */\n protected markStartAtLatest(): void {\n if (!this.#startMarkerSet) {\n this.#startMarkerSet = true\n this.#startIndex = Math.max(0, this.buffer.length - 1)\n }\n }\n\n // ─── Polling implementation ─────────────────────────────────────────────\n\n /**\n * Hook fired when the polling loop starts. Default: no-op. Subclasses\n * (adapters with their own logger) override to surface visibility.\n */\n protected onPollingStarted(_intervalMs: number): void {\n // no-op\n }\n\n /** Hook fired when polling stops cleanly (driver still alive at the time). */\n protected onPollingStopped(_frameCount: number): void {\n // no-op\n }\n\n /** Hook fired when the polling fallback couldn't even take the first shot. */\n protected onUnavailable(_err: unknown): void {\n // no-op\n }\n\n // ─── Polling implementation ─────────────────────────────────────────────\n\n /**\n * The first shot of a polling session, under the handshake ceiling — it runs\n * on the start/stop queue, so a driver that never answers it would park\n * teardown behind the handshake for good. Returns null when there is nothing\n * to record: a stale generation, a null shot, or a timeout.\n */\n async #takeFirstShot(generation: number): Promise<string | null> {\n const first = await withTimeout<string | null | typeof FIRST_SHOT_TIMEOUT>(\n this.takeScreenshot(),\n SCREENCAST_HANDSHAKE_TIMEOUT_MS,\n FIRST_SHOT_TIMEOUT\n )\n if (generation !== this.#pollGeneration) {\n return null\n }\n if (typeof first !== 'string') {\n this.onUnavailable(\n new Error(\n first === null\n ? 'first screenshot returned null'\n : 'first screenshot timed out'\n )\n )\n return null\n }\n return first\n }\n\n async #startPolling(generation: number): Promise<void> {\n try {\n const first = await this.#takeFirstShot(generation)\n if (first === null) {\n return\n }\n this.#appendFrame({ data: first, timestamp: Date.now() })\n\n const intervalMs = this.options.pollIntervalMs\n this.#pollTimer = setInterval(async () => {\n // A screenshot that reaches the driver mid-click makes the click report\n // success while dispatching nothing, so the page never navigates. Drop\n // the tick rather than race it — the gate is bounded, so a command whose\n // completion never arrives costs frames only until it expires.\n if (isInputDispatchInFlight()) {\n return\n }\n // setInterval does not wait for this handler. A screenshot slower than\n // the interval stacks requests that a serialised driver then serves\n // ahead of the test's own commands (a native session's screenshot runs\n // ~1.2 s against a 200 ms default) — keep at most one outstanding.\n if (this.#pollInFlight) {\n return\n }\n this.#pollInFlight = true\n try {\n const data = await this.takeScreenshot()\n if (data !== null && generation === this.#pollGeneration) {\n this.#appendFrame({ data, timestamp: Date.now() })\n }\n } catch {\n // Session ended mid-interval — stop polling gracefully.\n if (generation === this.#pollGeneration) {\n this.#stopPolling()\n }\n } finally {\n if (generation === this.#pollGeneration) {\n this.#pollInFlight = false\n }\n }\n }, intervalMs)\n\n this.#isRecording = true\n this.onPollingStarted(intervalMs)\n } catch (err) {\n this.onUnavailable(err)\n }\n }\n\n #stopPolling(): void {\n if (this.#pollTimer !== undefined) {\n clearInterval(this.#pollTimer)\n this.#pollTimer = undefined\n // A shot issued just before the stop outlives it. Bumping the generation\n // keeps that orphan from appending after the stop or clearing the\n // successor's latch, and clearing the latch here lets a restart tick\n // without waiting on it.\n this.#pollGeneration++\n this.#pollInFlight = false\n this.onPollingStopped(this.buffer.length)\n }\n }\n}\n","import { spawn } from 'node:child_process'\n\ntype Launch = (\n command: string,\n args: string[],\n options: { detached: boolean; stdio: 'ignore' }\n) => {\n unref?: () => void\n on?: (event: 'error', cb: (err: Error) => void) => void\n}\n\n/** The session facts that decide whether the local desktop Safari is the one\n * under test: an iOS session or one served from another host is not. */\nexport interface FocusTarget {\n browserName?: string\n platformName?: string\n /** The WebDriver endpoint's host; absent or loopback means this machine. */\n host?: string\n /** Where a failed `open` is reported; core has no logger of its own. */\n onError?: (message: string) => void\n}\n\nconst LOCAL_HOSTS = new Set(['localhost', '127.0.0.1', '::1', '[::1]'])\n\n/** Safari delivers synthesized mouse input only to its frontmost window, and\n * a live run opens the dashboard in another browser, which takes that place:\n * every click after that returned success and reached nothing. Brought back\n * with `open -a`, which activates an app without AppleScript permissions. */\nexport function restoreBrowserFocus(\n target: FocusTarget,\n platform: NodeJS.Platform = process.platform,\n // node's spawn overloads do not narrow to this one call shape.\n launch: Launch = spawn as unknown as Launch\n): boolean {\n const local = !target.host || LOCAL_HOSTS.has(target.host)\n if (\n platform !== 'darwin' ||\n !local ||\n !/safari/i.test(target.browserName ?? '') ||\n /ios/i.test(target.platformName ?? '')\n ) {\n return false\n }\n try {\n const child = launch('open', ['-a', 'Safari'], {\n detached: true,\n stdio: 'ignore'\n })\n child.on?.('error', (err) =>\n target.onError?.(`Could not bring Safari to the front: ${err.message}`)\n )\n child.unref?.()\n return true\n } catch (err) {\n target.onError?.(`Could not bring Safari to the front: ${String(err)}`)\n return false\n }\n}\n","import { randomInt } from 'node:crypto'\nimport fs from 'node:fs/promises'\nimport { WebSocket } from 'ws'\nimport type {\n CommandLog,\n ConsoleLog,\n LogLevel,\n LogSource,\n Metadata,\n NetworkRequest,\n SerializedError,\n TraceMutation\n} from '@wdio/devtools-shared'\nimport {\n NETWORK_CAPTURE_SOURCE,\n WORKER_WS_QUERY,\n WS_PATHS,\n WS_SCOPE\n} from '@wdio/devtools-shared'\nimport {\n isNativeAppSession,\n mapCommandToAction,\n sessionHasDocument\n} from '@wdio/devtools-shared'\nimport { resolveRunId } from './run-id.js'\nimport { reattributeDomAnchors } from '@wdio/devtools-trace/trace-mutations'\nimport {\n CONSOLE_METHODS,\n LOG_SOURCES,\n SPINNER_RE,\n createConsoleLogEntry,\n detectLogLevel,\n isInternalStreamLine,\n stripAnsi\n} from '@wdio/devtools-shared'\nimport { TerminalLineThrottle } from './terminal-throttle.js'\n\n/**\n * Foundation class for adapter SessionCapturers. Owns the cross-framework\n * scaffolding (WS connection, console/stream patching, command id\n * bookkeeping). Framework-specific event handling stays in subclasses.\n *\n * Step 2 of {@link file://./../../../SESSIONCAPTURER_EXTRACTION_PLAN.md}.\n * **Not yet consumed by any adapter** — published so a future session can\n * migrate adapter SessionCapturer classes one at a time.\n */\n\nexport interface SessionCapturerOptions {\n hostname?: string\n port?: number\n /**\n * Set when this capturer reconnects mid-run (e.g. after `browser.end()` opens\n * a new session). Tells the backend to keep the accumulated run state instead\n * of resetting it — otherwise earlier tests' commands are wiped and Preserve\n * & Rerun on those tests finds nothing. Every socket also carries the run id,\n * which covers the same-run case a process can't flag: the next spec's worker.\n */\n reconnect?: boolean\n}\n\ntype ConsoleMethod = (typeof CONSOLE_METHODS)[number]\n\nexport abstract class SessionCapturerBase {\n /** Ceiling on messages held while the socket connects. High enough that a\n * normal bringup never reaches it, low enough that a dashboard which never\n * answers cannot retain a run's worth of traffic. */\n static readonly MAX_PENDING_UPSTREAM = 1000\n\n // ── State (mostly private; subclasses access shared ws via `this.ws`) ────\n /**\n * Exposed as `protected` so subclasses with framework-specific close/wait\n * semantics (e.g. nightwatch's `closeWebSocket` with timeout) can operate\n * on the socket directly. Default lifecycle is fully managed by the base.\n */\n protected ws: WebSocket | undefined\n #hasConnected = false\n #originalConsoleMethods: Record<ConsoleMethod, typeof console.log>\n #originalStdoutWrite = process.stdout.write.bind(process.stdout)\n #originalStderrWrite = process.stderr.write.bind(process.stderr)\n // Two flags (not one): prevents re-entrant capture when console.* writes to\n // stdout, OR when stream forwarding wants to log via console.\n #isCapturingConsole = false\n #isCapturingStream = false\n // Collapses high-frequency identical terminal lines (e.g. WDIO's per-command\n // COMMAND/RESULT logger frames reprinted every ~100ms during an expect poll)\n // so they don't flood the console lane. Terminal-source only — user\n // console.* is source='test' and never throttled.\n #terminalThrottle = new TerminalLineThrottle()\n\n // Command bookkeeping — used by adapters that emit commands themselves\n // (nightwatch, selenium). The WDIO service adapter doesn't call sendCommand\n // (WDIO owns the command lifecycle), so this state is harmless overhead.\n // `protected` (not `#`) so subclasses can override the send/replace flow\n // while still sharing the counter and de-dup set with base helpers.\n // A row's public `id` must not repeat across processes: a rerun child's rows join its parent's in the backend's accumulator, and test workers share one dashboard.\n protected commandCounter = randomInt(2 ** 32) * 2 ** 20\n protected sentCommandIds = new Set<number>()\n\n // Map of file path → source text. Populated by `captureSource` (also\n // accessed by adapter-specific source-discovery flows, e.g. service's\n // `ensureSourceLoaded` which parses `file://` locations first).\n sources = new Map<string, string>()\n\n // Captured trace payload — populated by `processTracePayload` (driven from\n // adapter-specific `captureTrace` flows) and by direct pushes from BiDi/CDP\n // listeners. Mutations stay `unknown[]` here because the canonical\n // `TraceMutation` shape is a browser-only DOM type (script package); cross-\n // package consumers treat the array as opaque.\n commandsLog: CommandLog[] = []\n consoleLogs: ConsoleLog[] = []\n networkRequests: NetworkRequest[] = []\n mutations: TraceMutation[] = []\n traceLogs: string[] = []\n metadata?: Metadata\n\n /** Whether this session drove an app rather than a browser. Resolved from\n * the published metadata because selenium's own `getCapabilities()` is\n * async, and a guard cannot await it where it has to decide.\n *\n * Answers from the startup bag, so it stays true for a hybrid app that has\n * switched into a webview. Guards protecting a PAGE-side call want\n * {@link hasDocument} instead. */\n get isNativeAppSession(): boolean {\n return isNativeAppSession(this.metadata?.capabilities)\n }\n\n /** The Appium context last observed, fed by the adapter's command hook. The\n * switch command carries the new context in its own arguments, so following\n * it costs no round trip — undefined means none was ever seen, which for a\n * native session is the native context it started in. */\n currentContext: string | undefined\n\n /** Whether there is a web document to run page script in RIGHT NOW: true for\n * any browser session, and for a native one only while it sits in a webview\n * context. The question a capture guard should ask — `isNativeAppSession`\n * cannot change after session start, so a hybrid app's webview portion was\n * captured as if it had no DOM. */\n get hasDocument(): boolean {\n return sessionHasDocument(this.metadata?.capabilities, this.currentContext)\n }\n\n // ── Construction ────────────────────────────────────────────────────────\n constructor(opts: SessionCapturerOptions = {}) {\n const { hostname, port, reconnect } = opts\n if (hostname && port) {\n const query = new URLSearchParams({\n [WORKER_WS_QUERY.runId]: resolveRunId(),\n ...(reconnect ? { [WORKER_WS_QUERY.reconnect]: '1' } : {})\n })\n this.ws = new WebSocket(\n `ws://${hostname}:${port}${WS_PATHS.worker}?${query}`\n )\n this.ws.on('open', () => {\n this.#hasConnected = true\n this.#flushPending()\n this.onWsOpen()\n })\n this.ws.on('error', (err: unknown) => {\n this.#discardPending()\n this.onWsError(err)\n })\n this.ws.on('close', () => {\n this.#discardPending()\n this.onWsClose()\n })\n this.ws.on('message', (raw: Buffer | string) => {\n try {\n const parsed = JSON.parse(raw.toString())\n this.onWsMessage(parsed)\n } catch {\n // ignore non-JSON\n }\n })\n }\n\n this.#originalConsoleMethods = {\n log: console.log,\n info: console.info,\n warn: console.warn,\n error: console.error\n }\n }\n\n // ── Public API ──────────────────────────────────────────────────────────\n /**\n * Send a typed event to the dashboard. No-op if the WS isn't open. Catches\n * send-time exceptions so a transient socket error never aborts the host\n * runner. Subclasses that want diagnostics on drop or error override\n * {@link onUpstreamDrop}.\n */\n sendUpstream(event: string, data: unknown): void {\n if (!this.ws) {\n this.notifyDrop(event, 'closed')\n return\n }\n // A socket that has not opened YET is not a lost dashboard: a session's\n // metadata and its first suites are published while the driver is still\n // being created, and against Appium that is ~11 s before the worker socket\n // opens. Dropping them silently cost the live dashboard the whole run —\n // `metadata.type` gates the test-suite pane and `metadata.device` the\n // mobile layout, so both were simply absent with nothing logged.\n if (this.ws.readyState === WebSocket.CONNECTING) {\n this.#buffer(event, data)\n return\n }\n if (this.ws.readyState !== WebSocket.OPEN) {\n this.notifyDrop(event, 'closed')\n return\n }\n try {\n this.ws.send(JSON.stringify({ scope: event, data }))\n } catch (err) {\n this.notifyDrop(event, 'send-error', err)\n }\n }\n\n /** Messages published before the socket opened, in the order they were\n * published. Capped: a dashboard that never connects must not grow this\n * without bound for the length of a run. */\n #pending: { event: string; data: unknown }[] = []\n\n #buffer(event: string, data: unknown): void {\n if (this.#pending.length >= SessionCapturerBase.MAX_PENDING_UPSTREAM) {\n this.notifyDrop(event, 'closed')\n return\n }\n this.#pending.push({ event, data })\n }\n\n #inDropHandler = false\n\n /**\n * Report a drop, at most one level deep.\n *\n * An adapter's handler naturally wants to log, and `patchConsole` forwards\n * console output upstream — so a handler that logs re-enters `sendUpstream`,\n * drops again and recurses until the stack blows. Measured as \"Maximum call\n * stack size exceeded\" raised inside the user's own spec, which points\n * nowhere near this code.\n */\n protected notifyDrop(\n event: string,\n reason: 'closed' | 'send-error',\n err?: unknown\n ): void {\n if (this.#inDropHandler) {\n return\n }\n this.#inDropHandler = true\n try {\n this.onUpstreamDrop(event, reason, err)\n } finally {\n this.#inDropHandler = false\n }\n }\n\n /** A socket that dies before it ever opens is a dashboard that is not coming,\n * so its buffer is reported as dropped and released. Without this the\n * payloads — screenshots among them — were retained for the run's length and\n * the adapter's drop warning never fired, which is the silence the buffer\n * exists to end, not to relocate. */\n #discardPending(): void {\n const pending = this.#pending\n this.#pending = []\n for (const { event } of pending) {\n this.notifyDrop(event, 'closed')\n }\n }\n\n /** Publish what was buffered while connecting, oldest first — order matters,\n * since the app folds each metadata message into the previous one. */\n #flushPending(): void {\n const pending = this.#pending\n this.#pending = []\n for (const { event, data } of pending) {\n this.sendUpstream(event, data)\n }\n }\n\n /**\n * Hook fired when a {@link sendUpstream} call can't deliver. Default: silent\n * (matches the historical behavior of service/selenium). Nightwatch overrides\n * this to log a warning — useful when a runner drops mid-test and the user\n * needs to know why captured data is incomplete.\n */\n protected onUpstreamDrop(\n _event: string,\n _reason: 'closed' | 'send-error',\n _err?: unknown\n ): void {\n // no-op\n }\n\n /**\n * Store a metadata fragment AND publish the merged result. The only writer of\n * `this.metadata`, which is what the exporter serializes into the zip's\n * `context-options` — `sendUpstream` merely transmits, so a value resolved on\n * the driver (a native session's viewport, capabilities and device: it has no\n * page-side collector to report them) reached a live dashboard and was then\n * dropped before the zip.\n *\n * Merging rather than replacing is what lets a producer contribute the one\n * field it knows: a later push naming only a url cannot wipe the device.\n */\n mergeMetadata(partial: Partial<Metadata>): void {\n this.metadata = { ...this.metadata, ...partial } as Metadata\n this.sendUpstream('metadata', this.metadata)\n }\n\n /** True once the WS has opened at least once and is currently OPEN. */\n isConnected(): boolean {\n return Boolean(this.ws) && this.ws?.readyState === WebSocket.OPEN\n }\n\n /** Property-style alias for {@link isConnected} — used by tests that\n * read it as a getter while mutating `ws.readyState` directly. */\n get isReportingUpstream(): boolean {\n return this.isConnected()\n }\n\n /** Subclasses can read this to gate retry/reconnect logic. */\n protected hasEverConnected(): boolean {\n return this.#hasConnected\n }\n\n /**\n * Send a CommandLog over the WS. If the entry already has an `_id` (set by\n * the adapter's `captureCommand` during buffering), use it; otherwise\n * allocate a fresh one. The `_id` is the de-dup key and is stripped from\n * the broadcast payload — it's adapter-internal bookkeeping.\n * Returns the id, or 0 if the entry had no `_id` and none could be assigned.\n */\n sendCommand(command: CommandLog & { _id?: number }): number {\n if (command._id === undefined) {\n command._id = this.commandCounter++\n }\n const id = command._id\n if (this.sentCommandIds.has(id)) {\n return id\n }\n this.sentCommandIds.add(id)\n const toSend = { ...command }\n delete toSend._id\n this.sendUpstream('commands', [toSend])\n return id\n }\n\n /**\n * Emit a `replaceCommand` event swapping an earlier entry in-place. Strips\n * the adapter-internal `_id` field before sending — that's bookkeeping for\n * the local `sentCommandIds` set and shouldn't reach the UI.\n */\n sendReplaceCommand(\n oldTimestamp: number,\n command: CommandLog & { _id?: number }\n ): void {\n const toSend = { ...command }\n delete toSend._id\n this.sendUpstream(WS_SCOPE.replaceCommand, {\n oldTimestamp,\n command: toSend\n })\n }\n\n /**\n * Mark the most recent user action of a test as failed — for framework\n * failures that aren't captured as their own command. Broadcasts the swap so\n * live mode highlights it too. Returns false when no eligible action is found,\n * OR when that most-recent action already carries an error: the failure is\n * then already represented (e.g. an expect matcher captured as its own row via\n * afterAssertion), so it must not bleed onto an earlier *passing* action.\n */\n failLastAction(testUid: string | undefined, error: SerializedError): boolean {\n for (let i = this.commandsLog.length - 1; i >= 0; i--) {\n const command = this.commandsLog[i]\n if (!mapCommandToAction(command.command)) {\n continue\n }\n if (testUid && command.testUid !== testUid) {\n continue\n }\n // First (most-recent) action for this test. If it's already failed, the\n // failure is captured — stop rather than marking an earlier passing one.\n if (command.error) {\n return false\n }\n command.error = error\n this.sendReplaceCommand(command.timestamp, command)\n return true\n }\n return false\n }\n\n /**\n * Read a file from disk, store in `sources`, and broadcast to the UI via\n * `sendUpstream('sources', { [path]: text })`. Idempotent — a cached path is\n * a no-op. Read errors are logged via `onSourceReadError` (default: silent)\n * so a missing source never aborts capture.\n */\n async captureSource(filePath: string): Promise<void> {\n if (this.sources.has(filePath)) {\n return\n }\n try {\n const source = (await fs.readFile(filePath, 'utf-8')).toString()\n this.sources.set(filePath, source)\n this.sendUpstream('sources', { [filePath]: source })\n } catch (err) {\n this.onSourceReadError(filePath, err)\n }\n }\n\n /**\n * Hook fired when `captureSource` can't read a file. Default: silent.\n * Subclasses (nightwatch, selenium) override to log a warning.\n */\n protected onSourceReadError(_filePath: string, _err: unknown): void {\n // no-op — service silently swallows; subclasses can opt into a log line.\n }\n\n /**\n * The action a document born at `time` belongs to, or undefined when the\n * anchor already sits where it should and must be left alone.\n *\n * Derived from the anchor rather than from the draining call, because the\n * post-command hooks fire at command START in some adapters: a navigation's\n * own drain then runs against the document it is leaving, and whichever later\n * drain does see the destination would otherwise be credited with it.\n *\n * Answers only when NO logged command completed after `time`. A command that\n * completed after the document was born is the one that navigated there, and\n * its own row already resolves the anchor — pulling the anchor earlier then\n * mis-credits it to a preceding action and steals the DOM from rows that were\n * genuinely still on the previous page. That is the whole difference between\n * an adapter that stamps commands at invocation and one that stamps them at\n * completion, so it stays a property of the data rather than a per-adapter flag.\n */\n protected anchorOwnerTimestamp(time: number): number | undefined {\n let best: number | undefined\n for (const cmd of this.commandsLog) {\n if (cmd.timestamp > time) {\n return undefined\n }\n if (best === undefined || cmd.timestamp > best) {\n best = cmd.timestamp\n }\n }\n return best\n }\n\n /**\n * Ingest the `{ mutations, traceLogs, consoleLogs, networkRequests, metadata }`\n * payload returned by the page-side `wdioTraceCollector.getTraceData()`.\n * Tags console logs with `source: 'browser'`, pushes each array into the\n * matching local field, and broadcasts via the appropriate WS scopes.\n *\n * `skipConsoleLogs` / `skipNetworkRequests` opt out when an out-of-band\n * channel (BiDi) is already delivering those streams — without the gate\n * the dashboard would see each entry twice. `skipResourceTimingRequests`\n * drops only the page's Resource Timing entries: a driver-side source\n * reports the same loads with their headers, while the collector's patched\n * fetch/XHR rows still carry the bodies nothing else captures.\n */\n protected processTracePayload(\n payload: {\n mutations?: unknown\n traceLogs?: unknown\n consoleLogs?: unknown\n networkRequests?: unknown\n metadata?: unknown\n },\n opts: {\n skipConsoleLogs?: boolean\n skipNetworkRequests?: boolean\n skipResourceTimingRequests?: boolean\n /** The command this batch's anchors were CAUSED by, when the caller\n * observed the document being replaced right after it. Causal knowledge,\n * so it overrides the timestamp inference below. */\n anchorTimestamp?: number\n } = {}\n ): void {\n const { mutations, traceLogs, consoleLogs, networkRequests, metadata } =\n payload\n\n if (metadata && typeof metadata === 'object') {\n // Page-side trace data is a JS bag; only fields that match Metadata\n // survive at runtime, but TS can't prove that. Cast to Partial<Metadata>\n // so the merge stays type-checked while accepting incomplete payloads.\n this.mergeMetadata(metadata as Partial<Metadata>)\n }\n\n const tagged = opts.skipConsoleLogs\n ? []\n : this.#tagBrowserConsoleLogs(consoleLogs)\n if (tagged.length > 0) {\n this.consoleLogs.push(...tagged)\n this.sendUpstream('consoleLogs', tagged)\n }\n\n this.recordNetworkRequests(\n this.#acceptedNetworkRequests(networkRequests, opts)\n )\n\n if (Array.isArray(mutations) && mutations.length > 0) {\n const batch = mutations as TraceMutation[]\n reattributeDomAnchors(\n batch,\n (anchorOwnTime) =>\n opts.anchorTimestamp ?? this.anchorOwnerTimestamp(anchorOwnTime),\n this.mutations[this.mutations.length - 1]?.timestamp ?? 0\n )\n this.mutations.push(...batch)\n this.sendUpstream('mutations', batch)\n }\n\n if (Array.isArray(traceLogs) && traceLogs.length > 0) {\n const logs = traceLogs as string[]\n this.traceLogs.push(...logs)\n this.sendUpstream('logs', logs)\n }\n }\n\n #tagBrowserConsoleLogs(consoleLogs: unknown): ConsoleLog[] {\n if (!Array.isArray(consoleLogs)) {\n return []\n }\n return (consoleLogs as ConsoleLog[]).map((entry) => ({\n ...entry,\n source: LOG_SOURCES.BROWSER as LogSource\n }))\n }\n\n /** Lists network rows and sends them in one message; a re-sent id replaces its row, as the dashboard does. */\n recordNetworkRequests(reqs: NetworkRequest[]): void {\n if (reqs.length === 0) {\n return\n }\n for (const req of reqs) {\n this.#upsertNetworkRequest(req)\n }\n this.sendUpstream('networkRequests', reqs)\n }\n\n /** Row index by request id; checked against the row before use, since adapters also write rows by index. */\n #networkRequestIndex = new Map<string, number>()\n\n #upsertNetworkRequest(req: NetworkRequest): void {\n let at = req.id ? this.#networkRequestIndex.get(req.id) : undefined\n // A row moved since it was indexed: find it once, rather than list it twice.\n if (at !== undefined && this.networkRequests[at]?.id !== req.id) {\n at = this.networkRequests.findIndex((r) => r.id === req.id)\n }\n if (at !== undefined && at !== -1) {\n this.networkRequests[at] = req\n this.#networkRequestIndex.set(req.id, at)\n return\n }\n if (req.id) {\n this.#networkRequestIndex.set(req.id, this.networkRequests.length)\n }\n this.networkRequests.push(req)\n }\n\n #acceptedNetworkRequests(\n networkRequests: unknown,\n opts: {\n skipNetworkRequests?: boolean\n skipResourceTimingRequests?: boolean\n }\n ): NetworkRequest[] {\n if (opts.skipNetworkRequests || !Array.isArray(networkRequests)) {\n return []\n }\n return (networkRequests as NetworkRequest[]).filter(\n (req) =>\n !opts.skipResourceTimingRequests ||\n req.source !== NETWORK_CAPTURE_SOURCE.RESOURCE_TIMING\n )\n }\n\n /**\n * Resolve when the WS reaches OPEN state, or `false` on timeout / error.\n * Returns immediately if already open. Used by adapters that need a\n * synchronization barrier before injecting page-side scripts.\n */\n async waitForConnection(timeoutMs = 5000): Promise<boolean> {\n if (!this.ws) {\n return false\n }\n if (this.ws.readyState === WebSocket.OPEN) {\n return true\n }\n return new Promise((resolve) => {\n const timeout = setTimeout(() => resolve(false), timeoutMs)\n this.ws!.once('open', () => {\n clearTimeout(timeout)\n resolve(true)\n })\n this.ws!.once('error', () => {\n clearTimeout(timeout)\n resolve(false)\n })\n })\n }\n\n /**\n * Gracefully close the WS, waiting up to 2s for buffered messages to flush.\n * Call before process exit in reuse mode (or after dashboard close) so the\n * backend sees a clean close instead of an abrupt TCP reset.\n */\n async closeWebSocket(): Promise<void> {\n if (!this.ws || this.ws.readyState === WebSocket.CLOSED) {\n return\n }\n return new Promise<void>((resolve) => {\n const timeout = setTimeout(resolve, 2000)\n this.ws!.once('close', () => {\n clearTimeout(timeout)\n resolve()\n })\n this.ws!.close()\n })\n }\n\n /**\n * Restore console/streams. Does NOT close the WS — that's the subclass's\n * call (see `closeWebSocket` on nightwatch/selenium). Closing here would\n * break the wait-for-dashboard-close flow, since the worker WS is the\n * channel the backend uses to signal `clientDisconnected`.\n */\n cleanup(): void {\n this.restoreConsole()\n this.restoreStreams()\n }\n\n // ── Patching (call from subclass constructor) ───────────────────────────\n /** Patch `console.log/info/warn/error` to forward through `onLine`. */\n protected patchConsole(): void {\n CONSOLE_METHODS.forEach((method) => {\n const original = this.#originalConsoleMethods[method]\n console[method] = (...args: unknown[]) => {\n this.#isCapturingConsole = true\n const result = original.apply(console, args)\n this.#isCapturingConsole = false\n\n const serialized = args.map((a) =>\n typeof a === 'object' && a !== null ? safeStringify(a) : String(a)\n )\n const joined = stripAnsi(serialized.join(' ')).trim()\n if (!joined || this.isInternalStreamLine(joined)) {\n return result\n }\n // Pass the per-arg serialized array (`['payload', '{\"x\":1}']`) rather\n // than the joined string. The dashboard's `#formatArgs` joins on its\n // own; preserving the array form is lossless and lets future consumers\n // group/style individual args.\n this.onLine(method as LogLevel, serialized, LOG_SOURCES.TEST)\n return result\n }\n })\n }\n\n /**\n * Wrap `process.stdout.write` and `process.stderr.write` to forward chunks\n * through `onLine`. The base's `#isCapturingConsole` flag prevents\n * re-entrance when console patching itself writes to stdout.\n */\n protected patchStreams(): void {\n const captureChunk = (raw: string | Uint8Array) => {\n if (this.#isCapturingStream) {\n return\n }\n const text = typeof raw === 'string' ? raw : raw.toString()\n if (!text?.trim()) {\n return\n }\n this.#isCapturingStream = true\n try {\n for (const rawLine of text.split('\\n')) {\n // Strip CR-overwrites so progress bars don't show partial frames.\n const segments = rawLine.split('\\r').filter((s) => s.trim())\n const lastSegment = segments[segments.length - 1] ?? rawLine\n const clean = stripAnsi(lastSegment).trim()\n if (\n !clean ||\n this.isInternalStreamLine(clean) ||\n SPINNER_RE.test(clean) ||\n !this.#terminalThrottle.shouldEmit(clean)\n ) {\n continue\n }\n this.onLine(detectLogLevel(clean), [clean], LOG_SOURCES.TERMINAL)\n }\n } finally {\n this.#isCapturingStream = false\n }\n }\n\n // `stream.write` has Node's complex multi-overload signature that\n // doesn't unify with a generic `unknown[]` rest type — keep the\n // original ref typed by the stream itself.\n const wrap = (\n stream: NodeJS.WriteStream,\n original: NodeJS.WriteStream['write']\n ) => {\n const capturer = this\n // `stream.write` has Node's multi-overload signature that's hard to\n // satisfy with a single function expression — cast to the stream's\n // own `write` member type rather than `any`.\n stream.write = function (chunk: unknown, ...rest: unknown[]): boolean {\n // Cast original to a permissive shape — Node's multi-overload\n // signature for `stream.write` can't be unified with the `unknown`-\n // typed chunk we receive at the wrap boundary.\n const writeAny = original as unknown as (...a: unknown[]) => boolean\n const result = writeAny.call(stream, chunk, ...rest)\n if (chunk && !capturer.#isCapturingConsole) {\n captureChunk(chunk as string | Uint8Array)\n }\n return result\n } as typeof stream.write\n }\n\n wrap(process.stdout, this.#originalStdoutWrite)\n wrap(process.stderr, this.#originalStderrWrite)\n }\n\n protected restoreConsole(): void {\n CONSOLE_METHODS.forEach((method) => {\n console[method] = this.#originalConsoleMethods[method]\n })\n }\n\n protected restoreStreams(): void {\n // Restoring the pre-patch references — the typed write signature differs\n // slightly from the runtime instance type after `.bind()`, hence the cast\n // through the stream's own `write` member type.\n process.stdout.write = this\n .#originalStdoutWrite as typeof process.stdout.write\n process.stderr.write = this\n .#originalStderrWrite as typeof process.stderr.write\n }\n\n // ── Hooks (subclasses override) ─────────────────────────────────────────\n /**\n * Build a ConsoleLog entry from the captured line, push it into the local\n * `consoleLogs` array (so it ends up in any future trace export), and\n * broadcast it live via the `consoleLogs` WS scope.\n *\n * Args is passed as an array (matching the original console.* call shape:\n * `console.log('a', 'b')` → `args = ['a', 'b']`) so the multi-argument\n * structure is preserved for the UI.\n */\n protected onLine(type: LogLevel, args: string[], source: LogSource): void {\n const entry = createConsoleLogEntry(type, args, source)\n this.consoleLogs.push(entry)\n this.sendUpstream('consoleLogs', [entry])\n }\n\n /**\n * Default delegates to {@link isInternalStreamLine} from `./console.js`.\n * Subclasses can override to add framework-specific filters.\n */\n protected isInternalStreamLine(line: string): boolean {\n return isInternalStreamLine(line)\n }\n\n /** Hook: WS opened. Subclasses override to send a handshake, etc. */\n protected onWsOpen(): void {}\n\n /** Hook: WS errored before opening (likely no backend listening). */\n protected onWsError(_err: unknown): void {}\n\n /** Hook: WS closed (after open, or as a result of cleanup). */\n protected onWsClose(): void {}\n\n /**\n * Hook: WS message received from the backend. Currently used by selenium's\n * `awaitClientConnected` to know when a dashboard tab has subscribed.\n */\n protected onWsMessage(_msg: unknown): void {}\n}\n\nfunction safeStringify(value: unknown): string {\n try {\n return JSON.stringify(value)\n } catch {\n return String(value)\n }\n}\n","import type {\n SuiteStats,\n TestAncestor,\n TestMetadataMap,\n TestStats,\n TestStatus\n} from '@wdio/devtools-shared'\nimport { TEST_STATE } from '@wdio/devtools-shared'\n\n/**\n * Pure factories + state computations shared by the per-adapter SuiteManager\n * and TestManager classes. Pattern A from CLAUDE.md §the-four-patterns —\n * stateless helpers each adapter calls; no base class because storage\n * strategy differs (selenium uses a single root suite + optional Cucumber\n * sub-suites; nightwatch uses a Map keyed by test file).\n */\n\nexport interface SuiteStatsInit {\n uid: string\n title: string\n file: string\n cid?: string\n callSource?: string\n featureFile?: string\n parent?: string\n start?: Date\n state?: TestStatus\n}\n\nexport interface TestStatsInit {\n uid: string\n title: string\n file: string\n parent: string\n cid?: string\n fullTitle?: string\n callSource?: string\n state?: TestStatus\n start?: Date\n}\n\n/**\n * Build a SuiteStats with the standard defaults (empty children arrays,\n * RUNNING state, fresh start time). Adapters override fields as needed\n * before/after the call.\n */\nexport function createSuiteStats(init: SuiteStatsInit): SuiteStats {\n return {\n uid: init.uid,\n cid: init.cid ?? '0-0',\n title: init.title,\n fullTitle: init.title,\n file: init.file,\n type: 'suite',\n start: init.start ?? new Date(),\n state: init.state ?? TEST_STATE.RUNNING,\n end: null,\n tests: [],\n suites: [],\n hooks: [],\n _duration: 0,\n callSource: init.callSource,\n featureFile: init.featureFile,\n parent: init.parent\n }\n}\n\n/** Build a TestStats with the standard defaults. */\nexport function createTestStats(init: TestStatsInit): TestStats {\n return {\n uid: init.uid,\n cid: init.cid ?? '0-0',\n title: init.title,\n fullTitle: init.fullTitle ?? init.title,\n parent: init.parent,\n state: init.state ?? TEST_STATE.RUNNING,\n start: init.start ?? new Date(),\n end: null,\n type: 'test',\n file: init.file,\n retries: 0,\n _duration: 0,\n hooks: [],\n callSource: init.callSource\n }\n}\n\n/**\n * \"Permissive\" finalize: any failed child → FAILED, otherwise PASSED.\n * Matches selenium's policy — RUNNING tests don't fail the suite.\n */\nexport function computeSuiteFinalStatePermissive(\n suite: SuiteStats\n): TestStatus {\n const failedDirect = suite.tests.some(\n (t) => typeof t !== 'string' && t.state === TEST_STATE.FAILED\n )\n const failedNested = (suite.suites ?? []).some(\n (s) => s.state === TEST_STATE.FAILED\n )\n return failedDirect || failedNested ? TEST_STATE.FAILED : TEST_STATE.PASSED\n}\n\n/**\n * \"Strict\" finalize: any failed → FAILED; all PASSED/SKIPPED → PASSED;\n * empty suite → PASSED; orphaned RUNNING tests → FAILED. Matches nightwatch's\n * policy — incomplete runs are surfaced as failures.\n */\nexport function computeSuiteFinalStateStrict(suite: SuiteStats): TestStatus {\n const tests = suite.tests as TestStats[]\n const suites = suite.suites ?? []\n const hasFailures =\n tests.some((t) => t.state === TEST_STATE.FAILED) ||\n suites.some((s) => s.state === TEST_STATE.FAILED)\n if (hasFailures) {\n return TEST_STATE.FAILED\n }\n const allPassed =\n tests.every(\n (t) => t.state === TEST_STATE.PASSED || t.state === TEST_STATE.SKIPPED\n ) &&\n suites.every(\n (s) => s.state === TEST_STATE.PASSED || s.state === TEST_STATE.SKIPPED\n )\n const hasSkipped = tests.some((t) => t.state === TEST_STATE.SKIPPED)\n const hasItems = tests.length > 0 || suites.length > 0\n if (!hasItems || allPassed) {\n return TEST_STATE.PASSED\n }\n if (hasSkipped) {\n return TEST_STATE.PASSED\n }\n return TEST_STATE.FAILED\n}\n\n/**\n * In-progress state computation: if any child is FAILED → FAILED;\n * else if any RUNNING / unfinished → RUNNING; else → PASSED. Used to keep\n * a parent suite's state fresh while children are still executing\n * (Cucumber feature suite updates as each scenario completes).\n */\nexport function computeSuiteRunningState(suite: SuiteStats): TestStatus {\n const tests = suite.tests as TestStats[]\n const suites = suite.suites ?? []\n const hasFailures =\n tests.some((t) => t.state === TEST_STATE.FAILED) ||\n suites.some((s) => s.state === TEST_STATE.FAILED)\n if (hasFailures) {\n return TEST_STATE.FAILED\n }\n const hasRunning =\n tests.some((t) => t.state === TEST_STATE.RUNNING) ||\n suites.some((s) => s.state === TEST_STATE.RUNNING || !s.end)\n return hasRunning ? TEST_STATE.RUNNING : TEST_STATE.PASSED\n}\n\n/**\n * Stamp `end` + `_duration` on a suite. Pure mutation — kept here so the\n * arithmetic stays in one place; callers still own the state assignment.\n */\nexport function stampSuiteEnd(suite: SuiteStats, end: Date = new Date()): void {\n suite.end = end\n suite._duration = end.getTime() - (suite.start?.getTime() || end.getTime())\n}\n\n/** Stamp `end` + `_duration` on a test. */\nexport function stampTestEnd(test: TestStats, end: Date = new Date()): void {\n test.end = end\n test._duration = end.getTime() - (test.start?.getTime() ?? end.getTime())\n}\n\nfunction deriveAncestorKind(\n suite: SuiteStats,\n parentKind: TestAncestor['kind'] | undefined\n): TestAncestor['kind'] {\n if (parentKind === 'feature') {\n return 'scenario'\n }\n // Cucumber scenario suites carry the same .feature file as their parent —\n // only a suite not already under a feature/scenario counts as the feature.\n if (suite.file?.endsWith('.feature') && parentKind !== 'scenario') {\n return 'feature'\n }\n return 'suite'\n}\n\n/**\n * Recursive walk over suite trees collecting every test's metadata entry —\n * uid → title/specFile/state/attempt plus the ancestor chain (outermost\n * first, the test's own node excluded). Consolidates the per-adapter\n * collectTestMetadata walks.\n */\nexport function collectSuiteTestMetadata(\n suites: Iterable<SuiteStats>\n): TestMetadataMap {\n const metadata: TestMetadataMap = new Map()\n const walk = (suite: SuiteStats, ancestors: TestAncestor[]): void => {\n const parentKind = ancestors[ancestors.length - 1]?.kind\n const chain: TestAncestor[] = [\n ...ancestors,\n {\n uid: suite.uid,\n title: suite.title,\n kind: deriveAncestorKind(suite, parentKind)\n }\n ]\n // A cucumber scenario is modelled as a SUITE, not a test — but it IS the\n // test unit: the attempt ledger and the per-test slice boundary both key on\n // its uid, and so does every command's testUid. Without an entry here the\n // trace's group fell back to rendering that raw uid as the group's name.\n // `state` comes along so retention keeps evaluating a real outcome.\n if (deriveAncestorKind(suite, parentKind) === 'scenario') {\n metadata.set(suite.uid, {\n title: suite.title,\n specFile: suite.file,\n state: suite.state,\n ancestry: ancestors\n })\n }\n for (const entry of suite.tests ?? []) {\n // Trees can contain string placeholders for tests not yet reconciled.\n if (typeof entry !== 'object' || entry === null) {\n continue\n }\n metadata.set(entry.uid, {\n title: entry.fullTitle || entry.title,\n specFile: entry.file ?? suite.file,\n state: entry.state,\n attempt: entry.retries ?? 0,\n ancestry: chain\n })\n }\n for (const child of suite.suites ?? []) {\n walk(child, chain)\n }\n }\n for (const suite of suites) {\n if (typeof suite !== 'object' || suite === null) {\n continue\n }\n walk(suite, [])\n }\n return metadata\n}\n","import fs from 'node:fs'\n\n/**\n * One test/suite definition discovered in a source file by line-regex scan.\n * Adapter-agnostic shape — both selenium and nightwatch consume this.\n */\nexport interface TestDefinition {\n kind: 'suite' | 'test'\n title: string\n line: number\n}\n\n/**\n * Regex-scan a JS/TS test file for `describe('...')` / `it('...')` style\n * definitions (and Mocha/Jest aliases — `suite`, `context`, `test`,\n * `specify`). Adapters call this from line-based helpers that resolve a\n * call-source `file:line` for the dashboard's TestLens / Actions tab.\n *\n * Set `includeNightwatchObjectStyle` to also match the object-export shape\n * Nightwatch's `--yes` scaffolder generates:\n *\n * 'My test name': () => { ... }\n * 'My test name': async function () { ... }\n *\n * Returns definitions in source order. Unreadable / unparseable files yield\n * an empty array — the dashboard already degrades to `file:0` in that case.\n */\nexport function findTestDefinitions(\n filePath: string,\n opts: { includeNightwatchObjectStyle?: boolean } = {}\n): TestDefinition[] {\n if (!fs.existsSync(filePath)) {\n return []\n }\n let source: string\n try {\n source = fs.readFileSync(filePath, 'utf-8')\n } catch {\n return []\n }\n\n const out: TestDefinition[] = []\n const lines = source.split('\\n')\n\n const suiteRe = /\\b(?:describe|suite|context)\\s*\\(\\s*['\"`]([^'\"`]+)['\"`]/\n const testRe = /\\b(?:it|test|specify)\\s*\\(\\s*['\"`]([^'\"`]+)['\"`]/\n // Nightwatch object-export: `'Title': () => { ... }` or `: function () {`\n const objRe =\n /^\\s*['\"`]([^'\"`]+)['\"`]\\s*:\\s*(?:async\\s+)?(?:\\([^)]*\\)\\s*=>|function\\s*\\()/\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i]\n const lineNum = i + 1\n\n const suiteMatch = line.match(suiteRe)\n if (suiteMatch) {\n out.push({ kind: 'suite', title: suiteMatch[1], line: lineNum })\n continue\n }\n const testMatch = line.match(testRe)\n if (testMatch) {\n out.push({ kind: 'test', title: testMatch[1], line: lineNum })\n continue\n }\n if (opts.includeNightwatchObjectStyle) {\n const objMatch = line.match(objRe)\n if (objMatch) {\n out.push({ kind: 'test', title: objMatch[1], line: lineNum })\n }\n }\n }\n return out\n}\n\n/**\n * Convenience: find the line number where a specific test/suite is defined.\n * Returns null if the file or title isn't found. Used by adapter call-source\n * resolution from hooks where the user's stack frame isn't reachable.\n */\nexport function findTestLineInFile(\n filePath: string,\n title: string,\n kind: 'test' | 'suite' = 'test'\n): number | null {\n const defs = findTestDefinitions(filePath)\n return defs.find((d) => d.kind === kind && d.title === title)?.line ?? null\n}\n","import type { SuiteStats, TestStats } from '@wdio/devtools-shared'\nimport { resetSignatureCounters } from './uid.js'\n\n/**\n * Shape of the payload sent upstream — one record per suite, keyed by UID,\n * so the UI can merge it into its existing suite map without scanning.\n */\nexport type ReporterUpstreamPayload = Record<string, SuiteStats>[]\nexport type ReporterUpstream = (data: ReporterUpstreamPayload) => void\n\n/**\n * Foundation class for adapter TestReporters. Owns the cross-framework\n * scaffolding (suite collection, upstream batching). Framework-specific\n * lifecycle hooks (spec-file scanning, UID generation, skipped-test\n * synthesis) stay in subclasses.\n *\n * Service uses the WDIO reporter base instead — this class is for adapters\n * that own their own reporter lifecycle (nightwatch, selenium).\n */\nexport abstract class TestReporterBase {\n #report: ReporterUpstream\n protected allSuites: SuiteStats[] = []\n\n constructor(report: ReporterUpstream) {\n this.#report = report\n resetSignatureCounters()\n }\n\n /** Swap the upstream sink, e.g. after a WS reconnect. */\n updateUpstream(report: ReporterUpstream): void {\n this.#report = report\n }\n\n /** Manually trigger a flush of current state to the UI. */\n updateSuites(): void {\n this.sendUpstream()\n }\n\n /**\n * Reset collected state. Subclasses with extra state (test-name cache,\n * current-suite ref) override and call `super.clearExecutionData()` first.\n */\n clearExecutionData(): void {\n this.allSuites = []\n resetSignatureCounters()\n }\n\n /** Default: find by `uid`, replace in place. */\n onTestEnd(test: TestStats): void {\n for (const suite of this.allSuites) {\n const idx = suite.tests.findIndex(\n (t) => typeof t !== 'string' && t.uid === test.uid\n )\n if (idx !== -1) {\n suite.tests[idx] = test\n break\n }\n }\n this.sendUpstream()\n }\n\n /** Default: just flush. Subclasses with skipped-test synthesis override. */\n onSuiteEnd(_suite: SuiteStats): void {\n this.sendUpstream()\n }\n\n get report(): SuiteStats[] {\n return this.allSuites\n }\n\n /**\n * Flush current suite state to the upstream callback. Empty-payload guard\n * matches the existing adapter behavior — UI shouldn't receive an empty\n * array.\n */\n protected sendUpstream(): void {\n const payload: ReporterUpstreamPayload = []\n for (const suite of this.allSuites) {\n if (suite.uid) {\n payload.push({ [suite.uid]: suite })\n }\n }\n if (payload.length > 0) {\n this.#report(payload)\n }\n }\n}\n"],"mappings":"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","names":["path","fs","path","xpath","xpath","createHash","path","errorMessage","path","fs","path","path","fs","path","path","xpath","fs","fs","path","fs","path","fs","path","fs","path","createRequire","require","fs","path","fs","path","createRequire","path","require","createRequire","fs","path","createRequire","require","createRequire","path","fs","fs","os","path","path","fs","os","fs","fs","fs"]}
|