@cutleryapp/agent 1.0.39 → 1.0.40
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 +21 -15
- package/package.json +1 -1
package/dist/mcp-executor.js
CHANGED
|
@@ -334,36 +334,37 @@ class TestExecutor {
|
|
|
334
334
|
const optionValue = selMatch[1].trim();
|
|
335
335
|
const fieldLabel = selMatch[2].trim();
|
|
336
336
|
let selHandled = false;
|
|
337
|
-
//
|
|
337
|
+
// 1. Native <select> — fastest for actual <select> elements
|
|
338
338
|
try {
|
|
339
339
|
const fieldLoc = page.getByLabel(new RegExp(fieldLabel, 'i')).first();
|
|
340
340
|
await fieldLoc.selectOption({ label: optionValue }, { timeout: 800 });
|
|
341
341
|
selHandled = true;
|
|
342
342
|
}
|
|
343
343
|
catch { /* not a native select */ }
|
|
344
|
-
//
|
|
345
|
-
|
|
346
|
-
selHandled = await tryAutocomplete(page, fieldLabel, optionValue);
|
|
347
|
-
}
|
|
348
|
-
// Try clicking a visible option in an already-open dropdown
|
|
344
|
+
// 2. Radio / checkbox label click — do this BEFORE autocomplete so radio buttons
|
|
345
|
+
// are handled in <100ms instead of waiting through autocomplete timeouts
|
|
349
346
|
if (!selHandled) {
|
|
350
347
|
try {
|
|
351
|
-
await page.locator(`
|
|
348
|
+
await page.locator(`label:has-text("${optionValue}")`).first().click({ timeout: 800 });
|
|
352
349
|
selHandled = true;
|
|
353
350
|
}
|
|
354
|
-
catch { /*
|
|
351
|
+
catch { /* not a labelled radio/checkbox */ }
|
|
355
352
|
}
|
|
356
|
-
// Checkbox / radio fallback — label click or direct input click
|
|
357
353
|
if (!selHandled) {
|
|
358
354
|
try {
|
|
359
|
-
await page.locator(`
|
|
355
|
+
await page.locator(`input[type="radio"][value="${optionValue}" i], input[type="checkbox"][value="${optionValue}" i]`).first().click({ force: true, timeout: 800 });
|
|
360
356
|
selHandled = true;
|
|
361
357
|
}
|
|
362
|
-
catch { /*
|
|
358
|
+
catch { /* not an input with matching value */ }
|
|
363
359
|
}
|
|
360
|
+
// 3. React-select / autocomplete typeahead
|
|
361
|
+
if (!selHandled) {
|
|
362
|
+
selHandled = await tryAutocomplete(page, fieldLabel, optionValue);
|
|
363
|
+
}
|
|
364
|
+
// 4. Already-open dropdown option
|
|
364
365
|
if (!selHandled) {
|
|
365
366
|
try {
|
|
366
|
-
await page.locator(`
|
|
367
|
+
await page.locator(`[role="option"]:has-text("${optionValue}"), [class*="option"]:has-text("${optionValue}")`).first().click({ timeout: 1000 });
|
|
367
368
|
selHandled = true;
|
|
368
369
|
}
|
|
369
370
|
catch { /* fall to AI */ }
|
|
@@ -978,12 +979,17 @@ async function tryAutocomplete(page, fieldLabel, value) {
|
|
|
978
979
|
for (const loc of inputLocators) {
|
|
979
980
|
try {
|
|
980
981
|
const input = loc.first();
|
|
981
|
-
// Skip wrapper divs
|
|
982
|
+
// Skip wrapper divs and non-text inputs (radio/checkbox handled by label click above)
|
|
982
983
|
const tag = await input.evaluate((el) => el.tagName.toLowerCase()).catch(() => '');
|
|
983
984
|
if (tag && !['input', 'textarea'].includes(tag))
|
|
984
985
|
continue;
|
|
985
|
-
|
|
986
|
-
|
|
986
|
+
if (tag === 'input') {
|
|
987
|
+
const inputType = await input.evaluate((el) => el.type || '').catch(() => '');
|
|
988
|
+
if (['radio', 'checkbox', 'submit', 'button', 'file', 'hidden'].includes(inputType))
|
|
989
|
+
continue;
|
|
990
|
+
}
|
|
991
|
+
await input.waitFor({ state: 'visible', timeout: 500 });
|
|
992
|
+
await input.click({ timeout: 500 });
|
|
987
993
|
await input.fill('');
|
|
988
994
|
await input.type(value, { delay: 30 });
|
|
989
995
|
await page.waitForTimeout(350);
|