@cutleryapp/agent 1.0.34 → 1.0.35
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 +18 -18
- package/package.json +1 -1
package/dist/mcp-executor.js
CHANGED
|
@@ -216,7 +216,7 @@ class TestExecutor {
|
|
|
216
216
|
// Try native <select>
|
|
217
217
|
try {
|
|
218
218
|
const fieldLoc = page.getByLabel(new RegExp(fieldLabel, 'i')).first();
|
|
219
|
-
await fieldLoc.selectOption({ label: optionValue });
|
|
219
|
+
await fieldLoc.selectOption({ label: optionValue }, { timeout: 3000 });
|
|
220
220
|
selHandled = true;
|
|
221
221
|
}
|
|
222
222
|
catch { /* not a native select */ }
|
|
@@ -227,7 +227,7 @@ class TestExecutor {
|
|
|
227
227
|
// Try clicking a visible option in an already-open dropdown
|
|
228
228
|
if (!selHandled) {
|
|
229
229
|
try {
|
|
230
|
-
await page.locator(`[role="option"]:has-text("${optionValue}"), [class*="option"]:has-text("${optionValue}")`).first().click({ timeout:
|
|
230
|
+
await page.locator(`[role="option"]:has-text("${optionValue}"), [class*="option"]:has-text("${optionValue}")`).first().click({ timeout: 1500 });
|
|
231
231
|
selHandled = true;
|
|
232
232
|
}
|
|
233
233
|
catch { /* fall to AI */ }
|
|
@@ -243,12 +243,12 @@ class TestExecutor {
|
|
|
243
243
|
const labelText = cbMatch[1].trim();
|
|
244
244
|
try {
|
|
245
245
|
// Try label click (works for hidden checkboxes with styled labels)
|
|
246
|
-
await page.locator(`label:has-text("${labelText}")`).first().click({ timeout:
|
|
246
|
+
await page.locator(`label:has-text("${labelText}")`).first().click({ timeout: 2000 });
|
|
247
247
|
handled = true;
|
|
248
248
|
}
|
|
249
249
|
catch {
|
|
250
250
|
try {
|
|
251
|
-
await page.getByLabel(new RegExp(labelText, 'i')).first().check({ timeout:
|
|
251
|
+
await page.getByLabel(new RegExp(labelText, 'i')).first().check({ timeout: 2000 });
|
|
252
252
|
handled = true;
|
|
253
253
|
}
|
|
254
254
|
catch { /* fall to AI */ }
|
|
@@ -332,7 +332,7 @@ function extractSelector(step, pattern) {
|
|
|
332
332
|
}
|
|
333
333
|
// Fast probe: try each locator strategy with a short timeout so fallbacks don't stall
|
|
334
334
|
async function tryClick(page, nameRe, label) {
|
|
335
|
-
const FAST =
|
|
335
|
+
const FAST = 800;
|
|
336
336
|
const strategies = [
|
|
337
337
|
() => page.getByRole('button', { name: nameRe }).first().click({ timeout: FAST }),
|
|
338
338
|
() => page.getByRole('link', { name: nameRe }).first().click({ timeout: FAST }),
|
|
@@ -584,7 +584,7 @@ async function aiStepFallback(page, stepText, stepAttachment = null) {
|
|
|
584
584
|
console.log(` 🤖 aiStepFallback called. hasAttachment=${!!stepAttachment}`);
|
|
585
585
|
const { default: OpenAI } = await import('openai');
|
|
586
586
|
const openai = new OpenAI({ apiKey: openaiKey });
|
|
587
|
-
const MAX_ROUNDS = stepAttachment ?
|
|
587
|
+
const MAX_ROUNDS = stepAttachment ? 6 : 3;
|
|
588
588
|
let consecutiveFailures = 0;
|
|
589
589
|
for (let round = 0; round < MAX_ROUNDS; round++) {
|
|
590
590
|
const domElements = await extractDomElements(page);
|
|
@@ -642,9 +642,9 @@ ${domElements}` + (stepAttachment ? `\n\nThe REFERENCE IMAGE (second image) show
|
|
|
642
642
|
else if (act.action === 'select') {
|
|
643
643
|
let done = false;
|
|
644
644
|
for (const fn of [
|
|
645
|
-
() => page.locator(act.selector).first().selectOption({ label: act.value }, { timeout:
|
|
646
|
-
() => page.locator(act.selector).first().selectOption({ value: act.value }, { timeout:
|
|
647
|
-
async () => { await page.locator(act.selector).first().click({ timeout:
|
|
645
|
+
() => page.locator(act.selector).first().selectOption({ label: act.value }, { timeout: 2000 }),
|
|
646
|
+
() => page.locator(act.selector).first().selectOption({ value: act.value }, { timeout: 2000 }),
|
|
647
|
+
async () => { await page.locator(act.selector).first().click({ timeout: 2000 }); await page.getByText(act.value, { exact: false }).first().click({ timeout: 2000 }); },
|
|
648
648
|
]) {
|
|
649
649
|
try {
|
|
650
650
|
await fn();
|
|
@@ -664,11 +664,11 @@ ${domElements}` + (stepAttachment ? `\n\nThe REFERENCE IMAGE (second image) show
|
|
|
664
664
|
// Derive a label text from selector for label-based fallbacks
|
|
665
665
|
const labelHint = act.label || act.selector.replace(/[#.\[\]"'=*^$]/g, ' ').trim();
|
|
666
666
|
for (const fn of [
|
|
667
|
-
() => el.click({ force: true, timeout:
|
|
668
|
-
() => el.check({ force: true, timeout:
|
|
669
|
-
() => page.locator(`label[for="${act.selector.replace('#', '')}"]`).click({ timeout:
|
|
670
|
-
() => page.locator(`label:has-text("${labelHint}")`).first().click({ timeout:
|
|
671
|
-
() => page.locator(`label:has(${act.selector})`).click({ timeout:
|
|
667
|
+
() => el.click({ force: true, timeout: 2000 }), // force bypasses visibility
|
|
668
|
+
() => el.check({ force: true, timeout: 2000 }),
|
|
669
|
+
() => page.locator(`label[for="${act.selector.replace('#', '')}"]`).click({ timeout: 2000 }),
|
|
670
|
+
() => page.locator(`label:has-text("${labelHint}")`).first().click({ timeout: 2000 }),
|
|
671
|
+
() => page.locator(`label:has(${act.selector})`).click({ timeout: 2000 }),
|
|
672
672
|
]) {
|
|
673
673
|
try {
|
|
674
674
|
await fn();
|
|
@@ -726,7 +726,7 @@ ${domElements}` + (stepAttachment ? `\n\nThe REFERENCE IMAGE (second image) show
|
|
|
726
726
|
}
|
|
727
727
|
/** Try clicking with multiple selector strategies derived from AI suggestion */
|
|
728
728
|
async function tryAIClick(page, selector) {
|
|
729
|
-
const TIMEOUT =
|
|
729
|
+
const TIMEOUT = 3000;
|
|
730
730
|
// Build fallback variants: the AI selector + text-based alternatives
|
|
731
731
|
const textMatch = selector.match(/:has-text\("([^"]+)"\)|:text\("([^"]+)"\)/);
|
|
732
732
|
const text = textMatch ? (textMatch[1] || textMatch[2]) : null;
|
|
@@ -750,7 +750,7 @@ async function tryAIClick(page, selector) {
|
|
|
750
750
|
}
|
|
751
751
|
/** Fill an autocomplete/typeahead input: type value, wait for dropdown, click first matching option */
|
|
752
752
|
async function tryAutocomplete(page, labelOrSelector, value) {
|
|
753
|
-
const TIMEOUT =
|
|
753
|
+
const TIMEOUT = 2000;
|
|
754
754
|
const locators = [
|
|
755
755
|
page.getByLabel(new RegExp(labelOrSelector, 'i')),
|
|
756
756
|
page.getByPlaceholder(new RegExp(labelOrSelector, 'i')),
|
|
@@ -782,7 +782,7 @@ async function tryAutocomplete(page, labelOrSelector, value) {
|
|
|
782
782
|
for (const optSel of optionSelectors) {
|
|
783
783
|
try {
|
|
784
784
|
const opt = page.locator(optSel).first();
|
|
785
|
-
if (await opt.isVisible({ timeout:
|
|
785
|
+
if (await opt.isVisible({ timeout: 500 })) {
|
|
786
786
|
await opt.click({ timeout: TIMEOUT });
|
|
787
787
|
console.log(` ✓ Autocomplete: typed "${value}", clicked option via "${optSel}"`);
|
|
788
788
|
return true;
|
|
@@ -946,7 +946,7 @@ async function tryClickScoped(page, nameRe, target, scope) {
|
|
|
946
946
|
return false;
|
|
947
947
|
}
|
|
948
948
|
async function tryFill(page, label, value) {
|
|
949
|
-
const FAST =
|
|
949
|
+
const FAST = 800;
|
|
950
950
|
const labelRe = new RegExp(escapeRegex(label), "i");
|
|
951
951
|
const variants = labelVariants(label);
|
|
952
952
|
const attrContains = (attr) => variants
|