@orkestrel/browser 0.0.23 → 0.0.25

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.
@@ -107,7 +107,7 @@ export declare const BROWSER_FRAME_WORLD_NAME = "__orkestrelBrowserFrame";
107
107
  */
108
108
  export declare const BROWSER_HAR_CREATOR: Readonly<{
109
109
  name: "@orkestrel/browser";
110
- version: "0.0.23";
110
+ version: "0.0.25";
111
111
  }>;
112
112
 
113
113
  /** Names accessibility roles that receive actionable outline references. */
@@ -159,7 +159,7 @@ export declare const BROWSER_JOURNEY_STEP_KEYS: readonly string[];
159
159
 
160
160
  /**
161
161
  * Names the journey tools a toolset constructed with `journeys` registers and reserves: `record`,
162
- * `save`, `journeys`, `edit`, `replay`, and `forget`.
162
+ * `save`, `journeys`, `edit`, `replay`, `forget`, and `capture`.
163
163
  */
164
164
  export declare const BROWSER_JOURNEY_TOOL_NAMES: readonly BrowserToolName[];
165
165
 
@@ -711,6 +711,7 @@ export declare class BrowserContext implements BrowserContextInterface {
711
711
  constructor(client: CDPClientInterface, id?: string, viewport?: BrowserViewport, writer?: BrowserWriterInterface, emulation?: BrowserEmulationOptions, downloads?: BrowserDownloadOptions, options?: BrowserContextOptions);
712
712
  get emitter(): EmitterInterface<BrowserContextEventMap>;
713
713
  get id(): string | undefined;
714
+ get disposal(): BrowserContextDisposal | undefined;
714
715
  get cookies(): BrowserCookieManagerInterface;
715
716
  get permissions(): BrowserPermissionManagerInterface;
716
717
  get storage(): BrowserStorageManagerInterface;
@@ -723,6 +724,14 @@ export declare class BrowserContext implements BrowserContextInterface {
723
724
  close(): Promise<void>;
724
725
  }
725
726
 
727
+ /** Records Chromium's disposal acknowledgement or the failure that left disposal unconfirmed. */
728
+ export declare type BrowserContextDisposal = {
729
+ readonly confirmed: true;
730
+ } | {
731
+ readonly confirmed: false;
732
+ readonly error: unknown;
733
+ };
734
+
726
735
  /** Maps the browser-context lifecycle events. */
727
736
  export declare type BrowserContextEventMap = {
728
737
  readonly page: readonly [page: BrowserPageInterface];
@@ -746,6 +755,8 @@ export declare type BrowserContextEventMap = {
746
755
  export declare interface BrowserContextInterface {
747
756
  readonly emitter: EmitterInterface<BrowserContextEventMap>;
748
757
  readonly id: string | undefined;
758
+ /** Holds the remote disposal receipt after `close()` attempts disposal; wrapper destruction has none. */
759
+ readonly disposal: BrowserContextDisposal | undefined;
749
760
  readonly cookies: BrowserCookieManagerInterface;
750
761
  readonly permissions: BrowserPermissionManagerInterface;
751
762
  readonly storage: BrowserStorageManagerInterface;
@@ -1801,7 +1812,7 @@ export declare type BrowserJourneyEditRequest = BrowserJourneyEdit | {
1801
1812
  *
1802
1813
  * @remarks
1803
1814
  * - `store` — keeps the journeys the tools record, list, edit, and replay
1804
- * - `runs` — keeps each replay's run; omitting it writes no run
1815
+ * - `runs` — keeps each replay's run and saves standalone images when it provides `snapshot`
1805
1816
  * - `limit` — positive integer listing cap in characters; Default: the owner's `limit`, including direct construction
1806
1817
  * - `readonly` — if `true`, refuses `record`, `save`, `edit`, and `forget` before any store access, and
1807
1818
  * `replay` still writes runs; if `false` or omitted, every tool runs
@@ -1930,13 +1941,13 @@ export declare interface BrowserJourneyTarget {
1930
1941
  }
1931
1942
 
1932
1943
  /**
1933
- * Registers the journey tools `record`, `save`, `journeys`, `edit`, `replay`, and `forget` over a toolset
1944
+ * Registers the journey tools `record`, `save`, `journeys`, `edit`, `replay`, `forget`, and `capture` over a toolset
1934
1945
  * and owns the recording and the active replay.
1935
1946
  *
1936
1947
  * @remarks
1937
1948
  * The journey toolset composes the toolset's public members: `perform` reads the view a receipt
1938
1949
  * carries through `look`, `hold` and `emitter` drive the recorder and the replay, `view` converts
1939
- * an edit's `ref` to a target, and `tools` receives the six tools at construction, which refuses
1950
+ * an edit's `ref` to a target, and `tools` receives the journey tools at construction, which refuses
1940
1951
  * with `BROWSER_TOOLSET_RESERVED` when the manager already holds one of the names. Every refusal
1941
1952
  * is a sentence that names the next call, and a reason inside it is a clause without a directive
1942
1953
  * or a final period.
@@ -1952,7 +1963,7 @@ export declare interface BrowserJourneyTarget {
1952
1963
  * by the view after the run.
1953
1964
  *
1954
1965
  * `destroy()` aborts the active replay and waits for it to finish, stops a recording without
1955
- * saving it, and removes the six tools the manager still holds under the instances it added.
1966
+ * saving it, and removes the journey tools the manager still holds under the instances it added.
1956
1967
  *
1957
1968
  * @example
1958
1969
  * ```ts
@@ -1972,7 +1983,7 @@ export declare class BrowserJourneyToolset implements BrowserJourneyToolsetInter
1972
1983
  }
1973
1984
 
1974
1985
  /**
1975
- * Registers the six journey tools over a toolset and owns the recording and the active replay.
1986
+ * Registers the journey tools over a toolset and owns the recording and the active replay.
1976
1987
  *
1977
1988
  * @remarks
1978
1989
  * - `recording` — the name of the journey being recorded, or `undefined` when none is
@@ -1981,7 +1992,7 @@ export declare class BrowserJourneyToolset implements BrowserJourneyToolsetInter
1981
1992
  export declare interface BrowserJourneyToolsetInterface {
1982
1993
  readonly recording: string | undefined;
1983
1994
  readonly replaying: string | undefined;
1984
- /** Aborts the active replay and removes the six journey tools from the manager. */
1995
+ /** Aborts the active replay and removes the journey tools from the manager. */
1985
1996
  destroy(): Promise<void>;
1986
1997
  }
1987
1998
 
@@ -3483,6 +3494,14 @@ export declare interface BrowserRunStep {
3483
3494
 
3484
3495
  /** Keeps runs by the journey name and run id the run carries. */
3485
3496
  export declare interface BrowserRunStoreInterface {
3497
+ /**
3498
+ * Saves a standalone PNG under the runs root without replacing an existing file.
3499
+ * @param bytes - PNG bytes to persist
3500
+ * @param options - Cancellation options
3501
+ * @returns Absolute path of the saved image
3502
+ * @remarks Stores without standalone file storage omit this capability.
3503
+ */
3504
+ snapshot?(bytes: Uint8Array, options?: BrowserStoreOptions): Promise<string>;
3486
3505
  /** Mints a run id for the journey and creates its run directory exclusively. */
3487
3506
  open(name: string, options?: BrowserStoreOptions): Promise<BrowserRunSlot>;
3488
3507
  /** Returns the run stored under the journey name and id, or `undefined` when none is stored. */
@@ -3967,9 +3986,9 @@ export declare interface BrowserRunStoreInterface {
3967
3986
  /**
3968
3987
  * Names a tool the browser toolset reserves: the generic tools, the staged `dialog`, the
3969
3988
  * opt-in `tabs` and `switch`, and the journey tools `record`, `save`, `journeys`, `edit`,
3970
- * `replay`, and `forget`, which a toolset constructed with `journeys` reserves.
3989
+ * `replay`, `forget`, and `capture`, which a toolset constructed with `journeys` reserves.
3971
3990
  */
3972
- export declare type BrowserToolName = 'look' | 'read' | 'plain' | 'click' | 'type' | 'press' | 'navigate' | 'wait' | 'dialog' | 'tabs' | 'switch' | 'record' | 'save' | 'journeys' | 'edit' | 'replay' | 'forget';
3991
+ export declare type BrowserToolName = 'look' | 'read' | 'plain' | 'click' | 'type' | 'press' | 'navigate' | 'wait' | 'dialog' | 'tabs' | 'switch' | 'record' | 'save' | 'journeys' | 'edit' | 'replay' | 'forget' | 'capture';
3973
3992
 
3974
3993
  /** Identifies a removed WebMCP tool by document and name. */
3975
3994
  export declare interface BrowserToolRemoval {
@@ -4753,9 +4772,7 @@ export declare interface BrowserRunStoreInterface {
4753
4772
  }
4754
4773
 
4755
4774
  /**
4756
- * Reports that a CDP request could not be sent or completed because the client was not in a
4757
- * connectable state — not connected, closed while connecting, or the connection dropped
4758
- * mid-request — under the code `BROWSER_CDP_CONNECTION_ERROR`.
4775
+ * Reports that a CDP request could not be sent or completed because the client was not connected, closed while connecting, lost its connection, or lost the request's target session, under the code `BROWSER_CDP_CONNECTION_ERROR`.
4759
4776
  */
4760
4777
  export declare class CDPConnectionError extends BrowserError {
4761
4778
  constructor(message: string, context?: Readonly<Record<string, unknown>>);
@@ -107,7 +107,7 @@ export declare const BROWSER_FRAME_WORLD_NAME = "__orkestrelBrowserFrame";
107
107
  */
108
108
  export declare const BROWSER_HAR_CREATOR: Readonly<{
109
109
  name: "@orkestrel/browser";
110
- version: "0.0.23";
110
+ version: "0.0.25";
111
111
  }>;
112
112
 
113
113
  /** Names accessibility roles that receive actionable outline references. */
@@ -159,7 +159,7 @@ export declare const BROWSER_JOURNEY_STEP_KEYS: readonly string[];
159
159
 
160
160
  /**
161
161
  * Names the journey tools a toolset constructed with `journeys` registers and reserves: `record`,
162
- * `save`, `journeys`, `edit`, `replay`, and `forget`.
162
+ * `save`, `journeys`, `edit`, `replay`, `forget`, and `capture`.
163
163
  */
164
164
  export declare const BROWSER_JOURNEY_TOOL_NAMES: readonly BrowserToolName[];
165
165
 
@@ -711,6 +711,7 @@ export declare class BrowserContext implements BrowserContextInterface {
711
711
  constructor(client: CDPClientInterface, id?: string, viewport?: BrowserViewport, writer?: BrowserWriterInterface, emulation?: BrowserEmulationOptions, downloads?: BrowserDownloadOptions, options?: BrowserContextOptions);
712
712
  get emitter(): EmitterInterface<BrowserContextEventMap>;
713
713
  get id(): string | undefined;
714
+ get disposal(): BrowserContextDisposal | undefined;
714
715
  get cookies(): BrowserCookieManagerInterface;
715
716
  get permissions(): BrowserPermissionManagerInterface;
716
717
  get storage(): BrowserStorageManagerInterface;
@@ -723,6 +724,14 @@ export declare class BrowserContext implements BrowserContextInterface {
723
724
  close(): Promise<void>;
724
725
  }
725
726
 
727
+ /** Records Chromium's disposal acknowledgement or the failure that left disposal unconfirmed. */
728
+ export declare type BrowserContextDisposal = {
729
+ readonly confirmed: true;
730
+ } | {
731
+ readonly confirmed: false;
732
+ readonly error: unknown;
733
+ };
734
+
726
735
  /** Maps the browser-context lifecycle events. */
727
736
  export declare type BrowserContextEventMap = {
728
737
  readonly page: readonly [page: BrowserPageInterface];
@@ -746,6 +755,8 @@ export declare type BrowserContextEventMap = {
746
755
  export declare interface BrowserContextInterface {
747
756
  readonly emitter: EmitterInterface<BrowserContextEventMap>;
748
757
  readonly id: string | undefined;
758
+ /** Holds the remote disposal receipt after `close()` attempts disposal; wrapper destruction has none. */
759
+ readonly disposal: BrowserContextDisposal | undefined;
749
760
  readonly cookies: BrowserCookieManagerInterface;
750
761
  readonly permissions: BrowserPermissionManagerInterface;
751
762
  readonly storage: BrowserStorageManagerInterface;
@@ -1801,7 +1812,7 @@ export declare type BrowserJourneyEditRequest = BrowserJourneyEdit | {
1801
1812
  *
1802
1813
  * @remarks
1803
1814
  * - `store` — keeps the journeys the tools record, list, edit, and replay
1804
- * - `runs` — keeps each replay's run; omitting it writes no run
1815
+ * - `runs` — keeps each replay's run and saves standalone images when it provides `snapshot`
1805
1816
  * - `limit` — positive integer listing cap in characters; Default: the owner's `limit`, including direct construction
1806
1817
  * - `readonly` — if `true`, refuses `record`, `save`, `edit`, and `forget` before any store access, and
1807
1818
  * `replay` still writes runs; if `false` or omitted, every tool runs
@@ -1930,13 +1941,13 @@ export declare interface BrowserJourneyTarget {
1930
1941
  }
1931
1942
 
1932
1943
  /**
1933
- * Registers the journey tools `record`, `save`, `journeys`, `edit`, `replay`, and `forget` over a toolset
1944
+ * Registers the journey tools `record`, `save`, `journeys`, `edit`, `replay`, `forget`, and `capture` over a toolset
1934
1945
  * and owns the recording and the active replay.
1935
1946
  *
1936
1947
  * @remarks
1937
1948
  * The journey toolset composes the toolset's public members: `perform` reads the view a receipt
1938
1949
  * carries through `look`, `hold` and `emitter` drive the recorder and the replay, `view` converts
1939
- * an edit's `ref` to a target, and `tools` receives the six tools at construction, which refuses
1950
+ * an edit's `ref` to a target, and `tools` receives the journey tools at construction, which refuses
1940
1951
  * with `BROWSER_TOOLSET_RESERVED` when the manager already holds one of the names. Every refusal
1941
1952
  * is a sentence that names the next call, and a reason inside it is a clause without a directive
1942
1953
  * or a final period.
@@ -1952,7 +1963,7 @@ export declare interface BrowserJourneyTarget {
1952
1963
  * by the view after the run.
1953
1964
  *
1954
1965
  * `destroy()` aborts the active replay and waits for it to finish, stops a recording without
1955
- * saving it, and removes the six tools the manager still holds under the instances it added.
1966
+ * saving it, and removes the journey tools the manager still holds under the instances it added.
1956
1967
  *
1957
1968
  * @example
1958
1969
  * ```ts
@@ -1972,7 +1983,7 @@ export declare class BrowserJourneyToolset implements BrowserJourneyToolsetInter
1972
1983
  }
1973
1984
 
1974
1985
  /**
1975
- * Registers the six journey tools over a toolset and owns the recording and the active replay.
1986
+ * Registers the journey tools over a toolset and owns the recording and the active replay.
1976
1987
  *
1977
1988
  * @remarks
1978
1989
  * - `recording` — the name of the journey being recorded, or `undefined` when none is
@@ -1981,7 +1992,7 @@ export declare class BrowserJourneyToolset implements BrowserJourneyToolsetInter
1981
1992
  export declare interface BrowserJourneyToolsetInterface {
1982
1993
  readonly recording: string | undefined;
1983
1994
  readonly replaying: string | undefined;
1984
- /** Aborts the active replay and removes the six journey tools from the manager. */
1995
+ /** Aborts the active replay and removes the journey tools from the manager. */
1985
1996
  destroy(): Promise<void>;
1986
1997
  }
1987
1998
 
@@ -3483,6 +3494,14 @@ export declare interface BrowserRunStep {
3483
3494
 
3484
3495
  /** Keeps runs by the journey name and run id the run carries. */
3485
3496
  export declare interface BrowserRunStoreInterface {
3497
+ /**
3498
+ * Saves a standalone PNG under the runs root without replacing an existing file.
3499
+ * @param bytes - PNG bytes to persist
3500
+ * @param options - Cancellation options
3501
+ * @returns Absolute path of the saved image
3502
+ * @remarks Stores without standalone file storage omit this capability.
3503
+ */
3504
+ snapshot?(bytes: Uint8Array, options?: BrowserStoreOptions): Promise<string>;
3486
3505
  /** Mints a run id for the journey and creates its run directory exclusively. */
3487
3506
  open(name: string, options?: BrowserStoreOptions): Promise<BrowserRunSlot>;
3488
3507
  /** Returns the run stored under the journey name and id, or `undefined` when none is stored. */
@@ -3967,9 +3986,9 @@ export declare interface BrowserRunStoreInterface {
3967
3986
  /**
3968
3987
  * Names a tool the browser toolset reserves: the generic tools, the staged `dialog`, the
3969
3988
  * opt-in `tabs` and `switch`, and the journey tools `record`, `save`, `journeys`, `edit`,
3970
- * `replay`, and `forget`, which a toolset constructed with `journeys` reserves.
3989
+ * `replay`, `forget`, and `capture`, which a toolset constructed with `journeys` reserves.
3971
3990
  */
3972
- export declare type BrowserToolName = 'look' | 'read' | 'plain' | 'click' | 'type' | 'press' | 'navigate' | 'wait' | 'dialog' | 'tabs' | 'switch' | 'record' | 'save' | 'journeys' | 'edit' | 'replay' | 'forget';
3991
+ export declare type BrowserToolName = 'look' | 'read' | 'plain' | 'click' | 'type' | 'press' | 'navigate' | 'wait' | 'dialog' | 'tabs' | 'switch' | 'record' | 'save' | 'journeys' | 'edit' | 'replay' | 'forget' | 'capture';
3973
3992
 
3974
3993
  /** Identifies a removed WebMCP tool by document and name. */
3975
3994
  export declare interface BrowserToolRemoval {
@@ -4753,9 +4772,7 @@ export declare interface BrowserRunStoreInterface {
4753
4772
  }
4754
4773
 
4755
4774
  /**
4756
- * Reports that a CDP request could not be sent or completed because the client was not in a
4757
- * connectable state — not connected, closed while connecting, or the connection dropped
4758
- * mid-request — under the code `BROWSER_CDP_CONNECTION_ERROR`.
4775
+ * Reports that a CDP request could not be sent or completed because the client was not connected, closed while connecting, lost its connection, or lost the request's target session, under the code `BROWSER_CDP_CONNECTION_ERROR`.
4759
4776
  */
4760
4777
  export declare class CDPConnectionError extends BrowserError {
4761
4778
  constructor(message: string, context?: Readonly<Record<string, unknown>>);
@@ -162,7 +162,7 @@ var BROWSER_MOUSE_BUTTON_MASKS = Object.freeze({
162
162
  */
163
163
  var BROWSER_HAR_CREATOR = Object.freeze({
164
164
  name: "@orkestrel/browser",
165
- version: "0.0.23"
165
+ version: "0.0.25"
166
166
  });
167
167
  /** Names the attribute that tags temporary screenshot styles and masks. */
168
168
  var BROWSER_SCREENSHOT_ATTRIBUTE = "data-orkestrel-screenshot";
@@ -741,7 +741,7 @@ var BROWSER_TOOL_COPY = Object.freeze({
741
741
  description: "The journey name, such as add-kettle."
742
742
  }),
743
743
  edits: Object.freeze({
744
- description: "The changes, as an array or a JSON string of the array, applied in order; one invalid change refuses them all.",
744
+ description: "The changes, as an array or its JSON string, applied in order; one invalid change refuses them all.",
745
745
  anyOf: Object.freeze([Object.freeze({
746
746
  type: "array",
747
747
  items: Object.freeze({
@@ -817,6 +817,18 @@ var BROWSER_TOOL_COPY = Object.freeze({
817
817
  required: Object.freeze(["journey"])
818
818
  })
819
819
  }),
820
+ capture: Object.freeze({
821
+ name: "capture",
822
+ description: "Saves the current view as a PNG in the runs directory and returns its path.",
823
+ parameters: Object.freeze({
824
+ type: "object",
825
+ properties: Object.freeze({ full: Object.freeze({
826
+ type: "boolean",
827
+ description: "True captures the full page; false captures the viewport."
828
+ }) }),
829
+ required: Object.freeze(["full"])
830
+ })
831
+ }),
820
832
  forget: Object.freeze({
821
833
  name: "forget",
822
834
  description: "Removes a saved journey and all its runs; the name is free to record again.",
@@ -885,7 +897,7 @@ var BROWSER_ACTION_STAGES = Object.freeze([
885
897
  var BROWSER_JOURNEY_EMPTY_LISTING = "No journeys are saved; call record to start one.";
886
898
  /**
887
899
  * Names the journey tools a toolset constructed with `journeys` registers and reserves: `record`,
888
- * `save`, `journeys`, `edit`, `replay`, and `forget`.
900
+ * `save`, `journeys`, `edit`, `replay`, `forget`, and `capture`.
889
901
  */
890
902
  var BROWSER_JOURNEY_TOOL_NAMES = Object.freeze([
891
903
  "record",
@@ -893,7 +905,8 @@ var BROWSER_JOURNEY_TOOL_NAMES = Object.freeze([
893
905
  "journeys",
894
906
  "edit",
895
907
  "replay",
896
- "forget"
908
+ "forget",
909
+ "capture"
897
910
  ]);
898
911
  /** Names observation and journey tools that cannot become journey steps. */
899
912
  var BROWSER_JOURNEY_NON_STEP_TOOLS = Object.freeze([...BROWSER_OBSERVATION_TOOL_NAMES, ...BROWSER_JOURNEY_TOOL_NAMES]);
@@ -999,9 +1012,7 @@ var CDPError = class extends BrowserError {
999
1012
  }
1000
1013
  };
1001
1014
  /**
1002
- * Reports that a CDP request could not be sent or completed because the client was not in a
1003
- * connectable state — not connected, closed while connecting, or the connection dropped
1004
- * mid-request — under the code `BROWSER_CDP_CONNECTION_ERROR`.
1015
+ * Reports that a CDP request could not be sent or completed because the client was not connected, closed while connecting, lost its connection, or lost the request's target session, under the code `BROWSER_CDP_CONNECTION_ERROR`.
1005
1016
  */
1006
1017
  var CDPConnectionError = class extends BrowserError {
1007
1018
  constructor(message, context) {
@@ -5211,6 +5222,7 @@ var CDPClient = class {
5211
5222
  const listener = signal === void 0 ? void 0 : this.#abort(id, signal);
5212
5223
  this.#pending.set(id, {
5213
5224
  method,
5225
+ session: options?.session,
5214
5226
  resolve,
5215
5227
  reject,
5216
5228
  timer,
@@ -5355,6 +5367,16 @@ var CDPClient = class {
5355
5367
  const method = parsed["method"];
5356
5368
  const rawParams = parsed["params"];
5357
5369
  const params = isRecord(rawParams) ? rawParams : Object.freeze({});
5370
+ if (method === "Target.detachedFromTarget" && isString(params["sessionId"])) {
5371
+ const detached = params["sessionId"];
5372
+ for (const [id, entry] of this.#pending) {
5373
+ if (entry.session !== detached) continue;
5374
+ this.#settle(id)?.reject(new CDPConnectionError("CDP session detached", {
5375
+ method: entry.method,
5376
+ session: detached
5377
+ }));
5378
+ }
5379
+ }
5358
5380
  const global = this.#subscriptions.get(void 0)?.get(method);
5359
5381
  if (global !== void 0) this.#dispatch(global, method, params);
5360
5382
  const session = parsed["sessionId"];
@@ -6450,9 +6472,11 @@ var BrowserPageElement = class {
6450
6472
  object: result["object"]["objectId"],
6451
6473
  context
6452
6474
  };
6453
- } catch {
6475
+ } catch (error) {
6454
6476
  options?.signal?.throwIfAborted();
6455
- throw new BrowserElementError(this.reference, "GONE");
6477
+ if (isCDPTimeoutError(error) || isCDPConnectionError(error)) throw error;
6478
+ if (isCDPError(error) && /Could not find node|No node with given id|No node found for given backend id/i.test(error.message)) throw new BrowserElementError(this.reference, "GONE");
6479
+ throw error;
6456
6480
  }
6457
6481
  }
6458
6482
  async #call(declaration, options, target) {
@@ -6481,7 +6505,7 @@ var BrowserPageElement = class {
6481
6505
  }
6482
6506
  #failure(options, error) {
6483
6507
  options?.signal?.throwIfAborted();
6484
- if (isBrowserElementError(error)) throw error;
6508
+ if (isBrowserElementError(error) || isCDPTimeoutError(error) || isCDPConnectionError(error)) throw error;
6485
6509
  const message = (isError(error) ? error.message : String(error)).split("\n", 1)[0] ?? "";
6486
6510
  const known = BROWSER_ELEMENT_REFUSALS.get(message.replace(/^Error: /, ""));
6487
6511
  if (known !== void 0) throw new BrowserElementError(this.reference, known.reason, known.detail);
@@ -6513,11 +6537,20 @@ var BrowserPageElement = class {
6513
6537
  session: this.#input.node.session,
6514
6538
  ...options
6515
6539
  }).catch(this.#failure.bind(this, options));
6516
- await this.#call(compileActionabilityFunction({
6540
+ await this.#call(`async function() {
6541
+ await (${compileActionabilityFunction({
6517
6542
  visible: true,
6518
6543
  enabled: true,
6519
6544
  stable: true
6520
- }), options);
6545
+ })}).call(this)
6546
+ let previous
6547
+ for (;;) {
6548
+ await new Promise((resolve) => requestAnimationFrame(resolve))
6549
+ const current = [window.scrollX, window.scrollY]
6550
+ if (previous && current.every((value, index) => value === previous[index])) return true
6551
+ previous = current
6552
+ }
6553
+ }`, options);
6521
6554
  const geometry = await this.#geometry(options);
6522
6555
  const point = geometry.page.center;
6523
6556
  const local = geometry.local.center;
@@ -6528,6 +6561,10 @@ var BrowserPageElement = class {
6528
6561
  }, {
6529
6562
  session: this.#input.node.session,
6530
6563
  ...options
6564
+ }).catch((error) => {
6565
+ options?.signal?.throwIfAborted();
6566
+ if (isCDPError(error) && error.context?.["message"] === "No node found at given location") throw new BrowserElementError(this.reference, "OCCLUDED", "hit-test location held no node");
6567
+ throw error;
6531
6568
  });
6532
6569
  if (!isRecord(hit) || !isInteger(hit["backendNodeId"]) || hit["frameId"] !== void 0 && hit["frameId"] !== this.#input.frame) throw new BrowserElementError(this.reference, "OCCLUDED");
6533
6570
  if (hit["backendNodeId"] !== this.#input.backend && await this.#call(compileHitFunction(), options, hit["backendNodeId"]) !== true) {
@@ -7179,10 +7216,12 @@ var BrowserRegistry = class {
7179
7216
  for (const frame of new Set([...this.tools(), ...this.#pending].map((entry) => entry.frame))) this.#invalidate(frame);
7180
7217
  }
7181
7218
  #navigated(params) {
7219
+ if (this.#page.closed || this.#destroying !== void 0) return;
7182
7220
  const frame = params["frame"];
7183
7221
  if (isRecord(frame) && isString(frame["id"])) this.#invalidate(frame["id"]);
7184
7222
  }
7185
7223
  #detached(params) {
7224
+ if (this.#page.closed || this.#destroying !== void 0) return;
7186
7225
  if (isString(params["frameId"])) this.#invalidate(params["frameId"]);
7187
7226
  }
7188
7227
  #invalidate(frame) {
@@ -7196,6 +7235,7 @@ var BrowserRegistry = class {
7196
7235
  if (removed) this.#emitter.emit("change");
7197
7236
  }
7198
7237
  #add(session, params) {
7238
+ if (this.#page.closed || this.#destroying !== void 0) return;
7199
7239
  if (!isArray(params["tools"])) return;
7200
7240
  for (const value of params["tools"]) {
7201
7241
  const tool = parseBrowserTool(value);
@@ -7206,6 +7246,7 @@ var BrowserRegistry = class {
7206
7246
  this.#emitter.emit("change");
7207
7247
  }
7208
7248
  #remove(params) {
7249
+ if (this.#page.closed || this.#destroying !== void 0) return;
7209
7250
  if (!isArray(params["tools"])) return;
7210
7251
  for (const value of params["tools"]) {
7211
7252
  const key = parseBrowserRemoval(value);
@@ -10141,10 +10182,10 @@ var BrowserPage = class BrowserPage extends BrowserFrame {
10141
10182
  } catch {}
10142
10183
  }
10143
10184
  async #close() {
10144
- await this.#release();
10145
10185
  try {
10146
10186
  await this.#client.send("Target.closeTarget", { targetId: this.#targetId });
10147
10187
  } catch {}
10188
+ await this.#release();
10148
10189
  }
10149
10190
  #release() {
10150
10191
  const active = this.#releasing;
@@ -10445,12 +10486,14 @@ var BrowserPage = class BrowserPage extends BrowserFrame {
10445
10486
  }
10446
10487
  async #attachWorker(session, id, url, category) {
10447
10488
  try {
10448
- await this.#client.send("Runtime.enable", void 0, { session });
10489
+ const enabling = this.#client.send("Runtime.enable", void 0, { session });
10490
+ if (category === "service_worker") this.#resumeTarget(session);
10491
+ await enabling;
10449
10492
  if (this.#closed) {
10450
10493
  await this.#detachChild(session, this.#sessionId);
10451
10494
  return;
10452
10495
  }
10453
- this.#resumeTarget(session);
10496
+ if (category !== "service_worker") this.#resumeTarget(session);
10454
10497
  const worker = new BrowserWorker(this.#client, session, id, url, category);
10455
10498
  this.#workers.set(id, worker);
10456
10499
  this.#emitter.emit("worker", worker);
@@ -10787,6 +10830,7 @@ var BrowserPage = class BrowserPage extends BrowserFrame {
10787
10830
  this.#emitter.emit("navigate", params["url"], true);
10788
10831
  }
10789
10832
  #handleFrameDetached(session, params) {
10833
+ if (this.#closed) return;
10790
10834
  const frame = params["frameId"];
10791
10835
  if (!isString(frame) || !this.#accepts(session, frame)) return;
10792
10836
  const swapped = params["reason"] === "swap";
@@ -10887,7 +10931,10 @@ var BrowserPage = class BrowserPage extends BrowserFrame {
10887
10931
  const session = params["sessionId"];
10888
10932
  if (!isRecord(target) || !isString(target["targetId"]) || !isString(session)) return;
10889
10933
  const category = target["type"];
10890
- if (owner !== this.#sessionId && category !== "iframe") return;
10934
+ if (owner !== this.#sessionId && category !== "iframe") {
10935
+ this.#detachChild(session, owner);
10936
+ return;
10937
+ }
10891
10938
  if (category === "worker" || category === "service_worker" || category === "shared_worker") {
10892
10939
  this.#attachWorker(session, target["targetId"], isString(target["url"]) ? target["url"] : "", category);
10893
10940
  return;
@@ -11195,6 +11242,7 @@ var BrowserContext = class {
11195
11242
  #publishing = /* @__PURE__ */ new Map();
11196
11243
  #observed = /* @__PURE__ */ new WeakSet();
11197
11244
  #shutdown;
11245
+ #disposal;
11198
11246
  #reference = 0;
11199
11247
  constructor(client, id, viewport, writer, emulation, downloads, options) {
11200
11248
  this.#client = client;
@@ -11219,6 +11267,9 @@ var BrowserContext = class {
11219
11267
  get id() {
11220
11268
  return this.#id;
11221
11269
  }
11270
+ get disposal() {
11271
+ return this.#disposal;
11272
+ }
11222
11273
  get cookies() {
11223
11274
  return this.#cookies;
11224
11275
  }
@@ -11261,14 +11312,14 @@ var BrowserContext = class {
11261
11312
  destroy() {
11262
11313
  const active = this.#shutdown;
11263
11314
  if (active !== void 0) return active;
11264
- const shutdown = this.#destroyResources();
11315
+ const shutdown = Promise.resolve().then(() => this.#destroyResources());
11265
11316
  this.#shutdown = shutdown;
11266
11317
  return shutdown;
11267
11318
  }
11268
11319
  close() {
11269
11320
  const active = this.#shutdown;
11270
11321
  if (active !== void 0) return active;
11271
- const shutdown = this.#closeResources();
11322
+ const shutdown = Promise.resolve().then(() => this.#closeResources());
11272
11323
  this.#shutdown = shutdown;
11273
11324
  return shutdown;
11274
11325
  }
@@ -11390,9 +11441,15 @@ var BrowserContext = class {
11390
11441
  let failure = await settleBrowserTeardown(...[...this.#pages.values()].map((page) => () => page.close()));
11391
11442
  this.#pages.clear();
11392
11443
  const id = this.#id;
11393
- if (id !== void 0) {
11394
- const settled = await settleBrowserTeardown(() => this.#client.send("Target.disposeBrowserContext", { browserContextId: id }));
11395
- failure ??= settled;
11444
+ if (id !== void 0) try {
11445
+ await this.#client.send("Target.disposeBrowserContext", { browserContextId: id });
11446
+ this.#disposal = { confirmed: true };
11447
+ } catch (error) {
11448
+ this.#disposal = {
11449
+ confirmed: false,
11450
+ error
11451
+ };
11452
+ failure ??= error;
11396
11453
  }
11397
11454
  if (failure !== void 0) throw failure;
11398
11455
  } finally {
@@ -12277,13 +12334,13 @@ function createMemoryBrowserRunStore() {
12277
12334
  //#endregion
12278
12335
  //#region src/core/BrowserJourneyToolset.ts
12279
12336
  /**
12280
- * Registers the journey tools `record`, `save`, `journeys`, `edit`, `replay`, and `forget` over a toolset
12337
+ * Registers the journey tools `record`, `save`, `journeys`, `edit`, `replay`, `forget`, and `capture` over a toolset
12281
12338
  * and owns the recording and the active replay.
12282
12339
  *
12283
12340
  * @remarks
12284
12341
  * The journey toolset composes the toolset's public members: `perform` reads the view a receipt
12285
12342
  * carries through `look`, `hold` and `emitter` drive the recorder and the replay, `view` converts
12286
- * an edit's `ref` to a target, and `tools` receives the six tools at construction, which refuses
12343
+ * an edit's `ref` to a target, and `tools` receives the journey tools at construction, which refuses
12287
12344
  * with `BROWSER_TOOLSET_RESERVED` when the manager already holds one of the names. Every refusal
12288
12345
  * is a sentence that names the next call, and a reason inside it is a clause without a directive
12289
12346
  * or a final period.
@@ -12299,7 +12356,7 @@ function createMemoryBrowserRunStore() {
12299
12356
  * by the view after the run.
12300
12357
  *
12301
12358
  * `destroy()` aborts the active replay and waits for it to finish, stops a recording without
12302
- * saving it, and removes the six tools the manager still holds under the instances it added.
12359
+ * saving it, and removes the journey tools the manager still holds under the instances it added.
12303
12360
  *
12304
12361
  * @example
12305
12362
  * ```ts
@@ -12340,7 +12397,8 @@ var BrowserJourneyToolset = class {
12340
12397
  this.#create("journeys", this.#journeys.bind(this)),
12341
12398
  this.#create("edit", this.#edit.bind(this)),
12342
12399
  this.#create("replay", this.#replayJourney.bind(this)),
12343
- this.#create("forget", this.#forget.bind(this))
12400
+ this.#create("forget", this.#forget.bind(this)),
12401
+ this.#create("capture", this.#capture.bind(this))
12344
12402
  ]);
12345
12403
  toolset.tools.add(this.#tools);
12346
12404
  }
@@ -12367,6 +12425,22 @@ var BrowserJourneyToolset = class {
12367
12425
  signal.throwIfAborted();
12368
12426
  return handler(args, signal);
12369
12427
  }
12428
+ async #capture(args, signal) {
12429
+ const full = args["full"];
12430
+ if (!isBoolean(full)) throw new BrowserError("The full parameter must be true or false.", "BROWSER_TOOLSET_ARGUMENT");
12431
+ this.#idleReplay();
12432
+ const view = this.#toolset.view;
12433
+ if (!view.trusted) throw new BrowserError("This view is untrusted; capture requires a trusted browser view.", "BROWSER_CAPTURE_UNTRUSTED");
12434
+ if (view.screenshot === void 0) throw new BrowserError("This view cannot capture an image.", "BROWSER_CAPTURE_UNAVAILABLE");
12435
+ const runs = this.#runs;
12436
+ if (runs?.snapshot === void 0) throw new BrowserError("Capture requires a runs store with standalone file storage.", "BROWSER_CAPTURE_UNAVAILABLE");
12437
+ const screenshot = await view.screenshot({
12438
+ format: "png",
12439
+ full
12440
+ });
12441
+ signal.throwIfAborted();
12442
+ return runs.snapshot(screenshot.bytes, { signal });
12443
+ }
12370
12444
  async #record(args, signal) {
12371
12445
  if (this.#readonly) throw new BrowserError(BROWSER_JOURNEY_READONLY_REFUSAL, "BROWSER_JOURNEY_READONLY");
12372
12446
  this.#idleReplay();