@cutleryapp/agent 1.0.35 → 1.0.36
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 +91 -42
- package/package.json +1 -1
package/dist/mcp-executor.js
CHANGED
|
@@ -230,6 +230,21 @@ class TestExecutor {
|
|
|
230
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
|
+
catch { /* try checkbox */ }
|
|
234
|
+
}
|
|
235
|
+
// Checkbox / radio fallback — label click or direct input click
|
|
236
|
+
if (!selHandled) {
|
|
237
|
+
try {
|
|
238
|
+
await page.locator(`label:has-text("${optionValue}")`).first().click({ timeout: 2000 });
|
|
239
|
+
selHandled = true;
|
|
240
|
+
}
|
|
241
|
+
catch { /* try input by value */ }
|
|
242
|
+
}
|
|
243
|
+
if (!selHandled) {
|
|
244
|
+
try {
|
|
245
|
+
await page.locator(`input[type="radio"][value="${optionValue}" i], input[type="checkbox"][value="${optionValue}" i]`).first().click({ force: true, timeout: 2000 });
|
|
246
|
+
selHandled = true;
|
|
247
|
+
}
|
|
233
248
|
catch { /* fall to AI */ }
|
|
234
249
|
}
|
|
235
250
|
if (selHandled)
|
|
@@ -748,54 +763,88 @@ async function tryAIClick(page, selector) {
|
|
|
748
763
|
}
|
|
749
764
|
return false;
|
|
750
765
|
}
|
|
751
|
-
/**
|
|
752
|
-
async function tryAutocomplete(page,
|
|
753
|
-
const
|
|
754
|
-
const
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
766
|
+
/** Select an option from a React-select / autocomplete / combobox / dropdown */
|
|
767
|
+
async function tryAutocomplete(page, fieldLabel, value) {
|
|
768
|
+
const esc = (s) => s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
769
|
+
const labelRe = new RegExp(esc(fieldLabel), 'i');
|
|
770
|
+
// Click an option from whatever dropdown is currently open on the page
|
|
771
|
+
async function clickOpenOption() {
|
|
772
|
+
const optionSelectors = [
|
|
773
|
+
`[role="option"]:has-text("${value}")`,
|
|
774
|
+
`[class*="option"]:has-text("${value}")`,
|
|
775
|
+
`[class*="suggestion"]:has-text("${value}")`,
|
|
776
|
+
`[class*="item"]:has-text("${value}")`,
|
|
777
|
+
`li:has-text("${value}")`,
|
|
778
|
+
];
|
|
779
|
+
for (const sel of optionSelectors) {
|
|
780
|
+
try {
|
|
781
|
+
const opt = page.locator(sel).first();
|
|
782
|
+
if (await opt.isVisible({ timeout: 600 })) {
|
|
783
|
+
await opt.click({ timeout: 1500 });
|
|
784
|
+
console.log(` ✓ Dropdown option clicked via "${sel}"`);
|
|
785
|
+
return true;
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
catch { /* try next */ }
|
|
789
|
+
}
|
|
790
|
+
return false;
|
|
791
|
+
}
|
|
792
|
+
// Strategy 1: React-select — click the control div, type into its hidden input
|
|
793
|
+
const reactControlSelectors = [
|
|
794
|
+
`[class*="react-select__control"]`,
|
|
795
|
+
`[class*="select__control"]`,
|
|
796
|
+
`[class*="Select__control"]`,
|
|
758
797
|
];
|
|
759
|
-
for (const
|
|
798
|
+
for (const ctrlSel of reactControlSelectors) {
|
|
799
|
+
try {
|
|
800
|
+
// Find a React-select control near the label text
|
|
801
|
+
// Find label element, then look for the control near it via DOM traversal
|
|
802
|
+
const labelEl = page.getByText(labelRe, { exact: false }).first();
|
|
803
|
+
if (!await labelEl.isVisible({ timeout: 500 }).catch(() => false))
|
|
804
|
+
continue;
|
|
805
|
+
// Try sibling/parent-scoped control
|
|
806
|
+
const ctrl = page.locator(ctrlSel);
|
|
807
|
+
if (await ctrl.count() === 0)
|
|
808
|
+
continue;
|
|
809
|
+
await ctrl.first().click({ timeout: 2000 });
|
|
810
|
+
// The hidden input inside the control is now active
|
|
811
|
+
const innerInput = page.locator(`[class*="react-select__input"] input, [class*="select__input"] input`).first();
|
|
812
|
+
await innerInput.type(value, { delay: 60 });
|
|
813
|
+
await page.waitForTimeout(500);
|
|
814
|
+
if (await clickOpenOption())
|
|
815
|
+
return true;
|
|
816
|
+
// If specific option not found, press Enter
|
|
817
|
+
await page.keyboard.press('Enter');
|
|
818
|
+
await page.waitForTimeout(200);
|
|
819
|
+
return true;
|
|
820
|
+
}
|
|
821
|
+
catch { /* try next */ }
|
|
822
|
+
}
|
|
823
|
+
// Strategy 2: combobox / input by label or placeholder — type and pick option
|
|
824
|
+
const inputLocators = [
|
|
825
|
+
page.getByRole('combobox', { name: labelRe }),
|
|
826
|
+
page.getByLabel(labelRe),
|
|
827
|
+
page.getByPlaceholder(labelRe),
|
|
828
|
+
];
|
|
829
|
+
for (const loc of inputLocators) {
|
|
760
830
|
try {
|
|
761
831
|
const input = loc.first();
|
|
762
|
-
await input.
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
await input.
|
|
767
|
-
|
|
768
|
-
await
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
`[class*="item"]:has-text("${value}")`,
|
|
774
|
-
`[role="option"]:has-text("${value}")`,
|
|
775
|
-
`[role="listbox"] [class*="option"]`,
|
|
776
|
-
`[class*="menu"] [class*="option"]`,
|
|
777
|
-
`[class*="dropdown"] li`,
|
|
778
|
-
`ul[class*="auto"] li`,
|
|
779
|
-
`.react-select__option`,
|
|
780
|
-
`.autocomplete-item`,
|
|
781
|
-
];
|
|
782
|
-
for (const optSel of optionSelectors) {
|
|
783
|
-
try {
|
|
784
|
-
const opt = page.locator(optSel).first();
|
|
785
|
-
if (await opt.isVisible({ timeout: 500 })) {
|
|
786
|
-
await opt.click({ timeout: TIMEOUT });
|
|
787
|
-
console.log(` ✓ Autocomplete: typed "${value}", clicked option via "${optSel}"`);
|
|
788
|
-
return true;
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
|
-
catch { /* try next */ }
|
|
792
|
-
}
|
|
793
|
-
// Fallback: press Enter if a highlighted option exists
|
|
832
|
+
const tag = await input.evaluate((el) => el.tagName.toLowerCase()).catch(() => '');
|
|
833
|
+
// Skip non-input elements (e.g. wrapper divs returned by getByLabel for React-select)
|
|
834
|
+
if (tag && !['input', 'textarea'].includes(tag))
|
|
835
|
+
continue;
|
|
836
|
+
await input.waitFor({ state: 'visible', timeout: 1500 });
|
|
837
|
+
await input.click({ timeout: 1500 });
|
|
838
|
+
await input.fill('');
|
|
839
|
+
await input.type(value, { delay: 60 });
|
|
840
|
+
await page.waitForTimeout(500);
|
|
841
|
+
if (await clickOpenOption())
|
|
842
|
+
return true;
|
|
794
843
|
await page.keyboard.press('Enter');
|
|
795
|
-
await page.waitForTimeout(
|
|
844
|
+
await page.waitForTimeout(200);
|
|
796
845
|
return true;
|
|
797
846
|
}
|
|
798
|
-
catch { /* try next
|
|
847
|
+
catch { /* try next */ }
|
|
799
848
|
}
|
|
800
849
|
return false;
|
|
801
850
|
}
|