@cutleryapp/agent 1.0.9 → 1.0.10
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/dist/mcp-executor.js +29 -4
- package/package.json +1 -1
package/dist/mcp-executor.js
CHANGED
|
@@ -77,11 +77,16 @@ class TestExecutor {
|
|
|
77
77
|
}
|
|
78
78
|
else if (lower.includes("click")) {
|
|
79
79
|
const labelMatch = raw.match(/click\s+(?:on\s+)?(?:the\s+)?"?([^"]+?)"?\s*(?:button|link|tab)?$/i);
|
|
80
|
-
|
|
80
|
+
let label = labelMatch?.[1]?.trim();
|
|
81
81
|
if (label) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const
|
|
82
|
+
// Split "Add to cart under Sauce Labs Bike Light product" into target + scope
|
|
83
|
+
const scopeMatch = label.match(/^(.+?)\s+(?:under|inside|within|in the)\s+(.+)$/i);
|
|
84
|
+
const target = scopeMatch ? scopeMatch[1].trim() : label;
|
|
85
|
+
const scope = scopeMatch ? scopeMatch[2].trim() : null;
|
|
86
|
+
const nameRe = new RegExp(escapeRegex(target), 'i');
|
|
87
|
+
const clicked = scope
|
|
88
|
+
? await tryClickScoped(page, nameRe, target, scope)
|
|
89
|
+
: await tryClick(page, nameRe, target);
|
|
85
90
|
if (!clicked)
|
|
86
91
|
throw new Error(`Could not find clickable element: "${label}"`);
|
|
87
92
|
}
|
|
@@ -216,6 +221,26 @@ async function tryClick(page, nameRe, label) {
|
|
|
216
221
|
}
|
|
217
222
|
return false;
|
|
218
223
|
}
|
|
224
|
+
async function tryClickScoped(page, nameRe, target, scope) {
|
|
225
|
+
const FAST = 3000;
|
|
226
|
+
// Find a container that contains the scope text, then click the target inside it
|
|
227
|
+
const strategies = [
|
|
228
|
+
() => page.locator(`:has-text("${scope}")`).last().getByRole('button', { name: nameRe }).first().click({ timeout: FAST }),
|
|
229
|
+
() => page.locator(`:has-text("${scope}")`).last().getByRole('link', { name: nameRe }).first().click({ timeout: FAST }),
|
|
230
|
+
() => page.locator(`:has-text("${scope}")`).last().getByText(nameRe).first().click({ timeout: FAST }),
|
|
231
|
+
// Fallback: ignore scope and click anywhere
|
|
232
|
+
() => page.getByRole('button', { name: nameRe }).first().click({ timeout: FAST }),
|
|
233
|
+
() => page.getByText(nameRe).first().click({ timeout: FAST }),
|
|
234
|
+
];
|
|
235
|
+
for (const fn of strategies) {
|
|
236
|
+
try {
|
|
237
|
+
await fn();
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
catch { /* try next */ }
|
|
241
|
+
}
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
219
244
|
async function tryFill(page, label, value) {
|
|
220
245
|
const FAST = 1500;
|
|
221
246
|
const labelRe = new RegExp(escapeRegex(label), "i");
|