browser-debugger-cli 0.8.0 → 0.9.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 (251) hide show
  1. package/README.md +4 -1
  2. package/dist/cdp/schema.d.ts +4 -1
  3. package/dist/cdp/schema.js +48 -7
  4. package/dist/commands/cdp.js +3 -2
  5. package/dist/commands/cleanup.d.ts +11 -0
  6. package/dist/commands/cleanup.js +161 -57
  7. package/dist/commands/console.d.ts +20 -1
  8. package/dist/commands/console.js +57 -17
  9. package/dist/commands/details.js +3 -2
  10. package/dist/commands/dom/DomElementResolver.d.ts +10 -3
  11. package/dist/commands/dom/DomElementResolver.js +35 -17
  12. package/dist/commands/dom/a11y.d.ts +10 -0
  13. package/dist/commands/dom/a11y.js +27 -5
  14. package/dist/commands/dom/eval.d.ts +3 -1
  15. package/dist/commands/dom/eval.js +29 -4
  16. package/dist/commands/dom/form.js +16 -62
  17. package/dist/commands/dom/formInteraction.js +152 -113
  18. package/dist/commands/dom/formSummary.d.ts +49 -0
  19. package/dist/commands/dom/formSummary.js +180 -0
  20. package/dist/commands/dom/frames.d.ts +2 -1
  21. package/dist/commands/dom/frames.js +17 -2
  22. package/dist/commands/dom/get.d.ts +6 -5
  23. package/dist/commands/dom/get.js +92 -82
  24. package/dist/commands/dom/helpers/index.d.ts +1 -1
  25. package/dist/commands/dom/helpers/index.js +1 -1
  26. package/dist/commands/dom/helpers/query.d.ts +44 -17
  27. package/dist/commands/dom/helpers/query.js +244 -97
  28. package/dist/commands/dom/helpers/runElementCommand.d.ts +10 -2
  29. package/dist/commands/dom/helpers/runElementCommand.js +97 -30
  30. package/dist/commands/dom/helpers/screenshot.d.ts +4 -1
  31. package/dist/commands/dom/helpers/screenshot.js +164 -49
  32. package/dist/commands/dom/index.d.ts +3 -1
  33. package/dist/commands/dom/index.js +16 -6
  34. package/dist/commands/dom/layout.d.ts +14 -0
  35. package/dist/commands/dom/layout.js +54 -0
  36. package/dist/commands/dom/listeners.d.ts +5 -1
  37. package/dist/commands/dom/listeners.js +13 -3
  38. package/dist/commands/dom/query.js +2 -3
  39. package/dist/commands/dom/screenshot.d.ts +12 -2
  40. package/dist/commands/dom/screenshot.js +27 -3
  41. package/dist/commands/dom/semanticUtils.d.ts +6 -13
  42. package/dist/commands/dom/semanticUtils.js +15 -19
  43. package/dist/commands/dom/wait.d.ts +13 -0
  44. package/dist/commands/dom/wait.js +83 -0
  45. package/dist/commands/helpJson.js +2 -2
  46. package/dist/commands/network/list.js +4 -11
  47. package/dist/commands/optionBehaviors.js +112 -21
  48. package/dist/commands/page.d.ts +2 -1
  49. package/dist/commands/page.js +41 -5
  50. package/dist/commands/peek.js +4 -11
  51. package/dist/commands/sessions.d.ts +8 -0
  52. package/dist/commands/sessions.js +19 -0
  53. package/dist/commands/shared/CommandRunner.js +4 -4
  54. package/dist/commands/shared/dataFetcher.js +2 -2
  55. package/dist/commands/shared/followMode.d.ts +21 -1
  56. package/dist/commands/shared/followMode.js +29 -2
  57. package/dist/commands/shared/handleValidationError.d.ts +2 -2
  58. package/dist/commands/shared/handleValidationError.js +12 -3
  59. package/dist/commands/shared/optionTypes.d.ts +40 -5
  60. package/dist/commands/shared/startHelpers.js +12 -3
  61. package/dist/commands/shared/validation.d.ts +3 -2
  62. package/dist/commands/shared/validation.js +4 -3
  63. package/dist/commands/start.d.ts +63 -0
  64. package/dist/commands/start.js +115 -15
  65. package/dist/commands/status.js +29 -7
  66. package/dist/commands/stop.js +7 -6
  67. package/dist/commands/tail.js +4 -11
  68. package/dist/commands/types.d.ts +2 -0
  69. package/dist/commands.js +2 -0
  70. package/dist/connection/chromeIdentity.d.ts +65 -0
  71. package/dist/connection/chromeIdentity.js +143 -0
  72. package/dist/connection/launcher/profilePreferences.d.ts +47 -0
  73. package/dist/connection/launcher/profilePreferences.js +151 -0
  74. package/dist/connection/launcher.d.ts +21 -2
  75. package/dist/connection/launcher.js +42 -16
  76. package/dist/connection/portReservation.d.ts +14 -4
  77. package/dist/connection/portReservation.js +21 -6
  78. package/dist/connection/startupExit.d.ts +8 -0
  79. package/dist/connection/startupExit.js +15 -6
  80. package/dist/constants.d.ts +6 -2
  81. package/dist/constants.js +9 -2
  82. package/dist/daemon/SessionController.js +23 -7
  83. package/dist/daemon/errors.d.ts +1 -1
  84. package/dist/daemon/errors.js +1 -1
  85. package/dist/daemon/launcher.d.ts +2 -1
  86. package/dist/daemon/launcher.js +5 -6
  87. package/dist/daemon/server/SocketServer.js +1 -2
  88. package/dist/daemon/session/Session.d.ts +13 -0
  89. package/dist/daemon/session/Session.js +57 -8
  90. package/dist/daemon/session/chromeConnection.d.ts +9 -0
  91. package/dist/daemon/session/chromeConnection.js +45 -8
  92. package/dist/daemon/session/commandRegistry.js +52 -62
  93. package/dist/daemon/session/interactions.d.ts +35 -9
  94. package/dist/daemon/session/interactions.js +36 -9
  95. package/dist/daemon/session/triggeredRequests.d.ts +67 -0
  96. package/dist/daemon/session/triggeredRequests.js +157 -0
  97. package/dist/daemon/session/types.d.ts +5 -1
  98. package/dist/daemon.js +5393 -1600
  99. package/dist/errors/messages.d.ts +387 -24
  100. package/dist/errors/messages.js +761 -67
  101. package/dist/index.js +3976 -1558
  102. package/dist/ipc/client.d.ts +12 -1
  103. package/dist/ipc/client.js +22 -3
  104. package/dist/ipc/protocol/commands.d.ts +89 -4
  105. package/dist/ipc/protocol/commands.js +2 -0
  106. package/dist/ipc/protocol/domTypes.d.ts +258 -7
  107. package/dist/ipc/session/lifecycle.d.ts +8 -1
  108. package/dist/ipc/session/queries.d.ts +5 -1
  109. package/dist/ipc/session/types.d.ts +5 -0
  110. package/dist/ipc/transport/index.d.ts +2 -1
  111. package/dist/ipc/transport/index.js +2 -2
  112. package/dist/runtime/dom/actionEffects.d.ts +106 -0
  113. package/dist/runtime/dom/actionEffects.js +256 -0
  114. package/dist/runtime/dom/actionEffectsScripts.d.ts +52 -0
  115. package/dist/runtime/dom/actionEffectsScripts.js +234 -0
  116. package/dist/runtime/dom/elementGeometry.d.ts +170 -0
  117. package/dist/runtime/dom/elementGeometry.js +553 -0
  118. package/dist/runtime/dom/elementInfo.d.ts +77 -0
  119. package/dist/runtime/dom/elementInfo.js +191 -0
  120. package/dist/runtime/dom/evalHelpers.d.ts +51 -6
  121. package/dist/runtime/dom/evalHelpers.js +136 -26
  122. package/dist/runtime/dom/eventListeners.d.ts +2 -1
  123. package/dist/runtime/dom/eventListeners.js +174 -47
  124. package/dist/runtime/dom/formDiscovery.d.ts +1 -1
  125. package/dist/runtime/dom/formDiscovery.js +116 -16
  126. package/dist/runtime/dom/formFillHelpers/fill.d.ts +10 -0
  127. package/dist/runtime/dom/formFillHelpers/fill.js +125 -10
  128. package/dist/runtime/dom/formFillHelpers/index.d.ts +2 -2
  129. package/dist/runtime/dom/formFillHelpers/index.js +2 -2
  130. package/dist/runtime/dom/formFillHelpers/pressKey.js +14 -3
  131. package/dist/runtime/dom/formFillHelpers/scroll.d.ts +3 -0
  132. package/dist/runtime/dom/formFillHelpers/scroll.js +60 -18
  133. package/dist/runtime/dom/formFillHelpers/shared.d.ts +17 -0
  134. package/dist/runtime/dom/formFillHelpers/shared.js +25 -1
  135. package/dist/runtime/dom/formFillHelpers/stability.d.ts +20 -6
  136. package/dist/runtime/dom/formFillHelpers/stability.js +50 -19
  137. package/dist/runtime/dom/formSubmitHelpers.d.ts +3 -0
  138. package/dist/runtime/dom/formSubmitHelpers.js +89 -15
  139. package/dist/runtime/dom/frameLayout.d.ts +60 -0
  140. package/dist/runtime/dom/frameLayout.js +140 -0
  141. package/dist/runtime/dom/frameOrigin.d.ts +50 -0
  142. package/dist/runtime/dom/frameOrigin.js +62 -0
  143. package/dist/runtime/dom/frameScopedConnection.d.ts +92 -0
  144. package/dist/runtime/dom/frameScopedConnection.js +252 -0
  145. package/dist/runtime/dom/frameSelection.d.ts +1 -1
  146. package/dist/runtime/dom/frameSelection.js +2 -2
  147. package/dist/runtime/dom/frames.d.ts +25 -2
  148. package/dist/runtime/dom/frames.js +202 -63
  149. package/dist/runtime/dom/layout.d.ts +67 -0
  150. package/dist/runtime/dom/layout.js +333 -0
  151. package/dist/runtime/dom/listenerPageScripts.d.ts +66 -0
  152. package/dist/runtime/dom/listenerPageScripts.js +279 -0
  153. package/dist/runtime/dom/listenerSummary.d.ts +132 -11
  154. package/dist/runtime/dom/listenerSummary.js +344 -22
  155. package/dist/runtime/dom/pageActivity.d.ts +41 -0
  156. package/dist/runtime/dom/pageActivity.js +123 -0
  157. package/dist/runtime/dom/reactEventHelpers.d.ts +58 -2
  158. package/dist/runtime/dom/reactEventHelpers.js +212 -41
  159. package/dist/runtime/dom/targetNode.d.ts +80 -27
  160. package/dist/runtime/dom/targetNode.js +249 -33
  161. package/dist/runtime/dom/wait.d.ts +25 -0
  162. package/dist/runtime/dom/wait.js +199 -0
  163. package/dist/runtime/dom/waitCondition.d.ts +71 -0
  164. package/dist/runtime/dom/waitCondition.js +75 -0
  165. package/dist/runtime/page/emulation.d.ts +51 -0
  166. package/dist/runtime/page/emulation.js +80 -0
  167. package/dist/runtime/page/loadingState.d.ts +36 -0
  168. package/dist/runtime/page/loadingState.js +86 -0
  169. package/dist/runtime/page/navigation.d.ts +46 -2
  170. package/dist/runtime/page/navigation.js +69 -33
  171. package/dist/session/QueryCacheManager.d.ts +11 -1
  172. package/dist/session/QueryCacheManager.js +25 -3
  173. package/dist/session/chromeOwners.d.ts +34 -0
  174. package/dist/session/chromeOwners.js +51 -0
  175. package/dist/session/cleanup/staleSession.d.ts +11 -1
  176. package/dist/session/cleanup/staleSession.js +17 -6
  177. package/dist/session/cleanup/userCommands.js +2 -4
  178. package/dist/session/metadata.d.ts +5 -1
  179. package/dist/session/metadata.js +2 -1
  180. package/dist/session/paths.d.ts +77 -3
  181. package/dist/session/paths.js +111 -5
  182. package/dist/session/port.d.ts +31 -7
  183. package/dist/session/port.js +50 -43
  184. package/dist/session/portClaims.d.ts +66 -0
  185. package/dist/session/portClaims.js +284 -0
  186. package/dist/session/sessionList.d.ts +58 -0
  187. package/dist/session/sessionList.js +199 -0
  188. package/dist/session/sessionName.d.ts +46 -0
  189. package/dist/session/sessionName.js +97 -0
  190. package/dist/telemetry/a11y.d.ts +8 -3
  191. package/dist/telemetry/a11y.js +92 -28
  192. package/dist/telemetry/requestKinds.d.ts +32 -0
  193. package/dist/telemetry/requestKinds.js +61 -0
  194. package/dist/telemetry/requestState.d.ts +31 -0
  195. package/dist/telemetry/requestState.js +38 -0
  196. package/dist/types.d.ts +80 -3
  197. package/dist/ui/formatters/a11y.js +3 -0
  198. package/dist/ui/formatters/console/chronological.d.ts +8 -0
  199. package/dist/ui/formatters/console/chronological.js +17 -4
  200. package/dist/ui/formatters/console/json.js +3 -4
  201. package/dist/ui/formatters/console/shared.d.ts +12 -0
  202. package/dist/ui/formatters/console.d.ts +2 -2
  203. package/dist/ui/formatters/console.js +1 -1
  204. package/dist/ui/formatters/details.js +2 -1
  205. package/dist/ui/formatters/dom.d.ts +26 -14
  206. package/dist/ui/formatters/dom.js +65 -52
  207. package/dist/ui/formatters/form.js +29 -18
  208. package/dist/ui/formatters/layout.d.ts +31 -0
  209. package/dist/ui/formatters/layout.js +53 -0
  210. package/dist/ui/formatters/listeners.d.ts +3 -2
  211. package/dist/ui/formatters/listeners.js +73 -9
  212. package/dist/ui/formatters/networkHeaders.js +13 -0
  213. package/dist/ui/formatters/preview.js +2 -1
  214. package/dist/ui/formatters/requestStatus.d.ts +1 -17
  215. package/dist/ui/formatters/requestStatus.js +2 -30
  216. package/dist/ui/formatters/sessions.d.ts +12 -0
  217. package/dist/ui/formatters/sessions.js +40 -0
  218. package/dist/ui/formatters/status.d.ts +21 -2
  219. package/dist/ui/formatters/status.js +47 -10
  220. package/dist/ui/formatters/triggeredRequests.d.ts +36 -0
  221. package/dist/ui/formatters/triggeredRequests.js +65 -0
  222. package/dist/ui/formatting.d.ts +10 -0
  223. package/dist/ui/formatting.js +28 -36
  224. package/dist/ui/messages/chrome.d.ts +9 -0
  225. package/dist/ui/messages/chrome.js +17 -5
  226. package/dist/ui/messages/commands.d.ts +388 -14
  227. package/dist/ui/messages/commands.js +664 -21
  228. package/dist/ui/messages/consoleMessages.d.ts +10 -0
  229. package/dist/ui/messages/consoleMessages.js +17 -0
  230. package/dist/ui/messages/hints.js +2 -1
  231. package/dist/ui/messages/preview.js +5 -4
  232. package/dist/ui/messages/session.d.ts +16 -21
  233. package/dist/ui/messages/session.js +28 -26
  234. package/dist/ui/messages/sessionCommand.d.ts +43 -0
  235. package/dist/ui/messages/sessionCommand.js +52 -0
  236. package/dist/utils/async.d.ts +8 -0
  237. package/dist/utils/async.js +19 -0
  238. package/dist/utils/http.d.ts +22 -1
  239. package/dist/utils/http.js +28 -9
  240. package/dist/utils/selectorFilters.d.ts +36 -8
  241. package/dist/utils/selectorFilters.js +267 -53
  242. package/dist/utils/shellDetection.d.ts +8 -2
  243. package/dist/utils/shellDetection.js +120 -33
  244. package/dist/utils/suggestions.d.ts +26 -0
  245. package/dist/utils/suggestions.js +73 -0
  246. package/dist/utils/taskMappings.js +10 -0
  247. package/dist/utils/url.d.ts +12 -2
  248. package/dist/utils/url.js +69 -7
  249. package/package.json +1 -1
  250. package/dist/ui/formatters/sessionFormatters.d.ts +0 -58
  251. package/dist/ui/formatters/sessionFormatters.js +0 -121
@@ -12,7 +12,7 @@ import { join } from 'path';
12
12
  import { DaemonStartupError, SessionDirError } from './errors.js';
13
13
  import { sessionDirIsFileError, sessionDirNotWritableError, socketPathTooLongError, } from '../errors/messages.js';
14
14
  import { isDaemonAlive } from '../session/daemonSocket.js';
15
- import { ensureSessionDir, getSessionDir, getSessionFilePath } from '../session/paths.js';
15
+ import { MAX_DAEMON_SOCKET_PATH_BYTES, ensureSessionDir, getSessionDir, getSessionFilePath, } from '../session/paths.js';
16
16
  import { createLogger } from '../ui/logging/index.js';
17
17
  import { delay } from '../utils/async.js';
18
18
  import { getErrorMessage } from '../utils/errors.js';
@@ -52,13 +52,12 @@ export async function launchDaemon() {
52
52
  daemon.unref();
53
53
  await waitForDaemonReady(() => exited);
54
54
  }
55
- /** Longest Unix socket path (sun_path is 104 bytes on macOS, 108 on Linux, NUL included) */
56
- const MAX_SOCKET_PATH = process.platform === 'darwin' ? 103 : 107;
57
55
  /**
58
56
  * Check that the session directory can hold the daemon's files before
59
57
  * spawning it (otherwise the daemon dies and only its log says why).
60
58
  *
61
- * @throws SessionDirError (103) for a file or a too-long path, (82) when not writable
59
+ * @throws SessionDirError (103) for a file, (81) for a too-long path like a
60
+ * named session's, (82) when not writable
62
61
  */
63
62
  export function assertUsableSessionDir() {
64
63
  const dir = getSessionDir();
@@ -68,8 +67,8 @@ export function assertUsableSessionDir() {
68
67
  if (fs.existsSync(dir) && !fs.statSync(dir).isDirectory())
69
68
  fail(sessionDirIsFileError(dir));
70
69
  const socketPath = getSessionFilePath('DAEMON_SOCKET');
71
- if (Buffer.byteLength(socketPath) > MAX_SOCKET_PATH) {
72
- fail(socketPathTooLongError(socketPath, MAX_SOCKET_PATH));
70
+ if (Buffer.byteLength(socketPath) > MAX_DAEMON_SOCKET_PATH_BYTES) {
71
+ fail(socketPathTooLongError(socketPath, MAX_DAEMON_SOCKET_PATH_BYTES), EXIT_CODES.INVALID_ARGUMENTS);
73
72
  }
74
73
  try {
75
74
  ensureSessionDir();
@@ -2,11 +2,10 @@ import { linkSync, lstatSync, unlinkSync } from 'fs';
2
2
  import { createServer } from 'net';
3
3
  import { DaemonError } from '../errors.js';
4
4
  import { probeDaemonSocket } from '../../session/daemonSocket.js';
5
+ import { MAX_SOCKET_PATH_BYTES } from '../../session/paths.js';
5
6
  import { createLogger } from '../../ui/logging/index.js';
6
7
  import { getErrorMessage } from '../../utils/errors.js';
7
8
  import { EXIT_CODES } from '../../utils/exitCodes.js';
8
- /** Unix socket path limit (sun_path is 104 bytes on macOS incl. NUL; 108 on Linux). */
9
- const MAX_SOCKET_PATH_BYTES = 103;
10
9
  /** Error code thrown when another daemon already owns the socket path. */
11
10
  export const DAEMON_ALREADY_RUNNING_CODE = 'DAEMON_ALREADY_RUNNING';
12
11
  /**
@@ -6,6 +6,7 @@
6
6
  * separate worker process: the daemon process *is* the session.
7
7
  */
8
8
  import type { CommandName, CommandSchemas } from '../../ipc/index.js';
9
+ import type { PageLoadingState } from '../../ipc/protocol/commands.js';
9
10
  import type { SessionOptions } from '../../ipc/session/lifecycle.js';
10
11
  import type { StatusResponseData } from '../../ipc/session/queries.js';
11
12
  /** Why a session ended. */
@@ -18,6 +19,8 @@ export interface SessionInfo {
18
19
  targetTitle?: string;
19
20
  /** HTTP status of the page's main document, when known */
20
21
  documentStatus?: number;
22
+ /** The page had not finished loading when the readiness wait ended */
23
+ loading?: PageLoadingState;
21
24
  }
22
25
  /** Session metadata as reported in status responses. */
23
26
  export type SessionStatusMetadata = NonNullable<StatusResponseData['sessionMetadata']>;
@@ -50,6 +53,7 @@ export declare class Session {
50
53
  private readonly launchAbort;
51
54
  private started;
52
55
  private documentRequestId;
56
+ private loading;
53
57
  private constructor();
54
58
  /**
55
59
  * Validate the request and create a session that has not been launched yet.
@@ -115,6 +119,15 @@ export declare class Session {
115
119
  * Acquire Chrome, CDP and collectors, checking for a concurrent stop after each step.
116
120
  */
117
121
  private acquireResources;
122
+ /**
123
+ * Choose the port and launch Chrome on it.
124
+ *
125
+ * A Chrome left by this session's crashed daemon is reaped first, so the
126
+ * session gets its remembered port back. If another process took an
127
+ * automatically chosen port before Chrome could listen on it, another port
128
+ * is chosen (the taken one now answers, so it is skipped).
129
+ */
130
+ private launchOwnChrome;
118
131
  /**
119
132
  * End the session after its page connection was lost. A launched Chrome
120
133
  * still running a moment later means the tab was closed (e.g.
@@ -5,6 +5,7 @@
5
5
  * telemetry collectors and executes session commands directly. There is no
6
6
  * separate worker process: the daemon process *is* the session.
7
7
  */
8
+ import { ChromeLaunchError } from '../../connection/errors.js';
8
9
  import { TelemetryStore } from './TelemetryStore.js';
9
10
  import { connectCDP, navigateToTarget } from './cdpSetup.js';
10
11
  import { externalChromePort, findPageTarget, setupChromeConnection, } from './chromeConnection.js';
@@ -12,6 +13,8 @@ import { startTelemetryCollectors } from './collectors.js';
12
13
  import { createCommandRegistry } from './commandRegistry.js';
13
14
  import { teardownSession } from './teardown.js';
14
15
  import { CommandError } from '../../errors/index.js';
16
+ import { applySessionEmulation } from '../../runtime/page/emulation.js';
17
+ import { readPageLoadingState } from '../../runtime/page/loadingState.js';
15
18
  import { reapOrphanedChrome, removeSessionFiles } from '../../session/cleanup/staleSession.js';
16
19
  import { writeSessionMetadata } from '../../session/metadata.js';
17
20
  import { getSessionPort } from '../../session/port.js';
@@ -25,6 +28,17 @@ import { validateUrl } from '../../utils/url.js';
25
28
  const log = createLogger('session');
26
29
  /** How long Chrome gets to exit after the page connection drops, before the end is called a closed tab */
27
30
  const CRASH_SETTLE_MS = 500;
31
+ /** Ports tried when automatically chosen ports turn out to be taken */
32
+ const PORT_ATTEMPTS = 3;
33
+ /**
34
+ * Whether a launch failed because another process holds the port.
35
+ *
36
+ * @param error - Launch error
37
+ * @returns True for a PORT_IN_USE launch error
38
+ */
39
+ function isPortConflict(error) {
40
+ return error instanceof ChromeLaunchError && error.issue?.code === 'PORT_IN_USE';
41
+ }
28
42
  /**
29
43
  * A launch that failed because the session was stopped while starting.
30
44
  */
@@ -56,6 +70,7 @@ export class Session {
56
70
  launchAbort = new AbortController();
57
71
  started = false;
58
72
  documentRequestId;
73
+ loading;
59
74
  constructor(config, onEnded) {
60
75
  this.config = config;
61
76
  this.onEnded = onEnded;
@@ -134,6 +149,7 @@ export class Session {
134
149
  targetUrl: target?.url ?? this.config.url,
135
150
  ...(target?.title && { targetTitle: target.title }),
136
151
  ...(documentStatus !== undefined && { documentStatus }),
152
+ ...(this.loading && { loading: this.loading }),
137
153
  };
138
154
  }
139
155
  /**
@@ -153,6 +169,8 @@ export class Session {
153
169
  autoStopAt: this.autoStopAt,
154
170
  targetId: target?.id,
155
171
  webSocketDebuggerUrl: target?.webSocketDebuggerUrl,
172
+ viewport: this.config.viewport,
173
+ colorScheme: this.config.colorScheme,
156
174
  }),
157
175
  };
158
176
  }
@@ -194,15 +212,14 @@ export class Session {
194
212
  async acquireResources() {
195
213
  this.store.resetSessionStart();
196
214
  const { chromeWsUrl } = this.config;
197
- const port = chromeWsUrl
198
- ? externalChromePort(chromeWsUrl)
199
- : await getSessionPort(this.config.port || undefined);
200
- this.config = { ...this.config, port };
201
- this.throwIfStopping();
202
- if (!this.config.chromeWsUrl) {
203
- await reapOrphanedChrome();
215
+ if (chromeWsUrl) {
216
+ this.config = { ...this.config, port: externalChromePort(chromeWsUrl) };
217
+ this.throwIfStopping();
218
+ this.chrome = await setupChromeConnection(this.config, this.store, log, this.notify);
219
+ }
220
+ else {
221
+ await this.launchOwnChrome();
204
222
  }
205
- this.chrome = await setupChromeConnection(this.config, this.store, log, this.notify);
206
223
  this.throwIfStopping();
207
224
  if (this.chrome) {
208
225
  await findPageTarget(this.config, this.store, log);
@@ -213,10 +230,40 @@ export class Session {
213
230
  signal: this.launchAbort.signal,
214
231
  });
215
232
  this.throwIfStopping();
233
+ await applySessionEmulation(this.cdp, this.config);
234
+ this.throwIfStopping();
216
235
  this.cleanupFunctions = await startTelemetryCollectors(this.cdp, this.config, this.store, log);
217
236
  this.throwIfStopping();
218
237
  this.documentRequestId = await navigateToTarget(this.cdp, this.config, this.store, this.chrome, log);
219
238
  this.throwIfStopping();
239
+ this.loading = await readPageLoadingState(this.cdp, this.store.pendingNetworkRequests.values());
240
+ this.throwIfStopping();
241
+ }
242
+ /**
243
+ * Choose the port and launch Chrome on it.
244
+ *
245
+ * A Chrome left by this session's crashed daemon is reaped first, so the
246
+ * session gets its remembered port back. If another process took an
247
+ * automatically chosen port before Chrome could listen on it, another port
248
+ * is chosen (the taken one now answers, so it is skipped).
249
+ */
250
+ async launchOwnChrome() {
251
+ await reapOrphanedChrome();
252
+ const explicitPort = this.config.port || undefined;
253
+ for (let attempt = 1;; attempt++) {
254
+ this.config = { ...this.config, port: await getSessionPort(explicitPort) };
255
+ this.throwIfStopping();
256
+ try {
257
+ this.chrome = await setupChromeConnection(this.config, this.store, log, this.notify);
258
+ return;
259
+ }
260
+ catch (error) {
261
+ const retry = !explicitPort && attempt < PORT_ATTEMPTS && isPortConflict(error);
262
+ if (!retry)
263
+ throw error;
264
+ log.info(`Port ${this.config.port} was taken by another process; choosing another`);
265
+ }
266
+ }
220
267
  }
221
268
  /**
222
269
  * End the session after its page connection was lost. A launched Chrome
@@ -282,6 +329,8 @@ function buildConfig(url, port, options) {
282
329
  maxBodySize: options.maxBodySize,
283
330
  chromeWsUrl: options.chromeWsUrl,
284
331
  chromeFlags: options.chromeFlags,
332
+ viewport: options.viewport,
333
+ colorScheme: options.colorScheme,
285
334
  }),
286
335
  };
287
336
  }
@@ -22,6 +22,15 @@ export declare function setupChromeConnection(config: SessionConfig, telemetrySt
22
22
  * @returns Port in the URL, or the protocol default
23
23
  */
24
24
  export declare function externalChromePort(wsUrl: string): number;
25
+ /**
26
+ * Chrome flags of a launched Chrome: with `--viewport`, a window of that size
27
+ * first (so tabs the page opens get it too), then the user's own flags, which
28
+ * win over it.
29
+ *
30
+ * @param config - Session configuration
31
+ * @returns Flags, or undefined without any
32
+ */
33
+ export declare function windowSizeFlags(config: SessionConfig): string[] | undefined;
25
34
  /**
26
35
  * Find the page target of a Chrome that bdg launched.
27
36
  *
@@ -9,11 +9,12 @@ import { launchChrome } from '../../connection/launcher.js';
9
9
  import { HTTP_LOCALHOST } from '../../constants.js';
10
10
  import { ConfigError } from '../errors.js';
11
11
  import { CommandError } from '../../errors/index.js';
12
- import { externalBrowserIdMismatchError, externalChromeUnreachableError, externalPageNotFoundError, } from '../../errors/messages.js';
12
+ import { chromeInUseBySessionError, externalBrowserIdMismatchError, externalChromeUnreachableError, externalPageNotFoundError, notDevToolsEndpointError, } from '../../errors/messages.js';
13
13
  import { writeChromePid } from '../../session/chrome.js';
14
+ import { findConflictingOwner } from '../../session/chromeOwners.js';
14
15
  import { getSessionDir } from '../../session/paths.js';
15
16
  import { EXIT_CODES } from '../../utils/exitCodes.js';
16
- import { createPageTarget, fetchBrowserWsUrl, fetchCDPTargets } from '../../utils/http.js';
17
+ import { createPageTarget, fetchCDPTargets, probeDevToolsEndpoint } from '../../utils/http.js';
17
18
  import { filterDefined } from '../../utils/objects.js';
18
19
  /**
19
20
  * Setup Chrome connection - either launch new instance or connect to existing.
@@ -71,23 +72,30 @@ async function resolveExternalTarget(wsUrl, config, log) {
71
72
  const { hostname, pathname, protocol, port } = new URL(wsUrl);
72
73
  const http = { host: hostname, secure: protocol === 'wss:' };
73
74
  const endpoint = `${http.secure ? 'https' : 'http'}://${hostname}:${config.port}`;
74
- const browserWsUrl = await fetchBrowserWsUrl(config.port, log, http);
75
- if (!browserWsUrl)
76
- fail(externalChromeUnreachableError(endpoint, http.secure), EXIT_CODES.CDP_CONNECTION_FAILURE);
75
+ const probe = await probeDevToolsEndpoint(config.port, log, http);
76
+ if (probe.kind !== 'devtools') {
77
+ const err = probe.kind === 'not-devtools'
78
+ ? notDevToolsEndpointError(endpoint)
79
+ : externalChromeUnreachableError(endpoint, http.secure);
80
+ fail(err, EXIT_CODES.CDP_CONNECTION_FAILURE);
81
+ }
82
+ const browserWsUrl = probe.wsUrl;
77
83
  const targets = await fetchCDPTargets(config.port, log, http);
78
84
  const targetId = pathname.split('/').pop() ?? '';
79
85
  if (!pathname.startsWith(BROWSER_WS_PATH)) {
80
86
  const known = targets.find((t) => t.id === targetId);
81
87
  if (!known)
82
88
  fail(externalPageNotFoundError(targetId, endpoint), EXIT_CODES.RESOURCE_NOT_FOUND);
89
+ await assertNotOwned(targets, known.id, endpoint);
83
90
  return { ...known, webSocketDebuggerUrl: wsUrl };
84
91
  }
85
92
  if (new URL(browserWsUrl).pathname !== pathname) {
86
93
  const actual = withEndpoint(browserWsUrl, protocol, hostname, port);
87
94
  fail(externalBrowserIdMismatchError(endpoint, actual), EXIT_CODES.RESOURCE_NOT_FOUND);
88
95
  }
89
- const page = targets.find((t) => t.type === 'page') ??
90
- (await createPageTarget(hostname, config.port, log, http));
96
+ const firstPage = targets.find((t) => t.type === 'page');
97
+ await assertNotOwned(targets, firstPage?.id ?? '', endpoint);
98
+ const page = firstPage ?? (await createPageTarget(hostname, config.port, log, http));
91
99
  if (!page)
92
100
  fail(externalChromeUnreachableError(endpoint, http.secure), EXIT_CODES.CDP_CONNECTION_FAILURE);
93
101
  log.info(`Attaching to page ${page.url} of the external Chrome`);
@@ -96,6 +104,21 @@ async function resolveExternalTarget(wsUrl, config, log) {
96
104
  webSocketDebuggerUrl: withEndpoint(page.webSocketDebuggerUrl, protocol, hostname, port),
97
105
  };
98
106
  }
107
+ /**
108
+ * Refuse to attach when another running bdg session launched this Chrome or
109
+ * drives the tab this session would use: taking it over would mix both
110
+ * sessions' telemetry and let one end the other.
111
+ *
112
+ * @param targets - Every target of the Chrome
113
+ * @param targetId - Tab this session would drive ('' for a tab still to be opened)
114
+ * @param endpoint - e.g. http://127.0.0.1:9222
115
+ * @throws CommandError (90) naming the other session
116
+ */
117
+ async function assertNotOwned(targets, targetId, endpoint) {
118
+ const owner = await findConflictingOwner(targets.map((t) => t.id), targetId);
119
+ if (owner)
120
+ fail(chromeInUseBySessionError(endpoint, owner), EXIT_CODES.RESOURCE_CONFLICT);
121
+ }
99
122
  /**
100
123
  * A DevTools WebSocket URL from Chrome, reached the way the user's URL is
101
124
  * (Chrome reports its own host, e.g. 127.0.0.1, even behind a proxy).
@@ -123,6 +146,20 @@ function withEndpoint(url, protocol, hostname, port) {
123
146
  function fail(err, exitCode) {
124
147
  throw new CommandError(err.message, { suggestion: err.suggestion }, exitCode);
125
148
  }
149
+ /**
150
+ * Chrome flags of a launched Chrome: with `--viewport`, a window of that size
151
+ * first (so tabs the page opens get it too), then the user's own flags, which
152
+ * win over it.
153
+ *
154
+ * @param config - Session configuration
155
+ * @returns Flags, or undefined without any
156
+ */
157
+ export function windowSizeFlags(config) {
158
+ if (!config.viewport)
159
+ return config.chromeFlags;
160
+ const { width, height } = config.viewport;
161
+ return [`--window-size=${width},${height}`, ...(config.chromeFlags ?? [])];
162
+ }
126
163
  /**
127
164
  * Launch a new Chrome instance and record its PID for crash cleanup.
128
165
  */
@@ -134,7 +171,7 @@ async function setupLaunchedChrome(config, log) {
134
171
  ...filterDefined({
135
172
  userDataDir: config.userDataDir,
136
173
  headless: config.headless,
137
- chromeFlags: config.chromeFlags,
174
+ chromeFlags: windowSizeFlags(config),
138
175
  }),
139
176
  });
140
177
  log.info(`Chrome launched (PID ${chrome.pid})`);
@@ -1,16 +1,24 @@
1
1
  import { CDPConnectionError } from '../../connection/errors.js';
2
2
  import { PatternDetector } from '../patternDetector.js';
3
3
  import { createInteractionRunner } from './interactions.js';
4
+ import { withTriggeredRequestCount } from './triggeredRequests.js';
4
5
  import { CommandError } from '../../errors/index.js';
6
+ import { formDiscoveryFailedError } from '../../errors/messages.js';
5
7
  import { evaluateScript, withBusyPageRecovery } from '../../runtime/dom/evalHelpers.js';
6
8
  import { inspectEventListeners } from '../../runtime/dom/eventListeners.js';
7
9
  import { FORM_DISCOVERY_SCRIPT, isRawFormData } from '../../runtime/dom/formDiscovery.js';
8
- import { fillElement, clickElement, pressKeyElement, scrollPage, waitForActionStability, } from '../../runtime/dom/formFillHelpers/index.js';
10
+ import { fillElement, clickElement, pressKeyElement, scrollPage, withActionStability, } from '../../runtime/dom/formFillHelpers/index.js';
11
+ import { exceptionSummary } from '../../runtime/dom/formFillHelpers/shared.js';
9
12
  import { submitForm } from '../../runtime/dom/formSubmitHelpers.js';
10
13
  import { evaluateInFrame, listFrames } from '../../runtime/dom/frames.js';
11
- import { resolveScriptTarget, withUserSelector } from '../../runtime/dom/targetNode.js';
14
+ import { inspectLayout } from '../../runtime/dom/layout.js';
15
+ import { onScriptTarget } from '../../runtime/dom/targetNode.js';
16
+ import { waitForCondition } from '../../runtime/dom/wait.js';
17
+ import { pageAppearance } from '../../runtime/page/emulation.js';
18
+ import { readDocumentReadyState } from '../../runtime/page/loadingState.js';
12
19
  import { navigatePage } from '../../runtime/page/navigation.js';
13
20
  import { skippedBodyReason } from '../../telemetry/network.js';
21
+ import { sessionCommand } from '../../ui/messages/sessionCommand.js';
14
22
  import { EXIT_CODES } from '../../utils/exitCodes.js';
15
23
  import { filterDefined } from '../../utils/objects.js';
16
24
  import { VERSION } from '../../utils/version.js';
@@ -169,7 +177,7 @@ function findNetworkRequestOrThrow(store, id) {
169
177
  store.pendingNetworkRequests.get(id)?.request ??
170
178
  (webSocket && webSocketAsRequest(webSocket));
171
179
  if (!request) {
172
- throw new CommandError(`Network request not found: ${id}`, { suggestion: 'List request ids with: bdg network list' }, EXIT_CODES.RESOURCE_NOT_FOUND);
180
+ throw new CommandError(`Network request not found: ${id}`, { suggestion: `List request ids with: ${sessionCommand('bdg network list')}` }, EXIT_CODES.RESOURCE_NOT_FOUND);
173
181
  }
174
182
  return request;
175
183
  }
@@ -183,13 +191,13 @@ function findNetworkRequestOrThrow(store, id) {
183
191
  */
184
192
  function findConsoleMessageOrThrow(messages, indexStr) {
185
193
  if (!/^\d+$/.test(indexStr)) {
186
- throw new CommandError(`Invalid console message index: ${indexStr}`, { suggestion: 'Use a 0-based index from: bdg console --list' }, EXIT_CODES.INVALID_ARGUMENTS);
194
+ throw new CommandError(`Invalid console message index: ${indexStr}`, { suggestion: `Use a 0-based index from: ${sessionCommand('bdg console --list')}` }, EXIT_CODES.INVALID_ARGUMENTS);
187
195
  }
188
196
  const index = parseInt(indexStr, 10);
189
197
  if (index >= messages.length) {
190
198
  throw new CommandError(messages.length === 0
191
199
  ? `Console message not found at index: ${indexStr} (no messages captured yet)`
192
- : `Console message not found at index: ${indexStr} (available: 0-${messages.length - 1})`, { suggestion: 'List messages with: bdg console --list' }, EXIT_CODES.RESOURCE_NOT_FOUND);
200
+ : `Console message not found at index: ${indexStr} (available: 0-${messages.length - 1})`, { suggestion: `List messages with: ${sessionCommand('bdg console --list')}` }, EXIT_CODES.RESOURCE_NOT_FOUND);
193
201
  }
194
202
  const message = messages[index];
195
203
  if (!message) {
@@ -304,7 +312,7 @@ export function createCommandRegistry(store) {
304
312
  }
305
313
  return Promise.reject(new Error(`Unknown itemType: ${String(params.itemType)}. Expected 'network' or 'console'.`));
306
314
  },
307
- session_status: async (_cdp, _params) => {
315
+ session_status: async (cdp, _params) => {
308
316
  const duration = Date.now() - store.sessionStartTime;
309
317
  const lastNetworkRequest = store.networkRequests[store.networkRequests.length - 1];
310
318
  const lastConsoleMessage = store.consoleMessages[store.consoleMessages.length - 1];
@@ -314,6 +322,7 @@ export function createCommandRegistry(store) {
314
322
  target: {
315
323
  url: store.targetInfo?.url ?? '',
316
324
  title: store.targetInfo?.title ?? '',
325
+ ...(await pageAppearance(cdp)),
317
326
  },
318
327
  activeTelemetry: store.activeTelemetry,
319
328
  activity: filterDefined({
@@ -324,7 +333,7 @@ export function createCommandRegistry(store) {
324
333
  }),
325
334
  navigationId: store.getCurrentNavigationId?.() ?? 0,
326
335
  };
327
- return Promise.resolve(result);
336
+ return result;
328
337
  },
329
338
  session_har_data: async (_cdp, _params) => {
330
339
  return Promise.resolve({
@@ -342,6 +351,10 @@ export function createCommandRegistry(store) {
342
351
  return Promise.resolve({
343
352
  url: targetRequest.url,
344
353
  requestId: targetRequest.requestId,
354
+ method: targetRequest.method,
355
+ ...(targetRequest.status !== undefined && { status: targetRequest.status }),
356
+ ...(targetRequest.statusText && { statusText: targetRequest.statusText }),
357
+ ...(targetRequest.errorText && { errorText: targetRequest.errorText }),
345
358
  requestHeaders,
346
359
  responseHeaders,
347
360
  });
@@ -365,66 +378,40 @@ export function createCommandRegistry(store) {
365
378
  ? evaluateScript(cdp, params.script)
366
379
  : evaluateInFrame(cdp, pageWebSocketUrl(store), params.script, params.frame),
367
380
  dom_frames: async (cdp) => ({ frames: await listFrames(cdp, pageWebSocketUrl(store)) }),
368
- dom_fill: async (cdp, params) => interact(cdp, async () => {
369
- const target = await resolveScriptTarget(cdp, params);
370
- const fillOptions = filterDefined({
371
- index: target.index,
372
- blur: params.blur,
373
- cwd: params.cwd,
374
- });
375
- const result = withUserSelector(await fillElement(cdp, target.selector, params.value, fillOptions), params.selector);
376
- if (result.success && params.wait !== false)
377
- await waitForActionStability(cdp);
378
- return result;
381
+ 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)), {
383
+ detectNoEffect: params.wait !== false && params.action !== 'hover' && params.action !== 'right',
379
384
  }),
380
- dom_click: async (cdp, params) => interact(cdp, async () => {
381
- const target = await resolveScriptTarget(cdp, params);
382
- const clickOptions = filterDefined({ index: target.index, action: params.action });
383
- const result = withUserSelector(await clickElement(cdp, target.selector, clickOptions), params.selector);
384
- if (result.success && params.wait !== false)
385
- await waitForActionStability(cdp);
386
- return result;
387
- }),
388
- dom_submit: async (cdp, params) => interact(cdp, async () => {
389
- const target = await resolveScriptTarget(cdp, params);
390
- const submitOptions = filterDefined({
385
+ dom_submit: async (cdp, params) => withTriggeredRequestCount(await interact(cdp, async () => onScriptTarget(cdp, params, (target) => submitForm(target.cdp, target.selector, {
386
+ ...filterDefined({
391
387
  index: target.index,
392
388
  waitNavigation: params.waitNavigation,
393
389
  waitNetwork: params.waitNetwork,
394
390
  timeout: params.timeout,
395
- });
396
- return withUserSelector(await submitForm(cdp, target.selector, submitOptions), params.selector);
397
- }),
398
- dom_press_key: async (cdp, params) => interact(cdp, async () => {
399
- const target = await resolveScriptTarget(cdp, params);
400
- const pressKeyOptions = filterDefined({
401
- index: target.index,
402
- times: params.times,
403
- modifiers: params.modifiers,
404
- });
405
- const result = withUserSelector(await pressKeyElement(cdp, target.selector, params.key, pressKeyOptions), params.selector);
406
- if (result.success && params.wait !== false)
407
- await waitForActionStability(cdp);
408
- return result;
409
- }),
410
- dom_scroll: async (cdp, params) => interact(cdp, async () => {
411
- const target = await resolveScriptTarget(cdp, params);
412
- const scrollOptions = filterDefined({
413
- index: target.index,
414
- down: params.down,
415
- up: params.up,
416
- left: params.left,
417
- right: params.right,
418
- top: params.top,
419
- bottom: params.bottom,
420
- });
421
- const result = withUserSelector(await scrollPage(cdp, target.selector || undefined, scrollOptions), params.selector);
422
- if (result.success && params.wait !== false)
423
- await waitForActionStability(cdp);
424
- return result;
425
- }),
391
+ }),
392
+ pendingRequests: () => store.pendingNetworkRequests.values(),
393
+ })), { detectNoEffect: params.waitNetwork !== 0 || params.waitNavigation === true })),
394
+ dom_press_key: async (cdp, params) => interact(cdp, async () => onScriptTarget(cdp, params, (target) => withActionStability(cdp, () => pressKeyElement(target.cdp, target.selector, params.key, filterDefined({
395
+ index: target.index,
396
+ times: params.times,
397
+ modifiers: params.modifiers,
398
+ })), params.wait !== false))),
399
+ dom_scroll: async (cdp, params) => interact(cdp, async () => onScriptTarget(cdp, params, (target) => withActionStability(cdp, () => scrollPage(target.cdp, target.selector || undefined, filterDefined({
400
+ index: target.index,
401
+ down: params.down,
402
+ up: params.up,
403
+ left: params.left,
404
+ right: params.right,
405
+ top: params.top,
406
+ bottom: params.bottom,
407
+ })), params.wait !== false))),
426
408
  dom_listeners: async (cdp, params) => withBusyPageRecovery(cdp, inspectEventListeners(cdp, params)),
427
- page_navigate: async (cdp, params) => interact(cdp, () => navigatePage(cdp, params.action, filterDefined({ url: params.url, wait: params.wait }))),
409
+ dom_layout: async (cdp, params) => withBusyPageRecovery(cdp, inspectLayout(cdp, params)),
410
+ dom_wait: async (cdp, params) => waitForCondition(cdp, params),
411
+ page_navigate: async (cdp, params) => interact(cdp, () => navigatePage(cdp, params.action, {
412
+ ...filterDefined({ url: params.url, wait: params.wait }),
413
+ pendingRequests: () => store.pendingNetworkRequests.values(),
414
+ }), { reportRequests: false, reportEffects: false }),
428
415
  dom_form_discover: async (cdp) => {
429
416
  const response = await withBusyPageRecovery(cdp, cdp.send('Runtime.evaluate', {
430
417
  expression: FORM_DISCOVERY_SCRIPT,
@@ -432,7 +419,10 @@ export function createCommandRegistry(store) {
432
419
  }));
433
420
  const cdpResponse = response;
434
421
  if (cdpResponse.exceptionDetails) {
435
- throw new Error(`Form discovery failed: ${cdpResponse.exceptionDetails.text}`);
422
+ const readyState = await readDocumentReadyState(cdp);
423
+ const loading = readyState !== undefined && readyState !== 'complete';
424
+ const err = formDiscoveryFailedError(exceptionSummary(cdpResponse.exceptionDetails), readyState);
425
+ throw new CommandError(err.message, { suggestion: err.suggestion }, loading ? EXIT_CODES.RESOURCE_NOT_FOUND : EXIT_CODES.SOFTWARE_ERROR);
436
426
  }
437
427
  const rawData = cdpResponse.result?.value;
438
428
  if (!isRawFormData(rawData)) {
@@ -1,14 +1,36 @@
1
1
  /**
2
2
  * Running page interactions (fill, click, submit, pressKey, scroll) one at a
3
- * time, reporting the dialogs they caused.
3
+ * time, reporting the dialogs, network requests and page changes they caused.
4
4
  */
5
5
  import type { TelemetryStore } from './TelemetryStore.js';
6
6
  import type { CDPConnection } from '../../connection/cdp.js';
7
- import type { DialogInfo } from '../../ipc/protocol/domTypes.js';
8
- /** Runs one interaction after the previous one finished */
9
- export type InteractionRunner = <T extends object>(cdp: CDPConnection, action: () => Promise<T>) => Promise<T & {
7
+ import type { ActionEffects, DialogInfo, TriggeredRequest } from '../../ipc/protocol/domTypes.js';
8
+ /** What an interaction caused besides its own result */
9
+ interface InteractionReport extends ActionEffects {
10
10
  dialogs?: DialogInfo[];
11
- }>;
11
+ triggeredRequests?: TriggeredRequest[];
12
+ triggeredRequestsOmitted?: number;
13
+ }
14
+ /** How an interaction is reported */
15
+ export interface InteractionOptions {
16
+ /**
17
+ * List the network requests it triggered (default true; off for page
18
+ * navigation, whose requests are the whole page load)
19
+ */
20
+ reportRequests?: boolean;
21
+ /**
22
+ * Report navigation and new messages (default true; off for page
23
+ * navigation, which reports its own page)
24
+ */
25
+ reportEffects?: boolean;
26
+ /**
27
+ * Say when it had no visible effect (`effect: "none"`): only for actions
28
+ * whose effect shows in the DOM (click, submit) and that waited for it
29
+ */
30
+ detectNoEffect?: boolean;
31
+ }
32
+ /** Runs one interaction after the previous one finished */
33
+ export type InteractionRunner = <T extends object>(cdp: CDPConnection, action: () => Promise<T>, options?: InteractionOptions) => Promise<T & InteractionReport>;
12
34
  /**
13
35
  * Create the runner for a session's interactions.
14
36
  *
@@ -16,12 +38,16 @@ export type InteractionRunner = <T extends object>(cdp: CDPConnection, action: (
16
38
  * commands would interleave keystrokes into each other's field, so they are
17
39
  * queued. After each one, the bound target (`window.__bdgTarget`) is removed
18
40
  * from the page (without waiting: during a pending navigation that takes until
19
- * the new page commits), and the dialogs it opened are added to its result.
20
- * Dialogs are attributed by time: one opened by a page timer or a navigation
21
- * started earlier is reported by whichever interaction is running then.
41
+ * the new page commits), and the dialogs it opened, what it changed on the
42
+ * 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.
22
47
  *
23
- * @param store - Session store recording accepted dialogs
48
+ * @param store - Session store recording accepted dialogs and network requests
24
49
  * @returns Interaction runner
25
50
  */
26
51
  export declare function createInteractionRunner(store: TelemetryStore): InteractionRunner;
52
+ export {};
27
53
  //# sourceMappingURL=interactions.d.ts.map