@cutleryapp/agent 1.0.19 → 1.0.20

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.
@@ -60,9 +60,9 @@ class TestExecutor {
60
60
  });
61
61
  let stepError;
62
62
  try {
63
+ // Navigate is handled directly — URL extraction doesn't need vision
63
64
  if (lower.includes("navigate to") || lower.includes("go to")) {
64
- // Extract URL wherever it appears in the step (handles emoji/symbol prefixes)
65
- const urlMatch = raw.match(/(?:navigate to|go to)\s+(https?:\/\/\S+|\/\S*|\S+\.\S+)/i);
65
+ const urlMatch = raw.match(/(?:navigate\s+to|go\s+to)\s+(https?:\/\/\S+|\/\S*|\S+\.\S+)/i);
66
66
  if (urlMatch) {
67
67
  let url = urlMatch[1].trim();
68
68
  if (url.startsWith("/") && this.options.baseUrl) {
@@ -71,110 +71,18 @@ class TestExecutor {
71
71
  await page.goto(url, { waitUntil: "domcontentloaded", timeout: 30000 });
72
72
  }
73
73
  else if (this.options.baseUrl) {
74
- // Step says "navigate to" but no URL found — go to baseUrl
75
74
  await page.goto(this.options.baseUrl, { waitUntil: "domcontentloaded", timeout: 30000 });
76
75
  }
77
76
  }
78
- else if (lower.includes("click")) {
79
- const labelMatch = raw.match(/click\s+(?:on\s+)?(?:the\s+)?"?([^"]+?)"?(?:\s+(?:button|link|tab))?$/i);
80
- let label = labelMatch?.[1]?.trim();
81
- if (label) {
82
- // Split "Add to cart under Sauce Labs Bike Light product" into target + scope
83
- const scopeMatch = label.match(/^(.+?)\s+(?:under|inside|within|in the|in)\s+(.+)$/i);
84
- const target = scopeMatch ? scopeMatch[1].trim() : label;
85
- const scope = scopeMatch ? scopeMatch[2].trim() : null;
86
- const nameRe = new RegExp(escapeRegex(target), 'i');
87
- const clicked = scope
88
- ? await tryClickScoped(page, nameRe, target, scope)
89
- : await tryClick(page, nameRe, target);
90
- if (!clicked)
91
- throw new Error(`Could not find clickable element: "${label}"`);
92
- }
93
- }
94
- else if (lower.includes("fill") || lower.includes("type") || lower.includes("enter")) {
95
- // Support both quoted and unquoted formats:
96
- // Fill "standard_user" in "Username"
97
- // Fill standard_user in Username field
98
- // Split on first " in " / " into " to separate value from field
99
- const match = raw.match(/(?:enter|fill|type)\s+"([^"]+)"\s+(?:in|into)\s+(?:the\s+)?"?([^"]+?)"?\s*(?:field|input|box|area)?\s*$/i) ||
100
- raw.match(/(?:enter|fill|type)\s+(\S+)\s+(?:in|into)\s+(?:the\s+)?(.+?)\s*(?:field|input|box|area)?\s*$/i);
101
- if (match) {
102
- const value = match[1].trim();
103
- const fieldLabel = match[2].trim();
104
- const looksLikeCss = (s) => /[#.\[\]:>]/.test(s);
105
- if (looksLikeCss(fieldLabel)) {
106
- await page.waitForSelector(fieldLabel, { state: "visible", timeout: 5000 });
107
- await page.fill(fieldLabel, value);
108
- }
109
- else {
110
- await tryFill(page, fieldLabel, value);
111
- }
112
- }
113
- }
114
- else if (lower.includes("wait") && lower.includes("second")) {
115
- const ms = raw.match(/wait\s+(\d+)\s*sec/i);
116
- if (ms)
117
- await page.waitForTimeout(parseInt(ms[1]) * 1000);
118
- }
119
- else if (lower.includes("wait for") && !lower.includes("second")) {
120
- const sel = extractSelector(raw, /wait for\s+"?([^"]+)"?\s+to be/i);
121
- if (sel)
122
- await page.waitForSelector(sel, { state: "visible", timeout: 15000 });
123
- }
124
- else if (lower.includes("verify") || lower.includes("check") || lower.includes("assert") || lower.includes("should")) {
125
- // Support: Verify "text", Verify I see text Foo, Verify text Foo is not displayed
126
- const isNegative = /not\s+(?:displayed|visible|present)/i.test(raw);
127
- const textMatch = raw.match(/"([^"]+)"/) ||
128
- raw.match(/(?:verify|check|assert)\s+(?:i\s+see\s+(?:text\s+)?|text\s+)?(.+?)(?:\s+is\s+(?:not\s+)?(?:displayed|visible|present))?$/i);
129
- if (textMatch) {
130
- const expected = textMatch[1].trim();
131
- if (isNegative) {
132
- const content = await page.textContent('body') || '';
133
- if (content.includes(expected))
134
- throw new Error(`Text "${expected}" should NOT be visible but was found`);
135
- }
136
- else {
137
- try {
138
- await page.waitForFunction((text) => document.body.innerText.includes(text), expected, { timeout: 10000 });
139
- }
140
- catch {
141
- throw new Error(`Expected text not found: "${expected}"`);
142
- }
143
- }
144
- }
145
- }
146
- else if (lower.includes("select") || lower.includes("choose")) {
147
- const selMatch = raw.match(/select\s+"?([^"]+?)"?\s+(?:from|in)\s+"?([^"]+?)"?\s*(?:dropdown|select|field)?$/i);
148
- if (selMatch) {
149
- try {
150
- await page.selectOption(selMatch[2].trim(), { label: selMatch[1].trim() });
151
- }
152
- catch {
153
- // fallback: click the dropdown then click the option
154
- await tryClick(page, new RegExp(escapeRegex(selMatch[2].trim()), 'i'), selMatch[2].trim());
155
- await tryClick(page, new RegExp(escapeRegex(selMatch[1].trim()), 'i'), selMatch[1].trim());
156
- }
157
- }
158
- else {
159
- await aiStepFallback(page, raw);
160
- }
161
- }
162
77
  else {
163
- // Unknown step let AI interpret and execute it
78
+ // ALL other steps: AI reads the screen and performs the action
79
+ console.log(` 🤖 AI executing: "${raw}"`);
164
80
  await aiStepFallback(page, raw);
165
81
  }
166
82
  }
167
83
  catch (err) {
168
- // If a recognised handler threw, try AI fallback before marking as failed
169
- console.log(` ⚠️ Step failed (${err.message}), trying AI fallback...`);
170
- try {
171
- await aiStepFallback(page, raw);
172
- stepError = undefined; // AI recovered it
173
- }
174
- catch (aiErr) {
175
- stepError = err.message; // Report original error
176
- result.success = false;
177
- }
84
+ stepError = err.message;
85
+ result.success = false;
178
86
  }
179
87
  // Screenshot after each step
180
88
  let screenshotB64 = "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cutleryapp/agent",
3
- "version": "1.0.19",
3
+ "version": "1.0.20",
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": {