@cutleryapp/agent 1.0.32 → 1.0.34

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.
@@ -61,7 +61,7 @@ class TestExecutor {
61
61
  }
62
62
  console.log('[executor] varMap:', JSON.stringify(varMap));
63
63
  const resolveVars = (s) => s.replace(/\{\{([^}]+)\}\}/g, (match, key) => varMap[key.trim()] ?? match).trim();
64
- const steps = testCase.automated_steps.map(resolveVars);
64
+ const steps = testCase.automated_steps.filter((s) => !s.startsWith('[DISABLED] ')).map(resolveVars);
65
65
  console.log('[executor] resolved steps:', JSON.stringify(steps));
66
66
  const browserLaunchers = { chromium: playwright_1.chromium, firefox: playwright_1.firefox, webkit: playwright_1.webkit };
67
67
  const launchFn = browserLaunchers[this.options.browserType] ?? playwright_1.chromium;
@@ -206,15 +206,53 @@ class TestExecutor {
206
206
  handled = true;
207
207
  }
208
208
  }
209
- // 6. Select — dropdown
209
+ // 6. Select — native dropdown, then React-select/autocomplete fallback
210
210
  if (!handled && (lower.includes("select") || lower.includes("choose"))) {
211
- const selMatch = raw.match(/select\s+"?([^"]+?)"?\s+(?:from|in)\s+"?([^"]+?)"?\s*(?:dropdown|select|field)?$/i);
211
+ const selMatch = raw.match(/(?:select|choose)\s+"?([^"]+?)"?\s+(?:from|in)\s+"?([^"]+?)"?\s*(?:dropdown|select|field)?$/i);
212
212
  if (selMatch) {
213
+ const optionValue = selMatch[1].trim();
214
+ const fieldLabel = selMatch[2].trim();
215
+ let selHandled = false;
216
+ // Try native <select>
213
217
  try {
214
- await page.selectOption(selMatch[2].trim(), { label: selMatch[1].trim() });
218
+ const fieldLoc = page.getByLabel(new RegExp(fieldLabel, 'i')).first();
219
+ await fieldLoc.selectOption({ label: optionValue });
220
+ selHandled = true;
221
+ }
222
+ catch { /* not a native select */ }
223
+ // Try React-select / autocomplete typeahead
224
+ if (!selHandled) {
225
+ selHandled = await tryAutocomplete(page, fieldLabel, optionValue);
226
+ }
227
+ // Try clicking a visible option in an already-open dropdown
228
+ if (!selHandled) {
229
+ try {
230
+ await page.locator(`[role="option"]:has-text("${optionValue}"), [class*="option"]:has-text("${optionValue}")`).first().click({ timeout: 3000 });
231
+ selHandled = true;
232
+ }
233
+ catch { /* fall to AI */ }
234
+ }
235
+ if (selHandled)
236
+ handled = true;
237
+ }
238
+ }
239
+ // 6b. Check/uncheck — checkbox or radio by label
240
+ if (!handled && (lower.startsWith("check ") || lower.startsWith("tick ") || lower.startsWith("select ")) && !handled) {
241
+ const cbMatch = raw.match(/(?:check|tick|select)\s+"?([^"]+?)"?\s*(?:checkbox|option|hobby|hobbies)?$/i);
242
+ if (cbMatch) {
243
+ const labelText = cbMatch[1].trim();
244
+ try {
245
+ // Try label click (works for hidden checkboxes with styled labels)
246
+ await page.locator(`label:has-text("${labelText}")`).first().click({ timeout: 3000 });
215
247
  handled = true;
216
248
  }
217
- catch { /* fall to AI */ }
249
+ catch {
250
+ try {
251
+ await page.getByLabel(new RegExp(labelText, 'i')).first().check({ timeout: 3000 });
252
+ handled = true;
253
+ }
254
+ catch { /* fall to AI */ }
255
+ }
218
256
  }
219
257
  }
220
258
  // 7. AI — single-shot for deterministic steps, full loop for intent steps
@@ -623,9 +661,14 @@ ${domElements}` + (stepAttachment ? `\n\nThe REFERENCE IMAGE (second image) show
623
661
  const checked = await el.isChecked().catch(() => false);
624
662
  if (!checked) {
625
663
  let done = false;
664
+ // Derive a label text from selector for label-based fallbacks
665
+ const labelHint = act.label || act.selector.replace(/[#.\[\]"'=*^$]/g, ' ').trim();
626
666
  for (const fn of [
627
- () => el.click({ timeout: 4000 }),
628
- () => page.locator(`label:has-text("${act.label || ''}")`).click({ timeout: 4000 }),
667
+ () => el.click({ force: true, timeout: 4000 }), // force bypasses visibility
668
+ () => el.check({ force: true, timeout: 4000 }),
669
+ () => page.locator(`label[for="${act.selector.replace('#', '')}"]`).click({ timeout: 4000 }),
670
+ () => page.locator(`label:has-text("${labelHint}")`).first().click({ timeout: 4000 }),
671
+ () => page.locator(`label:has(${act.selector})`).click({ timeout: 4000 }),
629
672
  ]) {
630
673
  try {
631
674
  await fn();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cutleryapp/agent",
3
- "version": "1.0.32",
3
+ "version": "1.0.34",
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": {