@cutleryapp/agent 1.0.9 → 1.0.11
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 +30 -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,27 @@ 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
|
+
// Strip trailing generic nouns that won't appear verbatim on the page
|
|
227
|
+
const cleanScope = scope.replace(/\s+(?:product|item|section|card|row|container|element|button|link|area|panel|block)$/i, '').trim();
|
|
228
|
+
const strategies = [
|
|
229
|
+
() => page.locator(`:has-text("${cleanScope}")`).last().getByRole('button', { name: nameRe }).first().click({ timeout: FAST }),
|
|
230
|
+
() => page.locator(`:has-text("${cleanScope}")`).last().getByRole('link', { name: nameRe }).first().click({ timeout: FAST }),
|
|
231
|
+
() => page.locator(`:has-text("${cleanScope}")`).last().getByText(nameRe).first().click({ timeout: FAST }),
|
|
232
|
+
// Fallback: ignore scope and click anywhere
|
|
233
|
+
() => page.getByRole('button', { name: nameRe }).first().click({ timeout: FAST }),
|
|
234
|
+
() => page.getByText(nameRe).first().click({ timeout: FAST }),
|
|
235
|
+
];
|
|
236
|
+
for (const fn of strategies) {
|
|
237
|
+
try {
|
|
238
|
+
await fn();
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
241
|
+
catch { /* try next */ }
|
|
242
|
+
}
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
219
245
|
async function tryFill(page, label, value) {
|
|
220
246
|
const FAST = 1500;
|
|
221
247
|
const labelRe = new RegExp(escapeRegex(label), "i");
|