browser-debugger-cli 0.9.0 → 0.10.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.
Files changed (133) hide show
  1. package/README.md +4 -1
  2. package/dist/commands/dom/a11y.js +2 -1
  3. package/dist/commands/dom/formInteraction.js +56 -25
  4. package/dist/commands/dom/helpers/keyAttributes.d.ts +20 -0
  5. package/dist/commands/dom/helpers/keyAttributes.js +54 -0
  6. package/dist/commands/dom/helpers/query.d.ts +1 -1
  7. package/dist/commands/dom/helpers/query.js +66 -19
  8. package/dist/commands/dom/helpers/runElementCommand.js +4 -3
  9. package/dist/commands/dom/helpers/screenshot.js +85 -12
  10. package/dist/commands/dom/index.d.ts +1 -0
  11. package/dist/commands/dom/index.js +8 -3
  12. package/dist/commands/dom/inspect.d.ts +15 -0
  13. package/dist/commands/dom/inspect.js +82 -0
  14. package/dist/commands/dom/layout.js +2 -2
  15. package/dist/commands/dom/listeners.js +2 -2
  16. package/dist/commands/dom/semanticUtils.d.ts +14 -1
  17. package/dist/commands/dom/semanticUtils.js +44 -3
  18. package/dist/commands/network/list.js +13 -2
  19. package/dist/commands/optionBehaviors.js +48 -6
  20. package/dist/commands/page.d.ts +1 -1
  21. package/dist/commands/page.js +62 -3
  22. package/dist/commands/shared/commonOptions.d.ts +4 -0
  23. package/dist/commands/shared/commonOptions.js +9 -0
  24. package/dist/commands/shared/optionTypes.d.ts +21 -0
  25. package/dist/commands/shared/startHelpers.d.ts +66 -0
  26. package/dist/commands/shared/startHelpers.js +91 -10
  27. package/dist/commands/shared/validation.d.ts +11 -0
  28. package/dist/commands/shared/validation.js +16 -0
  29. package/dist/daemon/launcher.d.ts +8 -1
  30. package/dist/daemon/launcher.js +3 -1
  31. package/dist/daemon/session/Session.d.ts +7 -0
  32. package/dist/daemon/session/Session.js +23 -1
  33. package/dist/daemon/session/commandRegistry.d.ts +14 -1
  34. package/dist/daemon/session/commandRegistry.js +65 -9
  35. package/dist/daemon/session/interactions.d.ts +18 -5
  36. package/dist/daemon/session/interactions.js +22 -12
  37. package/dist/daemon.js +3565 -329
  38. package/dist/errors/messages.d.ts +69 -0
  39. package/dist/errors/messages.js +102 -1
  40. package/dist/index.js +2416 -1320
  41. package/dist/ipc/client.d.ts +9 -0
  42. package/dist/ipc/client.js +13 -0
  43. package/dist/ipc/protocol/commands.d.ts +56 -1
  44. package/dist/ipc/protocol/commands.js +2 -0
  45. package/dist/ipc/protocol/domTypes.d.ts +35 -2
  46. package/dist/ipc/protocol/inspectTypes.d.ts +388 -0
  47. package/dist/ipc/protocol/inspectTypes.js +10 -0
  48. package/dist/runtime/dom/actionEffects.d.ts +94 -15
  49. package/dist/runtime/dom/actionEffects.js +173 -27
  50. package/dist/runtime/dom/actionEffectsScripts.d.ts +52 -14
  51. package/dist/runtime/dom/actionEffectsScripts.js +224 -32
  52. package/dist/runtime/dom/elementInfo.d.ts +26 -0
  53. package/dist/runtime/dom/elementInfo.js +65 -0
  54. package/dist/runtime/dom/eventListeners.js +14 -4
  55. package/dist/runtime/dom/formFillHelpers/fill.d.ts +3 -4
  56. package/dist/runtime/dom/formFillHelpers/fill.js +77 -28
  57. package/dist/runtime/dom/frameSelection.d.ts +11 -0
  58. package/dist/runtime/dom/frameSelection.js +20 -1
  59. package/dist/runtime/dom/frames.d.ts +38 -5
  60. package/dist/runtime/dom/frames.js +136 -21
  61. package/dist/runtime/dom/inspect.d.ts +28 -0
  62. package/dist/runtime/dom/inspect.js +557 -0
  63. package/dist/runtime/dom/inspectAllStyles.d.ts +62 -0
  64. package/dist/runtime/dom/inspectAllStyles.js +385 -0
  65. package/dist/runtime/dom/inspectCascade.d.ts +94 -0
  66. package/dist/runtime/dom/inspectCascade.js +371 -0
  67. package/dist/runtime/dom/inspectCascadeModel.d.ts +39 -0
  68. package/dist/runtime/dom/inspectCascadeModel.js +232 -0
  69. package/dist/runtime/dom/inspectHints.d.ts +62 -0
  70. package/dist/runtime/dom/inspectHints.js +305 -0
  71. package/dist/runtime/dom/inspectLayoutModel.d.ts +87 -0
  72. package/dist/runtime/dom/inspectLayoutModel.js +346 -0
  73. package/dist/runtime/dom/inspectModel.d.ts +74 -0
  74. package/dist/runtime/dom/inspectModel.js +184 -0
  75. package/dist/runtime/dom/inspectPaintModel.d.ts +157 -0
  76. package/dist/runtime/dom/inspectPaintModel.js +461 -0
  77. package/dist/runtime/dom/inspectRules.d.ts +37 -0
  78. package/dist/runtime/dom/inspectRules.js +101 -0
  79. package/dist/runtime/dom/inspectScripts.d.ts +132 -0
  80. package/dist/runtime/dom/inspectScripts.js +263 -0
  81. package/dist/runtime/dom/inspectTree.d.ts +40 -0
  82. package/dist/runtime/dom/inspectTree.js +134 -0
  83. package/dist/runtime/dom/inspectVariables.d.ts +33 -0
  84. package/dist/runtime/dom/inspectVariables.js +94 -0
  85. package/dist/runtime/dom/inspectWhyModel.d.ts +20 -0
  86. package/dist/runtime/dom/inspectWhyModel.js +134 -0
  87. package/dist/runtime/dom/layout.d.ts +5 -1
  88. package/dist/runtime/dom/layout.js +10 -3
  89. package/dist/runtime/dom/listenerPageScripts.d.ts +11 -5
  90. package/dist/runtime/dom/listenerPageScripts.js +95 -9
  91. package/dist/runtime/dom/listenerSummary.d.ts +4 -0
  92. package/dist/runtime/dom/listenerSummary.js +26 -9
  93. package/dist/runtime/dom/reactEventHelpers.d.ts +5 -0
  94. package/dist/runtime/dom/reactEventHelpers.js +12 -4
  95. package/dist/runtime/page/emulation.d.ts +20 -0
  96. package/dist/runtime/page/emulation.js +37 -0
  97. package/dist/telemetry/a11y.d.ts +10 -0
  98. package/dist/telemetry/a11y.js +78 -1
  99. package/dist/telemetry/console.d.ts +1 -0
  100. package/dist/telemetry/console.js +100 -5
  101. package/dist/telemetry/network.js +3 -1
  102. package/dist/types.d.ts +32 -0
  103. package/dist/ui/formatters/details.d.ts +8 -0
  104. package/dist/ui/formatters/details.js +59 -3
  105. package/dist/ui/formatters/dom.d.ts +2 -1
  106. package/dist/ui/formatters/dom.js +25 -9
  107. package/dist/ui/formatters/inspect.d.ts +39 -0
  108. package/dist/ui/formatters/inspect.js +596 -0
  109. package/dist/ui/formatters/keyAttributes.d.ts +19 -0
  110. package/dist/ui/formatters/keyAttributes.js +84 -0
  111. package/dist/ui/formatters/layout.js +2 -2
  112. package/dist/ui/formatters/networkHeaders.d.ts +13 -0
  113. package/dist/ui/formatters/networkHeaders.js +23 -3
  114. package/dist/ui/formatters/networkList.d.ts +29 -1
  115. package/dist/ui/formatters/networkList.js +86 -20
  116. package/dist/ui/formatters/status.js +1 -1
  117. package/dist/ui/formatting.d.ts +9 -0
  118. package/dist/ui/formatting.js +6 -3
  119. package/dist/ui/messages/commands.d.ts +123 -7
  120. package/dist/ui/messages/commands.js +181 -10
  121. package/dist/ui/messages/networkMessages.d.ts +14 -0
  122. package/dist/ui/messages/networkMessages.js +18 -0
  123. package/dist/ui/messages/session.d.ts +14 -0
  124. package/dist/ui/messages/session.js +20 -0
  125. package/dist/utils/async.d.ts +9 -0
  126. package/dist/utils/async.js +17 -0
  127. package/dist/utils/color.d.ts +84 -0
  128. package/dist/utils/color.js +376 -0
  129. package/dist/utils/cssValues.d.ts +109 -0
  130. package/dist/utils/cssValues.js +236 -0
  131. package/dist/utils/selectorFilters.d.ts +12 -0
  132. package/dist/utils/selectorFilters.js +29 -0
  133. package/package.json +1 -1
@@ -21,6 +21,7 @@ import { getSessionPort } from '../../session/port.js';
21
21
  import { createLogger } from '../../ui/logging/index.js';
22
22
  import { formatChromeNotice } from '../../ui/messages/chrome.js';
23
23
  import { delay } from '../../utils/async.js';
24
+ import { getErrorMessage } from '../../utils/errors.js';
24
25
  import { EXIT_CODES } from '../../utils/exitCodes.js';
25
26
  import { filterDefined } from '../../utils/objects.js';
26
27
  import { isProcessAlive } from '../../utils/process.js';
@@ -55,7 +56,10 @@ export class Session {
55
56
  config;
56
57
  onEnded;
57
58
  store = new TelemetryStore();
58
- registry = createCommandRegistry(this.store);
59
+ registry = createCommandRegistry(this.store, {
60
+ get: () => filterDefined({ viewport: this.config.viewport, colorScheme: this.config.colorScheme }),
61
+ set: (emulation) => this.setEmulation(emulation),
62
+ });
59
63
  notify = (notice) => log.info(formatChromeNotice(notice));
60
64
  chrome = null;
61
65
  cdp = null;
@@ -174,6 +178,24 @@ export class Session {
174
178
  }),
175
179
  };
176
180
  }
181
+ /**
182
+ * Record a page emulation changed mid-session, so screenshots restore it
183
+ * and `bdg status` reports it.
184
+ *
185
+ * @param emulation - Viewport and color scheme now emulated
186
+ */
187
+ setEmulation(emulation) {
188
+ const { viewport: _viewport, colorScheme: _colorScheme, ...rest } = this.config;
189
+ this.config = { ...rest, ...emulation };
190
+ if (!this.started)
191
+ return;
192
+ try {
193
+ writeSessionMetadata(this.metadata());
194
+ }
195
+ catch (error) {
196
+ log.debug(`Session metadata not updated: ${getErrorMessage(error)}`);
197
+ }
198
+ }
177
199
  /**
178
200
  * Whether stop() has been called.
179
201
  *
@@ -1,10 +1,23 @@
1
1
  import type { TelemetryStore } from './TelemetryStore.js';
2
2
  import type { CDPConnection } from '../../connection/cdp.js';
3
3
  import type { CommandName, CommandSchemas } from '../../ipc/index.js';
4
+ import { type SessionEmulation } from '../../runtime/page/emulation.js';
4
5
  type Handler<K extends CommandName> = (cdp: CDPConnection, params: CommandSchemas[K]['requestSchema']) => Promise<CommandSchemas[K]['responseSchema']>;
5
6
  export type CommandRegistry = {
6
7
  [K in CommandName]: Handler<K>;
7
8
  };
8
- export declare function createCommandRegistry(store: TelemetryStore): CommandRegistry;
9
+ /** The session's page emulation, which `page emulate` reads and changes */
10
+ export interface EmulationState {
11
+ get: () => SessionEmulation;
12
+ set: (emulation: SessionEmulation) => void;
13
+ }
14
+ /**
15
+ * The handlers of the session commands.
16
+ *
17
+ * @param store - Telemetry of the session
18
+ * @param emulation - The session's page emulation
19
+ * @returns Command registry
20
+ */
21
+ export declare function createCommandRegistry(store: TelemetryStore, emulation: EmulationState): CommandRegistry;
9
22
  export {};
10
23
  //# sourceMappingURL=commandRegistry.d.ts.map
@@ -1,9 +1,9 @@
1
- import { CDPConnectionError } from '../../connection/errors.js';
1
+ import { CDPConnectionError, CDPProtocolError } from '../../connection/errors.js';
2
2
  import { PatternDetector } from '../patternDetector.js';
3
3
  import { createInteractionRunner } from './interactions.js';
4
4
  import { withTriggeredRequestCount } from './triggeredRequests.js';
5
5
  import { CommandError } from '../../errors/index.js';
6
- import { formDiscoveryFailedError } from '../../errors/messages.js';
6
+ import { cdpCallError, formDiscoveryFailedError } from '../../errors/messages.js';
7
7
  import { evaluateScript, withBusyPageRecovery } from '../../runtime/dom/evalHelpers.js';
8
8
  import { inspectEventListeners } from '../../runtime/dom/eventListeners.js';
9
9
  import { FORM_DISCOVERY_SCRIPT, isRawFormData } from '../../runtime/dom/formDiscovery.js';
@@ -11,10 +11,11 @@ import { fillElement, clickElement, pressKeyElement, scrollPage, withActionStabi
11
11
  import { exceptionSummary } from '../../runtime/dom/formFillHelpers/shared.js';
12
12
  import { submitForm } from '../../runtime/dom/formSubmitHelpers.js';
13
13
  import { evaluateInFrame, listFrames } from '../../runtime/dom/frames.js';
14
+ import { inspectElement } from '../../runtime/dom/inspect.js';
14
15
  import { inspectLayout } from '../../runtime/dom/layout.js';
15
16
  import { onScriptTarget } from '../../runtime/dom/targetNode.js';
16
17
  import { waitForCondition } from '../../runtime/dom/wait.js';
17
- import { pageAppearance } from '../../runtime/page/emulation.js';
18
+ import { emulatePage, pageAppearance } from '../../runtime/page/emulation.js';
18
19
  import { readDocumentReadyState } from '../../runtime/page/loadingState.js';
19
20
  import { navigatePage } from '../../runtime/page/navigation.js';
20
21
  import { skippedBodyReason } from '../../telemetry/network.js';
@@ -62,6 +63,8 @@ function mapNetworkRequestToPreview(req, withHeaders = false) {
62
63
  return filterDefined({
63
64
  requestId: req.requestId,
64
65
  timestamp: req.timestamp,
66
+ sentTime: req.sentTime,
67
+ navigationId: req.navigationId,
65
68
  method: req.method,
66
69
  url: req.url,
67
70
  status: req.status,
@@ -260,9 +263,45 @@ function pageWebSocketUrl(store) {
260
263
  throw new CDPConnectionError('No page target');
261
264
  return url;
262
265
  }
263
- export function createCommandRegistry(store) {
266
+ /** Chrome's "server error" code, used for failures on the page's state (a missing node, a bad id…) */
267
+ const CDP_SERVER_ERROR = -32000;
268
+ /**
269
+ * A `bdg cdp` failure that is the caller's: wrong parameters (81), or an
270
+ * id of a node, target or frame that does not exist (83). Other failures
271
+ * (internal errors, a detached page) stay software errors.
272
+ *
273
+ * @param method - CDP method
274
+ * @param error - What the call threw
275
+ * @returns The error to report, or undefined to keep the original
276
+ */
277
+ function callerError(method, error) {
278
+ if (!(error instanceof CDPProtocolError))
279
+ return undefined;
280
+ const err = cdpCallError(method, error.message);
281
+ if (error.isRequestError()) {
282
+ return new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.INVALID_ARGUMENTS);
283
+ }
284
+ if (error.code === CDP_SERVER_ERROR && err.notFound) {
285
+ return new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.RESOURCE_NOT_FOUND);
286
+ }
287
+ if (error.code === CDP_SERVER_ERROR &&
288
+ /must be specified|invalid|expected|missing/i.test(error.message)) {
289
+ return new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.INVALID_ARGUMENTS);
290
+ }
291
+ return undefined;
292
+ }
293
+ /**
294
+ * The handlers of the session commands.
295
+ *
296
+ * @param store - Telemetry of the session
297
+ * @param emulation - The session's page emulation
298
+ * @returns Command registry
299
+ */
300
+ export function createCommandRegistry(store, emulation) {
264
301
  const patternDetector = new PatternDetector();
265
302
  const interact = createInteractionRunner(store);
303
+ /** Frame id behind each index of the last `dom frames` listing */
304
+ let listedFrameIds;
266
305
  return {
267
306
  session_peek: async (_cdp, params) => {
268
307
  const lastN = calculateLastN(params.lastN);
@@ -360,7 +399,9 @@ export function createCommandRegistry(store) {
360
399
  });
361
400
  },
362
401
  cdp_call: async (cdp, params) => {
363
- const result = await withBusyPageRecovery(cdp, cdp.send(params.method, params.params ?? {}));
402
+ const result = await withBusyPageRecovery(cdp, cdp.send(params.method, params.params ?? {}).catch((error) => {
403
+ throw callerError(params.method, error) ?? error;
404
+ }));
364
405
  const detectionResult = patternDetector.trackCommand(params.method);
365
406
  let hint;
366
407
  if (detectionResult.shouldShow && detectionResult.pattern) {
@@ -376,11 +417,21 @@ export function createCommandRegistry(store) {
376
417
  },
377
418
  dom_eval: async (cdp, params) => params.frame === undefined
378
419
  ? evaluateScript(cdp, params.script)
379
- : evaluateInFrame(cdp, pageWebSocketUrl(store), params.script, params.frame),
380
- dom_frames: async (cdp) => ({ frames: await listFrames(cdp, pageWebSocketUrl(store)) }),
420
+ : evaluateInFrame(cdp, pageWebSocketUrl(store), params.script, params.frame, listedFrameIds),
421
+ dom_frames: async (cdp) => {
422
+ const { frames, frameIds } = await listFrames(cdp, pageWebSocketUrl(store));
423
+ listedFrameIds = frameIds;
424
+ return { frames };
425
+ },
381
426
  dom_fill: async (cdp, params) => interact(cdp, async () => onScriptTarget(cdp, params, (target) => withActionStability(cdp, () => fillElement(target.cdp, target.selector, params.value, filterDefined({ index: target.index, blur: params.blur, cwd: params.cwd })), params.wait !== false))),
382
- dom_click: async (cdp, params) => interact(cdp, async () => onScriptTarget(cdp, params, (target) => withActionStability(cdp, () => clickElement(target.cdp, target.selector, filterDefined({ index: target.index, action: params.action })), params.wait !== false)), {
427
+ dom_click: async (cdp, params) => interact(cdp, async () => onScriptTarget(cdp, params, (target) => withActionStability(cdp, () => clickElement(target.cdp, target.selector, filterDefined({
428
+ index: target.index,
429
+ action: params.action,
430
+ strict: params.strict,
431
+ })), params.wait !== false)), {
383
432
  detectNoEffect: params.wait !== false && params.action !== 'hover' && params.action !== 'right',
433
+ reportShown: params.action === 'hover',
434
+ detectUnsettled: params.wait !== false && params.action !== 'hover',
384
435
  }),
385
436
  dom_submit: async (cdp, params) => withTriggeredRequestCount(await interact(cdp, async () => onScriptTarget(cdp, params, (target) => submitForm(target.cdp, target.selector, {
386
437
  ...filterDefined({
@@ -395,7 +446,7 @@ export function createCommandRegistry(store) {
395
446
  index: target.index,
396
447
  times: params.times,
397
448
  modifiers: params.modifiers,
398
- })), params.wait !== false))),
449
+ })), params.wait !== false)), { reportShown: true, detectUnsettled: params.wait !== false }),
399
450
  dom_scroll: async (cdp, params) => interact(cdp, async () => onScriptTarget(cdp, params, (target) => withActionStability(cdp, () => scrollPage(target.cdp, target.selector || undefined, filterDefined({
400
451
  index: target.index,
401
452
  down: params.down,
@@ -407,11 +458,16 @@ export function createCommandRegistry(store) {
407
458
  })), params.wait !== false))),
408
459
  dom_listeners: async (cdp, params) => withBusyPageRecovery(cdp, inspectEventListeners(cdp, params)),
409
460
  dom_layout: async (cdp, params) => withBusyPageRecovery(cdp, inspectLayout(cdp, params)),
461
+ dom_inspect: async (cdp, params) => withBusyPageRecovery(cdp, inspectElement(cdp, params)),
410
462
  dom_wait: async (cdp, params) => waitForCondition(cdp, params),
411
463
  page_navigate: async (cdp, params) => interact(cdp, () => navigatePage(cdp, params.action, {
412
464
  ...filterDefined({ url: params.url, wait: params.wait }),
413
465
  pendingRequests: () => store.pendingNetworkRequests.values(),
414
466
  }), { reportRequests: false, reportEffects: false }),
467
+ page_emulate: async (cdp, params) => {
468
+ const emulated = await emulatePage(cdp, emulation.get(), params, emulation.set);
469
+ return { emulated, ...(await pageAppearance(cdp)) };
470
+ },
415
471
  dom_form_discover: async (cdp) => {
416
472
  const response = await withBusyPageRecovery(cdp, cdp.send('Runtime.evaluate', {
417
473
  expression: FORM_DISCOVERY_SCRIPT,
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Running page interactions (fill, click, submit, pressKey, scroll) one at a
3
- * time, reporting the dialogs, network requests and page changes they caused.
3
+ * time, reporting the dialogs, network requests and page changes they caused,
4
+ * and whether the page was still changing when they returned.
4
5
  */
5
6
  import type { TelemetryStore } from './TelemetryStore.js';
6
7
  import type { CDPConnection } from '../../connection/cdp.js';
@@ -28,6 +29,17 @@ export interface InteractionOptions {
28
29
  * whose effect shows in the DOM (click, submit) and that waited for it
29
30
  */
30
31
  detectNoEffect?: boolean;
32
+ /**
33
+ * List the elements it showed (`shown`): for hover and key presses, whose
34
+ * effect is often a menu, tooltip or new item rather than a message
35
+ */
36
+ reportShown?: boolean;
37
+ /**
38
+ * Say when the page was still changing as it returned (`settled: false`
39
+ * with `pending`): for actions that start app transitions (click, key
40
+ * press) and waited for the network
41
+ */
42
+ detectUnsettled?: boolean;
31
43
  }
32
44
  /** Runs one interaction after the previous one finished */
33
45
  export type InteractionRunner = <T extends object>(cdp: CDPConnection, action: () => Promise<T>, options?: InteractionOptions) => Promise<T & InteractionReport>;
@@ -40,10 +52,11 @@ export type InteractionRunner = <T extends object>(cdp: CDPConnection, action: (
40
52
  * from the page (without waiting: during a pending navigation that takes until
41
53
  * the new page commits), and the dialogs it opened, what it changed on the
42
54
  * page (see {@link watchActionEffects}) and the network requests it
43
- * triggered (see {@link watchTriggeredRequests}) are added to its result.
44
- * They are attributed by time: a dialog or request started by a page timer
45
- * or a navigation started earlier is reported by whichever interaction is
46
- * running then.
55
+ * triggered (see {@link watchTriggeredRequests}) are added to its result,
56
+ * and, when asked, what the page was still working on (see
57
+ * {@link pendingChanges}). They are attributed by time: a dialog or request
58
+ * started by a page timer or a navigation started earlier is reported by
59
+ * whichever interaction is running then.
47
60
  *
48
61
  * @param store - Session store recording accepted dialogs and network requests
49
62
  * @returns Interaction runner
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * Running page interactions (fill, click, submit, pressKey, scroll) one at a
3
- * time, reporting the dialogs, network requests and page changes they caused.
3
+ * time, reporting the dialogs, network requests and page changes they caused,
4
+ * and whether the page was still changing when they returned.
4
5
  */
5
- import { watchActionEffects } from '../../runtime/dom/actionEffects.js';
6
+ import { pendingChanges, watchActionEffects } from '../../runtime/dom/actionEffects.js';
6
7
  import { UNBIND_TARGET_SCRIPT } from '../../runtime/dom/targetNode.js';
7
8
  import { createLogger } from '../../ui/logging/index.js';
8
9
  import { getErrorMessage } from '../../utils/errors.js';
@@ -26,10 +27,11 @@ function succeeded(result) {
26
27
  * from the page (without waiting: during a pending navigation that takes until
27
28
  * the new page commits), and the dialogs it opened, what it changed on the
28
29
  * page (see {@link watchActionEffects}) and the network requests it
29
- * triggered (see {@link watchTriggeredRequests}) are added to its result.
30
- * They are attributed by time: a dialog or request started by a page timer
31
- * or a navigation started earlier is reported by whichever interaction is
32
- * running then.
30
+ * triggered (see {@link watchTriggeredRequests}) are added to its result,
31
+ * and, when asked, what the page was still working on (see
32
+ * {@link pendingChanges}). They are attributed by time: a dialog or request
33
+ * started by a page timer or a navigation started earlier is reported by
34
+ * whichever interaction is running then.
33
35
  *
34
36
  * @param store - Session store recording accepted dialogs and network requests
35
37
  * @returns Interaction runner
@@ -44,17 +46,25 @@ export function createInteractionRunner(store) {
44
46
  try {
45
47
  const result = await action();
46
48
  const dialogs = store.dialogs.slice(firstDialog);
47
- const changes = succeeded(result)
48
- ? await effects?.collect({
49
- dialogs: dialogs.length,
50
- detectNoEffect: options.detectNoEffect === true,
51
- })
49
+ if (!succeeded(result))
50
+ return { ...result, ...(dialogs.length > 0 && { dialogs }) };
51
+ const collected = await effects?.collect({
52
+ dialogs: dialogs.length,
53
+ detectNoEffect: options.detectNoEffect === true,
54
+ reportShown: options.reportShown === true,
55
+ detectUnsettled: options.detectUnsettled === true,
56
+ });
57
+ const { work, ...changes } = collected ?? {};
58
+ const requests = collectRequests?.();
59
+ const pending = options.detectUnsettled && work
60
+ ? pendingChanges(work, requests?.triggeredRequests)
52
61
  : undefined;
53
62
  return {
54
63
  ...result,
55
64
  ...(dialogs.length > 0 && { dialogs }),
56
65
  ...changes,
57
- ...(succeeded(result) && collectRequests?.()),
66
+ ...(pending && { settled: false, pending }),
67
+ ...requests,
58
68
  };
59
69
  }
60
70
  finally {