@wdio/nightwatch-devtools 1.7.0 → 1.7.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
@@ -6826,6 +6826,14 @@ var SessionCapturer = class extends SessionCapturerBase {
6826
6826
  this.commandsLog.push(entry);
6827
6827
  return { entry, oldTimestamp };
6828
6828
  }
6829
+ /** Birth time of the document this capturer last observed, so a replacement
6830
+ * is still visible to a poll that starts after the navigation committed. */
6831
+ lastDocumentOrigin;
6832
+ /** The current document's own birth time, which is what a DOM anchor is
6833
+ * stamped with. Undefined when the session cannot answer. */
6834
+ async documentOrigin(browser) {
6835
+ return webdriverExecute(browser, "return performance.timeOrigin;").then((origin) => origin ?? void 0).catch(() => void 0);
6836
+ }
6829
6837
  /**
6830
6838
  * Wait for a navigation to actually replace the document, then drain with a
6831
6839
  * forced anchor so the destination's DOM enters the stream.
@@ -6833,23 +6841,39 @@ var SessionCapturer = class extends SessionCapturerBase {
6833
6841
  * A Nightwatch click resolves BEFORE its navigation commits, so the drain that
6834
6842
  * runs at command completion still finds the outgoing page's collector: it
6835
6843
  * recovers nothing, and the destination is never anchored — every action on the
6836
- * new page then replays the page it came from. Waiting for the collector to
6837
- * disappear is the signal that the document was replaced; a command that
6838
- * navigated nowhere polls out and costs one drain of an empty buffer.
6844
+ * new page then replays the page it came from.
6845
+ *
6846
+ * The replacement signal is the document's own `performance.timeOrigin`
6847
+ * changing, NOT the collector going missing. Under the preload every document
6848
+ * instruments itself at document-start, so the collector is never absent and
6849
+ * that older signal stood permanently false — the destination was then only
6850
+ * ever anchored by a drain that happened to be slow enough to land after the
6851
+ * navigation, and tightening that latency lost the destination page entirely.
6852
+ * A same-URL re-navigation is a new document and a new origin, so this covers
6853
+ * it too; a command that navigated nowhere polls out and costs one drain of an
6854
+ * empty buffer.
6855
+ *
6856
+ * The baseline is the last origin THIS capturer saw, never one read on entry.
6857
+ * Reading it here would compare the document against itself: a command that
6858
+ * completes AFTER its navigation committed (`browser.url` waits for load, so
6859
+ * that is the normal case) would baseline the destination and then report no
6860
+ * change, dropping the very anchor this exists to take. With no baseline yet
6861
+ * a navigation cannot be ruled out either, so the first DOM-mutating command
6862
+ * of a run anchors unconditionally rather than guessing — `captureCurrentDom`
6863
+ * is idempotent per document, so an already-anchored page costs one drain.
6839
6864
  */
6840
6865
  async anchorAfterNavigation(browser) {
6866
+ const before = this.lastDocumentOrigin;
6841
6867
  const replaced = await pollUntilReady(
6842
- async () => await webdriverExecute(
6843
- browser,
6844
- `return ${COLLECTOR_READY_EXPRESSION};`
6845
- ) !== true,
6846
- // With the preload active every document instruments itself at
6847
- // document-start, so there is normally nothing to catch — but ONE probe
6848
- // still runs rather than standing down entirely, because a document created
6849
- // before the registration landed has no collector and nothing else would
6850
- // ever notice. Without the preload this is the only detector, so it keeps
6851
- // the full settle poll.
6852
- this.preloadRegistered ? { attempts: 1, intervalMs: 0 } : { attempts: 5, intervalMs: 150 }
6868
+ async () => {
6869
+ const now = await this.documentOrigin(browser);
6870
+ if (now === void 0) {
6871
+ return false;
6872
+ }
6873
+ this.lastDocumentOrigin = now;
6874
+ return before === void 0 || now !== before;
6875
+ },
6876
+ { attempts: 5, intervalMs: 150 }
6853
6877
  );
6854
6878
  if (!replaced) {
6855
6879
  return;
@@ -6970,16 +6994,16 @@ var SessionCapturer = class extends SessionCapturerBase {
6970
6994
  }
6971
6995
  }
6972
6996
  }
6973
- /**
6974
- * Capture trace data from the browser (network requests, console logs, etc.)
6975
- */
6976
6997
  /** `forceAnchor` forces a full-DOM anchor of the current document before
6977
6998
  * draining, so a freshly injected collector's async anchor isn't lost.
6978
6999
  * `anchorTimestamp` is the command this drain's anchors were CAUSED by — pass
6979
7000
  * it only when the caller observed the document being replaced right after
6980
7001
  * that command, since it overrides the inference at ingest. */
6981
7002
  async captureTrace(browser, forceAnchor = false, anchorTimestamp) {
7003
+ await this.drainCollector(browser, forceAnchor, anchorTimestamp);
6982
7004
  await this.captureNetworkFromPerformanceLogs(browser);
7005
+ }
7006
+ async drainCollector(browser, forceAnchor, anchorTimestamp) {
6983
7007
  try {
6984
7008
  const traceData = await drainCollectorWithRecovery({
6985
7009
  drain: () => webdriverExecute(
@@ -8081,6 +8105,7 @@ var BrowserProxy = class {
8081
8105
  captureFreshCommand(browser, methodName, logArgs, serializedResult, meta, cmdSig, commandError) {
8082
8106
  this.retryTracker.setLastSig(cmdSig);
8083
8107
  this.retryTracker.setLastId(null);
8108
+ this.drainOutgoingPage(browser, methodName);
8084
8109
  this.sessionCapturer.captureCommand(methodName, logArgs, serializedResult, commandError, meta).catch(
8085
8110
  (err) => log7.error(`Failed to capture ${methodName}: ${err.message}`)
8086
8111
  );
@@ -8091,32 +8116,10 @@ var BrowserProxy = class {
8091
8116
  log7.info(`[command] ${methodName}`);
8092
8117
  this.attachScreenshot(browser, lastCommand, methodName);
8093
8118
  }
8094
- this.maybeRepollMutations(browser, methodName);
8119
+ this.anchorDestination(browser, methodName);
8095
8120
  }
8096
- /**
8097
- * After a DOM-mutating command, drain the collector — TWICE, for two different
8098
- * reasons that no single drain can serve.
8099
- *
8100
- * The first drain fires synchronously, with no setTimeout in front. It used to
8101
- * defer 200ms to stay off Nightwatch's callback stack, safe only because the
8102
- * drain went through `browser.execute`, a QUEUED command; over the raw HTTP
8103
- * transport it touches no queue, and because a driver serialises requests per
8104
- * session, a drain issued here is served BEFORE the next queued command. That
8105
- * is what captures the OUTGOING page's field edits — deferred, a submit click
8106
- * navigated first and they died with the page, so the `#password` fill row
8107
- * replayed with an empty password box.
8108
- *
8109
- * The second drain is what anchors the DESTINATION, and it has to wait: a
8110
- * Nightwatch click resolves before its navigation commits, so the first drain
8111
- * still finds the outgoing page's collector, recovers nothing, and the new
8112
- * document is never anchored — every action on it then replayed the page it
8113
- * came from (measured: 5 of 20 rows). `anchorAfterNavigation` keys off the
8114
- * document actually being replaced rather than off which command ran, so it
8115
- * covers any route to a new document — a submit click, a JS redirect, a
8116
- * same-command re-navigation — without a list of commands to keep in sync.
8117
- */
8118
- maybeRepollMutations(browser, methodName) {
8119
- const isDomMutating = NAVIGATION_COMMANDS.includes(methodName) || [
8121
+ isDomMutating(methodName) {
8122
+ return NAVIGATION_COMMANDS.includes(methodName) || [
8120
8123
  "click",
8121
8124
  "doubleClick",
8122
8125
  "rightClick",
@@ -8128,11 +8131,43 @@ var BrowserProxy = class {
8128
8131
  "forward",
8129
8132
  "refresh"
8130
8133
  ].includes(methodName);
8131
- if (!isDomMutating) {
8134
+ }
8135
+ /**
8136
+ * Drains the OUTGOING page's field edits, which die with the document the
8137
+ * moment a submit navigates. It goes over the raw HTTP transport, so it
8138
+ * touches no Nightwatch queue, and a driver serialises requests per session —
8139
+ * so it is served before the next queued command only for as long as it stays
8140
+ * the FIRST request this class issues when a command completes. That call
8141
+ * position, and the drain-before-perf-logs order inside `captureTrace`, are
8142
+ * both load-bearing: behind the snapshot probes and the screenshot it needed
8143
+ * about 130 ms of head start and got 18 ms, and the last field a test filled
8144
+ * before submitting replayed as an empty box.
8145
+ *
8146
+ * It deliberately carries only this one request. `anchorAfterNavigation` is
8147
+ * issued later, from `anchorDestination`, so the row's own screenshot and DOM
8148
+ * snapshot are not pushed behind two round trips.
8149
+ */
8150
+ drainOutgoingPage(browser, methodName) {
8151
+ if (!this.isDomMutating(methodName)) {
8132
8152
  return;
8133
8153
  }
8134
8154
  this.sessionCapturer.captureTrace(browser, true).catch(() => {
8135
8155
  });
8156
+ }
8157
+ /**
8158
+ * Anchors the DESTINATION, and it has to wait: a Nightwatch click resolves
8159
+ * before its navigation commits, so the outgoing drain still finds the old
8160
+ * page's collector, recovers nothing, and the new document is never anchored —
8161
+ * every action on it then replayed the page it came from (measured: 5 of 20
8162
+ * rows). `anchorAfterNavigation` keys off the document actually being replaced
8163
+ * rather than off which command ran, so it covers any route to a new document —
8164
+ * a submit click, a JS redirect, a same-command re-navigation — without a list
8165
+ * of commands to keep in sync.
8166
+ */
8167
+ anchorDestination(browser, methodName) {
8168
+ if (!this.isDomMutating(methodName)) {
8169
+ return;
8170
+ }
8136
8171
  this.sessionCapturer.snapshotCaptures.push(
8137
8172
  this.sessionCapturer.anchorAfterNavigation(browser).catch(() => {
8138
8173
  })