@wdio/nightwatch-devtools 1.8.1 → 2.0.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/dist/index.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import {
2
2
  CAPTURE_PERFORMANCE_SCRIPT,
3
3
  COLLECTOR_READY_EXPRESSION,
4
+ NETWORK_CAPTURE_SOURCE,
5
+ NativeNetworkCapture,
4
6
  REUSE_ENV,
5
7
  RetryTracker,
6
8
  SCREENCAST_DEFAULTS,
@@ -47,9 +49,11 @@ import {
47
49
  recordSpecBoundary,
48
50
  registerCollectorPreload,
49
51
  rememberReadValue,
52
+ requestsInterceptedNetwork,
50
53
  resolveAdapterOutputDir,
51
54
  resolveAssertTargetFromArgs,
52
55
  resolveViewport,
56
+ restoreBrowserFocus,
53
57
  safeSerializeAssertArg,
54
58
  serializeError,
55
59
  stampSuiteEnd,
@@ -58,9 +62,10 @@ import {
58
62
  toError,
59
63
  tracePolicyModeWarning,
60
64
  upsertRichestSnapshot,
65
+ webdriverExecute,
61
66
  webdriverGet,
62
67
  webdriverPost
63
- } from "./chunk-DM4FCAIJ.js";
68
+ } from "./chunk-TUK7ZLQI.js";
64
69
 
65
70
  // src/index.ts
66
71
  import { fileURLToPath } from "url";
@@ -546,23 +551,16 @@ async function openDevtoolsBrowser(ctx, url) {
546
551
  }
547
552
  ctx.devtoolsBrowser = await remote({
548
553
  logLevel: "info",
549
- automationProtocol: "devtools",
550
554
  capabilities: {
551
555
  browserName: "chrome",
552
556
  "goog:chromeOptions": {
557
+ excludeSwitches: ["enable-automation"],
553
558
  args: [
554
559
  "--window-size=1600,1200",
555
560
  `--user-data-dir=${ctx.userDataDir}`,
556
561
  "--no-first-run",
557
562
  "--no-default-browser-check"
558
563
  ]
559
- },
560
- // Dashboard uses the Puppeteer-based 'devtools' protocol; drop the
561
- // "controlled by automated test software" infobar by ignoring
562
- // Puppeteer's --enable-automation default. (Runtime-valid capability
563
- // not present in the type.)
564
- "wdio:devtoolsOptions": {
565
- ignoreDefaultArgs: ["--enable-automation"]
566
564
  }
567
565
  }
568
566
  });
@@ -601,7 +599,7 @@ async function waitForDevtoolsBrowserClose(ctx) {
601
599
  }
602
600
  ;
603
601
  logger2.setLevel(
604
- "devtools",
602
+ "webdriver",
605
603
  "warn"
606
604
  );
607
605
  let exitBySignal = false;
@@ -629,7 +627,7 @@ async function waitForDevtoolsBrowserClose(ctx) {
629
627
  process.removeListener("SIGINT", signalHandler);
630
628
  process.removeListener("SIGTERM", signalHandler);
631
629
  logger2.setLevel(
632
- "devtools",
630
+ "webdriver",
633
631
  "info"
634
632
  );
635
633
  try {
@@ -661,7 +659,7 @@ var firstDefined = (obj, ...keys) => {
661
659
  }
662
660
  return void 0;
663
661
  };
664
- function resolveWebDriverAddress(browser) {
662
+ function configuredWebDriver(browser) {
665
663
  const transportSettings = getPath(browser, "transport", "settings", "webdriver") || getPath(browser, "queue", "transport", "settings", "webdriver") || getPath(
666
664
  browser,
667
665
  "nightwatchInstance",
@@ -671,13 +669,24 @@ function resolveWebDriverAddress(browser) {
671
669
  ) || {};
672
670
  const opts = getProp(browser, "options") ?? {};
673
671
  const nightwatchSettings = getPath(browser, "nightwatchInstance", "settings") || getPath(browser, "globals", "nightwatchInstance", "settings") || {};
674
- const driverHost = String(
675
- firstDefined(transportSettings, "host", "server_address") || firstDefined(getProp(opts, "webdriver"), "host") || firstDefined(getProp(nightwatchSettings, "webdriver"), "host") || "localhost"
676
- );
677
- const driverPort = Number(
678
- firstDefined(transportSettings, "port") || firstDefined(getProp(opts, "webdriver"), "port") || firstDefined(getProp(nightwatchSettings, "webdriver"), "port") || 9515
679
- );
680
- return { driverHost, driverPort };
672
+ const sources = [
673
+ transportSettings,
674
+ getProp(opts, "webdriver"),
675
+ getProp(nightwatchSettings, "webdriver")
676
+ ];
677
+ const pick = (...keys) => sources.map((source) => firstDefined(source, ...keys)).find(Boolean);
678
+ return {
679
+ host: pick("host", "server_address"),
680
+ port: pick("port"),
681
+ pathPrefix: pick("default_path_prefix")
682
+ };
683
+ }
684
+ function resolveWebDriverAddress(browser) {
685
+ const { host, port } = configuredWebDriver(browser);
686
+ return {
687
+ driverHost: String(host || "localhost"),
688
+ driverPort: Number(port || 9515)
689
+ };
681
690
  }
682
691
  function address(browser) {
683
692
  const { driverHost, driverPort } = resolveWebDriverAddress(browser);
@@ -698,7 +707,21 @@ function webdriverPost2(browser, path5, body) {
698
707
  const id = sessionId(browser);
699
708
  return id ? webdriverPost(address(browser), id, path5, body) : Promise.resolve(null);
700
709
  }
701
- function webdriverExecute(browser, body) {
710
+ function sessionExecutor(browser) {
711
+ const id = sessionId(browser);
712
+ const { host, port, pathPrefix } = configuredWebDriver(browser);
713
+ if (!id || !port) {
714
+ return void 0;
715
+ }
716
+ const at = {
717
+ hostname: String(host || "localhost"),
718
+ port: Number(port),
719
+ path: typeof pathPrefix === "string" ? pathPrefix : void 0,
720
+ onWarn: (message) => log3.warn(message)
721
+ };
722
+ return (script, args) => webdriverExecute(at, id, script, args);
723
+ }
724
+ function webdriverExecute2(browser, body) {
702
725
  return webdriverPost2(browser, "execute/sync", { script: body, args: [] });
703
726
  }
704
727
 
@@ -713,7 +736,7 @@ function captureActionSnapshot2(browser, command, timestamp, runner, native = fa
713
736
  runner,
714
737
  takeScreenshot: () => webdriverGet2(browser, "screenshot"),
715
738
  ...native ? {} : {
716
- runScript: (src) => webdriverExecute(browser, `return (${src})`),
739
+ runScript: (src) => webdriverExecute2(browser, `return (${src})`),
717
740
  getUrl: () => webdriverGet2(browser, "url"),
718
741
  getTitle: () => webdriverGet2(browser, "title")
719
742
  }
@@ -721,6 +744,7 @@ function captureActionSnapshot2(browser, command, timestamp, runner, native = fa
721
744
  }
722
745
 
723
746
  // src/helpers/perfLogs.ts
747
+ var RESOURCE_TIMING_MATCH_MS = 2e3;
724
748
  function applyResponseReceived(p, response) {
725
749
  const responseHeaders = {};
726
750
  for (const [k, v] of Object.entries(response.headers || {})) {
@@ -746,7 +770,9 @@ function startPendingRequest(params, entry, pending) {
746
770
  url: req.url,
747
771
  method: req.method,
748
772
  requestHeaders: req.headers ?? {},
749
- timestamp: startTime,
773
+ // Epoch, from the event's own wall clock: every consumer dates a row by
774
+ // `timestamp`, and the monotonic `startTime` only diffs within a session.
775
+ timestamp: typeof params.wallTime === "number" ? Math.round(params.wallTime * 1e3) : entry.timestamp,
750
776
  startTime
751
777
  });
752
778
  }
@@ -804,6 +830,95 @@ function parseNetworkFromPerfLogs(logs) {
804
830
  }
805
831
  return completed;
806
832
  }
833
+ function resourceTimingRowsByUrl(rows) {
834
+ const byUrl = /* @__PURE__ */ new Map();
835
+ rows.forEach((row, index) => {
836
+ if (row.source !== NETWORK_CAPTURE_SOURCE.RESOURCE_TIMING) {
837
+ return;
838
+ }
839
+ const indexes = byUrl.get(row.url);
840
+ if (indexes) {
841
+ indexes.push(index);
842
+ } else {
843
+ byUrl.set(row.url, [index]);
844
+ }
845
+ });
846
+ return byUrl;
847
+ }
848
+ function takeNearestResourceTimingRow(indexes, rows, at) {
849
+ let best;
850
+ let bestDelta = RESOURCE_TIMING_MATCH_MS;
851
+ for (const index of indexes) {
852
+ const delta = Math.abs(rows[index].timestamp - at);
853
+ if (delta <= bestDelta) {
854
+ best = index;
855
+ bestDelta = delta;
856
+ }
857
+ }
858
+ if (best !== void 0) {
859
+ indexes.splice(indexes.indexOf(best), 1);
860
+ }
861
+ return best;
862
+ }
863
+ function overlay(row, entry) {
864
+ const merged = {
865
+ ...row,
866
+ method: entry.method,
867
+ requestHeaders: entry.requestHeaders,
868
+ type: entry.type ?? row.type,
869
+ ...entry.status !== void 0 ? { status: entry.status } : {},
870
+ ...entry.statusText !== void 0 ? { statusText: entry.statusText } : {},
871
+ ...entry.responseHeaders ? { responseHeaders: entry.responseHeaders } : {},
872
+ ...entry.size !== void 0 ? { size: entry.size } : {},
873
+ ...entry.error !== void 0 ? { error: entry.error } : {}
874
+ };
875
+ delete merged.source;
876
+ return merged;
877
+ }
878
+ function withoutLoadsTheLogListed(payload, listed) {
879
+ const requests = payload.networkRequests;
880
+ if (!Array.isArray(requests) || listed.length === 0) {
881
+ return payload;
882
+ }
883
+ const rows = requests;
884
+ const rowsByUrl = resourceTimingRowsByUrl(rows);
885
+ const taken = /* @__PURE__ */ new Set();
886
+ for (const load of listed) {
887
+ const index = takeNearestResourceTimingRow(
888
+ rowsByUrl.get(load.url) ?? [],
889
+ rows,
890
+ load.timestamp
891
+ );
892
+ if (index !== void 0) {
893
+ taken.add(index);
894
+ }
895
+ }
896
+ if (taken.size === 0) {
897
+ return payload;
898
+ }
899
+ return {
900
+ ...payload,
901
+ networkRequests: rows.filter((_, index) => !taken.has(index))
902
+ };
903
+ }
904
+ function upgradeResourceTimingRows(incoming, existing) {
905
+ const rowsByUrl = resourceTimingRowsByUrl(existing);
906
+ const upgrades = [];
907
+ const fresh = [];
908
+ for (const entry of incoming) {
909
+ const index = takeNearestResourceTimingRow(
910
+ rowsByUrl.get(entry.url) ?? [],
911
+ existing,
912
+ entry.timestamp
913
+ );
914
+ if (index === void 0) {
915
+ fresh.push(entry);
916
+ continue;
917
+ }
918
+ upgrades.push({ index, request: overlay(existing[index], entry) });
919
+ }
920
+ return { upgrades, fresh };
921
+ }
807
922
  function dedupeNetworkRequests(incoming, existing) {
808
923
  const failedKey = (entry) => {
809
924
  try {
@@ -856,6 +971,9 @@ var SessionCapturer = class extends SessionCapturerBase {
856
971
  // True once BiDi inspectors are attached — the per-command perf-log network
857
972
  // capture path skips when set, so we don't double-emit network requests.
858
973
  bidiActive = false;
974
+ /** Whether BiDi has delivered at least one network event; a subscription
975
+ * that delivers nothing is not a source. */
976
+ bidiNetworkObserved = false;
859
977
  /** Whether the session was created with `goog:loggingPrefs.performance`, set
860
978
  * from the capabilities at session init. A perf log exists only because a
861
979
  * session asked for one, so without it every fetch is a round trip that can
@@ -864,6 +982,10 @@ var SessionCapturer = class extends SessionCapturerBase {
864
982
  * 21 of them in a two-test run, where XCUITest lists `performance` among its
865
983
  * supported types and then serves none of it. */
866
984
  perfLogsRequested = false;
985
+ /** Loads the performance log listed before the page's drain could, spent
986
+ * at the next drain that reaches the driver, whatever it finds, and
987
+ * emptied with the session that listed them. */
988
+ loadsListedByLog = [];
867
989
  /** True once the collector is registered to run at document-start. Every
868
990
  * document then instruments and anchors itself, so the paths that exist to
869
991
  * notice a navigation after the fact — re-injection and the settle poll — have
@@ -876,6 +998,11 @@ var SessionCapturer = class extends SessionCapturerBase {
876
998
  runner = nightwatchRunnerId(false);
877
999
  actionSnapshots = [];
878
1000
  snapshotCaptures = [];
1001
+ /** Native app network from appium-interceptor's proxy; started per session. */
1002
+ nativeNetwork = new NativeNetworkCapture(
1003
+ this,
1004
+ (level, message) => log4[level](message)
1005
+ );
879
1006
  constructor(devtoolsOptions = {}, browser) {
880
1007
  super(devtoolsOptions);
881
1008
  this.#browser = browser;
@@ -1021,7 +1148,7 @@ var SessionCapturer = class extends SessionCapturerBase {
1021
1148
  /** The current document's own birth time, which is what a DOM anchor is
1022
1149
  * stamped with. Undefined when the session cannot answer. */
1023
1150
  async documentOrigin(browser) {
1024
- return webdriverExecute(browser, "return performance.timeOrigin;").then((origin) => origin ?? void 0).catch(() => void 0);
1151
+ return webdriverExecute2(browser, "return performance.timeOrigin;").then((origin) => origin ?? void 0).catch(() => void 0);
1025
1152
  }
1026
1153
  /**
1027
1154
  * Wait for a navigation to actually replace the document, then drain with a
@@ -1104,7 +1231,7 @@ var SessionCapturer = class extends SessionCapturerBase {
1104
1231
  return;
1105
1232
  }
1106
1233
  try {
1107
- const alreadyPresent = await webdriverExecute(
1234
+ const alreadyPresent = await webdriverExecute2(
1108
1235
  browser,
1109
1236
  `return ${COLLECTOR_READY_EXPRESSION};`
1110
1237
  );
@@ -1116,14 +1243,15 @@ var SessionCapturer = class extends SessionCapturerBase {
1116
1243
  const script = document.createElement('script');
1117
1244
  script.textContent = ${JSON.stringify(scriptContent)};
1118
1245
  document.head.appendChild(script);
1119
- return true;
1246
+ return ${COLLECTOR_READY_EXPRESSION};
1120
1247
  `;
1121
- await webdriverExecute(browser, injectionScript);
1122
- const hasCollector = await pollUntilReady(
1123
- async () => await webdriverExecute(
1248
+ const injected = await webdriverExecute2(browser, injectionScript);
1249
+ const hasCollector = injected === true || await pollUntilReady(
1250
+ async () => await webdriverExecute2(
1124
1251
  browser,
1125
1252
  `return ${COLLECTOR_READY_EXPRESSION};`
1126
- ) === true
1253
+ ) === true,
1254
+ { checkFirst: true }
1127
1255
  );
1128
1256
  if (hasCollector) {
1129
1257
  log4.info("\u2713 Script injected and collector ready");
@@ -1167,20 +1295,8 @@ var SessionCapturer = class extends SessionCapturerBase {
1167
1295
  return;
1168
1296
  }
1169
1297
  const networkEntries = parseNetworkFromPerfLogs(logs);
1170
- if (networkEntries.length === 0) {
1171
- return;
1172
- }
1173
- const deduped = dedupeNetworkRequests(
1174
- networkEntries,
1175
- this.networkRequests
1176
- );
1177
- if (deduped.length > 0) {
1178
- const normalized = deduped.map((d) => ({
1179
- ...d,
1180
- type: d.type ?? "other"
1181
- }));
1182
- this.networkRequests.push(...normalized);
1183
- this.sendUpstream("networkRequests", normalized);
1298
+ if (networkEntries.length > 0) {
1299
+ this.#ingestPerfLogEntries(networkEntries);
1184
1300
  }
1185
1301
  } catch (err) {
1186
1302
  const msg = errorMessage(err) ?? "";
@@ -1189,6 +1305,30 @@ var SessionCapturer = class extends SessionCapturerBase {
1189
1305
  }
1190
1306
  }
1191
1307
  }
1308
+ /** A log entry for a load the page already reported replaces that row in
1309
+ * place, under the row's own id; the rest are appended once. */
1310
+ #ingestPerfLogEntries(entries) {
1311
+ const { upgrades, fresh } = upgradeResourceTimingRows(
1312
+ entries,
1313
+ this.networkRequests
1314
+ );
1315
+ for (const { index, request } of upgrades) {
1316
+ this.networkRequests[index] = request;
1317
+ }
1318
+ const deduped = dedupeNetworkRequests(
1319
+ fresh,
1320
+ this.networkRequests
1321
+ );
1322
+ const appended = deduped.map((d) => ({ ...d, type: d.type ?? "other" }));
1323
+ this.networkRequests.push(...appended);
1324
+ this.loadsListedByLog.push(
1325
+ ...appended.map(({ url, timestamp }) => ({ url, timestamp }))
1326
+ );
1327
+ const sent = [...upgrades.map((upgrade) => upgrade.request), ...appended];
1328
+ if (sent.length > 0) {
1329
+ this.sendUpstream("networkRequests", sent);
1330
+ }
1331
+ }
1192
1332
  /** `forceAnchor` forces a full-DOM anchor of the current document before
1193
1333
  * draining, so a freshly injected collector's async anchor isn't lost.
1194
1334
  * `anchorTimestamp` is the command this drain's anchors were CAUSED by — pass
@@ -1196,28 +1336,45 @@ var SessionCapturer = class extends SessionCapturerBase {
1196
1336
  * that command, since it overrides the inference at ingest. */
1197
1337
  async captureTrace(browser, forceAnchor = false, anchorTimestamp) {
1198
1338
  if (this.isNativeAppSession) {
1339
+ await this.nativeNetwork.forward({ throttle: true });
1199
1340
  return;
1200
1341
  }
1201
1342
  await this.drainCollector(browser, forceAnchor, anchorTimestamp);
1202
1343
  await this.captureNetworkFromPerformanceLogs(browser);
1203
1344
  }
1345
+ /** At a test or scenario end, before its slice is written: the proxy drain there is never throttled. */
1346
+ async captureTraceAtTestEnd(browser) {
1347
+ if (this.isNativeAppSession) {
1348
+ await this.nativeNetwork.forward();
1349
+ return;
1350
+ }
1351
+ await this.captureTrace(browser, true);
1352
+ }
1204
1353
  async drainCollector(browser, forceAnchor, anchorTimestamp) {
1205
1354
  try {
1206
1355
  const traceData = await drainCollectorWithRecovery({
1207
- drain: () => webdriverExecute(
1356
+ drain: () => webdriverExecute2(
1208
1357
  browser,
1209
1358
  collectorDrainExpression(forceAnchor)
1210
1359
  ),
1211
1360
  injectIntoCurrentDocument: () => this.injectScript(browser),
1212
- currentUrl: () => webdriverExecute(browser, "return location.href;").then(
1361
+ currentUrl: () => webdriverExecute2(browser, "return location.href;").then(
1213
1362
  (u) => u ?? void 0
1214
1363
  ),
1215
1364
  log: (level, message) => log4[level](message)
1216
1365
  });
1366
+ const listedByLog = this.loadsListedByLog;
1367
+ this.loadsListedByLog = [];
1217
1368
  if (!traceData) {
1218
1369
  return;
1219
1370
  }
1220
- this.processTracePayload(traceData, { anchorTimestamp });
1371
+ this.processTracePayload(
1372
+ withoutLoadsTheLogListed(traceData, listedByLog),
1373
+ {
1374
+ anchorTimestamp,
1375
+ skipResourceTimingRequests: this.bidiNetworkObserved
1376
+ }
1377
+ );
1221
1378
  const mutationCount = Array.isArray(
1222
1379
  traceData.mutations
1223
1380
  ) ? traceData.mutations.length : 0;
@@ -1631,17 +1788,23 @@ function callbackError(result, depth = 0) {
1631
1788
  return void 0;
1632
1789
  }
1633
1790
  const r = result;
1634
- if ("value" in r) {
1635
- return depth < 2 ? callbackError(r.value, depth + 1) : void 0;
1636
- }
1637
- if ("passed" in r || !r.error) {
1791
+ if ("passed" in r) {
1638
1792
  return void 0;
1639
1793
  }
1640
- const err = new Error(String(r.message ?? r.error));
1641
- if (typeof r.stack === "string") {
1642
- err.stack = r.stack;
1794
+ if (r.error instanceof Error) {
1795
+ return r.error;
1796
+ }
1797
+ if (r.error) {
1798
+ const err = new Error(String(r.message ?? r.error));
1799
+ if (typeof r.stack === "string") {
1800
+ err.stack = r.stack;
1801
+ }
1802
+ return err;
1803
+ }
1804
+ if ("value" in r) {
1805
+ return depth < 2 ? callbackError(r.value, depth + 1) : void 0;
1643
1806
  }
1644
- return err;
1807
+ return void 0;
1645
1808
  }
1646
1809
 
1647
1810
  // src/helpers/assertTarget.ts
@@ -2315,6 +2478,9 @@ var BrowserProxy = class {
2315
2478
  this.attachScreenshot(browser, lastCommand, methodName);
2316
2479
  }
2317
2480
  this.anchorDestination(browser, methodName);
2481
+ if (!this.isDomMutating(methodName)) {
2482
+ void this.sessionCapturer.nativeNetwork.forward({ throttle: true });
2483
+ }
2318
2484
  }
2319
2485
  isDomMutating(methodName) {
2320
2486
  return NAVIGATION_COMMANDS.includes(methodName) || [
@@ -2643,7 +2809,7 @@ function initReporterChain(ctx) {
2643
2809
  }
2644
2810
  function readViewport(browser) {
2645
2811
  return resolveViewport(browser.capabilities || {}, {
2646
- runScript: (body) => webdriverExecute(browser, body),
2812
+ runScript: (body) => webdriverExecute2(browser, body),
2647
2813
  getWindowSize: () => webdriverGet2(browser, "window/rect"),
2648
2814
  onWarn: (message) => log10.warn(message)
2649
2815
  });
@@ -2681,6 +2847,18 @@ async function broadcastSessionMetadata(ctx, browser) {
2681
2847
  log10.info(
2682
2848
  `\u2713 Browser: ${browserName}${browserVersion ? " " + browserVersion : ""} (session: ${sessionId2})`
2683
2849
  );
2850
+ if (ctx.mode === "live" && restoreBrowserFocus({
2851
+ browserName: String(browserName),
2852
+ platformName: String(
2853
+ capabilities.platformName || desiredCapabilities.platformName || ""
2854
+ ),
2855
+ host: resolveWebDriverAddress(browser).driverHost,
2856
+ onError: (message) => log10.warn(message)
2857
+ })) {
2858
+ log10.info(
2859
+ "\u2713 Safari brought back to the front (it delivers clicks only there)"
2860
+ );
2861
+ }
2684
2862
  const wantsPerfLog = wantsPerformanceLog(
2685
2863
  capabilities,
2686
2864
  desiredCapabilities
@@ -2688,7 +2866,7 @@ async function broadcastSessionMetadata(ctx, browser) {
2688
2866
  ctx.sessionCapturer.perfLogsRequested = wantsPerfLog;
2689
2867
  if (!wantsPerfLog && !ctx.bidiEnabled) {
2690
2868
  log10.warn(
2691
- "\u26A0 Network tab will be empty \u2014 add 'goog:loggingPrefs': { performance: 'ALL' } to your capabilities (or enable bidi:true)"
2869
+ "\u26A0 Network tab will carry the page's own Resource Timing only (no headers, no bodies). Add 'goog:loggingPrefs': { performance: 'ALL' } to your capabilities (or enable bidi:true) for full requests"
2692
2870
  );
2693
2871
  }
2694
2872
  }
@@ -2701,7 +2879,7 @@ async function tryAttachBidi(ctx, browser) {
2701
2879
  log10.warn("bidi:true set but browser.driver unavailable \u2014 skipping");
2702
2880
  return;
2703
2881
  }
2704
- const { attachBidiHandlers, buildBidiSinks } = await import("./bidi-WNZOXX5X.js");
2882
+ const { attachBidiHandlers, buildBidiSinks } = await import("./bidi-AZX3WRGJ.js");
2705
2883
  const ok = await attachBidiHandlers(
2706
2884
  driver,
2707
2885
  buildBidiSinks(ctx.sessionCapturer)
@@ -2725,6 +2903,12 @@ async function armCaptureForSession(ctx, browser) {
2725
2903
  ctx.armedSessionId = browser.sessionId ?? void 0;
2726
2904
  await tryRegisterPreload(ctx, browser);
2727
2905
  await tryAttachBidi(ctx, browser);
2906
+ await ctx.sessionCapturer.nativeNetwork.start({
2907
+ requested: requestsInterceptedNetwork(browser.desiredCapabilities),
2908
+ capabilities: browser.capabilities,
2909
+ execute: sessionExecutor(browser),
2910
+ unavailableReason: "the session id or the configured WebDriver port is unknown"
2911
+ });
2728
2912
  }
2729
2913
  function needsCaptureRearm(ctx, sessionId2) {
2730
2914
  return Boolean(ctx.sessionCapturer) && sessionId2 !== void 0 && ctx.armedSessionId !== void 0 && ctx.armedSessionId !== sessionId2;
@@ -2736,6 +2920,9 @@ async function armReplacedSession(ctx, browser) {
2736
2920
  }
2737
2921
  ctx.sessionCapturer.preloadRegistered = false;
2738
2922
  ctx.sessionCapturer.bidiActive = false;
2923
+ ctx.sessionCapturer.bidiNetworkObserved = false;
2924
+ ctx.sessionCapturer.loadsListedByLog = [];
2925
+ ctx.sessionCapturer.nativeNetwork.stop();
2739
2926
  log10.info(`Session replaced \u2014 re-arming capture for ${sessionId2}`);
2740
2927
  await armCaptureForSession(ctx, browser);
2741
2928
  }
@@ -3222,7 +3409,7 @@ async function captureCucumberScenarioBeforeQuit(ctx, browser, result) {
3222
3409
  assertCalls
3223
3410
  );
3224
3411
  }
3225
- await ctx.sessionCapturer.captureTrace(browser, true);
3412
+ await ctx.sessionCapturer.captureTraceAtTestEnd(browser);
3226
3413
  flushTestSlice(ctx);
3227
3414
  await ctx.emitTestArtifacts(
3228
3415
  scenario?.uid,
@@ -3943,7 +4130,7 @@ var NightwatchDevToolsPlugin = class {
3943
4130
  this.browserProxy.drainNativeAssertCalls()
3944
4131
  );
3945
4132
  }
3946
- await this.sessionCapturer.captureTrace(browser, true);
4133
+ await this.sessionCapturer.captureTraceAtTestEnd(browser);
3947
4134
  flushTestSlice(this.#getInternals());
3948
4135
  const results = browser.currentTest?.results;
3949
4136
  const failed = !!results && ((results.errors ?? 0) > 0 || (results.failed ?? 0) > 0);
@@ -3989,6 +4176,7 @@ var NightwatchDevToolsPlugin = class {
3989
4176
  });
3990
4177
  }
3991
4178
  async after(browser) {
4179
+ this.sessionCapturer?.nativeNetwork.stop();
3992
4180
  await this.#screencast.rotation;
3993
4181
  await this.#finalizeCurrentScreencast();
3994
4182
  try {