@toolstackhq/cdpwright 1.1.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/README.md +25 -7
- package/dist/assert/expect.d.ts +1 -1
- package/dist/assert/expect.js +1 -1
- package/dist/{chunk-M5G6EMAS.js → chunk-EIAXMGD5.js} +639 -69
- package/dist/chunk-EIAXMGD5.js.map +1 -0
- package/dist/cli.js +964 -120
- package/dist/cli.js.map +1 -1
- package/dist/{expect-BY8vnFfi.d.ts → expect-Cd5SNFuF.d.ts} +113 -7
- package/dist/index.d.ts +25 -4
- package/dist/index.js +53 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-M5G6EMAS.js.map +0 -1
|
@@ -165,27 +165,77 @@ function serializeShadowDomHelpers() {
|
|
|
165
165
|
// src/core/Locator.ts
|
|
166
166
|
var Locator = class {
|
|
167
167
|
frame;
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
constructor(frame, selector, options = {}) {
|
|
168
|
+
query;
|
|
169
|
+
constructor(frame, query) {
|
|
171
170
|
this.frame = frame;
|
|
172
|
-
this.
|
|
173
|
-
|
|
171
|
+
this.query = query;
|
|
172
|
+
}
|
|
173
|
+
getEvents() {
|
|
174
|
+
return this.frame.getEvents();
|
|
175
|
+
}
|
|
176
|
+
getFrameId() {
|
|
177
|
+
return this.frame.id;
|
|
178
|
+
}
|
|
179
|
+
describe() {
|
|
180
|
+
switch (this.query.kind) {
|
|
181
|
+
case "selector":
|
|
182
|
+
return this.query.selector;
|
|
183
|
+
case "text":
|
|
184
|
+
return typeof this.query.text === "string" ? `text=${JSON.stringify(this.query.text)}` : `text=${this.query.text.toString()}`;
|
|
185
|
+
case "role":
|
|
186
|
+
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()}` : ""}`;
|
|
187
|
+
}
|
|
174
188
|
}
|
|
175
189
|
async click(options = {}) {
|
|
176
|
-
return this.frame.
|
|
190
|
+
return this.frame.clickLocator(this.query, { ...this.queryTimeoutOptions(), ...options });
|
|
177
191
|
}
|
|
178
192
|
async dblclick(options = {}) {
|
|
179
|
-
return this.frame.
|
|
193
|
+
return this.frame.dblclickLocator(this.query, { ...this.queryTimeoutOptions(), ...options });
|
|
180
194
|
}
|
|
181
195
|
async type(text, options = {}) {
|
|
182
|
-
return this.frame.
|
|
196
|
+
return this.frame.typeLocator(this.query, text, { ...this.queryTimeoutOptions(), ...options });
|
|
183
197
|
}
|
|
184
198
|
async exists() {
|
|
185
|
-
return this.frame.
|
|
199
|
+
return this.frame.existsLocator(this.query);
|
|
200
|
+
}
|
|
201
|
+
async isVisible() {
|
|
202
|
+
return this.frame.isVisibleLocator(this.query);
|
|
203
|
+
}
|
|
204
|
+
async isEnabled() {
|
|
205
|
+
return this.frame.isEnabledLocator(this.query);
|
|
206
|
+
}
|
|
207
|
+
async isChecked() {
|
|
208
|
+
return this.frame.isCheckedLocator(this.query);
|
|
186
209
|
}
|
|
187
210
|
async text() {
|
|
188
|
-
return this.frame.
|
|
211
|
+
return this.frame.textLocator(this.query);
|
|
212
|
+
}
|
|
213
|
+
async value() {
|
|
214
|
+
return this.frame.valueLocator(this.query);
|
|
215
|
+
}
|
|
216
|
+
async attribute(name) {
|
|
217
|
+
return this.frame.attributeLocator(this.query, name);
|
|
218
|
+
}
|
|
219
|
+
async classes() {
|
|
220
|
+
return this.frame.classesLocator(this.query);
|
|
221
|
+
}
|
|
222
|
+
async css(property) {
|
|
223
|
+
return this.frame.cssLocator(this.query, property);
|
|
224
|
+
}
|
|
225
|
+
async hasFocus() {
|
|
226
|
+
return this.frame.hasFocusLocator(this.query);
|
|
227
|
+
}
|
|
228
|
+
async isInViewport(fully = false) {
|
|
229
|
+
return this.frame.isInViewportLocator(this.query, fully);
|
|
230
|
+
}
|
|
231
|
+
async isEditable() {
|
|
232
|
+
return this.frame.isEditableLocator(this.query);
|
|
233
|
+
}
|
|
234
|
+
async count() {
|
|
235
|
+
return this.frame.countLocator(this.query);
|
|
236
|
+
}
|
|
237
|
+
queryTimeoutOptions() {
|
|
238
|
+
return "options" in this.query && this.query.options?.timeoutMs ? { timeoutMs: this.query.options.timeoutMs } : {};
|
|
189
239
|
}
|
|
190
240
|
};
|
|
191
241
|
|
|
@@ -217,6 +267,9 @@ var Frame = class {
|
|
|
217
267
|
this.url = meta.url;
|
|
218
268
|
this.parentId = meta.parentId;
|
|
219
269
|
}
|
|
270
|
+
getEvents() {
|
|
271
|
+
return this.events;
|
|
272
|
+
}
|
|
220
273
|
async evaluate(fnOrString, ...args) {
|
|
221
274
|
return this.evaluateInContext(fnOrString, args);
|
|
222
275
|
}
|
|
@@ -233,7 +286,13 @@ var Frame = class {
|
|
|
233
286
|
return this.querySelectorAllInternal(selector, options, true);
|
|
234
287
|
}
|
|
235
288
|
locator(selector, options = {}) {
|
|
236
|
-
return new Locator(this, selector, options);
|
|
289
|
+
return new Locator(this, { kind: "selector", selector, options });
|
|
290
|
+
}
|
|
291
|
+
getByText(text, options = {}) {
|
|
292
|
+
return new Locator(this, { kind: "text", text, options });
|
|
293
|
+
}
|
|
294
|
+
getByRole(role, options = {}) {
|
|
295
|
+
return new Locator(this, { kind: "role", role, options });
|
|
237
296
|
}
|
|
238
297
|
async click(selector, options = {}) {
|
|
239
298
|
await this.performClick(selector, options, false);
|
|
@@ -668,6 +727,176 @@ var Frame = class {
|
|
|
668
727
|
const box = await this.resolveElementBox(selector, options);
|
|
669
728
|
return Boolean(box && box.visible);
|
|
670
729
|
}
|
|
730
|
+
async clickLocator(query, options = {}) {
|
|
731
|
+
await this.performClickLocator(query, options, false);
|
|
732
|
+
}
|
|
733
|
+
async dblclickLocator(query, options = {}) {
|
|
734
|
+
await this.performClickLocator(query, options, true);
|
|
735
|
+
}
|
|
736
|
+
async typeLocator(query, text, options = {}) {
|
|
737
|
+
const start = Date.now();
|
|
738
|
+
const description = this.locatorDescription(query);
|
|
739
|
+
this.events.emit("action:start", { name: "type", selector: description, frameId: this.id, sensitive: options.sensitive });
|
|
740
|
+
await waitFor(async () => {
|
|
741
|
+
const box = await this.resolveLocatorElementBox(query, options);
|
|
742
|
+
if (!box || !box.visible) {
|
|
743
|
+
return false;
|
|
744
|
+
}
|
|
745
|
+
return true;
|
|
746
|
+
}, { timeoutMs: options.timeoutMs ?? this.defaultTimeout, description: `type ${description}` });
|
|
747
|
+
const focusExpression = this.buildLocatorExpression(query, `
|
|
748
|
+
if (!el) {
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
751
|
+
el.focus();
|
|
752
|
+
`);
|
|
753
|
+
const focusParams = {
|
|
754
|
+
expression: focusExpression,
|
|
755
|
+
returnByValue: true
|
|
756
|
+
};
|
|
757
|
+
if (this.contextId) {
|
|
758
|
+
focusParams.contextId = this.contextId;
|
|
759
|
+
}
|
|
760
|
+
await this.session.send("Runtime.evaluate", focusParams);
|
|
761
|
+
await this.session.send("Input.insertText", { text });
|
|
762
|
+
const duration = Date.now() - start;
|
|
763
|
+
this.events.emit("action:end", { name: "type", selector: description, frameId: this.id, durationMs: duration, sensitive: options.sensitive });
|
|
764
|
+
this.logger.debug("Type", description, `${duration}ms`);
|
|
765
|
+
}
|
|
766
|
+
async existsLocator(query) {
|
|
767
|
+
return Boolean(await this.evalOnLocator(query, false, `
|
|
768
|
+
return Boolean(el);
|
|
769
|
+
`));
|
|
770
|
+
}
|
|
771
|
+
async isVisibleLocator(query) {
|
|
772
|
+
return this.evalOnLocator(query, false, `
|
|
773
|
+
if (!el) {
|
|
774
|
+
return null;
|
|
775
|
+
}
|
|
776
|
+
const rect = el.getBoundingClientRect();
|
|
777
|
+
const style = window.getComputedStyle(el);
|
|
778
|
+
return rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none" && Number(style.opacity || "1") > 0;
|
|
779
|
+
`);
|
|
780
|
+
}
|
|
781
|
+
async isEnabledLocator(query) {
|
|
782
|
+
return this.evalOnLocator(query, false, `
|
|
783
|
+
if (!el) {
|
|
784
|
+
return null;
|
|
785
|
+
}
|
|
786
|
+
const disabled = Boolean(el.disabled) || el.hasAttribute("disabled");
|
|
787
|
+
const ariaDisabled = el.getAttribute && el.getAttribute("aria-disabled") === "true";
|
|
788
|
+
return !(disabled || ariaDisabled);
|
|
789
|
+
`);
|
|
790
|
+
}
|
|
791
|
+
async isCheckedLocator(query) {
|
|
792
|
+
return this.evalOnLocator(query, false, `
|
|
793
|
+
if (!el) {
|
|
794
|
+
return null;
|
|
795
|
+
}
|
|
796
|
+
const aria = el.getAttribute && el.getAttribute("aria-checked");
|
|
797
|
+
if (aria === "true") {
|
|
798
|
+
return true;
|
|
799
|
+
}
|
|
800
|
+
if (aria === "false") {
|
|
801
|
+
return false;
|
|
802
|
+
}
|
|
803
|
+
if ("checked" in el) {
|
|
804
|
+
return Boolean(el.checked);
|
|
805
|
+
}
|
|
806
|
+
return null;
|
|
807
|
+
`);
|
|
808
|
+
}
|
|
809
|
+
async textLocator(query) {
|
|
810
|
+
return this.evalOnLocator(query, false, `
|
|
811
|
+
if (!el) {
|
|
812
|
+
return null;
|
|
813
|
+
}
|
|
814
|
+
if (el instanceof HTMLInputElement) {
|
|
815
|
+
const type = (el.getAttribute("type") || "text").toLowerCase();
|
|
816
|
+
if (type === "button" || type === "submit" || type === "reset") {
|
|
817
|
+
return el.value || "";
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
return el.textContent || "";
|
|
821
|
+
`);
|
|
822
|
+
}
|
|
823
|
+
async valueLocator(query) {
|
|
824
|
+
return this.evalOnLocator(query, false, `
|
|
825
|
+
if (!el) {
|
|
826
|
+
return null;
|
|
827
|
+
}
|
|
828
|
+
if ("value" in el) {
|
|
829
|
+
return el.value ?? "";
|
|
830
|
+
}
|
|
831
|
+
return el.getAttribute("value");
|
|
832
|
+
`);
|
|
833
|
+
}
|
|
834
|
+
async attributeLocator(query, name) {
|
|
835
|
+
return this.evalOnLocator(query, false, `
|
|
836
|
+
if (!el || !(el instanceof Element)) {
|
|
837
|
+
return null;
|
|
838
|
+
}
|
|
839
|
+
return el.getAttribute(${JSON.stringify(name)});
|
|
840
|
+
`);
|
|
841
|
+
}
|
|
842
|
+
async classesLocator(query) {
|
|
843
|
+
return this.evalOnLocator(query, false, `
|
|
844
|
+
if (!el) {
|
|
845
|
+
return null;
|
|
846
|
+
}
|
|
847
|
+
if (!el.classList) {
|
|
848
|
+
return [];
|
|
849
|
+
}
|
|
850
|
+
return Array.from(el.classList);
|
|
851
|
+
`);
|
|
852
|
+
}
|
|
853
|
+
async cssLocator(query, property) {
|
|
854
|
+
return this.evalOnLocator(query, false, `
|
|
855
|
+
if (!el) {
|
|
856
|
+
return null;
|
|
857
|
+
}
|
|
858
|
+
const style = window.getComputedStyle(el);
|
|
859
|
+
return style.getPropertyValue(${JSON.stringify(property)}) || "";
|
|
860
|
+
`);
|
|
861
|
+
}
|
|
862
|
+
async hasFocusLocator(query) {
|
|
863
|
+
return this.evalOnLocator(query, false, `
|
|
864
|
+
if (!el) {
|
|
865
|
+
return null;
|
|
866
|
+
}
|
|
867
|
+
return document.activeElement === el;
|
|
868
|
+
`);
|
|
869
|
+
}
|
|
870
|
+
async isInViewportLocator(query, fully = false) {
|
|
871
|
+
return this.evalOnLocator(query, false, `
|
|
872
|
+
if (!el) {
|
|
873
|
+
return null;
|
|
874
|
+
}
|
|
875
|
+
const rect = el.getBoundingClientRect();
|
|
876
|
+
const viewWidth = window.innerWidth || document.documentElement.clientWidth;
|
|
877
|
+
const viewHeight = window.innerHeight || document.documentElement.clientHeight;
|
|
878
|
+
if (${fully ? "true" : "false"}) {
|
|
879
|
+
return rect.top >= 0 && rect.left >= 0 && rect.bottom <= viewHeight && rect.right <= viewWidth;
|
|
880
|
+
}
|
|
881
|
+
return rect.bottom > 0 && rect.right > 0 && rect.top < viewHeight && rect.left < viewWidth;
|
|
882
|
+
`);
|
|
883
|
+
}
|
|
884
|
+
async isEditableLocator(query) {
|
|
885
|
+
return this.evalOnLocator(query, false, `
|
|
886
|
+
if (!el) {
|
|
887
|
+
return null;
|
|
888
|
+
}
|
|
889
|
+
const disabled = Boolean(el.disabled) || el.hasAttribute("disabled");
|
|
890
|
+
const readOnly = Boolean(el.readOnly) || el.hasAttribute("readonly");
|
|
891
|
+
const ariaDisabled = el.getAttribute && el.getAttribute("aria-disabled") === "true";
|
|
892
|
+
return !(disabled || readOnly || ariaDisabled);
|
|
893
|
+
`);
|
|
894
|
+
}
|
|
895
|
+
async countLocator(query) {
|
|
896
|
+
return this.evalOnLocator(query, true, `
|
|
897
|
+
return elements.length;
|
|
898
|
+
`);
|
|
899
|
+
}
|
|
671
900
|
async text(selector, options = {}) {
|
|
672
901
|
return this.evalOnSelector(selector, options, false, `
|
|
673
902
|
if (!el) {
|
|
@@ -849,6 +1078,263 @@ var Frame = class {
|
|
|
849
1078
|
return !(disabled || readOnly || ariaDisabled);
|
|
850
1079
|
`);
|
|
851
1080
|
}
|
|
1081
|
+
async performClickLocator(query, options, isDouble) {
|
|
1082
|
+
const start = Date.now();
|
|
1083
|
+
const actionName = isDouble ? "dblclick" : "click";
|
|
1084
|
+
const description = this.locatorDescription(query);
|
|
1085
|
+
this.events.emit("action:start", { name: actionName, selector: description, frameId: this.id });
|
|
1086
|
+
const box = await waitFor(async () => {
|
|
1087
|
+
const result = await this.resolveLocatorElementBox(query, options);
|
|
1088
|
+
if (!result || !result.visible) {
|
|
1089
|
+
return null;
|
|
1090
|
+
}
|
|
1091
|
+
return result;
|
|
1092
|
+
}, { timeoutMs: options.timeoutMs ?? this.defaultTimeout, description: `${actionName} ${description}` });
|
|
1093
|
+
const centerX = box.x + box.width / 2;
|
|
1094
|
+
const centerY = box.y + box.height / 2;
|
|
1095
|
+
await this.session.send("Input.dispatchMouseEvent", { type: "mouseMoved", x: centerX, y: centerY });
|
|
1096
|
+
await this.session.send("Input.dispatchMouseEvent", { type: "mousePressed", x: centerX, y: centerY, button: "left", clickCount: 1, buttons: 1 });
|
|
1097
|
+
await this.session.send("Input.dispatchMouseEvent", { type: "mouseReleased", x: centerX, y: centerY, button: "left", clickCount: 1, buttons: 0 });
|
|
1098
|
+
if (isDouble) {
|
|
1099
|
+
await this.session.send("Input.dispatchMouseEvent", { type: "mouseMoved", x: centerX, y: centerY });
|
|
1100
|
+
await this.session.send("Input.dispatchMouseEvent", { type: "mousePressed", x: centerX, y: centerY, button: "left", clickCount: 2, buttons: 1 });
|
|
1101
|
+
await this.session.send("Input.dispatchMouseEvent", { type: "mouseReleased", x: centerX, y: centerY, button: "left", clickCount: 2, buttons: 0 });
|
|
1102
|
+
}
|
|
1103
|
+
const duration = Date.now() - start;
|
|
1104
|
+
this.events.emit("action:end", { name: actionName, selector: description, frameId: this.id, durationMs: duration });
|
|
1105
|
+
this.logger.debug("Click", description, `${duration}ms`);
|
|
1106
|
+
}
|
|
1107
|
+
locatorDescription(query) {
|
|
1108
|
+
switch (query.kind) {
|
|
1109
|
+
case "selector":
|
|
1110
|
+
return query.selector;
|
|
1111
|
+
case "text":
|
|
1112
|
+
return typeof query.text === "string" ? `text=${JSON.stringify(query.text)}` : `text=${query.text.toString()}`;
|
|
1113
|
+
case "role":
|
|
1114
|
+
return `role=${query.role}${query.options?.name ? ` name=${typeof query.options.name === "string" ? JSON.stringify(query.options.name) : query.options.name.toString()}` : ""}`;
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
async resolveLocatorElementBox(query, options) {
|
|
1118
|
+
return this.evalOnLocator(query, false, `
|
|
1119
|
+
if (!el) {
|
|
1120
|
+
return null;
|
|
1121
|
+
}
|
|
1122
|
+
el.scrollIntoView({ block: "center", inline: "center" });
|
|
1123
|
+
const rect = el.getBoundingClientRect();
|
|
1124
|
+
const style = window.getComputedStyle(el);
|
|
1125
|
+
return {
|
|
1126
|
+
x: rect.x,
|
|
1127
|
+
y: rect.y,
|
|
1128
|
+
width: rect.width,
|
|
1129
|
+
height: rect.height,
|
|
1130
|
+
visible: rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none" && Number(style.opacity || "1") > 0
|
|
1131
|
+
};
|
|
1132
|
+
`);
|
|
1133
|
+
}
|
|
1134
|
+
async evalOnLocator(query, all, body) {
|
|
1135
|
+
const expression = this.buildLocatorExpression(query, body, all);
|
|
1136
|
+
const params = {
|
|
1137
|
+
expression,
|
|
1138
|
+
returnByValue: true
|
|
1139
|
+
};
|
|
1140
|
+
if (this.contextId) {
|
|
1141
|
+
params.contextId = this.contextId;
|
|
1142
|
+
}
|
|
1143
|
+
const result = await this.session.send("Runtime.evaluate", params);
|
|
1144
|
+
return result.result.value;
|
|
1145
|
+
}
|
|
1146
|
+
buildLocatorExpression(query, body, all = false) {
|
|
1147
|
+
const helpers = serializeShadowDomHelpers();
|
|
1148
|
+
const serializedQuery = this.serializeLocatorQuery(query);
|
|
1149
|
+
return `(function() {
|
|
1150
|
+
const querySelectorAllDeep = ${helpers.querySelectorAllDeep};
|
|
1151
|
+
const normalizeWhitespace = (value) => String(value ?? "").replace(/\\s+/g, " ").trim();
|
|
1152
|
+
const cssEscape = (value) => {
|
|
1153
|
+
if (typeof CSS !== "undefined" && CSS.escape) return CSS.escape(value);
|
|
1154
|
+
return String(value).replace(/[^a-zA-Z0-9_-]/g, (c) => "\\\\" + c.charCodeAt(0).toString(16) + " ");
|
|
1155
|
+
};
|
|
1156
|
+
const textFromElement = (el) => {
|
|
1157
|
+
if (!el) return "";
|
|
1158
|
+
if (el instanceof HTMLInputElement) {
|
|
1159
|
+
const type = (el.getAttribute("type") || "").toLowerCase();
|
|
1160
|
+
if (type === "button" || type === "submit" || type === "reset" || type === "image") {
|
|
1161
|
+
return el.value || el.getAttribute("alt") || "";
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
return el.textContent || "";
|
|
1165
|
+
};
|
|
1166
|
+
const isHidden = (el) => {
|
|
1167
|
+
if (!el || !(el instanceof Element)) return true;
|
|
1168
|
+
const style = window.getComputedStyle(el);
|
|
1169
|
+
if (el.hidden || el.getAttribute("aria-hidden") === "true") return true;
|
|
1170
|
+
return style.display === "none" || style.visibility === "hidden" || Number(style.opacity || "1") <= 0;
|
|
1171
|
+
};
|
|
1172
|
+
const getLabelText = (el) => {
|
|
1173
|
+
if (!el || !(el instanceof Element)) return "";
|
|
1174
|
+
const ariaLabel = el.getAttribute("aria-label");
|
|
1175
|
+
if (ariaLabel) return normalizeWhitespace(ariaLabel);
|
|
1176
|
+
const labelledBy = el.getAttribute("aria-labelledby");
|
|
1177
|
+
if (labelledBy) {
|
|
1178
|
+
const parts = labelledBy.split(/\\s+/).filter(Boolean).map((id) => document.getElementById(id)?.textContent || "");
|
|
1179
|
+
const text = normalizeWhitespace(parts.join(" "));
|
|
1180
|
+
if (text) return text;
|
|
1181
|
+
}
|
|
1182
|
+
if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement) {
|
|
1183
|
+
if (el.id) {
|
|
1184
|
+
const label = document.querySelector("label[for="" + cssEscape(el.id) + ""]");
|
|
1185
|
+
if (label) {
|
|
1186
|
+
const text = normalizeWhitespace(label.textContent || "");
|
|
1187
|
+
if (text) return text;
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
const wrap = el.closest("label");
|
|
1191
|
+
if (wrap) {
|
|
1192
|
+
const text = normalizeWhitespace(wrap.textContent || "");
|
|
1193
|
+
if (text) return text;
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
if (el instanceof HTMLImageElement) {
|
|
1197
|
+
return normalizeWhitespace(el.getAttribute("alt") || el.getAttribute("title") || "");
|
|
1198
|
+
}
|
|
1199
|
+
return normalizeWhitespace(el.getAttribute("title") || "");
|
|
1200
|
+
};
|
|
1201
|
+
const getImplicitRole = (el) => {
|
|
1202
|
+
if (!el || !(el instanceof Element)) return "";
|
|
1203
|
+
const explicitRole = (el.getAttribute("role") || "").trim().split(/\\s+/)[0];
|
|
1204
|
+
if (explicitRole) return explicitRole;
|
|
1205
|
+
const tag = el.tagName.toLowerCase();
|
|
1206
|
+
if (tag === "button") return "button";
|
|
1207
|
+
if (tag === "summary") return "button";
|
|
1208
|
+
if (tag === "a" && el.hasAttribute("href")) return "link";
|
|
1209
|
+
if (tag === "input") {
|
|
1210
|
+
const type = (el.getAttribute("type") || "text").toLowerCase();
|
|
1211
|
+
if (type === "checkbox") return "checkbox";
|
|
1212
|
+
if (type === "radio") return "radio";
|
|
1213
|
+
if (type === "range") return "slider";
|
|
1214
|
+
if (type === "submit" || type === "button" || type === "reset") return "button";
|
|
1215
|
+
if (type === "file") return "button";
|
|
1216
|
+
return "textbox";
|
|
1217
|
+
}
|
|
1218
|
+
if (tag === "textarea") return "textbox";
|
|
1219
|
+
if (tag === "select") return "combobox";
|
|
1220
|
+
if (tag === "img") return "img";
|
|
1221
|
+
if (tag === "ul" || tag === "ol") return "list";
|
|
1222
|
+
if (tag === "li") return "listitem";
|
|
1223
|
+
if (tag === "table") return "table";
|
|
1224
|
+
if (tag === "tr") return "row";
|
|
1225
|
+
if (tag === "td") return "cell";
|
|
1226
|
+
if (tag === "th") return "columnheader";
|
|
1227
|
+
if (/^h[1-6]$/.test(tag)) return "heading";
|
|
1228
|
+
if (tag === "option") return "option";
|
|
1229
|
+
if (tag === "fieldset") return "group";
|
|
1230
|
+
if (tag === "form") return "form";
|
|
1231
|
+
if (el.hasAttribute("contenteditable")) return "textbox";
|
|
1232
|
+
return explicitRole;
|
|
1233
|
+
};
|
|
1234
|
+
const matchText = (actual, expected, exact) => {
|
|
1235
|
+
const normalizedActual = normalizeWhitespace(actual);
|
|
1236
|
+
if (expected && expected.kind === "regex") {
|
|
1237
|
+
const regex = new RegExp(expected.source, expected.flags);
|
|
1238
|
+
return regex.test(normalizedActual);
|
|
1239
|
+
}
|
|
1240
|
+
const normalizedExpected = normalizeWhitespace(expected.value);
|
|
1241
|
+
if (exact) {
|
|
1242
|
+
return normalizedActual === normalizedExpected;
|
|
1243
|
+
}
|
|
1244
|
+
return normalizedActual.toLowerCase().includes(normalizedExpected.toLowerCase());
|
|
1245
|
+
};
|
|
1246
|
+
const matchName = (actual, expected, exact) => {
|
|
1247
|
+
const normalizedActual = normalizeWhitespace(actual);
|
|
1248
|
+
if (expected && expected.kind === "regex") {
|
|
1249
|
+
const regex = new RegExp(expected.source, expected.flags);
|
|
1250
|
+
return regex.test(normalizedActual);
|
|
1251
|
+
}
|
|
1252
|
+
const normalizedExpected = normalizeWhitespace(expected.value);
|
|
1253
|
+
if (exact) {
|
|
1254
|
+
return normalizedActual === normalizedExpected;
|
|
1255
|
+
}
|
|
1256
|
+
return normalizedActual.toLowerCase().includes(normalizedExpected.toLowerCase());
|
|
1257
|
+
};
|
|
1258
|
+
const query = ${serializedQuery};
|
|
1259
|
+
const nodes = Array.from(querySelectorAllDeep(document, "*"));
|
|
1260
|
+
const selectorMatches = () => {
|
|
1261
|
+
if (query.kind !== "selector") {
|
|
1262
|
+
return [];
|
|
1263
|
+
}
|
|
1264
|
+
if (query.selector.includes(">>>")) {
|
|
1265
|
+
return querySelectorAllDeep(document, query.selector);
|
|
1266
|
+
}
|
|
1267
|
+
if (query.parsed?.type === "xpath") {
|
|
1268
|
+
const result = document.evaluate(query.parsed.value, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
|
|
1269
|
+
const list = [];
|
|
1270
|
+
for (let i = 0; i < result.snapshotLength; i += 1) {
|
|
1271
|
+
const item = result.snapshotItem(i);
|
|
1272
|
+
if (item instanceof Element) {
|
|
1273
|
+
list.push(item);
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
return list;
|
|
1277
|
+
}
|
|
1278
|
+
return Array.from(document.querySelectorAll(query.selector));
|
|
1279
|
+
};
|
|
1280
|
+
const selectorMatchSet = selectorMatches();
|
|
1281
|
+
const matches = nodes.filter((el) => {
|
|
1282
|
+
if (!(el instanceof Element)) return false;
|
|
1283
|
+
if (query.kind === "selector") {
|
|
1284
|
+
return selectorMatchSet.includes(el);
|
|
1285
|
+
}
|
|
1286
|
+
if (query.kind === "text") {
|
|
1287
|
+
const text = textFromElement(el);
|
|
1288
|
+
return matchText(text, query.text, Boolean(query.options?.exact));
|
|
1289
|
+
}
|
|
1290
|
+
const role = getImplicitRole(el);
|
|
1291
|
+
if (!role || role !== query.role) {
|
|
1292
|
+
return false;
|
|
1293
|
+
}
|
|
1294
|
+
if (!query.options?.includeHidden && isHidden(el)) {
|
|
1295
|
+
return false;
|
|
1296
|
+
}
|
|
1297
|
+
if (query.options?.name == null) {
|
|
1298
|
+
return true;
|
|
1299
|
+
}
|
|
1300
|
+
const name = getLabelText(el) || textFromElement(el);
|
|
1301
|
+
return matchName(name, query.options.name, Boolean(query.options?.exact));
|
|
1302
|
+
});
|
|
1303
|
+
const textMatches = query.kind === "text"
|
|
1304
|
+
? matches.filter((el) => !matches.some((other) => other !== el && el.contains(other)))
|
|
1305
|
+
: matches;
|
|
1306
|
+
const elements = ${all ? "textMatches" : "textMatches.slice(0, 1)"};
|
|
1307
|
+
const el = elements[0] || null;
|
|
1308
|
+
${body}
|
|
1309
|
+
})()`;
|
|
1310
|
+
}
|
|
1311
|
+
serializeLocatorQuery(query) {
|
|
1312
|
+
switch (query.kind) {
|
|
1313
|
+
case "selector":
|
|
1314
|
+
return JSON.stringify({ kind: "selector", selector: query.selector, options: query.options, parsed: parseSelector(query.selector) });
|
|
1315
|
+
case "text":
|
|
1316
|
+
return JSON.stringify({
|
|
1317
|
+
kind: "text",
|
|
1318
|
+
text: this.serializeTextQuery(query.text),
|
|
1319
|
+
options: query.options
|
|
1320
|
+
});
|
|
1321
|
+
case "role":
|
|
1322
|
+
return JSON.stringify({
|
|
1323
|
+
kind: "role",
|
|
1324
|
+
role: query.role,
|
|
1325
|
+
options: query.options ? {
|
|
1326
|
+
...query.options,
|
|
1327
|
+
name: query.options.name != null ? this.serializeTextQuery(query.options.name) : void 0
|
|
1328
|
+
} : void 0
|
|
1329
|
+
});
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
serializeTextQuery(text) {
|
|
1333
|
+
if (text instanceof RegExp) {
|
|
1334
|
+
return { kind: "regex", source: text.source, flags: text.flags.replace("g", "") };
|
|
1335
|
+
}
|
|
1336
|
+
return { kind: "string", value: text };
|
|
1337
|
+
}
|
|
852
1338
|
async performClick(selector, options, isDouble) {
|
|
853
1339
|
const start = Date.now();
|
|
854
1340
|
const actionName = isDouble ? "dblclick" : "click";
|
|
@@ -1069,6 +1555,11 @@ function ensureAllowedUrl(url, options = {}) {
|
|
|
1069
1555
|
}
|
|
1070
1556
|
|
|
1071
1557
|
// src/core/Page.ts
|
|
1558
|
+
function assertPdfBuffer(buffer) {
|
|
1559
|
+
if (buffer.length < 5 || buffer.subarray(0, 5).toString("utf-8") !== "%PDF-") {
|
|
1560
|
+
throw new Error("PDF generation failed: Chromium did not return a valid PDF");
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1072
1563
|
var Page = class {
|
|
1073
1564
|
session;
|
|
1074
1565
|
logger;
|
|
@@ -1121,7 +1612,13 @@ var Page = class {
|
|
|
1121
1612
|
return null;
|
|
1122
1613
|
}
|
|
1123
1614
|
locator(selector) {
|
|
1124
|
-
return
|
|
1615
|
+
return this.mainFrame().locator(selector);
|
|
1616
|
+
}
|
|
1617
|
+
getByText(text, options = {}) {
|
|
1618
|
+
return this.mainFrame().getByText(text, options);
|
|
1619
|
+
}
|
|
1620
|
+
getByRole(role, options = {}) {
|
|
1621
|
+
return this.mainFrame().getByRole(role, options);
|
|
1125
1622
|
}
|
|
1126
1623
|
async goto(url, options = {}) {
|
|
1127
1624
|
ensureAllowedUrl(url, { allowFileUrl: options.allowFileUrl });
|
|
@@ -1188,9 +1685,19 @@ var Page = class {
|
|
|
1188
1685
|
async findLocators(options = {}) {
|
|
1189
1686
|
return this.mainFrame().findLocators(options);
|
|
1190
1687
|
}
|
|
1688
|
+
async content() {
|
|
1689
|
+
await this.waitForLoad();
|
|
1690
|
+
return this.mainFrame().evaluate(() => {
|
|
1691
|
+
const doctype = document.doctype;
|
|
1692
|
+
const doctypeText = doctype ? `<!DOCTYPE ${doctype.name}${doctype.publicId ? ` PUBLIC "${doctype.publicId}"` : ""}${doctype.systemId ? ` "${doctype.systemId}"` : ""}>` : "<!doctype html>";
|
|
1693
|
+
return `${doctypeText}
|
|
1694
|
+
${document.documentElement.outerHTML}`;
|
|
1695
|
+
});
|
|
1696
|
+
}
|
|
1191
1697
|
async screenshot(options = {}) {
|
|
1192
1698
|
const start = Date.now();
|
|
1193
1699
|
this.events.emit("action:start", { name: "screenshot", frameId: this.mainFrameId });
|
|
1700
|
+
await this.waitForLoad();
|
|
1194
1701
|
const result = await this.session.send("Page.captureScreenshot", {
|
|
1195
1702
|
format: options.format ?? "png",
|
|
1196
1703
|
quality: options.quality,
|
|
@@ -1209,6 +1716,7 @@ var Page = class {
|
|
|
1209
1716
|
async screenshotBase64(options = {}) {
|
|
1210
1717
|
const start = Date.now();
|
|
1211
1718
|
this.events.emit("action:start", { name: "screenshotBase64", frameId: this.mainFrameId });
|
|
1719
|
+
await this.waitForLoad();
|
|
1212
1720
|
const result = await this.session.send("Page.captureScreenshot", {
|
|
1213
1721
|
format: options.format ?? "png",
|
|
1214
1722
|
quality: options.quality,
|
|
@@ -1222,26 +1730,37 @@ var Page = class {
|
|
|
1222
1730
|
async pdf(options = {}) {
|
|
1223
1731
|
const start = Date.now();
|
|
1224
1732
|
this.events.emit("action:start", { name: "pdf", frameId: this.mainFrameId });
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1733
|
+
await this.waitForLoad();
|
|
1734
|
+
await this.session.send("Emulation.setEmulatedMedia", { media: "screen" });
|
|
1735
|
+
let buffer;
|
|
1736
|
+
try {
|
|
1737
|
+
const result = await this.session.send("Page.printToPDF", {
|
|
1738
|
+
landscape: options.landscape ?? false,
|
|
1739
|
+
printBackground: options.printBackground ?? true,
|
|
1740
|
+
scale: options.scale,
|
|
1741
|
+
paperWidth: options.paperWidth,
|
|
1742
|
+
paperHeight: options.paperHeight,
|
|
1743
|
+
marginTop: options.marginTop,
|
|
1744
|
+
marginBottom: options.marginBottom,
|
|
1745
|
+
marginLeft: options.marginLeft,
|
|
1746
|
+
marginRight: options.marginRight,
|
|
1747
|
+
pageRanges: options.pageRanges,
|
|
1748
|
+
preferCSSPageSize: options.preferCSSPageSize
|
|
1749
|
+
});
|
|
1750
|
+
buffer = Buffer.from(result.data, "base64");
|
|
1751
|
+
assertPdfBuffer(buffer);
|
|
1752
|
+
if (options.path) {
|
|
1753
|
+
const resolved = path2.resolve(options.path);
|
|
1754
|
+
fs2.writeFileSync(resolved, buffer);
|
|
1755
|
+
}
|
|
1756
|
+
} finally {
|
|
1757
|
+
try {
|
|
1758
|
+
await this.session.send("Emulation.setEmulatedMedia", { media: "" });
|
|
1759
|
+
} catch {
|
|
1760
|
+
}
|
|
1761
|
+
const duration = Date.now() - start;
|
|
1762
|
+
this.events.emit("action:end", { name: "pdf", frameId: this.mainFrameId, durationMs: duration });
|
|
1242
1763
|
}
|
|
1243
|
-
const duration = Date.now() - start;
|
|
1244
|
-
this.events.emit("action:end", { name: "pdf", frameId: this.mainFrameId, durationMs: duration });
|
|
1245
1764
|
return buffer;
|
|
1246
1765
|
}
|
|
1247
1766
|
getEvents() {
|
|
@@ -1250,6 +1769,13 @@ var Page = class {
|
|
|
1250
1769
|
getDefaultTimeout() {
|
|
1251
1770
|
return this.defaultTimeout;
|
|
1252
1771
|
}
|
|
1772
|
+
async waitForLoad(timeoutMs = this.defaultTimeout) {
|
|
1773
|
+
const frame = this.mainFrame();
|
|
1774
|
+
await waitFor(async () => {
|
|
1775
|
+
const readyState = await frame.evaluate("document.readyState");
|
|
1776
|
+
return readyState === "complete";
|
|
1777
|
+
}, { timeoutMs, description: "page load" });
|
|
1778
|
+
}
|
|
1253
1779
|
buildFrameTree(tree) {
|
|
1254
1780
|
const frame = this.ensureFrame(tree.frame.id);
|
|
1255
1781
|
frame.setMeta({ name: tree.frame.name, url: tree.frame.url, parentId: tree.frame.parentId });
|
|
@@ -1328,42 +1854,42 @@ var Page = class {
|
|
|
1328
1854
|
|
|
1329
1855
|
// src/assert/expect.ts
|
|
1330
1856
|
var ElementExpectation = class _ElementExpectation {
|
|
1331
|
-
|
|
1332
|
-
selector;
|
|
1857
|
+
target;
|
|
1333
1858
|
options;
|
|
1334
1859
|
negate;
|
|
1335
1860
|
events;
|
|
1336
|
-
constructor(
|
|
1337
|
-
this.
|
|
1338
|
-
this.selector = selector;
|
|
1861
|
+
constructor(target, options, negate, events) {
|
|
1862
|
+
this.target = target;
|
|
1339
1863
|
this.options = options;
|
|
1340
1864
|
this.negate = negate;
|
|
1341
1865
|
this.events = events;
|
|
1342
1866
|
}
|
|
1343
1867
|
get not() {
|
|
1344
|
-
return new _ElementExpectation(this.
|
|
1868
|
+
return new _ElementExpectation(this.target, this.options, !this.negate, this.events);
|
|
1345
1869
|
}
|
|
1346
1870
|
async toExist() {
|
|
1347
1871
|
return this.assert(async () => {
|
|
1348
|
-
const exists = await this.
|
|
1872
|
+
const exists = await this.target.exists();
|
|
1349
1873
|
return this.negate ? !exists : exists;
|
|
1350
1874
|
}, this.negate ? "Expected element not to exist" : "Expected element to exist");
|
|
1351
1875
|
}
|
|
1352
1876
|
async toBeVisible() {
|
|
1353
1877
|
return this.assert(async () => {
|
|
1354
|
-
const visible = await this.
|
|
1878
|
+
const visible = await this.target.isVisible();
|
|
1879
|
+
if (visible == null) return this.negate ? true : false;
|
|
1355
1880
|
return this.negate ? !visible : visible;
|
|
1356
1881
|
}, this.negate ? "Expected element not to be visible" : "Expected element to be visible");
|
|
1357
1882
|
}
|
|
1358
1883
|
async toBeHidden() {
|
|
1359
1884
|
return this.assert(async () => {
|
|
1360
|
-
const visible = await this.
|
|
1885
|
+
const visible = await this.target.isVisible();
|
|
1886
|
+
if (visible == null) return this.negate ? true : false;
|
|
1361
1887
|
return this.negate ? visible : !visible;
|
|
1362
1888
|
}, this.negate ? "Expected element not to be hidden" : "Expected element to be hidden");
|
|
1363
1889
|
}
|
|
1364
1890
|
async toBeEnabled() {
|
|
1365
1891
|
return this.assert(async () => {
|
|
1366
|
-
const enabled = await this.
|
|
1892
|
+
const enabled = await this.target.isEnabled();
|
|
1367
1893
|
if (enabled == null) {
|
|
1368
1894
|
return this.negate ? true : false;
|
|
1369
1895
|
}
|
|
@@ -1372,7 +1898,7 @@ var ElementExpectation = class _ElementExpectation {
|
|
|
1372
1898
|
}
|
|
1373
1899
|
async toBeDisabled() {
|
|
1374
1900
|
return this.assert(async () => {
|
|
1375
|
-
const enabled = await this.
|
|
1901
|
+
const enabled = await this.target.isEnabled();
|
|
1376
1902
|
if (enabled == null) {
|
|
1377
1903
|
return this.negate ? true : false;
|
|
1378
1904
|
}
|
|
@@ -1382,7 +1908,7 @@ var ElementExpectation = class _ElementExpectation {
|
|
|
1382
1908
|
}
|
|
1383
1909
|
async toBeChecked() {
|
|
1384
1910
|
return this.assert(async () => {
|
|
1385
|
-
const checked = await this.
|
|
1911
|
+
const checked = await this.target.isChecked();
|
|
1386
1912
|
if (checked == null) {
|
|
1387
1913
|
return this.negate ? true : false;
|
|
1388
1914
|
}
|
|
@@ -1391,7 +1917,7 @@ var ElementExpectation = class _ElementExpectation {
|
|
|
1391
1917
|
}
|
|
1392
1918
|
async toBeUnchecked() {
|
|
1393
1919
|
return this.assert(async () => {
|
|
1394
|
-
const checked = await this.
|
|
1920
|
+
const checked = await this.target.isChecked();
|
|
1395
1921
|
if (checked == null) {
|
|
1396
1922
|
return this.negate ? true : false;
|
|
1397
1923
|
}
|
|
@@ -1402,7 +1928,7 @@ var ElementExpectation = class _ElementExpectation {
|
|
|
1402
1928
|
async toHaveText(textOrRegex) {
|
|
1403
1929
|
const expected = textOrRegex;
|
|
1404
1930
|
return this.assert(async () => {
|
|
1405
|
-
const text = await this.
|
|
1931
|
+
const text = await this.target.text();
|
|
1406
1932
|
if (text == null) {
|
|
1407
1933
|
return this.negate ? true : false;
|
|
1408
1934
|
}
|
|
@@ -1413,7 +1939,7 @@ var ElementExpectation = class _ElementExpectation {
|
|
|
1413
1939
|
async toHaveExactText(textOrRegex) {
|
|
1414
1940
|
const expected = textOrRegex;
|
|
1415
1941
|
return this.assert(async () => {
|
|
1416
|
-
const text = await this.
|
|
1942
|
+
const text = await this.target.text();
|
|
1417
1943
|
if (text == null) {
|
|
1418
1944
|
return this.negate ? true : false;
|
|
1419
1945
|
}
|
|
@@ -1424,7 +1950,7 @@ var ElementExpectation = class _ElementExpectation {
|
|
|
1424
1950
|
async toContainText(textOrRegex) {
|
|
1425
1951
|
const expected = textOrRegex;
|
|
1426
1952
|
return this.assert(async () => {
|
|
1427
|
-
const text = await this.
|
|
1953
|
+
const text = await this.target.text();
|
|
1428
1954
|
if (text == null) {
|
|
1429
1955
|
return this.negate ? true : false;
|
|
1430
1956
|
}
|
|
@@ -1435,7 +1961,7 @@ var ElementExpectation = class _ElementExpectation {
|
|
|
1435
1961
|
async toHaveValue(valueOrRegex) {
|
|
1436
1962
|
const expected = valueOrRegex;
|
|
1437
1963
|
return this.assert(async () => {
|
|
1438
|
-
const value = await this.
|
|
1964
|
+
const value = await this.target.value();
|
|
1439
1965
|
if (value == null) {
|
|
1440
1966
|
return this.negate ? true : false;
|
|
1441
1967
|
}
|
|
@@ -1446,7 +1972,7 @@ var ElementExpectation = class _ElementExpectation {
|
|
|
1446
1972
|
async toHaveAttribute(name, valueOrRegex) {
|
|
1447
1973
|
const expected = valueOrRegex;
|
|
1448
1974
|
return this.assert(async () => {
|
|
1449
|
-
const value = await this.
|
|
1975
|
+
const value = await this.target.attribute(name);
|
|
1450
1976
|
if (expected === void 0) {
|
|
1451
1977
|
const exists = value != null;
|
|
1452
1978
|
return this.negate ? !exists : exists;
|
|
@@ -1466,7 +1992,7 @@ var ElementExpectation = class _ElementExpectation {
|
|
|
1466
1992
|
}
|
|
1467
1993
|
async toHaveCount(expected) {
|
|
1468
1994
|
return this.assert(async () => {
|
|
1469
|
-
const count = await this.
|
|
1995
|
+
const count = await this.target.count();
|
|
1470
1996
|
const matches = count === expected;
|
|
1471
1997
|
return this.negate ? !matches : matches;
|
|
1472
1998
|
}, this.negate ? "Expected element count not to match" : "Expected element count to match", { expected });
|
|
@@ -1474,7 +2000,7 @@ var ElementExpectation = class _ElementExpectation {
|
|
|
1474
2000
|
async toHaveClass(nameOrRegex) {
|
|
1475
2001
|
const expected = nameOrRegex;
|
|
1476
2002
|
return this.assert(async () => {
|
|
1477
|
-
const classes = await this.
|
|
2003
|
+
const classes = await this.target.classes();
|
|
1478
2004
|
if (classes == null) {
|
|
1479
2005
|
return this.negate ? true : false;
|
|
1480
2006
|
}
|
|
@@ -1484,7 +2010,7 @@ var ElementExpectation = class _ElementExpectation {
|
|
|
1484
2010
|
}
|
|
1485
2011
|
async toHaveClasses(expected) {
|
|
1486
2012
|
return this.assert(async () => {
|
|
1487
|
-
const classes = await this.
|
|
2013
|
+
const classes = await this.target.classes();
|
|
1488
2014
|
if (classes == null) {
|
|
1489
2015
|
return this.negate ? true : false;
|
|
1490
2016
|
}
|
|
@@ -1495,7 +2021,7 @@ var ElementExpectation = class _ElementExpectation {
|
|
|
1495
2021
|
async toHaveCss(property, valueOrRegex) {
|
|
1496
2022
|
const expected = valueOrRegex;
|
|
1497
2023
|
return this.assert(async () => {
|
|
1498
|
-
const value = await this.
|
|
2024
|
+
const value = await this.target.css(property);
|
|
1499
2025
|
if (value == null) {
|
|
1500
2026
|
return this.negate ? true : false;
|
|
1501
2027
|
}
|
|
@@ -1506,7 +2032,7 @@ var ElementExpectation = class _ElementExpectation {
|
|
|
1506
2032
|
}
|
|
1507
2033
|
async toHaveFocus() {
|
|
1508
2034
|
return this.assert(async () => {
|
|
1509
|
-
const focused = await this.
|
|
2035
|
+
const focused = await this.target.hasFocus();
|
|
1510
2036
|
if (focused == null) {
|
|
1511
2037
|
return this.negate ? true : false;
|
|
1512
2038
|
}
|
|
@@ -1515,7 +2041,7 @@ var ElementExpectation = class _ElementExpectation {
|
|
|
1515
2041
|
}
|
|
1516
2042
|
async toBeInViewport(options = {}) {
|
|
1517
2043
|
return this.assert(async () => {
|
|
1518
|
-
const inViewport = await this.
|
|
2044
|
+
const inViewport = await this.target.isInViewport(Boolean(options.fully));
|
|
1519
2045
|
if (inViewport == null) {
|
|
1520
2046
|
return this.negate ? true : false;
|
|
1521
2047
|
}
|
|
@@ -1524,7 +2050,7 @@ var ElementExpectation = class _ElementExpectation {
|
|
|
1524
2050
|
}
|
|
1525
2051
|
async toBeEditable() {
|
|
1526
2052
|
return this.assert(async () => {
|
|
1527
|
-
const editable = await this.
|
|
2053
|
+
const editable = await this.target.isEditable();
|
|
1528
2054
|
if (editable == null) {
|
|
1529
2055
|
return this.negate ? true : false;
|
|
1530
2056
|
}
|
|
@@ -1534,7 +2060,7 @@ var ElementExpectation = class _ElementExpectation {
|
|
|
1534
2060
|
async assert(predicate, message, details = {}) {
|
|
1535
2061
|
const timeoutMs = this.options.timeoutMs ?? 3e4;
|
|
1536
2062
|
const start = Date.now();
|
|
1537
|
-
this.events.emit("assertion:start", { name: message, selector: this.
|
|
2063
|
+
this.events.emit("assertion:start", { name: message, selector: this.target.label, frameId: this.target.frameId });
|
|
1538
2064
|
let lastState;
|
|
1539
2065
|
try {
|
|
1540
2066
|
await waitFor(async () => {
|
|
@@ -1544,13 +2070,51 @@ var ElementExpectation = class _ElementExpectation {
|
|
|
1544
2070
|
}, { timeoutMs, description: message });
|
|
1545
2071
|
} catch {
|
|
1546
2072
|
const duration2 = Date.now() - start;
|
|
1547
|
-
this.events.emit("assertion:end", { name: message, selector: this.
|
|
1548
|
-
throw new AssertionError(message, { selector: this.
|
|
2073
|
+
this.events.emit("assertion:end", { name: message, selector: this.target.label, frameId: this.target.frameId, durationMs: duration2, status: "failed" });
|
|
2074
|
+
throw new AssertionError(message, { selector: this.target.label, timeoutMs, lastState: { lastState, ...details } });
|
|
1549
2075
|
}
|
|
1550
2076
|
const duration = Date.now() - start;
|
|
1551
|
-
this.events.emit("assertion:end", { name: message, selector: this.
|
|
2077
|
+
this.events.emit("assertion:end", { name: message, selector: this.target.label, frameId: this.target.frameId, durationMs: duration, status: "passed" });
|
|
1552
2078
|
}
|
|
1553
2079
|
};
|
|
2080
|
+
function frameTarget(frame, selector, options) {
|
|
2081
|
+
return {
|
|
2082
|
+
label: selector,
|
|
2083
|
+
frameId: frame.id,
|
|
2084
|
+
exists: () => frame.exists(selector, options),
|
|
2085
|
+
isVisible: () => frame.isVisible(selector, options),
|
|
2086
|
+
isEnabled: () => frame.isEnabled(selector, options),
|
|
2087
|
+
isChecked: () => frame.isChecked(selector, options),
|
|
2088
|
+
text: () => frame.text(selector, options),
|
|
2089
|
+
value: () => frame.value(selector, options),
|
|
2090
|
+
attribute: (name) => frame.attribute(selector, name, options),
|
|
2091
|
+
count: () => frame.count(selector, options),
|
|
2092
|
+
classes: () => frame.classes(selector, options),
|
|
2093
|
+
css: (property) => frame.css(selector, property, options),
|
|
2094
|
+
hasFocus: () => frame.hasFocus(selector, options),
|
|
2095
|
+
isInViewport: (fully = false) => frame.isInViewport(selector, options, fully),
|
|
2096
|
+
isEditable: () => frame.isEditable(selector, options)
|
|
2097
|
+
};
|
|
2098
|
+
}
|
|
2099
|
+
function locatorTarget(locator) {
|
|
2100
|
+
return {
|
|
2101
|
+
label: locator.describe(),
|
|
2102
|
+
frameId: locator.getFrameId(),
|
|
2103
|
+
exists: () => locator.exists(),
|
|
2104
|
+
isVisible: () => locator.isVisible(),
|
|
2105
|
+
isEnabled: () => locator.isEnabled(),
|
|
2106
|
+
isChecked: () => locator.isChecked(),
|
|
2107
|
+
text: () => locator.text(),
|
|
2108
|
+
value: () => locator.value(),
|
|
2109
|
+
attribute: (name) => locator.attribute(name),
|
|
2110
|
+
count: () => locator.count(),
|
|
2111
|
+
classes: () => locator.classes(),
|
|
2112
|
+
css: (property) => locator.css(property),
|
|
2113
|
+
hasFocus: () => locator.hasFocus(),
|
|
2114
|
+
isInViewport: (fully = false) => locator.isInViewport(fully),
|
|
2115
|
+
isEditable: () => locator.isEditable()
|
|
2116
|
+
};
|
|
2117
|
+
}
|
|
1554
2118
|
var ExpectFrame = class {
|
|
1555
2119
|
frame;
|
|
1556
2120
|
events;
|
|
@@ -1559,25 +2123,31 @@ var ExpectFrame = class {
|
|
|
1559
2123
|
this.events = events;
|
|
1560
2124
|
}
|
|
1561
2125
|
element(selector, options = {}) {
|
|
1562
|
-
return new ElementExpectation(this.frame, selector, options, false, this.events);
|
|
2126
|
+
return new ElementExpectation(frameTarget(this.frame, selector, options), options, false, this.events);
|
|
1563
2127
|
}
|
|
1564
2128
|
};
|
|
1565
|
-
function expect(
|
|
2129
|
+
function expect(target) {
|
|
2130
|
+
if (target instanceof Locator) {
|
|
2131
|
+
return new ElementExpectation(locatorTarget(target), {}, false, target.getEvents());
|
|
2132
|
+
}
|
|
1566
2133
|
return {
|
|
1567
|
-
element: (selector, options = {}) => new ElementExpectation(
|
|
2134
|
+
element: (selector, options = {}) => new ElementExpectation(frameTarget(target.mainFrame(), selector, options), options, false, target.getEvents()),
|
|
1568
2135
|
frame: (options) => {
|
|
1569
|
-
const frame =
|
|
2136
|
+
const frame = target.frame(options);
|
|
1570
2137
|
if (!frame) {
|
|
1571
2138
|
throw new AssertionError("Frame not found", { selector: JSON.stringify(options) });
|
|
1572
2139
|
}
|
|
1573
|
-
return new ExpectFrame(frame,
|
|
2140
|
+
return new ExpectFrame(frame, target.getEvents());
|
|
1574
2141
|
}
|
|
1575
2142
|
};
|
|
1576
2143
|
}
|
|
1577
|
-
Page.prototype.expect = function(
|
|
2144
|
+
Page.prototype.expect = function(target, options) {
|
|
1578
2145
|
const builder = expect(this);
|
|
1579
|
-
if (
|
|
1580
|
-
return
|
|
2146
|
+
if (target instanceof Locator) {
|
|
2147
|
+
return expect(target);
|
|
2148
|
+
}
|
|
2149
|
+
if (target) {
|
|
2150
|
+
return builder.element(target, options);
|
|
1581
2151
|
}
|
|
1582
2152
|
return builder;
|
|
1583
2153
|
};
|
|
@@ -1596,4 +2166,4 @@ export {
|
|
|
1596
2166
|
AssertionError,
|
|
1597
2167
|
expect
|
|
1598
2168
|
};
|
|
1599
|
-
//# sourceMappingURL=chunk-
|
|
2169
|
+
//# sourceMappingURL=chunk-EIAXMGD5.js.map
|