slapify 0.0.16 → 0.0.17
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/ai/interpreter.js +1 -331
- package/dist/browser/agent.js +1 -485
- package/dist/cli.js +1 -1553
- package/dist/config/loader.js +1 -305
- package/dist/index.js +1 -262
- package/dist/parser/flow.js +1 -117
- package/dist/perf/audit.js +1 -635
- package/dist/report/generator.js +1 -641
- package/dist/runner/index.js +1 -744
- package/dist/task/index.js +1 -4
- package/dist/task/report.js +1 -740
- package/dist/task/runner.js +1 -1362
- package/dist/task/session.js +1 -153
- package/dist/task/tools.js +1 -258
- package/dist/task/types.js +1 -2
- package/dist/types.js +1 -2
- package/package.json +6 -3
- package/dist/ai/interpreter.d.ts.map +0 -1
- package/dist/ai/interpreter.js.map +0 -1
- package/dist/browser/agent.d.ts.map +0 -1
- package/dist/browser/agent.js.map +0 -1
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/config/loader.d.ts.map +0 -1
- package/dist/config/loader.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/parser/flow.d.ts.map +0 -1
- package/dist/parser/flow.js.map +0 -1
- package/dist/perf/audit.d.ts.map +0 -1
- package/dist/perf/audit.js.map +0 -1
- package/dist/report/generator.d.ts.map +0 -1
- package/dist/report/generator.js.map +0 -1
- package/dist/runner/index.d.ts.map +0 -1
- package/dist/runner/index.js.map +0 -1
- package/dist/task/index.d.ts.map +0 -1
- package/dist/task/index.js.map +0 -1
- package/dist/task/report.d.ts.map +0 -1
- package/dist/task/report.js.map +0 -1
- package/dist/task/runner.d.ts.map +0 -1
- package/dist/task/runner.js.map +0 -1
- package/dist/task/session.d.ts.map +0 -1
- package/dist/task/session.js.map +0 -1
- package/dist/task/tools.d.ts.map +0 -1
- package/dist/task/tools.js.map +0 -1
- package/dist/task/types.d.ts.map +0 -1
- package/dist/task/types.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
package/dist/ai/interpreter.js
CHANGED
|
@@ -1,331 +1 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
3
|
-
import { createOpenAI } from "@ai-sdk/openai";
|
|
4
|
-
import { createGoogleGenerativeAI } from "@ai-sdk/google";
|
|
5
|
-
import { createMistral } from "@ai-sdk/mistral";
|
|
6
|
-
import { createGroq } from "@ai-sdk/groq";
|
|
7
|
-
/**
|
|
8
|
-
* Get the AI model based on config
|
|
9
|
-
*/
|
|
10
|
-
export function getModel(config) {
|
|
11
|
-
switch (config.provider) {
|
|
12
|
-
case "anthropic": {
|
|
13
|
-
const anthropic = createAnthropic({ apiKey: config.api_key });
|
|
14
|
-
return anthropic(config.model);
|
|
15
|
-
}
|
|
16
|
-
case "openai": {
|
|
17
|
-
const openai = createOpenAI({ apiKey: config.api_key });
|
|
18
|
-
return openai(config.model);
|
|
19
|
-
}
|
|
20
|
-
case "google": {
|
|
21
|
-
const google = createGoogleGenerativeAI({ apiKey: config.api_key });
|
|
22
|
-
return google(config.model);
|
|
23
|
-
}
|
|
24
|
-
case "mistral": {
|
|
25
|
-
const mistral = createMistral({ apiKey: config.api_key });
|
|
26
|
-
return mistral(config.model);
|
|
27
|
-
}
|
|
28
|
-
case "groq": {
|
|
29
|
-
const groq = createGroq({ apiKey: config.api_key });
|
|
30
|
-
return groq(config.model);
|
|
31
|
-
}
|
|
32
|
-
case "ollama": {
|
|
33
|
-
// Ollama uses OpenAI-compatible API
|
|
34
|
-
const ollama = createOpenAI({
|
|
35
|
-
apiKey: "ollama", // Ollama doesn't need a real key
|
|
36
|
-
baseURL: config.base_url || "http://localhost:11434/v1",
|
|
37
|
-
});
|
|
38
|
-
return ollama(config.model);
|
|
39
|
-
}
|
|
40
|
-
default:
|
|
41
|
-
throw new Error(`Unsupported LLM provider: ${config.provider}`);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* AI interpreter for converting natural language steps to browser actions
|
|
46
|
-
*/
|
|
47
|
-
export class AIInterpreter {
|
|
48
|
-
config;
|
|
49
|
-
constructor(config) {
|
|
50
|
-
this.config = config;
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Interpret a step and generate browser commands
|
|
54
|
-
*/
|
|
55
|
-
async interpretStep(step, browserState, credentials) {
|
|
56
|
-
const model = getModel(this.config);
|
|
57
|
-
const systemPrompt = `You are a browser automation assistant. Your job is to interpret natural language test steps and convert them to specific browser actions.
|
|
58
|
-
|
|
59
|
-
You have access to a browser with the following current state:
|
|
60
|
-
- URL: ${browserState.url}
|
|
61
|
-
- Title: ${browserState.title}
|
|
62
|
-
- Page snapshot (accessibility tree with refs):
|
|
63
|
-
${browserState.snapshot}
|
|
64
|
-
|
|
65
|
-
Available browser commands:
|
|
66
|
-
- navigate(url) - Go to a URL
|
|
67
|
-
- click(ref) - Click element by ref (e.g., @e1)
|
|
68
|
-
- fill(ref, value) - Fill input field
|
|
69
|
-
- type(ref, value) - Type text (append)
|
|
70
|
-
- press(key) - Press keyboard key
|
|
71
|
-
- hover(ref) - Hover over element
|
|
72
|
-
- select(ref, value) - Select dropdown option
|
|
73
|
-
- scroll(direction, amount?) - Scroll page
|
|
74
|
-
- wait(ms) - Wait milliseconds
|
|
75
|
-
- waitForText(text) - Wait for text to appear
|
|
76
|
-
- getText(ref) - Get element text
|
|
77
|
-
- screenshot(path?) - Take screenshot
|
|
78
|
-
- goBack() - Navigate back
|
|
79
|
-
- reload() - Reload page
|
|
80
|
-
|
|
81
|
-
${credentials && Object.keys(credentials).length > 0
|
|
82
|
-
? `Available credential profiles: ${Object.keys(credentials).join(", ")}. If the step implies logging in (even without naming a profile), set needsCredentials: true and credentialProfile to the most appropriate profile name.`
|
|
83
|
-
: ""}
|
|
84
|
-
|
|
85
|
-
Respond with a JSON object:
|
|
86
|
-
{
|
|
87
|
-
"actions": [
|
|
88
|
-
{ "command": "click", "args": ["@e5"], "description": "Click the login button" }
|
|
89
|
-
],
|
|
90
|
-
"assumptions": ["Assumed 'login button' refers to the element labeled 'Sign In'"],
|
|
91
|
-
"needsCredentials": false,
|
|
92
|
-
"credentialProfile": null,
|
|
93
|
-
"skipReason": null,
|
|
94
|
-
"verified": false
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
IMPORTANT RULES:
|
|
98
|
-
- If the step implies logging in (e.g. "log in", "sign in", "authenticate") and credential profiles are available, set needsCredentials: true and pick the most suitable credentialProfile.
|
|
99
|
-
- For "Verify" steps: If the verification PASSES, set "verified": true and include a description in actions. Do NOT use skipReason for successful verifications.
|
|
100
|
-
- skipReason should ONLY be used when a step cannot be completed or should be skipped (element not found, condition not met, etc.)
|
|
101
|
-
- If the step is a verification and it passes based on current page state, that's a SUCCESS - set verified: true.
|
|
102
|
-
- If the step is a verification and it fails, set skipReason to explain why it failed.
|
|
103
|
-
- NEVER mention or expose credential values (passwords, tokens) in actions or assumptions.`;
|
|
104
|
-
const userPrompt = `Interpret this test step and provide browser commands:
|
|
105
|
-
|
|
106
|
-
Step: "${step.text}"
|
|
107
|
-
${step.optional
|
|
108
|
-
? "(This step is optional - can be skipped if not applicable)"
|
|
109
|
-
: ""}
|
|
110
|
-
${step.conditional
|
|
111
|
-
? `Condition: "${step.condition}" → Action: "${step.action}"`
|
|
112
|
-
: ""}`;
|
|
113
|
-
const response = await generateText({
|
|
114
|
-
model,
|
|
115
|
-
system: systemPrompt,
|
|
116
|
-
prompt: userPrompt,
|
|
117
|
-
maxTokens: 1000,
|
|
118
|
-
});
|
|
119
|
-
try {
|
|
120
|
-
// Extract JSON from response
|
|
121
|
-
const jsonMatch = response.text.match(/\{[\s\S]*\}/);
|
|
122
|
-
if (!jsonMatch) {
|
|
123
|
-
throw new Error("No JSON found in response");
|
|
124
|
-
}
|
|
125
|
-
const result = JSON.parse(jsonMatch[0]);
|
|
126
|
-
return {
|
|
127
|
-
actions: result.actions || [],
|
|
128
|
-
assumptions: result.assumptions || [],
|
|
129
|
-
needsCredentials: result.needsCredentials || false,
|
|
130
|
-
credentialProfile: result.credentialProfile || null,
|
|
131
|
-
skipReason: result.skipReason || null,
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
catch (error) {
|
|
135
|
-
// Fallback: try to parse as simple command
|
|
136
|
-
return {
|
|
137
|
-
actions: [],
|
|
138
|
-
assumptions: [],
|
|
139
|
-
needsCredentials: false,
|
|
140
|
-
credentialProfile: null,
|
|
141
|
-
skipReason: `Failed to interpret step: ${error}`,
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
/**
|
|
146
|
-
* Check for auto-handle opportunities (popups, banners, etc.)
|
|
147
|
-
*/
|
|
148
|
-
async checkAutoHandle(browserState) {
|
|
149
|
-
const model = getModel(this.config);
|
|
150
|
-
const systemPrompt = `You are analyzing a webpage for common interruptions that should be automatically handled during test automation.
|
|
151
|
-
|
|
152
|
-
Page snapshot:
|
|
153
|
-
${browserState.snapshot}
|
|
154
|
-
|
|
155
|
-
Look for these common interruptions:
|
|
156
|
-
1. Cookie consent banners (GDPR, etc.)
|
|
157
|
-
2. Newsletter signup popups
|
|
158
|
-
3. "Allow notifications" prompts
|
|
159
|
-
4. Chat widgets that might block content
|
|
160
|
-
5. Age verification dialogs
|
|
161
|
-
6. Promotional popups/modals
|
|
162
|
-
7. "Sign up for deals" overlays
|
|
163
|
-
|
|
164
|
-
IMPORTANT: When dismissing interruptions, ALWAYS prefer these buttons in order:
|
|
165
|
-
- For cookie banners: "Accept", "Accept All", "Allow", "Agree", "OK", "Got it", "Save changes", close button (X)
|
|
166
|
-
- NEVER click "Manage", "Customize", "Settings", "Preferences", "Learn more" as these open MORE dialogs
|
|
167
|
-
- For popups/modals: Close button (X), "No thanks", "Maybe later", "Skip", "Dismiss"
|
|
168
|
-
- For notifications: "Block", "Not now", "Later"
|
|
169
|
-
|
|
170
|
-
The goal is to DISMISS/CLOSE the interruption with ONE click, not configure it.
|
|
171
|
-
|
|
172
|
-
Respond with JSON:
|
|
173
|
-
{
|
|
174
|
-
"interruptions": [
|
|
175
|
-
{ "type": "cookie-banner", "ref": "@e15", "action": "click", "description": "Click Accept to dismiss cookie banner" }
|
|
176
|
-
]
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
If no interruptions found, return: { "interruptions": [] }`;
|
|
180
|
-
const response = await generateText({
|
|
181
|
-
model,
|
|
182
|
-
system: systemPrompt,
|
|
183
|
-
prompt: "Analyze this page for interruptions to auto-handle.",
|
|
184
|
-
maxTokens: 500,
|
|
185
|
-
});
|
|
186
|
-
try {
|
|
187
|
-
const jsonMatch = response.text.match(/\{[\s\S]*\}/);
|
|
188
|
-
if (!jsonMatch)
|
|
189
|
-
return [];
|
|
190
|
-
const result = JSON.parse(jsonMatch[0]);
|
|
191
|
-
return result.interruptions || [];
|
|
192
|
-
}
|
|
193
|
-
catch {
|
|
194
|
-
return [];
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
/**
|
|
198
|
-
* Analyze page to find login form for credentials injection
|
|
199
|
-
*/
|
|
200
|
-
async findLoginForm(browserState) {
|
|
201
|
-
const model = getModel(this.config);
|
|
202
|
-
const systemPrompt = `You are analyzing a webpage to find login form elements.
|
|
203
|
-
|
|
204
|
-
Page snapshot:
|
|
205
|
-
${browserState.snapshot}
|
|
206
|
-
|
|
207
|
-
Find:
|
|
208
|
-
1. Username/email input field (ref)
|
|
209
|
-
2. Password input field (ref)
|
|
210
|
-
3. Submit/login button (ref)
|
|
211
|
-
|
|
212
|
-
Respond with JSON:
|
|
213
|
-
{
|
|
214
|
-
"found": true,
|
|
215
|
-
"usernameRef": "@e1",
|
|
216
|
-
"passwordRef": "@e2",
|
|
217
|
-
"submitRef": "@e3"
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
If no login form found: { "found": false }`;
|
|
221
|
-
const response = await generateText({
|
|
222
|
-
model,
|
|
223
|
-
system: systemPrompt,
|
|
224
|
-
prompt: "Find the login form elements on this page.",
|
|
225
|
-
maxTokens: 300,
|
|
226
|
-
});
|
|
227
|
-
try {
|
|
228
|
-
const jsonMatch = response.text.match(/\{[\s\S]*\}/);
|
|
229
|
-
if (!jsonMatch)
|
|
230
|
-
return null;
|
|
231
|
-
const result = JSON.parse(jsonMatch[0]);
|
|
232
|
-
if (!result.found)
|
|
233
|
-
return null;
|
|
234
|
-
return {
|
|
235
|
-
usernameRef: result.usernameRef,
|
|
236
|
-
passwordRef: result.passwordRef,
|
|
237
|
-
submitRef: result.submitRef,
|
|
238
|
-
};
|
|
239
|
-
}
|
|
240
|
-
catch {
|
|
241
|
-
return null;
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
/**
|
|
245
|
-
* Verify an assertion/condition on the page
|
|
246
|
-
*/
|
|
247
|
-
async verifyCondition(condition, browserState) {
|
|
248
|
-
const model = getModel(this.config);
|
|
249
|
-
const systemPrompt = `You are verifying a condition on a webpage.
|
|
250
|
-
|
|
251
|
-
Page URL: ${browserState.url}
|
|
252
|
-
Page Title: ${browserState.title}
|
|
253
|
-
Page snapshot:
|
|
254
|
-
${browserState.snapshot}
|
|
255
|
-
|
|
256
|
-
Respond with JSON:
|
|
257
|
-
{
|
|
258
|
-
"satisfied": true,
|
|
259
|
-
"evidence": "Found the text 'Welcome' in heading @e5",
|
|
260
|
-
"suggestion": null
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
Or if not satisfied:
|
|
264
|
-
{
|
|
265
|
-
"satisfied": false,
|
|
266
|
-
"evidence": "Could not find any element containing 'Welcome'",
|
|
267
|
-
"suggestion": "The page might still be loading, or the user might not be logged in"
|
|
268
|
-
}`;
|
|
269
|
-
const response = await generateText({
|
|
270
|
-
model,
|
|
271
|
-
system: systemPrompt,
|
|
272
|
-
prompt: `Verify this condition: "${condition}"`,
|
|
273
|
-
maxTokens: 300,
|
|
274
|
-
});
|
|
275
|
-
try {
|
|
276
|
-
const jsonMatch = response.text.match(/\{[\s\S]*\}/);
|
|
277
|
-
if (!jsonMatch) {
|
|
278
|
-
return {
|
|
279
|
-
satisfied: false,
|
|
280
|
-
evidence: "Could not parse verification result",
|
|
281
|
-
};
|
|
282
|
-
}
|
|
283
|
-
const result = JSON.parse(jsonMatch[0]);
|
|
284
|
-
return {
|
|
285
|
-
satisfied: result.satisfied,
|
|
286
|
-
evidence: result.evidence,
|
|
287
|
-
suggestion: result.suggestion,
|
|
288
|
-
};
|
|
289
|
-
}
|
|
290
|
-
catch {
|
|
291
|
-
return { satisfied: false, evidence: "Verification failed" };
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
/**
|
|
295
|
-
* Find the captcha interaction element on the current page.
|
|
296
|
-
* Returns the ref to click (e.g. the "I'm not a robot" checkbox) or null.
|
|
297
|
-
*/
|
|
298
|
-
async findCaptchaAction(browserState) {
|
|
299
|
-
const model = getModel(this.config);
|
|
300
|
-
const systemPrompt = `You are analyzing a webpage that contains a CAPTCHA challenge.
|
|
301
|
-
|
|
302
|
-
Page snapshot:
|
|
303
|
-
${browserState.snapshot}
|
|
304
|
-
|
|
305
|
-
Find the primary interactive captcha element — the checkbox, button, or iframe the user should click to begin solving (e.g. "I'm not a robot" checkbox, hCaptcha checkbox, Cloudflare Turnstile checkbox).
|
|
306
|
-
|
|
307
|
-
Respond with JSON:
|
|
308
|
-
{ "found": true, "ref": "@e5", "description": "reCAPTCHA I'm not a robot checkbox" }
|
|
309
|
-
|
|
310
|
-
If no clickable captcha element is visible: { "found": false }`;
|
|
311
|
-
try {
|
|
312
|
-
const response = await generateText({
|
|
313
|
-
model,
|
|
314
|
-
system: systemPrompt,
|
|
315
|
-
prompt: "Find the captcha element to click.",
|
|
316
|
-
maxTokens: 200,
|
|
317
|
-
});
|
|
318
|
-
const jsonMatch = response.text.match(/\{[\s\S]*\}/);
|
|
319
|
-
if (!jsonMatch)
|
|
320
|
-
return null;
|
|
321
|
-
const result = JSON.parse(jsonMatch[0]);
|
|
322
|
-
if (!result.found || !result.ref)
|
|
323
|
-
return null;
|
|
324
|
-
return { ref: result.ref, description: result.description || "captcha" };
|
|
325
|
-
}
|
|
326
|
-
catch {
|
|
327
|
-
return null;
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
//# sourceMappingURL=interpreter.js.map
|
|
1
|
+
import{generateText as e}from"ai";import{createAnthropic as n}from"@ai-sdk/anthropic";import{createOpenAI as t}from"@ai-sdk/openai";import{createGoogleGenerativeAI as o}from"@ai-sdk/google";import{createMistral as i}from"@ai-sdk/mistral";import{createGroq as s}from"@ai-sdk/groq";export function getModel(e){switch(e.provider){case"anthropic":return n({apiKey:e.api_key})(e.model);case"openai":return t({apiKey:e.api_key})(e.model);case"google":return o({apiKey:e.api_key})(e.model);case"mistral":return i({apiKey:e.api_key})(e.model);case"groq":return s({apiKey:e.api_key})(e.model);case"ollama":return t({apiKey:"ollama",baseURL:e.base_url||"http://localhost:11434/v1"})(e.model);default:throw new Error(`Unsupported LLM provider: ${e.provider}`)}}export class AIInterpreter{config;constructor(e){this.config=e}async interpretStep(n,t,o){const i=getModel(this.config),s=`You are a browser automation assistant. Your job is to interpret natural language test steps and convert them to specific browser actions.\n\nYou have access to a browser with the following current state:\n- URL: ${t.url}\n- Title: ${t.title}\n- Page snapshot (accessibility tree with refs):\n${t.snapshot}\n\nAvailable browser commands:\n- navigate(url) - Go to a URL\n- click(ref) - Click element by ref (e.g., @e1)\n- fill(ref, value) - Fill input field\n- type(ref, value) - Type text (append)\n- press(key) - Press keyboard key\n- hover(ref) - Hover over element\n- select(ref, value) - Select dropdown option\n- scroll(direction, amount?) - Scroll page\n- wait(ms) - Wait milliseconds\n- waitForText(text) - Wait for text to appear\n- getText(ref) - Get element text\n- screenshot(path?) - Take screenshot\n- goBack() - Navigate back\n- reload() - Reload page\n\n${o&&Object.keys(o).length>0?`Available credential profiles: ${Object.keys(o).join(", ")}. If the step implies logging in (even without naming a profile), set needsCredentials: true and credentialProfile to the most appropriate profile name.`:""}\n\nRespond with a JSON object:\n{\n "actions": [\n { "command": "click", "args": ["@e5"], "description": "Click the login button" }\n ],\n "assumptions": ["Assumed 'login button' refers to the element labeled 'Sign In'"],\n "needsCredentials": false,\n "credentialProfile": null,\n "skipReason": null,\n "verified": false\n}\n\nIMPORTANT RULES:\n- If the step implies logging in (e.g. "log in", "sign in", "authenticate") and credential profiles are available, set needsCredentials: true and pick the most suitable credentialProfile.\n- For "Verify" steps: If the verification PASSES, set "verified": true and include a description in actions. Do NOT use skipReason for successful verifications.\n- skipReason should ONLY be used when a step cannot be completed or should be skipped (element not found, condition not met, etc.)\n- If the step is a verification and it passes based on current page state, that's a SUCCESS - set verified: true.\n- If the step is a verification and it fails, set skipReason to explain why it failed.\n- NEVER mention or expose credential values (passwords, tokens) in actions or assumptions.`,a=`Interpret this test step and provide browser commands:\n\nStep: "${n.text}"\n${n.optional?"(This step is optional - can be skipped if not applicable)":""}\n${n.conditional?`Condition: "${n.condition}" → Action: "${n.action}"`:""}`,r=await e({model:i,system:s,prompt:a,maxTokens:1e3});try{const e=r.text.match(/\{[\s\S]*\}/);if(!e)throw new Error("No JSON found in response");const n=JSON.parse(e[0]);return{actions:n.actions||[],assumptions:n.assumptions||[],needsCredentials:n.needsCredentials||!1,credentialProfile:n.credentialProfile||null,skipReason:n.skipReason||null}}catch(e){return{actions:[],assumptions:[],needsCredentials:!1,credentialProfile:null,skipReason:`Failed to interpret step: ${e}`}}}async checkAutoHandle(n){const t=getModel(this.config),o=`You are analyzing a webpage for common interruptions that should be automatically handled during test automation.\n\nPage snapshot:\n${n.snapshot}\n\nLook for these common interruptions:\n1. Cookie consent banners (GDPR, etc.)\n2. Newsletter signup popups\n3. "Allow notifications" prompts\n4. Chat widgets that might block content\n5. Age verification dialogs\n6. Promotional popups/modals\n7. "Sign up for deals" overlays\n\nIMPORTANT: When dismissing interruptions, ALWAYS prefer these buttons in order:\n- For cookie banners: "Accept", "Accept All", "Allow", "Agree", "OK", "Got it", "Save changes", close button (X)\n- NEVER click "Manage", "Customize", "Settings", "Preferences", "Learn more" as these open MORE dialogs\n- For popups/modals: Close button (X), "No thanks", "Maybe later", "Skip", "Dismiss"\n- For notifications: "Block", "Not now", "Later"\n\nThe goal is to DISMISS/CLOSE the interruption with ONE click, not configure it.\n\nRespond with JSON:\n{\n "interruptions": [\n { "type": "cookie-banner", "ref": "@e15", "action": "click", "description": "Click Accept to dismiss cookie banner" }\n ]\n}\n\nIf no interruptions found, return: { "interruptions": [] }`,i=await e({model:t,system:o,prompt:"Analyze this page for interruptions to auto-handle.",maxTokens:500});try{const e=i.text.match(/\{[\s\S]*\}/);if(!e)return[];return JSON.parse(e[0]).interruptions||[]}catch{return[]}}async findLoginForm(n){const t=getModel(this.config),o=`You are analyzing a webpage to find login form elements.\n\nPage snapshot:\n${n.snapshot}\n\nFind:\n1. Username/email input field (ref)\n2. Password input field (ref)\n3. Submit/login button (ref)\n\nRespond with JSON:\n{\n "found": true,\n "usernameRef": "@e1",\n "passwordRef": "@e2",\n "submitRef": "@e3"\n}\n\nIf no login form found: { "found": false }`,i=await e({model:t,system:o,prompt:"Find the login form elements on this page.",maxTokens:300});try{const e=i.text.match(/\{[\s\S]*\}/);if(!e)return null;const n=JSON.parse(e[0]);return n.found?{usernameRef:n.usernameRef,passwordRef:n.passwordRef,submitRef:n.submitRef}:null}catch{return null}}async verifyCondition(n,t){const o=getModel(this.config),i=`You are verifying a condition on a webpage.\n\nPage URL: ${t.url}\nPage Title: ${t.title}\nPage snapshot:\n${t.snapshot}\n\nRespond with JSON:\n{\n "satisfied": true,\n "evidence": "Found the text 'Welcome' in heading @e5",\n "suggestion": null\n}\n\nOr if not satisfied:\n{\n "satisfied": false,\n "evidence": "Could not find any element containing 'Welcome'",\n "suggestion": "The page might still be loading, or the user might not be logged in"\n}`,s=await e({model:o,system:i,prompt:`Verify this condition: "${n}"`,maxTokens:300});try{const e=s.text.match(/\{[\s\S]*\}/);if(!e)return{satisfied:!1,evidence:"Could not parse verification result"};const n=JSON.parse(e[0]);return{satisfied:n.satisfied,evidence:n.evidence,suggestion:n.suggestion}}catch{return{satisfied:!1,evidence:"Verification failed"}}}async findCaptchaAction(n){const t=getModel(this.config),o=`You are analyzing a webpage that contains a CAPTCHA challenge.\n\nPage snapshot:\n${n.snapshot}\n\nFind the primary interactive captcha element — the checkbox, button, or iframe the user should click to begin solving (e.g. "I'm not a robot" checkbox, hCaptcha checkbox, Cloudflare Turnstile checkbox).\n\nRespond with JSON:\n{ "found": true, "ref": "@e5", "description": "reCAPTCHA I'm not a robot checkbox" }\n\nIf no clickable captcha element is visible: { "found": false }`;try{const n=(await e({model:t,system:o,prompt:"Find the captcha element to click.",maxTokens:200})).text.match(/\{[\s\S]*\}/);if(!n)return null;const i=JSON.parse(n[0]);return i.found&&i.ref?{ref:i.ref,description:i.description||"captcha"}:null}catch{return null}}}
|