@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/README.md +328 -158
- package/dist/index.d.mts +71 -2
- package/dist/index.mjs +1571 -97
- package/package.json +2 -2
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 =
|
|
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
|
-
|
|
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$
|
|
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$
|
|
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(
|
|
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
|
-
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
13703
|
+
if (isRegExp$3(body)) return this.regexToSourceString(body);
|
|
13671
13704
|
return this.quote(body);
|
|
13672
13705
|
}
|
|
13673
13706
|
toTestIdValue(value) {
|
|
13674
|
-
if (isRegExp$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
13771
|
+
if (isRegExp$3(body)) return this.regexToString(body);
|
|
13739
13772
|
return `${this.quote(body)}`;
|
|
13740
13773
|
}
|
|
13741
13774
|
toTestIdValue(value) {
|
|
13742
|
-
if (isRegExp$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
13844
|
+
if (isRegExp$3(body)) return this.regexToString(body);
|
|
13812
13845
|
return this.quote(body);
|
|
13813
13846
|
}
|
|
13814
13847
|
toTestIdValue(value) {
|
|
13815
|
-
if (isRegExp$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
13911
|
+
if (isRegExp$3(value)) return this.regexToString(value);
|
|
13879
13912
|
return this.quote(value);
|
|
13880
13913
|
}
|
|
13881
13914
|
toHasNotText(body) {
|
|
13882
|
-
if (isRegExp$
|
|
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$
|
|
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
|
|
20918
|
-
*
|
|
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 (
|
|
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 =
|
|
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 =
|
|
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 &&
|
|
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
|
|
22671
|
+
let unobserve = () => {};
|
|
21259
22672
|
const cleanup = () => {
|
|
21260
|
-
|
|
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)
|
|
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.
|
|
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
|
-
|
|
21315
|
-
|
|
21316
|
-
|
|
21317
|
-
|
|
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
|
-
|
|
21320
|
-
|
|
21321
|
-
|
|
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");
|