@wdio/nightwatch-devtools 1.4.0 → 1.5.1

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/dist/index.js CHANGED
@@ -17,7 +17,72 @@ import {
17
17
  // src/index.ts
18
18
  import { fileURLToPath } from "url";
19
19
 
20
- // ../core/src/action-mapping.ts
20
+ // ../shared/src/files.ts
21
+ var SPEC_FILE_RE = /\.(?:test|spec)\.[cm]?[jt]sx?$/i;
22
+
23
+ // ../shared/src/routes.ts
24
+ var WS_PATHS = {
25
+ /** Adapter session upgrade endpoint. One socket per running adapter. */
26
+ worker: "/worker",
27
+ /** App/UI client upgrade endpoint. Multiple browser tabs may connect. */
28
+ client: "/client"
29
+ };
30
+ var WS_SCOPE = {
31
+ /** Backend → worker: a dashboard client has subscribed. Wakes up the
32
+ * adapter's `await UI ready` gate before tests start. */
33
+ clientConnected: "clientConnected",
34
+ /** Backend → worker: the last dashboard tab closed. Triggers the
35
+ * interactive shutdown flow (close WS, exit, pkill the dashboard). */
36
+ clientDisconnected: "clientDisconnected",
37
+ /** Worker → backend → clients (or app-local): wipe the visualization data
38
+ * for a specific test/suite uid (or the whole tree). */
39
+ clearExecutionData: "clearExecutionData",
40
+ /** Worker → backend: clear-by-test-uid request (drives clearExecutionData). */
41
+ clearCommands: "clearCommands",
42
+ /** Backend → clients: signal that the active run was stopped by the UI. */
43
+ testStopped: "testStopped",
44
+ /** Worker → backend → clients: swap an earlier captured command for an
45
+ * updated entry (used when a retry coalesces commands). */
46
+ replaceCommand: "replaceCommand",
47
+ /** Worker → backend: register the config file path so reruns can spawn
48
+ * with the same config. */
49
+ config: "config"
50
+ };
51
+
52
+ // ../shared/src/runner.ts
53
+ var REUSE_ENV = {
54
+ REUSE: "DEVTOOLS_APP_REUSE",
55
+ HOST: "DEVTOOLS_APP_HOST",
56
+ PORT: "DEVTOOLS_APP_PORT",
57
+ RERUN_LABEL: "DEVTOOLS_RERUN_LABEL",
58
+ RERUN_ENTRY_TYPE: "DEVTOOLS_RERUN_ENTRY_TYPE"
59
+ };
60
+
61
+ // ../shared/src/timing.ts
62
+ var TIMING_BASE = {
63
+ /** ms to let the dashboard render between rapid state updates. */
64
+ UI_RENDER_DELAY: 150,
65
+ /** ms to wait after a test starts before flushing initial state. */
66
+ TEST_START_DELAY: 100,
67
+ /** ms gap between consecutive suite-finalize broadcasts. */
68
+ SUITE_COMPLETE_DELAY: 200,
69
+ /** ms allowed for the browser-close handshake before forcing teardown. */
70
+ BROWSER_CLOSE_WAIT: 2e3,
71
+ /** ms before first worker WS connection attempt — lets backend bind. */
72
+ INITIAL_CONNECTION_WAIT: 500,
73
+ /** ms between browser-window-alive polls. */
74
+ BROWSER_POLL_INTERVAL: 1e3
75
+ };
76
+ var DEFAULTS_BASE = {
77
+ /** Synthetic capability ID — present on every CommandLog / TestStats. */
78
+ CID: "0-0",
79
+ /** TestStats.retries default — adapters that surface retry counts override. */
80
+ RETRIES: 0,
81
+ /** _duration placeholder for not-yet-finalized stats. */
82
+ DURATION: 0
83
+ };
84
+
85
+ // ../shared/src/trace-actions.ts
21
86
  var ACTION_MAP = {
22
87
  // WDIO browser-level
23
88
  url: { class: "Page", method: "navigate" },
@@ -48,6 +113,25 @@ var ACTION_MAP = {
48
113
  switchToFrame: { class: "Frame", method: "goto" },
49
114
  touchAction: { class: "Element", method: "tap" }
50
115
  };
116
+
117
+ // ../shared/src/types.ts
118
+ var TEST_STATE = {
119
+ PENDING: "pending",
120
+ RUNNING: "running",
121
+ PASSED: "passed",
122
+ FAILED: "failed",
123
+ SKIPPED: "skipped"
124
+ };
125
+ var SCREENCAST_DEFAULTS = {
126
+ enabled: false,
127
+ captureFormat: "jpeg",
128
+ quality: 70,
129
+ maxWidth: 1280,
130
+ maxHeight: 720,
131
+ pollIntervalMs: 200
132
+ };
133
+
134
+ // ../core/src/action-mapping.ts
51
135
  function mapCommandToAction(command) {
52
136
  return ACTION_MAP[command] ?? null;
53
137
  }
@@ -470,10 +554,45 @@ function isSameElement(node1, node2) {
470
554
  }
471
555
  return false;
472
556
  }
557
+ function sanitizePageSource(sourceXML) {
558
+ let sanitized = sourceXML.replace(
559
+ /&(?!(?:amp|lt|gt|quot|apos|#\d+|#x[0-9a-fA-F]+);)/g,
560
+ "&"
561
+ );
562
+ const VOID_ELEMENTS = [
563
+ "link",
564
+ "meta",
565
+ "br",
566
+ "hr",
567
+ "img",
568
+ "input",
569
+ "source",
570
+ "area",
571
+ "base",
572
+ "col",
573
+ "embed",
574
+ "track",
575
+ "wbr"
576
+ ];
577
+ for (const tag of VOID_ELEMENTS) {
578
+ sanitized = sanitized.replace(
579
+ // eslint-disable-next-line security/detect-non-literal-regexp -- tag from hardcoded list
580
+ new RegExp(`<${tag}(\\s[^>]*?[^/])>`, "gi"),
581
+ `<${tag}$1/>`
582
+ );
583
+ sanitized = sanitized.replace(
584
+ // eslint-disable-next-line security/detect-non-literal-regexp -- tag from hardcoded list
585
+ new RegExp(`<${tag}>`, "gi"),
586
+ `<${tag}/>`
587
+ );
588
+ }
589
+ return sanitized;
590
+ }
473
591
  function xmlToJSON(sourceXML) {
474
592
  try {
593
+ const sanitized = sanitizePageSource(sourceXML);
475
594
  const parser = new DOMParser();
476
- const sourceDoc = parser.parseFromString(sourceXML, "text/xml");
595
+ const sourceDoc = parser.parseFromString(sanitized, "text/xml");
477
596
  const parseErrors = sourceDoc.getElementsByTagName("parsererror");
478
597
  if (parseErrors.length > 0) {
479
598
  console.error(
@@ -492,8 +611,9 @@ function xmlToJSON(sourceXML) {
492
611
  }
493
612
  function xmlToDOM(sourceXML) {
494
613
  try {
614
+ const sanitized = sanitizePageSource(sourceXML);
495
615
  const parser = new DOMParser();
496
- const doc = parser.parseFromString(sourceXML, "text/xml");
616
+ const doc = parser.parseFromString(sanitized, "text/xml");
497
617
  const parseErrors = doc.getElementsByTagName("parsererror");
498
618
  if (parseErrors.length > 0) {
499
619
  console.error("[xmlToDOM] XML parsing error:", parseErrors[0].textContent);
@@ -750,7 +870,7 @@ function isValidValue(value) {
750
870
  return value !== void 0 && value !== null && value !== "null" && value.trim() !== "";
751
871
  }
752
872
  function escapeText(text) {
753
- return text.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n");
873
+ return text.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
754
874
  }
755
875
  function escapeXPathValue(value) {
756
876
  if (!value.includes("'")) {
@@ -1206,21 +1326,8 @@ function serializeWebSnapshot(nodes, context, options = {}) {
1206
1326
  }
1207
1327
  const indent = " ".repeat(node.depth + 1);
1208
1328
  const isInteractive = INTERACTIVE_ROLES.has(node.role);
1209
- if (node.role === "statictext" && node.name) {
1210
- let echoedByParent = false;
1211
- for (let j = i - 1; j >= 0; j--) {
1212
- if (nodes[j].depth < node.depth) {
1213
- const parentRole = nodes[j].role;
1214
- const parentName = nodes[j].name;
1215
- if (INTERACTIVE_ROLES.has(parentRole) && parentName && parentName.includes(node.name)) {
1216
- echoedByParent = true;
1217
- }
1218
- break;
1219
- }
1220
- }
1221
- if (echoedByParent) {
1222
- continue;
1223
- }
1329
+ if (isStatictextEchoedByParent(nodes, i)) {
1330
+ continue;
1224
1331
  }
1225
1332
  const roleLabel = node.role === "heading" && node.level ? `heading[${node.level}]` : node.role;
1226
1333
  if (isInteractive) {
@@ -1245,6 +1352,22 @@ function serializeWebSnapshot(nodes, context, options = {}) {
1245
1352
  }
1246
1353
  return lines.join("\n");
1247
1354
  }
1355
+ function isStatictextEchoedByParent(nodes, index) {
1356
+ const node = nodes[index];
1357
+ if (node.role !== "statictext" || !node.name) {
1358
+ return false;
1359
+ }
1360
+ for (let j = index - 1; j >= 0; j--) {
1361
+ if (nodes[j].depth < node.depth) {
1362
+ const parent = nodes[j];
1363
+ if (INTERACTIVE_ROLES.has(parent.role) && parent.name && parent.name.includes(node.name)) {
1364
+ return true;
1365
+ }
1366
+ break;
1367
+ }
1368
+ }
1369
+ return false;
1370
+ }
1248
1371
  function simplifyTag(tagName) {
1249
1372
  const dot = tagName.lastIndexOf(".");
1250
1373
  if (dot !== -1) {
@@ -1308,6 +1431,20 @@ function classifyMobileRole(tagName, platform) {
1308
1431
  }
1309
1432
  return IOS_ROLE_MAP[tagName] || simplifyTag(tagName);
1310
1433
  }
1434
+ function getFirstChildText(element) {
1435
+ const queue = [...element.children || []];
1436
+ while (queue.length > 0) {
1437
+ const el = queue.shift();
1438
+ const text = el.attributes?.text?.trim();
1439
+ if (text) {
1440
+ return text;
1441
+ }
1442
+ if (el.children) {
1443
+ queue.push(...el.children);
1444
+ }
1445
+ }
1446
+ return void 0;
1447
+ }
1311
1448
  function getBestAndroidLocator(attrs) {
1312
1449
  if (attrs._selector) {
1313
1450
  return attrs._selector;
@@ -1393,7 +1530,7 @@ function isMobileInViewport(element, platform, viewport) {
1393
1530
  function collectMobileNodes(element, platform, depth, nodes, walkOpts) {
1394
1531
  const attrs = element.attributes;
1395
1532
  const role = classifyMobileRole(element.tagName, platform);
1396
- const name = getMobileNodeIdentity(attrs, platform);
1533
+ let name = getMobileNodeIdentity(attrs, platform);
1397
1534
  const explicit = isExplicitlyInteractive(attrs, platform);
1398
1535
  const interactive = isMobileInteractive(element, platform);
1399
1536
  const inViewport = isMobileInViewport(element, platform, walkOpts.viewport);
@@ -1412,7 +1549,8 @@ function collectMobileNodes(element, platform, depth, nodes, walkOpts) {
1412
1549
  depth,
1413
1550
  isInteractive: false,
1414
1551
  isExplicitInteractive: false,
1415
- isInViewport: false
1552
+ isInViewport: false,
1553
+ tagName: element.tagName
1416
1554
  });
1417
1555
  return;
1418
1556
  }
@@ -1437,6 +1575,12 @@ function collectMobileNodes(element, platform, depth, nodes, walkOpts) {
1437
1575
  if (!locator) {
1438
1576
  locator = (platform === "android" ? getBestAndroidLocator(attrs) : getBestIOSLocator(attrs)) ?? "";
1439
1577
  }
1578
+ if (!name && platform === "android" && (locator.startsWith("class:") || locator.startsWith('android=new UiSelector().className("'))) {
1579
+ const childText = getFirstChildText(element);
1580
+ if (childText && childText.length < 100) {
1581
+ name = childText;
1582
+ }
1583
+ }
1440
1584
  }
1441
1585
  nodes.push({
1442
1586
  role,
@@ -1445,7 +1589,8 @@ function collectMobileNodes(element, platform, depth, nodes, walkOpts) {
1445
1589
  depth,
1446
1590
  isInteractive: interactive,
1447
1591
  isExplicitInteractive: explicit,
1448
- isInViewport: inViewport
1592
+ isInViewport: inViewport,
1593
+ tagName: element.tagName
1449
1594
  });
1450
1595
  for (const child of element.children || []) {
1451
1596
  collectMobileNodes(child, platform, depth + 1, nodes, walkOpts);
@@ -2695,88 +2840,6 @@ var RetryTracker = class {
2695
2840
  }
2696
2841
  };
2697
2842
 
2698
- // ../shared/src/files.ts
2699
- var SPEC_FILE_RE = /\.(?:test|spec)\.[cm]?[jt]sx?$/i;
2700
-
2701
- // ../shared/src/routes.ts
2702
- var WS_PATHS = {
2703
- /** Adapter session upgrade endpoint. One socket per running adapter. */
2704
- worker: "/worker",
2705
- /** App/UI client upgrade endpoint. Multiple browser tabs may connect. */
2706
- client: "/client"
2707
- };
2708
- var WS_SCOPE = {
2709
- /** Backend → worker: a dashboard client has subscribed. Wakes up the
2710
- * adapter's `await UI ready` gate before tests start. */
2711
- clientConnected: "clientConnected",
2712
- /** Backend → worker: the last dashboard tab closed. Triggers the
2713
- * interactive shutdown flow (close WS, exit, pkill the dashboard). */
2714
- clientDisconnected: "clientDisconnected",
2715
- /** Worker → backend → clients (or app-local): wipe the visualization data
2716
- * for a specific test/suite uid (or the whole tree). */
2717
- clearExecutionData: "clearExecutionData",
2718
- /** Worker → backend: clear-by-test-uid request (drives clearExecutionData). */
2719
- clearCommands: "clearCommands",
2720
- /** Backend → clients: signal that the active run was stopped by the UI. */
2721
- testStopped: "testStopped",
2722
- /** Worker → backend → clients: swap an earlier captured command for an
2723
- * updated entry (used when a retry coalesces commands). */
2724
- replaceCommand: "replaceCommand",
2725
- /** Worker → backend: register the config file path so reruns can spawn
2726
- * with the same config. */
2727
- config: "config"
2728
- };
2729
-
2730
- // ../shared/src/runner.ts
2731
- var REUSE_ENV = {
2732
- REUSE: "DEVTOOLS_APP_REUSE",
2733
- HOST: "DEVTOOLS_APP_HOST",
2734
- PORT: "DEVTOOLS_APP_PORT",
2735
- RERUN_LABEL: "DEVTOOLS_RERUN_LABEL",
2736
- RERUN_ENTRY_TYPE: "DEVTOOLS_RERUN_ENTRY_TYPE"
2737
- };
2738
-
2739
- // ../shared/src/timing.ts
2740
- var TIMING_BASE = {
2741
- /** ms to let the dashboard render between rapid state updates. */
2742
- UI_RENDER_DELAY: 150,
2743
- /** ms to wait after a test starts before flushing initial state. */
2744
- TEST_START_DELAY: 100,
2745
- /** ms gap between consecutive suite-finalize broadcasts. */
2746
- SUITE_COMPLETE_DELAY: 200,
2747
- /** ms allowed for the browser-close handshake before forcing teardown. */
2748
- BROWSER_CLOSE_WAIT: 2e3,
2749
- /** ms before first worker WS connection attempt — lets backend bind. */
2750
- INITIAL_CONNECTION_WAIT: 500,
2751
- /** ms between browser-window-alive polls. */
2752
- BROWSER_POLL_INTERVAL: 1e3
2753
- };
2754
- var DEFAULTS_BASE = {
2755
- /** Synthetic capability ID — present on every CommandLog / TestStats. */
2756
- CID: "0-0",
2757
- /** TestStats.retries default — adapters that surface retry counts override. */
2758
- RETRIES: 0,
2759
- /** _duration placeholder for not-yet-finalized stats. */
2760
- DURATION: 0
2761
- };
2762
-
2763
- // ../shared/src/types.ts
2764
- var TEST_STATE = {
2765
- PENDING: "pending",
2766
- RUNNING: "running",
2767
- PASSED: "passed",
2768
- FAILED: "failed",
2769
- SKIPPED: "skipped"
2770
- };
2771
- var SCREENCAST_DEFAULTS = {
2772
- enabled: false,
2773
- captureFormat: "jpeg",
2774
- quality: 70,
2775
- maxWidth: 1280,
2776
- maxHeight: 720,
2777
- pollIntervalMs: 200
2778
- };
2779
-
2780
2843
  // ../core/src/screencast.ts
2781
2844
  var ScreencastRecorderBase = class {
2782
2845
  buffer = [];