@one2x/playwright 1.57.0-alpha.12 → 1.57.0-alpha.14

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.
@@ -63,9 +63,8 @@ class BaseContextFactory {
63
63
  }
64
64
  _obtainBrowser(clientInfo) {
65
65
  const key = this._calcFingerprint();
66
- if (BaseContextFactory._cache.has(key)) {
66
+ if (BaseContextFactory._cache.has(key))
67
67
  return BaseContextFactory._cache.get(key);
68
- }
69
68
  (0, import_log.testDebug)(`obtain browser (${this._logName})`);
70
69
  const browserPromise = this._doObtainBrowser(clientInfo);
71
70
  void browserPromise.then((browser) => {
@@ -292,7 +291,6 @@ class SharedContextFactory {
292
291
  static create(config) {
293
292
  if (SharedContextFactory._instance)
294
293
  return SharedContextFactory._instance;
295
- ;
296
294
  const baseConfig = { ...config, sharedBrowserContext: false };
297
295
  const baseFactory = contextFactory(baseConfig);
298
296
  SharedContextFactory._instance = new SharedContextFactory(baseFactory);
@@ -335,15 +333,12 @@ async function computeTracesDir(config, clientInfo) {
335
333
  return await (0, import_config.outputFile)(config, clientInfo, `traces`, { origin: "code", reason: "Collecting trace" });
336
334
  }
337
335
  function stableStringify(obj) {
338
- if (obj === null || obj === void 0) {
336
+ if (obj === null || obj === void 0)
339
337
  return String(obj);
340
- }
341
- if (typeof obj !== "object") {
338
+ if (typeof obj !== "object")
342
339
  return JSON.stringify(obj);
343
- }
344
- if (Array.isArray(obj)) {
340
+ if (Array.isArray(obj))
345
341
  return "[" + obj.map((item) => stableStringify(item)).join(",") + "]";
346
- }
347
342
  const sortedKeys = Object.keys(obj).sort();
348
343
  const pairs = sortedKeys.map((key) => {
349
344
  return JSON.stringify(key) + ":" + stableStringify(obj[key]);
@@ -397,9 +397,8 @@ function configFromURLParams(url) {
397
397
  const viewportSize = getParam("viewport-size");
398
398
  if (viewportSize)
399
399
  options.viewportSize = resolutionParser("--viewport-size", viewportSize);
400
- if (!Object.values(options).some((v) => v !== void 0)) {
400
+ if (!Object.values(options).some((v) => v !== void 0))
401
401
  return {};
402
- }
403
402
  return configFromCLIOptions(options);
404
403
  }
405
404
  function queryToBoolean(value) {
@@ -24,6 +24,7 @@ module.exports = __toCommonJS(mouse_exports);
24
24
  var import_bundle = require("../../sdk/bundle");
25
25
  var import_tool = require("./tool");
26
26
  var import_snapshot = require("./snapshot");
27
+ var import_utils = require("./utils");
27
28
  const elementSchema = import_bundle.z.object({
28
29
  element: import_bundle.z.string().describe("Human-readable element description used to obtain permission to interact with the element")
29
30
  });
@@ -132,9 +133,242 @@ const mouseDragToXY = (0, import_tool.defineTabTool)({
132
133
  response.setIncludeAutoScreenshot();
133
134
  }
134
135
  });
136
+ const inspectAtXY = (0, import_tool.defineTabTool)({
137
+ capability: "core",
138
+ schema: {
139
+ name: "browser_inspect_element_at_xy",
140
+ title: "Inspect elements at coordinates",
141
+ description: "Inspect all meaningful elements at x,y coordinates (excluding parent containers). Returns element details for generating test assertions including suggested selector, HTML, and computed styles",
142
+ inputSchema: elementSchema.extend({
143
+ x: import_bundle.z.number().describe("X coordinate"),
144
+ y: import_bundle.z.number().describe("Y coordinate")
145
+ }),
146
+ type: "readOnly"
147
+ },
148
+ handle: async (tab, params, response) => {
149
+ const elementHandlesData = await tab.page.evaluateHandle(({ x, y }) => {
150
+ const allElements = document.elementsFromPoint(x, y);
151
+ const meaningfulElements = allElements.filter((element, index) => {
152
+ for (let i = 0; i < allElements.length; i++) {
153
+ if (i !== index && element.contains(allElements[i]))
154
+ return false;
155
+ }
156
+ return true;
157
+ });
158
+ return meaningfulElements;
159
+ }, params);
160
+ const elementHandlesArray = await elementHandlesData.evaluateHandle((arr) => arr);
161
+ const elementHandles = [];
162
+ const length = await elementHandlesArray.evaluate((arr) => arr.length);
163
+ for (let i = 0; i < length; i++) {
164
+ const handle = await elementHandlesArray.evaluateHandle((arr, index) => arr[index], i);
165
+ const element = handle.asElement();
166
+ if (element)
167
+ elementHandles.push(element);
168
+ }
169
+ const elementsData = [];
170
+ for (const elementHandle of elementHandles) {
171
+ const pathToRoot = await elementHandle.getPathWithRefs().catch(() => "");
172
+ const elementData = await elementHandle.evaluate((element) => {
173
+ const computedStyle = window.getComputedStyle(element);
174
+ function serializeAttributes(el) {
175
+ const essentialAttrs = ["id", "class", "type", "name", "role", "aria-label"];
176
+ const attrs = [];
177
+ for (const attrName of essentialAttrs) {
178
+ const value = el.getAttribute(attrName);
179
+ if (value)
180
+ attrs.push(` ${attrName}="${value}"`);
181
+ }
182
+ return attrs.join("");
183
+ }
184
+ function buildTree(el) {
185
+ return {
186
+ element: el,
187
+ expanded: false,
188
+ childNodes: Array.from(el.children).map((child) => buildTree(child))
189
+ };
190
+ }
191
+ function renderTree(node) {
192
+ const tag = node.element.tagName.toLowerCase();
193
+ const attrs = serializeAttributes(node.element);
194
+ const openTag = `<${tag}${attrs}>`;
195
+ const closeTag = `</${tag}>`;
196
+ const selfClosing = ["img", "input", "br", "hr", "meta", "link"];
197
+ if (selfClosing.includes(tag))
198
+ return `<${tag}${attrs} />`;
199
+ if (node.childNodes.length === 0) {
200
+ const text = node.element.textContent?.trim() || "";
201
+ if (text && text.length <= 50)
202
+ return openTag + text + closeTag;
203
+ else if (text)
204
+ return openTag + text.substring(0, 47) + "..." + closeTag;
205
+ return openTag + closeTag;
206
+ }
207
+ if (!node.expanded) {
208
+ const count = node.element.children.length;
209
+ return `${openTag}<!-- ${count} child element${count !== 1 ? "s" : ""} removed -->${closeTag}`;
210
+ }
211
+ const childrenHtml = node.childNodes.map((child) => renderTree(child)).join("");
212
+ return openTag + childrenHtml + closeTag;
213
+ }
214
+ function getExpandableNodes(node) {
215
+ const queue = [node];
216
+ const expandable = [];
217
+ while (queue.length > 0) {
218
+ const current = queue.shift();
219
+ if (current.childNodes.length > 0 && !current.expanded)
220
+ expandable.push(current);
221
+ if (current.expanded)
222
+ queue.push(...current.childNodes);
223
+ }
224
+ return expandable;
225
+ }
226
+ function truncateIterativeDeepening(el, maxBudget) {
227
+ if (el.outerHTML.length <= maxBudget)
228
+ return el.outerHTML;
229
+ const root = buildTree(el);
230
+ let currentHtml = renderTree(root);
231
+ while (currentHtml.length < maxBudget) {
232
+ const expandable = getExpandableNodes(root);
233
+ if (expandable.length === 0)
234
+ break;
235
+ let expanded = false;
236
+ for (const node of expandable) {
237
+ node.expanded = true;
238
+ const newHtml = renderTree(root);
239
+ if (newHtml.length <= maxBudget) {
240
+ currentHtml = newHtml;
241
+ expanded = true;
242
+ break;
243
+ } else {
244
+ node.expanded = false;
245
+ }
246
+ }
247
+ if (!expanded)
248
+ break;
249
+ }
250
+ return currentHtml;
251
+ }
252
+ const ariaRef = element._ariaRef;
253
+ return {
254
+ ref: ariaRef?.ref || null,
255
+ role: ariaRef?.role || null,
256
+ element: ariaRef?.name || element.tagName.toLowerCase(),
257
+ truncatedHTML: truncateIterativeDeepening(element, 300),
258
+ tagName: element.tagName.toLowerCase(),
259
+ computedStyles: {
260
+ display: computedStyle.display,
261
+ visibility: computedStyle.visibility,
262
+ opacity: computedStyle.opacity,
263
+ position: computedStyle.position,
264
+ zIndex: computedStyle.zIndex,
265
+ width: computedStyle.width,
266
+ height: computedStyle.height,
267
+ backgroundColor: computedStyle.backgroundColor,
268
+ color: computedStyle.color,
269
+ fontSize: computedStyle.fontSize,
270
+ fontFamily: computedStyle.fontFamily
271
+ },
272
+ textContent: element.textContent?.trim() || "",
273
+ innerText: element.innerText?.trim() || "",
274
+ value: element.value || void 0,
275
+ checked: element.checked || void 0,
276
+ disabled: element.disabled || void 0,
277
+ readonly: element.readOnly || void 0
278
+ };
279
+ });
280
+ elementsData.push({
281
+ ...elementData,
282
+ pathToRoot
283
+ });
284
+ }
285
+ let suggestedSelectors = "";
286
+ if (elementsData.length > 0 && elementsData[0].ref) {
287
+ try {
288
+ const topElement = elementsData[0];
289
+ const { locator } = await tab.refLocator({
290
+ element: topElement.element,
291
+ ref: topElement.ref
292
+ });
293
+ suggestedSelectors = (await (0, import_utils.generateLocators)(locator)).map((s) => `\`page.${s}\``).join("\n");
294
+ } catch (e) {
295
+ }
296
+ }
297
+ let result = "";
298
+ if (elementsData.length > 1)
299
+ result += `\u26A0\uFE0F Warning: Found ${elementsData.length} elements at (${params.x}, ${params.y})
300
+
301
+ `;
302
+ elementsData.forEach((elementInfo, index) => {
303
+ const isTopmost = index === 0;
304
+ const header = isTopmost ? `**Element at (${params.x}, ${params.y}) - Topmost (will receive clicks):**` : `**Element ${index + 1} at (${params.x}, ${params.y}) - Below topmost:**`;
305
+ result += header + "\n";
306
+ result += `- RefId: ${elementInfo.ref || "N/A"}
307
+ `;
308
+ result += `- Path to root: ${elementInfo.pathToRoot}
309
+ `;
310
+ if (isTopmost && suggestedSelectors)
311
+ result += `- Suggested Playwright Selector:
312
+ ${suggestedSelectors}
313
+ `;
314
+ result += `
315
+ **HTML (truncated to 300 chars):**
316
+ \`\`\`html
317
+ ${elementInfo.truncatedHTML}
318
+ \`\`\`
319
+ `;
320
+ result += `
321
+ **Key Computed Styles:**
322
+ `;
323
+ result += `- Display: ${elementInfo.computedStyles.display}
324
+ `;
325
+ result += `- Visibility: ${elementInfo.computedStyles.visibility}
326
+ `;
327
+ result += `- Opacity: ${elementInfo.computedStyles.opacity}
328
+ `;
329
+ result += `- Position: ${elementInfo.computedStyles.position}
330
+ `;
331
+ result += `- Z-Index: ${elementInfo.computedStyles.zIndex}
332
+ `;
333
+ result += `- Width: ${elementInfo.computedStyles.width}
334
+ `;
335
+ result += `- Height: ${elementInfo.computedStyles.height}
336
+ `;
337
+ result += `- Background Color: ${elementInfo.computedStyles.backgroundColor}
338
+ `;
339
+ result += `- Color: ${elementInfo.computedStyles.color}
340
+ `;
341
+ result += `
342
+ **Element Properties:**
343
+ `;
344
+ result += `- Tag: ${elementInfo.tagName}
345
+ `;
346
+ result += `- Text Content: "${elementInfo.textContent}"
347
+ `;
348
+ result += `- Inner Text: "${elementInfo.innerText}"
349
+ `;
350
+ if (elementInfo.value !== void 0)
351
+ result += `- Value: "${elementInfo.value}"
352
+ `;
353
+ if (elementInfo.checked !== void 0)
354
+ result += `- Checked: ${elementInfo.checked}
355
+ `;
356
+ if (elementInfo.disabled !== void 0)
357
+ result += `- Disabled: ${elementInfo.disabled}
358
+ `;
359
+ if (elementInfo.readonly !== void 0)
360
+ result += `- Read Only: ${elementInfo.readonly}
361
+ `;
362
+ if (index < elementsData.length - 1)
363
+ result += "\n---\n\n";
364
+ });
365
+ response.addResult(result);
366
+ }
367
+ });
135
368
  var mouse_default = [
136
369
  mouseMove,
137
370
  mouseClick,
138
371
  mouseDrag,
139
- mouseDragToXY
372
+ mouseDragToXY,
373
+ inspectAtXY
140
374
  ];
@@ -51,7 +51,7 @@ const screenshot = (0, import_tool.defineTabTool)({
51
51
  schema: {
52
52
  name: "browser_take_screenshot",
53
53
  title: "Take a screenshot",
54
- description: `Take a screenshot of the current page. You can't perform actions based on the screenshot, use browser_snapshot for actions.`,
54
+ description: `Take a screenshot of the current page. You can call tools ends with '_xy' based on the screenshot coordinates.`,
55
55
  inputSchema: screenshotSchema,
56
56
  type: "readOnly"
57
57
  },
@@ -88,9 +88,8 @@ const click = (0, import_tool.defineTabTool)({
88
88
  options
89
89
  );
90
90
  });
91
- if (!result) {
91
+ if (!result)
92
92
  return;
93
- }
94
93
  if (result.mode === "auto" && result.interceptorInfo) {
95
94
  const actionName = params.doubleClick ? ".dblclick({ allowIntercept: true })" : ".click({ allowIntercept: true })";
96
95
  (0, import_utils.addInterceptorWarning)(response, resolved, actionName, result.interceptorInfo);
@@ -133,9 +132,8 @@ const drag = (0, import_tool.defineTabTool)({
133
132
  {}
134
133
  );
135
134
  });
136
- if (!result) {
135
+ if (!result)
137
136
  return;
138
- }
139
137
  if (result.mode === "auto" && result.interceptorInfo)
140
138
  (0, import_utils.addInterceptorWarning)(response, start.resolved, ".dragTo(target, { allowIntercept: true })", result.interceptorInfo);
141
139
  const needsAllowIntercept = result.mode === "auto" && result.interceptorInfo || result.mode === "always";
@@ -163,9 +161,8 @@ const hover = (0, import_tool.defineTabTool)({
163
161
  {}
164
162
  );
165
163
  });
166
- if (!result) {
164
+ if (!result)
167
165
  return;
168
- }
169
166
  if (result.mode === "auto" && result.interceptorInfo)
170
167
  (0, import_utils.addInterceptorWarning)(response, resolved, ".hover({ allowIntercept: true })", result.interceptorInfo);
171
168
  const needsAllowIntercept = result.mode === "auto" && result.interceptorInfo || result.mode === "always";
@@ -213,7 +210,7 @@ const pickLocator = (0, import_tool.defineTabTool)({
213
210
  const elementInspect = (0, import_tool.defineTool)({
214
211
  capability: "core",
215
212
  schema: {
216
- name: "browser_element_inspect",
213
+ name: "browser_inspect_element",
217
214
  title: "Inspect element for testing",
218
215
  description: "Get element details for generating test assertions - returns suggested selector, HTML, and computed styles",
219
216
  inputSchema: elementSchema,
@@ -226,31 +223,19 @@ const elementInspect = (0, import_tool.defineTool)({
226
223
  let elementDetails;
227
224
  const suggestedSelectors = (await (0, import_utils.generateLocators)(locator)).map((s) => `\`page.${s}\``).join("\n");
228
225
  try {
226
+ const elementHandle = await locator.elementHandle();
227
+ if (!elementHandle)
228
+ throw new Error("Element not found");
229
+ const pathToRoot = await elementHandle.getPathWithRefs();
229
230
  elementDetails = await locator.evaluate((element) => {
230
231
  const computedStyle = window.getComputedStyle(element);
231
- const path = [];
232
- let current = element;
233
- while (current && current !== document.documentElement) {
234
- const ariaRef = current._ariaRef;
235
- const tagName = current.tagName?.toLowerCase() || "unknown";
236
- let nodeInfo = tagName;
237
- if (ariaRef?.ref) {
238
- nodeInfo += ` [${ariaRef.ref}]`;
239
- if (ariaRef.role) {
240
- nodeInfo += ` [${ariaRef.role}]`;
241
- }
242
- }
243
- path.unshift(nodeInfo);
244
- current = current.parentElement;
245
- }
246
232
  function serializeAttributes(el) {
247
233
  const essentialAttrs = ["id", "class", "type", "name", "role", "aria-label"];
248
234
  const attrs = [];
249
235
  for (const attrName of essentialAttrs) {
250
236
  const value = el.getAttribute(attrName);
251
- if (value) {
237
+ if (value)
252
238
  attrs.push(` ${attrName}="${value}"`);
253
- }
254
239
  }
255
240
  return attrs.join("");
256
241
  }
@@ -267,16 +252,14 @@ const elementInspect = (0, import_tool.defineTool)({
267
252
  const openTag = `<${tag}${attrs}>`;
268
253
  const closeTag = `</${tag}>`;
269
254
  const selfClosing = ["img", "input", "br", "hr", "meta", "link"];
270
- if (selfClosing.includes(tag)) {
255
+ if (selfClosing.includes(tag))
271
256
  return `<${tag}${attrs} />`;
272
- }
273
257
  if (node.childNodes.length === 0) {
274
258
  const text = node.element.textContent?.trim() || "";
275
- if (text && text.length <= 50) {
259
+ if (text && text.length <= 50)
276
260
  return openTag + text + closeTag;
277
- } else if (text) {
261
+ else if (text)
278
262
  return openTag + text.substring(0, 47) + "..." + closeTag;
279
- }
280
263
  return openTag + closeTag;
281
264
  }
282
265
  if (!node.expanded) {
@@ -290,27 +273,23 @@ const elementInspect = (0, import_tool.defineTool)({
290
273
  const queue = [node];
291
274
  const expandable = [];
292
275
  while (queue.length > 0) {
293
- const current2 = queue.shift();
294
- if (current2.childNodes.length > 0 && !current2.expanded) {
295
- expandable.push(current2);
296
- }
297
- if (current2.expanded) {
298
- queue.push(...current2.childNodes);
299
- }
276
+ const current = queue.shift();
277
+ if (current.childNodes.length > 0 && !current.expanded)
278
+ expandable.push(current);
279
+ if (current.expanded)
280
+ queue.push(...current.childNodes);
300
281
  }
301
282
  return expandable;
302
283
  }
303
284
  function truncateIterativeDeepening(el, maxBudget) {
304
- if (el.outerHTML.length <= maxBudget) {
285
+ if (el.outerHTML.length <= maxBudget)
305
286
  return el.outerHTML;
306
- }
307
287
  const root = buildTree(el);
308
288
  let currentHtml = renderTree(root);
309
289
  while (currentHtml.length < maxBudget) {
310
290
  const expandable = getExpandableNodes(root);
311
- if (expandable.length === 0) {
291
+ if (expandable.length === 0)
312
292
  break;
313
- }
314
293
  let expanded = false;
315
294
  for (const node of expandable) {
316
295
  node.expanded = true;
@@ -323,16 +302,14 @@ const elementInspect = (0, import_tool.defineTool)({
323
302
  node.expanded = false;
324
303
  }
325
304
  }
326
- if (!expanded) {
305
+ if (!expanded)
327
306
  break;
328
- }
329
307
  }
330
308
  return currentHtml;
331
309
  }
332
310
  return {
333
311
  outerHTML: element.outerHTML,
334
312
  truncatedHTML: truncateIterativeDeepening(element, 300),
335
- pathToRoot: path.join(" > "),
336
313
  tagName: element.tagName.toLowerCase(),
337
314
  computedStyles: {
338
315
  display: computedStyle.display,
@@ -354,10 +331,12 @@ const elementInspect = (0, import_tool.defineTool)({
354
331
  zIndex: computedStyle.zIndex,
355
332
  transform: computedStyle.transform
356
333
  },
357
- attributes: Array.from(element.attributes).reduce((acc, attr) => {
358
- acc[attr.name] = attr.value;
359
- return acc;
360
- }, {}),
334
+ attributes: (() => {
335
+ const attrs = {};
336
+ for (const attr of Array.from(element.attributes))
337
+ attrs[attr.name] = attr.value;
338
+ return attrs;
339
+ })(),
361
340
  textContent: element.textContent?.trim() || "",
362
341
  innerText: element.innerText?.trim() || "",
363
342
  value: element.value || void 0,
@@ -367,6 +346,7 @@ const elementInspect = (0, import_tool.defineTool)({
367
346
  bounds: element.getBoundingClientRect()
368
347
  };
369
348
  });
349
+ elementDetails.pathToRoot = pathToRoot;
370
350
  } catch (error) {
371
351
  response.addResult(`Error inspecting element "${params.element}": ${error instanceof Error ? error.message : String(error)}
372
352
 
@@ -403,22 +383,18 @@ ${elementDetails.truncatedHTML}
403
383
  - Tag: ${elementDetails.tagName}
404
384
  - Text Content: "${elementDetails.textContent}"
405
385
  - Inner Text: "${elementDetails.innerText}"`;
406
- if (elementDetails.value !== void 0) {
386
+ if (elementDetails.value !== void 0)
407
387
  result += `
408
388
  - Value: "${elementDetails.value}"`;
409
- }
410
- if (elementDetails.checked !== void 0) {
389
+ if (elementDetails.checked !== void 0)
411
390
  result += `
412
391
  - Checked: ${elementDetails.checked}`;
413
- }
414
- if (elementDetails.disabled !== void 0) {
392
+ if (elementDetails.disabled !== void 0)
415
393
  result += `
416
394
  - Disabled: ${elementDetails.disabled}`;
417
- }
418
- if (elementDetails.readonly !== void 0) {
395
+ if (elementDetails.readonly !== void 0)
419
396
  result += `
420
397
  - Read Only: ${elementDetails.readonly}`;
421
- }
422
398
  response.addResult(result);
423
399
  }
424
400
  });
@@ -241,16 +241,14 @@ function configFromInitRequest(body) {
241
241
  for (const [snakeKey, camelKey] of Object.entries(keyMap)) {
242
242
  if (playwrightConfig[snakeKey] !== void 0) {
243
243
  let value = playwrightConfig[snakeKey];
244
- if (snakeKey === "no_sandbox") {
244
+ if (snakeKey === "no_sandbox")
245
245
  value = !value;
246
- }
247
246
  options[camelKey] = value;
248
247
  }
249
248
  }
250
249
  for (const key of Object.keys(playwrightConfig)) {
251
- if (!key.includes("_") && options[key] === void 0) {
250
+ if (!key.includes("_") && options[key] === void 0)
252
251
  options[key] = playwrightConfig[key];
253
- }
254
252
  }
255
253
  testDebug("Converted to CLIOptions:", options);
256
254
  return (0, import_config.configFromCLIOptions)(options);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@one2x/playwright",
3
- "version": "1.57.0-alpha.12",
3
+ "version": "1.57.0-alpha.14",
4
4
  "description": "A high-level API to automate web browsers",
5
5
  "repository": {
6
6
  "type": "git",
@@ -65,7 +65,7 @@
65
65
  "license": "Apache-2.0",
66
66
  "dependencies": {
67
67
  "content-type": "^1.0.5",
68
- "playwright-core": "npm:@one2x/playwright-core@1.57.0-alpha.12",
68
+ "playwright-core": "npm:@one2x/playwright-core@1.57.0-alpha.14",
69
69
  "raw-body": "^2.5.2"
70
70
  },
71
71
  "optionalDependencies": {