@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.
@@ -165,27 +165,77 @@ function serializeShadowDomHelpers() {
165
165
  // src/core/Locator.ts
166
166
  var Locator = class {
167
167
  frame;
168
- selector;
169
- options;
170
- constructor(frame, selector, options = {}) {
168
+ query;
169
+ constructor(frame, query) {
171
170
  this.frame = frame;
172
- this.selector = selector;
173
- this.options = options;
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.click(this.selector, { ...this.options, ...options });
190
+ return this.frame.clickLocator(this.query, { ...this.queryTimeoutOptions(), ...options });
177
191
  }
178
192
  async dblclick(options = {}) {
179
- return this.frame.dblclick(this.selector, { ...this.options, ...options });
193
+ return this.frame.dblclickLocator(this.query, { ...this.queryTimeoutOptions(), ...options });
180
194
  }
181
195
  async type(text, options = {}) {
182
- return this.frame.type(this.selector, text, { ...this.options, ...options });
196
+ return this.frame.typeLocator(this.query, text, { ...this.queryTimeoutOptions(), ...options });
183
197
  }
184
198
  async exists() {
185
- return this.frame.exists(this.selector, this.options);
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.text(this.selector, this.options);
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";
@@ -1126,7 +1612,13 @@ var Page = class {
1126
1612
  return null;
1127
1613
  }
1128
1614
  locator(selector) {
1129
- return new Locator(this.mainFrame(), selector);
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);
1130
1622
  }
1131
1623
  async goto(url, options = {}) {
1132
1624
  ensureAllowedUrl(url, { allowFileUrl: options.allowFileUrl });
@@ -1362,42 +1854,42 @@ ${document.documentElement.outerHTML}`;
1362
1854
 
1363
1855
  // src/assert/expect.ts
1364
1856
  var ElementExpectation = class _ElementExpectation {
1365
- frame;
1366
- selector;
1857
+ target;
1367
1858
  options;
1368
1859
  negate;
1369
1860
  events;
1370
- constructor(frame, selector, options, negate, events) {
1371
- this.frame = frame;
1372
- this.selector = selector;
1861
+ constructor(target, options, negate, events) {
1862
+ this.target = target;
1373
1863
  this.options = options;
1374
1864
  this.negate = negate;
1375
1865
  this.events = events;
1376
1866
  }
1377
1867
  get not() {
1378
- return new _ElementExpectation(this.frame, this.selector, this.options, !this.negate, this.events);
1868
+ return new _ElementExpectation(this.target, this.options, !this.negate, this.events);
1379
1869
  }
1380
1870
  async toExist() {
1381
1871
  return this.assert(async () => {
1382
- const exists = await this.frame.exists(this.selector, this.options);
1872
+ const exists = await this.target.exists();
1383
1873
  return this.negate ? !exists : exists;
1384
1874
  }, this.negate ? "Expected element not to exist" : "Expected element to exist");
1385
1875
  }
1386
1876
  async toBeVisible() {
1387
1877
  return this.assert(async () => {
1388
- const visible = await this.frame.isVisible(this.selector, this.options);
1878
+ const visible = await this.target.isVisible();
1879
+ if (visible == null) return this.negate ? true : false;
1389
1880
  return this.negate ? !visible : visible;
1390
1881
  }, this.negate ? "Expected element not to be visible" : "Expected element to be visible");
1391
1882
  }
1392
1883
  async toBeHidden() {
1393
1884
  return this.assert(async () => {
1394
- const visible = await this.frame.isVisible(this.selector, this.options);
1885
+ const visible = await this.target.isVisible();
1886
+ if (visible == null) return this.negate ? true : false;
1395
1887
  return this.negate ? visible : !visible;
1396
1888
  }, this.negate ? "Expected element not to be hidden" : "Expected element to be hidden");
1397
1889
  }
1398
1890
  async toBeEnabled() {
1399
1891
  return this.assert(async () => {
1400
- const enabled = await this.frame.isEnabled(this.selector, this.options);
1892
+ const enabled = await this.target.isEnabled();
1401
1893
  if (enabled == null) {
1402
1894
  return this.negate ? true : false;
1403
1895
  }
@@ -1406,7 +1898,7 @@ var ElementExpectation = class _ElementExpectation {
1406
1898
  }
1407
1899
  async toBeDisabled() {
1408
1900
  return this.assert(async () => {
1409
- const enabled = await this.frame.isEnabled(this.selector, this.options);
1901
+ const enabled = await this.target.isEnabled();
1410
1902
  if (enabled == null) {
1411
1903
  return this.negate ? true : false;
1412
1904
  }
@@ -1416,7 +1908,7 @@ var ElementExpectation = class _ElementExpectation {
1416
1908
  }
1417
1909
  async toBeChecked() {
1418
1910
  return this.assert(async () => {
1419
- const checked = await this.frame.isChecked(this.selector, this.options);
1911
+ const checked = await this.target.isChecked();
1420
1912
  if (checked == null) {
1421
1913
  return this.negate ? true : false;
1422
1914
  }
@@ -1425,7 +1917,7 @@ var ElementExpectation = class _ElementExpectation {
1425
1917
  }
1426
1918
  async toBeUnchecked() {
1427
1919
  return this.assert(async () => {
1428
- const checked = await this.frame.isChecked(this.selector, this.options);
1920
+ const checked = await this.target.isChecked();
1429
1921
  if (checked == null) {
1430
1922
  return this.negate ? true : false;
1431
1923
  }
@@ -1436,7 +1928,7 @@ var ElementExpectation = class _ElementExpectation {
1436
1928
  async toHaveText(textOrRegex) {
1437
1929
  const expected = textOrRegex;
1438
1930
  return this.assert(async () => {
1439
- const text = await this.frame.text(this.selector, this.options);
1931
+ const text = await this.target.text();
1440
1932
  if (text == null) {
1441
1933
  return this.negate ? true : false;
1442
1934
  }
@@ -1447,7 +1939,7 @@ var ElementExpectation = class _ElementExpectation {
1447
1939
  async toHaveExactText(textOrRegex) {
1448
1940
  const expected = textOrRegex;
1449
1941
  return this.assert(async () => {
1450
- const text = await this.frame.text(this.selector, this.options);
1942
+ const text = await this.target.text();
1451
1943
  if (text == null) {
1452
1944
  return this.negate ? true : false;
1453
1945
  }
@@ -1458,7 +1950,7 @@ var ElementExpectation = class _ElementExpectation {
1458
1950
  async toContainText(textOrRegex) {
1459
1951
  const expected = textOrRegex;
1460
1952
  return this.assert(async () => {
1461
- const text = await this.frame.text(this.selector, this.options);
1953
+ const text = await this.target.text();
1462
1954
  if (text == null) {
1463
1955
  return this.negate ? true : false;
1464
1956
  }
@@ -1469,7 +1961,7 @@ var ElementExpectation = class _ElementExpectation {
1469
1961
  async toHaveValue(valueOrRegex) {
1470
1962
  const expected = valueOrRegex;
1471
1963
  return this.assert(async () => {
1472
- const value = await this.frame.value(this.selector, this.options);
1964
+ const value = await this.target.value();
1473
1965
  if (value == null) {
1474
1966
  return this.negate ? true : false;
1475
1967
  }
@@ -1480,7 +1972,7 @@ var ElementExpectation = class _ElementExpectation {
1480
1972
  async toHaveAttribute(name, valueOrRegex) {
1481
1973
  const expected = valueOrRegex;
1482
1974
  return this.assert(async () => {
1483
- const value = await this.frame.attribute(this.selector, name, this.options);
1975
+ const value = await this.target.attribute(name);
1484
1976
  if (expected === void 0) {
1485
1977
  const exists = value != null;
1486
1978
  return this.negate ? !exists : exists;
@@ -1500,7 +1992,7 @@ var ElementExpectation = class _ElementExpectation {
1500
1992
  }
1501
1993
  async toHaveCount(expected) {
1502
1994
  return this.assert(async () => {
1503
- const count = await this.frame.count(this.selector, this.options);
1995
+ const count = await this.target.count();
1504
1996
  const matches = count === expected;
1505
1997
  return this.negate ? !matches : matches;
1506
1998
  }, this.negate ? "Expected element count not to match" : "Expected element count to match", { expected });
@@ -1508,7 +2000,7 @@ var ElementExpectation = class _ElementExpectation {
1508
2000
  async toHaveClass(nameOrRegex) {
1509
2001
  const expected = nameOrRegex;
1510
2002
  return this.assert(async () => {
1511
- const classes = await this.frame.classes(this.selector, this.options);
2003
+ const classes = await this.target.classes();
1512
2004
  if (classes == null) {
1513
2005
  return this.negate ? true : false;
1514
2006
  }
@@ -1518,7 +2010,7 @@ var ElementExpectation = class _ElementExpectation {
1518
2010
  }
1519
2011
  async toHaveClasses(expected) {
1520
2012
  return this.assert(async () => {
1521
- const classes = await this.frame.classes(this.selector, this.options);
2013
+ const classes = await this.target.classes();
1522
2014
  if (classes == null) {
1523
2015
  return this.negate ? true : false;
1524
2016
  }
@@ -1529,7 +2021,7 @@ var ElementExpectation = class _ElementExpectation {
1529
2021
  async toHaveCss(property, valueOrRegex) {
1530
2022
  const expected = valueOrRegex;
1531
2023
  return this.assert(async () => {
1532
- const value = await this.frame.css(this.selector, property, this.options);
2024
+ const value = await this.target.css(property);
1533
2025
  if (value == null) {
1534
2026
  return this.negate ? true : false;
1535
2027
  }
@@ -1540,7 +2032,7 @@ var ElementExpectation = class _ElementExpectation {
1540
2032
  }
1541
2033
  async toHaveFocus() {
1542
2034
  return this.assert(async () => {
1543
- const focused = await this.frame.hasFocus(this.selector, this.options);
2035
+ const focused = await this.target.hasFocus();
1544
2036
  if (focused == null) {
1545
2037
  return this.negate ? true : false;
1546
2038
  }
@@ -1549,7 +2041,7 @@ var ElementExpectation = class _ElementExpectation {
1549
2041
  }
1550
2042
  async toBeInViewport(options = {}) {
1551
2043
  return this.assert(async () => {
1552
- const inViewport = await this.frame.isInViewport(this.selector, this.options, Boolean(options.fully));
2044
+ const inViewport = await this.target.isInViewport(Boolean(options.fully));
1553
2045
  if (inViewport == null) {
1554
2046
  return this.negate ? true : false;
1555
2047
  }
@@ -1558,7 +2050,7 @@ var ElementExpectation = class _ElementExpectation {
1558
2050
  }
1559
2051
  async toBeEditable() {
1560
2052
  return this.assert(async () => {
1561
- const editable = await this.frame.isEditable(this.selector, this.options);
2053
+ const editable = await this.target.isEditable();
1562
2054
  if (editable == null) {
1563
2055
  return this.negate ? true : false;
1564
2056
  }
@@ -1568,7 +2060,7 @@ var ElementExpectation = class _ElementExpectation {
1568
2060
  async assert(predicate, message, details = {}) {
1569
2061
  const timeoutMs = this.options.timeoutMs ?? 3e4;
1570
2062
  const start = Date.now();
1571
- this.events.emit("assertion:start", { name: message, selector: this.selector, frameId: this.frame.id });
2063
+ this.events.emit("assertion:start", { name: message, selector: this.target.label, frameId: this.target.frameId });
1572
2064
  let lastState;
1573
2065
  try {
1574
2066
  await waitFor(async () => {
@@ -1578,13 +2070,51 @@ var ElementExpectation = class _ElementExpectation {
1578
2070
  }, { timeoutMs, description: message });
1579
2071
  } catch {
1580
2072
  const duration2 = Date.now() - start;
1581
- this.events.emit("assertion:end", { name: message, selector: this.selector, frameId: this.frame.id, durationMs: duration2, status: "failed" });
1582
- throw new AssertionError(message, { selector: this.selector, timeoutMs, lastState: { lastState, ...details } });
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 } });
1583
2075
  }
1584
2076
  const duration = Date.now() - start;
1585
- this.events.emit("assertion:end", { name: message, selector: this.selector, frameId: this.frame.id, durationMs: duration, status: "passed" });
2077
+ this.events.emit("assertion:end", { name: message, selector: this.target.label, frameId: this.target.frameId, durationMs: duration, status: "passed" });
1586
2078
  }
1587
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
+ }
1588
2118
  var ExpectFrame = class {
1589
2119
  frame;
1590
2120
  events;
@@ -1593,25 +2123,31 @@ var ExpectFrame = class {
1593
2123
  this.events = events;
1594
2124
  }
1595
2125
  element(selector, options = {}) {
1596
- return new ElementExpectation(this.frame, selector, options, false, this.events);
2126
+ return new ElementExpectation(frameTarget(this.frame, selector, options), options, false, this.events);
1597
2127
  }
1598
2128
  };
1599
- function expect(page) {
2129
+ function expect(target) {
2130
+ if (target instanceof Locator) {
2131
+ return new ElementExpectation(locatorTarget(target), {}, false, target.getEvents());
2132
+ }
1600
2133
  return {
1601
- element: (selector, options = {}) => new ElementExpectation(page.mainFrame(), selector, options, false, page.getEvents()),
2134
+ element: (selector, options = {}) => new ElementExpectation(frameTarget(target.mainFrame(), selector, options), options, false, target.getEvents()),
1602
2135
  frame: (options) => {
1603
- const frame = page.frame(options);
2136
+ const frame = target.frame(options);
1604
2137
  if (!frame) {
1605
2138
  throw new AssertionError("Frame not found", { selector: JSON.stringify(options) });
1606
2139
  }
1607
- return new ExpectFrame(frame, page.getEvents());
2140
+ return new ExpectFrame(frame, target.getEvents());
1608
2141
  }
1609
2142
  };
1610
2143
  }
1611
- Page.prototype.expect = function(selector, options) {
2144
+ Page.prototype.expect = function(target, options) {
1612
2145
  const builder = expect(this);
1613
- if (selector) {
1614
- return builder.element(selector, options);
2146
+ if (target instanceof Locator) {
2147
+ return expect(target);
2148
+ }
2149
+ if (target) {
2150
+ return builder.element(target, options);
1615
2151
  }
1616
2152
  return builder;
1617
2153
  };
@@ -1630,4 +2166,4 @@ export {
1630
2166
  AssertionError,
1631
2167
  expect
1632
2168
  };
1633
- //# sourceMappingURL=chunk-PDUS6BDZ.js.map
2169
+ //# sourceMappingURL=chunk-EIAXMGD5.js.map