@wdio/nightwatch-devtools 2.0.0 → 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.
@@ -12,10 +12,15 @@ var RUNNER_ENV = {
12
12
  WDIO_CONFIG: "DEVTOOLS_WDIO_CONFIG",
13
13
  NIGHTWATCH_CONFIG: "DEVTOOLS_NIGHTWATCH_CONFIG",
14
14
  WDIO_INITIAL_SPECS: "DEVTOOLS_WDIO_INITIAL_SPECS",
15
+ // The selection the Nightwatch run was launched with (specs, --tag, --env, ...), without --config; see encodeInitialArgs.
16
+ NIGHTWATCH_INITIAL_ARGS: "DEVTOOLS_NIGHTWATCH_INITIAL_ARGS",
15
17
  RUNNER_CWD: "DEVTOOLS_RUNNER_CWD",
16
18
  WDIO_BIN: "DEVTOOLS_WDIO_BIN",
17
19
  NIGHTWATCH_BIN: "DEVTOOLS_NIGHTWATCH_BIN"
18
20
  };
21
+ function encodeInitialArgs(args) {
22
+ return JSON.stringify(args);
23
+ }
19
24
 
20
25
  // ../core/src/error.ts
21
26
  function toError(value) {
@@ -30,7 +35,11 @@ function toError(value) {
30
35
  }
31
36
  return e;
32
37
  }
33
- return new Error(String(value));
38
+ try {
39
+ return new Error(String(value));
40
+ } catch {
41
+ return new Error(Object.prototype.toString.call(value));
42
+ }
34
43
  }
35
44
  function errorMessage(value) {
36
45
  if (value instanceof Error) {
@@ -248,6 +257,9 @@ import fs2 from "fs/promises";
248
257
  import path2 from "path";
249
258
 
250
259
  // ../shared/src/trace-actions.ts
260
+ function isWaitAction(action) {
261
+ return action?.method.startsWith("waitFor") ?? false;
262
+ }
251
263
  var ASSERT_ACTION_CLASS = "Assert";
252
264
  var TRACKED_ASSERT_METHODS = [
253
265
  "equal",
@@ -334,6 +346,8 @@ var ACTION_MAP = {
334
346
  waitForElementNotVisible: { class: "Element", method: "waitForHidden" },
335
347
  waitForElementPresent: { class: "Element", method: "waitForExist" },
336
348
  waitForElementNotPresent: { class: "Element", method: "waitForNotExist" },
349
+ // A Selenium wait on an element locator (`until.elementLocated`, `presence_of_element_located`), named by its adapter.
350
+ waitForElementLocated: { class: "Element", method: "waitForExist" },
337
351
  // WDIO page/browser reads
338
352
  getTitle: { class: "Page", method: "getTitle" },
339
353
  getUrl: { class: "Page", method: "getUrl" },
@@ -344,6 +358,8 @@ var ACTION_MAP = {
344
358
  getCssValue: { class: "Element", method: "getCSSProperty" },
345
359
  getRect: { class: "Element", method: "getRect" },
346
360
  getCurrentUrl: { class: "Page", method: "getUrl" },
361
+ // A Selenium wait on any condition other than an element locator.
362
+ wait: { class: "Browser", method: "waitForFunction" },
347
363
  // Raw W3C protocol names. The JS adapters wrap a client library and report
348
364
  // its method names, but an adapter that patches the protocol chokepoint —
349
365
  // the Python one, and any future language binding — reports what WebDriver
@@ -420,6 +436,9 @@ function isInputDispatchingCommand(command) {
420
436
  function mapCommandToAction(command) {
421
437
  return ACTION_MAP[command] ?? mapAssertCommand(command);
422
438
  }
439
+ function isWaitCommand(command) {
440
+ return isWaitAction(mapCommandToAction(command));
441
+ }
423
442
  var ASSERT_TITLE_VALUE_MAX = 40;
424
443
  function formatAssertValue(value) {
425
444
  let text;
@@ -526,16 +545,6 @@ function detectLogLevel(text) {
526
545
  function createConsoleLogEntry(type, args, source = LOG_SOURCES.TEST) {
527
546
  return { timestamp: Date.now(), type, args, source };
528
547
  }
529
- function mapChromeBrowserLogs(entries) {
530
- return entries.map((entry) => ({
531
- timestamp: entry.timestamp,
532
- type: chromeLogLevelToLogLevel(
533
- entry.level
534
- ),
535
- args: [entry.message],
536
- source: LOG_SOURCES.BROWSER
537
- }));
538
- }
539
548
  function chromeLogLevelToLogLevel(level) {
540
549
  const levelName = (typeof level === "object" ? level?.name ?? "" : level ?? "").toUpperCase();
541
550
  switch (levelName) {
@@ -584,6 +593,9 @@ function namesAnAutomation(caps) {
584
593
  deepCapString(caps, "appium:automationName") ?? deepCapString(caps, "automationName")
585
594
  );
586
595
  }
596
+ function isCapabilityBag(value) {
597
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
598
+ }
587
599
  function firstCapString(caps, keys, reject = () => false) {
588
600
  for (const key of keys) {
589
601
  const value = deepCapString(caps, key);
@@ -594,7 +606,7 @@ function firstCapString(caps, keys, reject = () => false) {
594
606
  return void 0;
595
607
  }
596
608
  function deviceFromCapabilities(capabilities) {
597
- if (!capabilities || typeof capabilities !== "object") {
609
+ if (!isCapabilityBag(capabilities)) {
598
610
  return void 0;
599
611
  }
600
612
  const caps = capabilities;
@@ -618,7 +630,7 @@ function deviceFromCapabilities(capabilities) {
618
630
  };
619
631
  }
620
632
  function isNativeAppSession(capabilities) {
621
- if (!capabilities || typeof capabilities !== "object") {
633
+ if (!isCapabilityBag(capabilities)) {
622
634
  return false;
623
635
  }
624
636
  const caps = capabilities;
@@ -1068,6 +1080,155 @@ function elementsScript(includeBounds, inViewportOnly, runner) {
1068
1080
  })()`;
1069
1081
  }
1070
1082
 
1083
+ // ../shared/src/page-scripts.ts
1084
+ var COLLECTOR_READY_EXPRESSION = 'typeof window.wdioTraceCollector !== "undefined"';
1085
+ var COLLECTOR_READY_SCRIPT = `return ${COLLECTOR_READY_EXPRESSION};`;
1086
+ var COLLECTOR_INJECT_SCRIPT = `var s=document.createElement('script');s.textContent=arguments[0];document.head.appendChild(s);${COLLECTOR_READY_SCRIPT}`;
1087
+ function collectorDrainExpression(forceAnchor = false) {
1088
+ const call = forceAnchor ? "window.wdioTraceCollector.captureCurrentDom();" : "";
1089
+ return `if (!(${COLLECTOR_READY_EXPRESSION})) { return null; } ${call} return window.wdioTraceCollector.getTraceData();`;
1090
+ }
1091
+ var CAPTURE_PERFORMANCE_SCRIPT = `
1092
+ return (function() {
1093
+ const performance = window.performance;
1094
+ const navigation = performance.getEntriesByType?.('navigation')?.[0];
1095
+ const resources = performance.getEntriesByType?.('resource') || [];
1096
+
1097
+ return {
1098
+ navigation: navigation ? {
1099
+ url: window.location.href,
1100
+ timing: {
1101
+ loadTime: navigation.loadEventEnd - navigation.fetchStart,
1102
+ domReady: navigation.domContentLoadedEventEnd - navigation.fetchStart,
1103
+ responseTime: navigation.responseEnd - navigation.requestStart,
1104
+ dnsLookup: navigation.domainLookupEnd - navigation.domainLookupStart,
1105
+ tcpConnection: navigation.connectEnd - navigation.connectStart,
1106
+ serverResponse: navigation.responseEnd - navigation.responseStart
1107
+ }
1108
+ } : undefined,
1109
+ resources: resources.map(function(resource) {
1110
+ return {
1111
+ url: resource.name,
1112
+ duration: resource.duration,
1113
+ size: resource.transferSize || 0,
1114
+ type: resource.initiatorType,
1115
+ startTime: resource.startTime,
1116
+ responseEnd: resource.responseEnd
1117
+ };
1118
+ }),
1119
+ cookies: (function() {
1120
+ try { return document.cookie; } catch (e) { return ''; }
1121
+ })(),
1122
+ documentInfo: {
1123
+ url: window.location.href,
1124
+ title: document.title,
1125
+ headers: {
1126
+ userAgent: navigator.userAgent,
1127
+ language: navigator.language,
1128
+ platform: navigator.platform
1129
+ },
1130
+ documentInfo: {
1131
+ readyState: document.readyState,
1132
+ referrer: document.referrer,
1133
+ characterSet: document.characterSet
1134
+ }
1135
+ }
1136
+ };
1137
+ })()
1138
+ `;
1139
+
1140
+ // ../shared/src/command-tree.ts
1141
+ function nestCommands(commands) {
1142
+ const nodes = commands.map((command) => ({
1143
+ command,
1144
+ children: []
1145
+ }));
1146
+ const byId = /* @__PURE__ */ new Map();
1147
+ for (const node of nodes) {
1148
+ const { id } = node.command;
1149
+ if (typeof id === "number" && !byId.has(id)) {
1150
+ byId.set(id, node);
1151
+ }
1152
+ }
1153
+ const parentOf = (node) => {
1154
+ const { parentId } = node.command;
1155
+ return typeof parentId === "number" ? byId.get(parentId) : void 0;
1156
+ };
1157
+ const roots = [];
1158
+ for (const node of nodes) {
1159
+ const parent = parentOf(node);
1160
+ if (parent && !closesCycle(node, parentOf)) {
1161
+ parent.children.push(node);
1162
+ } else {
1163
+ roots.push(node);
1164
+ }
1165
+ }
1166
+ return roots;
1167
+ }
1168
+ function closesCycle(node, parentOf) {
1169
+ const seen = /* @__PURE__ */ new Set();
1170
+ for (let at = parentOf(node); at && !seen.has(at); at = parentOf(at)) {
1171
+ if (at === node) {
1172
+ return true;
1173
+ }
1174
+ seen.add(at);
1175
+ }
1176
+ return false;
1177
+ }
1178
+
1179
+ // ../shared/src/command-failure.ts
1180
+ function collapsedAssertResult(result) {
1181
+ if (typeof result === "object" && result !== null && "passed" in result) {
1182
+ return result;
1183
+ }
1184
+ return void 0;
1185
+ }
1186
+ function commandFailure(cmd, action) {
1187
+ if (cmd.error) {
1188
+ return errorFailure(cmd.error);
1189
+ }
1190
+ const collapsed = collapsedAssertResult(cmd.result);
1191
+ if (collapsed && collapsed.passed === false) {
1192
+ const isAssert = action.class === ASSERT_ACTION_CLASS;
1193
+ return {
1194
+ message: String(
1195
+ collapsed.message ?? (isAssert ? "Assertion failed" : `${cmd.command} failed`)
1196
+ )
1197
+ };
1198
+ }
1199
+ if (cmd.result === false && isWaitAction(action)) {
1200
+ const target = cmd.args?.find((a) => typeof a === "string");
1201
+ return {
1202
+ message: target ? `${cmd.command} timed out waiting for ${String(target)}` : `${cmd.command} timed out`
1203
+ };
1204
+ }
1205
+ return void 0;
1206
+ }
1207
+ function errorFailure(error) {
1208
+ return { message: error.message ?? String(error) };
1209
+ }
1210
+ function rowFailure(cmd) {
1211
+ const action = mapCommandToAction(cmd.command);
1212
+ if (action) {
1213
+ return commandFailure(cmd, action);
1214
+ }
1215
+ return cmd.error ? errorFailure(cmd.error) : void 0;
1216
+ }
1217
+ function retriedRows(commands) {
1218
+ const retried = /* @__PURE__ */ new Set();
1219
+ const walk = (nodes, underPassing) => {
1220
+ for (const { command, children } of nodes) {
1221
+ const failed = Boolean(rowFailure(command));
1222
+ if (underPassing && failed) {
1223
+ retried.add(command);
1224
+ }
1225
+ walk(children, underPassing || !failed);
1226
+ }
1227
+ };
1228
+ walk(nestCommands(commands), false);
1229
+ return retried;
1230
+ }
1231
+
1071
1232
  // ../shared/src/image.ts
1072
1233
  var PNG_BASE64_MAGIC = "iVBOR";
1073
1234
  function imageMime(base64) {
@@ -1584,7 +1745,7 @@ function splitNumericSuffix(value) {
1584
1745
  return { rest: value.slice(0, sep), num: Number(digits) };
1585
1746
  }
1586
1747
  function callSourceToStack(callSource) {
1587
- if (!callSource || callSource === "unknown:0") {
1748
+ if (!callSource) {
1588
1749
  return void 0;
1589
1750
  }
1590
1751
  const last = splitNumericSuffix(callSource);
@@ -1617,28 +1778,28 @@ function ownTitle(title, parent) {
1617
1778
  return title.slice(parent.title.length).trim() || title;
1618
1779
  }
1619
1780
  function buildGroupPath(cmd, testMetadata) {
1620
- const path10 = [];
1781
+ const path11 = [];
1621
1782
  if (cmd.testUid) {
1622
1783
  const entry = testMetadata?.get(cmd.testUid);
1623
1784
  for (const ancestor of entry?.ancestry ?? []) {
1624
- path10.push({
1785
+ path11.push({
1625
1786
  uid: ancestor.uid,
1626
- title: ownTitle(ancestor.title, path10[path10.length - 1])
1787
+ title: ownTitle(ancestor.title, path11[path11.length - 1])
1627
1788
  });
1628
1789
  }
1629
- path10.push({
1790
+ path11.push({
1630
1791
  uid: cmd.testUid,
1631
- title: ownTitle(entry?.title ?? cmd.testUid, path10[path10.length - 1])
1792
+ title: ownTitle(entry?.title ?? cmd.testUid, path11[path11.length - 1])
1632
1793
  });
1633
1794
  }
1634
1795
  if (cmd.stepUid) {
1635
1796
  const stepEntry = testMetadata?.get(cmd.stepUid);
1636
- path10.push({
1797
+ path11.push({
1637
1798
  uid: cmd.stepUid,
1638
- title: ownTitle(stepEntry?.title ?? cmd.stepUid, path10[path10.length - 1])
1799
+ title: ownTitle(stepEntry?.title ?? cmd.stepUid, path11[path11.length - 1])
1639
1800
  });
1640
1801
  }
1641
- return path10;
1802
+ return path11;
1642
1803
  }
1643
1804
 
1644
1805
  // ../trace/src/trace-action-events.ts
@@ -1657,12 +1818,6 @@ function serializableResult(result) {
1657
1818
  return void 0;
1658
1819
  }
1659
1820
  }
1660
- function collapsedAssertResult(result) {
1661
- if (typeof result === "object" && result !== null && "passed" in result) {
1662
- return result;
1663
- }
1664
- return void 0;
1665
- }
1666
1821
  function buildAssertParams(cmd) {
1667
1822
  const params = Object.fromEntries(
1668
1823
  cmd.args.map((arg, index) => [String(index), arg])
@@ -1755,28 +1910,6 @@ function buildParamsAndTitle(action, cmd) {
1755
1910
  )
1756
1911
  };
1757
1912
  }
1758
- function actionError(cmd, action) {
1759
- if (cmd.error) {
1760
- const err = cmd.error;
1761
- return { message: err.message ?? String(cmd.error) };
1762
- }
1763
- const isAssert = action.class === ASSERT_ACTION_CLASS;
1764
- const collapsed = collapsedAssertResult(cmd.result);
1765
- if (collapsed && collapsed.passed === false) {
1766
- return {
1767
- message: String(
1768
- collapsed.message ?? (isAssert ? "Assertion failed" : `${cmd.command} failed`)
1769
- )
1770
- };
1771
- }
1772
- if (cmd.result === false && action.method.startsWith("waitFor")) {
1773
- const target = cmd.args?.find((a) => typeof a === "string");
1774
- return {
1775
- message: target ? `${cmd.command} timed out waiting for ${String(target)}` : `${cmd.command} timed out`
1776
- };
1777
- }
1778
- return void 0;
1779
- }
1780
1913
  function resolveActionPoint(action, cmd, snapshotIndex) {
1781
1914
  if (action.class !== "Element" || !POINTABLE_METHODS.has(action.method)) {
1782
1915
  return void 0;
@@ -1795,10 +1928,13 @@ function resolveActionPoint(action, cmd, snapshotIndex) {
1795
1928
  }
1796
1929
  return void 0;
1797
1930
  }
1798
- function buildAfterEvent(cmd, action, callId, endMs, snapshotIndex) {
1931
+ function buildAfterEvent(cmd, action, callId, endMs, ctx) {
1932
+ const { snapshotIndex } = ctx;
1799
1933
  const afterEvent = { type: "after", callId, endTime: endMs };
1800
- const error = actionError(cmd, action);
1801
- if (error) {
1934
+ const error = commandFailure(cmd, action);
1935
+ if (error && ctx.retried.has(cmd)) {
1936
+ afterEvent.retriedError = error;
1937
+ } else if (error) {
1802
1938
  afterEvent.error = error;
1803
1939
  }
1804
1940
  const result = serializableResult(cmd.result);
@@ -1815,13 +1951,11 @@ function buildAfterEvent(cmd, action, callId, endMs, snapshotIndex) {
1815
1951
  }
1816
1952
  return afterEvent;
1817
1953
  }
1818
- function pushActionPair(stream, cmd, action, pageId, wallTime, snapshotIndex) {
1954
+ function pushBefore(stream, cmd, action, pageId, wallTime, parentCallId, snapshotIndex) {
1819
1955
  stream.callCounter++;
1820
1956
  const callId = `call@${stream.callCounter}`;
1821
1957
  const rawStartMs = (cmd.startTime ?? cmd.timestamp) - wallTime;
1822
- const rawEndMs = cmd.timestamp - wallTime;
1823
1958
  const startMs = Math.max(stream.prevEndMs, rawStartMs);
1824
- const endMs = Math.max(startMs + 1, rawEndMs);
1825
1959
  const { params, title } = buildParamsAndTitle(action, cmd);
1826
1960
  const beforeEvent = {
1827
1961
  type: "before",
@@ -1833,7 +1967,7 @@ function pushActionPair(stream, cmd, action, pageId, wallTime, snapshotIndex) {
1833
1967
  params,
1834
1968
  title,
1835
1969
  apiName: `${action.class.toLowerCase()}.${action.method}`,
1836
- parentId: stream.openGroups[stream.openGroups.length - 1]?.callId
1970
+ parentId: parentCallId ?? stream.openGroups[stream.openGroups.length - 1]?.callId
1837
1971
  };
1838
1972
  const stack = callSourceToStack(cmd.callSource);
1839
1973
  if (stack) {
@@ -1844,7 +1978,38 @@ function pushActionPair(stream, cmd, action, pageId, wallTime, snapshotIndex) {
1844
1978
  beforeEvent.beforeSnapshot = beforeName;
1845
1979
  }
1846
1980
  stream.events.push(beforeEvent);
1847
- stream.events.push(buildAfterEvent(cmd, action, callId, endMs, snapshotIndex));
1981
+ return { callId, startMs };
1982
+ }
1983
+ function emitNode(stream, node, ctx, parentCallId) {
1984
+ const cmd = node.command;
1985
+ const action = mapCommandToAction(cmd.command);
1986
+ if (!action) {
1987
+ for (const child of node.children) {
1988
+ emitNode(stream, child, ctx, parentCallId);
1989
+ }
1990
+ return;
1991
+ }
1992
+ const { callId, startMs } = pushBefore(
1993
+ stream,
1994
+ cmd,
1995
+ action,
1996
+ ctx.pageId,
1997
+ ctx.wallTime,
1998
+ parentCallId,
1999
+ ctx.snapshotIndex
2000
+ );
2001
+ if (node.children.length) {
2002
+ stream.prevEndMs = startMs;
2003
+ for (const child of node.children) {
2004
+ emitNode(stream, child, ctx, callId);
2005
+ }
2006
+ }
2007
+ const endMs = Math.max(
2008
+ startMs + 1,
2009
+ cmd.timestamp - ctx.wallTime,
2010
+ node.children.length ? stream.prevEndMs : 0
2011
+ );
2012
+ stream.events.push(buildAfterEvent(cmd, action, callId, endMs, ctx));
1848
2013
  stream.prevEndMs = endMs;
1849
2014
  }
1850
2015
  function buildActionEvents(commands, pageId, wallTime, testMetadata, snapshotIndex) {
@@ -1857,17 +2022,24 @@ function buildActionEvents(commands, pageId, wallTime, testMetadata, snapshotInd
1857
2022
  const ordered = [...commands].sort(
1858
2023
  (a, b) => (a.startTime ?? a.timestamp) - (b.startTime ?? b.timestamp) || (a.sequence ?? 0) - (b.sequence ?? 0)
1859
2024
  );
1860
- for (const cmd of ordered) {
1861
- const action = mapCommandToAction(cmd.command);
1862
- if (!action) {
1863
- continue;
2025
+ const ctx = {
2026
+ pageId,
2027
+ wallTime,
2028
+ snapshotIndex,
2029
+ retried: retriedRows(ordered)
2030
+ };
2031
+ for (const node of nestCommands(ordered)) {
2032
+ if (hasAction(node)) {
2033
+ syncGroups(stream, node.command, pageId, wallTime, testMetadata);
1864
2034
  }
1865
- syncGroups(stream, cmd, pageId, wallTime, testMetadata);
1866
- pushActionPair(stream, cmd, action, pageId, wallTime, snapshotIndex);
2035
+ emitNode(stream, node, ctx);
1867
2036
  }
1868
2037
  closeGroupsFrom(stream, 0);
1869
2038
  return stream.events;
1870
2039
  }
2040
+ function hasAction(node) {
2041
+ return Boolean(mapCommandToAction(node.command.command)) || node.children.some(hasAction);
2042
+ }
1871
2043
 
1872
2044
  // ../trace/src/trace-har.ts
1873
2045
  var INLINE_BODY_TEXT_MAX_BYTES = 8 * 1024;
@@ -2068,6 +2240,7 @@ function capturedSteps(commands) {
2068
2240
  function generateTranscript(commands, startWallTime, title) {
2069
2241
  const wallTimeISO = new Date(startWallTime).toISOString();
2070
2242
  const lines = [`# ${title ?? "Session"} \u2014 ${wallTimeISO}`, ""];
2243
+ const retried = retriedRows(commands);
2071
2244
  capturedSteps(commands).forEach(({ entry, action }, idx) => {
2072
2245
  const rawArgs = entry.args;
2073
2246
  const parts = [stripAnsi(formatActionTitle(action, rawArgs))];
@@ -2078,7 +2251,8 @@ function generateTranscript(commands, startWallTime, title) {
2078
2251
  }
2079
2252
  }
2080
2253
  if (entry.error) {
2081
- parts.push(`ERROR: ${errorMessage2(entry.error)}`);
2254
+ const label = retried.has(entry) ? "RETRIED" : "ERROR";
2255
+ parts.push(`${label}: ${errorMessage2(entry.error)}`);
2082
2256
  }
2083
2257
  lines.push(asNumberedItem(`${idx + 1}. `, parts.join(" ")));
2084
2258
  });
@@ -2820,7 +2994,7 @@ async function maybeWriteManifest(ctx, fanOutArtifacts) {
2820
2994
  }
2821
2995
  const artifacts = ctx.collectedArtifacts ?? fanOutArtifacts;
2822
2996
  try {
2823
- const path10 = await writeArtifactsManifest(
2997
+ const path11 = await writeArtifactsManifest(
2824
2998
  ctx.resolveOutputDir(),
2825
2999
  buildArtifactsManifest({
2826
3000
  sessionId: ctx.sessionId,
@@ -2829,7 +3003,7 @@ async function maybeWriteManifest(ctx, fanOutArtifacts) {
2829
3003
  testMetadata: ctx.testMetadata
2830
3004
  })
2831
3005
  );
2832
- ctx.log?.("info", `Artifacts manifest written to ${path10}`);
3006
+ ctx.log?.("info", `Artifacts manifest written to ${path11}`);
2833
3007
  } catch (err) {
2834
3008
  ctx.log?.(
2835
3009
  "warn",
@@ -3148,7 +3322,7 @@ function resolveBaseDir(input) {
3148
3322
  if (!dir) {
3149
3323
  continue;
3150
3324
  }
3151
- if (dir.includes(NODE_MODULES_SEGMENT)) {
3325
+ if (`${dir}${path4.sep}`.includes(NODE_MODULES_SEGMENT)) {
3152
3326
  continue;
3153
3327
  }
3154
3328
  if (isWritable(dir)) {
@@ -3384,14 +3558,14 @@ function translateRecursively(domNode, parentPath = "", index = null) {
3384
3558
  }
3385
3559
  }
3386
3560
  }
3387
- const path10 = index === null ? "" : `${parentPath ? parentPath + "." : ""}${index}`;
3561
+ const path11 = index === null ? "" : `${parentPath ? parentPath + "." : ""}${index}`;
3388
3562
  return {
3389
3563
  children: childNodesOf(domNode).map(
3390
- (childNode, childIndex) => translateRecursively(childNode, path10, childIndex)
3564
+ (childNode, childIndex) => translateRecursively(childNode, path11, childIndex)
3391
3565
  ),
3392
3566
  tagName: domNode.nodeName,
3393
3567
  attributes,
3394
- path: path10
3568
+ path: path11
3395
3569
  };
3396
3570
  }
3397
3571
  function isSameElement(node1, node2) {
@@ -3532,11 +3706,11 @@ function checkXPathUniqueness(doc, xpathExpr, targetNode) {
3532
3706
  return { isUnique: false };
3533
3707
  }
3534
3708
  }
3535
- function findDOMNodeByPath(doc, path10) {
3536
- if (!path10) {
3709
+ function findDOMNodeByPath(doc, path11) {
3710
+ if (!path11) {
3537
3711
  return doc.documentElement;
3538
3712
  }
3539
- const indices = path10.split(".").map(Number);
3713
+ const indices = path11.split(".").map(Number);
3540
3714
  let current = doc.documentElement;
3541
3715
  for (const index of indices) {
3542
3716
  if (!current) {
@@ -5086,21 +5260,21 @@ async function captureAndAttachVideo(input) {
5086
5260
  import http from "http";
5087
5261
  import https from "https";
5088
5262
  var WEBDRIVER_REQUEST_TIMEOUT_MS = 5e3;
5089
- function basePrefix(path10) {
5090
- if (!path10 || path10 === "/") {
5263
+ function basePrefix(path11) {
5264
+ if (!path11 || path11 === "/") {
5091
5265
  return "";
5092
5266
  }
5093
- return path10.endsWith("/") ? path10.slice(0, -1) : path10;
5267
+ return path11.endsWith("/") ? path11.slice(0, -1) : path11;
5094
5268
  }
5095
5269
  function formatHost(hostname) {
5096
5270
  const isIpv6Literal = hostname.includes(":") && !hostname.startsWith("[");
5097
5271
  return isIpv6Literal ? `[${hostname}]` : hostname;
5098
5272
  }
5099
- function sessionEndpoint(address, sessionId, path10) {
5273
+ function sessionEndpoint(address, sessionId, path11) {
5100
5274
  const protocol = address.protocol ?? "http";
5101
5275
  const prefix = basePrefix(address.path);
5102
5276
  const host = formatHost(address.hostname);
5103
- return `${protocol}://${host}:${address.port}${prefix}/session/${sessionId}/${path10}`;
5277
+ return `${protocol}://${host}:${address.port}${prefix}/session/${sessionId}/${path11}`;
5104
5278
  }
5105
5279
  function authHeaders(address) {
5106
5280
  if (!address.user || !address.key) {
@@ -5184,17 +5358,17 @@ function webdriverRequest(address, endpoint, method, body) {
5184
5358
  req.end();
5185
5359
  });
5186
5360
  }
5187
- function webdriverGet(address, sessionId, path10) {
5361
+ function webdriverGet(address, sessionId, path11) {
5188
5362
  return webdriverRequest(
5189
5363
  address,
5190
- sessionEndpoint(address, sessionId, path10),
5364
+ sessionEndpoint(address, sessionId, path11),
5191
5365
  "GET"
5192
5366
  );
5193
5367
  }
5194
- function webdriverPost(address, sessionId, path10, body) {
5368
+ function webdriverPost(address, sessionId, path11, body) {
5195
5369
  return webdriverRequest(
5196
5370
  address,
5197
- sessionEndpoint(address, sessionId, path10),
5371
+ sessionEndpoint(address, sessionId, path11),
5198
5372
  "POST",
5199
5373
  body
5200
5374
  );
@@ -5454,10 +5628,60 @@ async function resolveViewport(capabilities, probes) {
5454
5628
  import { createRequire as createRequire3 } from "module";
5455
5629
 
5456
5630
  // ../core/src/stack.ts
5631
+ import { existsSync, readFileSync } from "fs";
5632
+ import path8 from "path";
5633
+ import { fileURLToPath } from "url";
5457
5634
  import { parse as parseStackTrace } from "stacktrace-parser";
5635
+ var WORKSPACE_ROOT_NAME = "devtools-monorepo";
5636
+ function findOwnPackagesDir(fromDir) {
5637
+ for (let dir = fromDir; ; dir = path8.dirname(dir)) {
5638
+ if (existsSync(path8.join(dir, "pnpm-workspace.yaml"))) {
5639
+ return readPackageName(dir) === WORKSPACE_ROOT_NAME ? path8.join(dir, "packages") : void 0;
5640
+ }
5641
+ if (path8.dirname(dir) === dir) {
5642
+ return void 0;
5643
+ }
5644
+ }
5645
+ }
5646
+ function readPackageName(dir) {
5647
+ try {
5648
+ const pkg = JSON.parse(
5649
+ readFileSync(path8.join(dir, "package.json"), "utf8")
5650
+ );
5651
+ return typeof pkg.name === "string" ? pkg.name : void 0;
5652
+ } catch {
5653
+ return void 0;
5654
+ }
5655
+ }
5656
+ var ownPackagesDir;
5657
+ function resolveOwnPackagesDir() {
5658
+ if (ownPackagesDir === void 0) {
5659
+ try {
5660
+ ownPackagesDir = findOwnPackagesDir(path8.dirname(fileURLToPath(import.meta.url))) ?? null;
5661
+ } catch {
5662
+ ownPackagesDir = null;
5663
+ }
5664
+ }
5665
+ return ownPackagesDir ?? void 0;
5666
+ }
5667
+ function isPackageSource(file, packagesDir) {
5668
+ if (!packagesDir) {
5669
+ return false;
5670
+ }
5671
+ try {
5672
+ const filePath = file.startsWith("file://") ? fileURLToPath(file) : file;
5673
+ const relative = path8.relative(packagesDir, filePath);
5674
+ if (relative.startsWith("..") || path8.isAbsolute(relative)) {
5675
+ return false;
5676
+ }
5677
+ return relative.split(path8.sep)[1] === "src";
5678
+ } catch {
5679
+ return false;
5680
+ }
5681
+ }
5458
5682
  function isUserCodeFrame(frame) {
5459
5683
  const { file } = frame;
5460
- return !!(file && !file.includes("/node_modules/") && !file.includes("<anonymous>") && !file.includes("node:internal") && !file.includes("/dist/") && !file.endsWith("/index.js"));
5684
+ return !!(file && !file.includes("/node_modules/") && !file.includes("<anonymous>") && !file.includes("node:internal") && !file.includes("/dist/") && !file.endsWith("/index.js") && !isPackageSource(file, resolveOwnPackagesDir()));
5461
5685
  }
5462
5686
  function normalizeFilePath(filePath) {
5463
5687
  const stripped = filePath.replace(/^file:\/\//, "").split(":")[0];
@@ -5474,16 +5698,24 @@ function isAssertFromUserCode(stack) {
5474
5698
  const caller = parseStackTrace(stack)[1];
5475
5699
  return !!caller && isUserCodeFrame(caller);
5476
5700
  }
5701
+ var NO_CALL_SOURCE = {
5702
+ filePath: void 0,
5703
+ callSource: void 0
5704
+ };
5477
5705
  function getCallSourceFromStack(stack = new Error().stack) {
5478
5706
  if (!stack) {
5479
- return { filePath: void 0, callSource: "unknown:0" };
5707
+ return NO_CALL_SOURCE;
5480
5708
  }
5481
- const frame = parseStackTrace(stack).find(isUserCodeFrame);
5482
- if (!frame?.file) {
5483
- return { filePath: void 0, callSource: "unknown:0" };
5709
+ try {
5710
+ const frame = parseStackTrace(stack).find(isUserCodeFrame);
5711
+ if (!frame?.file) {
5712
+ return NO_CALL_SOURCE;
5713
+ }
5714
+ const filePath = normalizeFilePath(frame.file);
5715
+ return { filePath, callSource: `${filePath}:${frame.lineNumber ?? 0}` };
5716
+ } catch {
5717
+ return NO_CALL_SOURCE;
5484
5718
  }
5485
- const filePath = normalizeFilePath(frame.file);
5486
- return { filePath, callSource: `${filePath}:${frame.lineNumber ?? 0}` };
5487
5719
  }
5488
5720
 
5489
5721
  // ../core/src/assert-patcher.ts
@@ -5705,19 +5937,14 @@ function patchNodeAssert(onCommand, onLog, resolveAssertTarget) {
5705
5937
 
5706
5938
  // ../core/src/script-loader.ts
5707
5939
  import fs8 from "fs/promises";
5708
- import path8 from "path";
5940
+ import path9 from "path";
5709
5941
  import { createRequire as createRequire4 } from "module";
5710
5942
  var require4 = createRequire4(import.meta.url);
5711
- var COLLECTOR_READY_EXPRESSION = 'typeof window.wdioTraceCollector !== "undefined"';
5712
- function collectorDrainExpression(forceAnchor = false) {
5713
- const call = forceAnchor ? "window.wdioTraceCollector.captureCurrentDom();" : "";
5714
- return `if (!(${COLLECTOR_READY_EXPRESSION})) { return null; } ${call} return window.wdioTraceCollector.getTraceData();`;
5715
- }
5716
5943
  function collectorSourceCandidates(entry) {
5717
- const dir = path8.dirname(entry);
5944
+ const dir = path9.dirname(entry);
5718
5945
  return [
5719
- path8.join(dir, "script.js"),
5720
- path8.join(dir, "..", "dist", "script.js")
5946
+ path9.join(dir, "script.js"),
5947
+ path9.join(dir, "..", "dist", "script.js")
5721
5948
  ];
5722
5949
  }
5723
5950
  async function loadCollectorSource() {
@@ -5848,7 +6075,7 @@ var TerminalLineThrottle = class {
5848
6075
  // ../core/src/finalize-screencast.ts
5849
6076
  import fs9 from "fs";
5850
6077
  import os2 from "os";
5851
- import path9 from "path";
6078
+ import path10 from "path";
5852
6079
  async function finalizeScreencast({
5853
6080
  recorder,
5854
6081
  sessionId,
@@ -5872,12 +6099,12 @@ async function finalizeScreencast({
5872
6099
  }
5873
6100
  const fileName = `${filenamePrefix}-${sessionId}.webm`;
5874
6101
  const candidate = outputDir || process.cwd();
5875
- let videoPath = path9.join(candidate, fileName);
6102
+ let videoPath = path10.join(candidate, fileName);
5876
6103
  try {
5877
6104
  fs9.mkdirSync(candidate, { recursive: true });
5878
6105
  fs9.accessSync(candidate, fs9.constants.W_OK);
5879
6106
  } catch {
5880
- videoPath = path9.join(os2.tmpdir(), fileName);
6107
+ videoPath = path10.join(os2.tmpdir(), fileName);
5881
6108
  }
5882
6109
  try {
5883
6110
  await encodeToVideo(frames, videoPath, { captureFormat });
@@ -5945,54 +6172,9 @@ function isInputDispatchInFlight() {
5945
6172
  }
5946
6173
 
5947
6174
  // ../core/src/performance-capture.ts
5948
- var CAPTURE_PERFORMANCE_SCRIPT = `
5949
- (function() {
5950
- const performance = window.performance;
5951
- const navigation = performance.getEntriesByType?.('navigation')?.[0];
5952
- const resources = performance.getEntriesByType?.('resource') || [];
5953
-
5954
- return {
5955
- navigation: navigation ? {
5956
- url: window.location.href,
5957
- timing: {
5958
- loadTime: navigation.loadEventEnd - navigation.fetchStart,
5959
- domReady: navigation.domContentLoadedEventEnd - navigation.fetchStart,
5960
- responseTime: navigation.responseEnd - navigation.requestStart,
5961
- dnsLookup: navigation.domainLookupEnd - navigation.domainLookupStart,
5962
- tcpConnection: navigation.connectEnd - navigation.connectStart,
5963
- serverResponse: navigation.responseEnd - navigation.responseStart
5964
- }
5965
- } : undefined,
5966
- resources: resources.map(function(resource) {
5967
- return {
5968
- url: resource.name,
5969
- duration: resource.duration,
5970
- size: resource.transferSize || 0,
5971
- type: resource.initiatorType,
5972
- startTime: resource.startTime,
5973
- responseEnd: resource.responseEnd
5974
- };
5975
- }),
5976
- cookies: (function() {
5977
- try { return document.cookie; } catch (e) { return ''; }
5978
- })(),
5979
- documentInfo: {
5980
- url: window.location.href,
5981
- title: document.title,
5982
- headers: {
5983
- userAgent: navigator.userAgent,
5984
- language: navigator.language,
5985
- platform: navigator.platform
5986
- },
5987
- documentInfo: {
5988
- readyState: document.readyState,
5989
- referrer: document.referrer,
5990
- characterSet: document.characterSet
5991
- }
5992
- }
5993
- };
5994
- })()
5995
- `;
6175
+ function formatMilliseconds(ms) {
6176
+ return typeof ms === "number" && Number.isFinite(ms) ? `${ms.toFixed(2)} ms` : void 0;
6177
+ }
5996
6178
  function applyPerformanceData(command, payload, navigatedUrl) {
5997
6179
  if (!payload || !payload.navigation) {
5998
6180
  return false;
@@ -6005,7 +6187,7 @@ function applyPerformanceData(command, payload, navigatedUrl) {
6005
6187
  command.documentInfo = payload.documentInfo;
6006
6188
  command.result = {
6007
6189
  url: navigatedUrl,
6008
- loadTime: payload.navigation?.timing?.loadTime,
6190
+ loadTime: formatMilliseconds(payload.navigation?.timing?.loadTime),
6009
6191
  resources: payload.resources,
6010
6192
  resourceCount: payload.resources?.length,
6011
6193
  cookies: payload.cookies,
@@ -6037,42 +6219,35 @@ function rememberReadValue(value, selector) {
6037
6219
  }
6038
6220
  }
6039
6221
  }
6040
- function selectorForReadValue(value) {
6041
- return selectorByReadValue.get(value) ?? void 0;
6222
+ function clearReadValues() {
6223
+ selectorByReadValue.clear();
6042
6224
  }
6043
6225
  function resolveAssertTargetFromArgs(args, fromHandle) {
6044
6226
  for (const arg of args) {
6045
- const selector = fromHandle?.(arg) ?? selectorForReadValue(arg);
6046
- if (selector) {
6047
- return selector;
6227
+ const handle = fromHandle?.(arg);
6228
+ if (handle) {
6229
+ return handle;
6230
+ }
6231
+ if (selectorByReadValue.has(arg)) {
6232
+ return selectorByReadValue.get(arg) ?? void 0;
6048
6233
  }
6049
6234
  }
6050
6235
  return void 0;
6051
6236
  }
6052
6237
 
6053
6238
  // ../core/src/retry-tracker.ts
6054
- var RetryTracker = class _RetryTracker {
6239
+ var RetryTracker = class {
6055
6240
  #lastSig = null;
6056
- #lastBareSig = null;
6057
6241
  #lastId = null;
6058
6242
  /** Build the canonical signature used for retry-equality checks. */
6059
6243
  static signature(command, args, callSource) {
6060
6244
  return JSON.stringify({ command, args, src: callSource ?? null });
6061
6245
  }
6062
- /** The signature without the call site: a framework's own repeat of a call
6063
- * (a `wait()` poll from a timer) has no user frame on its stack. */
6064
- static bareSignature(command, args) {
6065
- return JSON.stringify({ command, args });
6066
- }
6067
6246
  /** True when the incoming signature matches the last captured one AND we
6068
6247
  * have an id to replace (otherwise there's nothing to replace yet). */
6069
6248
  isRetry(sig) {
6070
6249
  return sig === this.#lastSig && this.#lastId !== null;
6071
6250
  }
6072
- /** As `isRetry`, for a repeat that carries no call site of its own. */
6073
- isRetryIgnoringSource(bareSig) {
6074
- return bareSig === this.#lastBareSig && this.#lastId !== null;
6075
- }
6076
6251
  /** The id of the last captured command, if any (for the replace-in-place
6077
6252
  * flow). */
6078
6253
  get lastId() {
@@ -6081,17 +6256,8 @@ var RetryTracker = class _RetryTracker {
6081
6256
  /** Record a fresh capture — sets both sig and id together. */
6082
6257
  recordCapture(sig, id) {
6083
6258
  this.#lastSig = sig;
6084
- this.#lastBareSig = _RetryTracker.bareOf(sig);
6085
6259
  this.#lastId = id;
6086
6260
  }
6087
- static bareOf(sig) {
6088
- try {
6089
- const { command, args } = JSON.parse(sig);
6090
- return _RetryTracker.bareSignature(command, args);
6091
- } catch {
6092
- return null;
6093
- }
6094
- }
6095
6261
  /** Record only the id (used by adapters that compute the sig but defer the
6096
6262
  * id assignment to after an async capture call). */
6097
6263
  setLastId(id) {
@@ -6102,12 +6268,10 @@ var RetryTracker = class _RetryTracker {
6102
6268
  * commands). Pair with {@link setLastId} once the capture resolves. */
6103
6269
  setLastSig(sig) {
6104
6270
  this.#lastSig = sig;
6105
- this.#lastBareSig = _RetryTracker.bareOf(sig);
6106
6271
  }
6107
6272
  /** Reset at test/scenario boundaries so the next capture is "fresh". */
6108
6273
  reset() {
6109
6274
  this.#lastSig = null;
6110
- this.#lastBareSig = null;
6111
6275
  this.#lastId = null;
6112
6276
  }
6113
6277
  };
@@ -6412,6 +6576,7 @@ function restoreBrowserFocus(target, platform = process.platform, launch = spawn
6412
6576
  }
6413
6577
 
6414
6578
  // ../core/src/session-capturer.ts
6579
+ import { randomInt } from "crypto";
6415
6580
  import fs10 from "fs/promises";
6416
6581
  import { WebSocket } from "ws";
6417
6582
  var SessionCapturerBase = class _SessionCapturerBase {
@@ -6444,7 +6609,8 @@ var SessionCapturerBase = class _SessionCapturerBase {
6444
6609
  // (WDIO owns the command lifecycle), so this state is harmless overhead.
6445
6610
  // `protected` (not `#`) so subclasses can override the send/replace flow
6446
6611
  // while still sharing the counter and de-dup set with base helpers.
6447
- commandCounter = 0;
6612
+ // 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.
6613
+ commandCounter = randomInt(2 ** 32) * 2 ** 20;
6448
6614
  sentCommandIds = /* @__PURE__ */ new Set();
6449
6615
  // Map of file path → source text. Populated by `captureSource` (also
6450
6616
  // accessed by adapter-specific source-discovery flows, e.g. service's
@@ -7245,11 +7411,18 @@ var TestReporterBase = class {
7245
7411
 
7246
7412
  export {
7247
7413
  mapCommandToAction,
7414
+ isWaitCommand,
7248
7415
  stripAnsi,
7249
- mapChromeBrowserLogs,
7416
+ COLLECTOR_READY_SCRIPT,
7417
+ COLLECTOR_INJECT_SCRIPT,
7418
+ collectorDrainExpression,
7419
+ CAPTURE_PERFORMANCE_SCRIPT,
7420
+ commandFailure,
7250
7421
  getRequestType,
7251
7422
  WS_SCOPE,
7252
7423
  REUSE_ENV,
7424
+ RUNNER_ENV,
7425
+ encodeInitialArgs,
7253
7426
  TEST_STATE,
7254
7427
  NETWORK_CAPTURE_SOURCE,
7255
7428
  SCREENCAST_DEFAULTS,
@@ -7285,8 +7458,6 @@ export {
7285
7458
  finalizeTraceExport,
7286
7459
  findFreePort,
7287
7460
  attachBidiHandlers,
7288
- COLLECTOR_READY_EXPRESSION,
7289
- collectorDrainExpression,
7290
7461
  loadInjectableScript,
7291
7462
  drainCollectorWithRecovery,
7292
7463
  pollUntilReady,
@@ -7294,9 +7465,9 @@ export {
7294
7465
  finalizeScreencast,
7295
7466
  beginInputDispatch,
7296
7467
  resolveAdapterOutputDir,
7297
- CAPTURE_PERFORMANCE_SCRIPT,
7298
7468
  applyPerformanceData,
7299
7469
  rememberReadValue,
7470
+ clearReadValues,
7300
7471
  resolveAssertTargetFromArgs,
7301
7472
  RetryTracker,
7302
7473
  ScreencastRecorderBase,
@@ -7312,4 +7483,4 @@ export {
7312
7483
  findTestDefinitions,
7313
7484
  TestReporterBase
7314
7485
  };
7315
- //# sourceMappingURL=chunk-TUK7ZLQI.js.map
7486
+ //# sourceMappingURL=chunk-DRZC45YG.js.map