@cutleryapp/agent 1.0.45 → 1.0.46

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.
@@ -392,29 +392,19 @@ class TestExecutor {
392
392
  catch { /* next */ }
393
393
  }
394
394
  }
395
- // 2. Radio / checkbox — try before autocomplete so radios resolve in <100ms
395
+ // 2. Radio / checkbox — race all selectors simultaneously; first click wins
396
396
  if (!selHandled) {
397
- for (const sel of [
398
- `label:text-is("${optionValue}")`, // exact case-insensitive
399
- `label:has-text("${optionValue}")`, // substring match
400
- `[role="radio"]:has-text("${optionValue}")`,
401
- `input[type="radio"][value="${optionValue}" i]`,
402
- `input[type="checkbox"][value="${optionValue}" i]`,
403
- ]) {
404
- try {
405
- const loc = page.locator(sel).first();
406
- const tag = await loc.evaluate((el) => el.tagName.toLowerCase()).catch(() => 'label');
407
- if (tag === 'input') {
408
- await loc.click({ force: true, timeout: 800 });
409
- }
410
- else {
411
- await loc.click({ timeout: 800 });
412
- }
413
- selHandled = true;
414
- break;
415
- }
416
- catch { /* try next */ }
397
+ try {
398
+ await Promise.any([
399
+ page.locator(`label:text-is("${optionValue}")`).first().click({ timeout: 300 }),
400
+ page.locator(`label:has-text("${optionValue}")`).first().click({ timeout: 300 }),
401
+ page.locator(`[role="radio"]:has-text("${optionValue}")`).first().click({ timeout: 300 }),
402
+ page.locator(`input[type="radio"][value="${optionValue}" i]`).first().click({ force: true, timeout: 300 }),
403
+ page.locator(`input[type="checkbox"][value="${optionValue}" i]`).first().click({ force: true, timeout: 300 }),
404
+ ]);
405
+ selHandled = true;
417
406
  }
407
+ catch { /* not a radio/checkbox, fall through */ }
418
408
  }
419
409
  // 3. React-select / autocomplete typeahead
420
410
  if (!selHandled) {
@@ -446,17 +436,14 @@ class TestExecutor {
446
436
  if (cbMatch) {
447
437
  const labelText = cbMatch[1].trim();
448
438
  try {
449
- // Try label click (works for hidden checkboxes with styled labels)
450
- await page.locator(`label:has-text("${labelText}")`).first().click({ timeout: 2000 });
439
+ await Promise.any([
440
+ page.locator(`label:text-is("${labelText}")`).first().click({ timeout: 300 }),
441
+ page.locator(`label:has-text("${labelText}")`).first().click({ timeout: 300 }),
442
+ page.getByLabel(new RegExp(labelText, 'i')).first().check({ force: true, timeout: 300 }),
443
+ ]);
451
444
  handled = true;
452
445
  }
453
- catch {
454
- try {
455
- await page.getByLabel(new RegExp(labelText, 'i')).first().check({ timeout: 2000 });
456
- handled = true;
457
- }
458
- catch { /* fall to AI */ }
459
- }
446
+ catch { /* fall to AI */ }
460
447
  }
461
448
  }
462
449
  // 7. Generic click fallback — try any element containing the step text before AI
@@ -980,25 +967,20 @@ async function tryAIClick(page, selector) {
980
967
  async function tryAutocomplete(page, fieldLabel, value) {
981
968
  // Click the matching option in whatever dropdown is currently open — returns true only on success
982
969
  async function clickOpenOption() {
983
- const optionSelectors = [
984
- `[role="option"]:has-text("${value}")`,
985
- `[class*="option"]:has-text("${value}")`,
986
- `[class*="suggestion"]:has-text("${value}")`,
987
- `[class*="item"]:has-text("${value}")`,
988
- `li:has-text("${value}")`,
989
- ];
990
- for (const sel of optionSelectors) {
991
- try {
992
- const opt = page.locator(sel).first();
993
- if (await opt.isVisible({ timeout: 600 })) {
994
- await opt.click({ timeout: 1500 });
995
- console.log(` ✓ Dropdown option clicked via "${sel}"`);
996
- return true;
997
- }
998
- }
999
- catch { /* try next */ }
970
+ // Race all option selectors simultaneously — first visible one wins
971
+ try {
972
+ await Promise.any([
973
+ `[role="option"]:has-text("${value}")`,
974
+ `[class*="option"]:has-text("${value}")`,
975
+ `[class*="suggestion"]:has-text("${value}")`,
976
+ `[class*="item"]:has-text("${value}")`,
977
+ `li:has-text("${value}")`,
978
+ ].map(sel => page.locator(sel).first().click({ timeout: 300 })));
979
+ return true;
980
+ }
981
+ catch {
982
+ return false;
1000
983
  }
1001
- return false;
1002
984
  }
1003
985
  // Strategy 1: React-select — find the control that BELONGS to this label via DOM traversal
1004
986
  // so we don't accidentally open the wrong dropdown on a page with multiple selects
@@ -1035,13 +1017,13 @@ async function tryAutocomplete(page, fieldLabel, value) {
1035
1017
  // Type into the now-visible input inside the react-select
1036
1018
  const innerInput = page.locator('[class*="react-select__input"] input,[class*="select__input"] input').first();
1037
1019
  try {
1038
- await innerInput.waitFor({ state: 'visible', timeout: 800 });
1039
- await innerInput.type(value, { delay: 30 });
1020
+ await innerInput.waitFor({ state: 'visible', timeout: 400 });
1021
+ await innerInput.type(value, { delay: 20 });
1040
1022
  }
1041
1023
  catch {
1042
- await page.keyboard.type(value, { delay: 30 });
1024
+ await page.keyboard.type(value, { delay: 20 });
1043
1025
  }
1044
- await page.waitForTimeout(400);
1026
+ await page.waitForTimeout(250);
1045
1027
  if (await clickOpenOption())
1046
1028
  return true;
1047
1029
  // No confirmed click → don't claim success, fall through to next strategy
@@ -1068,11 +1050,11 @@ async function tryAutocomplete(page, fieldLabel, value) {
1068
1050
  if (['radio', 'checkbox', 'submit', 'button', 'file', 'hidden'].includes(inputType))
1069
1051
  continue;
1070
1052
  }
1071
- await input.waitFor({ state: 'visible', timeout: 500 });
1072
- await input.click({ timeout: 500 });
1053
+ await input.waitFor({ state: 'visible', timeout: 300 });
1054
+ await input.click({ timeout: 300 });
1073
1055
  await input.fill('');
1074
- await input.type(value, { delay: 30 });
1075
- await page.waitForTimeout(350);
1056
+ await input.type(value, { delay: 20 });
1057
+ await page.waitForTimeout(250);
1076
1058
  if (await clickOpenOption())
1077
1059
  return true;
1078
1060
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cutleryapp/agent",
3
- "version": "1.0.45",
3
+ "version": "1.0.46",
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": {