@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.
@@ -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
- /** Fill an autocomplete/typeahead input: type value, wait for dropdown, click first matching option */
752
- async function tryAutocomplete(page, labelOrSelector, value) {
753
- const TIMEOUT = 2000;
754
- const locators = [
755
- page.getByLabel(new RegExp(labelOrSelector, 'i')),
756
- page.getByPlaceholder(new RegExp(labelOrSelector, 'i')),
757
- page.locator(labelOrSelector),
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 loc of locators) {
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.waitFor({ state: 'visible', timeout: TIMEOUT });
763
- await input.click({ timeout: TIMEOUT });
764
- await input.fill('', { timeout: TIMEOUT });
765
- // Type slowly so the autocomplete can react
766
- await input.type(value, { delay: 80 });
767
- // Wait for dropdown options to appear
768
- await page.waitForTimeout(600);
769
- // Try common dropdown option selectors
770
- const optionSelectors = [
771
- `[class*="option"]:has-text("${value}")`,
772
- `[class*="suggestion"]:has-text("${value}")`,
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(300);
844
+ await page.waitForTimeout(200);
796
845
  return true;
797
846
  }
798
- catch { /* try next locator */ }
847
+ catch { /* try next */ }
799
848
  }
800
849
  return false;
801
850
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cutleryapp/agent",
3
- "version": "1.0.35",
3
+ "version": "1.0.36",
4
4
  "description": "Local agent that connects your machine to the Cutlery QA platform and runs UI tests via Playwright",
5
5
  "main": "dist/cli.js",
6
6
  "bin": {