@toolstackhq/cdpwright 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assert/expect.d.ts +1 -1
- package/dist/assert/expect.js +1 -1
- package/dist/{chunk-PDUS6BDZ.js → chunk-EIAXMGD5.js} +586 -50
- package/dist/chunk-EIAXMGD5.js.map +1 -0
- package/dist/cli.js +601 -73
- package/dist/cli.js.map +1 -1
- package/dist/{expect-CLal_AQd.d.ts → expect-Cd5SNFuF.d.ts} +111 -7
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-PDUS6BDZ.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -419,27 +419,77 @@ function serializeShadowDomHelpers() {
|
|
|
419
419
|
// src/core/Locator.ts
|
|
420
420
|
var Locator = class {
|
|
421
421
|
frame;
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
constructor(frame, selector, options = {}) {
|
|
422
|
+
query;
|
|
423
|
+
constructor(frame, query) {
|
|
425
424
|
this.frame = frame;
|
|
426
|
-
this.
|
|
427
|
-
|
|
425
|
+
this.query = query;
|
|
426
|
+
}
|
|
427
|
+
getEvents() {
|
|
428
|
+
return this.frame.getEvents();
|
|
429
|
+
}
|
|
430
|
+
getFrameId() {
|
|
431
|
+
return this.frame.id;
|
|
432
|
+
}
|
|
433
|
+
describe() {
|
|
434
|
+
switch (this.query.kind) {
|
|
435
|
+
case "selector":
|
|
436
|
+
return this.query.selector;
|
|
437
|
+
case "text":
|
|
438
|
+
return typeof this.query.text === "string" ? `text=${JSON.stringify(this.query.text)}` : `text=${this.query.text.toString()}`;
|
|
439
|
+
case "role":
|
|
440
|
+
return `role=${this.query.role}${this.query.options?.name ? ` name=${typeof this.query.options.name === "string" ? JSON.stringify(this.query.options.name) : this.query.options.name.toString()}` : ""}`;
|
|
441
|
+
}
|
|
428
442
|
}
|
|
429
443
|
async click(options = {}) {
|
|
430
|
-
return this.frame.
|
|
444
|
+
return this.frame.clickLocator(this.query, { ...this.queryTimeoutOptions(), ...options });
|
|
431
445
|
}
|
|
432
446
|
async dblclick(options = {}) {
|
|
433
|
-
return this.frame.
|
|
447
|
+
return this.frame.dblclickLocator(this.query, { ...this.queryTimeoutOptions(), ...options });
|
|
434
448
|
}
|
|
435
449
|
async type(text, options = {}) {
|
|
436
|
-
return this.frame.
|
|
450
|
+
return this.frame.typeLocator(this.query, text, { ...this.queryTimeoutOptions(), ...options });
|
|
437
451
|
}
|
|
438
452
|
async exists() {
|
|
439
|
-
return this.frame.
|
|
453
|
+
return this.frame.existsLocator(this.query);
|
|
454
|
+
}
|
|
455
|
+
async isVisible() {
|
|
456
|
+
return this.frame.isVisibleLocator(this.query);
|
|
457
|
+
}
|
|
458
|
+
async isEnabled() {
|
|
459
|
+
return this.frame.isEnabledLocator(this.query);
|
|
460
|
+
}
|
|
461
|
+
async isChecked() {
|
|
462
|
+
return this.frame.isCheckedLocator(this.query);
|
|
440
463
|
}
|
|
441
464
|
async text() {
|
|
442
|
-
return this.frame.
|
|
465
|
+
return this.frame.textLocator(this.query);
|
|
466
|
+
}
|
|
467
|
+
async value() {
|
|
468
|
+
return this.frame.valueLocator(this.query);
|
|
469
|
+
}
|
|
470
|
+
async attribute(name) {
|
|
471
|
+
return this.frame.attributeLocator(this.query, name);
|
|
472
|
+
}
|
|
473
|
+
async classes() {
|
|
474
|
+
return this.frame.classesLocator(this.query);
|
|
475
|
+
}
|
|
476
|
+
async css(property) {
|
|
477
|
+
return this.frame.cssLocator(this.query, property);
|
|
478
|
+
}
|
|
479
|
+
async hasFocus() {
|
|
480
|
+
return this.frame.hasFocusLocator(this.query);
|
|
481
|
+
}
|
|
482
|
+
async isInViewport(fully = false) {
|
|
483
|
+
return this.frame.isInViewportLocator(this.query, fully);
|
|
484
|
+
}
|
|
485
|
+
async isEditable() {
|
|
486
|
+
return this.frame.isEditableLocator(this.query);
|
|
487
|
+
}
|
|
488
|
+
async count() {
|
|
489
|
+
return this.frame.countLocator(this.query);
|
|
490
|
+
}
|
|
491
|
+
queryTimeoutOptions() {
|
|
492
|
+
return "options" in this.query && this.query.options?.timeoutMs ? { timeoutMs: this.query.options.timeoutMs } : {};
|
|
443
493
|
}
|
|
444
494
|
};
|
|
445
495
|
|
|
@@ -471,6 +521,9 @@ var Frame = class {
|
|
|
471
521
|
this.url = meta.url;
|
|
472
522
|
this.parentId = meta.parentId;
|
|
473
523
|
}
|
|
524
|
+
getEvents() {
|
|
525
|
+
return this.events;
|
|
526
|
+
}
|
|
474
527
|
async evaluate(fnOrString, ...args) {
|
|
475
528
|
return this.evaluateInContext(fnOrString, args);
|
|
476
529
|
}
|
|
@@ -487,7 +540,13 @@ var Frame = class {
|
|
|
487
540
|
return this.querySelectorAllInternal(selector, options, true);
|
|
488
541
|
}
|
|
489
542
|
locator(selector, options = {}) {
|
|
490
|
-
return new Locator(this, selector, options);
|
|
543
|
+
return new Locator(this, { kind: "selector", selector, options });
|
|
544
|
+
}
|
|
545
|
+
getByText(text, options = {}) {
|
|
546
|
+
return new Locator(this, { kind: "text", text, options });
|
|
547
|
+
}
|
|
548
|
+
getByRole(role, options = {}) {
|
|
549
|
+
return new Locator(this, { kind: "role", role, options });
|
|
491
550
|
}
|
|
492
551
|
async click(selector, options = {}) {
|
|
493
552
|
await this.performClick(selector, options, false);
|
|
@@ -922,6 +981,176 @@ var Frame = class {
|
|
|
922
981
|
const box = await this.resolveElementBox(selector, options);
|
|
923
982
|
return Boolean(box && box.visible);
|
|
924
983
|
}
|
|
984
|
+
async clickLocator(query, options = {}) {
|
|
985
|
+
await this.performClickLocator(query, options, false);
|
|
986
|
+
}
|
|
987
|
+
async dblclickLocator(query, options = {}) {
|
|
988
|
+
await this.performClickLocator(query, options, true);
|
|
989
|
+
}
|
|
990
|
+
async typeLocator(query, text, options = {}) {
|
|
991
|
+
const start = Date.now();
|
|
992
|
+
const description = this.locatorDescription(query);
|
|
993
|
+
this.events.emit("action:start", { name: "type", selector: description, frameId: this.id, sensitive: options.sensitive });
|
|
994
|
+
await waitFor(async () => {
|
|
995
|
+
const box = await this.resolveLocatorElementBox(query, options);
|
|
996
|
+
if (!box || !box.visible) {
|
|
997
|
+
return false;
|
|
998
|
+
}
|
|
999
|
+
return true;
|
|
1000
|
+
}, { timeoutMs: options.timeoutMs ?? this.defaultTimeout, description: `type ${description}` });
|
|
1001
|
+
const focusExpression = this.buildLocatorExpression(query, `
|
|
1002
|
+
if (!el) {
|
|
1003
|
+
return;
|
|
1004
|
+
}
|
|
1005
|
+
el.focus();
|
|
1006
|
+
`);
|
|
1007
|
+
const focusParams = {
|
|
1008
|
+
expression: focusExpression,
|
|
1009
|
+
returnByValue: true
|
|
1010
|
+
};
|
|
1011
|
+
if (this.contextId) {
|
|
1012
|
+
focusParams.contextId = this.contextId;
|
|
1013
|
+
}
|
|
1014
|
+
await this.session.send("Runtime.evaluate", focusParams);
|
|
1015
|
+
await this.session.send("Input.insertText", { text });
|
|
1016
|
+
const duration = Date.now() - start;
|
|
1017
|
+
this.events.emit("action:end", { name: "type", selector: description, frameId: this.id, durationMs: duration, sensitive: options.sensitive });
|
|
1018
|
+
this.logger.debug("Type", description, `${duration}ms`);
|
|
1019
|
+
}
|
|
1020
|
+
async existsLocator(query) {
|
|
1021
|
+
return Boolean(await this.evalOnLocator(query, false, `
|
|
1022
|
+
return Boolean(el);
|
|
1023
|
+
`));
|
|
1024
|
+
}
|
|
1025
|
+
async isVisibleLocator(query) {
|
|
1026
|
+
return this.evalOnLocator(query, false, `
|
|
1027
|
+
if (!el) {
|
|
1028
|
+
return null;
|
|
1029
|
+
}
|
|
1030
|
+
const rect = el.getBoundingClientRect();
|
|
1031
|
+
const style = window.getComputedStyle(el);
|
|
1032
|
+
return rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none" && Number(style.opacity || "1") > 0;
|
|
1033
|
+
`);
|
|
1034
|
+
}
|
|
1035
|
+
async isEnabledLocator(query) {
|
|
1036
|
+
return this.evalOnLocator(query, false, `
|
|
1037
|
+
if (!el) {
|
|
1038
|
+
return null;
|
|
1039
|
+
}
|
|
1040
|
+
const disabled = Boolean(el.disabled) || el.hasAttribute("disabled");
|
|
1041
|
+
const ariaDisabled = el.getAttribute && el.getAttribute("aria-disabled") === "true";
|
|
1042
|
+
return !(disabled || ariaDisabled);
|
|
1043
|
+
`);
|
|
1044
|
+
}
|
|
1045
|
+
async isCheckedLocator(query) {
|
|
1046
|
+
return this.evalOnLocator(query, false, `
|
|
1047
|
+
if (!el) {
|
|
1048
|
+
return null;
|
|
1049
|
+
}
|
|
1050
|
+
const aria = el.getAttribute && el.getAttribute("aria-checked");
|
|
1051
|
+
if (aria === "true") {
|
|
1052
|
+
return true;
|
|
1053
|
+
}
|
|
1054
|
+
if (aria === "false") {
|
|
1055
|
+
return false;
|
|
1056
|
+
}
|
|
1057
|
+
if ("checked" in el) {
|
|
1058
|
+
return Boolean(el.checked);
|
|
1059
|
+
}
|
|
1060
|
+
return null;
|
|
1061
|
+
`);
|
|
1062
|
+
}
|
|
1063
|
+
async textLocator(query) {
|
|
1064
|
+
return this.evalOnLocator(query, false, `
|
|
1065
|
+
if (!el) {
|
|
1066
|
+
return null;
|
|
1067
|
+
}
|
|
1068
|
+
if (el instanceof HTMLInputElement) {
|
|
1069
|
+
const type = (el.getAttribute("type") || "text").toLowerCase();
|
|
1070
|
+
if (type === "button" || type === "submit" || type === "reset") {
|
|
1071
|
+
return el.value || "";
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
return el.textContent || "";
|
|
1075
|
+
`);
|
|
1076
|
+
}
|
|
1077
|
+
async valueLocator(query) {
|
|
1078
|
+
return this.evalOnLocator(query, false, `
|
|
1079
|
+
if (!el) {
|
|
1080
|
+
return null;
|
|
1081
|
+
}
|
|
1082
|
+
if ("value" in el) {
|
|
1083
|
+
return el.value ?? "";
|
|
1084
|
+
}
|
|
1085
|
+
return el.getAttribute("value");
|
|
1086
|
+
`);
|
|
1087
|
+
}
|
|
1088
|
+
async attributeLocator(query, name) {
|
|
1089
|
+
return this.evalOnLocator(query, false, `
|
|
1090
|
+
if (!el || !(el instanceof Element)) {
|
|
1091
|
+
return null;
|
|
1092
|
+
}
|
|
1093
|
+
return el.getAttribute(${JSON.stringify(name)});
|
|
1094
|
+
`);
|
|
1095
|
+
}
|
|
1096
|
+
async classesLocator(query) {
|
|
1097
|
+
return this.evalOnLocator(query, false, `
|
|
1098
|
+
if (!el) {
|
|
1099
|
+
return null;
|
|
1100
|
+
}
|
|
1101
|
+
if (!el.classList) {
|
|
1102
|
+
return [];
|
|
1103
|
+
}
|
|
1104
|
+
return Array.from(el.classList);
|
|
1105
|
+
`);
|
|
1106
|
+
}
|
|
1107
|
+
async cssLocator(query, property) {
|
|
1108
|
+
return this.evalOnLocator(query, false, `
|
|
1109
|
+
if (!el) {
|
|
1110
|
+
return null;
|
|
1111
|
+
}
|
|
1112
|
+
const style = window.getComputedStyle(el);
|
|
1113
|
+
return style.getPropertyValue(${JSON.stringify(property)}) || "";
|
|
1114
|
+
`);
|
|
1115
|
+
}
|
|
1116
|
+
async hasFocusLocator(query) {
|
|
1117
|
+
return this.evalOnLocator(query, false, `
|
|
1118
|
+
if (!el) {
|
|
1119
|
+
return null;
|
|
1120
|
+
}
|
|
1121
|
+
return document.activeElement === el;
|
|
1122
|
+
`);
|
|
1123
|
+
}
|
|
1124
|
+
async isInViewportLocator(query, fully = false) {
|
|
1125
|
+
return this.evalOnLocator(query, false, `
|
|
1126
|
+
if (!el) {
|
|
1127
|
+
return null;
|
|
1128
|
+
}
|
|
1129
|
+
const rect = el.getBoundingClientRect();
|
|
1130
|
+
const viewWidth = window.innerWidth || document.documentElement.clientWidth;
|
|
1131
|
+
const viewHeight = window.innerHeight || document.documentElement.clientHeight;
|
|
1132
|
+
if (${fully ? "true" : "false"}) {
|
|
1133
|
+
return rect.top >= 0 && rect.left >= 0 && rect.bottom <= viewHeight && rect.right <= viewWidth;
|
|
1134
|
+
}
|
|
1135
|
+
return rect.bottom > 0 && rect.right > 0 && rect.top < viewHeight && rect.left < viewWidth;
|
|
1136
|
+
`);
|
|
1137
|
+
}
|
|
1138
|
+
async isEditableLocator(query) {
|
|
1139
|
+
return this.evalOnLocator(query, false, `
|
|
1140
|
+
if (!el) {
|
|
1141
|
+
return null;
|
|
1142
|
+
}
|
|
1143
|
+
const disabled = Boolean(el.disabled) || el.hasAttribute("disabled");
|
|
1144
|
+
const readOnly = Boolean(el.readOnly) || el.hasAttribute("readonly");
|
|
1145
|
+
const ariaDisabled = el.getAttribute && el.getAttribute("aria-disabled") === "true";
|
|
1146
|
+
return !(disabled || readOnly || ariaDisabled);
|
|
1147
|
+
`);
|
|
1148
|
+
}
|
|
1149
|
+
async countLocator(query) {
|
|
1150
|
+
return this.evalOnLocator(query, true, `
|
|
1151
|
+
return elements.length;
|
|
1152
|
+
`);
|
|
1153
|
+
}
|
|
925
1154
|
async text(selector, options = {}) {
|
|
926
1155
|
return this.evalOnSelector(selector, options, false, `
|
|
927
1156
|
if (!el) {
|
|
@@ -1103,6 +1332,263 @@ var Frame = class {
|
|
|
1103
1332
|
return !(disabled || readOnly || ariaDisabled);
|
|
1104
1333
|
`);
|
|
1105
1334
|
}
|
|
1335
|
+
async performClickLocator(query, options, isDouble) {
|
|
1336
|
+
const start = Date.now();
|
|
1337
|
+
const actionName = isDouble ? "dblclick" : "click";
|
|
1338
|
+
const description = this.locatorDescription(query);
|
|
1339
|
+
this.events.emit("action:start", { name: actionName, selector: description, frameId: this.id });
|
|
1340
|
+
const box = await waitFor(async () => {
|
|
1341
|
+
const result = await this.resolveLocatorElementBox(query, options);
|
|
1342
|
+
if (!result || !result.visible) {
|
|
1343
|
+
return null;
|
|
1344
|
+
}
|
|
1345
|
+
return result;
|
|
1346
|
+
}, { timeoutMs: options.timeoutMs ?? this.defaultTimeout, description: `${actionName} ${description}` });
|
|
1347
|
+
const centerX = box.x + box.width / 2;
|
|
1348
|
+
const centerY = box.y + box.height / 2;
|
|
1349
|
+
await this.session.send("Input.dispatchMouseEvent", { type: "mouseMoved", x: centerX, y: centerY });
|
|
1350
|
+
await this.session.send("Input.dispatchMouseEvent", { type: "mousePressed", x: centerX, y: centerY, button: "left", clickCount: 1, buttons: 1 });
|
|
1351
|
+
await this.session.send("Input.dispatchMouseEvent", { type: "mouseReleased", x: centerX, y: centerY, button: "left", clickCount: 1, buttons: 0 });
|
|
1352
|
+
if (isDouble) {
|
|
1353
|
+
await this.session.send("Input.dispatchMouseEvent", { type: "mouseMoved", x: centerX, y: centerY });
|
|
1354
|
+
await this.session.send("Input.dispatchMouseEvent", { type: "mousePressed", x: centerX, y: centerY, button: "left", clickCount: 2, buttons: 1 });
|
|
1355
|
+
await this.session.send("Input.dispatchMouseEvent", { type: "mouseReleased", x: centerX, y: centerY, button: "left", clickCount: 2, buttons: 0 });
|
|
1356
|
+
}
|
|
1357
|
+
const duration = Date.now() - start;
|
|
1358
|
+
this.events.emit("action:end", { name: actionName, selector: description, frameId: this.id, durationMs: duration });
|
|
1359
|
+
this.logger.debug("Click", description, `${duration}ms`);
|
|
1360
|
+
}
|
|
1361
|
+
locatorDescription(query) {
|
|
1362
|
+
switch (query.kind) {
|
|
1363
|
+
case "selector":
|
|
1364
|
+
return query.selector;
|
|
1365
|
+
case "text":
|
|
1366
|
+
return typeof query.text === "string" ? `text=${JSON.stringify(query.text)}` : `text=${query.text.toString()}`;
|
|
1367
|
+
case "role":
|
|
1368
|
+
return `role=${query.role}${query.options?.name ? ` name=${typeof query.options.name === "string" ? JSON.stringify(query.options.name) : query.options.name.toString()}` : ""}`;
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
async resolveLocatorElementBox(query, options) {
|
|
1372
|
+
return this.evalOnLocator(query, false, `
|
|
1373
|
+
if (!el) {
|
|
1374
|
+
return null;
|
|
1375
|
+
}
|
|
1376
|
+
el.scrollIntoView({ block: "center", inline: "center" });
|
|
1377
|
+
const rect = el.getBoundingClientRect();
|
|
1378
|
+
const style = window.getComputedStyle(el);
|
|
1379
|
+
return {
|
|
1380
|
+
x: rect.x,
|
|
1381
|
+
y: rect.y,
|
|
1382
|
+
width: rect.width,
|
|
1383
|
+
height: rect.height,
|
|
1384
|
+
visible: rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none" && Number(style.opacity || "1") > 0
|
|
1385
|
+
};
|
|
1386
|
+
`);
|
|
1387
|
+
}
|
|
1388
|
+
async evalOnLocator(query, all, body) {
|
|
1389
|
+
const expression = this.buildLocatorExpression(query, body, all);
|
|
1390
|
+
const params = {
|
|
1391
|
+
expression,
|
|
1392
|
+
returnByValue: true
|
|
1393
|
+
};
|
|
1394
|
+
if (this.contextId) {
|
|
1395
|
+
params.contextId = this.contextId;
|
|
1396
|
+
}
|
|
1397
|
+
const result = await this.session.send("Runtime.evaluate", params);
|
|
1398
|
+
return result.result.value;
|
|
1399
|
+
}
|
|
1400
|
+
buildLocatorExpression(query, body, all = false) {
|
|
1401
|
+
const helpers = serializeShadowDomHelpers();
|
|
1402
|
+
const serializedQuery = this.serializeLocatorQuery(query);
|
|
1403
|
+
return `(function() {
|
|
1404
|
+
const querySelectorAllDeep = ${helpers.querySelectorAllDeep};
|
|
1405
|
+
const normalizeWhitespace = (value) => String(value ?? "").replace(/\\s+/g, " ").trim();
|
|
1406
|
+
const cssEscape = (value) => {
|
|
1407
|
+
if (typeof CSS !== "undefined" && CSS.escape) return CSS.escape(value);
|
|
1408
|
+
return String(value).replace(/[^a-zA-Z0-9_-]/g, (c) => "\\\\" + c.charCodeAt(0).toString(16) + " ");
|
|
1409
|
+
};
|
|
1410
|
+
const textFromElement = (el) => {
|
|
1411
|
+
if (!el) return "";
|
|
1412
|
+
if (el instanceof HTMLInputElement) {
|
|
1413
|
+
const type = (el.getAttribute("type") || "").toLowerCase();
|
|
1414
|
+
if (type === "button" || type === "submit" || type === "reset" || type === "image") {
|
|
1415
|
+
return el.value || el.getAttribute("alt") || "";
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
return el.textContent || "";
|
|
1419
|
+
};
|
|
1420
|
+
const isHidden = (el) => {
|
|
1421
|
+
if (!el || !(el instanceof Element)) return true;
|
|
1422
|
+
const style = window.getComputedStyle(el);
|
|
1423
|
+
if (el.hidden || el.getAttribute("aria-hidden") === "true") return true;
|
|
1424
|
+
return style.display === "none" || style.visibility === "hidden" || Number(style.opacity || "1") <= 0;
|
|
1425
|
+
};
|
|
1426
|
+
const getLabelText = (el) => {
|
|
1427
|
+
if (!el || !(el instanceof Element)) return "";
|
|
1428
|
+
const ariaLabel = el.getAttribute("aria-label");
|
|
1429
|
+
if (ariaLabel) return normalizeWhitespace(ariaLabel);
|
|
1430
|
+
const labelledBy = el.getAttribute("aria-labelledby");
|
|
1431
|
+
if (labelledBy) {
|
|
1432
|
+
const parts = labelledBy.split(/\\s+/).filter(Boolean).map((id) => document.getElementById(id)?.textContent || "");
|
|
1433
|
+
const text = normalizeWhitespace(parts.join(" "));
|
|
1434
|
+
if (text) return text;
|
|
1435
|
+
}
|
|
1436
|
+
if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement) {
|
|
1437
|
+
if (el.id) {
|
|
1438
|
+
const label = document.querySelector("label[for="" + cssEscape(el.id) + ""]");
|
|
1439
|
+
if (label) {
|
|
1440
|
+
const text = normalizeWhitespace(label.textContent || "");
|
|
1441
|
+
if (text) return text;
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
const wrap = el.closest("label");
|
|
1445
|
+
if (wrap) {
|
|
1446
|
+
const text = normalizeWhitespace(wrap.textContent || "");
|
|
1447
|
+
if (text) return text;
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
if (el instanceof HTMLImageElement) {
|
|
1451
|
+
return normalizeWhitespace(el.getAttribute("alt") || el.getAttribute("title") || "");
|
|
1452
|
+
}
|
|
1453
|
+
return normalizeWhitespace(el.getAttribute("title") || "");
|
|
1454
|
+
};
|
|
1455
|
+
const getImplicitRole = (el) => {
|
|
1456
|
+
if (!el || !(el instanceof Element)) return "";
|
|
1457
|
+
const explicitRole = (el.getAttribute("role") || "").trim().split(/\\s+/)[0];
|
|
1458
|
+
if (explicitRole) return explicitRole;
|
|
1459
|
+
const tag = el.tagName.toLowerCase();
|
|
1460
|
+
if (tag === "button") return "button";
|
|
1461
|
+
if (tag === "summary") return "button";
|
|
1462
|
+
if (tag === "a" && el.hasAttribute("href")) return "link";
|
|
1463
|
+
if (tag === "input") {
|
|
1464
|
+
const type = (el.getAttribute("type") || "text").toLowerCase();
|
|
1465
|
+
if (type === "checkbox") return "checkbox";
|
|
1466
|
+
if (type === "radio") return "radio";
|
|
1467
|
+
if (type === "range") return "slider";
|
|
1468
|
+
if (type === "submit" || type === "button" || type === "reset") return "button";
|
|
1469
|
+
if (type === "file") return "button";
|
|
1470
|
+
return "textbox";
|
|
1471
|
+
}
|
|
1472
|
+
if (tag === "textarea") return "textbox";
|
|
1473
|
+
if (tag === "select") return "combobox";
|
|
1474
|
+
if (tag === "img") return "img";
|
|
1475
|
+
if (tag === "ul" || tag === "ol") return "list";
|
|
1476
|
+
if (tag === "li") return "listitem";
|
|
1477
|
+
if (tag === "table") return "table";
|
|
1478
|
+
if (tag === "tr") return "row";
|
|
1479
|
+
if (tag === "td") return "cell";
|
|
1480
|
+
if (tag === "th") return "columnheader";
|
|
1481
|
+
if (/^h[1-6]$/.test(tag)) return "heading";
|
|
1482
|
+
if (tag === "option") return "option";
|
|
1483
|
+
if (tag === "fieldset") return "group";
|
|
1484
|
+
if (tag === "form") return "form";
|
|
1485
|
+
if (el.hasAttribute("contenteditable")) return "textbox";
|
|
1486
|
+
return explicitRole;
|
|
1487
|
+
};
|
|
1488
|
+
const matchText = (actual, expected, exact) => {
|
|
1489
|
+
const normalizedActual = normalizeWhitespace(actual);
|
|
1490
|
+
if (expected && expected.kind === "regex") {
|
|
1491
|
+
const regex = new RegExp(expected.source, expected.flags);
|
|
1492
|
+
return regex.test(normalizedActual);
|
|
1493
|
+
}
|
|
1494
|
+
const normalizedExpected = normalizeWhitespace(expected.value);
|
|
1495
|
+
if (exact) {
|
|
1496
|
+
return normalizedActual === normalizedExpected;
|
|
1497
|
+
}
|
|
1498
|
+
return normalizedActual.toLowerCase().includes(normalizedExpected.toLowerCase());
|
|
1499
|
+
};
|
|
1500
|
+
const matchName = (actual, expected, exact) => {
|
|
1501
|
+
const normalizedActual = normalizeWhitespace(actual);
|
|
1502
|
+
if (expected && expected.kind === "regex") {
|
|
1503
|
+
const regex = new RegExp(expected.source, expected.flags);
|
|
1504
|
+
return regex.test(normalizedActual);
|
|
1505
|
+
}
|
|
1506
|
+
const normalizedExpected = normalizeWhitespace(expected.value);
|
|
1507
|
+
if (exact) {
|
|
1508
|
+
return normalizedActual === normalizedExpected;
|
|
1509
|
+
}
|
|
1510
|
+
return normalizedActual.toLowerCase().includes(normalizedExpected.toLowerCase());
|
|
1511
|
+
};
|
|
1512
|
+
const query = ${serializedQuery};
|
|
1513
|
+
const nodes = Array.from(querySelectorAllDeep(document, "*"));
|
|
1514
|
+
const selectorMatches = () => {
|
|
1515
|
+
if (query.kind !== "selector") {
|
|
1516
|
+
return [];
|
|
1517
|
+
}
|
|
1518
|
+
if (query.selector.includes(">>>")) {
|
|
1519
|
+
return querySelectorAllDeep(document, query.selector);
|
|
1520
|
+
}
|
|
1521
|
+
if (query.parsed?.type === "xpath") {
|
|
1522
|
+
const result = document.evaluate(query.parsed.value, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
|
|
1523
|
+
const list = [];
|
|
1524
|
+
for (let i = 0; i < result.snapshotLength; i += 1) {
|
|
1525
|
+
const item = result.snapshotItem(i);
|
|
1526
|
+
if (item instanceof Element) {
|
|
1527
|
+
list.push(item);
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
return list;
|
|
1531
|
+
}
|
|
1532
|
+
return Array.from(document.querySelectorAll(query.selector));
|
|
1533
|
+
};
|
|
1534
|
+
const selectorMatchSet = selectorMatches();
|
|
1535
|
+
const matches = nodes.filter((el) => {
|
|
1536
|
+
if (!(el instanceof Element)) return false;
|
|
1537
|
+
if (query.kind === "selector") {
|
|
1538
|
+
return selectorMatchSet.includes(el);
|
|
1539
|
+
}
|
|
1540
|
+
if (query.kind === "text") {
|
|
1541
|
+
const text = textFromElement(el);
|
|
1542
|
+
return matchText(text, query.text, Boolean(query.options?.exact));
|
|
1543
|
+
}
|
|
1544
|
+
const role = getImplicitRole(el);
|
|
1545
|
+
if (!role || role !== query.role) {
|
|
1546
|
+
return false;
|
|
1547
|
+
}
|
|
1548
|
+
if (!query.options?.includeHidden && isHidden(el)) {
|
|
1549
|
+
return false;
|
|
1550
|
+
}
|
|
1551
|
+
if (query.options?.name == null) {
|
|
1552
|
+
return true;
|
|
1553
|
+
}
|
|
1554
|
+
const name = getLabelText(el) || textFromElement(el);
|
|
1555
|
+
return matchName(name, query.options.name, Boolean(query.options?.exact));
|
|
1556
|
+
});
|
|
1557
|
+
const textMatches = query.kind === "text"
|
|
1558
|
+
? matches.filter((el) => !matches.some((other) => other !== el && el.contains(other)))
|
|
1559
|
+
: matches;
|
|
1560
|
+
const elements = ${all ? "textMatches" : "textMatches.slice(0, 1)"};
|
|
1561
|
+
const el = elements[0] || null;
|
|
1562
|
+
${body}
|
|
1563
|
+
})()`;
|
|
1564
|
+
}
|
|
1565
|
+
serializeLocatorQuery(query) {
|
|
1566
|
+
switch (query.kind) {
|
|
1567
|
+
case "selector":
|
|
1568
|
+
return JSON.stringify({ kind: "selector", selector: query.selector, options: query.options, parsed: parseSelector(query.selector) });
|
|
1569
|
+
case "text":
|
|
1570
|
+
return JSON.stringify({
|
|
1571
|
+
kind: "text",
|
|
1572
|
+
text: this.serializeTextQuery(query.text),
|
|
1573
|
+
options: query.options
|
|
1574
|
+
});
|
|
1575
|
+
case "role":
|
|
1576
|
+
return JSON.stringify({
|
|
1577
|
+
kind: "role",
|
|
1578
|
+
role: query.role,
|
|
1579
|
+
options: query.options ? {
|
|
1580
|
+
...query.options,
|
|
1581
|
+
name: query.options.name != null ? this.serializeTextQuery(query.options.name) : void 0
|
|
1582
|
+
} : void 0
|
|
1583
|
+
});
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
serializeTextQuery(text) {
|
|
1587
|
+
if (text instanceof RegExp) {
|
|
1588
|
+
return { kind: "regex", source: text.source, flags: text.flags.replace("g", "") };
|
|
1589
|
+
}
|
|
1590
|
+
return { kind: "string", value: text };
|
|
1591
|
+
}
|
|
1106
1592
|
async performClick(selector, options, isDouble) {
|
|
1107
1593
|
const start = Date.now();
|
|
1108
1594
|
const actionName = isDouble ? "dblclick" : "click";
|
|
@@ -1380,7 +1866,13 @@ var Page = class {
|
|
|
1380
1866
|
return null;
|
|
1381
1867
|
}
|
|
1382
1868
|
locator(selector) {
|
|
1383
|
-
return
|
|
1869
|
+
return this.mainFrame().locator(selector);
|
|
1870
|
+
}
|
|
1871
|
+
getByText(text, options = {}) {
|
|
1872
|
+
return this.mainFrame().getByText(text, options);
|
|
1873
|
+
}
|
|
1874
|
+
getByRole(role, options = {}) {
|
|
1875
|
+
return this.mainFrame().getByRole(role, options);
|
|
1384
1876
|
}
|
|
1385
1877
|
async goto(url, options = {}) {
|
|
1386
1878
|
ensureAllowedUrl(url, { allowFileUrl: options.allowFileUrl });
|
|
@@ -2253,42 +2745,42 @@ var AssertionError = class extends Error {
|
|
|
2253
2745
|
|
|
2254
2746
|
// src/assert/expect.ts
|
|
2255
2747
|
var ElementExpectation = class _ElementExpectation {
|
|
2256
|
-
|
|
2257
|
-
selector;
|
|
2748
|
+
target;
|
|
2258
2749
|
options;
|
|
2259
2750
|
negate;
|
|
2260
2751
|
events;
|
|
2261
|
-
constructor(
|
|
2262
|
-
this.
|
|
2263
|
-
this.selector = selector;
|
|
2752
|
+
constructor(target, options, negate, events) {
|
|
2753
|
+
this.target = target;
|
|
2264
2754
|
this.options = options;
|
|
2265
2755
|
this.negate = negate;
|
|
2266
2756
|
this.events = events;
|
|
2267
2757
|
}
|
|
2268
2758
|
get not() {
|
|
2269
|
-
return new _ElementExpectation(this.
|
|
2759
|
+
return new _ElementExpectation(this.target, this.options, !this.negate, this.events);
|
|
2270
2760
|
}
|
|
2271
2761
|
async toExist() {
|
|
2272
2762
|
return this.assert(async () => {
|
|
2273
|
-
const exists = await this.
|
|
2763
|
+
const exists = await this.target.exists();
|
|
2274
2764
|
return this.negate ? !exists : exists;
|
|
2275
2765
|
}, this.negate ? "Expected element not to exist" : "Expected element to exist");
|
|
2276
2766
|
}
|
|
2277
2767
|
async toBeVisible() {
|
|
2278
2768
|
return this.assert(async () => {
|
|
2279
|
-
const visible = await this.
|
|
2769
|
+
const visible = await this.target.isVisible();
|
|
2770
|
+
if (visible == null) return this.negate ? true : false;
|
|
2280
2771
|
return this.negate ? !visible : visible;
|
|
2281
2772
|
}, this.negate ? "Expected element not to be visible" : "Expected element to be visible");
|
|
2282
2773
|
}
|
|
2283
2774
|
async toBeHidden() {
|
|
2284
2775
|
return this.assert(async () => {
|
|
2285
|
-
const visible = await this.
|
|
2776
|
+
const visible = await this.target.isVisible();
|
|
2777
|
+
if (visible == null) return this.negate ? true : false;
|
|
2286
2778
|
return this.negate ? visible : !visible;
|
|
2287
2779
|
}, this.negate ? "Expected element not to be hidden" : "Expected element to be hidden");
|
|
2288
2780
|
}
|
|
2289
2781
|
async toBeEnabled() {
|
|
2290
2782
|
return this.assert(async () => {
|
|
2291
|
-
const enabled = await this.
|
|
2783
|
+
const enabled = await this.target.isEnabled();
|
|
2292
2784
|
if (enabled == null) {
|
|
2293
2785
|
return this.negate ? true : false;
|
|
2294
2786
|
}
|
|
@@ -2297,7 +2789,7 @@ var ElementExpectation = class _ElementExpectation {
|
|
|
2297
2789
|
}
|
|
2298
2790
|
async toBeDisabled() {
|
|
2299
2791
|
return this.assert(async () => {
|
|
2300
|
-
const enabled = await this.
|
|
2792
|
+
const enabled = await this.target.isEnabled();
|
|
2301
2793
|
if (enabled == null) {
|
|
2302
2794
|
return this.negate ? true : false;
|
|
2303
2795
|
}
|
|
@@ -2307,7 +2799,7 @@ var ElementExpectation = class _ElementExpectation {
|
|
|
2307
2799
|
}
|
|
2308
2800
|
async toBeChecked() {
|
|
2309
2801
|
return this.assert(async () => {
|
|
2310
|
-
const checked = await this.
|
|
2802
|
+
const checked = await this.target.isChecked();
|
|
2311
2803
|
if (checked == null) {
|
|
2312
2804
|
return this.negate ? true : false;
|
|
2313
2805
|
}
|
|
@@ -2316,7 +2808,7 @@ var ElementExpectation = class _ElementExpectation {
|
|
|
2316
2808
|
}
|
|
2317
2809
|
async toBeUnchecked() {
|
|
2318
2810
|
return this.assert(async () => {
|
|
2319
|
-
const checked = await this.
|
|
2811
|
+
const checked = await this.target.isChecked();
|
|
2320
2812
|
if (checked == null) {
|
|
2321
2813
|
return this.negate ? true : false;
|
|
2322
2814
|
}
|
|
@@ -2327,7 +2819,7 @@ var ElementExpectation = class _ElementExpectation {
|
|
|
2327
2819
|
async toHaveText(textOrRegex) {
|
|
2328
2820
|
const expected = textOrRegex;
|
|
2329
2821
|
return this.assert(async () => {
|
|
2330
|
-
const text = await this.
|
|
2822
|
+
const text = await this.target.text();
|
|
2331
2823
|
if (text == null) {
|
|
2332
2824
|
return this.negate ? true : false;
|
|
2333
2825
|
}
|
|
@@ -2338,7 +2830,7 @@ var ElementExpectation = class _ElementExpectation {
|
|
|
2338
2830
|
async toHaveExactText(textOrRegex) {
|
|
2339
2831
|
const expected = textOrRegex;
|
|
2340
2832
|
return this.assert(async () => {
|
|
2341
|
-
const text = await this.
|
|
2833
|
+
const text = await this.target.text();
|
|
2342
2834
|
if (text == null) {
|
|
2343
2835
|
return this.negate ? true : false;
|
|
2344
2836
|
}
|
|
@@ -2349,7 +2841,7 @@ var ElementExpectation = class _ElementExpectation {
|
|
|
2349
2841
|
async toContainText(textOrRegex) {
|
|
2350
2842
|
const expected = textOrRegex;
|
|
2351
2843
|
return this.assert(async () => {
|
|
2352
|
-
const text = await this.
|
|
2844
|
+
const text = await this.target.text();
|
|
2353
2845
|
if (text == null) {
|
|
2354
2846
|
return this.negate ? true : false;
|
|
2355
2847
|
}
|
|
@@ -2360,7 +2852,7 @@ var ElementExpectation = class _ElementExpectation {
|
|
|
2360
2852
|
async toHaveValue(valueOrRegex) {
|
|
2361
2853
|
const expected = valueOrRegex;
|
|
2362
2854
|
return this.assert(async () => {
|
|
2363
|
-
const value = await this.
|
|
2855
|
+
const value = await this.target.value();
|
|
2364
2856
|
if (value == null) {
|
|
2365
2857
|
return this.negate ? true : false;
|
|
2366
2858
|
}
|
|
@@ -2371,7 +2863,7 @@ var ElementExpectation = class _ElementExpectation {
|
|
|
2371
2863
|
async toHaveAttribute(name, valueOrRegex) {
|
|
2372
2864
|
const expected = valueOrRegex;
|
|
2373
2865
|
return this.assert(async () => {
|
|
2374
|
-
const value = await this.
|
|
2866
|
+
const value = await this.target.attribute(name);
|
|
2375
2867
|
if (expected === void 0) {
|
|
2376
2868
|
const exists = value != null;
|
|
2377
2869
|
return this.negate ? !exists : exists;
|
|
@@ -2391,7 +2883,7 @@ var ElementExpectation = class _ElementExpectation {
|
|
|
2391
2883
|
}
|
|
2392
2884
|
async toHaveCount(expected) {
|
|
2393
2885
|
return this.assert(async () => {
|
|
2394
|
-
const count = await this.
|
|
2886
|
+
const count = await this.target.count();
|
|
2395
2887
|
const matches = count === expected;
|
|
2396
2888
|
return this.negate ? !matches : matches;
|
|
2397
2889
|
}, this.negate ? "Expected element count not to match" : "Expected element count to match", { expected });
|
|
@@ -2399,7 +2891,7 @@ var ElementExpectation = class _ElementExpectation {
|
|
|
2399
2891
|
async toHaveClass(nameOrRegex) {
|
|
2400
2892
|
const expected = nameOrRegex;
|
|
2401
2893
|
return this.assert(async () => {
|
|
2402
|
-
const classes = await this.
|
|
2894
|
+
const classes = await this.target.classes();
|
|
2403
2895
|
if (classes == null) {
|
|
2404
2896
|
return this.negate ? true : false;
|
|
2405
2897
|
}
|
|
@@ -2409,7 +2901,7 @@ var ElementExpectation = class _ElementExpectation {
|
|
|
2409
2901
|
}
|
|
2410
2902
|
async toHaveClasses(expected) {
|
|
2411
2903
|
return this.assert(async () => {
|
|
2412
|
-
const classes = await this.
|
|
2904
|
+
const classes = await this.target.classes();
|
|
2413
2905
|
if (classes == null) {
|
|
2414
2906
|
return this.negate ? true : false;
|
|
2415
2907
|
}
|
|
@@ -2420,7 +2912,7 @@ var ElementExpectation = class _ElementExpectation {
|
|
|
2420
2912
|
async toHaveCss(property, valueOrRegex) {
|
|
2421
2913
|
const expected = valueOrRegex;
|
|
2422
2914
|
return this.assert(async () => {
|
|
2423
|
-
const value = await this.
|
|
2915
|
+
const value = await this.target.css(property);
|
|
2424
2916
|
if (value == null) {
|
|
2425
2917
|
return this.negate ? true : false;
|
|
2426
2918
|
}
|
|
@@ -2431,7 +2923,7 @@ var ElementExpectation = class _ElementExpectation {
|
|
|
2431
2923
|
}
|
|
2432
2924
|
async toHaveFocus() {
|
|
2433
2925
|
return this.assert(async () => {
|
|
2434
|
-
const focused = await this.
|
|
2926
|
+
const focused = await this.target.hasFocus();
|
|
2435
2927
|
if (focused == null) {
|
|
2436
2928
|
return this.negate ? true : false;
|
|
2437
2929
|
}
|
|
@@ -2440,7 +2932,7 @@ var ElementExpectation = class _ElementExpectation {
|
|
|
2440
2932
|
}
|
|
2441
2933
|
async toBeInViewport(options = {}) {
|
|
2442
2934
|
return this.assert(async () => {
|
|
2443
|
-
const inViewport = await this.
|
|
2935
|
+
const inViewport = await this.target.isInViewport(Boolean(options.fully));
|
|
2444
2936
|
if (inViewport == null) {
|
|
2445
2937
|
return this.negate ? true : false;
|
|
2446
2938
|
}
|
|
@@ -2449,7 +2941,7 @@ var ElementExpectation = class _ElementExpectation {
|
|
|
2449
2941
|
}
|
|
2450
2942
|
async toBeEditable() {
|
|
2451
2943
|
return this.assert(async () => {
|
|
2452
|
-
const editable = await this.
|
|
2944
|
+
const editable = await this.target.isEditable();
|
|
2453
2945
|
if (editable == null) {
|
|
2454
2946
|
return this.negate ? true : false;
|
|
2455
2947
|
}
|
|
@@ -2459,7 +2951,7 @@ var ElementExpectation = class _ElementExpectation {
|
|
|
2459
2951
|
async assert(predicate, message, details = {}) {
|
|
2460
2952
|
const timeoutMs = this.options.timeoutMs ?? 3e4;
|
|
2461
2953
|
const start = Date.now();
|
|
2462
|
-
this.events.emit("assertion:start", { name: message, selector: this.
|
|
2954
|
+
this.events.emit("assertion:start", { name: message, selector: this.target.label, frameId: this.target.frameId });
|
|
2463
2955
|
let lastState;
|
|
2464
2956
|
try {
|
|
2465
2957
|
await waitFor(async () => {
|
|
@@ -2469,13 +2961,51 @@ var ElementExpectation = class _ElementExpectation {
|
|
|
2469
2961
|
}, { timeoutMs, description: message });
|
|
2470
2962
|
} catch {
|
|
2471
2963
|
const duration2 = Date.now() - start;
|
|
2472
|
-
this.events.emit("assertion:end", { name: message, selector: this.
|
|
2473
|
-
throw new AssertionError(message, { selector: this.
|
|
2964
|
+
this.events.emit("assertion:end", { name: message, selector: this.target.label, frameId: this.target.frameId, durationMs: duration2, status: "failed" });
|
|
2965
|
+
throw new AssertionError(message, { selector: this.target.label, timeoutMs, lastState: { lastState, ...details } });
|
|
2474
2966
|
}
|
|
2475
2967
|
const duration = Date.now() - start;
|
|
2476
|
-
this.events.emit("assertion:end", { name: message, selector: this.
|
|
2968
|
+
this.events.emit("assertion:end", { name: message, selector: this.target.label, frameId: this.target.frameId, durationMs: duration, status: "passed" });
|
|
2477
2969
|
}
|
|
2478
2970
|
};
|
|
2971
|
+
function frameTarget(frame, selector, options) {
|
|
2972
|
+
return {
|
|
2973
|
+
label: selector,
|
|
2974
|
+
frameId: frame.id,
|
|
2975
|
+
exists: () => frame.exists(selector, options),
|
|
2976
|
+
isVisible: () => frame.isVisible(selector, options),
|
|
2977
|
+
isEnabled: () => frame.isEnabled(selector, options),
|
|
2978
|
+
isChecked: () => frame.isChecked(selector, options),
|
|
2979
|
+
text: () => frame.text(selector, options),
|
|
2980
|
+
value: () => frame.value(selector, options),
|
|
2981
|
+
attribute: (name) => frame.attribute(selector, name, options),
|
|
2982
|
+
count: () => frame.count(selector, options),
|
|
2983
|
+
classes: () => frame.classes(selector, options),
|
|
2984
|
+
css: (property) => frame.css(selector, property, options),
|
|
2985
|
+
hasFocus: () => frame.hasFocus(selector, options),
|
|
2986
|
+
isInViewport: (fully = false) => frame.isInViewport(selector, options, fully),
|
|
2987
|
+
isEditable: () => frame.isEditable(selector, options)
|
|
2988
|
+
};
|
|
2989
|
+
}
|
|
2990
|
+
function locatorTarget(locator) {
|
|
2991
|
+
return {
|
|
2992
|
+
label: locator.describe(),
|
|
2993
|
+
frameId: locator.getFrameId(),
|
|
2994
|
+
exists: () => locator.exists(),
|
|
2995
|
+
isVisible: () => locator.isVisible(),
|
|
2996
|
+
isEnabled: () => locator.isEnabled(),
|
|
2997
|
+
isChecked: () => locator.isChecked(),
|
|
2998
|
+
text: () => locator.text(),
|
|
2999
|
+
value: () => locator.value(),
|
|
3000
|
+
attribute: (name) => locator.attribute(name),
|
|
3001
|
+
count: () => locator.count(),
|
|
3002
|
+
classes: () => locator.classes(),
|
|
3003
|
+
css: (property) => locator.css(property),
|
|
3004
|
+
hasFocus: () => locator.hasFocus(),
|
|
3005
|
+
isInViewport: (fully = false) => locator.isInViewport(fully),
|
|
3006
|
+
isEditable: () => locator.isEditable()
|
|
3007
|
+
};
|
|
3008
|
+
}
|
|
2479
3009
|
var ExpectFrame = class {
|
|
2480
3010
|
frame;
|
|
2481
3011
|
events;
|
|
@@ -2484,25 +3014,31 @@ var ExpectFrame = class {
|
|
|
2484
3014
|
this.events = events;
|
|
2485
3015
|
}
|
|
2486
3016
|
element(selector, options = {}) {
|
|
2487
|
-
return new ElementExpectation(this.frame, selector, options, false, this.events);
|
|
3017
|
+
return new ElementExpectation(frameTarget(this.frame, selector, options), options, false, this.events);
|
|
2488
3018
|
}
|
|
2489
3019
|
};
|
|
2490
|
-
function expect(
|
|
3020
|
+
function expect(target) {
|
|
3021
|
+
if (target instanceof Locator) {
|
|
3022
|
+
return new ElementExpectation(locatorTarget(target), {}, false, target.getEvents());
|
|
3023
|
+
}
|
|
2491
3024
|
return {
|
|
2492
|
-
element: (selector, options = {}) => new ElementExpectation(
|
|
3025
|
+
element: (selector, options = {}) => new ElementExpectation(frameTarget(target.mainFrame(), selector, options), options, false, target.getEvents()),
|
|
2493
3026
|
frame: (options) => {
|
|
2494
|
-
const frame =
|
|
3027
|
+
const frame = target.frame(options);
|
|
2495
3028
|
if (!frame) {
|
|
2496
3029
|
throw new AssertionError("Frame not found", { selector: JSON.stringify(options) });
|
|
2497
3030
|
}
|
|
2498
|
-
return new ExpectFrame(frame,
|
|
3031
|
+
return new ExpectFrame(frame, target.getEvents());
|
|
2499
3032
|
}
|
|
2500
3033
|
};
|
|
2501
3034
|
}
|
|
2502
|
-
Page.prototype.expect = function(
|
|
3035
|
+
Page.prototype.expect = function(target, options) {
|
|
2503
3036
|
const builder = expect(this);
|
|
2504
|
-
if (
|
|
2505
|
-
return
|
|
3037
|
+
if (target instanceof Locator) {
|
|
3038
|
+
return expect(target);
|
|
3039
|
+
}
|
|
3040
|
+
if (target) {
|
|
3041
|
+
return builder.element(target, options);
|
|
2506
3042
|
}
|
|
2507
3043
|
return builder;
|
|
2508
3044
|
};
|
|
@@ -2588,36 +3124,28 @@ function printHelp() {
|
|
|
2588
3124
|
console.log(`cdpwright (cpw) \u2014 Chromium automation CLI
|
|
2589
3125
|
|
|
2590
3126
|
Commands:
|
|
3127
|
+
screenshot <url> -o f Take a screenshot (PNG/JPEG)
|
|
3128
|
+
pdf <url> -o file.pdf Generate visual PDF of page
|
|
3129
|
+
html <url> -o file.html Save the page HTML source
|
|
3130
|
+
eval <url> <script> Run JS in page, print result as JSON
|
|
2591
3131
|
download [options] Download pinned Chromium snapshot
|
|
2592
3132
|
install Alias for download
|
|
2593
|
-
open <url> Launch browser session and navigate to URL
|
|
2594
|
-
close Close the running browser session
|
|
2595
|
-
screenshot [url] -o f Take a screenshot (PNG/JPEG)
|
|
2596
|
-
html [url] -o file.html Save the page HTML source
|
|
2597
|
-
pdf [url] -o file.pdf Generate PDF of page (headless only)
|
|
2598
|
-
eval [url] <script> Run script in page, print result as JSON
|
|
2599
3133
|
version Print cdpwright and Chromium versions
|
|
2600
3134
|
|
|
2601
|
-
|
|
2602
|
-
'open' starts a browser and saves a session. Other commands auto-connect
|
|
2603
|
-
to the running session when no URL is given. 'close' shuts it down.
|
|
2604
|
-
|
|
2605
|
-
Download options:
|
|
2606
|
-
--latest Download the latest Chromium revision
|
|
2607
|
-
--mirror <url> Custom mirror base URL
|
|
2608
|
-
--url <url> Exact zip URL override
|
|
2609
|
-
|
|
2610
|
-
Common options:
|
|
3135
|
+
Options:
|
|
2611
3136
|
--headless Run in headless mode (default for screenshot, pdf, eval)
|
|
2612
|
-
--headed Run in headed mode
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
--
|
|
3137
|
+
--headed Run in headed mode
|
|
3138
|
+
-o, --output <file> Output file path (for screenshot, html, pdf)
|
|
3139
|
+
--full-page Capture full scrollable page (for screenshot)
|
|
3140
|
+
--latest Download the latest Chromium revision (for download)
|
|
3141
|
+
--mirror <url> Custom mirror base URL (for download)
|
|
3142
|
+
--url <url> Exact zip URL override (for download)
|
|
2617
3143
|
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
3144
|
+
Interactive session:
|
|
3145
|
+
open <url> Open browser and start a session
|
|
3146
|
+
close Close the running session
|
|
3147
|
+
When a session is running, commands accept no URL and operate on the
|
|
3148
|
+
live page instead: cpw screenshot -o shot.png | cpw eval "document.title"`);
|
|
2621
3149
|
}
|
|
2622
3150
|
function hasFlag(args, flag) {
|
|
2623
3151
|
return args.includes(flag);
|