@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.
@@ -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";
@@ -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 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);
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
- const result = await this.session.send("Page.printToPDF", {
1226
- landscape: options.landscape ?? false,
1227
- printBackground: options.printBackground ?? true,
1228
- scale: options.scale,
1229
- paperWidth: options.paperWidth,
1230
- paperHeight: options.paperHeight,
1231
- marginTop: options.marginTop,
1232
- marginBottom: options.marginBottom,
1233
- marginLeft: options.marginLeft,
1234
- marginRight: options.marginRight,
1235
- pageRanges: options.pageRanges,
1236
- preferCSSPageSize: options.preferCSSPageSize
1237
- });
1238
- const buffer = Buffer.from(result.data, "base64");
1239
- if (options.path) {
1240
- const resolved = path2.resolve(options.path);
1241
- fs2.writeFileSync(resolved, buffer);
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
- frame;
1332
- selector;
1857
+ target;
1333
1858
  options;
1334
1859
  negate;
1335
1860
  events;
1336
- constructor(frame, selector, options, negate, events) {
1337
- this.frame = frame;
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.frame, this.selector, this.options, !this.negate, this.events);
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.frame.exists(this.selector, this.options);
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.frame.isVisible(this.selector, this.options);
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.frame.isVisible(this.selector, this.options);
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.frame.isEnabled(this.selector, this.options);
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.frame.isEnabled(this.selector, this.options);
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.frame.isChecked(this.selector, this.options);
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.frame.isChecked(this.selector, this.options);
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.frame.text(this.selector, this.options);
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.frame.text(this.selector, this.options);
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.frame.text(this.selector, this.options);
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.frame.value(this.selector, this.options);
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.frame.attribute(this.selector, name, this.options);
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.frame.count(this.selector, this.options);
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.frame.classes(this.selector, this.options);
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.frame.classes(this.selector, this.options);
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.frame.css(this.selector, property, this.options);
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.frame.hasFocus(this.selector, this.options);
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.frame.isInViewport(this.selector, this.options, Boolean(options.fully));
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.frame.isEditable(this.selector, this.options);
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.selector, frameId: this.frame.id });
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.selector, frameId: this.frame.id, durationMs: duration2, status: "failed" });
1548
- 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 } });
1549
2075
  }
1550
2076
  const duration = Date.now() - start;
1551
- 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" });
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(page) {
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(page.mainFrame(), selector, options, false, page.getEvents()),
2134
+ element: (selector, options = {}) => new ElementExpectation(frameTarget(target.mainFrame(), selector, options), options, false, target.getEvents()),
1568
2135
  frame: (options) => {
1569
- const frame = page.frame(options);
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, page.getEvents());
2140
+ return new ExpectFrame(frame, target.getEvents());
1574
2141
  }
1575
2142
  };
1576
2143
  }
1577
- Page.prototype.expect = function(selector, options) {
2144
+ Page.prototype.expect = function(target, options) {
1578
2145
  const builder = expect(this);
1579
- if (selector) {
1580
- return builder.element(selector, options);
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-M5G6EMAS.js.map
2169
+ //# sourceMappingURL=chunk-EIAXMGD5.js.map