@wdio/nightwatch-devtools 1.8.2 → 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";
@@ -654,7 +659,7 @@ var firstDefined = (obj, ...keys) => {
654
659
  }
655
660
  return void 0;
656
661
  };
657
- function resolveWebDriverAddress(browser) {
662
+ function configuredWebDriver(browser) {
658
663
  const transportSettings = getPath(browser, "transport", "settings", "webdriver") || getPath(browser, "queue", "transport", "settings", "webdriver") || getPath(
659
664
  browser,
660
665
  "nightwatchInstance",
@@ -664,13 +669,24 @@ function resolveWebDriverAddress(browser) {
664
669
  ) || {};
665
670
  const opts = getProp(browser, "options") ?? {};
666
671
  const nightwatchSettings = getPath(browser, "nightwatchInstance", "settings") || getPath(browser, "globals", "nightwatchInstance", "settings") || {};
667
- const driverHost = String(
668
- firstDefined(transportSettings, "host", "server_address") || firstDefined(getProp(opts, "webdriver"), "host") || firstDefined(getProp(nightwatchSettings, "webdriver"), "host") || "localhost"
669
- );
670
- const driverPort = Number(
671
- firstDefined(transportSettings, "port") || firstDefined(getProp(opts, "webdriver"), "port") || firstDefined(getProp(nightwatchSettings, "webdriver"), "port") || 9515
672
- );
673
- 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
+ };
674
690
  }
675
691
  function address(browser) {
676
692
  const { driverHost, driverPort } = resolveWebDriverAddress(browser);
@@ -691,7 +707,21 @@ function webdriverPost2(browser, path5, body) {
691
707
  const id = sessionId(browser);
692
708
  return id ? webdriverPost(address(browser), id, path5, body) : Promise.resolve(null);
693
709
  }
694
- 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) {
695
725
  return webdriverPost2(browser, "execute/sync", { script: body, args: [] });
696
726
  }
697
727
 
@@ -706,7 +736,7 @@ function captureActionSnapshot2(browser, command, timestamp, runner, native = fa
706
736
  runner,
707
737
  takeScreenshot: () => webdriverGet2(browser, "screenshot"),
708
738
  ...native ? {} : {
709
- runScript: (src) => webdriverExecute(browser, `return (${src})`),
739
+ runScript: (src) => webdriverExecute2(browser, `return (${src})`),
710
740
  getUrl: () => webdriverGet2(browser, "url"),
711
741
  getTitle: () => webdriverGet2(browser, "title")
712
742
  }
@@ -714,6 +744,7 @@ function captureActionSnapshot2(browser, command, timestamp, runner, native = fa
714
744
  }
715
745
 
716
746
  // src/helpers/perfLogs.ts
747
+ var RESOURCE_TIMING_MATCH_MS = 2e3;
717
748
  function applyResponseReceived(p, response) {
718
749
  const responseHeaders = {};
719
750
  for (const [k, v] of Object.entries(response.headers || {})) {
@@ -739,7 +770,9 @@ function startPendingRequest(params, entry, pending) {
739
770
  url: req.url,
740
771
  method: req.method,
741
772
  requestHeaders: req.headers ?? {},
742
- 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,
743
776
  startTime
744
777
  });
745
778
  }
@@ -797,6 +830,95 @@ function parseNetworkFromPerfLogs(logs) {
797
830
  }
798
831
  return completed;
799
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
+ }
800
922
  function dedupeNetworkRequests(incoming, existing) {
801
923
  const failedKey = (entry) => {
802
924
  try {
@@ -849,6 +971,9 @@ var SessionCapturer = class extends SessionCapturerBase {
849
971
  // True once BiDi inspectors are attached — the per-command perf-log network
850
972
  // capture path skips when set, so we don't double-emit network requests.
851
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;
852
977
  /** Whether the session was created with `goog:loggingPrefs.performance`, set
853
978
  * from the capabilities at session init. A perf log exists only because a
854
979
  * session asked for one, so without it every fetch is a round trip that can
@@ -857,6 +982,10 @@ var SessionCapturer = class extends SessionCapturerBase {
857
982
  * 21 of them in a two-test run, where XCUITest lists `performance` among its
858
983
  * supported types and then serves none of it. */
859
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 = [];
860
989
  /** True once the collector is registered to run at document-start. Every
861
990
  * document then instruments and anchors itself, so the paths that exist to
862
991
  * notice a navigation after the fact — re-injection and the settle poll — have
@@ -869,6 +998,11 @@ var SessionCapturer = class extends SessionCapturerBase {
869
998
  runner = nightwatchRunnerId(false);
870
999
  actionSnapshots = [];
871
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
+ );
872
1006
  constructor(devtoolsOptions = {}, browser) {
873
1007
  super(devtoolsOptions);
874
1008
  this.#browser = browser;
@@ -1014,7 +1148,7 @@ var SessionCapturer = class extends SessionCapturerBase {
1014
1148
  /** The current document's own birth time, which is what a DOM anchor is
1015
1149
  * stamped with. Undefined when the session cannot answer. */
1016
1150
  async documentOrigin(browser) {
1017
- 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);
1018
1152
  }
1019
1153
  /**
1020
1154
  * Wait for a navigation to actually replace the document, then drain with a
@@ -1097,7 +1231,7 @@ var SessionCapturer = class extends SessionCapturerBase {
1097
1231
  return;
1098
1232
  }
1099
1233
  try {
1100
- const alreadyPresent = await webdriverExecute(
1234
+ const alreadyPresent = await webdriverExecute2(
1101
1235
  browser,
1102
1236
  `return ${COLLECTOR_READY_EXPRESSION};`
1103
1237
  );
@@ -1109,14 +1243,15 @@ var SessionCapturer = class extends SessionCapturerBase {
1109
1243
  const script = document.createElement('script');
1110
1244
  script.textContent = ${JSON.stringify(scriptContent)};
1111
1245
  document.head.appendChild(script);
1112
- return true;
1246
+ return ${COLLECTOR_READY_EXPRESSION};
1113
1247
  `;
1114
- await webdriverExecute(browser, injectionScript);
1115
- const hasCollector = await pollUntilReady(
1116
- async () => await webdriverExecute(
1248
+ const injected = await webdriverExecute2(browser, injectionScript);
1249
+ const hasCollector = injected === true || await pollUntilReady(
1250
+ async () => await webdriverExecute2(
1117
1251
  browser,
1118
1252
  `return ${COLLECTOR_READY_EXPRESSION};`
1119
- ) === true
1253
+ ) === true,
1254
+ { checkFirst: true }
1120
1255
  );
1121
1256
  if (hasCollector) {
1122
1257
  log4.info("\u2713 Script injected and collector ready");
@@ -1160,20 +1295,8 @@ var SessionCapturer = class extends SessionCapturerBase {
1160
1295
  return;
1161
1296
  }
1162
1297
  const networkEntries = parseNetworkFromPerfLogs(logs);
1163
- if (networkEntries.length === 0) {
1164
- return;
1165
- }
1166
- const deduped = dedupeNetworkRequests(
1167
- networkEntries,
1168
- this.networkRequests
1169
- );
1170
- if (deduped.length > 0) {
1171
- const normalized = deduped.map((d) => ({
1172
- ...d,
1173
- type: d.type ?? "other"
1174
- }));
1175
- this.networkRequests.push(...normalized);
1176
- this.sendUpstream("networkRequests", normalized);
1298
+ if (networkEntries.length > 0) {
1299
+ this.#ingestPerfLogEntries(networkEntries);
1177
1300
  }
1178
1301
  } catch (err) {
1179
1302
  const msg = errorMessage(err) ?? "";
@@ -1182,6 +1305,30 @@ var SessionCapturer = class extends SessionCapturerBase {
1182
1305
  }
1183
1306
  }
1184
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
+ }
1185
1332
  /** `forceAnchor` forces a full-DOM anchor of the current document before
1186
1333
  * draining, so a freshly injected collector's async anchor isn't lost.
1187
1334
  * `anchorTimestamp` is the command this drain's anchors were CAUSED by — pass
@@ -1189,28 +1336,45 @@ var SessionCapturer = class extends SessionCapturerBase {
1189
1336
  * that command, since it overrides the inference at ingest. */
1190
1337
  async captureTrace(browser, forceAnchor = false, anchorTimestamp) {
1191
1338
  if (this.isNativeAppSession) {
1339
+ await this.nativeNetwork.forward({ throttle: true });
1192
1340
  return;
1193
1341
  }
1194
1342
  await this.drainCollector(browser, forceAnchor, anchorTimestamp);
1195
1343
  await this.captureNetworkFromPerformanceLogs(browser);
1196
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
+ }
1197
1353
  async drainCollector(browser, forceAnchor, anchorTimestamp) {
1198
1354
  try {
1199
1355
  const traceData = await drainCollectorWithRecovery({
1200
- drain: () => webdriverExecute(
1356
+ drain: () => webdriverExecute2(
1201
1357
  browser,
1202
1358
  collectorDrainExpression(forceAnchor)
1203
1359
  ),
1204
1360
  injectIntoCurrentDocument: () => this.injectScript(browser),
1205
- currentUrl: () => webdriverExecute(browser, "return location.href;").then(
1361
+ currentUrl: () => webdriverExecute2(browser, "return location.href;").then(
1206
1362
  (u) => u ?? void 0
1207
1363
  ),
1208
1364
  log: (level, message) => log4[level](message)
1209
1365
  });
1366
+ const listedByLog = this.loadsListedByLog;
1367
+ this.loadsListedByLog = [];
1210
1368
  if (!traceData) {
1211
1369
  return;
1212
1370
  }
1213
- this.processTracePayload(traceData, { anchorTimestamp });
1371
+ this.processTracePayload(
1372
+ withoutLoadsTheLogListed(traceData, listedByLog),
1373
+ {
1374
+ anchorTimestamp,
1375
+ skipResourceTimingRequests: this.bidiNetworkObserved
1376
+ }
1377
+ );
1214
1378
  const mutationCount = Array.isArray(
1215
1379
  traceData.mutations
1216
1380
  ) ? traceData.mutations.length : 0;
@@ -1624,17 +1788,23 @@ function callbackError(result, depth = 0) {
1624
1788
  return void 0;
1625
1789
  }
1626
1790
  const r = result;
1627
- if ("value" in r) {
1628
- return depth < 2 ? callbackError(r.value, depth + 1) : void 0;
1629
- }
1630
- if ("passed" in r || !r.error) {
1791
+ if ("passed" in r) {
1631
1792
  return void 0;
1632
1793
  }
1633
- const err = new Error(String(r.message ?? r.error));
1634
- if (typeof r.stack === "string") {
1635
- 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;
1636
1806
  }
1637
- return err;
1807
+ return void 0;
1638
1808
  }
1639
1809
 
1640
1810
  // src/helpers/assertTarget.ts
@@ -2308,6 +2478,9 @@ var BrowserProxy = class {
2308
2478
  this.attachScreenshot(browser, lastCommand, methodName);
2309
2479
  }
2310
2480
  this.anchorDestination(browser, methodName);
2481
+ if (!this.isDomMutating(methodName)) {
2482
+ void this.sessionCapturer.nativeNetwork.forward({ throttle: true });
2483
+ }
2311
2484
  }
2312
2485
  isDomMutating(methodName) {
2313
2486
  return NAVIGATION_COMMANDS.includes(methodName) || [
@@ -2636,7 +2809,7 @@ function initReporterChain(ctx) {
2636
2809
  }
2637
2810
  function readViewport(browser) {
2638
2811
  return resolveViewport(browser.capabilities || {}, {
2639
- runScript: (body) => webdriverExecute(browser, body),
2812
+ runScript: (body) => webdriverExecute2(browser, body),
2640
2813
  getWindowSize: () => webdriverGet2(browser, "window/rect"),
2641
2814
  onWarn: (message) => log10.warn(message)
2642
2815
  });
@@ -2674,6 +2847,18 @@ async function broadcastSessionMetadata(ctx, browser) {
2674
2847
  log10.info(
2675
2848
  `\u2713 Browser: ${browserName}${browserVersion ? " " + browserVersion : ""} (session: ${sessionId2})`
2676
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
+ }
2677
2862
  const wantsPerfLog = wantsPerformanceLog(
2678
2863
  capabilities,
2679
2864
  desiredCapabilities
@@ -2681,7 +2866,7 @@ async function broadcastSessionMetadata(ctx, browser) {
2681
2866
  ctx.sessionCapturer.perfLogsRequested = wantsPerfLog;
2682
2867
  if (!wantsPerfLog && !ctx.bidiEnabled) {
2683
2868
  log10.warn(
2684
- "\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"
2685
2870
  );
2686
2871
  }
2687
2872
  }
@@ -2694,7 +2879,7 @@ async function tryAttachBidi(ctx, browser) {
2694
2879
  log10.warn("bidi:true set but browser.driver unavailable \u2014 skipping");
2695
2880
  return;
2696
2881
  }
2697
- const { attachBidiHandlers, buildBidiSinks } = await import("./bidi-WNZOXX5X.js");
2882
+ const { attachBidiHandlers, buildBidiSinks } = await import("./bidi-AZX3WRGJ.js");
2698
2883
  const ok = await attachBidiHandlers(
2699
2884
  driver,
2700
2885
  buildBidiSinks(ctx.sessionCapturer)
@@ -2718,6 +2903,12 @@ async function armCaptureForSession(ctx, browser) {
2718
2903
  ctx.armedSessionId = browser.sessionId ?? void 0;
2719
2904
  await tryRegisterPreload(ctx, browser);
2720
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
+ });
2721
2912
  }
2722
2913
  function needsCaptureRearm(ctx, sessionId2) {
2723
2914
  return Boolean(ctx.sessionCapturer) && sessionId2 !== void 0 && ctx.armedSessionId !== void 0 && ctx.armedSessionId !== sessionId2;
@@ -2729,6 +2920,9 @@ async function armReplacedSession(ctx, browser) {
2729
2920
  }
2730
2921
  ctx.sessionCapturer.preloadRegistered = false;
2731
2922
  ctx.sessionCapturer.bidiActive = false;
2923
+ ctx.sessionCapturer.bidiNetworkObserved = false;
2924
+ ctx.sessionCapturer.loadsListedByLog = [];
2925
+ ctx.sessionCapturer.nativeNetwork.stop();
2732
2926
  log10.info(`Session replaced \u2014 re-arming capture for ${sessionId2}`);
2733
2927
  await armCaptureForSession(ctx, browser);
2734
2928
  }
@@ -3215,7 +3409,7 @@ async function captureCucumberScenarioBeforeQuit(ctx, browser, result) {
3215
3409
  assertCalls
3216
3410
  );
3217
3411
  }
3218
- await ctx.sessionCapturer.captureTrace(browser, true);
3412
+ await ctx.sessionCapturer.captureTraceAtTestEnd(browser);
3219
3413
  flushTestSlice(ctx);
3220
3414
  await ctx.emitTestArtifacts(
3221
3415
  scenario?.uid,
@@ -3936,7 +4130,7 @@ var NightwatchDevToolsPlugin = class {
3936
4130
  this.browserProxy.drainNativeAssertCalls()
3937
4131
  );
3938
4132
  }
3939
- await this.sessionCapturer.captureTrace(browser, true);
4133
+ await this.sessionCapturer.captureTraceAtTestEnd(browser);
3940
4134
  flushTestSlice(this.#getInternals());
3941
4135
  const results = browser.currentTest?.results;
3942
4136
  const failed = !!results && ((results.errors ?? 0) > 0 || (results.failed ?? 0) > 0);
@@ -3982,6 +4176,7 @@ var NightwatchDevToolsPlugin = class {
3982
4176
  });
3983
4177
  }
3984
4178
  async after(browser) {
4179
+ this.sessionCapturer?.nativeNetwork.stop();
3985
4180
  await this.#screencast.rotation;
3986
4181
  await this.#finalizeCurrentScreencast();
3987
4182
  try {