@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.
- package/lib/mcp/browser/tools/mouse.js +214 -15
- package/package.json +2 -2
|
@@ -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 (
|
|
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
|
|
150
|
-
|
|
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
|
-
|
|
159
|
-
|
|
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
|
|
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 (
|
|
400
|
+
if (sortedElementsData.length > 0 && sortedElementsData[0].ref) {
|
|
288
401
|
try {
|
|
289
|
-
const topElement =
|
|
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 (
|
|
300
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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.
|
|
68
|
+
"playwright-core": "npm:@one2x/playwright-core@1.57.0-alpha.17",
|
|
69
69
|
"raw-body": "^2.5.2"
|
|
70
70
|
},
|
|
71
71
|
"optionalDependencies": {
|