@one2x/playwright 1.57.0-alpha.15 → 1.57.0-alpha.17

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.
@@ -133,21 +133,46 @@ const mouseDragToXY = (0, import_tool.defineTabTool)({
133
133
  response.setIncludeAutoScreenshot();
134
134
  }
135
135
  });
136
+ function generateSamplingPoints(x, y, radius) {
137
+ if (radius === 0)
138
+ return [{ x, y }];
139
+ const circumference = 2 * Math.PI * radius;
140
+ const numDirections = Math.floor(circumference / 2);
141
+ const points = [{ x, y }];
142
+ for (let i = 0; i < numDirections; i++) {
143
+ const angle = i * 2 * Math.PI / numDirections;
144
+ points.push({
145
+ x: x + radius * Math.cos(angle),
146
+ y: y + radius * Math.sin(angle)
147
+ });
148
+ }
149
+ return points;
150
+ }
136
151
  const inspectAtXY = (0, import_tool.defineTabTool)({
137
152
  capability: "core",
138
153
  schema: {
139
154
  name: "browser_inspect_element_at_xy",
140
155
  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",
156
+ description: "Inspect all meaningful elements at x,y coordinates with optional radius (default 16px). Returns element details for generating test assertions including suggested selector, HTML, and computed styles",
142
157
  inputSchema: elementSchema.extend({
143
158
  x: import_bundle.z.number().describe("X coordinate"),
144
- y: import_bundle.z.number().describe("Y coordinate")
159
+ y: import_bundle.z.number().describe("Y coordinate"),
160
+ radius: import_bundle.z.number().min(0).optional().default(16).describe("Search radius (default: 16). Use radius=0 for exact coordinate only.")
145
161
  }),
146
162
  type: "readOnly"
147
163
  },
148
164
  handle: async (tab, params, response) => {
149
- const elementHandlesData = await tab.page.evaluateHandle(({ x, y }) => {
150
- const allElements = document.elementsFromPoint(x, y);
165
+ const radius = params.radius ?? 16;
166
+ if (radius < 0)
167
+ throw new Error("radius must be non-negative");
168
+ const samplingPoints = generateSamplingPoints(params.x, params.y, radius);
169
+ const elementHandlesData = await tab.page.evaluateHandle(({ points }) => {
170
+ const elementSet = /* @__PURE__ */ new Set();
171
+ for (const point of points) {
172
+ const elements = document.elementsFromPoint(point.x, point.y);
173
+ elements.forEach((el) => elementSet.add(el));
174
+ }
175
+ const allElements = Array.from(elementSet);
151
176
  const meaningfulElements = allElements.filter((element, index) => {
152
177
  for (let i = 0; i < allElements.length; i++) {
153
178
  if (i !== index && element.contains(allElements[i]))
@@ -155,8 +180,52 @@ const inspectAtXY = (0, import_tool.defineTabTool)({
155
180
  }
156
181
  return true;
157
182
  });
158
- return meaningfulElements;
159
- }, params);
183
+ const consolidatedElements = [];
184
+ const svgSet = /* @__PURE__ */ new Set();
185
+ for (const element of meaningfulElements) {
186
+ let current = element;
187
+ let parentSVG = null;
188
+ while (current) {
189
+ if (current.tagName === "svg") {
190
+ parentSVG = current;
191
+ break;
192
+ }
193
+ current = current.parentElement;
194
+ }
195
+ if (parentSVG) {
196
+ if (!svgSet.has(parentSVG)) {
197
+ svgSet.add(parentSVG);
198
+ consolidatedElements.push(parentSVG);
199
+ }
200
+ } else {
201
+ consolidatedElements.push(element);
202
+ }
203
+ }
204
+ const sortedElements = [];
205
+ const processed = /* @__PURE__ */ new Set();
206
+ const consolidatedSet = new Set(consolidatedElements);
207
+ for (const point of points) {
208
+ const elements = document.elementsFromPoint(point.x, point.y);
209
+ for (const el of elements) {
210
+ let elementToCheck = el;
211
+ if (el.tagName !== "svg") {
212
+ let current = el;
213
+ while (current) {
214
+ if (current.tagName === "svg") {
215
+ elementToCheck = current;
216
+ break;
217
+ }
218
+ current = current.parentElement;
219
+ }
220
+ }
221
+ if (consolidatedSet.has(elementToCheck) && !processed.has(elementToCheck)) {
222
+ sortedElements.push(elementToCheck);
223
+ processed.add(elementToCheck);
224
+ }
225
+ }
226
+ }
227
+ return sortedElements;
228
+ }, { points: samplingPoints });
160
229
  const elementHandlesArray = await elementHandlesData.evaluateHandle((arr) => arr);
161
230
  const elementHandles = [];
162
231
  const length = await elementHandlesArray.evaluate((arr) => arr.length);
@@ -169,7 +238,7 @@ const inspectAtXY = (0, import_tool.defineTabTool)({
169
238
  const elementsData = [];
170
239
  for (const elementHandle of elementHandles) {
171
240
  const pathToRoot = await elementHandle.getPathWithRefs().catch(() => "");
172
- const elementData = await elementHandle.evaluate((element) => {
241
+ const elementData = await elementHandle.evaluate((element, centerPoint) => {
173
242
  const computedStyle = window.getComputedStyle(element);
174
243
  function serializeAttributes(el) {
175
244
  const essentialAttrs = ["id", "class", "type", "name", "role", "aria-label"];
@@ -250,13 +319,47 @@ const inspectAtXY = (0, import_tool.defineTabTool)({
250
319
  return currentHtml;
251
320
  }
252
321
  const ariaRef = element._ariaRef;
322
+ const bounds = element.getBoundingClientRect();
323
+ const containsCenterPoint = bounds.left <= centerPoint.x && centerPoint.x <= bounds.right && bounds.top <= centerPoint.y && centerPoint.y <= bounds.bottom;
324
+ const elementCenterX = bounds.left + bounds.width / 2;
325
+ const elementCenterY = bounds.top + bounds.height / 2;
326
+ const distanceToCenter = Math.sqrt(
327
+ Math.pow(elementCenterX - centerPoint.x, 2) + Math.pow(elementCenterY - centerPoint.y, 2)
328
+ );
329
+ let svgColors = null;
330
+ if (element.tagName.toLowerCase() === "svg") {
331
+ const colorSet = /* @__PURE__ */ new Set();
332
+ const fillSet = /* @__PURE__ */ new Set();
333
+ const strokeSet = /* @__PURE__ */ new Set();
334
+ const allDescendants = element.querySelectorAll("*");
335
+ for (const descendant of Array.from(allDescendants)) {
336
+ const style = window.getComputedStyle(descendant);
337
+ const fill = descendant.getAttribute("fill") || style.fill;
338
+ if (fill && fill !== "none" && fill !== "transparent" && !fill.startsWith("url("))
339
+ fillSet.add(fill);
340
+ const stroke = descendant.getAttribute("stroke") || style.stroke;
341
+ if (stroke && stroke !== "none" && stroke !== "transparent" && !stroke.startsWith("url("))
342
+ strokeSet.add(stroke);
343
+ const color = style.color;
344
+ if (color && color !== "transparent")
345
+ colorSet.add(color);
346
+ }
347
+ svgColors = {
348
+ colors: Array.from(colorSet),
349
+ fills: Array.from(fillSet),
350
+ strokes: Array.from(strokeSet)
351
+ };
352
+ }
253
353
  return {
254
354
  ref: ariaRef?.ref || null,
255
355
  role: ariaRef?.role || null,
256
356
  element: ariaRef?.name || element.tagName.toLowerCase(),
257
357
  truncatedHTML: truncateIterativeDeepening(element, 300),
258
358
  tagName: element.tagName.toLowerCase(),
259
- bounds: element.getBoundingClientRect(),
359
+ bounds,
360
+ containsCenterPoint,
361
+ distanceToCenter,
362
+ svgColors,
260
363
  computedStyles: {
261
364
  display: computedStyle.display,
262
365
  visibility: computedStyle.visibility,
@@ -277,16 +380,26 @@ const inspectAtXY = (0, import_tool.defineTabTool)({
277
380
  disabled: element.disabled || void 0,
278
381
  readonly: element.readOnly || void 0
279
382
  };
280
- });
383
+ }, { x: params.x, y: params.y });
281
384
  elementsData.push({
282
385
  ...elementData,
283
386
  pathToRoot
284
387
  });
285
388
  }
389
+ const elementsContainingCenter = [];
390
+ const elementsNotContainingCenter = [];
391
+ for (const el of elementsData) {
392
+ if (el.containsCenterPoint)
393
+ elementsContainingCenter.push(el);
394
+ else
395
+ elementsNotContainingCenter.push(el);
396
+ }
397
+ elementsNotContainingCenter.sort((a, b) => a.distanceToCenter - b.distanceToCenter);
398
+ const sortedElementsData = [...elementsContainingCenter, ...elementsNotContainingCenter];
286
399
  let suggestedSelectors = "";
287
- if (elementsData.length > 0 && elementsData[0].ref) {
400
+ if (sortedElementsData.length > 0 && sortedElementsData[0].ref) {
288
401
  try {
289
- const topElement = elementsData[0];
402
+ const topElement = sortedElementsData[0];
290
403
  const { locator } = await tab.refLocator({
291
404
  element: topElement.element,
292
405
  ref: topElement.ref
@@ -296,13 +409,71 @@ const inspectAtXY = (0, import_tool.defineTabTool)({
296
409
  }
297
410
  }
298
411
  let result = "";
299
- if (elementsData.length > 1)
300
- result += `\u26A0\uFE0F Warning: Found ${elementsData.length} elements at (${params.x}, ${params.y})
412
+ if (sortedElementsData.length === 0) {
413
+ const viewport = await tab.page.viewportSize();
414
+ const searchArea = radius === 0 ? `at coordinates (${params.x}, ${params.y})` : `within ${radius}px radius of (${params.x}, ${params.y})`;
415
+ result += `\u26A0\uFE0F No elements found ${searchArea}
301
416
 
302
417
  `;
303
- elementsData.forEach((elementInfo, index) => {
418
+ result += `**Possible reasons:**
419
+ `;
420
+ result += `- The coordinates may be outside the viewport
421
+ `;
422
+ if (viewport) {
423
+ result += ` - Current viewport size: ${viewport.width}x${viewport.height}
424
+ `;
425
+ if (params.x < 0 || params.y < 0 || params.x >= viewport.width || params.y >= viewport.height)
426
+ result += ` - \u26A0\uFE0F Coordinates are outside viewport bounds!
427
+ `;
428
+ }
429
+ result += `- The page may still be loading or animating
430
+ `;
431
+ result += `- The search area may be on empty space (no rendered elements)
432
+ `;
433
+ result += `- Elements in this area may have been removed from the DOM
434
+ `;
435
+ if (radius > 0)
436
+ result += `- Try increasing the radius to expand the search area
437
+ `;
438
+ result += `
439
+ **Suggestions:**
440
+ `;
441
+ result += `- Take a screenshot with \`browser_take_screenshot\` to see the current page state
442
+ `;
443
+ result += `- Wait for the page to stabilize with \`browser_wait_for\`
444
+ `;
445
+ result += `- Try different coordinates closer to visible elements
446
+ `;
447
+ if (viewport)
448
+ result += `- Ensure coordinates are within viewport bounds (0-${viewport.width}, 0-${viewport.height})
449
+ `;
450
+ response.addResult(result);
451
+ return;
452
+ }
453
+ if (sortedElementsData.length > 1) {
454
+ const numAtCenter = elementsContainingCenter.length;
455
+ const numNearby = elementsNotContainingCenter.length;
456
+ const searchArea = radius === 0 ? `at (${params.x}, ${params.y})` : `within ${radius}px radius of (${params.x}, ${params.y})`;
457
+ let warning = `\u26A0\uFE0F Warning: Found ${sortedElementsData.length} elements ${searchArea}`;
458
+ if (numAtCenter > 0 && numNearby > 0)
459
+ warning += ` (${numAtCenter} at center, ${numNearby} nearby)`;
460
+ else if (numAtCenter > 0)
461
+ warning += ` (all at center)`;
462
+ else if (numNearby > 0)
463
+ warning += ` (all nearby)`;
464
+ result += warning + "\n\n";
465
+ }
466
+ sortedElementsData.forEach((elementInfo, index) => {
304
467
  const isTopmost = index === 0;
305
- const header = isTopmost ? `**Element at (${params.x}, ${params.y}) - Topmost (will receive clicks):**` : `**Element ${index + 1} at (${params.x}, ${params.y}) - Below topmost:**`;
468
+ const locationDesc = radius === 0 ? `at (${params.x}, ${params.y})` : `within ${radius}px of (${params.x}, ${params.y})`;
469
+ let category = "";
470
+ if (elementInfo.containsCenterPoint && isTopmost)
471
+ category = " - Topmost at center (will receive clicks)";
472
+ else if (elementInfo.containsCenterPoint)
473
+ category = " - At center (below topmost)";
474
+ else
475
+ category = ` - Nearby (${Math.round(elementInfo.distanceToCenter)}px from center)`;
476
+ const header = `**Element ${index + 1} ${locationDesc}${category}:**`;
306
477
  result += header + "\n";
307
478
  result += `- RefId: ${elementInfo.ref || "N/A"}
308
479
  `;
@@ -329,6 +500,34 @@ ${elementInfo.truncatedHTML}
329
500
  `;
330
501
  result += `- height: ${elementInfo.bounds.height}
331
502
  `;
503
+ result += `- Contains center point (${params.x}, ${params.y}): ${elementInfo.containsCenterPoint ? "\u2713 Yes" : "\u2717 No"}
504
+ `;
505
+ if (!elementInfo.containsCenterPoint)
506
+ result += `- Distance to center: ${Math.round(elementInfo.distanceToCenter)}px
507
+ `;
508
+ if (elementInfo.svgColors) {
509
+ result += `
510
+ **SVG Colors (from descendants):**
511
+ `;
512
+ if (elementInfo.svgColors.fills.length > 0)
513
+ result += `- Fill colors: ${elementInfo.svgColors.fills.join(", ")}
514
+ `;
515
+ else
516
+ result += `- Fill colors: none
517
+ `;
518
+ if (elementInfo.svgColors.strokes.length > 0)
519
+ result += `- Stroke colors: ${elementInfo.svgColors.strokes.join(", ")}
520
+ `;
521
+ else
522
+ result += `- Stroke colors: none
523
+ `;
524
+ if (elementInfo.svgColors.colors.length > 0)
525
+ result += `- Text colors: ${elementInfo.svgColors.colors.join(", ")}
526
+ `;
527
+ else
528
+ result += `- Text colors: none
529
+ `;
530
+ }
332
531
  result += `
333
532
  **Key Computed Styles:**
334
533
  `;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@one2x/playwright",
3
- "version": "1.57.0-alpha.15",
3
+ "version": "1.57.0-alpha.17",
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.15",
68
+ "playwright-core": "npm:@one2x/playwright-core@1.57.0-alpha.17",
69
69
  "raw-body": "^2.5.2"
70
70
  },
71
71
  "optionalDependencies": {