@enekesabel/playwright-lite 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -4505,7 +4505,6 @@ Resolved to value: ${printReceived(actual)}`;
4505
4505
  const invalidArguments = "Too many arguments. If you need to pass more than 1 argument to the function wrap them in an object.";
4506
4506
  function assertEvaluationOptions(options) {
4507
4507
  if (options !== void 0 && (typeof options !== "object" || options === null || Array.isArray(options))) throw new Error(invalidArguments);
4508
- if (options?.exposeFunctions === true) throw new Error("Unsupported Playwright option: evaluate.exposeFunctions");
4509
4508
  if (options?.exposeFunctions !== void 0 && typeof options.exposeFunctions !== "boolean") throw new Error("exposeFunctions must be a boolean");
4510
4509
  }
4511
4510
  function assertMaxArguments(count, maximum) {
@@ -4540,12 +4539,12 @@ var AdapterJSHandle = class {
4540
4539
  async evaluate(pageFunction, arg, options) {
4541
4540
  assertMaxArguments(arguments.length, 3);
4542
4541
  assertEvaluationOptions(options);
4543
- return this.evaluation.byValue(pageFunction, typeof pageFunction === "function", arg, this);
4542
+ return this.evaluation.byValue(pageFunction, typeof pageFunction === "function", arg, this, options);
4544
4543
  }
4545
4544
  async evaluateHandle(pageFunction, arg, options) {
4546
4545
  assertMaxArguments(arguments.length, 3);
4547
4546
  assertEvaluationOptions(options);
4548
- return this.evaluation.byHandle(pageFunction, typeof pageFunction === "function", arg, this);
4547
+ return this.evaluation.byHandle(pageFunction, typeof pageFunction === "function", arg, this, options);
4549
4548
  }
4550
4549
  /** Pinned javascript.ts:188 reads the property off the value itself. */
4551
4550
  async getProperty(propertyName) {
@@ -4585,19 +4584,13 @@ var AdapterJSHandle = class {
4585
4584
  return previewValue(this.value);
4586
4585
  }
4587
4586
  };
4588
- /**
4589
- * Mirrors the handle preview pinned 26a9e47 crExecutionContext.ts:123
4590
- * (`renderPreview`) derives from a Chromium remote object: a value that crosses
4591
- * the protocol renders as `String(value)`, and an object renders as V8's
4592
- * `RemoteObject.description`. Handles created inside the document carry no
4593
- * remote object, so the description is reconstructed from the value.
4594
- */
4587
+ /** Mirrors pinned crExecutionContext.ts `renderPreview` for a value with no remote object: `String(value)`, or a name for an object. Also used by `console.ts`'s argument previews. */
4595
4588
  function previewValue(value) {
4596
4589
  if (value === null) return "null";
4597
4590
  if (typeof value === "bigint") return `${value}n`;
4598
4591
  if (typeof value === "function") return String(value);
4599
4592
  if (typeof value !== "object") return Object.is(value, -0) ? "-0" : String(value);
4600
- const tag = Object.prototype.toString.call(value).slice(8, -1);
4593
+ const tag = tagOf(value);
4601
4594
  if (tag === "Date" || tag === "RegExp") return String(value);
4602
4595
  if (tag === "Error") return value.stack || String(value);
4603
4596
  const name = tag === "Object" ? constructorName(value) : tag;
@@ -4605,8 +4598,27 @@ function previewValue(value) {
4605
4598
  if (tag === "Array" || ArrayBuffer.isView(value)) return `${name}(${value.length})`;
4606
4599
  return name;
4607
4600
  }
4601
+ /**
4602
+ * V8 names an object without running page code, verified against real
4603
+ * Chromium: a `Symbol.toStringTag` or `constructor` accessor is never called.
4604
+ * These lookups read descriptors along the prototype chain instead; a Proxy's
4605
+ * traps still run, as nothing in the document can inspect one without them.
4606
+ */
4607
+ function tagOf(value) {
4608
+ const descriptor = findDescriptor(value, Symbol.toStringTag);
4609
+ if (descriptor && !("value" in descriptor)) return "Object";
4610
+ return Object.prototype.toString.call(value).slice(8, -1);
4611
+ }
4608
4612
  function constructorName(value) {
4609
- return value.constructor?.name || "Object";
4613
+ const constructor = findDescriptor(value, "constructor")?.value;
4614
+ const name = typeof constructor === "function" ? findDescriptor(constructor, "name")?.value : void 0;
4615
+ return typeof name === "string" && name || "Object";
4616
+ }
4617
+ function findDescriptor(value, key) {
4618
+ for (let owner = value; owner; owner = Object.getPrototypeOf(owner)) {
4619
+ const descriptor = Object.getOwnPropertyDescriptor(owner, key);
4620
+ if (descriptor) return descriptor;
4621
+ }
4610
4622
  }
4611
4623
  //#endregion
4612
4624
  //#region src/protocolValidation.ts
@@ -4880,7 +4892,7 @@ __export$1(utilityScript_exports, { UtilityScript: () => UtilityScript$1 });
4880
4892
  module$2.exports = __toCommonJS$1(utilityScript_exports);
4881
4893
  var kFunctionBindingPrefix$1 = "__pw_fn_";
4882
4894
  var kBindingsControllerProperty$1 = "__playwright__binding__controller__";
4883
- function isRegExp$3(obj) {
4895
+ function isRegExp$4(obj) {
4884
4896
  try {
4885
4897
  return obj instanceof RegExp || Object.prototype.toString.call(obj) === "[object RegExp]";
4886
4898
  } catch (error) {
@@ -5038,7 +5050,7 @@ ${value.stack}`;
5038
5050
  }
5039
5051
  if (isDate$1(value)) return { d: value.toJSON() };
5040
5052
  if (isURL$1(value)) return { u: value.toJSON() };
5041
- if (isRegExp$3(value)) return { r: {
5053
+ if (isRegExp$4(value)) return { r: {
5042
5054
  p: value.source,
5043
5055
  f: value.flags
5044
5056
  } };
@@ -5092,7 +5104,7 @@ ${value.stack}`;
5092
5104
  id: id2
5093
5105
  };
5094
5106
  }
5095
- if (typeof value === "function" && value.name.startsWith(kFunctionBindingPrefix$1)) return { fn: value.name };
5107
+ if (typeof value === "function" && value.name.startsWith("__pw_fn_")) return { fn: value.name };
5096
5108
  }
5097
5109
  var UtilityScript$1 = class {
5098
5110
  constructor(global, isUnderTest) {
@@ -5263,15 +5275,6 @@ var AdapterElementHandle = class extends AdapterJSHandle {
5263
5275
  assertMaxArguments(arguments.length, 3);
5264
5276
  return this.ownerPage.evaluation.byValue(pageFunction, typeof pageFunction === "function", arg, this.ownerPage.resolveAllWithinElement(this.requireElement(), selector));
5265
5277
  }
5266
- async evaluate(pageFunction, arg, options) {
5267
- assertMaxArguments(arguments.length, 3);
5268
- assertEvaluationOptions(options);
5269
- return this.ownerPage.evaluation.byValue(pageFunction, typeof pageFunction === "function", arg, this.requireElement());
5270
- }
5271
- /** Kept here so releasing a node is attributed to ElementHandle. */
5272
- async dispose() {
5273
- await super.dispose();
5274
- }
5275
5278
  async textContent() {
5276
5279
  return this.requireElement().textContent;
5277
5280
  }
@@ -5352,13 +5355,22 @@ var Evaluation = class {
5352
5355
  get script() {
5353
5356
  return this.utility ??= new LiteUtilityScript(this.page.window, false);
5354
5357
  }
5355
- argument(value) {
5358
+ /**
5359
+ * `exposeFunctions` registers each function nested in `value` as a binding
5360
+ * (pinned client/jsHandle.ts `serializeArgumentWithCallbacks`) instead of
5361
+ * letting the protocol serializer reject it; its generated name survives
5362
+ * the value copy and is what the pinned UtilityScript's own serializer
5363
+ * (`serializeAsCallArgument`) recognizes to re-emit `{ fn }` for the
5364
+ * reconstructed callable the page function receives.
5365
+ */
5366
+ argument(value, exposeFunctions = false) {
5356
5367
  const references = [];
5357
5368
  const protocolValue = serializeValue(value, (candidate) => {
5358
5369
  if (candidate instanceof AdapterJSHandle) {
5359
5370
  references.push(candidate);
5360
5371
  return { h: references.length - 1 };
5361
5372
  }
5373
+ if (exposeFunctions && typeof candidate === "function") return { fn: this.page.bindings.registerEvaluateCallback(this.page.bindingOwner, candidate) };
5362
5374
  return { fallThrough: candidate };
5363
5375
  });
5364
5376
  const copy = parseSerializedValue(protocolValue, references);
@@ -5383,20 +5395,20 @@ var Evaluation = class {
5383
5395
  handleFor(value) {
5384
5396
  return value instanceof this.node ? new AdapterElementHandle(this.page, value) : new AdapterJSHandle(value, this);
5385
5397
  }
5386
- async byValue(expression, isFunction, arg, target) {
5387
- return protocolResult(parseEvaluationResultValue$1(await this.run(expression, isFunction, true, arg, target)));
5398
+ async byValue(expression, isFunction, arg, target, options) {
5399
+ return protocolResult(parseEvaluationResultValue$1(await this.run(expression, isFunction, true, arg, target, options?.exposeFunctions)));
5388
5400
  }
5389
5401
  /**
5390
5402
  * Pinned javascript.ts:249 evaluates with `returnByValue: false`, and the
5391
5403
  * protocol's `awaitPromise` settles a returned promise before it hands back
5392
5404
  * the handle.
5393
5405
  */
5394
- async byHandle(expression, isFunction, arg, target) {
5395
- return this.handleFor(await this.run(expression, isFunction, false, arg, target));
5406
+ async byHandle(expression, isFunction, arg, target, options) {
5407
+ return this.handleFor(await this.run(expression, isFunction, false, arg, target, options?.exposeFunctions));
5396
5408
  }
5397
- async run(expression, isFunction, returnByValue, arg, target) {
5409
+ async run(expression, isFunction, returnByValue, arg, target, exposeFunctions) {
5398
5410
  const normalized = normalizeExpression(String(expression), isFunction);
5399
- const { serialized, handles } = this.argument(arg);
5411
+ const { serialized, handles } = this.argument(arg, exposeFunctions);
5400
5412
  const parameters = [serialized];
5401
5413
  if (target !== void 0) {
5402
5414
  handles.push(target instanceof AdapterJSHandle ? target.valueForEvaluation(this) : target);
@@ -5437,6 +5449,27 @@ var Evaluation = class {
5437
5449
  jsonValue(value) {
5438
5450
  return protocolResult(parseEvaluationResultValue$1(this.script.jsonValue(true, value)));
5439
5451
  }
5452
+ /**
5453
+ * One round trip through the pinned by-value call-argument serializer,
5454
+ * resolving this page's handles to their referenced value. `exposeFunction`
5455
+ * / `exposeBinding` cross their arguments and result this way: there is no
5456
+ * Node/browser split to serialize across, so a binding call takes one hop
5457
+ * (the pinned browser-side `serializeAsCallArgument` alone), not the two
5458
+ * the pinned client and server each take. Unlike `unwrapHandles`, this
5459
+ * skips the protocol-level pass, so a `Window`/`Document`/`Node` argument
5460
+ * still aliases to the pinned `"ref: <Window>"`-style string instead of
5461
+ * losing its identity to the protocol serializer's plain-object walk first.
5462
+ */
5463
+ bindingValue(value) {
5464
+ const handles = [];
5465
+ return parseEvaluationResultValue$1(serializeAsCallArgument$1(value, (candidate) => {
5466
+ if (candidate instanceof AdapterJSHandle) {
5467
+ handles.push(candidate.valueForEvaluation(this));
5468
+ return { h: handles.length - 1 };
5469
+ }
5470
+ return { fallThrough: candidate };
5471
+ }), handles);
5472
+ }
5440
5473
  };
5441
5474
  function protocolResult(value) {
5442
5475
  return parseSerializedValue(serializeValue(value, (value) => ({ fallThrough: value })));
@@ -13396,7 +13429,7 @@ function trimString(input, cap, suffix = "") {
13396
13429
  if (chars.length > cap) return chars.slice(0, cap - suffix.length).join("") + suffix;
13397
13430
  return chars.join("");
13398
13431
  }
13399
- function trimStringWithEllipsis(input, cap) {
13432
+ function trimStringWithEllipsis$1(input, cap) {
13400
13433
  return trimString(input, cap, "…");
13401
13434
  }
13402
13435
  function truncateDataUrl(url) {
@@ -13632,9 +13665,9 @@ var JavaScriptLocatorFactory = class {
13632
13665
  case "visible": return `filter({ visible: ${body === "true" ? "true" : "false"} })`;
13633
13666
  case "role":
13634
13667
  const attrs = [];
13635
- if (isRegExp$2(options.name)) attrs.push(`name: ${this.regexToSourceString(options.name)}`);
13668
+ if (isRegExp$3(options.name)) attrs.push(`name: ${this.regexToSourceString(options.name)}`);
13636
13669
  else if (typeof options.name === "string") attrs.push(`name: ${this.quote(options.name)}`);
13637
- if (isRegExp$2(options.description)) attrs.push(`description: ${this.regexToSourceString(options.description)}`);
13670
+ if (isRegExp$3(options.description)) attrs.push(`description: ${this.regexToSourceString(options.description)}`);
13638
13671
  else if (typeof options.description === "string") attrs.push(`description: ${this.quote(options.description)}`);
13639
13672
  if (options.exact && (typeof options.name === "string" || typeof options.description === "string")) attrs.push(`exact: true`);
13640
13673
  for (const { name, value } of options.attrs) attrs.push(`${name}: ${typeof value === "string" ? this.quote(value) : value}`);
@@ -13663,15 +13696,15 @@ var JavaScriptLocatorFactory = class {
13663
13696
  return normalizeEscapedRegexQuotes(String(re));
13664
13697
  }
13665
13698
  toCallWithExact(method, body, exact) {
13666
- if (isRegExp$2(body)) return `${method}(${this.regexToSourceString(body)})`;
13699
+ if (isRegExp$3(body)) return `${method}(${this.regexToSourceString(body)})`;
13667
13700
  return exact ? `${method}(${this.quote(body)}, { exact: true })` : `${method}(${this.quote(body)})`;
13668
13701
  }
13669
13702
  toHasText(body) {
13670
- if (isRegExp$2(body)) return this.regexToSourceString(body);
13703
+ if (isRegExp$3(body)) return this.regexToSourceString(body);
13671
13704
  return this.quote(body);
13672
13705
  }
13673
13706
  toTestIdValue(value) {
13674
- if (isRegExp$2(value)) return this.regexToSourceString(value);
13707
+ if (isRegExp$3(value)) return this.regexToSourceString(value);
13675
13708
  return this.quote(value);
13676
13709
  }
13677
13710
  quote(text) {
@@ -13694,9 +13727,9 @@ var PythonLocatorFactory = class {
13694
13727
  case "visible": return `filter(visible=${body === "true" ? "True" : "False"})`;
13695
13728
  case "role":
13696
13729
  const attrs = [];
13697
- if (isRegExp$2(options.name)) attrs.push(`name=${this.regexToString(options.name)}`);
13730
+ if (isRegExp$3(options.name)) attrs.push(`name=${this.regexToString(options.name)}`);
13698
13731
  else if (typeof options.name === "string") attrs.push(`name=${this.quote(options.name)}`);
13699
- if (isRegExp$2(options.description)) attrs.push(`description=${this.regexToString(options.description)}`);
13732
+ if (isRegExp$3(options.description)) attrs.push(`description=${this.regexToString(options.description)}`);
13700
13733
  else if (typeof options.description === "string") attrs.push(`description=${this.quote(options.description)}`);
13701
13734
  if (options.exact && (typeof options.name === "string" || typeof options.description === "string")) attrs.push(`exact=True`);
13702
13735
  for (const { name, value } of options.attrs) {
@@ -13730,16 +13763,16 @@ var PythonLocatorFactory = class {
13730
13763
  return `re.compile(r"${normalizeEscapedRegexQuotes(body.source).replace(/\\\//, "/").replace(/"/g, "\\\"")}"${suffix})`;
13731
13764
  }
13732
13765
  toCallWithExact(method, body, exact) {
13733
- if (isRegExp$2(body)) return `${method}(${this.regexToString(body)})`;
13766
+ if (isRegExp$3(body)) return `${method}(${this.regexToString(body)})`;
13734
13767
  if (exact) return `${method}(${this.quote(body)}, exact=True)`;
13735
13768
  return `${method}(${this.quote(body)})`;
13736
13769
  }
13737
13770
  toHasText(body) {
13738
- if (isRegExp$2(body)) return this.regexToString(body);
13771
+ if (isRegExp$3(body)) return this.regexToString(body);
13739
13772
  return `${this.quote(body)}`;
13740
13773
  }
13741
13774
  toTestIdValue(value) {
13742
- if (isRegExp$2(value)) return this.regexToString(value);
13775
+ if (isRegExp$3(value)) return this.regexToString(value);
13743
13776
  return this.quote(value);
13744
13777
  }
13745
13778
  quote(text) {
@@ -13771,9 +13804,9 @@ var JavaLocatorFactory = class {
13771
13804
  case "visible": return `filter(new ${clazz}.FilterOptions().setVisible(${body === "true" ? "true" : "false"}))`;
13772
13805
  case "role":
13773
13806
  const attrs = [];
13774
- if (isRegExp$2(options.name)) attrs.push(`.setName(${this.regexToString(options.name)})`);
13807
+ if (isRegExp$3(options.name)) attrs.push(`.setName(${this.regexToString(options.name)})`);
13775
13808
  else if (typeof options.name === "string") attrs.push(`.setName(${this.quote(options.name)})`);
13776
- if (isRegExp$2(options.description)) attrs.push(`.setDescription(${this.regexToString(options.description)})`);
13809
+ if (isRegExp$3(options.description)) attrs.push(`.setDescription(${this.regexToString(options.description)})`);
13777
13810
  else if (typeof options.description === "string") attrs.push(`.setDescription(${this.quote(options.description)})`);
13778
13811
  if (options.exact && (typeof options.name === "string" || typeof options.description === "string")) attrs.push(`.setExact(true)`);
13779
13812
  for (const { name, value } of options.attrs) attrs.push(`.set${toTitleCase(name)}(${typeof value === "string" ? this.quote(value) : value})`);
@@ -13803,16 +13836,16 @@ var JavaLocatorFactory = class {
13803
13836
  return `Pattern.compile(${this.quote(normalizeEscapedRegexQuotes(body.source))}${suffix})`;
13804
13837
  }
13805
13838
  toCallWithExact(clazz, method, body, exact) {
13806
- if (isRegExp$2(body)) return `${method}(${this.regexToString(body)})`;
13839
+ if (isRegExp$3(body)) return `${method}(${this.regexToString(body)})`;
13807
13840
  if (exact) return `${method}(${this.quote(body)}, new ${clazz}.${toTitleCase(method)}Options().setExact(true))`;
13808
13841
  return `${method}(${this.quote(body)})`;
13809
13842
  }
13810
13843
  toHasText(body) {
13811
- if (isRegExp$2(body)) return this.regexToString(body);
13844
+ if (isRegExp$3(body)) return this.regexToString(body);
13812
13845
  return this.quote(body);
13813
13846
  }
13814
13847
  toTestIdValue(value) {
13815
- if (isRegExp$2(value)) return this.regexToString(value);
13848
+ if (isRegExp$3(value)) return this.regexToString(value);
13816
13849
  return this.quote(value);
13817
13850
  }
13818
13851
  quote(text) {
@@ -13834,9 +13867,9 @@ var CSharpLocatorFactory = class {
13834
13867
  case "visible": return `Filter(new() { Visible = ${body === "true" ? "true" : "false"} })`;
13835
13868
  case "role":
13836
13869
  const attrs = [];
13837
- if (isRegExp$2(options.name)) attrs.push(`NameRegex = ${this.regexToString(options.name)}`);
13870
+ if (isRegExp$3(options.name)) attrs.push(`NameRegex = ${this.regexToString(options.name)}`);
13838
13871
  else if (typeof options.name === "string") attrs.push(`Name = ${this.quote(options.name)}`);
13839
- if (isRegExp$2(options.description)) attrs.push(`DescriptionRegex = ${this.regexToString(options.description)}`);
13872
+ if (isRegExp$3(options.description)) attrs.push(`DescriptionRegex = ${this.regexToString(options.description)}`);
13840
13873
  else if (typeof options.description === "string") attrs.push(`Description = ${this.quote(options.description)}`);
13841
13874
  if (options.exact && (typeof options.name === "string" || typeof options.description === "string")) attrs.push(`Exact = true`);
13842
13875
  for (const { name, value } of options.attrs) attrs.push(`${toTitleCase(name)} = ${typeof value === "string" ? this.quote(value) : value}`);
@@ -13866,20 +13899,20 @@ var CSharpLocatorFactory = class {
13866
13899
  return `new Regex(${this.quote(normalizeEscapedRegexQuotes(body.source))}${suffix})`;
13867
13900
  }
13868
13901
  toCallWithExact(method, body, exact) {
13869
- if (isRegExp$2(body)) return `${method}(${this.regexToString(body)})`;
13902
+ if (isRegExp$3(body)) return `${method}(${this.regexToString(body)})`;
13870
13903
  if (exact) return `${method}(${this.quote(body)}, new() { Exact = true })`;
13871
13904
  return `${method}(${this.quote(body)})`;
13872
13905
  }
13873
13906
  toHasText(body) {
13874
- if (isRegExp$2(body)) return `HasTextRegex = ${this.regexToString(body)}`;
13907
+ if (isRegExp$3(body)) return `HasTextRegex = ${this.regexToString(body)}`;
13875
13908
  return `HasText = ${this.quote(body)}`;
13876
13909
  }
13877
13910
  toTestIdValue(value) {
13878
- if (isRegExp$2(value)) return this.regexToString(value);
13911
+ if (isRegExp$3(value)) return this.regexToString(value);
13879
13912
  return this.quote(value);
13880
13913
  }
13881
13914
  toHasNotText(body) {
13882
- if (isRegExp$2(body)) return `HasNotTextRegex = ${this.regexToString(body)}`;
13915
+ if (isRegExp$3(body)) return `HasNotTextRegex = ${this.regexToString(body)}`;
13883
13916
  return `HasNotText = ${this.quote(body)}`;
13884
13917
  }
13885
13918
  quote(text) {
@@ -13911,7 +13944,7 @@ var generators = {
13911
13944
  csharp: CSharpLocatorFactory,
13912
13945
  jsonl: JsonlLocatorFactory
13913
13946
  };
13914
- function isRegExp$2(obj) {
13947
+ function isRegExp$3(obj) {
13915
13948
  return obj instanceof RegExp;
13916
13949
  }
13917
13950
  function getByAttributeTextSelector(attrName, text, options) {
@@ -18915,7 +18948,7 @@ var InjectedScript = class {
18915
18948
  else attrs.push(` ${name}="${value}"`);
18916
18949
  }
18917
18950
  attrs.sort((a, b) => a.length - b.length);
18918
- const attrText = trimStringWithEllipsis(attrs.join(""), 500);
18951
+ const attrText = trimStringWithEllipsis$1(attrs.join(""), 500);
18919
18952
  if (this._autoClosingTags.has(element.nodeName)) return oneLine(`<${element.nodeName.toLowerCase()}${attrText}/>`);
18920
18953
  const children = element.childNodes;
18921
18954
  let onlyText = false;
@@ -18924,7 +18957,7 @@ var InjectedScript = class {
18924
18957
  for (let i = 0; i < children.length; i++) onlyText = onlyText && children[i].nodeType === Node.TEXT_NODE;
18925
18958
  }
18926
18959
  const text = onlyText ? element.textContent || "" : children.length ? "…" : "";
18927
- return oneLine(`<${element.nodeName.toLowerCase()}${attrText}>${trimStringWithEllipsis(text, 50)}</${element.nodeName.toLowerCase()}>`);
18960
+ return oneLine(`<${element.nodeName.toLowerCase()}${attrText}>${trimStringWithEllipsis$1(text, 50)}</${element.nodeName.toLowerCase()}>`);
18928
18961
  }
18929
18962
  _generateSelectors(elements) {
18930
18963
  this._evaluator.begin();
@@ -19476,6 +19509,997 @@ var AdapterTimeoutError = class extends Error {
19476
19509
  }
19477
19510
  };
19478
19511
  //#endregion
19512
+ //#region src/hostGlobals.ts
19513
+ /**
19514
+ * One instance per window, created by `create` on first use and shared by
19515
+ * every later caller for the same window.
19516
+ */
19517
+ function perWindow(create) {
19518
+ const instances = /* @__PURE__ */ new WeakMap();
19519
+ return (browserWindow) => {
19520
+ let instance = instances.get(browserWindow);
19521
+ if (instance === void 0) instances.set(browserWindow, instance = create(browserWindow));
19522
+ return instance;
19523
+ };
19524
+ }
19525
+ /**
19526
+ * A host function replaced by a callable `Proxy` between `install` and
19527
+ * `restore`.
19528
+ *
19529
+ * The proxy traps only `apply`, so `name`, `length` and
19530
+ * `Function.prototype.toString` keep answering for the original function, and
19531
+ * `this` and the arguments reach it untouched: a call with a receiver the
19532
+ * platform object rejects still throws the same `TypeError`. While nothing
19533
+ * is installed, every proxy this object made forwards calls to its original
19534
+ * untouched, so a Site's wrapper that closed over one keeps working
19535
+ * unobserved.
19536
+ */
19537
+ var WrappedHostFunction = class {
19538
+ member;
19539
+ original;
19540
+ proxy;
19541
+ constructor(member) {
19542
+ this.member = member;
19543
+ }
19544
+ install() {
19545
+ const { holder, name } = this.member;
19546
+ const original = holder[name];
19547
+ const proxy = new Proxy(original, { apply: (target, thisArg, args) => this.proxy !== void 0 ? this.member.intercept(target, thisArg, args) : Reflect.apply(target, thisArg, args) });
19548
+ this.original = original;
19549
+ this.proxy = proxy;
19550
+ holder[name] = proxy;
19551
+ }
19552
+ restore() {
19553
+ const { holder, name } = this.member;
19554
+ if (holder[name] === this.proxy) holder[name] = this.original;
19555
+ this.proxy = void 0;
19556
+ this.original = void 0;
19557
+ }
19558
+ };
19559
+ /**
19560
+ * Host functions replaced together as one subscription, and the reporters
19561
+ * subscribed to them. Every wrapper is installed when the first reporter
19562
+ * subscribes and restored when the last one releases.
19563
+ *
19564
+ * This is the only subscription count: a reporter subscribed n times is
19565
+ * reported to once per call, until its n-th release.
19566
+ */
19567
+ var HostObservation = class {
19568
+ options;
19569
+ wrappers;
19570
+ reporters = /* @__PURE__ */ new Map();
19571
+ constructor(members, options = {}) {
19572
+ this.options = options;
19573
+ this.wrappers = members.map((member) => new WrappedHostFunction(member));
19574
+ }
19575
+ /** Reports to `report` until the returned release is called. The release is idempotent. */
19576
+ subscribe(report) {
19577
+ const first = this.reporters.size === 0;
19578
+ this.reporters.set(report, (this.reporters.get(report) ?? 0) + 1);
19579
+ if (first) for (const wrapper of this.wrappers) wrapper.install();
19580
+ let released = false;
19581
+ return () => {
19582
+ if (released) return;
19583
+ released = true;
19584
+ const held = this.reporters.get(report) - 1;
19585
+ if (held > 0) {
19586
+ this.reporters.set(report, held);
19587
+ return;
19588
+ }
19589
+ this.reporters.delete(report);
19590
+ if (this.reporters.size > 0) return;
19591
+ for (const wrapper of this.wrappers) wrapper.restore();
19592
+ this.options.onLastRelease?.();
19593
+ };
19594
+ }
19595
+ /**
19596
+ * Calls every subscribed reporter once, synchronously, over a snapshot
19597
+ * taken now. A reporter's throw is not caught: it reaches the caller and
19598
+ * skips the reporters after it.
19599
+ */
19600
+ report(...args) {
19601
+ for (const reporter of [...this.reporters.keys()]) reporter(...args);
19602
+ }
19603
+ };
19604
+ //#endregion
19605
+ //#region src/bindings.ts
19606
+ /**
19607
+ * Pinned server/page.ts `_pageBindings`: one registry per window, since the
19608
+ * pinned UtilityScript bundle calls back into a single `window[name]`.
19609
+ */
19610
+ var PageBindings = class {
19611
+ window;
19612
+ bindings = /* @__PURE__ */ new Map();
19613
+ controllerInstalled = false;
19614
+ nextCallbackId = 0;
19615
+ constructor(window) {
19616
+ this.window = window;
19617
+ }
19618
+ /**
19619
+ * Pinned server/page.ts `exposeBinding`'s duplicate-name error. Installs
19620
+ * `window[name]` and returns its idempotent removal, pinned
19621
+ * `removeExposedBinding`: the registration goes, so `name` can be exposed
19622
+ * again, and so does `window[name]` unless the Site has since replaced it.
19623
+ */
19624
+ expose(owner, name, handler) {
19625
+ if (this.bindings.has(name)) throw new Error(`Function "${name}" has been already registered`);
19626
+ const entry = {
19627
+ owner,
19628
+ handler
19629
+ };
19630
+ this.install(name, entry);
19631
+ const holder = this.window;
19632
+ const exposed = (...args) => this.callBinding(name, ...args);
19633
+ holder[name] = exposed;
19634
+ return () => {
19635
+ if (this.bindings.get(name) !== entry) return;
19636
+ this.bindings.delete(name);
19637
+ if (holder[name] === exposed) delete holder[name];
19638
+ };
19639
+ }
19640
+ /**
19641
+ * Registers the callback a function nested in an `evaluate(..., {
19642
+ * exposeFunctions: true })` argument becomes, under a fresh generated name.
19643
+ * Mirrors pinned client/jsHandle.ts `serializeArgumentWithCallbacks` /
19644
+ * `page._exposeEvaluateCallback`: the binding ignores `source` (there is no
19645
+ * caller frame to report), forwards the call's own arguments, and (pinned
19646
+ * `noGlobal: true`) never installs on `window`.
19647
+ */
19648
+ registerEvaluateCallback(owner, fn) {
19649
+ const name = kFunctionBindingPrefix$1 + this.nextCallbackId++;
19650
+ this.install(name, {
19651
+ owner,
19652
+ handler: (_source, ...args) => fn(...args)
19653
+ });
19654
+ return name;
19655
+ }
19656
+ /** Installs the controller the first time anything is registered. */
19657
+ install(name, entry) {
19658
+ if (!this.controllerInstalled) {
19659
+ this.controllerInstalled = true;
19660
+ this.window[kBindingsControllerProperty$1] = { callBinding: (name, ...args) => this.callBinding(name, ...args) };
19661
+ }
19662
+ this.bindings.set(name, entry);
19663
+ }
19664
+ /** Pinned server/page.ts `PageBinding.dispatch`: the result or the thrown
19665
+ * error both cross back through the owning page's by-value round trip
19666
+ * (`serializeError`/`parseError`'s pinned equivalent). */
19667
+ async callBinding(name, ...args) {
19668
+ const entry = this.bindings.get(name);
19669
+ if (!entry) throw new Error(`Function "${name}" is not exposed`);
19670
+ const { owner, handler } = entry;
19671
+ try {
19672
+ const result = await handler(owner.source, ...args.map((arg) => owner.toByValue(arg)));
19673
+ return owner.toByValue(result);
19674
+ } catch (error) {
19675
+ throw owner.toByValue(error);
19676
+ }
19677
+ }
19678
+ };
19679
+ /** The one binding registry of a window, created for its first subscriber. */
19680
+ const bindingsFor = perWindow((browserWindow) => new PageBindings(browserWindow));
19681
+ //#endregion
19682
+ //#region src/network.ts
19683
+ /** Pinned client/events.ts Page events this observation emits. */
19684
+ const NETWORK_EVENTS = [
19685
+ "request",
19686
+ "response",
19687
+ "requestfinished",
19688
+ "requestfailed"
19689
+ ];
19690
+ /** Pinned server/frames.ts `_startNetworkIdleTimer`: the quiet period. */
19691
+ const NETWORK_IDLE_TIMEOUT = 500;
19692
+ /** Pinned server/page.ts `addNetworkRequest`: the recent-request bound. */
19693
+ const REQUEST_LOG_LIMIT = 100;
19694
+ function deferred() {
19695
+ let resolve;
19696
+ return {
19697
+ promise: new Promise((settle) => resolve = settle),
19698
+ resolve
19699
+ };
19700
+ }
19701
+ /** Pinned server/network.ts `stripFragmentFromUrl`. */
19702
+ function stripFragmentFromUrl(url) {
19703
+ if (!url.includes("#")) return url;
19704
+ return url.substring(0, url.indexOf("#"));
19705
+ }
19706
+ /** `XMLHttpRequest.open` uppercases these method names before they go out. */
19707
+ function normalizeXhrMethod(method) {
19708
+ return /^(delete|get|head|options|post|put)$/i.test(method) ? method.toUpperCase() : method;
19709
+ }
19710
+ /** Parses `getAllResponseHeaders()`, whose names the browser has lowercased. */
19711
+ function parseRawHeaders(raw) {
19712
+ const headers = {};
19713
+ for (const line of raw.split("\r\n")) {
19714
+ const separator = line.indexOf(":");
19715
+ if (separator < 0) continue;
19716
+ headers[line.slice(0, separator).trim().toLowerCase()] = line.slice(separator + 1).trim();
19717
+ }
19718
+ return headers;
19719
+ }
19720
+ /**
19721
+ * The bytes an `XMLHttpRequest` kept. A `responseType` of `"json"` or
19722
+ * `"document"` leaves only the value the browser parsed from the body, so the
19723
+ * body itself is gone and reading it reports that instead of inventing bytes.
19724
+ */
19725
+ async function xhrResponseBody(xhr) {
19726
+ if (xhr.responseType === "json" || xhr.responseType === "document") throw new Error(`Response body is not available: the request set responseType "${xhr.responseType}".`);
19727
+ const body = xhr.response;
19728
+ if (typeof body === "string") return new TextEncoder().encode(body);
19729
+ if (body === null) return /* @__PURE__ */ new Uint8Array();
19730
+ if (body instanceof ArrayBuffer) return new Uint8Array(body);
19731
+ if (body instanceof Blob) return new Uint8Array(await body.arrayBuffer());
19732
+ throw new Error(`Response body is not available: the request set responseType "${xhr.responseType}".`);
19733
+ }
19734
+ function headersObject(headers) {
19735
+ const result = {};
19736
+ headers.forEach((value, name) => {
19737
+ result[name.toLowerCase()] = value;
19738
+ });
19739
+ return result;
19740
+ }
19741
+ /**
19742
+ * The request body a `fetch` init or an `XMLHttpRequest.send` argument
19743
+ * carries, in the forms the caller can hand over synchronously. `postData()`
19744
+ * answers without waiting, as the pinned client's does, and the call must be
19745
+ * forwarded in the same turn; a `Blob`, `FormData` or `ReadableStream` body,
19746
+ * and a body carried by a `Request` argument, can only be read
19747
+ * asynchronously, so those report `null`.
19748
+ */
19749
+ function readableBody(body) {
19750
+ if (typeof body === "string") return new TextEncoder().encode(body);
19751
+ if (body instanceof URLSearchParams) return new TextEncoder().encode(body.toString());
19752
+ if (body instanceof ArrayBuffer) return new Uint8Array(body.slice(0));
19753
+ if (ArrayBuffer.isView(body)) return new Uint8Array(body.buffer.slice(body.byteOffset, body.byteOffset + body.byteLength));
19754
+ return null;
19755
+ }
19756
+ var ObservedRequest = class {
19757
+ _init;
19758
+ _response = deferred();
19759
+ _failureText = null;
19760
+ constructor(_init) {
19761
+ this._init = _init;
19762
+ }
19763
+ url() {
19764
+ return stripFragmentFromUrl(this._init.url);
19765
+ }
19766
+ resourceType() {
19767
+ return this._init.resourceType;
19768
+ }
19769
+ method() {
19770
+ return this._init.method;
19771
+ }
19772
+ headers() {
19773
+ return { ...this._init.headers };
19774
+ }
19775
+ async headerValue(name) {
19776
+ return this._init.headers[name.toLowerCase()] ?? null;
19777
+ }
19778
+ postData() {
19779
+ return this._init.postData === null ? null : new TextDecoder().decode(this._init.postData);
19780
+ }
19781
+ postDataBuffer() {
19782
+ return this._init.postData;
19783
+ }
19784
+ /** Pinned client/network.ts Request.postDataJSON. */
19785
+ postDataJSON() {
19786
+ const postData = this.postData();
19787
+ if (!postData) return null;
19788
+ if (this.headers()["content-type"]?.includes("application/x-www-form-urlencoded")) {
19789
+ const entries = {};
19790
+ for (const [key, value] of new URLSearchParams(postData).entries()) entries[key] = value;
19791
+ return entries;
19792
+ }
19793
+ try {
19794
+ return JSON.parse(postData);
19795
+ } catch {
19796
+ throw new Error("POST data is not a valid JSON object: " + postData);
19797
+ }
19798
+ }
19799
+ isNavigationRequest() {
19800
+ return false;
19801
+ }
19802
+ failure() {
19803
+ return this._failureText === null ? null : { errorText: this._failureText };
19804
+ }
19805
+ async response() {
19806
+ return await this._response.promise;
19807
+ }
19808
+ setResponse(response) {
19809
+ this._response.resolve(response);
19810
+ }
19811
+ setFailure(errorText) {
19812
+ this._failureText = errorText;
19813
+ this._response.resolve(null);
19814
+ }
19815
+ };
19816
+ var ObservedResponse = class {
19817
+ _request;
19818
+ _init;
19819
+ _finished = deferred();
19820
+ _body;
19821
+ constructor(_request, _init) {
19822
+ this._request = _request;
19823
+ this._init = _init;
19824
+ }
19825
+ url() {
19826
+ return this._init.url;
19827
+ }
19828
+ status() {
19829
+ return this._init.status;
19830
+ }
19831
+ statusText() {
19832
+ return this._init.statusText;
19833
+ }
19834
+ /** Pinned client/network.ts Response.ok, which also counts status 0. */
19835
+ ok() {
19836
+ return this._init.status === 0 || this._init.status >= 200 && this._init.status <= 299;
19837
+ }
19838
+ headers() {
19839
+ return { ...this._init.headers };
19840
+ }
19841
+ async headerValue(name) {
19842
+ return this._init.headers[name.toLowerCase()] ?? null;
19843
+ }
19844
+ async body() {
19845
+ this._body ??= this._init.readBody();
19846
+ return await this._body;
19847
+ }
19848
+ async text() {
19849
+ return new TextDecoder().decode(await this.body());
19850
+ }
19851
+ async json() {
19852
+ return JSON.parse(await this.text());
19853
+ }
19854
+ async finished() {
19855
+ return await this._finished.promise;
19856
+ }
19857
+ request() {
19858
+ return this._request;
19859
+ }
19860
+ /** Reports that the body has ended, which is what `finished()` waits for. */
19861
+ markFinished() {
19862
+ this._finished.resolve(null);
19863
+ }
19864
+ };
19865
+ /**
19866
+ * The one observation of a window's `fetch` and `XMLHttpRequest`. Every `Page`
19867
+ * created for the same window shares it: a second wrapper would wrap the first
19868
+ * one's proxy, and unsubscribing in the order they were installed would then
19869
+ * leave that proxy behind for good.
19870
+ */
19871
+ const networkObservationFor = perWindow((browserWindow) => new NetworkObservation(browserWindow));
19872
+ /**
19873
+ * Reports the `fetch` and `XMLHttpRequest` calls the document makes as
19874
+ * Playwright's four network events, for as long as something is subscribed.
19875
+ *
19876
+ * Playwright observes requests in the browser process, so it sees every
19877
+ * resource and every realm. This observation replaces `window.fetch` and
19878
+ * `XMLHttpRequest.prototype.open`, `setRequestHeader` and `send`, so it sees
19879
+ * this realm's `fetch` calls made after the wrappers were installed and its
19880
+ * `XMLHttpRequest`s opened after that, and nothing else. A call is intercepted
19881
+ * once and reported to every subscriber.
19882
+ */
19883
+ var NetworkObservation = class {
19884
+ window;
19885
+ /**
19886
+ * The host functions this observation replaces. They are installed and
19887
+ * restored together, so one subscription is one decision about the document.
19888
+ */
19889
+ host;
19890
+ /** What `open` recorded for an `XMLHttpRequest` this observation saw. */
19891
+ openedRequests = /* @__PURE__ */ new WeakMap();
19892
+ /**
19893
+ * The reported requests that count for network idle and have not ended.
19894
+ * Kept while anything is subscribed, so a `networkidle` wait also sees the
19895
+ * requests reported to an earlier subscriber; emptied with the last one.
19896
+ */
19897
+ inflight = /* @__PURE__ */ new Set();
19898
+ constructor(window) {
19899
+ this.window = window;
19900
+ const xhr = window.XMLHttpRequest.prototype;
19901
+ this.host = new HostObservation([
19902
+ {
19903
+ holder: window,
19904
+ name: "fetch",
19905
+ intercept: (original, thisArg, args) => this.observeFetch(original, thisArg, args)
19906
+ },
19907
+ {
19908
+ holder: xhr,
19909
+ name: "open",
19910
+ intercept: (original, thisArg, args) => this.observeOpen(original, thisArg, args)
19911
+ },
19912
+ {
19913
+ holder: xhr,
19914
+ name: "setRequestHeader",
19915
+ intercept: (original, thisArg, args) => this.observeSetRequestHeader(original, thisArg, args)
19916
+ },
19917
+ {
19918
+ holder: xhr,
19919
+ name: "send",
19920
+ intercept: (original, thisArg, args) => this.observeSend(original, thisArg, args)
19921
+ }
19922
+ ], { onLastRelease: () => this.inflight.clear() });
19923
+ }
19924
+ /** Reports to `report` until the returned release is called. */
19925
+ subscribe(report) {
19926
+ return this.host.subscribe(report);
19927
+ }
19928
+ /**
19929
+ * Calls `onIdle` after 500 ms with no observed request in flight, as pinned
19930
+ * server/frames.ts fires `networkidle`. Subscribes until the release is called.
19931
+ */
19932
+ observeIdle(onIdle) {
19933
+ let timer;
19934
+ const stopTimer = () => {
19935
+ this.window.clearTimeout(timer);
19936
+ timer = void 0;
19937
+ };
19938
+ const startTimer = () => {
19939
+ timer = this.window.setTimeout(() => {
19940
+ timer = void 0;
19941
+ onIdle();
19942
+ }, NETWORK_IDLE_TIMEOUT);
19943
+ };
19944
+ const resources = new this.window.PerformanceObserver((list) => {
19945
+ if (!list.getEntriesByType("resource").some((entry) => !["fetch", "xmlhttprequest"].includes(entry.initiatorType)) || timer === void 0) return;
19946
+ stopTimer();
19947
+ startTimer();
19948
+ });
19949
+ const release = this.subscribe(() => {
19950
+ if (this.inflight.size > 0) stopTimer();
19951
+ else if (timer === void 0) startTimer();
19952
+ });
19953
+ resources.observe({ type: "resource" });
19954
+ if (this.inflight.size === 0) startTimer();
19955
+ return () => {
19956
+ resources.disconnect();
19957
+ stopTimer();
19958
+ release();
19959
+ };
19960
+ }
19961
+ emit(event, payload) {
19962
+ if (event === "request") {
19963
+ const request = payload;
19964
+ if (!request.url().endsWith("/favicon.ico")) this.inflight.add(request);
19965
+ } else if (event === "requestfinished" || event === "requestfailed") this.inflight.delete(payload);
19966
+ this.host.report(event, payload);
19967
+ }
19968
+ observeFetch(original, thisArg, args) {
19969
+ let request;
19970
+ try {
19971
+ request = new this.window.Request(args[0], args[1]);
19972
+ } catch {
19973
+ return Reflect.apply(original, thisArg, args);
19974
+ }
19975
+ const postData = readableBody(args[1]?.body);
19976
+ const result = Reflect.apply(original, thisArg, [request]);
19977
+ const observed = new ObservedRequest({
19978
+ url: request.url,
19979
+ method: request.method,
19980
+ headers: headersObject(request.headers),
19981
+ resourceType: "fetch",
19982
+ postData
19983
+ });
19984
+ this.emit("request", observed);
19985
+ this.follow(observed, result);
19986
+ return result;
19987
+ }
19988
+ async follow(request, result) {
19989
+ let response;
19990
+ try {
19991
+ const native = await result;
19992
+ const recording = native.clone();
19993
+ response = new ObservedResponse(request, {
19994
+ url: native.url,
19995
+ status: native.status,
19996
+ statusText: native.statusText,
19997
+ headers: headersObject(native.headers),
19998
+ readBody: async () => new Uint8Array(await recording.arrayBuffer())
19999
+ });
20000
+ } catch (error) {
20001
+ request.setFailure(failureText(error));
20002
+ this.emit("requestfailed", request);
20003
+ return;
20004
+ }
20005
+ request.setResponse(response);
20006
+ this.emit("response", response);
20007
+ try {
20008
+ await response.body();
20009
+ response.markFinished();
20010
+ } catch (error) {
20011
+ request.setFailure(failureText(error));
20012
+ this.emit("requestfailed", request);
20013
+ return;
20014
+ }
20015
+ this.emit("requestfinished", request);
20016
+ }
20017
+ /**
20018
+ * Records what `open` accepted. Once the original has returned, the receiver
20019
+ * is an `XMLHttpRequest` and the URL resolves against the document base;
20020
+ * `open` has just discarded the headers set before it, which is why the
20021
+ * record starts with none.
20022
+ */
20023
+ observeOpen(original, thisArg, args) {
20024
+ const previous = this.openedRequests.get(thisArg)?.sent;
20025
+ previous?.settleIfDone();
20026
+ const result = Reflect.apply(original, thisArg, args);
20027
+ previous?.cancel();
20028
+ this.openedRequests.set(thisArg, {
20029
+ method: normalizeXhrMethod(String(args[0])),
20030
+ url: new URL(String(args[1]), this.window.document.baseURI).href,
20031
+ headers: new this.window.Headers()
20032
+ });
20033
+ return result;
20034
+ }
20035
+ /** Records a header the original accepted, for `Request.headers`. */
20036
+ observeSetRequestHeader(original, thisArg, args) {
20037
+ const result = Reflect.apply(original, thisArg, args);
20038
+ this.openedRequests.get(thisArg)?.headers.append(String(args[0]), String(args[1]));
20039
+ return result;
20040
+ }
20041
+ /**
20042
+ * Reports the request this `send` starts and the events it produces.
20043
+ *
20044
+ * The `request` event is emitted before the original runs: a synchronous
20045
+ * `XMLHttpRequest` delivers its `load` while `send` is still on the stack,
20046
+ * so waiting for the original to return would report the response first.
20047
+ *
20048
+ * A `send` the platform is about to reject with `InvalidStateError`, and one
20049
+ * on an `XMLHttpRequest` opened before the wrappers were installed, is
20050
+ * forwarded untouched and reports nothing, so a call that never starts a
20051
+ * request leaves nothing of this observation behind.
20052
+ */
20053
+ observeSend(original, thisArg, args) {
20054
+ const opened = this.openedRequests.get(thisArg);
20055
+ const xhr = thisArg;
20056
+ if (!opened || opened.sent || xhr.readyState !== xhr.OPENED) return Reflect.apply(original, thisArg, args);
20057
+ const request = new ObservedRequest({
20058
+ url: opened.url,
20059
+ method: opened.method,
20060
+ headers: headersObject(new this.window.Request(this.window.document.baseURI, {
20061
+ method: opened.method,
20062
+ headers: opened.headers
20063
+ }).headers),
20064
+ resourceType: "xhr",
20065
+ postData: opened.method === "GET" || opened.method === "HEAD" ? null : readableBody(args[0])
20066
+ });
20067
+ let response;
20068
+ let ended = false;
20069
+ const body = Promise.withResolvers();
20070
+ body.promise.catch(() => {});
20071
+ const respond = () => {
20072
+ if (!response) {
20073
+ response = new ObservedResponse(request, {
20074
+ url: xhr.responseURL,
20075
+ status: xhr.status,
20076
+ statusText: xhr.statusText,
20077
+ headers: parseRawHeaders(xhr.getAllResponseHeaders()),
20078
+ readBody: () => body.promise
20079
+ });
20080
+ request.setResponse(response);
20081
+ this.emit("response", response);
20082
+ }
20083
+ return response;
20084
+ };
20085
+ const listeners = new AbortController();
20086
+ const fail = (errorText) => {
20087
+ if (ended) return;
20088
+ ended = true;
20089
+ listeners.abort();
20090
+ body.reject(new Error(errorText));
20091
+ request.setFailure(errorText);
20092
+ this.emit("requestfailed", request);
20093
+ };
20094
+ const finish = () => {
20095
+ if (ended) return;
20096
+ ended = true;
20097
+ listeners.abort();
20098
+ body.resolve(xhrResponseBody(xhr));
20099
+ respond().markFinished();
20100
+ this.emit("requestfinished", request);
20101
+ };
20102
+ opened.sent = {
20103
+ settleIfDone: () => {
20104
+ if (xhr.readyState !== xhr.DONE) return;
20105
+ if (xhr.status === 0) fail("XMLHttpRequest: error");
20106
+ else finish();
20107
+ },
20108
+ cancel: () => fail("XMLHttpRequest: abort")
20109
+ };
20110
+ const { signal } = listeners;
20111
+ xhr.addEventListener("readystatechange", () => {
20112
+ if (xhr.readyState === xhr.HEADERS_RECEIVED) respond();
20113
+ if (xhr.readyState === xhr.DONE && xhr.status !== 0) finish();
20114
+ }, { signal });
20115
+ xhr.addEventListener("load", finish, { signal });
20116
+ for (const event of [
20117
+ "error",
20118
+ "timeout",
20119
+ "abort"
20120
+ ]) xhr.addEventListener(event, () => fail(`XMLHttpRequest: ${event}`), { signal });
20121
+ this.emit("request", request);
20122
+ try {
20123
+ return Reflect.apply(original, thisArg, args);
20124
+ } catch (error) {
20125
+ fail(failureText(error));
20126
+ throw error;
20127
+ }
20128
+ }
20129
+ };
20130
+ /**
20131
+ * Appends to a `Page`'s recent-request log, bounded as pinned server/page.ts
20132
+ * `addNetworkRequest` bounds it with `ensureArrayLimit`: once the log passes
20133
+ * the limit, its oldest tenth is dropped.
20134
+ */
20135
+ function recordRequest(log, request) {
20136
+ log.push(request);
20137
+ if (log.length > REQUEST_LOG_LIMIT) log.splice(0, REQUEST_LOG_LIMIT / 10);
20138
+ }
20139
+ /**
20140
+ * Pinned client/page.ts: a string or `RegExp` matches the observed URL, a
20141
+ * function is awaited with the `Request`/`Response` itself.
20142
+ */
20143
+ function networkPredicate(urlOrPredicate, urlMatches) {
20144
+ return async (payload) => {
20145
+ const target = payload;
20146
+ if (typeof urlOrPredicate === "function") return await urlOrPredicate(target);
20147
+ return urlMatches(target.url(), urlOrPredicate);
20148
+ };
20149
+ }
20150
+ /** Pinned client/page.ts `trimUrl`, for the line the timeout reports. */
20151
+ function logLineFor(event, match) {
20152
+ if (isRegExp$2(match)) return `waiting for ${event} /${trimStringWithEllipsis(match.source, 50)}/${match.flags}`;
20153
+ if (typeof match === "string") return `waiting for ${event} "${trimStringWithEllipsis(match, 50)}"`;
20154
+ return `waiting for event "${event}"`;
20155
+ }
20156
+ function isRegExp$2(value) {
20157
+ return value instanceof RegExp || Object.prototype.toString.call(value) === "[object RegExp]";
20158
+ }
20159
+ /** Pinned isomorphic/stringUtils.ts `trimStringWithEllipsis`. */
20160
+ function trimStringWithEllipsis(input, cap) {
20161
+ if (input.length <= cap) return input;
20162
+ const chars = [...input];
20163
+ if (chars.length > cap) return chars.slice(0, cap - 1).join("") + "…";
20164
+ return chars.join("");
20165
+ }
20166
+ /**
20167
+ * Playwright's `failure().errorText` is the browser's `net::ERR_*` code. A
20168
+ * page only sees what `fetch` rejected with: a `TypeError` whose message the
20169
+ * browser chooses, or the reason an `AbortSignal` carried. An
20170
+ * `XMLHttpRequest` carries no error at all, so it reports the name of the
20171
+ * event that ended it instead.
20172
+ */
20173
+ function failureText(error) {
20174
+ return error instanceof Error ? `${error.name}: ${error.message}` : String(error);
20175
+ }
20176
+ //#endregion
20177
+ //#region src/dialog.ts
20178
+ /** One `alert`/`confirm`/`prompt` call, built per subscribed page around a settlement its siblings share. */
20179
+ var Dialog = class {
20180
+ _type;
20181
+ _message;
20182
+ _defaultValue;
20183
+ box;
20184
+ _page;
20185
+ constructor(_type, _message, _defaultValue, box, _page) {
20186
+ this._type = _type;
20187
+ this._message = _message;
20188
+ this._defaultValue = _defaultValue;
20189
+ this.box = box;
20190
+ this._page = _page;
20191
+ }
20192
+ type() {
20193
+ return this._type;
20194
+ }
20195
+ message() {
20196
+ return this._message;
20197
+ }
20198
+ defaultValue() {
20199
+ return this._defaultValue;
20200
+ }
20201
+ page() {
20202
+ return this._page();
20203
+ }
20204
+ async accept(promptText) {
20205
+ if (promptText !== void 0) try {
20206
+ promptText = validateString(promptText, "promptText");
20207
+ } catch (error) {
20208
+ throw new TypeError(`dialog.accept: ${error.message}`, { cause: error });
20209
+ }
20210
+ this.settle(true, promptText);
20211
+ }
20212
+ async dismiss() {
20213
+ this.settle(false, void 0);
20214
+ }
20215
+ settle(accepted, value) {
20216
+ if (this.box.result !== void 0) throw new Error(`dialog.${accepted ? "accept" : "dismiss"}: Cannot ${accepted ? "accept" : "dismiss"} dialog which is already handled!`);
20217
+ this.box.result = {
20218
+ accepted,
20219
+ value
20220
+ };
20221
+ }
20222
+ };
20223
+ /** A `message`/`default` argument, coerced the way the platform coerces it: omitted becomes `""`, a `Symbol` throws. */
20224
+ function stringArg(value) {
20225
+ return value === void 0 ? "" : `${value}`;
20226
+ }
20227
+ /** `undefined` for `alert`, the accepted boolean for `confirm`, the accepted string (or `null`) for `prompt`. */
20228
+ function nativeReturnValue(type, defaultValue, result) {
20229
+ if (type === "alert") return void 0;
20230
+ if (type === "confirm") return result.accepted;
20231
+ if (!result.accepted) return null;
20232
+ return result.value ?? defaultValue;
20233
+ }
20234
+ /** The one observation of a window's dialogs, shared by every `Page` created for it (see `networkObservationFor`). */
20235
+ const dialogObservationFor = perWindow((browserWindow) => new DialogObservation(browserWindow));
20236
+ const DIALOG_HOST_MEMBERS = [
20237
+ "alert",
20238
+ "confirm",
20239
+ "prompt"
20240
+ ];
20241
+ /** Reports `window.alert`/`confirm`/`prompt` as Playwright's `dialog` event for as long as something is subscribed. */
20242
+ var DialogObservation = class {
20243
+ window;
20244
+ host;
20245
+ constructor(window) {
20246
+ this.window = window;
20247
+ const holder = window;
20248
+ this.host = new HostObservation(DIALOG_HOST_MEMBERS.map((type) => ({
20249
+ holder,
20250
+ name: type,
20251
+ intercept: (original, thisArg, args) => this.observe(type, original, thisArg, args)
20252
+ })));
20253
+ }
20254
+ /** Reports to `report` until the returned release is called. */
20255
+ subscribe(report) {
20256
+ return this.host.subscribe(report);
20257
+ }
20258
+ observe(type, original, thisArg, args) {
20259
+ if (thisArg !== void 0 && thisArg !== null && thisArg !== this.window) return Reflect.apply(original, thisArg, args);
20260
+ const message = stringArg(args[0]);
20261
+ const defaultValue = type === "prompt" ? stringArg(args[1]) : "";
20262
+ const box = {};
20263
+ this.host.report(type, message, defaultValue, box);
20264
+ box.result ??= { accepted: false };
20265
+ return nativeReturnValue(type, defaultValue, box.result);
20266
+ }
20267
+ };
20268
+ //#endregion
20269
+ //#region src/console.ts
20270
+ /** Pinned client/events.ts Page event this observation emits. */
20271
+ const CONSOLE_EVENT = "console";
20272
+ /**
20273
+ * Builds a `ConsoleMessage`, this package's own `JSHandle`s cast to
20274
+ * Playwright's public `JSHandle` the way `createPage`'s `Page` types every
20275
+ * other adapter handle it returns. Built per subscribing `Page`, not inside
20276
+ * `ConsoleObservation`: `args()` must hold that page's own handles, which
20277
+ * only its `Evaluation` can create, and `page()` must return that same page.
20278
+ */
20279
+ function buildConsoleMessage(page, type, args, text, location, timestamp) {
20280
+ return {
20281
+ args: () => args,
20282
+ location: () => ({
20283
+ url: location.url,
20284
+ line: location.lineNumber,
20285
+ column: location.columnNumber,
20286
+ lineNumber: location.lineNumber,
20287
+ columnNumber: location.columnNumber
20288
+ }),
20289
+ page: () => page,
20290
+ text: () => text,
20291
+ timestamp: () => timestamp,
20292
+ type: () => type,
20293
+ worker: () => null
20294
+ };
20295
+ }
20296
+ /**
20297
+ * Pinned server/chromium/crPage.ts `_onConsoleAPI`: the native `console.*`
20298
+ * method name each pinned `ConsoleMessage.type()` value comes from. `time` is
20299
+ * never wrapped: the browser's own `Runtime.consoleAPICalled` never fires for
20300
+ * it, only for `timeLog` and the paired `timeEnd`. `timeLog` reports as type
20301
+ * `log`, verified against real Chromium; the pinned public `type()` union has
20302
+ * no separate `timeLog` value.
20303
+ */
20304
+ const CONSOLE_METHOD_TYPES = {
20305
+ log: "log",
20306
+ debug: "debug",
20307
+ info: "info",
20308
+ error: "error",
20309
+ warn: "warning",
20310
+ dir: "dir",
20311
+ dirxml: "dirxml",
20312
+ table: "table",
20313
+ trace: "trace",
20314
+ clear: "clear",
20315
+ group: "startGroup",
20316
+ groupCollapsed: "startGroupCollapsed",
20317
+ groupEnd: "endGroup",
20318
+ assert: "assert",
20319
+ profile: "profile",
20320
+ profileEnd: "profileEnd",
20321
+ count: "count",
20322
+ timeEnd: "timeEnd",
20323
+ timeLog: "log"
20324
+ };
20325
+ /**
20326
+ * Verified against real Chromium, not documented in the pinned TypeScript
20327
+ * source (the CDP protocol formats console text in the browser process,
20328
+ * which the pinned client only receives already formatted): `group()`,
20329
+ * `groupCollapsed()`, `groupEnd()`, `clear()` and `trace()` always report,
20330
+ * falling back to `console.<method>` when called with no message argument
20331
+ * (`groupEnd()`/`clear()` take none at all, so this is their only text).
20332
+ * That fallback is the call's one argument, in `args()` as well as `text()`,
20333
+ * as V8's `reportCallWithDefaultArgument` passes it. `assert` falls back the
20334
+ * same way in `observe`, once its condition argument is dropped.
20335
+ */
20336
+ const FALLBACK_TEXT_METHODS = /* @__PURE__ */ new Set([
20337
+ "group",
20338
+ "groupCollapsed",
20339
+ "groupEnd",
20340
+ "clear",
20341
+ "trace",
20342
+ "assert"
20343
+ ]);
20344
+ /**
20345
+ * Verified against real Chromium: a bare call (no arguments) to any of these
20346
+ * is never reported at all, unlike the `FALLBACK_TEXT_METHODS` above.
20347
+ */
20348
+ const SUPPRESSED_WHEN_EMPTY = /* @__PURE__ */ new Set([
20349
+ "log",
20350
+ "debug",
20351
+ "info",
20352
+ "error",
20353
+ "warn",
20354
+ "dir",
20355
+ "dirxml",
20356
+ "table"
20357
+ ]);
20358
+ const STACK_FRAME = /\(?([^()\s]+):(\d+):(\d+)\)?$/;
20359
+ /**
20360
+ * Pinned server/chromium/crProtocolHelper.ts `stackTraceToLocation`: the
20361
+ * console call's own frame, not the interceptor's. No CDP stack trace is
20362
+ * available inside the document, so this is reconstructed from a captured
20363
+ * `Error` stack and named best-effort in the ledger: unlike the pinned
20364
+ * CDP-sourced location, engine stack-formatting differences and inlining can
20365
+ * shift or drop a frame.
20366
+ */
20367
+ function captureLocation() {
20368
+ const stack = (/* @__PURE__ */ new Error()).stack;
20369
+ if (!stack) return {
20370
+ url: "",
20371
+ lineNumber: 0,
20372
+ columnNumber: 0
20373
+ };
20374
+ for (const line of stack.split("\n").slice(5)) {
20375
+ const match = STACK_FRAME.exec(line.trim());
20376
+ if (!match) continue;
20377
+ const lineNumber = Number(match[2]) - 1;
20378
+ const columnNumber = Number(match[3]) - 1;
20379
+ if (Number.isNaN(lineNumber) || Number.isNaN(columnNumber)) continue;
20380
+ return {
20381
+ url: match[1],
20382
+ lineNumber,
20383
+ columnNumber
20384
+ };
20385
+ }
20386
+ return {
20387
+ url: "",
20388
+ lineNumber: 0,
20389
+ columnNumber: 0
20390
+ };
20391
+ }
20392
+ /**
20393
+ * A shallow, best-effort stand-in for the pinned CDP object-preview
20394
+ * algorithm: a string renders bare; a plain object or array lists its own
20395
+ * entries one level deep, each through `previewValue`; anything else is
20396
+ * `previewValue` itself. This follows this package's own `JSHandle`
20397
+ * description, not V8's preview: no truncation, no sparse-array markers, and
20398
+ * a class instance passed directly lists its own members (`{a: 1}`) where
20399
+ * Playwright prints its constructor name (`Foo`).
20400
+ *
20401
+ * Like V8's preview, verified against real Chromium, it never calls the
20402
+ * Site's code: entries are read from property descriptors, an accessor
20403
+ * renders as `undefined` without being called, and a setter-only property
20404
+ * is left out.
20405
+ */
20406
+ function formatConsoleArg(value) {
20407
+ if (typeof value === "string") return value;
20408
+ if (Array.isArray(value)) return `[${Array.from({ length: value.length }, (_, index) => previewEntry(Object.getOwnPropertyDescriptor(value, index))).join(", ")}]`;
20409
+ if (value !== null && typeof value === "object" && tagOf(value) === "Object") return `{${Object.keys(value).map((key) => [key, Object.getOwnPropertyDescriptor(value, key)]).filter(([, descriptor]) => descriptor && !isSetterOnly(descriptor)).map(([key, descriptor]) => `${key}: ${previewEntry(descriptor)}`).join(", ")}}`;
20410
+ return previewValue(value);
20411
+ }
20412
+ /** An array hole renders empty, as `Array.prototype.join` renders it. */
20413
+ function previewEntry(descriptor) {
20414
+ if (!descriptor) return "";
20415
+ return "value" in descriptor ? previewValue(descriptor.value) : "undefined";
20416
+ }
20417
+ function isSetterOnly(descriptor) {
20418
+ return !("value" in descriptor) && !descriptor.get;
20419
+ }
20420
+ /**
20421
+ * Pinned client/consoleMessage.ts `text()`: argument previews joined by a
20422
+ * space. The preview never calls Site code, but a Proxy's traps can still
20423
+ * throw; that argument then renders as `Object` rather than failing the
20424
+ * report.
20425
+ */
20426
+ function formatConsoleText(args) {
20427
+ return args.map((arg) => {
20428
+ try {
20429
+ return formatConsoleArg(arg);
20430
+ } catch {
20431
+ return "Object";
20432
+ }
20433
+ }).join(" ");
20434
+ }
20435
+ /** The one observation of a window's `console`, shared by every `Page` of it. */
20436
+ const consoleObservationFor = perWindow((browserWindow) => new ConsoleObservation(browserWindow));
20437
+ /**
20438
+ * Reports the document's `console.*` calls as Playwright's `console` event,
20439
+ * for as long as something is subscribed. Every wrapped method is installed
20440
+ * and restored together as one subscription.
20441
+ */
20442
+ var ConsoleObservation = class {
20443
+ window;
20444
+ host;
20445
+ constructor(window) {
20446
+ this.window = window;
20447
+ const holder = window.console;
20448
+ this.host = new HostObservation(Object.entries(CONSOLE_METHOD_TYPES).filter(([method]) => typeof holder[method] === "function").map(([method, type]) => ({
20449
+ holder,
20450
+ name: method,
20451
+ intercept: (original, thisArg, args) => this.observe(type, method, original, thisArg, args)
20452
+ })));
20453
+ }
20454
+ /** Reports to `report` until the returned release is called. */
20455
+ subscribe(report) {
20456
+ return this.host.subscribe(report);
20457
+ }
20458
+ /** Guards one report against the reentrancy `observe` documents. */
20459
+ emitting = false;
20460
+ /**
20461
+ * Pinned `console.assert`: reports only when the asserted condition is
20462
+ * falsy, and drops the condition from the reported arguments. Every
20463
+ * synthesized-fallback and bare-call-suppression rule is verified against
20464
+ * real Chromium; see `FALLBACK_TEXT_METHODS`/`SUPPRESSED_WHEN_EMPTY`.
20465
+ *
20466
+ * Observing never changes the Site's call: it returns the native method's
20467
+ * own result, and nothing that fails while building or emitting the report
20468
+ * reaches the caller; such a report is dropped instead.
20469
+ *
20470
+ * A subscriber runs inside this document, unlike the pinned client's
20471
+ * Node-side listener, so one that itself calls a wrapped method (including
20472
+ * indirectly, through this observation's own listener-failure logging)
20473
+ * would otherwise re-enter this method without end; `emitting` guards one
20474
+ * report against that, forwarding to the original method regardless.
20475
+ */
20476
+ observe(type, method, original, thisArg, args) {
20477
+ const result = Reflect.apply(original, thisArg, args);
20478
+ if (this.emitting) return result;
20479
+ this.emitting = true;
20480
+ try {
20481
+ if (method === "assert") {
20482
+ if (args[0]) return result;
20483
+ args = args.slice(1);
20484
+ }
20485
+ if (args.length === 0) {
20486
+ if (SUPPRESSED_WHEN_EMPTY.has(method)) return result;
20487
+ if (FALLBACK_TEXT_METHODS.has(method)) args = [`console.${method}`];
20488
+ }
20489
+ this.host.report({
20490
+ type,
20491
+ args,
20492
+ text: formatConsoleText(args),
20493
+ location: captureLocation(),
20494
+ timestamp: Date.now()
20495
+ });
20496
+ } catch {} finally {
20497
+ this.emitting = false;
20498
+ }
20499
+ return result;
20500
+ }
20501
+ };
20502
+ //#endregion
19479
20503
  //#region src/inputFiles.ts
19480
20504
  /**
19481
20505
  * Pinned 26a9e47 client/elementHandle.ts converts payloads before resolving the
@@ -20161,6 +21185,41 @@ const CURRENT_DOCUMENT_WAIT_POLL_DELAY = 20;
20161
21185
  /** Cross-realm brand symbol used to identify this package's Page instances. */
20162
21186
  const PAGE_BRAND = Symbol.for("playwright-lite:page");
20163
21187
  const PAGE_BRAND_TOKEN = Object.freeze({});
21188
+ /**
21189
+ * One page's use of a host source. `start` subscribes this page and returns
21190
+ * the release; the feed holds a subscription while a listener wants the
21191
+ * source, and another for good once `retain` is called. For a host observation,
21192
+ * `start` always subscribes the page's one stable reporter, which the
21193
+ * observation counts once per call, so holding both never reports a call twice.
21194
+ */
21195
+ var PageFeed = class {
21196
+ start;
21197
+ stopListening;
21198
+ retained = false;
21199
+ constructor(start) {
21200
+ this.start = start;
21201
+ }
21202
+ /** Starts or stops the listened subscription to match `wanted`. */
21203
+ listen(wanted) {
21204
+ if (wanted === (this.stopListening !== void 0)) return;
21205
+ if (wanted) {
21206
+ this.stopListening = this.start();
21207
+ return;
21208
+ }
21209
+ this.stopListening();
21210
+ this.stopListening = void 0;
21211
+ }
21212
+ /**
21213
+ * Holds a subscription that is never released, as the pinned dispatcher
21214
+ * keeps the subscription a log read adds: a log nobody observes cannot be
21215
+ * filled later.
21216
+ */
21217
+ retain() {
21218
+ if (this.retained) return;
21219
+ this.retained = true;
21220
+ this.start();
21221
+ }
21222
+ };
20164
21223
  var PageImpl = class PageImpl {
20165
21224
  testIdAttribute;
20166
21225
  [PAGE_BRAND] = PAGE_BRAND_TOKEN;
@@ -20180,6 +21239,35 @@ var PageImpl = class PageImpl {
20180
21239
  };
20181
21240
  defaultTimeout;
20182
21241
  defaultNavigationTimeout;
21242
+ listeners = /* @__PURE__ */ new Map();
21243
+ pendingListeners = /* @__PURE__ */ new Map();
21244
+ network;
21245
+ dialogs;
21246
+ /** Pinned server/page.ts `_pageBindings`, shared per window like `network`. */
21247
+ bindings;
21248
+ /** This page's identity and by-value round trip for its own bindings. */
21249
+ bindingOwner = {
21250
+ source: {
21251
+ page: this,
21252
+ frame: this
21253
+ },
21254
+ toByValue: (value) => this.evaluation.bindingValue(value)
21255
+ };
21256
+ /** Pinned server/page.ts keeps the recent requests per page, not per realm. */
21257
+ requestLog = [];
21258
+ documentObservers = /* @__PURE__ */ new Set();
21259
+ unobserveDocument;
21260
+ pageErrorsBuffer = [];
21261
+ consoleObservation;
21262
+ consoleMessagesBuffer = [];
21263
+ /**
21264
+ * This page's feed from each host source, started while a listener wants
21265
+ * it and, for the ones with a log, for good once the log has been read.
21266
+ */
21267
+ navigationFeed;
21268
+ networkFeed;
21269
+ dialogFeed;
21270
+ consoleFeed;
20183
21271
  constructor(browserWindow, testIdAttribute = DEFAULT_TEST_ID_ATTRIBUTE) {
20184
21272
  this.testIdAttribute = testIdAttribute;
20185
21273
  this.window = browserWindow;
@@ -20188,6 +21276,28 @@ var PageImpl = class PageImpl {
20188
21276
  this.evaluation = new Evaluation(this);
20189
21277
  this.localStorage = new PageWebStorage(this, "local");
20190
21278
  this.sessionStorage = new PageWebStorage(this, "session");
21279
+ this.network = networkObservationFor(browserWindow);
21280
+ this.dialogs = dialogObservationFor(browserWindow);
21281
+ this.bindings = bindingsFor(browserWindow);
21282
+ this.consoleObservation = consoleObservationFor(browserWindow);
21283
+ this.navigationFeed = new PageFeed(() => this.observeNavigation());
21284
+ const reportNetwork = (event, payload) => {
21285
+ if (event === "request") recordRequest(this.requestLog, payload);
21286
+ this.emit(event, payload);
21287
+ };
21288
+ this.networkFeed = new PageFeed(() => this.network.subscribe(reportNetwork));
21289
+ const reportDialog = (type, message, defaultValue, box) => {
21290
+ this.emit("dialog", new Dialog(type, message, defaultValue, box, () => this));
21291
+ };
21292
+ this.dialogFeed = new PageFeed(() => this.dialogs.subscribe(reportDialog));
21293
+ const reportConsole = (call) => {
21294
+ const message = buildConsoleMessage(this, call.type, call.args.map((arg) => this.evaluation.handleFor(arg)), call.text, call.location, call.timestamp);
21295
+ this.consoleMessagesBuffer.push(message);
21296
+ ensureArrayLimit(this.consoleMessagesBuffer, 200);
21297
+ this.emit(CONSOLE_EVENT, message);
21298
+ };
21299
+ this.consoleFeed = new PageFeed(() => this.consoleObservation.subscribe(reportConsole));
21300
+ this.startPageErrorCollection();
20191
21301
  }
20192
21302
  get injected() {
20193
21303
  const testIdAttributeName = this.testIdAttribute;
@@ -20769,10 +21879,278 @@ var PageImpl = class PageImpl {
20769
21879
  mainFrame() {
20770
21880
  return this;
20771
21881
  }
21882
+ /**
21883
+ * Pinned client/eventEmitter.ts is a Node EventEmitter: any event name is
21884
+ * accepted, `once` unsubscribes before it fires, and `removeListener` drops
21885
+ * the most recently added copy of a listener.
21886
+ */
21887
+ on(event, listener) {
21888
+ return this.addListener(event, listener);
21889
+ }
21890
+ addListener(event, listener) {
21891
+ return this.subscribe(event, {
21892
+ listener,
21893
+ once: false
21894
+ }, false);
21895
+ }
21896
+ prependListener(event, listener) {
21897
+ return this.subscribe(event, {
21898
+ listener,
21899
+ once: false
21900
+ }, true);
21901
+ }
21902
+ once(event, listener) {
21903
+ return this.subscribe(event, {
21904
+ listener,
21905
+ once: true
21906
+ }, false);
21907
+ }
21908
+ off(event, listener) {
21909
+ return this.removeListener(event, listener);
21910
+ }
21911
+ removeListener(event, listener) {
21912
+ const entry = this.listeners.get(event)?.findLast((entry) => entry.listener === listener);
21913
+ if (entry) this.unsubscribe(event, entry);
21914
+ return this;
21915
+ }
21916
+ /**
21917
+ * Pinned client/eventEmitter.ts: without options the listeners are dropped
21918
+ * synchronously. With options, `wait` awaits the listener promises still
21919
+ * pending at removal and rethrows the first listener error, `ignoreErrors`
21920
+ * swallows those errors, and `default` removes without waiting. The pinned
21921
+ * emitter replaces its rejection handler for good; here the choice applies
21922
+ * to the listeners pending at removal, and later failures are logged again.
21923
+ */
21924
+ removeAllListeners(event, options) {
21925
+ if (event === void 0) this.listeners.clear();
21926
+ else this.listeners.delete(event);
21927
+ this.observeHost();
21928
+ if (!options) return this;
21929
+ const pending = event === void 0 ? [...this.pendingListeners.values()].flatMap((set) => [...set]) : [...this.pendingListeners.get(event) ?? []];
21930
+ return this.settlePendingListeners(pending, options);
21931
+ }
21932
+ async settlePendingListeners(pending, options) {
21933
+ rejectUnsupportedOptions("removeAllListeners", options, ["behavior"]);
21934
+ const behavior = options.behavior ?? "default";
21935
+ if (![
21936
+ "wait",
21937
+ "ignoreErrors",
21938
+ "default"
21939
+ ].includes(behavior)) throw new TypeError("behavior: expected one of (wait|ignoreErrors|default)");
21940
+ if (behavior === "default") return;
21941
+ const errors = [];
21942
+ for (const listener of pending) listener.onError = behavior === "wait" ? (error) => errors.push(error) : () => {};
21943
+ if (behavior !== "wait") return;
21944
+ await Promise.all(pending.map((listener) => listener.settled));
21945
+ if (errors.length) throw errors[0];
21946
+ }
21947
+ /**
21948
+ * Pinned client/page.ts `_waitForEvent`: a predicate or options argument,
21949
+ * the action timeout, and the listener removed however the wait ends. There
21950
+ * is no browser process here, so no crash or close rejects the wait.
21951
+ */
21952
+ async waitForEvent(event, optionsOrPredicate = {}) {
21953
+ const options = typeof optionsOrPredicate === "function" ? { predicate: optionsOrPredicate } : optionsOrPredicate;
21954
+ rejectUnsupportedOptions("waitForEvent", options, [
21955
+ "predicate",
21956
+ "signal",
21957
+ "timeout"
21958
+ ]);
21959
+ return await this.waitForPageEvent(event, options, "page.waitForEvent", `waiting for event "${event}"`);
21960
+ }
21961
+ /**
21962
+ * Pinned client/page.ts `waitForRequest`: a string or `RegExp` is matched
21963
+ * with the same `urlMatches` `waitForURL` uses, and a function is awaited as
21964
+ * a predicate on the `Request` itself.
21965
+ */
21966
+ async waitForRequest(urlOrPredicate, options = {}) {
21967
+ rejectUnsupportedOptions("waitForRequest", options, ["signal", "timeout"]);
21968
+ return await this.waitForPageEvent("request", {
21969
+ ...options,
21970
+ predicate: networkPredicate(urlOrPredicate, urlMatches)
21971
+ }, "page.waitForRequest", logLineFor("request", urlOrPredicate));
21972
+ }
21973
+ /** Pinned client/page.ts `waitForResponse`, matched like `waitForRequest`. */
21974
+ async waitForResponse(urlOrPredicate, options = {}) {
21975
+ rejectUnsupportedOptions("waitForResponse", options, ["signal", "timeout"]);
21976
+ return await this.waitForPageEvent("response", {
21977
+ ...options,
21978
+ predicate: networkPredicate(urlOrPredicate, urlMatches)
21979
+ }, "page.waitForResponse", logLineFor("response", urlOrPredicate));
21980
+ }
21981
+ /**
21982
+ * Pinned client/page.ts `requests`, which also starts the `request`
21983
+ * subscription so the log keeps filling once it has been read.
21984
+ */
21985
+ async requests() {
21986
+ this.networkFeed.retain();
21987
+ return [...this.requestLog];
21988
+ }
21989
+ async waitForPageEvent(event, options, apiName, logLine) {
21990
+ const timeout = this.resolveTimeout(options.timeout, DEFAULT_ACTION_TIMEOUT);
21991
+ const { predicate, signal } = options;
21992
+ return withAbortPrefix(apiName, () => new Promise((resolve, reject) => {
21993
+ if (signal?.aborted) throw actionAborted(signal, false);
21994
+ let timer;
21995
+ const finish = (done) => {
21996
+ this.removeListener(event, listener);
21997
+ signal?.removeEventListener("abort", onAbort);
21998
+ this.window.clearTimeout(timer);
21999
+ done();
22000
+ };
22001
+ const listener = async (payload) => {
22002
+ try {
22003
+ if (predicate && !await predicate(payload)) return;
22004
+ finish(() => resolve(payload));
22005
+ } catch (error) {
22006
+ finish(() => reject(error));
22007
+ }
22008
+ };
22009
+ const onAbort = () => finish(() => reject(actionAborted(signal, true)));
22010
+ this.addListener(event, listener);
22011
+ signal?.addEventListener("abort", onAbort, { once: true });
22012
+ if (timeout > 0) timer = this.window.setTimeout(() => finish(() => reject(new AdapterTimeoutError(`${apiName}: Timeout ${timeout}ms exceeded while ${logLine}`))), timeout);
22013
+ }));
22014
+ }
22015
+ subscribe(event, entry, prepend) {
22016
+ let entries = this.listeners.get(event);
22017
+ if (!entries) this.listeners.set(event, entries = []);
22018
+ if (prepend) entries.unshift(entry);
22019
+ else entries.push(entry);
22020
+ this.observeHost();
22021
+ return this;
22022
+ }
22023
+ unsubscribe(event, entry) {
22024
+ const entries = this.listeners.get(event);
22025
+ const index = entries?.indexOf(entry) ?? -1;
22026
+ if (index !== -1) entries.splice(index, 1);
22027
+ this.observeHost();
22028
+ }
22029
+ /**
22030
+ * Playwright raises a throwing or rejecting listener as an unhandled
22031
+ * exception in Node. Here the listener runs inside the page, where escaping
22032
+ * would report it as another page error, so it is logged instead. Listeners
22033
+ * are not awaited, as in the pinned emitter; a returned thenable stays
22034
+ * pending for `removeAllListeners` to settle.
22035
+ */
22036
+ emit(event, payload) {
22037
+ const log = (error) => this.window.console.error(`page.on("${event}"): listener failed`, error);
22038
+ for (const entry of [...this.listeners.get(event) ?? []]) {
22039
+ if (entry.once) this.unsubscribe(event, entry);
22040
+ try {
22041
+ const result = entry.listener(payload);
22042
+ if (typeof result?.then === "function") this.trackPendingListener(event, Promise.resolve(result), log);
22043
+ } catch (error) {
22044
+ log(error);
22045
+ }
22046
+ }
22047
+ }
22048
+ trackPendingListener(event, promise, onError) {
22049
+ let set = this.pendingListeners.get(event);
22050
+ if (!set) this.pendingListeners.set(event, set = /* @__PURE__ */ new Set());
22051
+ const pending = {
22052
+ settled: promise,
22053
+ onError
22054
+ };
22055
+ pending.settled = promise.catch((error) => pending.onError(error)).finally(() => set.delete(pending));
22056
+ set.add(pending);
22057
+ }
22058
+ /**
22059
+ * `framenavigated` is observed on the host only while a listener for it
22060
+ * exists, so the page leaves no trace once it is unsubscribed. Page errors
22061
+ * are collected from page creation regardless of `pageerror` subscribers;
22062
+ * see `startPageErrorCollection`.
22063
+ */
22064
+ observeHost() {
22065
+ this.navigationFeed.listen(this.isListened("framenavigated"));
22066
+ this.networkFeed.listen(this.isListened(...NETWORK_EVENTS));
22067
+ this.dialogFeed.listen(this.isListened("dialog"));
22068
+ this.consoleFeed.listen(this.isListened(CONSOLE_EVENT));
22069
+ }
22070
+ isListened(...events) {
22071
+ return events.some((event) => (this.listeners.get(event)?.length ?? 0) > 0);
22072
+ }
22073
+ /**
22074
+ * Playwright receives same-document navigations as a browser push
22075
+ * (`Page.navigatedWithinDocument`); here they come from the current-document
22076
+ * observation `waitForURL` uses. Ceiling: the event can arrive up to one
22077
+ * poll tick late, and URL changes within one tick collapse into one event
22078
+ * (none when the URL is back at its previous value).
22079
+ */
22080
+ observeNavigation() {
22081
+ let url = this.window.location.href;
22082
+ return this.observeDocument(() => {
22083
+ if (this.window.location.href === url) return;
22084
+ url = this.window.location.href;
22085
+ this.emit("framenavigated", this);
22086
+ });
22087
+ }
22088
+ /**
22089
+ * Pinned server/page.ts registers its `error`/`unhandledrejection`
22090
+ * listeners for the page's whole lifetime, not only while a `pageerror`
22091
+ * consumer is subscribed: `pageErrors()` must see errors raised before any
22092
+ * listener existed. These are page-scoped `window` listeners, not a patch
22093
+ * of a host global, so the last-resort rule for patching globals does not
22094
+ * apply. This package has no `Page` dispose/close lifecycle yet (see
22095
+ * `close` in the ledger), so there is nothing to remove them on; they live
22096
+ * for the window's lifetime, same as the page itself.
22097
+ */
22098
+ startPageErrorCollection() {
22099
+ const onError = (event) => this.addPageError(pageError(event.error));
22100
+ const onRejection = (event) => this.addPageError(pageError(event.reason));
22101
+ this.window.addEventListener("error", onError);
22102
+ this.window.addEventListener("unhandledrejection", onRejection);
22103
+ }
22104
+ /**
22105
+ * Pinned server/page.ts `addPageError`: push, then trim to the pinned
22106
+ * limit, then emit. `emit` is a no-op without `pageerror` subscribers.
22107
+ */
22108
+ addPageError(error) {
22109
+ this.pageErrorsBuffer.push(error);
22110
+ ensureArrayLimit(this.pageErrorsBuffer, 200);
22111
+ this.emit("pageerror", error);
22112
+ }
20772
22113
  /** Mirrors pinned Page.title by reading the controlled document title. */
20773
22114
  async title() {
20774
22115
  return this.document.title;
20775
22116
  }
22117
+ /**
22118
+ * Pinned Page.pageErrors: up to the last 200 uncaught errors, wrapped the
22119
+ * same way as the `pageerror` event payload. Pinned server/page.ts marks
22120
+ * the buffer at the last cross-document navigation and, without `filter:
22121
+ * "all"`, returns only errors after that mark; this single-document
22122
+ * adapter never crosses documents within one page's lifetime (a `goto` to
22123
+ * another document replaces it), so no mark is ever set and both `filter`
22124
+ * values return the same errors, in order, since the page was created.
22125
+ */
22126
+ async pageErrors(options) {
22127
+ rejectUnsupportedOptions("pageErrors", options, ["filter"]);
22128
+ validateHistoryFilter(options?.filter);
22129
+ return this.pageErrorsBuffer.slice();
22130
+ }
22131
+ /** Pinned Page.clearPageErrors: empties the buffer `pageErrors()` reads. */
22132
+ async clearPageErrors() {
22133
+ this.pageErrorsBuffer.length = 0;
22134
+ }
22135
+ /**
22136
+ * Pinned Page.consoleMessages: up to the last 200 `console.*` calls,
22137
+ * wrapped the same way as the `console` event payload. Starts the
22138
+ * `console` subscription so the buffer keeps filling once it has been
22139
+ * read, like `requests()`. Reads the same `since-navigation` equivalence
22140
+ * as `pageErrors`: this single-document adapter never crosses documents
22141
+ * within one page's lifetime, so both `filter` values return the same
22142
+ * messages, in order, since the subscription began.
22143
+ */
22144
+ async consoleMessages(options) {
22145
+ rejectUnsupportedOptions("consoleMessages", options, ["filter"]);
22146
+ validateHistoryFilter(options?.filter);
22147
+ this.consoleFeed.retain();
22148
+ return this.consoleMessagesBuffer.slice();
22149
+ }
22150
+ /** Pinned Page.clearConsoleMessages: empties the buffer `consoleMessages()` reads. */
22151
+ async clearConsoleMessages() {
22152
+ this.consoleMessagesBuffer.length = 0;
22153
+ }
20776
22154
  setDefaultTimeout(timeout) {
20777
22155
  this.defaultTimeout = validateTimeout(timeout, "Default timeout");
20778
22156
  }
@@ -20914,13 +22292,13 @@ var PageImpl = class PageImpl {
20914
22292
  * navigation. Location supplies the browser-side navigation here.
20915
22293
  * Full-document navigation ends this execution; it never resolves with a
20916
22294
  * fabricated Response or destination-ready result in the old document.
20917
- * Relative URLs use document.baseURI. Custom referer, AbortSignal, and
20918
- * networkidle are unsupported and rejected before navigation starts.
22295
+ * Relative URLs use document.baseURI. Custom referer and AbortSignal are
22296
+ * unsupported and rejected before navigation starts. networkidle is
22297
+ * observed from the call on, over the document's fetch and XMLHttpRequest.
20919
22298
  */
20920
22299
  async goto(url, options = {}) {
20921
22300
  for (const [key, value] of Object.entries(options)) if (!["timeout", "waitUntil"].includes(key) && value !== void 0) throw new Error(`Unsupported Playwright option: goto.${key}`);
20922
22301
  const waitUntil = verifyLoadState("waitUntil", options.waitUntil === void 0 ? "load" : options.waitUntil);
20923
- if (waitUntil === "networkidle") throw new Error(`Unsupported waitUntil value: ${waitUntil}`);
20924
22302
  const timeout = this.resolveTimeout(options.timeout, DEFAULT_NAVIGATION_TIMEOUT, true);
20925
22303
  if (typeof url !== "string") throw new Error("goto URL must be a string");
20926
22304
  let target;
@@ -20940,8 +22318,10 @@ var PageImpl = class PageImpl {
20940
22318
  const sameDocument = target.href.includes("#") && target.href.split("#", 1)[0] === current.href.split("#", 1)[0];
20941
22319
  return new Promise((resolve, reject) => {
20942
22320
  let timer;
22321
+ const loadState = this.watchLoadState(waitUntil, () => check());
20943
22322
  const settle = (error) => {
20944
22323
  this.window.clearTimeout(timer);
22324
+ loadState.release();
20945
22325
  this.window.removeEventListener("hashchange", check);
20946
22326
  this.window.removeEventListener("load", check);
20947
22327
  this.document.removeEventListener("readystatechange", check);
@@ -20950,7 +22330,7 @@ var PageImpl = class PageImpl {
20950
22330
  };
20951
22331
  const check = () => {
20952
22332
  if (!sameDocument || this.window.location.href !== target.href) return;
20953
- if (waitUntil === "commit" || this.document.readyState === "complete" || waitUntil === "domcontentloaded" && this.document.readyState === "interactive") settle();
22333
+ if (loadState.reached()) settle();
20954
22334
  };
20955
22335
  if (sameDocument) {
20956
22336
  this.window.addEventListener("hashchange", check);
@@ -20973,7 +22353,7 @@ var PageImpl = class PageImpl {
20973
22353
  * fabricating a cross-document result.
20974
22354
  */
20975
22355
  async waitForLoadState(state = "load", options = {}) {
20976
- const waitUntil = this.currentDocumentLoadState("state", state);
22356
+ const waitUntil = verifyLoadState("state", state);
20977
22357
  rejectUnsupportedOptions("waitForLoadState", options, ["signal", "timeout"]);
20978
22358
  assertCurrentDocumentWaitTimeout("waitForLoadState", options.timeout);
20979
22359
  await this.waitForCurrentDocument("page.waitForLoadState", waitUntil, void 0, options);
@@ -20991,7 +22371,7 @@ var PageImpl = class PageImpl {
20991
22371
  "waitUntil"
20992
22372
  ]);
20993
22373
  assertCurrentDocumentWaitTimeout("waitForURL", options.timeout);
20994
- const waitUntil = this.currentDocumentLoadState("waitUntil", options.waitUntil ?? "load");
22374
+ const waitUntil = verifyLoadState("waitUntil", options.waitUntil ?? "load");
20995
22375
  await this.waitForCurrentDocument("page.waitForURL", waitUntil, url, options);
20996
22376
  }
20997
22377
  /**
@@ -21041,13 +22421,45 @@ var PageImpl = class PageImpl {
21041
22421
  async evaluate(pageFunction, arg, options) {
21042
22422
  assertMaxArguments(arguments.length, 3);
21043
22423
  assertEvaluationOptions(options);
21044
- return this._evaluateExpression(pageFunction, typeof pageFunction === "function", arg);
22424
+ return this._evaluateExpression(pageFunction, typeof pageFunction === "function", arg, options);
21045
22425
  }
21046
22426
  /** Keeps the result in the document, referenced by a handle. */
21047
22427
  async evaluateHandle(pageFunction, arg, options) {
21048
22428
  assertMaxArguments(arguments.length, 3);
21049
22429
  assertEvaluationOptions(options);
21050
- return this.evaluation.byHandle(pageFunction, typeof pageFunction === "function", arg);
22430
+ return this.evaluation.byHandle(pageFunction, typeof pageFunction === "function", arg, void 0, options);
22431
+ }
22432
+ /** Pinned client/page.ts `exposeFunction`: defines `name` on `window`,
22433
+ * forwarding the pinned by-value round trip both ways. */
22434
+ async exposeFunction(name, callback) {
22435
+ return this.installBinding("page.exposeFunction", name, (_source, ...args) => callback(...args));
22436
+ }
22437
+ /** Pinned client/page.ts `exposeBinding` / server/page.ts `exposeBinding`:
22438
+ * defines `name` on `window` with the pinned duplicate-name error; the
22439
+ * callback receives `{ page, frame: page }` as `source` (no `context`: this
22440
+ * package has no `BrowserContext`). */
22441
+ async exposeBinding(name, callback) {
22442
+ return this.installBinding("page.exposeBinding", name, callback);
22443
+ }
22444
+ /**
22445
+ * Pinned client methods prefix a thrown error with their own API name. The
22446
+ * returned `Disposable`'s `dispose()` (and `Symbol.asyncDispose`) removes
22447
+ * the binding, per pinned server/page.ts `PageBinding.dispose`.
22448
+ */
22449
+ async installBinding(apiName, name, callback) {
22450
+ let remove;
22451
+ try {
22452
+ remove = this.bindings.expose(this.bindingOwner, name, callback);
22453
+ } catch (error) {
22454
+ const result = asError(error);
22455
+ result.message = `${apiName}: ${result.message}`;
22456
+ throw result;
22457
+ }
22458
+ const dispose = async () => remove();
22459
+ return {
22460
+ dispose,
22461
+ [Symbol.asyncDispose]: dispose
22462
+ };
21051
22463
  }
21052
22464
  /** Evaluates through the pinned Playwright UtilityScript. */
21053
22465
  async $eval(selector, callback, arg) {
@@ -21067,8 +22479,8 @@ var PageImpl = class PageImpl {
21067
22479
  await this.wait(timeout);
21068
22480
  }
21069
22481
  /** Evaluates through the pinned Playwright UtilityScript. */
21070
- async _evaluateExpression(expression, isFunction, arg) {
21071
- return this.evaluation.byValue(expression, isFunction, arg);
22482
+ async _evaluateExpression(expression, isFunction, arg, options) {
22483
+ return this.evaluation.byValue(expression, isFunction, arg, void 0, options);
21072
22484
  }
21073
22485
  /**
21074
22486
  * Polls a predicate in the controlled document until it returns a
@@ -21240,10 +22652,11 @@ var PageImpl = class PageImpl {
21240
22652
  const signal = options.signal;
21241
22653
  await withAbortPrefix(apiName, async () => {
21242
22654
  let urlMatched = url === void 0;
22655
+ const loadState = this.watchLoadState(waitUntil);
21243
22656
  const observation = await this.observeCurrentDocument(() => {
21244
22657
  if (!urlMatched) urlMatched = urlMatches(this.window.location.href, url);
21245
- return urlMatched && this.currentDocumentHasLoadState(waitUntil);
21246
- }, timeout, signal);
22658
+ return urlMatched && loadState.reached();
22659
+ }, timeout, signal).finally(loadState.release);
21247
22660
  if ("completed" in observation) return;
21248
22661
  if ("aborted" in observation) throw observation.aborted;
21249
22662
  if ("error" in observation) throw observation.error;
@@ -21255,15 +22668,11 @@ var PageImpl = class PageImpl {
21255
22668
  return new Promise((resolve) => {
21256
22669
  let settled = false;
21257
22670
  let timeoutId;
21258
- let pollId;
22671
+ let unobserve = () => {};
21259
22672
  const cleanup = () => {
21260
- this.window.removeEventListener("hashchange", checkDocument);
21261
- this.window.removeEventListener("popstate", checkDocument);
21262
- this.window.removeEventListener("load", checkDocument);
21263
- this.document.removeEventListener("readystatechange", checkDocument);
22673
+ unobserve();
21264
22674
  signal?.removeEventListener("abort", onAbort);
21265
22675
  if (timeoutId !== void 0) this.window.clearTimeout(timeoutId);
21266
- if (pollId !== void 0) this.window.clearTimeout(pollId);
21267
22676
  };
21268
22677
  const settle = (result) => {
21269
22678
  if (settled) return;
@@ -21274,25 +22683,16 @@ var PageImpl = class PageImpl {
21274
22683
  const runCheck = () => {
21275
22684
  try {
21276
22685
  if (check()) settle({ completed: true });
21277
- return true;
21278
22686
  } catch (error) {
21279
22687
  settle({ error });
21280
- return false;
21281
22688
  }
21282
22689
  };
21283
22690
  const onAbort = () => {
21284
22691
  runCheck();
21285
22692
  if (!settled) settle({ aborted: actionAborted(signal, true) });
21286
22693
  };
21287
- const schedulePoll = () => {
21288
- if (!settled && pollId === void 0) pollId = this.window.setTimeout(() => {
21289
- pollId = void 0;
21290
- checkDocument();
21291
- }, CURRENT_DOCUMENT_WAIT_POLL_DELAY);
21292
- };
21293
22694
  const checkDocument = () => {
21294
- if (settled) return;
21295
- if (runCheck()) schedulePoll();
22695
+ if (!settled) runCheck();
21296
22696
  };
21297
22697
  const onTimeout = () => {
21298
22698
  runCheck();
@@ -21303,22 +22703,63 @@ var PageImpl = class PageImpl {
21303
22703
  return;
21304
22704
  }
21305
22705
  signal?.addEventListener("abort", onAbort, { once: true });
21306
- this.window.addEventListener("hashchange", checkDocument);
21307
- this.window.addEventListener("popstate", checkDocument);
21308
- this.window.addEventListener("load", checkDocument);
21309
- this.document.addEventListener("readystatechange", checkDocument);
22706
+ unobserve = this.observeDocument(checkDocument);
21310
22707
  if (timeout > 0) timeoutId = this.window.setTimeout(onTimeout, timeout);
21311
22708
  checkDocument();
21312
22709
  });
21313
22710
  }
21314
- currentDocumentLoadState(name, state) {
21315
- const waitUntil = verifyLoadState(name, state);
21316
- if (waitUntil === "networkidle") throw new Error(`Unsupported ${name} value: ${waitUntil}`);
21317
- return waitUntil;
22711
+ /**
22712
+ * One set of `hashchange`/`popstate`/`load`/`readystatechange` listeners
22713
+ * plus one 20 ms poll, shared by every current-document consumer
22714
+ * (`waitForURL`, `waitForLoadState`, `expect(page).toHaveURL`,
22715
+ * `expect(page).toHaveTitle`, `framenavigated`) and running only while at
22716
+ * least one observes.
22717
+ */
22718
+ observeDocument(observer) {
22719
+ this.documentObservers.add(observer);
22720
+ if (this.documentObservers.size === 1) {
22721
+ const notify = () => {
22722
+ for (const observer of [...this.documentObservers]) observer();
22723
+ };
22724
+ const pollId = this.window.setInterval(notify, CURRENT_DOCUMENT_WAIT_POLL_DELAY);
22725
+ this.window.addEventListener("hashchange", notify);
22726
+ this.window.addEventListener("popstate", notify);
22727
+ this.window.addEventListener("load", notify);
22728
+ this.document.addEventListener("readystatechange", notify);
22729
+ this.unobserveDocument = () => {
22730
+ this.window.clearInterval(pollId);
22731
+ this.window.removeEventListener("hashchange", notify);
22732
+ this.window.removeEventListener("popstate", notify);
22733
+ this.window.removeEventListener("load", notify);
22734
+ this.document.removeEventListener("readystatechange", notify);
22735
+ };
22736
+ }
22737
+ return () => {
22738
+ if (!this.documentObservers.delete(observer) || this.documentObservers.size) return;
22739
+ this.unobserveDocument();
22740
+ this.unobserveDocument = void 0;
22741
+ };
21318
22742
  }
21319
- currentDocumentHasLoadState(waitUntil) {
21320
- if (waitUntil === "commit") return true;
21321
- return this.document.readyState === "complete" || waitUntil === "domcontentloaded" && this.document.readyState === "interactive";
22743
+ /**
22744
+ * Whether the document reached `waitUntil`. `networkidle` subscribes to the
22745
+ * network observation until `release`, and calls `onIdle` once reached;
22746
+ * the other states are read from the document's ready state.
22747
+ */
22748
+ watchLoadState(waitUntil, onIdle = () => {}) {
22749
+ if (waitUntil === "networkidle") {
22750
+ let idle = false;
22751
+ return {
22752
+ reached: () => idle,
22753
+ release: this.network.observeIdle(() => {
22754
+ idle = true;
22755
+ onIdle();
22756
+ })
22757
+ };
22758
+ }
22759
+ return {
22760
+ reached: () => waitUntil === "commit" || this.document.readyState === "complete" || waitUntil === "domcontentloaded" && this.document.readyState === "interactive",
22761
+ release: () => {}
22762
+ };
21322
22763
  }
21323
22764
  createActionDeadline(timeout) {
21324
22765
  const effectiveTimeout = this.resolveTimeout(timeout, DEFAULT_ACTION_TIMEOUT);
@@ -21407,10 +22848,10 @@ var PageImpl = class PageImpl {
21407
22848
  }, true, (element) => this.injectedAriaSnapshot(element, options));
21408
22849
  }
21409
22850
  async locatorEvaluate(selector, label, pageFunction, arg, options) {
21410
- return this.evaluation.byValue(pageFunction, typeof pageFunction === "function", arg, await this.locatorEvaluationTarget(selector, label, options));
22851
+ return this.evaluation.byValue(pageFunction, typeof pageFunction === "function", arg, await this.locatorEvaluationTarget(selector, label, options), options);
21411
22852
  }
21412
22853
  async locatorEvaluateHandle(selector, label, pageFunction, arg, options) {
21413
- return this.evaluation.byHandle(pageFunction, typeof pageFunction === "function", arg, await this.locatorEvaluationTarget(selector, label, options));
22854
+ return this.evaluation.byHandle(pageFunction, typeof pageFunction === "function", arg, await this.locatorEvaluationTarget(selector, label, options), options);
21414
22855
  }
21415
22856
  /** Pinned locator.ts resolves the element before it evaluates against it. */
21416
22857
  async locatorEvaluationTarget(selector, label, options) {
@@ -22321,6 +23762,39 @@ function actionPoint(element, position, browserWindow) {
22321
23762
  function asError(error) {
22322
23763
  return error instanceof Error ? error : new Error(String(error));
22323
23764
  }
23765
+ /**
23766
+ * Pinned crProtocolHelper.ts `exceptionToError` receives a thrown non-Error
23767
+ * value as its protocol description (the class name of an object, otherwise
23768
+ * `String(value)`), splits it at the first `:` into name and message with
23769
+ * `splitErrorMessage`, has no stack for it, and takes the name from the
23770
+ * value's own `name` property when the protocol preview shows one. A thrown
23771
+ * Error already is the error Playwright would rebuild from its name, message
23772
+ * and stack.
23773
+ */
23774
+ function pageError(thrown) {
23775
+ if (thrown instanceof Error) return thrown;
23776
+ const description = thrown !== null && typeof thrown === "object" ? thrown.constructor?.name ?? "Object" : String(thrown);
23777
+ const separator = description.indexOf(":");
23778
+ const error = new Error(separator !== -1 && separator + 2 <= description.length ? description.slice(separator + 2) : description);
23779
+ const named = thrown !== null && typeof thrown === "object" && Object.hasOwn(thrown, "name") ? thrown.name : void 0;
23780
+ error.name = named !== void 0 ? typeof named === "object" || typeof named === "function" ? "Error" : String(named) : separator !== -1 ? description.slice(0, separator) : "";
23781
+ error.stack = "";
23782
+ return error;
23783
+ }
23784
+ /**
23785
+ * Pinned server/page.ts `ensureArrayLimit`: once an array exceeds `limit`,
23786
+ * drop the oldest tenth rather than trimming on every push. The pinned
23787
+ * function returns the spliced-off elements; nothing here reads that
23788
+ * return value, so this copy returns void.
23789
+ */
23790
+ function ensureArrayLimit(array, limit) {
23791
+ if (array.length > limit) array.splice(0, limit / 10);
23792
+ }
23793
+ /** Shared by `pageErrors` and `consoleMessages`, whose `filter` is identical. */
23794
+ function validateHistoryFilter(value) {
23795
+ if (value === void 0 || value === "all" || value === "since-navigation") return;
23796
+ throw new TypeError("filter: expected one of (all|since-navigation)");
23797
+ }
22324
23798
  function isRetryableActionError(error) {
22325
23799
  const message = asError(error).message;
22326
23800
  return message.startsWith("No elements found for locator") || message === "Element is not connected" || message.startsWith("Element is not ") || message === "Element is outside of the viewport" || message.startsWith("Element does not receive pointer events");