momentic 0.0.4 → 0.0.6
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/bin/cli.js +3205 -0
- package/dist/index.js +2365 -2884
- package/dist/index.mjs +2455 -0
- package/package.json +25 -4
package/bin/cli.js
ADDED
|
@@ -0,0 +1,3205 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __defProps = Object.defineProperties;
|
|
6
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
7
|
+
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
8
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
9
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
10
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
11
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
12
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
13
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
14
|
+
var __spreadValues = (a, b) => {
|
|
15
|
+
for (var prop in b || (b = {}))
|
|
16
|
+
if (__hasOwnProp.call(b, prop))
|
|
17
|
+
__defNormalProp(a, prop, b[prop]);
|
|
18
|
+
if (__getOwnPropSymbols)
|
|
19
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
20
|
+
if (__propIsEnum.call(b, prop))
|
|
21
|
+
__defNormalProp(a, prop, b[prop]);
|
|
22
|
+
}
|
|
23
|
+
return a;
|
|
24
|
+
};
|
|
25
|
+
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
26
|
+
var __objRest = (source, exclude) => {
|
|
27
|
+
var target = {};
|
|
28
|
+
for (var prop in source)
|
|
29
|
+
if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
|
|
30
|
+
target[prop] = source[prop];
|
|
31
|
+
if (source != null && __getOwnPropSymbols)
|
|
32
|
+
for (var prop of __getOwnPropSymbols(source)) {
|
|
33
|
+
if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
|
|
34
|
+
target[prop] = source[prop];
|
|
35
|
+
}
|
|
36
|
+
return target;
|
|
37
|
+
};
|
|
38
|
+
var __copyProps = (to, from, except, desc) => {
|
|
39
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
40
|
+
for (let key of __getOwnPropNames(from))
|
|
41
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
42
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
43
|
+
}
|
|
44
|
+
return to;
|
|
45
|
+
};
|
|
46
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
47
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
48
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
49
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
50
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
51
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
52
|
+
mod
|
|
53
|
+
));
|
|
54
|
+
var __async = (__this, __arguments, generator) => {
|
|
55
|
+
return new Promise((resolve, reject) => {
|
|
56
|
+
var fulfilled = (value) => {
|
|
57
|
+
try {
|
|
58
|
+
step(generator.next(value));
|
|
59
|
+
} catch (e) {
|
|
60
|
+
reject(e);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
var rejected = (value) => {
|
|
64
|
+
try {
|
|
65
|
+
step(generator.throw(value));
|
|
66
|
+
} catch (e) {
|
|
67
|
+
reject(e);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
|
|
71
|
+
step((generator = generator.apply(__this, __arguments)).next());
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// src/cli.ts
|
|
76
|
+
var import_chalk = __toESM(require("chalk"));
|
|
77
|
+
var import_commander = require("commander");
|
|
78
|
+
var import_execa = require("execa");
|
|
79
|
+
var import_wait_on = __toESM(require("wait-on"));
|
|
80
|
+
|
|
81
|
+
// ../../packages/types/src/commands.ts
|
|
82
|
+
var import_dedent = __toESM(require("dedent"), 1);
|
|
83
|
+
var z2 = __toESM(require("zod"), 1);
|
|
84
|
+
|
|
85
|
+
// ../../packages/types/src/a11y-targets.ts
|
|
86
|
+
var z = __toESM(require("zod"), 1);
|
|
87
|
+
var A11yTargetWithCacheSchema = z.object({
|
|
88
|
+
// a11y ID
|
|
89
|
+
id: z.number().int(),
|
|
90
|
+
// additional metadata stored after the action is executed
|
|
91
|
+
// to assist in re-execution
|
|
92
|
+
role: z.string().optional(),
|
|
93
|
+
name: z.string().optional(),
|
|
94
|
+
content: z.string().optional(),
|
|
95
|
+
pathFromRoot: z.string().optional(),
|
|
96
|
+
serializedForm: z.string().optional()
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// ../../packages/types/src/commands.ts
|
|
100
|
+
var CommandType = /* @__PURE__ */ ((CommandType2) => {
|
|
101
|
+
CommandType2["AI_ASSERTION"] = "AI_ASSERTION";
|
|
102
|
+
CommandType2["CLICK"] = "CLICK";
|
|
103
|
+
CommandType2["SELECT_OPTION"] = "SELECT_OPTION";
|
|
104
|
+
CommandType2["TYPE"] = "TYPE";
|
|
105
|
+
CommandType2["PRESS"] = "PRESS";
|
|
106
|
+
CommandType2["NAVIGATE"] = "NAVIGATE";
|
|
107
|
+
CommandType2["SCROLL_UP"] = "SCROLL_UP";
|
|
108
|
+
CommandType2["SCROLL_DOWN"] = "SCROLL_DOWN";
|
|
109
|
+
CommandType2["GO_BACK"] = "GO_BACK";
|
|
110
|
+
CommandType2["GO_FORWARD"] = "GO_FORWARD";
|
|
111
|
+
CommandType2["WAIT"] = "WAIT";
|
|
112
|
+
CommandType2["REFRESH"] = "REFRESH";
|
|
113
|
+
CommandType2["TAB"] = "TAB";
|
|
114
|
+
CommandType2["COOKIE"] = "COOKIE";
|
|
115
|
+
CommandType2["SUCCESS"] = "SUCCESS";
|
|
116
|
+
return CommandType2;
|
|
117
|
+
})(CommandType || {});
|
|
118
|
+
var ElementDescriptorSchema = z2.object({
|
|
119
|
+
// natural language passed to LLM
|
|
120
|
+
elementDescriptor: z2.string(),
|
|
121
|
+
// Cached A11y target - when a user creates a preset action, this will not exist
|
|
122
|
+
a11yData: A11yTargetWithCacheSchema.optional()
|
|
123
|
+
});
|
|
124
|
+
var CommonCommandSchema = z2.object({
|
|
125
|
+
// If the command is suggested by AI, why it did so
|
|
126
|
+
thoughts: z2.string().optional()
|
|
127
|
+
});
|
|
128
|
+
var NavigateCommandSchema = CommonCommandSchema.merge(
|
|
129
|
+
z2.object({
|
|
130
|
+
type: z2.literal("NAVIGATE" /* NAVIGATE */),
|
|
131
|
+
url: z2.string()
|
|
132
|
+
})
|
|
133
|
+
).describe("NAVIGATE <url> - Go to the specified url");
|
|
134
|
+
var ScrollUpCommandSchema = CommonCommandSchema.merge(
|
|
135
|
+
z2.object({
|
|
136
|
+
type: z2.literal("SCROLL_UP" /* SCROLL_UP */)
|
|
137
|
+
})
|
|
138
|
+
).describe("SCROLL_UP - Scroll up one page");
|
|
139
|
+
var ScrollDownCommandSchema = CommonCommandSchema.merge(
|
|
140
|
+
z2.object({
|
|
141
|
+
type: z2.literal("SCROLL_DOWN" /* SCROLL_DOWN */)
|
|
142
|
+
})
|
|
143
|
+
).describe("SCROLL_DOWN - Scroll down one page");
|
|
144
|
+
var WaitCommandSchema = CommonCommandSchema.merge(
|
|
145
|
+
z2.object({
|
|
146
|
+
type: z2.literal("WAIT" /* WAIT */),
|
|
147
|
+
delay: z2.number()
|
|
148
|
+
// seconds
|
|
149
|
+
})
|
|
150
|
+
);
|
|
151
|
+
var RefreshCommandSchema = CommonCommandSchema.merge(
|
|
152
|
+
z2.object({
|
|
153
|
+
type: z2.literal("REFRESH" /* REFRESH */)
|
|
154
|
+
})
|
|
155
|
+
);
|
|
156
|
+
var GoBackCommandSchema = CommonCommandSchema.merge(
|
|
157
|
+
z2.object({
|
|
158
|
+
type: z2.literal("GO_BACK" /* GO_BACK */)
|
|
159
|
+
})
|
|
160
|
+
);
|
|
161
|
+
var GoForwardCommandSchema = CommonCommandSchema.merge(
|
|
162
|
+
z2.object({
|
|
163
|
+
type: z2.literal("GO_FORWARD" /* GO_FORWARD */)
|
|
164
|
+
})
|
|
165
|
+
);
|
|
166
|
+
var ClickCommandSchema = CommonCommandSchema.merge(
|
|
167
|
+
z2.object({
|
|
168
|
+
type: z2.literal("CLICK" /* CLICK */),
|
|
169
|
+
target: ElementDescriptorSchema,
|
|
170
|
+
doubleClick: z2.boolean().default(false),
|
|
171
|
+
rightClick: z2.boolean().default(false)
|
|
172
|
+
})
|
|
173
|
+
).describe(
|
|
174
|
+
import_dedent.default`CLICK <id> - click on the element that has the specified id.
|
|
175
|
+
You are NOT allowed to click on disabled, hidden or StaticText elements.
|
|
176
|
+
Only click on elements on the Current Page.
|
|
177
|
+
Only click on elements with the following tag names: button, input, link, image, generic.
|
|
178
|
+
`.replace("\n", " ")
|
|
179
|
+
);
|
|
180
|
+
var SelectOptionCommandSchema = CommonCommandSchema.merge(
|
|
181
|
+
z2.object({
|
|
182
|
+
type: z2.literal("SELECT_OPTION" /* SELECT_OPTION */),
|
|
183
|
+
target: ElementDescriptorSchema,
|
|
184
|
+
option: z2.string()
|
|
185
|
+
})
|
|
186
|
+
).describe(
|
|
187
|
+
// TODO: if we move to a non-mutative way of selecting elements (e.g. by selector), we should update this description
|
|
188
|
+
`SELECT_OPTION <id> "<option>" - select the specified item from the select with the specified id. The item should exist on the page. Use the name of the item instead of the id. Make sure to include quotes around the option.`
|
|
189
|
+
);
|
|
190
|
+
var AIAssertionCommandSchema = CommonCommandSchema.merge(
|
|
191
|
+
z2.object({
|
|
192
|
+
type: z2.literal("AI_ASSERTION" /* AI_ASSERTION */),
|
|
193
|
+
assertion: z2.string(),
|
|
194
|
+
useVision: z2.boolean().default(false),
|
|
195
|
+
disableCache: z2.boolean().default(false)
|
|
196
|
+
})
|
|
197
|
+
);
|
|
198
|
+
var TypeOptionsSchema = z2.object({
|
|
199
|
+
clearContent: z2.boolean().default(true),
|
|
200
|
+
pressKeysSequentially: z2.boolean().default(false)
|
|
201
|
+
});
|
|
202
|
+
var TypeCommandSchema = CommonCommandSchema.merge(
|
|
203
|
+
z2.object({
|
|
204
|
+
type: z2.literal("TYPE" /* TYPE */),
|
|
205
|
+
target: ElementDescriptorSchema,
|
|
206
|
+
value: z2.string(),
|
|
207
|
+
pressEnter: z2.boolean().default(false)
|
|
208
|
+
})
|
|
209
|
+
).merge(TypeOptionsSchema).describe(
|
|
210
|
+
`TYPE <id> "<text>" - type the specified text into the input with the specified id. The text should be specified by the user - do not use text from the EXAMPLES or generate text yourself. Make sure to include quotes around the text.`
|
|
211
|
+
);
|
|
212
|
+
var PressCommandSchema = CommonCommandSchema.merge(
|
|
213
|
+
z2.object({
|
|
214
|
+
type: z2.literal("PRESS" /* PRESS */),
|
|
215
|
+
value: z2.string()
|
|
216
|
+
})
|
|
217
|
+
).describe(
|
|
218
|
+
`PRESS <key> - press the specified key, such as "ArrowLeft", "Enter", or "a". You must specify at least one key.`
|
|
219
|
+
);
|
|
220
|
+
var TabCommandSchema = CommonCommandSchema.merge(
|
|
221
|
+
z2.object({
|
|
222
|
+
type: z2.literal("TAB" /* TAB */),
|
|
223
|
+
url: z2.string()
|
|
224
|
+
})
|
|
225
|
+
);
|
|
226
|
+
var CookieCommandSchema = CommonCommandSchema.merge(
|
|
227
|
+
z2.object({
|
|
228
|
+
type: z2.literal("COOKIE" /* COOKIE */),
|
|
229
|
+
value: z2.string()
|
|
230
|
+
})
|
|
231
|
+
);
|
|
232
|
+
var SuccessCommandSchema = CommonCommandSchema.merge(
|
|
233
|
+
z2.object({
|
|
234
|
+
type: z2.literal("SUCCESS" /* SUCCESS */),
|
|
235
|
+
condition: AIAssertionCommandSchema.optional()
|
|
236
|
+
})
|
|
237
|
+
).describe("SUCCESS - the user goal has been successfully achieved");
|
|
238
|
+
var UserEditableAICommandSchema = z2.discriminatedUnion("type", [
|
|
239
|
+
ClickCommandSchema,
|
|
240
|
+
TypeCommandSchema,
|
|
241
|
+
PressCommandSchema,
|
|
242
|
+
SelectOptionCommandSchema,
|
|
243
|
+
NavigateCommandSchema,
|
|
244
|
+
ScrollDownCommandSchema,
|
|
245
|
+
ScrollUpCommandSchema,
|
|
246
|
+
SuccessCommandSchema
|
|
247
|
+
]);
|
|
248
|
+
var UserEditablePresetCommandSchema = z2.discriminatedUnion("type", [
|
|
249
|
+
GoBackCommandSchema,
|
|
250
|
+
GoForwardCommandSchema,
|
|
251
|
+
RefreshCommandSchema,
|
|
252
|
+
AIAssertionCommandSchema,
|
|
253
|
+
WaitCommandSchema,
|
|
254
|
+
TabCommandSchema,
|
|
255
|
+
CookieCommandSchema
|
|
256
|
+
]);
|
|
257
|
+
var CommandSchema = z2.discriminatedUnion("type", [
|
|
258
|
+
// Commands that can be either specified manually or auto-created by AI in an AI step
|
|
259
|
+
...UserEditableAICommandSchema.options,
|
|
260
|
+
// Commands that can only be specified manually ("preset commands")
|
|
261
|
+
...UserEditablePresetCommandSchema.options
|
|
262
|
+
]);
|
|
263
|
+
var FailureCommandSchema = CommonCommandSchema.merge(
|
|
264
|
+
z2.object({
|
|
265
|
+
type: z2.literal("FAILURE")
|
|
266
|
+
})
|
|
267
|
+
).describe(
|
|
268
|
+
"FAILURE - there are no commands to suggest that could make progress that have not already been tried before"
|
|
269
|
+
);
|
|
270
|
+
var AICommandSchema = z2.discriminatedUnion("type", [
|
|
271
|
+
...UserEditableAICommandSchema.options,
|
|
272
|
+
FailureCommandSchema
|
|
273
|
+
]);
|
|
274
|
+
|
|
275
|
+
// ../../packages/types/src/steps.ts
|
|
276
|
+
var z3 = __toESM(require("zod"), 1);
|
|
277
|
+
var StepType = /* @__PURE__ */ ((StepType2) => {
|
|
278
|
+
StepType2["AI_ACTION"] = "AI_ACTION";
|
|
279
|
+
StepType2["PRESET_ACTION"] = "PRESET_ACTION";
|
|
280
|
+
StepType2["MODULE"] = "MODULE";
|
|
281
|
+
return StepType2;
|
|
282
|
+
})(StepType || {});
|
|
283
|
+
var AIActionSchema = z3.object({
|
|
284
|
+
type: z3.literal("AI_ACTION" /* AI_ACTION */),
|
|
285
|
+
text: z3.string(),
|
|
286
|
+
// Cached commands for this step
|
|
287
|
+
commands: z3.array(UserEditableAICommandSchema).optional()
|
|
288
|
+
});
|
|
289
|
+
var PresetActionSchema = z3.object({
|
|
290
|
+
type: z3.literal("PRESET_ACTION" /* PRESET_ACTION */),
|
|
291
|
+
command: CommandSchema
|
|
292
|
+
});
|
|
293
|
+
var ModuleStepSchema = z3.object({
|
|
294
|
+
type: z3.literal("MODULE" /* MODULE */),
|
|
295
|
+
moduleId: z3.string().uuid()
|
|
296
|
+
});
|
|
297
|
+
var AllowedModuleStepSchema = z3.union([
|
|
298
|
+
AIActionSchema,
|
|
299
|
+
PresetActionSchema
|
|
300
|
+
]);
|
|
301
|
+
var ResolvedModuleStepSchema = z3.object({
|
|
302
|
+
type: z3.literal("RESOLVED_MODULE"),
|
|
303
|
+
moduleId: z3.string().uuid(),
|
|
304
|
+
name: z3.string(),
|
|
305
|
+
steps: AllowedModuleStepSchema.array()
|
|
306
|
+
});
|
|
307
|
+
var StepSchema = z3.union([
|
|
308
|
+
AIActionSchema,
|
|
309
|
+
PresetActionSchema,
|
|
310
|
+
ModuleStepSchema
|
|
311
|
+
]);
|
|
312
|
+
var ResolvedStepSchema = z3.union([
|
|
313
|
+
AIActionSchema,
|
|
314
|
+
PresetActionSchema,
|
|
315
|
+
ResolvedModuleStepSchema
|
|
316
|
+
]);
|
|
317
|
+
|
|
318
|
+
// ../../packages/web-agent/src/browsers/chrome.ts
|
|
319
|
+
var import_playwright = require("playwright");
|
|
320
|
+
|
|
321
|
+
// ../../packages/types/src/assertions.ts
|
|
322
|
+
var import_zod = require("zod");
|
|
323
|
+
var AIAssertionResultSchema = import_zod.z.object({
|
|
324
|
+
thoughts: import_zod.z.string(),
|
|
325
|
+
result: import_zod.z.boolean(),
|
|
326
|
+
relevantElements: import_zod.z.array(import_zod.z.number()).optional()
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
// ../../packages/types/src/ai-command-generation.ts
|
|
330
|
+
var import_zod2 = require("zod");
|
|
331
|
+
|
|
332
|
+
// ../../packages/types/src/errors.ts
|
|
333
|
+
var BrowserExecutionError = class extends Error {
|
|
334
|
+
constructor(message, options = {}) {
|
|
335
|
+
super(message, options);
|
|
336
|
+
this.name = "BrowserExecutionError";
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
var EmptyA11yTreeError = class extends Error {
|
|
340
|
+
constructor(options = {}) {
|
|
341
|
+
super("Got empty a11y tree", options);
|
|
342
|
+
this.name = "EmptyA11yTreeError";
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
// ../../packages/types/src/ai-command-generation.ts
|
|
347
|
+
var LLMOutputSchema = import_zod2.z.object({
|
|
348
|
+
command: import_zod2.z.string(),
|
|
349
|
+
thoughts: import_zod2.z.string()
|
|
350
|
+
});
|
|
351
|
+
var NumericStringSchema = import_zod2.z.string().pipe(import_zod2.z.coerce.number());
|
|
352
|
+
|
|
353
|
+
// ../../packages/types/src/command-results.ts
|
|
354
|
+
var z6 = __toESM(require("zod"), 1);
|
|
355
|
+
var ResultStatus = /* @__PURE__ */ ((ResultStatus2) => {
|
|
356
|
+
ResultStatus2["SUCCESS"] = "SUCCESS";
|
|
357
|
+
ResultStatus2["FAILED"] = "FAILED";
|
|
358
|
+
ResultStatus2["RUNNING"] = "RUNNING";
|
|
359
|
+
ResultStatus2["IDLE"] = "IDLE";
|
|
360
|
+
ResultStatus2["CANCELLED"] = "CANCELLED";
|
|
361
|
+
return ResultStatus2;
|
|
362
|
+
})(ResultStatus || {});
|
|
363
|
+
var CommandStatus = /* @__PURE__ */ ((CommandStatus2) => {
|
|
364
|
+
CommandStatus2["SUCCESS"] = "SUCCESS";
|
|
365
|
+
CommandStatus2["FAILED"] = "FAILED";
|
|
366
|
+
return CommandStatus2;
|
|
367
|
+
})(CommandStatus || {});
|
|
368
|
+
var CommandMetadataSchema = z6.object({
|
|
369
|
+
beforeUrl: z6.string(),
|
|
370
|
+
// FIXME: this should be a discriminated union of string | Buffer
|
|
371
|
+
// but to avoid too much schema wranging we leave this for now
|
|
372
|
+
// https://github.com/colinhacks/zod/issues/153
|
|
373
|
+
beforeScreenshot: z6.string().or(z6.instanceof(Buffer)),
|
|
374
|
+
afterUrl: z6.string().optional(),
|
|
375
|
+
afterScreenshot: z6.string().or(z6.instanceof(Buffer)).optional(),
|
|
376
|
+
startedAt: z6.coerce.date(),
|
|
377
|
+
finishedAt: z6.coerce.date(),
|
|
378
|
+
viewport: z6.object({
|
|
379
|
+
height: z6.number(),
|
|
380
|
+
width: z6.number()
|
|
381
|
+
}),
|
|
382
|
+
status: z6.nativeEnum(CommandStatus),
|
|
383
|
+
// used for error message and thoughts
|
|
384
|
+
message: z6.string().optional(),
|
|
385
|
+
elementInteracted: z6.string().optional()
|
|
386
|
+
});
|
|
387
|
+
var StepResultMetadataSchema = z6.object({
|
|
388
|
+
startedAt: z6.coerce.date(),
|
|
389
|
+
finishedAt: z6.coerce.date(),
|
|
390
|
+
status: z6.nativeEnum(ResultStatus),
|
|
391
|
+
// used for error message and thoughts
|
|
392
|
+
message: z6.string().optional(),
|
|
393
|
+
// browser info
|
|
394
|
+
userAgent: z6.string().optional()
|
|
395
|
+
});
|
|
396
|
+
var PresetActionResultSchema = PresetActionSchema.merge(
|
|
397
|
+
StepResultMetadataSchema
|
|
398
|
+
).merge(
|
|
399
|
+
z6.object({
|
|
400
|
+
// Array just for consistency with other result types, should only ever be one for preset.
|
|
401
|
+
results: CommandMetadataSchema.array()
|
|
402
|
+
})
|
|
403
|
+
);
|
|
404
|
+
var AIActionResultSchema = AIActionSchema.merge(
|
|
405
|
+
StepResultMetadataSchema
|
|
406
|
+
).merge(
|
|
407
|
+
z6.object({
|
|
408
|
+
results: PresetActionResultSchema.array()
|
|
409
|
+
})
|
|
410
|
+
);
|
|
411
|
+
var ModuleResultSchema = ModuleStepSchema.merge(
|
|
412
|
+
StepResultMetadataSchema
|
|
413
|
+
).merge(
|
|
414
|
+
z6.object({
|
|
415
|
+
// nested results
|
|
416
|
+
results: z6.union([AIActionResultSchema, PresetActionResultSchema]).array()
|
|
417
|
+
})
|
|
418
|
+
);
|
|
419
|
+
var ResultSchema = z6.discriminatedUnion("type", [
|
|
420
|
+
AIActionResultSchema,
|
|
421
|
+
PresetActionResultSchema,
|
|
422
|
+
ModuleResultSchema
|
|
423
|
+
]);
|
|
424
|
+
|
|
425
|
+
// ../../packages/types/src/cookies.ts
|
|
426
|
+
var import_set_cookie_parser = require("set-cookie-parser");
|
|
427
|
+
function parseCookieString(cookie) {
|
|
428
|
+
const parsedCookie = (0, import_set_cookie_parser.parseString)(cookie);
|
|
429
|
+
if (!parsedCookie.name) {
|
|
430
|
+
throw new Error("Name missing from cookie");
|
|
431
|
+
}
|
|
432
|
+
if (!parsedCookie.value) {
|
|
433
|
+
throw new Error("Value missing from cookie");
|
|
434
|
+
}
|
|
435
|
+
let sameSite;
|
|
436
|
+
if (parsedCookie.sameSite) {
|
|
437
|
+
const sameSiteSetting = parsedCookie.sameSite.trim().toLowerCase();
|
|
438
|
+
if (sameSiteSetting === "strict") {
|
|
439
|
+
sameSite = "Strict";
|
|
440
|
+
} else if (sameSiteSetting === "lax") {
|
|
441
|
+
sameSite = "Lax";
|
|
442
|
+
} else if (sameSiteSetting === "none") {
|
|
443
|
+
sameSite = "None";
|
|
444
|
+
} else {
|
|
445
|
+
throw new Error(`Invalid sameSite setting in cookie: ${sameSiteSetting}`);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
if (!parsedCookie.path && parsedCookie.domain) {
|
|
449
|
+
parsedCookie.path = "/";
|
|
450
|
+
}
|
|
451
|
+
const result = __spreadProps(__spreadValues({}, parsedCookie), {
|
|
452
|
+
expires: parsedCookie.expires ? parsedCookie.expires.getTime() / 1e3 : void 0,
|
|
453
|
+
sameSite
|
|
454
|
+
});
|
|
455
|
+
return result;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// ../../packages/types/src/execute-results.ts
|
|
459
|
+
var z7 = __toESM(require("zod"), 1);
|
|
460
|
+
var ExecuteCommandHistoryEntrySchema = z7.object({
|
|
461
|
+
// type of command executed
|
|
462
|
+
type: z7.nativeEnum(StepType),
|
|
463
|
+
// if AI step type, what command was executed
|
|
464
|
+
generatedStep: UserEditableAICommandSchema.optional(),
|
|
465
|
+
// human readable descriptor for action taken, including element interacted with
|
|
466
|
+
serializedCommand: z7.string().optional(),
|
|
467
|
+
// human readable descriptor for element interacted with
|
|
468
|
+
elementInteracted: z7.string().optional()
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
// ../../packages/types/src/goal-splitter.ts
|
|
472
|
+
var import_zod3 = require("zod");
|
|
473
|
+
var InstructionsSchema = import_zod3.z.string().array();
|
|
474
|
+
|
|
475
|
+
// ../../packages/types/src/locator.ts
|
|
476
|
+
var z9 = __toESM(require("zod"), 1);
|
|
477
|
+
var AILocatorSchema = z9.object({
|
|
478
|
+
thoughts: z9.string(),
|
|
479
|
+
// a11y id
|
|
480
|
+
id: z9.number().int(),
|
|
481
|
+
// dropdowns should have options
|
|
482
|
+
options: z9.array(z9.string()).optional()
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
// ../../packages/types/src/modules.ts
|
|
486
|
+
var import_zod4 = require("zod");
|
|
487
|
+
var ModuleMetadataSchema = import_zod4.z.object({
|
|
488
|
+
id: import_zod4.z.string(),
|
|
489
|
+
createdAt: import_zod4.z.coerce.date(),
|
|
490
|
+
createdBy: import_zod4.z.string(),
|
|
491
|
+
organizationId: import_zod4.z.string().or(import_zod4.z.null()),
|
|
492
|
+
name: import_zod4.z.string(),
|
|
493
|
+
schemaVersion: import_zod4.z.string(),
|
|
494
|
+
// this is only used in the client and is not stored in the db
|
|
495
|
+
numSteps: import_zod4.z.number()
|
|
496
|
+
});
|
|
497
|
+
var ModuleSchema = import_zod4.z.object({
|
|
498
|
+
steps: AllowedModuleStepSchema.array()
|
|
499
|
+
}).merge(ModuleMetadataSchema.omit({ numSteps: true }));
|
|
500
|
+
|
|
501
|
+
// ../../packages/types/src/runs.ts
|
|
502
|
+
var import_zod5 = require("zod");
|
|
503
|
+
var RunTrigger = {
|
|
504
|
+
WEBHOOK: "WEBHOOK",
|
|
505
|
+
CRON: "CRON",
|
|
506
|
+
MANUAL: "MANUAL"
|
|
507
|
+
};
|
|
508
|
+
var RunStatusEnum = {
|
|
509
|
+
PENDING: "PENDING",
|
|
510
|
+
RUNNING: "RUNNING",
|
|
511
|
+
PASSED: "PASSED",
|
|
512
|
+
FAILED: "FAILED",
|
|
513
|
+
CANCELLED: "CANCELLED"
|
|
514
|
+
};
|
|
515
|
+
var DateOrStringSchema = import_zod5.z.string().pipe(import_zod5.z.coerce.date()).or(import_zod5.z.date());
|
|
516
|
+
var RunMetadataSchema = import_zod5.z.object({
|
|
517
|
+
id: import_zod5.z.string(),
|
|
518
|
+
createdAt: DateOrStringSchema,
|
|
519
|
+
createdBy: import_zod5.z.string(),
|
|
520
|
+
organizationId: import_zod5.z.string().or(import_zod5.z.null()),
|
|
521
|
+
scheduledAt: DateOrStringSchema.or(import_zod5.z.null()),
|
|
522
|
+
startedAt: DateOrStringSchema.or(import_zod5.z.null()),
|
|
523
|
+
finishedAt: DateOrStringSchema.or(import_zod5.z.null()),
|
|
524
|
+
testId: import_zod5.z.string().or(import_zod5.z.null()),
|
|
525
|
+
status: import_zod5.z.nativeEnum(RunStatusEnum),
|
|
526
|
+
trigger: import_zod5.z.nativeEnum(RunTrigger),
|
|
527
|
+
test: import_zod5.z.object({
|
|
528
|
+
name: import_zod5.z.string(),
|
|
529
|
+
id: import_zod5.z.string()
|
|
530
|
+
}).or(import_zod5.z.null())
|
|
531
|
+
});
|
|
532
|
+
var RunWithTestSchema = RunMetadataSchema.merge(
|
|
533
|
+
import_zod5.z.object({
|
|
534
|
+
results: ResultSchema.array(),
|
|
535
|
+
test: import_zod5.z.object({
|
|
536
|
+
name: import_zod5.z.string(),
|
|
537
|
+
id: import_zod5.z.string(),
|
|
538
|
+
baseUrl: import_zod5.z.string()
|
|
539
|
+
}).or(import_zod5.z.null())
|
|
540
|
+
})
|
|
541
|
+
);
|
|
542
|
+
|
|
543
|
+
// ../../packages/types/src/serialization.ts
|
|
544
|
+
function clampText(text, length) {
|
|
545
|
+
if (text.length < length) {
|
|
546
|
+
return text;
|
|
547
|
+
}
|
|
548
|
+
return text.slice(0, length - 3) + "[...]";
|
|
549
|
+
}
|
|
550
|
+
function serializeCommand(command) {
|
|
551
|
+
var _a, _b;
|
|
552
|
+
switch (command.type) {
|
|
553
|
+
case "SUCCESS" /* SUCCESS */:
|
|
554
|
+
if ((_a = command.condition) == null ? void 0 : _a.assertion) {
|
|
555
|
+
return `Check success condition: ${command.condition.assertion}`;
|
|
556
|
+
}
|
|
557
|
+
return `All commands completed`;
|
|
558
|
+
case "NAVIGATE" /* NAVIGATE */:
|
|
559
|
+
return `Go to URL: ${clampText(command.url, 30)}`;
|
|
560
|
+
case "GO_BACK" /* GO_BACK */:
|
|
561
|
+
return `Go back to the previous page`;
|
|
562
|
+
case "GO_FORWARD" /* GO_FORWARD */:
|
|
563
|
+
return `Go forward to the next page`;
|
|
564
|
+
case "SCROLL_DOWN" /* SCROLL_DOWN */:
|
|
565
|
+
return `Scroll down one page`;
|
|
566
|
+
case "SCROLL_UP" /* SCROLL_UP */:
|
|
567
|
+
return `Scroll up one page`;
|
|
568
|
+
case "WAIT" /* WAIT */:
|
|
569
|
+
return `Wait for ${command.delay} seconds`;
|
|
570
|
+
case "REFRESH" /* REFRESH */:
|
|
571
|
+
return `Refresh the page`;
|
|
572
|
+
case "CLICK" /* CLICK */:
|
|
573
|
+
return `Click on '${command.target.elementDescriptor}'`;
|
|
574
|
+
case "TYPE" /* TYPE */:
|
|
575
|
+
let serializedTarget = "";
|
|
576
|
+
if ((_b = command.target.a11yData) == null ? void 0 : _b.serializedForm) {
|
|
577
|
+
serializedTarget = ` in element ${command.target.a11yData.serializedForm}`;
|
|
578
|
+
} else if (command.target.elementDescriptor.length > 0) {
|
|
579
|
+
serializedTarget = ` in element ${command.target.elementDescriptor}`;
|
|
580
|
+
}
|
|
581
|
+
return `Type${serializedTarget}: '${command.value}'`;
|
|
582
|
+
case "PRESS" /* PRESS */:
|
|
583
|
+
return `Press '${command.value}'`;
|
|
584
|
+
case "SELECT_OPTION" /* SELECT_OPTION */:
|
|
585
|
+
return `Select option '${command.option}' in '${command.target.elementDescriptor}'`;
|
|
586
|
+
case "TAB" /* TAB */:
|
|
587
|
+
return `Switch to tab: ${command.url}`;
|
|
588
|
+
case "COOKIE" /* COOKIE */:
|
|
589
|
+
return `Set cookie: ${command.value}`;
|
|
590
|
+
case "AI_ASSERTION" /* AI_ASSERTION */:
|
|
591
|
+
return `${command.useVision ? "Visual assertion" : "Assertion"}: '${command.assertion}'`;
|
|
592
|
+
default:
|
|
593
|
+
const assertUnreachable = (_x) => {
|
|
594
|
+
throw "If Typescript complains about the line below, you missed a case or break in the switch above";
|
|
595
|
+
};
|
|
596
|
+
return assertUnreachable(command);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// ../../packages/types/src/card-display.ts
|
|
601
|
+
var SELECTABLE_PRESET_COMMAND_OPTIONS_SET = new Set(
|
|
602
|
+
Object.values(CommandType)
|
|
603
|
+
);
|
|
604
|
+
var CARD_DISPLAY_NAMES = {
|
|
605
|
+
["AI_ACTION" /* AI_ACTION */]: "AI action",
|
|
606
|
+
["MODULE" /* MODULE */]: "Module",
|
|
607
|
+
["AI_ASSERTION" /* AI_ASSERTION */]: "AI check",
|
|
608
|
+
["CLICK" /* CLICK */]: "Click",
|
|
609
|
+
["SELECT_OPTION" /* SELECT_OPTION */]: "Select",
|
|
610
|
+
["TYPE" /* TYPE */]: "Type",
|
|
611
|
+
["PRESS" /* PRESS */]: "Press",
|
|
612
|
+
["NAVIGATE" /* NAVIGATE */]: "Navigate",
|
|
613
|
+
["SCROLL_UP" /* SCROLL_UP */]: "Scroll up",
|
|
614
|
+
["SCROLL_DOWN" /* SCROLL_DOWN */]: "Scroll down",
|
|
615
|
+
["GO_BACK" /* GO_BACK */]: "Go back",
|
|
616
|
+
["GO_FORWARD" /* GO_FORWARD */]: "Go forward",
|
|
617
|
+
["WAIT" /* WAIT */]: "Wait",
|
|
618
|
+
["REFRESH" /* REFRESH */]: "Refresh",
|
|
619
|
+
["TAB" /* TAB */]: "Switch tab",
|
|
620
|
+
["COOKIE" /* COOKIE */]: "Set cookie",
|
|
621
|
+
["SUCCESS" /* SUCCESS */]: "Done"
|
|
622
|
+
};
|
|
623
|
+
var CARD_DESCRIPTIONS = {
|
|
624
|
+
["AI_ACTION" /* AI_ACTION */]: "Ask AI to plan and execute something on the page.",
|
|
625
|
+
["MODULE" /* MODULE */]: "A list of steps that can be reused in multiple tests.",
|
|
626
|
+
["AI_ASSERTION" /* AI_ASSERTION */]: "Ask AI whether something is true on the page.",
|
|
627
|
+
["CLICK" /* CLICK */]: "Click on an element on the page based on a description.",
|
|
628
|
+
["SELECT_OPTION" /* SELECT_OPTION */]: "Select an option from a dropdown based on a description.",
|
|
629
|
+
["TYPE" /* TYPE */]: "Type the specified text into an element.",
|
|
630
|
+
["PRESS" /* PRESS */]: "Press the specified keys using the keyboard. (e.g. Ctrl+A)",
|
|
631
|
+
["NAVIGATE" /* NAVIGATE */]: "Navigate to the specified URL.",
|
|
632
|
+
["SCROLL_UP" /* SCROLL_UP */]: "Scroll up one page.",
|
|
633
|
+
["SCROLL_DOWN" /* SCROLL_DOWN */]: "Scroll down one page.",
|
|
634
|
+
["GO_BACK" /* GO_BACK */]: "Go back in browser history.",
|
|
635
|
+
["GO_FORWARD" /* GO_FORWARD */]: "Go forward in browser history.",
|
|
636
|
+
["WAIT" /* WAIT */]: "Wait for the specified number of seconds.",
|
|
637
|
+
["REFRESH" /* REFRESH */]: "Refresh the page. This will not clear cookies or session data.",
|
|
638
|
+
["TAB" /* TAB */]: "Switch to different tab in the browser.",
|
|
639
|
+
["COOKIE" /* COOKIE */]: "Set a cookie that will persist throughout the browser session",
|
|
640
|
+
["SUCCESS" /* SUCCESS */]: "Indicate the entire AI action has succeeded, optionally based on a condition."
|
|
641
|
+
};
|
|
642
|
+
|
|
643
|
+
// ../../packages/types/src/test.ts
|
|
644
|
+
var import_zod7 = require("zod");
|
|
645
|
+
|
|
646
|
+
// ../../packages/types/src/test-settings.ts
|
|
647
|
+
var import_cron_validator = require("cron-validator");
|
|
648
|
+
var import_zod6 = require("zod");
|
|
649
|
+
var TestAdvancedSettingsSchema = import_zod6.z.object({
|
|
650
|
+
availableAsModule: import_zod6.z.boolean().default(false),
|
|
651
|
+
disableAICaching: import_zod6.z.boolean().default(false)
|
|
652
|
+
});
|
|
653
|
+
var ScheduleSettingsSchema = import_zod6.z.object({
|
|
654
|
+
cron: import_zod6.z.string().refine(
|
|
655
|
+
(v) => {
|
|
656
|
+
return (0, import_cron_validator.isValidCron)(v);
|
|
657
|
+
},
|
|
658
|
+
{ message: "Invalid cron expression." }
|
|
659
|
+
).default("0 0 */1 * *"),
|
|
660
|
+
enabled: import_zod6.z.boolean().default(false),
|
|
661
|
+
timeZone: import_zod6.z.string().default("America/Los_Angeles"),
|
|
662
|
+
// this is used for removing repeatable jobs (not set by user)
|
|
663
|
+
jobKey: import_zod6.z.string().optional()
|
|
664
|
+
});
|
|
665
|
+
var WebhookSchema = import_zod6.z.object({
|
|
666
|
+
lastStatus: import_zod6.z.number().optional(),
|
|
667
|
+
url: import_zod6.z.string().url()
|
|
668
|
+
});
|
|
669
|
+
var WebhookSettingsSchema = import_zod6.z.array(WebhookSchema).default([]);
|
|
670
|
+
var TestSettingsSchema = import_zod6.z.object({
|
|
671
|
+
name: import_zod6.z.string().min(1),
|
|
672
|
+
baseUrl: import_zod6.z.string().url(),
|
|
673
|
+
advanced: TestAdvancedSettingsSchema
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
// ../../packages/types/src/test.ts
|
|
677
|
+
var ResolvedTestSchema = import_zod7.z.object({
|
|
678
|
+
id: import_zod7.z.string(),
|
|
679
|
+
name: import_zod7.z.string(),
|
|
680
|
+
baseUrl: import_zod7.z.string(),
|
|
681
|
+
steps: import_zod7.z.array(ResolvedStepSchema),
|
|
682
|
+
createdAt: import_zod7.z.coerce.date(),
|
|
683
|
+
updatedAt: import_zod7.z.coerce.date(),
|
|
684
|
+
createdBy: import_zod7.z.string(),
|
|
685
|
+
organizationId: import_zod7.z.string().or(import_zod7.z.null()),
|
|
686
|
+
schemaVersion: import_zod7.z.string(),
|
|
687
|
+
advanced: TestAdvancedSettingsSchema,
|
|
688
|
+
schedule: ScheduleSettingsSchema,
|
|
689
|
+
webhooks: WebhookSettingsSchema
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
// ../../packages/types/src/context.ts
|
|
693
|
+
var z14 = __toESM(require("zod"), 1);
|
|
694
|
+
var DynamicContextSchema = z14.object({
|
|
695
|
+
// user goal or instruction
|
|
696
|
+
goal: z14.string(),
|
|
697
|
+
// current url of the browser
|
|
698
|
+
url: z14.string(),
|
|
699
|
+
// serialized page state
|
|
700
|
+
browserState: z14.string(),
|
|
701
|
+
// serialized history of previous commands
|
|
702
|
+
history: z14.string(),
|
|
703
|
+
// number of previously executed commands
|
|
704
|
+
numPrevious: z14.number(),
|
|
705
|
+
// last executed command, if any
|
|
706
|
+
lastCommand: ExecuteCommandHistoryEntrySchema.or(z14.null())
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
// ../../packages/types/src/public-api.ts
|
|
710
|
+
var z15 = __toESM(require("zod"), 1);
|
|
711
|
+
var GeneratorOptionsSchema = z15.object({
|
|
712
|
+
disableCache: z15.boolean()
|
|
713
|
+
});
|
|
714
|
+
var GetNextCommandBodySchema = DynamicContextSchema.merge(
|
|
715
|
+
GeneratorOptionsSchema
|
|
716
|
+
);
|
|
717
|
+
var GetNextCommandResponseSchema = AICommandSchema;
|
|
718
|
+
var GetAssertionResultBodySchema = z15.discriminatedUnion("vision", [
|
|
719
|
+
DynamicContextSchema.merge(GeneratorOptionsSchema).merge(
|
|
720
|
+
z15.object({
|
|
721
|
+
vision: z15.literal(false)
|
|
722
|
+
})
|
|
723
|
+
),
|
|
724
|
+
DynamicContextSchema.pick({
|
|
725
|
+
goal: true,
|
|
726
|
+
url: true
|
|
727
|
+
}).merge(GeneratorOptionsSchema).merge(
|
|
728
|
+
z15.object({
|
|
729
|
+
// base64 encoded image
|
|
730
|
+
screenshot: z15.string(),
|
|
731
|
+
vision: z15.literal(true)
|
|
732
|
+
})
|
|
733
|
+
)
|
|
734
|
+
]);
|
|
735
|
+
var GetAssertionResponseSchema = AIAssertionResultSchema;
|
|
736
|
+
var LocateBodySchema = DynamicContextSchema.pick({
|
|
737
|
+
browserState: true,
|
|
738
|
+
goal: true
|
|
739
|
+
}).merge(GeneratorOptionsSchema);
|
|
740
|
+
var LocateResponseSchema = AILocatorSchema;
|
|
741
|
+
var SplitGoalBodySchema = DynamicContextSchema.pick({
|
|
742
|
+
goal: true,
|
|
743
|
+
url: true
|
|
744
|
+
}).merge(GeneratorOptionsSchema);
|
|
745
|
+
var SplitGoalResponseSchema = z15.string().array();
|
|
746
|
+
var QueueBodySchema = z15.object({
|
|
747
|
+
testIds: z15.string().array()
|
|
748
|
+
});
|
|
749
|
+
var GetTestResponseSchema = ResolvedTestSchema;
|
|
750
|
+
var CreateRunBodySchema = z15.object({
|
|
751
|
+
testId: z15.string()
|
|
752
|
+
});
|
|
753
|
+
var CreateRunResponseSchema = RunWithTestSchema;
|
|
754
|
+
var GetRunResponseSchema = RunWithTestSchema;
|
|
755
|
+
var UpdateRunBodySchema = z15.object({
|
|
756
|
+
finishedAt: z15.coerce.date(),
|
|
757
|
+
results: ResultSchema.array(),
|
|
758
|
+
status: z15.nativeEnum(RunStatusEnum)
|
|
759
|
+
}).partial();
|
|
760
|
+
var CreateScreenshotBodySchema = z15.object({
|
|
761
|
+
// base64 string
|
|
762
|
+
screenshot: z15.string()
|
|
763
|
+
});
|
|
764
|
+
var CreateScreenshotResponseSchema = z15.object({
|
|
765
|
+
key: z15.string()
|
|
766
|
+
});
|
|
767
|
+
|
|
768
|
+
// ../../packages/web-agent/src/utils/url.ts
|
|
769
|
+
var urlChanged = (url1, url2) => {
|
|
770
|
+
const { hostname, pathname } = new URL(url1);
|
|
771
|
+
const { hostname: hostname2, pathname: pathname2 } = new URL(url2);
|
|
772
|
+
return hostname !== hostname2 || pathname !== pathname2;
|
|
773
|
+
};
|
|
774
|
+
|
|
775
|
+
// ../../packages/web-agent/src/browsers/a11y.ts
|
|
776
|
+
var bannedProperties = /* @__PURE__ */ new Set(["focusable"]);
|
|
777
|
+
var alwaysInterestingRoles = /* @__PURE__ */ new Set([
|
|
778
|
+
"textbox",
|
|
779
|
+
"checkbox",
|
|
780
|
+
"button",
|
|
781
|
+
"link"
|
|
782
|
+
]);
|
|
783
|
+
var rolesToOmitID = /* @__PURE__ */ new Set(["paragraph", "menuitem", "option"]);
|
|
784
|
+
var defaultA11yNodeSerializeParams = {
|
|
785
|
+
indentLevel: 0,
|
|
786
|
+
noID: false,
|
|
787
|
+
noChildren: false,
|
|
788
|
+
noProperties: false
|
|
789
|
+
};
|
|
790
|
+
var ProcessedA11yNode = class {
|
|
791
|
+
constructor(params) {
|
|
792
|
+
this.id = params.id;
|
|
793
|
+
this.role = params.role;
|
|
794
|
+
this.name = params.name;
|
|
795
|
+
this.content = params.content;
|
|
796
|
+
this.properties = params.properties;
|
|
797
|
+
this.pathFromRoot = params.pathFromRoot;
|
|
798
|
+
this.children = params.children;
|
|
799
|
+
this.backendNodeID = params.backendNodeID;
|
|
800
|
+
}
|
|
801
|
+
getLogForm() {
|
|
802
|
+
var _a, _b;
|
|
803
|
+
return JSON.stringify({
|
|
804
|
+
id: this.id,
|
|
805
|
+
name: (_a = this.name) != null ? _a : "",
|
|
806
|
+
role: (_b = this.role) != null ? _b : "",
|
|
807
|
+
backendNodeId: this.backendNodeID
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
/**
|
|
811
|
+
* Returns true if the current node contains interesting properties.
|
|
812
|
+
* Does not go through children.
|
|
813
|
+
*/
|
|
814
|
+
isInteresting() {
|
|
815
|
+
if (alwaysInterestingRoles.has(this.role))
|
|
816
|
+
return true;
|
|
817
|
+
if (this.children.some((child) => child.role === "StaticText"))
|
|
818
|
+
return true;
|
|
819
|
+
return !!this.name.trim() || !!this.content;
|
|
820
|
+
}
|
|
821
|
+
serialize(opts = defaultA11yNodeSerializeParams) {
|
|
822
|
+
const { indentLevel, noChildren, noProperties, noID } = Object.assign(
|
|
823
|
+
{},
|
|
824
|
+
defaultA11yNodeSerializeParams,
|
|
825
|
+
opts
|
|
826
|
+
);
|
|
827
|
+
const indent = " ".repeat(indentLevel);
|
|
828
|
+
if (this.role === "StaticText") {
|
|
829
|
+
return `${indent}${this.name}
|
|
830
|
+
`;
|
|
831
|
+
}
|
|
832
|
+
let s = `${indent}<${this.role}`;
|
|
833
|
+
if (!noID && !rolesToOmitID.has(this.role)) {
|
|
834
|
+
s += ` id="${this.id}"`;
|
|
835
|
+
}
|
|
836
|
+
if (this.name) {
|
|
837
|
+
s += ` name="${this.name}"`;
|
|
838
|
+
}
|
|
839
|
+
if (this.content) {
|
|
840
|
+
s += ` content="${this.content}"`;
|
|
841
|
+
}
|
|
842
|
+
if (Object.keys(this.properties).length > 0 && !noProperties) {
|
|
843
|
+
Object.entries(this.properties).forEach(([k, v]) => {
|
|
844
|
+
if (bannedProperties.has(k)) {
|
|
845
|
+
return;
|
|
846
|
+
} else if (typeof v === "string") {
|
|
847
|
+
s += ` ${k}="${v}"`;
|
|
848
|
+
} else if (typeof v === "boolean") {
|
|
849
|
+
if (v) {
|
|
850
|
+
s += ` ${k}`;
|
|
851
|
+
} else {
|
|
852
|
+
s += ` ${k}={false}`;
|
|
853
|
+
}
|
|
854
|
+
} else if (typeof v !== "undefined") {
|
|
855
|
+
s += ` ${k}={${JSON.stringify(v)}}`;
|
|
856
|
+
}
|
|
857
|
+
});
|
|
858
|
+
}
|
|
859
|
+
if (this.children.length === 0 || noChildren) {
|
|
860
|
+
s += " />\n";
|
|
861
|
+
return s;
|
|
862
|
+
} else {
|
|
863
|
+
s += ">\n";
|
|
864
|
+
}
|
|
865
|
+
for (const child of this.children) {
|
|
866
|
+
s += child.serialize({ indentLevel: indentLevel + 2 });
|
|
867
|
+
}
|
|
868
|
+
s += `${indent}</${this.role}>
|
|
869
|
+
`;
|
|
870
|
+
return s;
|
|
871
|
+
}
|
|
872
|
+
};
|
|
873
|
+
var ProcessedA11yTree = class {
|
|
874
|
+
constructor(root, nodeMap) {
|
|
875
|
+
this.root = root;
|
|
876
|
+
this.nodeMap = nodeMap;
|
|
877
|
+
}
|
|
878
|
+
serialize() {
|
|
879
|
+
if (!this.root) {
|
|
880
|
+
return "";
|
|
881
|
+
}
|
|
882
|
+
return this.root.serialize();
|
|
883
|
+
}
|
|
884
|
+
// public diff(other: ProcessedA11yTree): string[] {
|
|
885
|
+
// const results: string[] = [];
|
|
886
|
+
// }
|
|
887
|
+
};
|
|
888
|
+
function getNodePathIdentifier(node) {
|
|
889
|
+
var _a, _b;
|
|
890
|
+
if ((_a = node.name) == null ? void 0 : _a.value) {
|
|
891
|
+
return `"${node.name.value}"`;
|
|
892
|
+
}
|
|
893
|
+
if (((_b = node.role) == null ? void 0 : _b.value) && node.role.value !== "none" && node.role.value !== "generic") {
|
|
894
|
+
return `"${node.role.value}"`;
|
|
895
|
+
}
|
|
896
|
+
return `"${node.nodeId}"`;
|
|
897
|
+
}
|
|
898
|
+
function processA11yTreeDFS(node, parent, inputNodeMap, outputNodeMap) {
|
|
899
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
900
|
+
if (!parent && node.parentId) {
|
|
901
|
+
throw new Error(
|
|
902
|
+
`Got no parent for accessibility node ${node.nodeId}: ${JSON.stringify(
|
|
903
|
+
node
|
|
904
|
+
)}`
|
|
905
|
+
);
|
|
906
|
+
}
|
|
907
|
+
const processedNode = new ProcessedA11yNode({
|
|
908
|
+
id: node.nodeId,
|
|
909
|
+
role: ((_a = node.role) == null ? void 0 : _a.value) || "",
|
|
910
|
+
name: ((_b = node.name) == null ? void 0 : _b.value) || "",
|
|
911
|
+
content: ((_c = node.value) == null ? void 0 : _c.value) || "",
|
|
912
|
+
properties: {},
|
|
913
|
+
children: [],
|
|
914
|
+
pathFromRoot: (parent ? `${parent.pathFromRoot} ` : "") + getNodePathIdentifier(node),
|
|
915
|
+
backendNodeID: node.backendDOMNodeId
|
|
916
|
+
// md5Sum: "",
|
|
917
|
+
});
|
|
918
|
+
if ((_d = node.value) == null ? void 0 : _d.value) {
|
|
919
|
+
processedNode.content = `${(_e = node.value) == null ? void 0 : _e.value}`;
|
|
920
|
+
}
|
|
921
|
+
if (node.properties) {
|
|
922
|
+
node.properties.forEach((prop) => {
|
|
923
|
+
processedNode.properties[prop.name] = prop.value.value;
|
|
924
|
+
});
|
|
925
|
+
}
|
|
926
|
+
outputNodeMap.set(processedNode.id, processedNode);
|
|
927
|
+
const children = (_f = node.childIds) != null ? _f : [];
|
|
928
|
+
for (const childId of children) {
|
|
929
|
+
if (!childId) {
|
|
930
|
+
continue;
|
|
931
|
+
}
|
|
932
|
+
const child = inputNodeMap.get(childId);
|
|
933
|
+
if (!child) {
|
|
934
|
+
continue;
|
|
935
|
+
}
|
|
936
|
+
const processedChildren = processA11yTreeDFS(
|
|
937
|
+
child,
|
|
938
|
+
processedNode,
|
|
939
|
+
inputNodeMap,
|
|
940
|
+
outputNodeMap
|
|
941
|
+
);
|
|
942
|
+
if (!processedChildren.length) {
|
|
943
|
+
continue;
|
|
944
|
+
}
|
|
945
|
+
processedNode.children = processedNode.children.concat(processedChildren);
|
|
946
|
+
}
|
|
947
|
+
if (processedNode.role === "StaticText") {
|
|
948
|
+
processedNode.children = [];
|
|
949
|
+
}
|
|
950
|
+
if (processedNode.children.length === 1 && processedNode.children[0].role === "StaticText") {
|
|
951
|
+
const currentName = processedNode.name;
|
|
952
|
+
const childName = (_g = processedNode.children[0]) == null ? void 0 : _g.name;
|
|
953
|
+
if (currentName === childName || !childName) {
|
|
954
|
+
processedNode.children = [];
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
const staticTextGroupedChildren = [];
|
|
958
|
+
for (let i = processedNode.children.length - 1; i >= 0; i--) {
|
|
959
|
+
const node2 = processedNode.children[i];
|
|
960
|
+
if (node2.role !== "StaticText") {
|
|
961
|
+
staticTextGroupedChildren.push(node2);
|
|
962
|
+
continue;
|
|
963
|
+
}
|
|
964
|
+
if (i === 0 || processedNode.children[i - 1].role !== "StaticText") {
|
|
965
|
+
staticTextGroupedChildren.push(node2);
|
|
966
|
+
continue;
|
|
967
|
+
}
|
|
968
|
+
processedNode.children[i - 1].name += ` ${node2.name}`;
|
|
969
|
+
}
|
|
970
|
+
processedNode.children = staticTextGroupedChildren.reverse();
|
|
971
|
+
for (const child of processedNode.children) {
|
|
972
|
+
child.parent = processedNode;
|
|
973
|
+
}
|
|
974
|
+
const interesting = processedNode.isInteresting();
|
|
975
|
+
if (!interesting) {
|
|
976
|
+
if (processedNode.children.length === 0) {
|
|
977
|
+
return [];
|
|
978
|
+
} else if (processedNode.children.length === 1) {
|
|
979
|
+
return [processedNode.children[0]];
|
|
980
|
+
} else if (node.parentId) {
|
|
981
|
+
return processedNode.children;
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
return [processedNode];
|
|
985
|
+
}
|
|
986
|
+
function processA11yTree(graph) {
|
|
987
|
+
if (!graph.root) {
|
|
988
|
+
throw new Error("a11y tree has null root");
|
|
989
|
+
}
|
|
990
|
+
graph.allNodes = graph.allNodes.filter((node) => {
|
|
991
|
+
var _a;
|
|
992
|
+
if (!node.ignored) {
|
|
993
|
+
return true;
|
|
994
|
+
}
|
|
995
|
+
return !((_a = node.ignoredReasons) == null ? void 0 : _a.find(
|
|
996
|
+
(reason) => {
|
|
997
|
+
var _a2;
|
|
998
|
+
return reason.name === "notRendered" && ((_a2 = reason.value) == null ? void 0 : _a2.value);
|
|
999
|
+
}
|
|
1000
|
+
));
|
|
1001
|
+
});
|
|
1002
|
+
const nodeMap = /* @__PURE__ */ new Map();
|
|
1003
|
+
for (const node of graph.allNodes) {
|
|
1004
|
+
nodeMap.set(node.nodeId, node);
|
|
1005
|
+
}
|
|
1006
|
+
const outputNodeMap = /* @__PURE__ */ new Map();
|
|
1007
|
+
const processedRoot = processA11yTreeDFS(
|
|
1008
|
+
graph.root,
|
|
1009
|
+
null,
|
|
1010
|
+
nodeMap,
|
|
1011
|
+
outputNodeMap
|
|
1012
|
+
);
|
|
1013
|
+
if (processedRoot.length > 1) {
|
|
1014
|
+
throw new Error(
|
|
1015
|
+
`Something went horribly wrong processing the a11y tree, we got: ${JSON.stringify(
|
|
1016
|
+
processedRoot
|
|
1017
|
+
)}`
|
|
1018
|
+
);
|
|
1019
|
+
} else if (processedRoot.length === 0) {
|
|
1020
|
+
throw new EmptyA11yTreeError();
|
|
1021
|
+
}
|
|
1022
|
+
return new ProcessedA11yTree(processedRoot[0], outputNodeMap);
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
// ../../packages/web-agent/src/browsers/cdp.ts
|
|
1026
|
+
var GREEN = { r: 147, g: 196, b: 125, a: 0.55 };
|
|
1027
|
+
var NODE_HIGHLIGHT_CONFIG = {
|
|
1028
|
+
showInfo: false,
|
|
1029
|
+
showRulers: false,
|
|
1030
|
+
showStyles: false,
|
|
1031
|
+
showAccessibilityInfo: false,
|
|
1032
|
+
showExtensionLines: false,
|
|
1033
|
+
contrastAlgorithm: "aa",
|
|
1034
|
+
contentColor: GREEN,
|
|
1035
|
+
paddingColor: GREEN,
|
|
1036
|
+
borderColor: GREEN,
|
|
1037
|
+
marginColor: GREEN,
|
|
1038
|
+
eventTargetColor: GREEN,
|
|
1039
|
+
shapeColor: GREEN,
|
|
1040
|
+
shapeMarginColor: GREEN
|
|
1041
|
+
};
|
|
1042
|
+
|
|
1043
|
+
// ../../packages/web-agent/src/browsers/constants.ts
|
|
1044
|
+
var RETINA_WINDOW_SCALE_FACTOR = 2;
|
|
1045
|
+
var MAX_LOAD_TIMEOUT_MS = 8e3;
|
|
1046
|
+
var NETWORK_STABLE_DURATION_MS = 1250;
|
|
1047
|
+
var NETWORK_IDLE_TIMEOUT_MS = 3e3;
|
|
1048
|
+
var CHECK_INTERVAL_MS = 250;
|
|
1049
|
+
var A11Y_LOAD_TIMEOUT_MS = 1e3;
|
|
1050
|
+
var A11Y_STABLE_TIMEOUT_MS = NETWORK_IDLE_TIMEOUT_MS;
|
|
1051
|
+
var A11Y_STABLE_DURATION_MS = NETWORK_STABLE_DURATION_MS;
|
|
1052
|
+
var BROWSER_ACTION_TIMEOUT_MS = MAX_LOAD_TIMEOUT_MS;
|
|
1053
|
+
var COMPLICATED_BROWSER_ACTION_TIMEOUT_MS = MAX_LOAD_TIMEOUT_MS;
|
|
1054
|
+
var HIGHLIGHT_DURATION_MS = 3e3;
|
|
1055
|
+
var CHROME_INTERNAL_URLS = /* @__PURE__ */ new Set([
|
|
1056
|
+
"about:blank",
|
|
1057
|
+
"chrome-error://chromewebdata/"
|
|
1058
|
+
]);
|
|
1059
|
+
var MAX_BROWSER_ACTION_ATTEMPTS = 2;
|
|
1060
|
+
|
|
1061
|
+
// ../../packages/web-agent/src/browsers/utils/time.ts
|
|
1062
|
+
var sleep = (ms = 1e3) => {
|
|
1063
|
+
return new Promise((resolve) => setTimeout(() => resolve(), ms));
|
|
1064
|
+
};
|
|
1065
|
+
|
|
1066
|
+
// ../../packages/web-agent/src/browsers/utils/scripts/cursor.ts
|
|
1067
|
+
function addCursorScript() {
|
|
1068
|
+
cursor = document.createElement("img");
|
|
1069
|
+
cursor.setAttribute(
|
|
1070
|
+
"src",
|
|
1071
|
+
"data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjMyIiB2aWV3Qm94PSIwIDAgMzIgMzIiIHdpZHRoPSIzMiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEwIDcpIj48cGF0aCBkPSJtNi4xNDggMTguNDczIDEuODYzLTEuMDAzIDEuNjE1LS44MzktMi41NjgtNC44MTZoNC4zMzJsLTExLjM3OS0xMS40MDh2MTYuMDE1bDMuMzE2LTMuMjIxeiIgZmlsbD0iI2ZmZiIvPjxwYXRoIGQ9Im02LjQzMSAxNyAxLjc2NS0uOTQxLTIuNzc1LTUuMjAyaDMuNjA0bC04LjAyNS04LjA0M3YxMS4xODhsMi41My0yLjQ0MnoiIGZpbGw9IiMwMDAiLz48L2c+PC9zdmc+"
|
|
1072
|
+
);
|
|
1073
|
+
cursor.setAttribute("id", "selenium_cursor");
|
|
1074
|
+
cursor.setAttribute(
|
|
1075
|
+
"style",
|
|
1076
|
+
"position: absolute; z-index: 99999999999; pointer-events: none; left:0; top:0"
|
|
1077
|
+
);
|
|
1078
|
+
cursor.style.filter = "invert(0%) sepia(6%) saturate(24%) hue-rotate(315deg) brightness(89%) contrast(110%)";
|
|
1079
|
+
document.body.appendChild(cursor);
|
|
1080
|
+
document.onmousemove = function(e) {
|
|
1081
|
+
e = e || window.event;
|
|
1082
|
+
document.getElementById("selenium_cursor").style.left = e.pageX + "px";
|
|
1083
|
+
document.getElementById("selenium_cursor").style.top = e.pageY + "px";
|
|
1084
|
+
};
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
// ../../packages/web-agent/src/browsers/utils/scripts/addIDs.ts
|
|
1088
|
+
function addIDsScript() {
|
|
1089
|
+
const allElements = document.getElementsByTagName("*");
|
|
1090
|
+
let currentID = 1;
|
|
1091
|
+
for (let i = 0; i < allElements.length; i++) {
|
|
1092
|
+
const element = allElements[i];
|
|
1093
|
+
element == null ? void 0 : element.setAttribute("data-momentic-id", currentID);
|
|
1094
|
+
currentID++;
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
// ../../packages/web-agent/src/browsers/utils/playwright.ts
|
|
1099
|
+
var sometimesRelevantResourceTypes = /* @__PURE__ */ new Set([
|
|
1100
|
+
"document",
|
|
1101
|
+
"script",
|
|
1102
|
+
"XMLHttpRequest",
|
|
1103
|
+
"fetch",
|
|
1104
|
+
"xhr"
|
|
1105
|
+
]);
|
|
1106
|
+
var alwaysRelevantResourceTypes = /* @__PURE__ */ new Set(["script", "document"]);
|
|
1107
|
+
var bannedDomains = [
|
|
1108
|
+
"intercom.io",
|
|
1109
|
+
"googletagmanager.com",
|
|
1110
|
+
"google-analytics.com",
|
|
1111
|
+
"www.gstatic.com",
|
|
1112
|
+
"apis.google.com",
|
|
1113
|
+
"sentry.io",
|
|
1114
|
+
"newrelic.com",
|
|
1115
|
+
"p.retool.com",
|
|
1116
|
+
"m.stripe.com",
|
|
1117
|
+
"m.stripe.network",
|
|
1118
|
+
"js.stripe.com",
|
|
1119
|
+
"assets.trybento.co",
|
|
1120
|
+
"udon.trybento.co",
|
|
1121
|
+
"cdn.lr-in-prod.com",
|
|
1122
|
+
"r.lr-in-prod.com",
|
|
1123
|
+
"content.product-usage.assembledhq.com",
|
|
1124
|
+
"data.product-usage.assembledhq.com",
|
|
1125
|
+
"static.zdassets.com"
|
|
1126
|
+
];
|
|
1127
|
+
function serializeRequest(request) {
|
|
1128
|
+
return `${request.resourceType()} ${request.method()} ${request.url()}`;
|
|
1129
|
+
}
|
|
1130
|
+
function stripWWWPrefix(url) {
|
|
1131
|
+
url = url.replace(/^www\./, "");
|
|
1132
|
+
return url;
|
|
1133
|
+
}
|
|
1134
|
+
function isRequestRelevantForPageLoad(request, currentURL) {
|
|
1135
|
+
if (!sometimesRelevantResourceTypes.has(request.resourceType())) {
|
|
1136
|
+
return false;
|
|
1137
|
+
}
|
|
1138
|
+
const parsedCurrentURL = new URL(currentURL);
|
|
1139
|
+
const parsedRequestURL = new URL(request.url());
|
|
1140
|
+
if (bannedDomains.some((domain) => parsedRequestURL.hostname.includes(domain))) {
|
|
1141
|
+
return false;
|
|
1142
|
+
}
|
|
1143
|
+
if (alwaysRelevantResourceTypes.has(request.resourceType())) {
|
|
1144
|
+
return true;
|
|
1145
|
+
}
|
|
1146
|
+
if (request.method() !== "GET") {
|
|
1147
|
+
return true;
|
|
1148
|
+
}
|
|
1149
|
+
return stripWWWPrefix(parsedRequestURL.hostname).includes(
|
|
1150
|
+
stripWWWPrefix(parsedCurrentURL.hostname)
|
|
1151
|
+
);
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
// ../../packages/web-agent/src/browsers/chrome.ts
|
|
1155
|
+
function initCDPSession(cdpClient) {
|
|
1156
|
+
return __async(this, null, function* () {
|
|
1157
|
+
yield cdpClient.send("Accessibility.enable");
|
|
1158
|
+
yield cdpClient.send("DOM.enable");
|
|
1159
|
+
yield cdpClient.send("Overlay.enable");
|
|
1160
|
+
});
|
|
1161
|
+
}
|
|
1162
|
+
var _ChromeBrowser = class _ChromeBrowser {
|
|
1163
|
+
constructor({
|
|
1164
|
+
browser,
|
|
1165
|
+
context,
|
|
1166
|
+
page,
|
|
1167
|
+
baseURL,
|
|
1168
|
+
cdpClient,
|
|
1169
|
+
logger
|
|
1170
|
+
}) {
|
|
1171
|
+
// key is nodeId, according to the a11y tree
|
|
1172
|
+
this.nodeMap = /* @__PURE__ */ new Map();
|
|
1173
|
+
this.browser = browser;
|
|
1174
|
+
this.context = context;
|
|
1175
|
+
this.page = page;
|
|
1176
|
+
this.baseURL = baseURL;
|
|
1177
|
+
this.cdpClient = cdpClient;
|
|
1178
|
+
this.logger = logger;
|
|
1179
|
+
}
|
|
1180
|
+
/**
|
|
1181
|
+
* Creates a new browser and waits for navigation to the given test URL.
|
|
1182
|
+
*/
|
|
1183
|
+
static init(_0, _1, _2) {
|
|
1184
|
+
return __async(this, arguments, function* (baseURL, logger, onScreenshot, timeout = MAX_LOAD_TIMEOUT_MS) {
|
|
1185
|
+
const browser = yield import_playwright.chromium.launch({ headless: true });
|
|
1186
|
+
const context = yield browser.newContext({
|
|
1187
|
+
viewport: {
|
|
1188
|
+
width: 1920,
|
|
1189
|
+
height: 1080
|
|
1190
|
+
},
|
|
1191
|
+
// comment out the below if you are on Mac OS but you're using a monitor
|
|
1192
|
+
deviceScaleFactor: process.platform === "darwin" ? RETINA_WINDOW_SCALE_FACTOR : 1,
|
|
1193
|
+
userAgent: import_playwright.devices["Desktop Chrome"].userAgent,
|
|
1194
|
+
geolocation: { latitude: 37.7749, longitude: -122.4194 },
|
|
1195
|
+
// san francisco
|
|
1196
|
+
locale: "en-US",
|
|
1197
|
+
timezoneId: "America/Los_Angeles"
|
|
1198
|
+
});
|
|
1199
|
+
const page = yield context.newPage();
|
|
1200
|
+
const cdpClient = yield context.newCDPSession(page);
|
|
1201
|
+
const chrome = new _ChromeBrowser({
|
|
1202
|
+
browser,
|
|
1203
|
+
context,
|
|
1204
|
+
page,
|
|
1205
|
+
baseURL,
|
|
1206
|
+
cdpClient,
|
|
1207
|
+
logger
|
|
1208
|
+
});
|
|
1209
|
+
let completed = false;
|
|
1210
|
+
const navigateAndInitCDP = () => __async(this, null, function* () {
|
|
1211
|
+
try {
|
|
1212
|
+
yield chrome.navigate(baseURL, false);
|
|
1213
|
+
yield initCDPSession(cdpClient);
|
|
1214
|
+
} catch (err) {
|
|
1215
|
+
logger.error({ err }, "Failed to initialize chrome browser");
|
|
1216
|
+
} finally {
|
|
1217
|
+
completed = true;
|
|
1218
|
+
}
|
|
1219
|
+
});
|
|
1220
|
+
void navigateAndInitCDP();
|
|
1221
|
+
const sendScreenshot = () => __async(this, null, function* () {
|
|
1222
|
+
if (!onScreenshot) {
|
|
1223
|
+
return;
|
|
1224
|
+
}
|
|
1225
|
+
try {
|
|
1226
|
+
onScreenshot({
|
|
1227
|
+
viewport: chrome.viewport,
|
|
1228
|
+
buffer: yield chrome.screenshot()
|
|
1229
|
+
});
|
|
1230
|
+
} catch (err) {
|
|
1231
|
+
logger.error({ err }, "Failed to take screenshot");
|
|
1232
|
+
}
|
|
1233
|
+
});
|
|
1234
|
+
void sendScreenshot();
|
|
1235
|
+
const screenshotInterval = setInterval(() => {
|
|
1236
|
+
void sendScreenshot();
|
|
1237
|
+
}, 250);
|
|
1238
|
+
const startTime = Date.now();
|
|
1239
|
+
while (!completed && Date.now() - startTime < timeout) {
|
|
1240
|
+
yield sleep(CHECK_INTERVAL_MS);
|
|
1241
|
+
}
|
|
1242
|
+
clearInterval(screenshotInterval);
|
|
1243
|
+
if (!completed) {
|
|
1244
|
+
logger.warn(
|
|
1245
|
+
"Timeout elapsed waiting for browser to initialize - are you sure this page is accessible?"
|
|
1246
|
+
);
|
|
1247
|
+
}
|
|
1248
|
+
return chrome;
|
|
1249
|
+
});
|
|
1250
|
+
}
|
|
1251
|
+
// Things to do on every page load
|
|
1252
|
+
pageSetup() {
|
|
1253
|
+
return __async(this, null, function* () {
|
|
1254
|
+
yield this.page.evaluate(addCursorScript);
|
|
1255
|
+
yield this.page.evaluate(addIDsScript);
|
|
1256
|
+
});
|
|
1257
|
+
}
|
|
1258
|
+
wait(timeoutMs) {
|
|
1259
|
+
return __async(this, null, function* () {
|
|
1260
|
+
yield this.page.waitForTimeout(timeoutMs);
|
|
1261
|
+
});
|
|
1262
|
+
}
|
|
1263
|
+
cleanup() {
|
|
1264
|
+
return __async(this, null, function* () {
|
|
1265
|
+
yield this.page.close();
|
|
1266
|
+
yield this.context.close();
|
|
1267
|
+
yield this.browser.close();
|
|
1268
|
+
});
|
|
1269
|
+
}
|
|
1270
|
+
get closed() {
|
|
1271
|
+
return this.page.isClosed() || !this.browser.isConnected();
|
|
1272
|
+
}
|
|
1273
|
+
html() {
|
|
1274
|
+
return __async(this, null, function* () {
|
|
1275
|
+
return yield this.page.content();
|
|
1276
|
+
});
|
|
1277
|
+
}
|
|
1278
|
+
get url() {
|
|
1279
|
+
return this.page.url();
|
|
1280
|
+
}
|
|
1281
|
+
screenshot(quality = 100, scale = "device") {
|
|
1282
|
+
return __async(this, null, function* () {
|
|
1283
|
+
return yield this.page.screenshot({
|
|
1284
|
+
fullPage: false,
|
|
1285
|
+
quality,
|
|
1286
|
+
scale,
|
|
1287
|
+
type: "jpeg",
|
|
1288
|
+
// allow the blinking text cursor thing to remain there
|
|
1289
|
+
caret: "initial"
|
|
1290
|
+
});
|
|
1291
|
+
});
|
|
1292
|
+
}
|
|
1293
|
+
get viewport() {
|
|
1294
|
+
const viewport = this.page.viewportSize();
|
|
1295
|
+
if (!viewport) {
|
|
1296
|
+
throw new Error("failed to get viewport");
|
|
1297
|
+
}
|
|
1298
|
+
return viewport;
|
|
1299
|
+
}
|
|
1300
|
+
navigate(url, wrapPossibleNavigation = true) {
|
|
1301
|
+
return __async(this, null, function* () {
|
|
1302
|
+
this.logger.debug(`Navigating to ${url}`);
|
|
1303
|
+
const startTime = Date.now();
|
|
1304
|
+
const doNav = () => __async(this, null, function* () {
|
|
1305
|
+
try {
|
|
1306
|
+
yield this.page.goto(url, {
|
|
1307
|
+
timeout: MAX_LOAD_TIMEOUT_MS
|
|
1308
|
+
});
|
|
1309
|
+
this.logger.debug(
|
|
1310
|
+
{ url },
|
|
1311
|
+
`Got load event in ${Math.floor(Date.now() - startTime)}ms`
|
|
1312
|
+
);
|
|
1313
|
+
} catch (e) {
|
|
1314
|
+
this.logger.warn(
|
|
1315
|
+
{ url, type: "navigate", err: e },
|
|
1316
|
+
"Timeout elapsed waiting for page to load, continuing anyways..."
|
|
1317
|
+
);
|
|
1318
|
+
}
|
|
1319
|
+
});
|
|
1320
|
+
if (wrapPossibleNavigation) {
|
|
1321
|
+
yield this.wrapPossibleNavigation(doNav);
|
|
1322
|
+
} else {
|
|
1323
|
+
yield doNav();
|
|
1324
|
+
}
|
|
1325
|
+
if (CHROME_INTERNAL_URLS.has(this.url) && process.env.NODE_ENV === "production") {
|
|
1326
|
+
throw new Error(
|
|
1327
|
+
`${url} took too long to load \u{1F61E}. Please ensure the site and your internet are working.`
|
|
1328
|
+
);
|
|
1329
|
+
}
|
|
1330
|
+
yield this.pageSetup();
|
|
1331
|
+
this.logger.debug({ url }, "Navigation complete");
|
|
1332
|
+
});
|
|
1333
|
+
}
|
|
1334
|
+
fill(_0, _1) {
|
|
1335
|
+
return __async(this, arguments, function* (target, text, options = {}) {
|
|
1336
|
+
const element = yield this.click(target, {
|
|
1337
|
+
doubleClick: false,
|
|
1338
|
+
rightClick: false
|
|
1339
|
+
});
|
|
1340
|
+
yield this.type(text, options);
|
|
1341
|
+
return element;
|
|
1342
|
+
});
|
|
1343
|
+
}
|
|
1344
|
+
type(_0) {
|
|
1345
|
+
return __async(this, arguments, function* (text, options = {}) {
|
|
1346
|
+
const { clearContent = true, pressKeysSequentially = false } = options;
|
|
1347
|
+
if (clearContent) {
|
|
1348
|
+
yield this.page.keyboard.press("Meta+A");
|
|
1349
|
+
yield this.page.keyboard.press("Backspace");
|
|
1350
|
+
}
|
|
1351
|
+
if (pressKeysSequentially) {
|
|
1352
|
+
yield this.page.keyboard.type(text);
|
|
1353
|
+
} else {
|
|
1354
|
+
yield this.page.keyboard.insertText(text);
|
|
1355
|
+
}
|
|
1356
|
+
});
|
|
1357
|
+
}
|
|
1358
|
+
clickByA11yID(_0) {
|
|
1359
|
+
return __async(this, arguments, function* (index, options = {}) {
|
|
1360
|
+
const node = this.nodeMap.get(`${index}`);
|
|
1361
|
+
if (!node) {
|
|
1362
|
+
throw new Error(`Could not find node in DOM with index: ${index}`);
|
|
1363
|
+
}
|
|
1364
|
+
const nodeClicked = yield this.clickUsingCDP(node, options);
|
|
1365
|
+
yield this.highlightNode(nodeClicked);
|
|
1366
|
+
return node.serialize({ noChildren: true, noProperties: true, noID: true });
|
|
1367
|
+
});
|
|
1368
|
+
}
|
|
1369
|
+
selectOptionByA11yID(index, option) {
|
|
1370
|
+
return __async(this, null, function* () {
|
|
1371
|
+
const node = this.nodeMap.get(`${index}`);
|
|
1372
|
+
if (!node) {
|
|
1373
|
+
throw new Error(`Could not find node in DOM with index: ${index}`);
|
|
1374
|
+
}
|
|
1375
|
+
if (!node.backendNodeID) {
|
|
1376
|
+
throw new Error(
|
|
1377
|
+
`Select target missing backend node id: ${node.getLogForm()}`
|
|
1378
|
+
);
|
|
1379
|
+
}
|
|
1380
|
+
const locator = yield this.getLocatorFromBackendID(node.backendNodeID);
|
|
1381
|
+
yield locator.selectOption(option, {
|
|
1382
|
+
timeout: COMPLICATED_BROWSER_ACTION_TIMEOUT_MS
|
|
1383
|
+
});
|
|
1384
|
+
yield this.highlightNode(node);
|
|
1385
|
+
return node.serialize({ noChildren: true, noProperties: true, noID: true });
|
|
1386
|
+
});
|
|
1387
|
+
}
|
|
1388
|
+
highlight(target) {
|
|
1389
|
+
return __async(this, null, function* () {
|
|
1390
|
+
try {
|
|
1391
|
+
yield this.highlightByA11yID(target.id);
|
|
1392
|
+
} catch (err) {
|
|
1393
|
+
this.logger.warn({ err, target }, "Failed to highlight target");
|
|
1394
|
+
}
|
|
1395
|
+
});
|
|
1396
|
+
}
|
|
1397
|
+
highlightByA11yID(index) {
|
|
1398
|
+
return __async(this, null, function* () {
|
|
1399
|
+
const node = this.nodeMap.get(`${index}`);
|
|
1400
|
+
if (!node) {
|
|
1401
|
+
throw new Error(`Could not find node in DOM with index: ${index}`);
|
|
1402
|
+
}
|
|
1403
|
+
if (!node.backendNodeID) {
|
|
1404
|
+
throw new Error(
|
|
1405
|
+
`Select target missing backend node id: ${node.getLogForm()}`
|
|
1406
|
+
);
|
|
1407
|
+
}
|
|
1408
|
+
yield this.highlightNode(node);
|
|
1409
|
+
});
|
|
1410
|
+
}
|
|
1411
|
+
highlightNode(node) {
|
|
1412
|
+
return __async(this, null, function* () {
|
|
1413
|
+
try {
|
|
1414
|
+
yield this.cdpClient.send("Overlay.highlightNode", {
|
|
1415
|
+
highlightConfig: NODE_HIGHLIGHT_CONFIG,
|
|
1416
|
+
backendNodeId: node.backendNodeID
|
|
1417
|
+
});
|
|
1418
|
+
} catch (err) {
|
|
1419
|
+
this.logger.warn({ err }, "Failed to add node highlight");
|
|
1420
|
+
}
|
|
1421
|
+
const hideHighlight = () => __async(this, null, function* () {
|
|
1422
|
+
try {
|
|
1423
|
+
yield this.cdpClient.send("Overlay.hideHighlight", {
|
|
1424
|
+
backendNodeId: node.backendNodeID
|
|
1425
|
+
});
|
|
1426
|
+
} catch (err) {
|
|
1427
|
+
this.logger.debug({ err }, "Failed to remove node highlight");
|
|
1428
|
+
}
|
|
1429
|
+
});
|
|
1430
|
+
setTimeout(() => {
|
|
1431
|
+
void hideHighlight();
|
|
1432
|
+
}, HIGHLIGHT_DURATION_MS);
|
|
1433
|
+
});
|
|
1434
|
+
}
|
|
1435
|
+
wrapPossibleNavigation(_0) {
|
|
1436
|
+
return __async(this, arguments, function* (fn, timeoutMS = MAX_LOAD_TIMEOUT_MS) {
|
|
1437
|
+
const startTime = Date.now();
|
|
1438
|
+
const startURL = this.url;
|
|
1439
|
+
let lastRequestReceived = Date.now();
|
|
1440
|
+
const firedRequests = /* @__PURE__ */ new Map();
|
|
1441
|
+
const finishedRequests = /* @__PURE__ */ new Map();
|
|
1442
|
+
const requestFinishedListener = (request) => {
|
|
1443
|
+
var _a;
|
|
1444
|
+
const key = serializeRequest(request);
|
|
1445
|
+
finishedRequests.set(key, ((_a = finishedRequests.get(key)) != null ? _a : 0) + 1);
|
|
1446
|
+
};
|
|
1447
|
+
const requestFiredListener = (request) => {
|
|
1448
|
+
var _a;
|
|
1449
|
+
if (!isRequestRelevantForPageLoad(request, this.url)) {
|
|
1450
|
+
this.logger.debug(
|
|
1451
|
+
{
|
|
1452
|
+
uri: serializeRequest(request)
|
|
1453
|
+
},
|
|
1454
|
+
"Ignoring request for page load network stability"
|
|
1455
|
+
);
|
|
1456
|
+
return;
|
|
1457
|
+
}
|
|
1458
|
+
const key = serializeRequest(request);
|
|
1459
|
+
this.logger.debug(
|
|
1460
|
+
{
|
|
1461
|
+
uri: key
|
|
1462
|
+
},
|
|
1463
|
+
"Request fired on page load, delaying network stability"
|
|
1464
|
+
);
|
|
1465
|
+
firedRequests.set(key, ((_a = firedRequests.get(key)) != null ? _a : 0) + 1);
|
|
1466
|
+
lastRequestReceived = Date.now();
|
|
1467
|
+
};
|
|
1468
|
+
this.page.on("requestfinished", requestFinishedListener);
|
|
1469
|
+
this.page.on("request", requestFiredListener);
|
|
1470
|
+
let rejected = false;
|
|
1471
|
+
const retPromise = fn().catch((e) => {
|
|
1472
|
+
rejected = true;
|
|
1473
|
+
if (e instanceof Error)
|
|
1474
|
+
return e;
|
|
1475
|
+
return new Error(`${e}`);
|
|
1476
|
+
});
|
|
1477
|
+
yield sleep(CHECK_INTERVAL_MS);
|
|
1478
|
+
const unwrapAndThrowError = (p) => __async(this, null, function* () {
|
|
1479
|
+
const v = yield p;
|
|
1480
|
+
if (v instanceof Error) {
|
|
1481
|
+
throw v;
|
|
1482
|
+
}
|
|
1483
|
+
return v;
|
|
1484
|
+
});
|
|
1485
|
+
let unfinishedRequests = /* @__PURE__ */ new Set();
|
|
1486
|
+
const waitForNetworkIdle = () => __async(this, null, function* () {
|
|
1487
|
+
while (!rejected && Date.now() - startTime < timeoutMS) {
|
|
1488
|
+
unfinishedRequests = /* @__PURE__ */ new Set();
|
|
1489
|
+
yield sleep(CHECK_INTERVAL_MS);
|
|
1490
|
+
if (Date.now() - lastRequestReceived <= NETWORK_STABLE_DURATION_MS) {
|
|
1491
|
+
continue;
|
|
1492
|
+
}
|
|
1493
|
+
let anyDifference = false;
|
|
1494
|
+
for (const key of firedRequests.keys()) {
|
|
1495
|
+
if (firedRequests.get(key) !== finishedRequests.get(key)) {
|
|
1496
|
+
this.logger.debug({ uri: key }, "Waiting on request to finish");
|
|
1497
|
+
anyDifference = true;
|
|
1498
|
+
unfinishedRequests.add(key);
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
if (!anyDifference) {
|
|
1502
|
+
this.logger.debug(
|
|
1503
|
+
{
|
|
1504
|
+
url: this.url,
|
|
1505
|
+
requests: JSON.stringify(Array.from(firedRequests.entries()))
|
|
1506
|
+
},
|
|
1507
|
+
`Network idle in ${Math.floor(Date.now() - startTime)}ms`
|
|
1508
|
+
);
|
|
1509
|
+
return true;
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
if (!rejected) {
|
|
1513
|
+
this.logger.warn(
|
|
1514
|
+
{
|
|
1515
|
+
url: this.url,
|
|
1516
|
+
requests: JSON.stringify(Array.from(unfinishedRequests.entries()))
|
|
1517
|
+
},
|
|
1518
|
+
"Timeout elapsed waiting for network idle, continuing anyways..."
|
|
1519
|
+
);
|
|
1520
|
+
}
|
|
1521
|
+
return false;
|
|
1522
|
+
});
|
|
1523
|
+
const waitResult = yield waitForNetworkIdle();
|
|
1524
|
+
this.page.off("requestfinished", requestFinishedListener);
|
|
1525
|
+
this.page.off("request", requestFiredListener);
|
|
1526
|
+
if (!waitResult) {
|
|
1527
|
+
return unwrapAndThrowError(retPromise);
|
|
1528
|
+
}
|
|
1529
|
+
if (!rejected && urlChanged(this.url, startURL)) {
|
|
1530
|
+
this.logger.debug(
|
|
1531
|
+
`Detected url change in wrapPossibleNavigation, waiting for load state`
|
|
1532
|
+
);
|
|
1533
|
+
try {
|
|
1534
|
+
yield this.page.waitForLoadState("load", {
|
|
1535
|
+
timeout: timeoutMS - (Date.now() - startTime)
|
|
1536
|
+
});
|
|
1537
|
+
} catch (e) {
|
|
1538
|
+
this.logger.warn(
|
|
1539
|
+
{ url: this.url },
|
|
1540
|
+
"Timeout elapsed waiting for load state to fire, continuing anyways..."
|
|
1541
|
+
);
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
return unwrapAndThrowError(retPromise);
|
|
1545
|
+
});
|
|
1546
|
+
}
|
|
1547
|
+
click(_0) {
|
|
1548
|
+
return __async(this, arguments, function* (target, options = {}) {
|
|
1549
|
+
const elementInteracted = yield this.wrapPossibleNavigation(
|
|
1550
|
+
() => this.clickByA11yID(target.id, options)
|
|
1551
|
+
);
|
|
1552
|
+
return elementInteracted;
|
|
1553
|
+
});
|
|
1554
|
+
}
|
|
1555
|
+
selectOption(target, option) {
|
|
1556
|
+
return __async(this, null, function* () {
|
|
1557
|
+
return this.selectOptionByA11yID(target.id, option);
|
|
1558
|
+
});
|
|
1559
|
+
}
|
|
1560
|
+
press(key) {
|
|
1561
|
+
return __async(this, null, function* () {
|
|
1562
|
+
yield this.wrapPossibleNavigation(() => this.page.keyboard.press(key));
|
|
1563
|
+
});
|
|
1564
|
+
}
|
|
1565
|
+
refresh() {
|
|
1566
|
+
return __async(this, null, function* () {
|
|
1567
|
+
yield this.page.reload();
|
|
1568
|
+
yield this.pageSetup();
|
|
1569
|
+
});
|
|
1570
|
+
}
|
|
1571
|
+
getA11yTree() {
|
|
1572
|
+
return __async(this, null, function* () {
|
|
1573
|
+
let processedTree = null;
|
|
1574
|
+
let attempt = 0;
|
|
1575
|
+
const url = this.url;
|
|
1576
|
+
while (!processedTree) {
|
|
1577
|
+
try {
|
|
1578
|
+
this.logger.debug(`Getting a11y tree at ${url}`);
|
|
1579
|
+
const graph = yield this.getRawA11yTree();
|
|
1580
|
+
if (!graph.root || graph.allNodes.length === 0) {
|
|
1581
|
+
throw new Error("No a11y tree found on page");
|
|
1582
|
+
}
|
|
1583
|
+
processedTree = processA11yTree(graph);
|
|
1584
|
+
} catch (e) {
|
|
1585
|
+
this.logger.error({ err: e, url }, "Error fetching a11y tree");
|
|
1586
|
+
if (attempt === 0) {
|
|
1587
|
+
yield sleep(1e3);
|
|
1588
|
+
attempt++;
|
|
1589
|
+
} else {
|
|
1590
|
+
throw new Error(`Max retries exceeded fetching a11y tree: ${e}`);
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
if (!processedTree.root) {
|
|
1595
|
+
this.logger.warn("A11y tree was pruned entirely");
|
|
1596
|
+
}
|
|
1597
|
+
this.nodeMap = processedTree.nodeMap;
|
|
1598
|
+
return processedTree;
|
|
1599
|
+
});
|
|
1600
|
+
}
|
|
1601
|
+
getRawA11yTree() {
|
|
1602
|
+
return __async(this, null, function* () {
|
|
1603
|
+
const url = this.page.url();
|
|
1604
|
+
let lastTreeUpdateTimestamp = Date.now();
|
|
1605
|
+
const treeUpdateListener = () => {
|
|
1606
|
+
lastTreeUpdateTimestamp = Date.now();
|
|
1607
|
+
};
|
|
1608
|
+
this.cdpClient.addListener(
|
|
1609
|
+
"Accessibility.nodesUpdated",
|
|
1610
|
+
treeUpdateListener
|
|
1611
|
+
);
|
|
1612
|
+
let accessibilityTreeLoadFired = false;
|
|
1613
|
+
const accessibilityLoadListener = () => {
|
|
1614
|
+
this.logger.info({ url }, `A11y tree load event fired`);
|
|
1615
|
+
accessibilityTreeLoadFired = true;
|
|
1616
|
+
};
|
|
1617
|
+
this.cdpClient.addListener(
|
|
1618
|
+
"Accessibility.loadComplete",
|
|
1619
|
+
accessibilityLoadListener
|
|
1620
|
+
);
|
|
1621
|
+
const a11yLoadStart = Date.now();
|
|
1622
|
+
let timeoutTriggered = true;
|
|
1623
|
+
while (Date.now() - a11yLoadStart < A11Y_STABLE_TIMEOUT_MS) {
|
|
1624
|
+
yield sleep(CHECK_INTERVAL_MS);
|
|
1625
|
+
if (!accessibilityTreeLoadFired && Date.now() - a11yLoadStart < A11Y_LOAD_TIMEOUT_MS) {
|
|
1626
|
+
this.logger.debug({ url }, `A11y tree not loaded yet, waiting...`);
|
|
1627
|
+
continue;
|
|
1628
|
+
}
|
|
1629
|
+
if (Date.now() - lastTreeUpdateTimestamp >= A11Y_STABLE_DURATION_MS) {
|
|
1630
|
+
this.logger.debug({ url }, `A11y tree not stable yet, waiting...`);
|
|
1631
|
+
continue;
|
|
1632
|
+
}
|
|
1633
|
+
timeoutTriggered = false;
|
|
1634
|
+
break;
|
|
1635
|
+
}
|
|
1636
|
+
this.logger.debug(
|
|
1637
|
+
{
|
|
1638
|
+
duration: Date.now() - a11yLoadStart,
|
|
1639
|
+
eventReceived: accessibilityTreeLoadFired,
|
|
1640
|
+
timeoutTriggered
|
|
1641
|
+
},
|
|
1642
|
+
"A11y wait phase completed"
|
|
1643
|
+
);
|
|
1644
|
+
const { node: root } = yield this.cdpClient.send(
|
|
1645
|
+
"Accessibility.getRootAXNode"
|
|
1646
|
+
);
|
|
1647
|
+
const { nodes } = yield this.cdpClient.send("Accessibility.queryAXTree", {
|
|
1648
|
+
backendNodeId: root.backendDOMNodeId
|
|
1649
|
+
});
|
|
1650
|
+
this.cdpClient.removeListener(
|
|
1651
|
+
"Accessibility.loadComplete",
|
|
1652
|
+
accessibilityLoadListener
|
|
1653
|
+
);
|
|
1654
|
+
this.cdpClient.removeListener(
|
|
1655
|
+
"Accessibility.nodesUpdated",
|
|
1656
|
+
treeUpdateListener
|
|
1657
|
+
);
|
|
1658
|
+
return {
|
|
1659
|
+
root,
|
|
1660
|
+
allNodes: nodes
|
|
1661
|
+
};
|
|
1662
|
+
});
|
|
1663
|
+
}
|
|
1664
|
+
clickUsingVisualCoordinates(backendNodeId) {
|
|
1665
|
+
return __async(this, null, function* () {
|
|
1666
|
+
const location = yield this.getElementLocation(backendNodeId);
|
|
1667
|
+
if (!location) {
|
|
1668
|
+
throw new Error(
|
|
1669
|
+
`Could not find element location with backend node id: ${backendNodeId}`
|
|
1670
|
+
);
|
|
1671
|
+
}
|
|
1672
|
+
this.logger.debug({ location }, "Executing mouse click");
|
|
1673
|
+
yield this.page.mouse.click(location.centerX, location.centerY);
|
|
1674
|
+
});
|
|
1675
|
+
}
|
|
1676
|
+
// Get the "id" attribute value from an HTML element.
|
|
1677
|
+
getIDAttributeUsingCDP(objectId) {
|
|
1678
|
+
return __async(this, null, function* () {
|
|
1679
|
+
yield this.cdpClient.send("DOM.getDocument", { depth: 0 });
|
|
1680
|
+
const cdpNodeResult = yield this.cdpClient.send("DOM.requestNode", {
|
|
1681
|
+
objectId
|
|
1682
|
+
});
|
|
1683
|
+
const attrResult = yield this.cdpClient.send("DOM.getAttributes", {
|
|
1684
|
+
nodeId: cdpNodeResult.nodeId
|
|
1685
|
+
});
|
|
1686
|
+
const attributes = attrResult.attributes;
|
|
1687
|
+
const indexAttr = attributes.findIndex((s) => s === "data-momentic-id");
|
|
1688
|
+
if (indexAttr === -1) {
|
|
1689
|
+
return "";
|
|
1690
|
+
}
|
|
1691
|
+
return attributes[indexAttr + 1] || "";
|
|
1692
|
+
});
|
|
1693
|
+
}
|
|
1694
|
+
getLocatorFromBackendID(backendNodeId) {
|
|
1695
|
+
return __async(this, null, function* () {
|
|
1696
|
+
yield this.page.evaluate(addIDsScript);
|
|
1697
|
+
const cdpResolveResult = yield this.cdpClient.send("DOM.resolveNode", {
|
|
1698
|
+
backendNodeId
|
|
1699
|
+
});
|
|
1700
|
+
if (!cdpResolveResult || !cdpResolveResult.object.objectId) {
|
|
1701
|
+
throw new Error(`Could not resolve backend node ${backendNodeId}`);
|
|
1702
|
+
}
|
|
1703
|
+
try {
|
|
1704
|
+
const id = yield this.getIDAttributeUsingCDP(
|
|
1705
|
+
cdpResolveResult.object.objectId
|
|
1706
|
+
);
|
|
1707
|
+
if (!id) {
|
|
1708
|
+
throw new Error("Failed getting data-momentic-id attribute using CDP");
|
|
1709
|
+
}
|
|
1710
|
+
return this.page.locator(`[data-momentic-id="${id}"]`);
|
|
1711
|
+
} catch (err) {
|
|
1712
|
+
this.logger.error(
|
|
1713
|
+
{
|
|
1714
|
+
err
|
|
1715
|
+
},
|
|
1716
|
+
"Failed to get ID attribute"
|
|
1717
|
+
);
|
|
1718
|
+
throw err;
|
|
1719
|
+
}
|
|
1720
|
+
});
|
|
1721
|
+
}
|
|
1722
|
+
clickUsingCDP(_0) {
|
|
1723
|
+
return __async(this, arguments, function* (originalNode, options = {}) {
|
|
1724
|
+
let clickAttempts = 0;
|
|
1725
|
+
let candidateNode = originalNode;
|
|
1726
|
+
while (clickAttempts < MAX_BROWSER_ACTION_ATTEMPTS) {
|
|
1727
|
+
if (!candidateNode || candidateNode.role === "RootWebArea") {
|
|
1728
|
+
throw new Error(
|
|
1729
|
+
`Attempted to click node with no clickable surrounding elements: ${originalNode.getLogForm()}`
|
|
1730
|
+
);
|
|
1731
|
+
}
|
|
1732
|
+
if (candidateNode.role === "StaticText") {
|
|
1733
|
+
candidateNode = candidateNode.parent;
|
|
1734
|
+
continue;
|
|
1735
|
+
}
|
|
1736
|
+
const candidateNodeID = candidateNode.backendNodeID;
|
|
1737
|
+
if (!candidateNodeID) {
|
|
1738
|
+
this.logger.warn(
|
|
1739
|
+
{ node: candidateNode.getLogForm() },
|
|
1740
|
+
"Click candidate had no backend node ID"
|
|
1741
|
+
);
|
|
1742
|
+
candidateNode = candidateNode.parent;
|
|
1743
|
+
continue;
|
|
1744
|
+
}
|
|
1745
|
+
try {
|
|
1746
|
+
const locator = yield this.getLocatorFromBackendID(candidateNodeID);
|
|
1747
|
+
if (options.doubleClick) {
|
|
1748
|
+
yield locator.dblclick({
|
|
1749
|
+
timeout: BROWSER_ACTION_TIMEOUT_MS
|
|
1750
|
+
});
|
|
1751
|
+
} else {
|
|
1752
|
+
yield locator.click({
|
|
1753
|
+
timeout: BROWSER_ACTION_TIMEOUT_MS,
|
|
1754
|
+
button: options.rightClick ? "right" : "left"
|
|
1755
|
+
});
|
|
1756
|
+
}
|
|
1757
|
+
if (candidateNode.id !== originalNode.id) {
|
|
1758
|
+
this.logger.info(
|
|
1759
|
+
{
|
|
1760
|
+
oldNode: originalNode.getLogForm(),
|
|
1761
|
+
newNode: candidateNode.getLogForm()
|
|
1762
|
+
},
|
|
1763
|
+
`Redirected click successfully to new element`
|
|
1764
|
+
);
|
|
1765
|
+
}
|
|
1766
|
+
return candidateNode;
|
|
1767
|
+
} catch (err) {
|
|
1768
|
+
this.logger.error(
|
|
1769
|
+
{ err, node: candidateNode.getLogForm() },
|
|
1770
|
+
"Failed click or click timed out"
|
|
1771
|
+
);
|
|
1772
|
+
clickAttempts++;
|
|
1773
|
+
candidateNode = candidateNode.parent;
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
throw new Error(
|
|
1777
|
+
`Max click redirection attempts exhausted on original element: ${originalNode.getLogForm()}`
|
|
1778
|
+
);
|
|
1779
|
+
});
|
|
1780
|
+
}
|
|
1781
|
+
/**
|
|
1782
|
+
* Currently unused, but could be useful for vision model integration.
|
|
1783
|
+
* Gets x/y position of an a11y node.
|
|
1784
|
+
*/
|
|
1785
|
+
getElementLocation(backendNodeId) {
|
|
1786
|
+
return __async(this, null, function* () {
|
|
1787
|
+
const tree = yield this.cdpClient.send("DOMSnapshot.captureSnapshot", {
|
|
1788
|
+
computedStyles: [],
|
|
1789
|
+
includeDOMRects: true,
|
|
1790
|
+
includePaintOrder: true
|
|
1791
|
+
});
|
|
1792
|
+
let devicePixelRatio = yield this.page.evaluate(
|
|
1793
|
+
() => window.devicePixelRatio
|
|
1794
|
+
);
|
|
1795
|
+
if (process.platform === "darwin" && devicePixelRatio === 1) {
|
|
1796
|
+
devicePixelRatio = RETINA_WINDOW_SCALE_FACTOR;
|
|
1797
|
+
}
|
|
1798
|
+
const document2 = tree["documents"][0];
|
|
1799
|
+
const layout = document2["layout"];
|
|
1800
|
+
const nodes = document2["nodes"];
|
|
1801
|
+
const nodeNames = nodes["nodeName"] || [];
|
|
1802
|
+
const backendNodeIds = nodes["backendNodeId"] || [];
|
|
1803
|
+
const layoutNodeIndex = layout["nodeIndex"];
|
|
1804
|
+
const bounds = layout["bounds"];
|
|
1805
|
+
let cursor2 = -1;
|
|
1806
|
+
for (let i = 0; i < nodeNames.length; i++) {
|
|
1807
|
+
if (backendNodeIds[i] === backendNodeId) {
|
|
1808
|
+
cursor2 = layoutNodeIndex.indexOf(i);
|
|
1809
|
+
break;
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
if (cursor2 === -1) {
|
|
1813
|
+
throw new Error(
|
|
1814
|
+
`Could not find any backend node with ID ${backendNodeId}`
|
|
1815
|
+
);
|
|
1816
|
+
}
|
|
1817
|
+
let [x = 0, y = 0, width = 0, height = 0] = bounds[cursor2];
|
|
1818
|
+
x /= devicePixelRatio;
|
|
1819
|
+
y /= devicePixelRatio;
|
|
1820
|
+
width /= devicePixelRatio;
|
|
1821
|
+
height /= devicePixelRatio;
|
|
1822
|
+
const centerX = x + width / 2;
|
|
1823
|
+
const centerY = y + height / 2;
|
|
1824
|
+
return { centerX, centerY };
|
|
1825
|
+
});
|
|
1826
|
+
}
|
|
1827
|
+
scrollUp() {
|
|
1828
|
+
return __async(this, null, function* () {
|
|
1829
|
+
yield this.page.evaluate(() => {
|
|
1830
|
+
(document.scrollingElement || document.body).scrollTop = (document.scrollingElement || document.body).scrollTop - window.innerHeight;
|
|
1831
|
+
});
|
|
1832
|
+
yield this.page.evaluate(() => {
|
|
1833
|
+
(document.scrollingElement || document.body).scrollTop = (document.scrollingElement || document.body).scrollTop + window.innerHeight;
|
|
1834
|
+
});
|
|
1835
|
+
});
|
|
1836
|
+
}
|
|
1837
|
+
scrollDown() {
|
|
1838
|
+
return __async(this, null, function* () {
|
|
1839
|
+
yield this.page.evaluate(() => {
|
|
1840
|
+
(document.scrollingElement || document.body).scrollTop = (document.scrollingElement || document.body).scrollTop + window.innerHeight;
|
|
1841
|
+
});
|
|
1842
|
+
});
|
|
1843
|
+
}
|
|
1844
|
+
goForward() {
|
|
1845
|
+
return __async(this, null, function* () {
|
|
1846
|
+
yield this.wrapPossibleNavigation(
|
|
1847
|
+
() => this.page.goForward({ timeout: MAX_LOAD_TIMEOUT_MS })
|
|
1848
|
+
);
|
|
1849
|
+
yield this.pageSetup();
|
|
1850
|
+
});
|
|
1851
|
+
}
|
|
1852
|
+
goBack() {
|
|
1853
|
+
return __async(this, null, function* () {
|
|
1854
|
+
yield this.wrapPossibleNavigation(
|
|
1855
|
+
() => this.page.goBack({ timeout: MAX_LOAD_TIMEOUT_MS })
|
|
1856
|
+
);
|
|
1857
|
+
yield this.pageSetup();
|
|
1858
|
+
});
|
|
1859
|
+
}
|
|
1860
|
+
switchToPage(urlSubstring) {
|
|
1861
|
+
return __async(this, null, function* () {
|
|
1862
|
+
const allPages = yield this.context.pages();
|
|
1863
|
+
for (let i = 0; i < allPages.length; i++) {
|
|
1864
|
+
const page = allPages[i];
|
|
1865
|
+
if (page.url().includes(urlSubstring)) {
|
|
1866
|
+
this.page = page;
|
|
1867
|
+
yield page.waitForLoadState("load", {
|
|
1868
|
+
timeout: MAX_LOAD_TIMEOUT_MS
|
|
1869
|
+
});
|
|
1870
|
+
yield this.pageSetup();
|
|
1871
|
+
this.cdpClient = yield this.context.newCDPSession(page);
|
|
1872
|
+
yield initCDPSession(this.cdpClient);
|
|
1873
|
+
this.logger.info(`Switching to tab ${i} with url ${page.url()}`);
|
|
1874
|
+
return;
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
throw new Error(`Could not find page with url containing ${urlSubstring}`);
|
|
1878
|
+
});
|
|
1879
|
+
}
|
|
1880
|
+
setCookie(cookie) {
|
|
1881
|
+
return __async(this, null, function* () {
|
|
1882
|
+
const cookieSettings = parseCookieString(cookie);
|
|
1883
|
+
yield this.context.addCookies([cookieSettings]);
|
|
1884
|
+
});
|
|
1885
|
+
}
|
|
1886
|
+
};
|
|
1887
|
+
_ChromeBrowser.USER_AGENT = import_playwright.devices["Desktop Chrome"].userAgent;
|
|
1888
|
+
var ChromeBrowser = _ChromeBrowser;
|
|
1889
|
+
|
|
1890
|
+
// ../../packages/web-agent/src/configs/controller.ts
|
|
1891
|
+
var A11Y_CONTROLLER_CONFIG = {
|
|
1892
|
+
type: "a11y",
|
|
1893
|
+
version: "1.0.0",
|
|
1894
|
+
useHistory: "diff",
|
|
1895
|
+
useGoalSplitter: true
|
|
1896
|
+
};
|
|
1897
|
+
var DEFAULT_CONTROLLER_CONFIG = A11Y_CONTROLLER_CONFIG;
|
|
1898
|
+
|
|
1899
|
+
// ../../packages/web-agent/src/controller.ts
|
|
1900
|
+
var import_dedent2 = __toESM(require("dedent"), 1);
|
|
1901
|
+
var import_diff_lines = __toESM(require("diff-lines"), 1);
|
|
1902
|
+
var MAX_HISTORY_CHAR_LENGTH = 1e4;
|
|
1903
|
+
var AgentController = class {
|
|
1904
|
+
constructor({ browser, config, generator, logger }) {
|
|
1905
|
+
this.browser = browser;
|
|
1906
|
+
this.generator = generator;
|
|
1907
|
+
this.config = config;
|
|
1908
|
+
this.logger = logger;
|
|
1909
|
+
this.pendingInstructions = [];
|
|
1910
|
+
this.commandHistory = [];
|
|
1911
|
+
}
|
|
1912
|
+
/**
|
|
1913
|
+
* Get copy of executed commands in human readable form. Most recent is last.
|
|
1914
|
+
* Only commands that have completed execution are returned.
|
|
1915
|
+
*/
|
|
1916
|
+
get history() {
|
|
1917
|
+
return this.commandHistory.filter((cmd) => cmd.state === "DONE");
|
|
1918
|
+
}
|
|
1919
|
+
get lastExecutedCommand() {
|
|
1920
|
+
const history = this.history;
|
|
1921
|
+
if (history.length === 0)
|
|
1922
|
+
return null;
|
|
1923
|
+
const lastEntry = history[history.length - 1];
|
|
1924
|
+
return lastEntry;
|
|
1925
|
+
}
|
|
1926
|
+
/**
|
|
1927
|
+
* Reset the command history provided to agents.
|
|
1928
|
+
* Should be called due to a logical break between commands
|
|
1929
|
+
* such as a SUCCESS being issued.
|
|
1930
|
+
*/
|
|
1931
|
+
resetHistory() {
|
|
1932
|
+
this.commandHistory = [];
|
|
1933
|
+
this.pendingInstructions = [];
|
|
1934
|
+
}
|
|
1935
|
+
/**
|
|
1936
|
+
* Reset controller and browser state.
|
|
1937
|
+
*/
|
|
1938
|
+
resetState() {
|
|
1939
|
+
return __async(this, null, function* () {
|
|
1940
|
+
this.resetHistory();
|
|
1941
|
+
yield this.browser.navigate(this.browser.baseURL);
|
|
1942
|
+
});
|
|
1943
|
+
}
|
|
1944
|
+
/**
|
|
1945
|
+
* Get the browser state as a string
|
|
1946
|
+
*/
|
|
1947
|
+
getBrowserState() {
|
|
1948
|
+
return __async(this, null, function* () {
|
|
1949
|
+
const a11yTree = yield this.browser.getA11yTree();
|
|
1950
|
+
return a11yTree.serialize();
|
|
1951
|
+
});
|
|
1952
|
+
}
|
|
1953
|
+
getSerializedHistory(url, currentBrowserState) {
|
|
1954
|
+
let history;
|
|
1955
|
+
if (this.config.useHistory === "diff") {
|
|
1956
|
+
history = this.getDiffHistory(url, currentBrowserState);
|
|
1957
|
+
} else {
|
|
1958
|
+
history = this.getListHistory();
|
|
1959
|
+
}
|
|
1960
|
+
return history;
|
|
1961
|
+
}
|
|
1962
|
+
splitUserGoal(type, goal, disableCache) {
|
|
1963
|
+
return __async(this, null, function* () {
|
|
1964
|
+
if (type === "AI_ACTION" /* AI_ACTION */ && goal.match(/[,!;.]|(?:and)|(?:then)/) && this.config.useGoalSplitter) {
|
|
1965
|
+
const granularInstructions = yield this.generator.getGranularGoals(
|
|
1966
|
+
{ goal, url: this.browser.url },
|
|
1967
|
+
disableCache
|
|
1968
|
+
);
|
|
1969
|
+
this.pendingInstructions = granularInstructions.reverse();
|
|
1970
|
+
} else {
|
|
1971
|
+
this.pendingInstructions = [goal];
|
|
1972
|
+
}
|
|
1973
|
+
});
|
|
1974
|
+
}
|
|
1975
|
+
/**
|
|
1976
|
+
* Given previously executed commands, generate command for the current prompt.
|
|
1977
|
+
* Should only be used for AI action.
|
|
1978
|
+
*/
|
|
1979
|
+
promptToCommand(type, goal, disableCache) {
|
|
1980
|
+
return __async(this, null, function* () {
|
|
1981
|
+
if (this.pendingInstructions.length === 0) {
|
|
1982
|
+
yield this.splitUserGoal(type, goal, disableCache);
|
|
1983
|
+
}
|
|
1984
|
+
const currInstruction = this.pendingInstructions[this.pendingInstructions.length - 1];
|
|
1985
|
+
this.logger.info({ goal: currInstruction }, "Starting prompt translation");
|
|
1986
|
+
const getBrowserStateStart = Date.now();
|
|
1987
|
+
const url = this.browser.url;
|
|
1988
|
+
const browserState = yield this.getBrowserState();
|
|
1989
|
+
this.logger.info(
|
|
1990
|
+
{
|
|
1991
|
+
duration: Date.now() - getBrowserStateStart,
|
|
1992
|
+
url
|
|
1993
|
+
},
|
|
1994
|
+
"Got browser state"
|
|
1995
|
+
);
|
|
1996
|
+
const numPrevious = this.commandHistory.length;
|
|
1997
|
+
this.commandHistory.push({
|
|
1998
|
+
state: "PENDING",
|
|
1999
|
+
browserStateBeforeCommand: browserState,
|
|
2000
|
+
urlBeforeCommand: url,
|
|
2001
|
+
type
|
|
2002
|
+
});
|
|
2003
|
+
const history = this.getSerializedHistory(url, browserState);
|
|
2004
|
+
const getCommandProposalStart = Date.now();
|
|
2005
|
+
const proposedCommand = yield this.generator.getProposedCommand(
|
|
2006
|
+
{
|
|
2007
|
+
url,
|
|
2008
|
+
numPrevious,
|
|
2009
|
+
browserState,
|
|
2010
|
+
history,
|
|
2011
|
+
goal: currInstruction,
|
|
2012
|
+
lastCommand: this.lastExecutedCommand
|
|
2013
|
+
},
|
|
2014
|
+
disableCache
|
|
2015
|
+
);
|
|
2016
|
+
this.logger.info(
|
|
2017
|
+
{ duration: Date.now() - getCommandProposalStart },
|
|
2018
|
+
"Got proposed command"
|
|
2019
|
+
);
|
|
2020
|
+
if (proposedCommand.type === "SUCCESS" /* SUCCESS */) {
|
|
2021
|
+
const finishedInstruction = this.pendingInstructions.pop();
|
|
2022
|
+
this.logger.info(
|
|
2023
|
+
{
|
|
2024
|
+
finishedInstruction,
|
|
2025
|
+
remainingInstructions: this.pendingInstructions
|
|
2026
|
+
},
|
|
2027
|
+
"Removing pending instruction due to SUCCESS"
|
|
2028
|
+
);
|
|
2029
|
+
if (this.pendingInstructions.length !== 0) {
|
|
2030
|
+
this.commandHistory.pop();
|
|
2031
|
+
return this.promptToCommand(type, "", disableCache);
|
|
2032
|
+
}
|
|
2033
|
+
} else if (
|
|
2034
|
+
// on failure, we don't continue to execute
|
|
2035
|
+
proposedCommand.type === "FAILURE"
|
|
2036
|
+
) {
|
|
2037
|
+
this.logger.info(
|
|
2038
|
+
{
|
|
2039
|
+
remainingInstructions: this.pendingInstructions
|
|
2040
|
+
},
|
|
2041
|
+
"Removing pending instructions due to FAILURE"
|
|
2042
|
+
);
|
|
2043
|
+
this.pendingInstructions = [];
|
|
2044
|
+
}
|
|
2045
|
+
return proposedCommand;
|
|
2046
|
+
});
|
|
2047
|
+
}
|
|
2048
|
+
locateElement(description, disableCache) {
|
|
2049
|
+
return __async(this, null, function* () {
|
|
2050
|
+
const locator = yield this.generator.getElementLocation(
|
|
2051
|
+
{ browserState: yield this.getBrowserState(), goal: description },
|
|
2052
|
+
disableCache
|
|
2053
|
+
);
|
|
2054
|
+
if (locator.id < 0) {
|
|
2055
|
+
throw new Error(
|
|
2056
|
+
`Unable to locate element with description: ${description}`
|
|
2057
|
+
);
|
|
2058
|
+
}
|
|
2059
|
+
return locator;
|
|
2060
|
+
});
|
|
2061
|
+
}
|
|
2062
|
+
/**
|
|
2063
|
+
* Construct a detailed history that can be passed to the LLM.
|
|
2064
|
+
* History includes commands executed as well as browser state changes that occurred
|
|
2065
|
+
* at each step.
|
|
2066
|
+
*/
|
|
2067
|
+
getDiffHistory(currentURL, currentPageState) {
|
|
2068
|
+
const doneCommands = this.history.filter(
|
|
2069
|
+
(h) => h.type === "AI_ACTION" /* AI_ACTION */
|
|
2070
|
+
);
|
|
2071
|
+
if (doneCommands.length === 0)
|
|
2072
|
+
return "<NONE/>";
|
|
2073
|
+
const historyLines = [
|
|
2074
|
+
"\nYou have already executed the following commands successfully (most recent listed first)",
|
|
2075
|
+
"-".repeat(10)
|
|
2076
|
+
];
|
|
2077
|
+
doneCommands.reverse().forEach((log, i) => {
|
|
2078
|
+
historyLines.push(
|
|
2079
|
+
`COMMAND ${doneCommands.length - i}${i === 0 ? " (command just executed)" : ""}: ${log.serializedCommand}`
|
|
2080
|
+
);
|
|
2081
|
+
if (i === 0) {
|
|
2082
|
+
if (urlChanged(log.urlBeforeCommand, currentURL)) {
|
|
2083
|
+
historyLines.push(
|
|
2084
|
+
` URL CHANGE: '${log.urlBeforeCommand}' -> '${currentURL}'`
|
|
2085
|
+
);
|
|
2086
|
+
} else {
|
|
2087
|
+
const browserStateDiff = (0, import_diff_lines.default)(
|
|
2088
|
+
log.browserStateBeforeCommand,
|
|
2089
|
+
currentPageState,
|
|
2090
|
+
{
|
|
2091
|
+
n_surrounding: 1
|
|
2092
|
+
}
|
|
2093
|
+
);
|
|
2094
|
+
if (!browserStateDiff) {
|
|
2095
|
+
historyLines.push("PAGE CONTENT CHANGE: <NONE/>");
|
|
2096
|
+
} else if (browserStateDiff.length < MAX_HISTORY_CHAR_LENGTH) {
|
|
2097
|
+
historyLines.push("PAGE CONTENT CHANGE:");
|
|
2098
|
+
browserStateDiff.split("\n").forEach((l) => historyLines.push(` ${l}`));
|
|
2099
|
+
} else {
|
|
2100
|
+
historyLines.push("PAGE CONTENT CHANGE: <TOO_LONG_TO_DISPLAY/>");
|
|
2101
|
+
}
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
historyLines.push("-".repeat(10));
|
|
2105
|
+
});
|
|
2106
|
+
historyLines.push(`STARTING URL: ${this.browser.baseURL}`);
|
|
2107
|
+
return historyLines.join("\n");
|
|
2108
|
+
}
|
|
2109
|
+
getListHistory() {
|
|
2110
|
+
return import_dedent2.default`Here are the commands that you have successfully executed:
|
|
2111
|
+
${this.commandHistory.filter((cmd) => cmd.type === "AI_ACTION" /* AI_ACTION */).map((cmd) => `- ${cmd.serializedCommand}`).join("\n")}`;
|
|
2112
|
+
}
|
|
2113
|
+
/**
|
|
2114
|
+
* Given a command, interact with the chromium browser to actually execute the actions
|
|
2115
|
+
* @param [stateless=false] Execute this command in a stateless fashion, without modifying any controller state such as
|
|
2116
|
+
* pending instructions. Useful when executing cached instructions.
|
|
2117
|
+
*/
|
|
2118
|
+
executeCommand(command, disableCache, stateless = false) {
|
|
2119
|
+
return __async(this, null, function* () {
|
|
2120
|
+
const pendingHistory = this.commandHistory[this.commandHistory.length - 1];
|
|
2121
|
+
if (!stateless) {
|
|
2122
|
+
if (!pendingHistory || pendingHistory.state !== "PENDING") {
|
|
2123
|
+
throw new Error(
|
|
2124
|
+
"Executing command but there is no pending entry in the history"
|
|
2125
|
+
);
|
|
2126
|
+
}
|
|
2127
|
+
} else {
|
|
2128
|
+
yield this.browser.getA11yTree();
|
|
2129
|
+
}
|
|
2130
|
+
let result;
|
|
2131
|
+
try {
|
|
2132
|
+
const executionStart = Date.now();
|
|
2133
|
+
result = yield this.executePresetStep(
|
|
2134
|
+
command,
|
|
2135
|
+
disableCache
|
|
2136
|
+
);
|
|
2137
|
+
this.logger.info(
|
|
2138
|
+
{ result, duration: Date.now() - executionStart },
|
|
2139
|
+
"Got execution result"
|
|
2140
|
+
);
|
|
2141
|
+
} catch (e) {
|
|
2142
|
+
if (e instanceof Error) {
|
|
2143
|
+
throw new BrowserExecutionError(`Failed to execute command: ${e}`, {
|
|
2144
|
+
cause: e
|
|
2145
|
+
});
|
|
2146
|
+
}
|
|
2147
|
+
throw new BrowserExecutionError(
|
|
2148
|
+
`Unexpected throw from executing command`,
|
|
2149
|
+
{
|
|
2150
|
+
cause: new Error(`${e}`)
|
|
2151
|
+
}
|
|
2152
|
+
);
|
|
2153
|
+
}
|
|
2154
|
+
if (result.succeedImmediately && !stateless) {
|
|
2155
|
+
this.pendingInstructions.pop();
|
|
2156
|
+
if (this.pendingInstructions.length > 0) {
|
|
2157
|
+
result.succeedImmediately = false;
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
if (result.elementInteracted && "target" in command && !command.target.elementDescriptor) {
|
|
2161
|
+
command.target.elementDescriptor = result.elementInteracted.trim();
|
|
2162
|
+
}
|
|
2163
|
+
if (!stateless) {
|
|
2164
|
+
pendingHistory.generatedStep = command;
|
|
2165
|
+
pendingHistory.serializedCommand = serializeCommand(command);
|
|
2166
|
+
pendingHistory.state = "DONE";
|
|
2167
|
+
}
|
|
2168
|
+
return result;
|
|
2169
|
+
});
|
|
2170
|
+
}
|
|
2171
|
+
executeAssertion(urlBeforeCommand, command) {
|
|
2172
|
+
return __async(this, null, function* () {
|
|
2173
|
+
let params;
|
|
2174
|
+
if (command.useVision) {
|
|
2175
|
+
params = {
|
|
2176
|
+
goal: command.assertion,
|
|
2177
|
+
url: urlBeforeCommand,
|
|
2178
|
+
// used for vision only
|
|
2179
|
+
screenshot: yield this.browser.screenshot(),
|
|
2180
|
+
// unused for visual assertion
|
|
2181
|
+
browserState: "",
|
|
2182
|
+
history: "",
|
|
2183
|
+
numPrevious: -1,
|
|
2184
|
+
lastCommand: null
|
|
2185
|
+
};
|
|
2186
|
+
} else {
|
|
2187
|
+
const browserState = yield this.getBrowserState();
|
|
2188
|
+
const history = this.getSerializedHistory(urlBeforeCommand, browserState);
|
|
2189
|
+
params = {
|
|
2190
|
+
goal: command.assertion,
|
|
2191
|
+
url: urlBeforeCommand,
|
|
2192
|
+
// used for text only
|
|
2193
|
+
browserState,
|
|
2194
|
+
history,
|
|
2195
|
+
lastCommand: this.lastExecutedCommand,
|
|
2196
|
+
numPrevious: this.commandHistory.length
|
|
2197
|
+
};
|
|
2198
|
+
}
|
|
2199
|
+
const assertionEval = yield this.generator.getAssertionResult(
|
|
2200
|
+
params,
|
|
2201
|
+
command.useVision,
|
|
2202
|
+
command.disableCache
|
|
2203
|
+
);
|
|
2204
|
+
if (assertionEval.relevantElements) {
|
|
2205
|
+
void Promise.all(
|
|
2206
|
+
assertionEval.relevantElements.map(
|
|
2207
|
+
(id) => this.browser.highlight({ id })
|
|
2208
|
+
)
|
|
2209
|
+
);
|
|
2210
|
+
}
|
|
2211
|
+
if (!assertionEval.result) {
|
|
2212
|
+
throw new Error(assertionEval.thoughts);
|
|
2213
|
+
}
|
|
2214
|
+
return {
|
|
2215
|
+
succeedImmediately: false,
|
|
2216
|
+
thoughts: assertionEval.thoughts,
|
|
2217
|
+
urlAfterCommand: urlBeforeCommand
|
|
2218
|
+
};
|
|
2219
|
+
});
|
|
2220
|
+
}
|
|
2221
|
+
/**
|
|
2222
|
+
* Executes a preset command.
|
|
2223
|
+
* For most cases, the execution result contains metadata about the command executed.
|
|
2224
|
+
* For assertions, an AssertionResult with thoughts is returned.
|
|
2225
|
+
* Throws on failure.
|
|
2226
|
+
*/
|
|
2227
|
+
executePresetStep(command, disableCache) {
|
|
2228
|
+
return __async(this, null, function* () {
|
|
2229
|
+
var _a, _b, _c;
|
|
2230
|
+
const urlBeforeCommand = this.browser.url;
|
|
2231
|
+
switch (command.type) {
|
|
2232
|
+
case "SUCCESS" /* SUCCESS */:
|
|
2233
|
+
if ((_a = command.condition) == null ? void 0 : _a.assertion.trim()) {
|
|
2234
|
+
return this.executeAssertion(urlBeforeCommand, command.condition);
|
|
2235
|
+
}
|
|
2236
|
+
return {
|
|
2237
|
+
succeedImmediately: false,
|
|
2238
|
+
urlAfterCommand: this.browser.url
|
|
2239
|
+
};
|
|
2240
|
+
case "AI_ASSERTION" /* AI_ASSERTION */: {
|
|
2241
|
+
return this.executeAssertion(urlBeforeCommand, command);
|
|
2242
|
+
}
|
|
2243
|
+
case "NAVIGATE" /* NAVIGATE */:
|
|
2244
|
+
yield this.browser.navigate(command.url);
|
|
2245
|
+
break;
|
|
2246
|
+
case "GO_BACK" /* GO_BACK */:
|
|
2247
|
+
yield this.browser.goBack();
|
|
2248
|
+
break;
|
|
2249
|
+
case "GO_FORWARD" /* GO_FORWARD */:
|
|
2250
|
+
yield this.browser.goForward();
|
|
2251
|
+
break;
|
|
2252
|
+
case "SCROLL_DOWN" /* SCROLL_DOWN */:
|
|
2253
|
+
yield this.browser.scrollDown();
|
|
2254
|
+
break;
|
|
2255
|
+
case "SCROLL_UP" /* SCROLL_UP */:
|
|
2256
|
+
yield this.browser.scrollUp();
|
|
2257
|
+
break;
|
|
2258
|
+
case "WAIT" /* WAIT */:
|
|
2259
|
+
yield this.browser.wait(command.delay * 1e3);
|
|
2260
|
+
break;
|
|
2261
|
+
case "REFRESH" /* REFRESH */:
|
|
2262
|
+
yield this.browser.refresh();
|
|
2263
|
+
break;
|
|
2264
|
+
case "CLICK" /* CLICK */: {
|
|
2265
|
+
let id;
|
|
2266
|
+
if (command.target.a11yData) {
|
|
2267
|
+
id = (_b = command.target.a11yData) == null ? void 0 : _b.id;
|
|
2268
|
+
} else {
|
|
2269
|
+
const locator = yield this.locateElement(
|
|
2270
|
+
command.target.elementDescriptor,
|
|
2271
|
+
disableCache
|
|
2272
|
+
);
|
|
2273
|
+
id = locator.id;
|
|
2274
|
+
}
|
|
2275
|
+
const elementInteracted = yield this.browser.click(
|
|
2276
|
+
{
|
|
2277
|
+
id
|
|
2278
|
+
},
|
|
2279
|
+
{
|
|
2280
|
+
doubleClick: command.doubleClick,
|
|
2281
|
+
rightClick: command.rightClick
|
|
2282
|
+
}
|
|
2283
|
+
);
|
|
2284
|
+
const result2 = {
|
|
2285
|
+
urlAfterCommand: this.browser.url,
|
|
2286
|
+
succeedImmediately: false,
|
|
2287
|
+
elementInteracted
|
|
2288
|
+
};
|
|
2289
|
+
if (urlChanged(urlBeforeCommand, result2.urlAfterCommand)) {
|
|
2290
|
+
result2.succeedImmediately = true;
|
|
2291
|
+
result2.succeedImmediatelyReason = "URL changed";
|
|
2292
|
+
}
|
|
2293
|
+
return result2;
|
|
2294
|
+
}
|
|
2295
|
+
case "SELECT_OPTION" /* SELECT_OPTION */: {
|
|
2296
|
+
let id;
|
|
2297
|
+
if (command.target.a11yData) {
|
|
2298
|
+
id = (_c = command.target.a11yData) == null ? void 0 : _c.id;
|
|
2299
|
+
} else {
|
|
2300
|
+
const locator = yield this.locateElement(
|
|
2301
|
+
command.target.elementDescriptor,
|
|
2302
|
+
disableCache
|
|
2303
|
+
);
|
|
2304
|
+
id = locator.id;
|
|
2305
|
+
}
|
|
2306
|
+
const elementInteracted = yield this.browser.selectOption(
|
|
2307
|
+
{
|
|
2308
|
+
id
|
|
2309
|
+
},
|
|
2310
|
+
command.option
|
|
2311
|
+
);
|
|
2312
|
+
return {
|
|
2313
|
+
succeedImmediately: false,
|
|
2314
|
+
urlAfterCommand: this.browser.url,
|
|
2315
|
+
elementInteracted
|
|
2316
|
+
};
|
|
2317
|
+
}
|
|
2318
|
+
case "TAB" /* TAB */:
|
|
2319
|
+
yield this.browser.switchToPage(command.url);
|
|
2320
|
+
break;
|
|
2321
|
+
case "COOKIE" /* COOKIE */:
|
|
2322
|
+
yield this.browser.setCookie(command.value);
|
|
2323
|
+
break;
|
|
2324
|
+
case "TYPE" /* TYPE */: {
|
|
2325
|
+
let elementInteracted;
|
|
2326
|
+
const target = command.target;
|
|
2327
|
+
if (target.a11yData) {
|
|
2328
|
+
elementInteracted = yield this.browser.click({
|
|
2329
|
+
id: target.a11yData.id
|
|
2330
|
+
});
|
|
2331
|
+
} else if (target.elementDescriptor.length > 0) {
|
|
2332
|
+
const locator = yield this.locateElement(
|
|
2333
|
+
command.target.elementDescriptor,
|
|
2334
|
+
disableCache
|
|
2335
|
+
);
|
|
2336
|
+
elementInteracted = yield this.browser.click({
|
|
2337
|
+
id: locator.id
|
|
2338
|
+
});
|
|
2339
|
+
}
|
|
2340
|
+
yield this.browser.type(command.value, {
|
|
2341
|
+
clearContent: command.clearContent,
|
|
2342
|
+
pressKeysSequentially: command.pressKeysSequentially
|
|
2343
|
+
});
|
|
2344
|
+
if (command.pressEnter) {
|
|
2345
|
+
yield this.browser.press("Enter");
|
|
2346
|
+
}
|
|
2347
|
+
const result2 = {
|
|
2348
|
+
urlAfterCommand: this.browser.url,
|
|
2349
|
+
succeedImmediately: false,
|
|
2350
|
+
elementInteracted
|
|
2351
|
+
};
|
|
2352
|
+
if (urlChanged(urlBeforeCommand, result2.urlAfterCommand)) {
|
|
2353
|
+
result2.succeedImmediately = true;
|
|
2354
|
+
result2.succeedImmediatelyReason = "URL changed";
|
|
2355
|
+
}
|
|
2356
|
+
return result2;
|
|
2357
|
+
}
|
|
2358
|
+
case "PRESS" /* PRESS */:
|
|
2359
|
+
yield this.browser.press(command.value);
|
|
2360
|
+
const result = {
|
|
2361
|
+
urlAfterCommand: this.browser.url,
|
|
2362
|
+
succeedImmediately: false
|
|
2363
|
+
};
|
|
2364
|
+
if (urlChanged(urlBeforeCommand, result.urlAfterCommand)) {
|
|
2365
|
+
result.succeedImmediately = true;
|
|
2366
|
+
result.succeedImmediatelyReason = "URL changed";
|
|
2367
|
+
}
|
|
2368
|
+
return result;
|
|
2369
|
+
default:
|
|
2370
|
+
const assertUnreachable = (_x) => {
|
|
2371
|
+
throw "If Typescript complains about the line below, you missed a case or break in the switch above";
|
|
2372
|
+
};
|
|
2373
|
+
return assertUnreachable(command);
|
|
2374
|
+
}
|
|
2375
|
+
return {
|
|
2376
|
+
succeedImmediately: false,
|
|
2377
|
+
urlAfterCommand: this.browser.url
|
|
2378
|
+
};
|
|
2379
|
+
});
|
|
2380
|
+
}
|
|
2381
|
+
};
|
|
2382
|
+
|
|
2383
|
+
// ../../packages/web-agent/src/generators/api-generator.ts
|
|
2384
|
+
var import_fetch_retry = __toESM(require("fetch-retry"), 1);
|
|
2385
|
+
var fetch2 = (0, import_fetch_retry.default)(global.fetch);
|
|
2386
|
+
var API_VERSION = "v1";
|
|
2387
|
+
var APIGenerator = class {
|
|
2388
|
+
constructor(params) {
|
|
2389
|
+
this.baseURL = params.baseURL;
|
|
2390
|
+
this.apiKey = params.apiKey;
|
|
2391
|
+
}
|
|
2392
|
+
getElementLocation(context, disableCache) {
|
|
2393
|
+
return __async(this, null, function* () {
|
|
2394
|
+
const result = yield this.sendRequest(
|
|
2395
|
+
`/${API_VERSION}/web-agent/locate-element`,
|
|
2396
|
+
{
|
|
2397
|
+
browserState: context.browserState,
|
|
2398
|
+
goal: context.goal,
|
|
2399
|
+
disableCache
|
|
2400
|
+
}
|
|
2401
|
+
);
|
|
2402
|
+
return LocateResponseSchema.parse(result);
|
|
2403
|
+
});
|
|
2404
|
+
}
|
|
2405
|
+
getAssertionResult(context, useVision, disableCache) {
|
|
2406
|
+
return __async(this, null, function* () {
|
|
2407
|
+
var _a;
|
|
2408
|
+
if (useVision) {
|
|
2409
|
+
const result2 = yield this.sendRequest(
|
|
2410
|
+
`/${API_VERSION}/web-agent/assertion`,
|
|
2411
|
+
{
|
|
2412
|
+
url: context.url,
|
|
2413
|
+
goal: context.goal,
|
|
2414
|
+
screenshot: (_a = context.screenshot) == null ? void 0 : _a.toString("base64"),
|
|
2415
|
+
disableCache,
|
|
2416
|
+
vision: true
|
|
2417
|
+
}
|
|
2418
|
+
);
|
|
2419
|
+
return GetAssertionResponseSchema.parse(result2);
|
|
2420
|
+
}
|
|
2421
|
+
const result = yield this.sendRequest(
|
|
2422
|
+
`/${API_VERSION}/web-agent/assertion`,
|
|
2423
|
+
{
|
|
2424
|
+
url: context.url,
|
|
2425
|
+
browserState: context.browserState,
|
|
2426
|
+
goal: context.goal,
|
|
2427
|
+
history: context.history,
|
|
2428
|
+
numPrevious: context.numPrevious,
|
|
2429
|
+
lastCommand: context.lastCommand,
|
|
2430
|
+
disableCache,
|
|
2431
|
+
vision: false
|
|
2432
|
+
}
|
|
2433
|
+
);
|
|
2434
|
+
return GetAssertionResponseSchema.parse(result);
|
|
2435
|
+
});
|
|
2436
|
+
}
|
|
2437
|
+
getProposedCommand(context, disableCache) {
|
|
2438
|
+
return __async(this, null, function* () {
|
|
2439
|
+
const result = yield this.sendRequest(
|
|
2440
|
+
`/${API_VERSION}/web-agent/next-command`,
|
|
2441
|
+
{
|
|
2442
|
+
url: context.url,
|
|
2443
|
+
browserState: context.browserState,
|
|
2444
|
+
goal: context.goal,
|
|
2445
|
+
history: context.history,
|
|
2446
|
+
numPrevious: context.numPrevious,
|
|
2447
|
+
lastCommand: context.lastCommand,
|
|
2448
|
+
disableCache
|
|
2449
|
+
}
|
|
2450
|
+
);
|
|
2451
|
+
return GetNextCommandResponseSchema.parse(result);
|
|
2452
|
+
});
|
|
2453
|
+
}
|
|
2454
|
+
getGranularGoals(context, disableCache) {
|
|
2455
|
+
return __async(this, null, function* () {
|
|
2456
|
+
const result = yield this.sendRequest(
|
|
2457
|
+
`/${API_VERSION}/web-agent/split-goal`,
|
|
2458
|
+
{
|
|
2459
|
+
url: context.url,
|
|
2460
|
+
goal: context.goal,
|
|
2461
|
+
disableCache
|
|
2462
|
+
}
|
|
2463
|
+
);
|
|
2464
|
+
return SplitGoalResponseSchema.parse(result);
|
|
2465
|
+
});
|
|
2466
|
+
}
|
|
2467
|
+
sendRequest(path, body) {
|
|
2468
|
+
return __async(this, null, function* () {
|
|
2469
|
+
const response = yield fetch2(`${this.baseURL}${path}`, {
|
|
2470
|
+
retries: 3,
|
|
2471
|
+
retryDelay: 1e3,
|
|
2472
|
+
method: "POST",
|
|
2473
|
+
body: JSON.stringify(body),
|
|
2474
|
+
headers: {
|
|
2475
|
+
"Content-Type": "application/json",
|
|
2476
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
2477
|
+
}
|
|
2478
|
+
});
|
|
2479
|
+
if (!response.ok) {
|
|
2480
|
+
throw new Error(
|
|
2481
|
+
`Request to ${path} failed with status ${response.status}: ${yield response.text()}`
|
|
2482
|
+
);
|
|
2483
|
+
}
|
|
2484
|
+
return response.json();
|
|
2485
|
+
});
|
|
2486
|
+
}
|
|
2487
|
+
};
|
|
2488
|
+
|
|
2489
|
+
// package.json
|
|
2490
|
+
var version = "1.0.0";
|
|
2491
|
+
|
|
2492
|
+
// src/api-client.ts
|
|
2493
|
+
var API_VERSION2 = "v1";
|
|
2494
|
+
var APIClient = class {
|
|
2495
|
+
constructor(params) {
|
|
2496
|
+
this.baseURL = params.baseURL;
|
|
2497
|
+
this.apiKey = params.apiKey;
|
|
2498
|
+
}
|
|
2499
|
+
getRun(runId) {
|
|
2500
|
+
return __async(this, null, function* () {
|
|
2501
|
+
const result = yield this.sendRequest(`/${API_VERSION2}/runs/${runId}`, {
|
|
2502
|
+
method: "GET"
|
|
2503
|
+
});
|
|
2504
|
+
return GetRunResponseSchema.parse(result);
|
|
2505
|
+
});
|
|
2506
|
+
}
|
|
2507
|
+
createRun(body) {
|
|
2508
|
+
return __async(this, null, function* () {
|
|
2509
|
+
const result = yield this.sendRequest(`/${API_VERSION2}/runs`, {
|
|
2510
|
+
method: "POST",
|
|
2511
|
+
body
|
|
2512
|
+
});
|
|
2513
|
+
return CreateRunResponseSchema.parse(result);
|
|
2514
|
+
});
|
|
2515
|
+
}
|
|
2516
|
+
updateRun(runId, body) {
|
|
2517
|
+
return __async(this, null, function* () {
|
|
2518
|
+
yield this.sendRequest(`/${API_VERSION2}/runs/${runId}`, {
|
|
2519
|
+
method: "PATCH",
|
|
2520
|
+
body
|
|
2521
|
+
});
|
|
2522
|
+
});
|
|
2523
|
+
}
|
|
2524
|
+
getTest(testId) {
|
|
2525
|
+
return __async(this, null, function* () {
|
|
2526
|
+
const result = yield this.sendRequest(`/${API_VERSION2}/tests/${testId}`, {
|
|
2527
|
+
method: "GET"
|
|
2528
|
+
});
|
|
2529
|
+
return GetTestResponseSchema.parse(result);
|
|
2530
|
+
});
|
|
2531
|
+
}
|
|
2532
|
+
uploadScreenshot(body) {
|
|
2533
|
+
return __async(this, null, function* () {
|
|
2534
|
+
const result = yield this.sendRequest(`/${API_VERSION2}/screenshots`, {
|
|
2535
|
+
method: "POST",
|
|
2536
|
+
body
|
|
2537
|
+
});
|
|
2538
|
+
return CreateScreenshotResponseSchema.parse(result);
|
|
2539
|
+
});
|
|
2540
|
+
}
|
|
2541
|
+
sendRequest(path, options) {
|
|
2542
|
+
return __async(this, null, function* () {
|
|
2543
|
+
const response = yield fetch(`${this.baseURL}${path}`, {
|
|
2544
|
+
method: options.method,
|
|
2545
|
+
body: options.body ? JSON.stringify(options.body) : void 0,
|
|
2546
|
+
headers: {
|
|
2547
|
+
"Content-Type": "application/json",
|
|
2548
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
2549
|
+
}
|
|
2550
|
+
});
|
|
2551
|
+
if (!response.ok) {
|
|
2552
|
+
throw new Error(
|
|
2553
|
+
`Request to ${path} failed with status ${response.status}: ${yield response.text()}`
|
|
2554
|
+
);
|
|
2555
|
+
}
|
|
2556
|
+
if (response.status === 204) {
|
|
2557
|
+
return response.text();
|
|
2558
|
+
}
|
|
2559
|
+
return response.json();
|
|
2560
|
+
});
|
|
2561
|
+
}
|
|
2562
|
+
};
|
|
2563
|
+
|
|
2564
|
+
// ../../packages/execute/src/constants.ts
|
|
2565
|
+
var MAX_COMMANDS_PER_STEP = 20;
|
|
2566
|
+
|
|
2567
|
+
// ../../packages/execute/src/steps/ai.ts
|
|
2568
|
+
var executeAIStep = (_a) => __async(void 0, null, function* () {
|
|
2569
|
+
var _b = _a, {
|
|
2570
|
+
controller,
|
|
2571
|
+
step,
|
|
2572
|
+
logger,
|
|
2573
|
+
advanced
|
|
2574
|
+
} = _b, callbacks = __objRest(_b, [
|
|
2575
|
+
"controller",
|
|
2576
|
+
"step",
|
|
2577
|
+
"logger",
|
|
2578
|
+
"advanced"
|
|
2579
|
+
]);
|
|
2580
|
+
var _a2, _b2, _c, _d, _e, _f, _g;
|
|
2581
|
+
(_a2 = callbacks.onStarted) == null ? void 0 : _a2.call(callbacks);
|
|
2582
|
+
controller.resetHistory();
|
|
2583
|
+
const result = __spreadProps(__spreadValues({}, step), {
|
|
2584
|
+
startedAt: /* @__PURE__ */ new Date(),
|
|
2585
|
+
userAgent: ChromeBrowser.USER_AGENT,
|
|
2586
|
+
// placeholder values
|
|
2587
|
+
finishedAt: /* @__PURE__ */ new Date(),
|
|
2588
|
+
results: [],
|
|
2589
|
+
status: "SUCCESS" /* SUCCESS */
|
|
2590
|
+
});
|
|
2591
|
+
try {
|
|
2592
|
+
let commandIndex = 0;
|
|
2593
|
+
let useSavedCommands = step.commands && step.commands.length > 0;
|
|
2594
|
+
while (true) {
|
|
2595
|
+
if (commandIndex > MAX_COMMANDS_PER_STEP) {
|
|
2596
|
+
throw new Error(
|
|
2597
|
+
`Exceeded max number of commands per step (${MAX_COMMANDS_PER_STEP})`
|
|
2598
|
+
);
|
|
2599
|
+
}
|
|
2600
|
+
let command;
|
|
2601
|
+
const startedAt = /* @__PURE__ */ new Date();
|
|
2602
|
+
const beforeScreenshotBuffer = yield controller.browser.screenshot();
|
|
2603
|
+
const beforeScreenshot = yield callbacks.onSaveScreenshot(
|
|
2604
|
+
beforeScreenshotBuffer
|
|
2605
|
+
);
|
|
2606
|
+
if (useSavedCommands) {
|
|
2607
|
+
command = step.commands[commandIndex];
|
|
2608
|
+
if (!command) {
|
|
2609
|
+
throw new Error(
|
|
2610
|
+
`Saved command at index ${commandIndex} is undefined.`
|
|
2611
|
+
);
|
|
2612
|
+
}
|
|
2613
|
+
} else {
|
|
2614
|
+
command = yield controller.promptToCommand(
|
|
2615
|
+
step.type,
|
|
2616
|
+
step.text,
|
|
2617
|
+
advanced.disableAICaching
|
|
2618
|
+
);
|
|
2619
|
+
}
|
|
2620
|
+
if (command.type === "FAILURE") {
|
|
2621
|
+
result.finishedAt = /* @__PURE__ */ new Date();
|
|
2622
|
+
result.status = "FAILED" /* FAILED */;
|
|
2623
|
+
result.message = command.thoughts;
|
|
2624
|
+
break;
|
|
2625
|
+
}
|
|
2626
|
+
(_b2 = callbacks.onCommandGenerated) == null ? void 0 : _b2.call(callbacks, {
|
|
2627
|
+
commandIndex,
|
|
2628
|
+
message: CARD_DISPLAY_NAMES[command.type] || `Unknown command (${command.type})`
|
|
2629
|
+
});
|
|
2630
|
+
const cmdResult = {
|
|
2631
|
+
beforeScreenshot,
|
|
2632
|
+
beforeUrl: controller.browser.url,
|
|
2633
|
+
startedAt,
|
|
2634
|
+
viewport: controller.browser.viewport,
|
|
2635
|
+
// placeholder values
|
|
2636
|
+
finishedAt: /* @__PURE__ */ new Date(),
|
|
2637
|
+
status: "SUCCESS" /* SUCCESS */
|
|
2638
|
+
};
|
|
2639
|
+
logger.info(
|
|
2640
|
+
`Executing command ${commandIndex}: ${serializeCommand(command)}`
|
|
2641
|
+
);
|
|
2642
|
+
try {
|
|
2643
|
+
const executionResult = yield controller.executeCommand(
|
|
2644
|
+
command,
|
|
2645
|
+
advanced.disableAICaching,
|
|
2646
|
+
useSavedCommands
|
|
2647
|
+
);
|
|
2648
|
+
cmdResult.elementInteracted = executionResult.elementInteracted;
|
|
2649
|
+
(_c = callbacks.onCommandExecuted) == null ? void 0 : _c.call(callbacks, {
|
|
2650
|
+
commandIndex,
|
|
2651
|
+
message: serializeCommand(command),
|
|
2652
|
+
command
|
|
2653
|
+
});
|
|
2654
|
+
const afterScreenshotBuffer = yield controller.browser.screenshot();
|
|
2655
|
+
const afterScreenshot = yield callbacks.onSaveScreenshot(
|
|
2656
|
+
afterScreenshotBuffer
|
|
2657
|
+
);
|
|
2658
|
+
cmdResult.afterScreenshot = afterScreenshot;
|
|
2659
|
+
cmdResult.afterUrl = controller.browser.url;
|
|
2660
|
+
cmdResult.finishedAt = /* @__PURE__ */ new Date();
|
|
2661
|
+
const presetActionResult = {
|
|
2662
|
+
status: "SUCCESS" /* SUCCESS */,
|
|
2663
|
+
startedAt: cmdResult.startedAt,
|
|
2664
|
+
finishedAt: cmdResult.finishedAt,
|
|
2665
|
+
type: "PRESET_ACTION" /* PRESET_ACTION */,
|
|
2666
|
+
command,
|
|
2667
|
+
results: [cmdResult]
|
|
2668
|
+
};
|
|
2669
|
+
result.results.push(presetActionResult);
|
|
2670
|
+
if (command.type === "SUCCESS" /* SUCCESS */) {
|
|
2671
|
+
result.finishedAt = /* @__PURE__ */ new Date();
|
|
2672
|
+
result.status = "SUCCESS" /* SUCCESS */;
|
|
2673
|
+
result.message = (_d = executionResult.thoughts) != null ? _d : "All commands completed.";
|
|
2674
|
+
break;
|
|
2675
|
+
}
|
|
2676
|
+
if (executionResult.succeedImmediately && !useSavedCommands) {
|
|
2677
|
+
result.finishedAt = /* @__PURE__ */ new Date();
|
|
2678
|
+
result.status = "SUCCESS" /* SUCCESS */;
|
|
2679
|
+
result.message = executionResult.succeedImmediatelyReason;
|
|
2680
|
+
command = {
|
|
2681
|
+
type: "SUCCESS" /* SUCCESS */
|
|
2682
|
+
};
|
|
2683
|
+
(_e = callbacks.onCommandExecuted) == null ? void 0 : _e.call(callbacks, {
|
|
2684
|
+
commandIndex: commandIndex + 1,
|
|
2685
|
+
message: serializeCommand(command),
|
|
2686
|
+
command
|
|
2687
|
+
});
|
|
2688
|
+
result.results.push(__spreadProps(__spreadValues({}, presetActionResult), {
|
|
2689
|
+
command
|
|
2690
|
+
}));
|
|
2691
|
+
break;
|
|
2692
|
+
}
|
|
2693
|
+
} catch (err) {
|
|
2694
|
+
if (useSavedCommands) {
|
|
2695
|
+
useSavedCommands = false;
|
|
2696
|
+
commandIndex = 0;
|
|
2697
|
+
result.results = [];
|
|
2698
|
+
continue;
|
|
2699
|
+
}
|
|
2700
|
+
cmdResult.status = "FAILED" /* FAILED */;
|
|
2701
|
+
cmdResult.message = `${err}`;
|
|
2702
|
+
cmdResult.finishedAt = /* @__PURE__ */ new Date();
|
|
2703
|
+
cmdResult.afterScreenshot = void 0;
|
|
2704
|
+
cmdResult.afterUrl = controller.browser.url;
|
|
2705
|
+
result.results.push({
|
|
2706
|
+
status: "FAILED" /* FAILED */,
|
|
2707
|
+
startedAt: cmdResult.startedAt,
|
|
2708
|
+
finishedAt: cmdResult.finishedAt,
|
|
2709
|
+
type: "PRESET_ACTION" /* PRESET_ACTION */,
|
|
2710
|
+
command,
|
|
2711
|
+
results: [cmdResult],
|
|
2712
|
+
message: `${err}`
|
|
2713
|
+
});
|
|
2714
|
+
result.status = "FAILED" /* FAILED */;
|
|
2715
|
+
result.finishedAt = /* @__PURE__ */ new Date();
|
|
2716
|
+
result.message = `${err}`;
|
|
2717
|
+
break;
|
|
2718
|
+
}
|
|
2719
|
+
commandIndex++;
|
|
2720
|
+
}
|
|
2721
|
+
} catch (err) {
|
|
2722
|
+
result.message = `${err}`;
|
|
2723
|
+
result.finishedAt = /* @__PURE__ */ new Date();
|
|
2724
|
+
result.status = "FAILED" /* FAILED */;
|
|
2725
|
+
}
|
|
2726
|
+
if (result.status === "SUCCESS" /* SUCCESS */) {
|
|
2727
|
+
(_f = callbacks.onSuccess) == null ? void 0 : _f.call(callbacks, {
|
|
2728
|
+
message: result.message || "AI step succeeded.",
|
|
2729
|
+
startedAt: result.startedAt.getTime(),
|
|
2730
|
+
durationMs: result.finishedAt.getTime() - result.startedAt.getTime()
|
|
2731
|
+
});
|
|
2732
|
+
} else {
|
|
2733
|
+
(_g = callbacks.onFailure) == null ? void 0 : _g.call(callbacks, {
|
|
2734
|
+
message: result.message || "AI step errored.",
|
|
2735
|
+
startedAt: result.startedAt.getTime(),
|
|
2736
|
+
durationMs: result.finishedAt.getTime() - result.startedAt.getTime()
|
|
2737
|
+
});
|
|
2738
|
+
}
|
|
2739
|
+
return result;
|
|
2740
|
+
});
|
|
2741
|
+
|
|
2742
|
+
// ../../packages/execute/src/steps/preset.ts
|
|
2743
|
+
var executePresetStep = (_a) => __async(void 0, null, function* () {
|
|
2744
|
+
var _b = _a, {
|
|
2745
|
+
controller,
|
|
2746
|
+
step,
|
|
2747
|
+
advanced
|
|
2748
|
+
} = _b, callbacks = __objRest(_b, [
|
|
2749
|
+
"controller",
|
|
2750
|
+
"step",
|
|
2751
|
+
"advanced"
|
|
2752
|
+
]);
|
|
2753
|
+
var _a2, _b2, _c;
|
|
2754
|
+
(_a2 = callbacks.onStarted) == null ? void 0 : _a2.call(callbacks);
|
|
2755
|
+
const startedAt = /* @__PURE__ */ new Date();
|
|
2756
|
+
const beforeUrl = controller.browser.url;
|
|
2757
|
+
const beforeScreenshotBuffer = yield controller.browser.screenshot();
|
|
2758
|
+
const beforeScreenshot = yield callbacks.onSaveScreenshot(
|
|
2759
|
+
beforeScreenshotBuffer
|
|
2760
|
+
);
|
|
2761
|
+
try {
|
|
2762
|
+
const execResult = yield controller.executePresetStep(
|
|
2763
|
+
step.command,
|
|
2764
|
+
advanced.disableAICaching
|
|
2765
|
+
);
|
|
2766
|
+
const afterScreenshotBuffer = yield controller.browser.screenshot();
|
|
2767
|
+
const afterScreenshot = yield callbacks.onSaveScreenshot(
|
|
2768
|
+
afterScreenshotBuffer
|
|
2769
|
+
);
|
|
2770
|
+
const finishedAt = /* @__PURE__ */ new Date();
|
|
2771
|
+
const result = __spreadProps(__spreadValues({}, step), {
|
|
2772
|
+
startedAt,
|
|
2773
|
+
finishedAt,
|
|
2774
|
+
// placeholder values
|
|
2775
|
+
status: "SUCCESS" /* SUCCESS */,
|
|
2776
|
+
results: []
|
|
2777
|
+
});
|
|
2778
|
+
let message = "Successfully executed preset action.";
|
|
2779
|
+
if (step.command.type === "AI_ASSERTION" /* AI_ASSERTION */) {
|
|
2780
|
+
message = execResult.thoughts || "Assertion passed.";
|
|
2781
|
+
}
|
|
2782
|
+
const cmdMetadata = {
|
|
2783
|
+
beforeUrl,
|
|
2784
|
+
beforeScreenshot,
|
|
2785
|
+
afterUrl: controller.browser.url,
|
|
2786
|
+
afterScreenshot,
|
|
2787
|
+
startedAt,
|
|
2788
|
+
finishedAt,
|
|
2789
|
+
viewport: controller.browser.viewport,
|
|
2790
|
+
status: "SUCCESS" /* SUCCESS */
|
|
2791
|
+
};
|
|
2792
|
+
result.status = "SUCCESS" /* SUCCESS */;
|
|
2793
|
+
result.results = [cmdMetadata];
|
|
2794
|
+
result.message = message;
|
|
2795
|
+
(_b2 = callbacks.onSuccess) == null ? void 0 : _b2.call(callbacks, {
|
|
2796
|
+
message,
|
|
2797
|
+
startedAt: startedAt.getTime(),
|
|
2798
|
+
durationMs: finishedAt.getTime() - startedAt.getTime()
|
|
2799
|
+
});
|
|
2800
|
+
return result;
|
|
2801
|
+
} catch (err) {
|
|
2802
|
+
const finishedAt = /* @__PURE__ */ new Date();
|
|
2803
|
+
const result = __spreadProps(__spreadValues({}, step), {
|
|
2804
|
+
startedAt,
|
|
2805
|
+
finishedAt,
|
|
2806
|
+
status: "FAILED" /* FAILED */,
|
|
2807
|
+
message: `${err}`,
|
|
2808
|
+
results: [
|
|
2809
|
+
{
|
|
2810
|
+
beforeUrl,
|
|
2811
|
+
beforeScreenshot,
|
|
2812
|
+
afterUrl: controller.browser.url,
|
|
2813
|
+
afterScreenshot: void 0,
|
|
2814
|
+
startedAt,
|
|
2815
|
+
finishedAt,
|
|
2816
|
+
viewport: controller.browser.viewport,
|
|
2817
|
+
status: "FAILED" /* FAILED */,
|
|
2818
|
+
message: `${err}`
|
|
2819
|
+
}
|
|
2820
|
+
]
|
|
2821
|
+
});
|
|
2822
|
+
(_c = callbacks.onFailure) == null ? void 0 : _c.call(callbacks, {
|
|
2823
|
+
message: `${err}`,
|
|
2824
|
+
startedAt: startedAt.getTime(),
|
|
2825
|
+
durationMs: finishedAt.getTime() - startedAt.getTime()
|
|
2826
|
+
});
|
|
2827
|
+
return result;
|
|
2828
|
+
}
|
|
2829
|
+
});
|
|
2830
|
+
|
|
2831
|
+
// ../../packages/execute/src/steps/module.ts
|
|
2832
|
+
var executeModuleStep = (_a) => __async(void 0, null, function* () {
|
|
2833
|
+
var _b = _a, {
|
|
2834
|
+
controller,
|
|
2835
|
+
step,
|
|
2836
|
+
advanced,
|
|
2837
|
+
logger
|
|
2838
|
+
} = _b, callbacks = __objRest(_b, [
|
|
2839
|
+
"controller",
|
|
2840
|
+
"step",
|
|
2841
|
+
"advanced",
|
|
2842
|
+
"logger"
|
|
2843
|
+
]);
|
|
2844
|
+
var _a2, _b2, _c;
|
|
2845
|
+
(_a2 = callbacks.onStarted) == null ? void 0 : _a2.call(callbacks);
|
|
2846
|
+
const result = {
|
|
2847
|
+
type: "MODULE" /* MODULE */,
|
|
2848
|
+
moduleId: step.moduleId,
|
|
2849
|
+
startedAt: /* @__PURE__ */ new Date(),
|
|
2850
|
+
userAgent: ChromeBrowser.USER_AGENT,
|
|
2851
|
+
// placeholder values
|
|
2852
|
+
results: [],
|
|
2853
|
+
finishedAt: /* @__PURE__ */ new Date(),
|
|
2854
|
+
status: "SUCCESS" /* SUCCESS */
|
|
2855
|
+
};
|
|
2856
|
+
for (let i = 0; i < step.steps.length; i++) {
|
|
2857
|
+
const moduleStep = step.steps[i];
|
|
2858
|
+
logger.info({ i, moduleStep }, `Starting module step`);
|
|
2859
|
+
let moduleStepResult;
|
|
2860
|
+
switch (moduleStep.type) {
|
|
2861
|
+
case "PRESET_ACTION" /* PRESET_ACTION */:
|
|
2862
|
+
moduleStepResult = yield executePresetStep({
|
|
2863
|
+
controller,
|
|
2864
|
+
step: moduleStep,
|
|
2865
|
+
advanced,
|
|
2866
|
+
logger,
|
|
2867
|
+
onSaveScreenshot: callbacks.onSaveScreenshot,
|
|
2868
|
+
onStarted() {
|
|
2869
|
+
var _a3;
|
|
2870
|
+
(_a3 = callbacks.onStepStarted) == null ? void 0 : _a3.call(callbacks, { index: i });
|
|
2871
|
+
},
|
|
2872
|
+
onSuccess({ message, startedAt, durationMs }) {
|
|
2873
|
+
var _a3;
|
|
2874
|
+
(_a3 = callbacks.onStepSuccess) == null ? void 0 : _a3.call(callbacks, {
|
|
2875
|
+
index: i,
|
|
2876
|
+
message,
|
|
2877
|
+
startedAt,
|
|
2878
|
+
durationMs
|
|
2879
|
+
});
|
|
2880
|
+
},
|
|
2881
|
+
onFailure({ message, startedAt, durationMs }) {
|
|
2882
|
+
var _a3;
|
|
2883
|
+
(_a3 = callbacks.onStepFailure) == null ? void 0 : _a3.call(callbacks, {
|
|
2884
|
+
index: i,
|
|
2885
|
+
message,
|
|
2886
|
+
startedAt,
|
|
2887
|
+
durationMs
|
|
2888
|
+
});
|
|
2889
|
+
}
|
|
2890
|
+
});
|
|
2891
|
+
break;
|
|
2892
|
+
case "AI_ACTION" /* AI_ACTION */:
|
|
2893
|
+
moduleStepResult = yield executeAIStep({
|
|
2894
|
+
controller,
|
|
2895
|
+
step: moduleStep,
|
|
2896
|
+
advanced,
|
|
2897
|
+
logger,
|
|
2898
|
+
onSaveScreenshot: callbacks.onSaveScreenshot,
|
|
2899
|
+
onStarted() {
|
|
2900
|
+
var _a3;
|
|
2901
|
+
(_a3 = callbacks.onStepStarted) == null ? void 0 : _a3.call(callbacks, { index: i });
|
|
2902
|
+
},
|
|
2903
|
+
onSuccess({ message, startedAt, durationMs }) {
|
|
2904
|
+
var _a3;
|
|
2905
|
+
(_a3 = callbacks.onStepSuccess) == null ? void 0 : _a3.call(callbacks, {
|
|
2906
|
+
index: i,
|
|
2907
|
+
message,
|
|
2908
|
+
startedAt,
|
|
2909
|
+
durationMs
|
|
2910
|
+
});
|
|
2911
|
+
},
|
|
2912
|
+
onFailure({ message, startedAt, durationMs }) {
|
|
2913
|
+
var _a3;
|
|
2914
|
+
(_a3 = callbacks.onStepFailure) == null ? void 0 : _a3.call(callbacks, {
|
|
2915
|
+
index: i,
|
|
2916
|
+
message,
|
|
2917
|
+
startedAt,
|
|
2918
|
+
durationMs
|
|
2919
|
+
});
|
|
2920
|
+
},
|
|
2921
|
+
onCommandGenerated({ commandIndex, message }) {
|
|
2922
|
+
var _a3;
|
|
2923
|
+
(_a3 = callbacks.onCommandGenerated) == null ? void 0 : _a3.call(callbacks, { index: i, commandIndex, message });
|
|
2924
|
+
},
|
|
2925
|
+
onCommandExecuted({ commandIndex, message, command }) {
|
|
2926
|
+
var _a3;
|
|
2927
|
+
(_a3 = callbacks.onCommandExecuted) == null ? void 0 : _a3.call(callbacks, {
|
|
2928
|
+
index: i,
|
|
2929
|
+
commandIndex,
|
|
2930
|
+
message,
|
|
2931
|
+
command
|
|
2932
|
+
});
|
|
2933
|
+
}
|
|
2934
|
+
});
|
|
2935
|
+
break;
|
|
2936
|
+
default:
|
|
2937
|
+
const assertUnreachable = (_x) => {
|
|
2938
|
+
throw "If Typescript complains about the line below, you missed a case or break in the switch above";
|
|
2939
|
+
};
|
|
2940
|
+
return assertUnreachable(moduleStep);
|
|
2941
|
+
}
|
|
2942
|
+
result.results.push(moduleStepResult);
|
|
2943
|
+
if (moduleStepResult.status === "FAILED" /* FAILED */) {
|
|
2944
|
+
result.status = "FAILED" /* FAILED */;
|
|
2945
|
+
result.finishedAt = /* @__PURE__ */ new Date();
|
|
2946
|
+
for (let j = i + 1; j < step.steps.length; j++) {
|
|
2947
|
+
const skippedStep = step.steps[j];
|
|
2948
|
+
const skippedResult = __spreadProps(__spreadValues({}, skippedStep), {
|
|
2949
|
+
status: "CANCELLED" /* CANCELLED */,
|
|
2950
|
+
startedAt: /* @__PURE__ */ new Date(),
|
|
2951
|
+
finishedAt: /* @__PURE__ */ new Date(),
|
|
2952
|
+
userAgent: ChromeBrowser.USER_AGENT,
|
|
2953
|
+
results: [],
|
|
2954
|
+
message: "Cancelled due to previous failure."
|
|
2955
|
+
});
|
|
2956
|
+
result.results.push(skippedResult);
|
|
2957
|
+
}
|
|
2958
|
+
break;
|
|
2959
|
+
}
|
|
2960
|
+
}
|
|
2961
|
+
if (result.status === "SUCCESS" /* SUCCESS */) {
|
|
2962
|
+
(_b2 = callbacks.onSuccess) == null ? void 0 : _b2.call(callbacks, {
|
|
2963
|
+
message: "Executed module step.",
|
|
2964
|
+
startedAt: result.startedAt.getTime(),
|
|
2965
|
+
durationMs: result.finishedAt.getTime() - result.startedAt.getTime()
|
|
2966
|
+
});
|
|
2967
|
+
} else {
|
|
2968
|
+
(_c = callbacks.onFailure) == null ? void 0 : _c.call(callbacks, {
|
|
2969
|
+
message: "Failed to execute module step.",
|
|
2970
|
+
startedAt: result.startedAt.getTime(),
|
|
2971
|
+
durationMs: result.finishedAt.getTime() - result.startedAt.getTime()
|
|
2972
|
+
});
|
|
2973
|
+
}
|
|
2974
|
+
return result;
|
|
2975
|
+
});
|
|
2976
|
+
|
|
2977
|
+
// ../../packages/execute/src/test.ts
|
|
2978
|
+
var executeTest = (_0) => __async(void 0, [_0], function* ({
|
|
2979
|
+
test,
|
|
2980
|
+
runId,
|
|
2981
|
+
controller,
|
|
2982
|
+
logger,
|
|
2983
|
+
onUpdateRun,
|
|
2984
|
+
onSaveScreenshot
|
|
2985
|
+
}) {
|
|
2986
|
+
const advanced = TestAdvancedSettingsSchema.parse(test.advanced);
|
|
2987
|
+
logger.info(`Starting run ${runId} for test ${test.id}`);
|
|
2988
|
+
yield onUpdateRun({
|
|
2989
|
+
status: "RUNNING",
|
|
2990
|
+
startedAt: /* @__PURE__ */ new Date()
|
|
2991
|
+
});
|
|
2992
|
+
let failed = false;
|
|
2993
|
+
const results = [];
|
|
2994
|
+
for (let i = 0; i < test.steps.length; i++) {
|
|
2995
|
+
const step = test.steps[i];
|
|
2996
|
+
let result;
|
|
2997
|
+
switch (step.type) {
|
|
2998
|
+
case "PRESET_ACTION" /* PRESET_ACTION */:
|
|
2999
|
+
result = yield executePresetStep({
|
|
3000
|
+
controller,
|
|
3001
|
+
step,
|
|
3002
|
+
advanced,
|
|
3003
|
+
logger,
|
|
3004
|
+
onSaveScreenshot
|
|
3005
|
+
});
|
|
3006
|
+
break;
|
|
3007
|
+
case "AI_ACTION" /* AI_ACTION */:
|
|
3008
|
+
result = yield executeAIStep({
|
|
3009
|
+
controller,
|
|
3010
|
+
step,
|
|
3011
|
+
advanced,
|
|
3012
|
+
logger,
|
|
3013
|
+
onSaveScreenshot
|
|
3014
|
+
});
|
|
3015
|
+
break;
|
|
3016
|
+
case "RESOLVED_MODULE":
|
|
3017
|
+
result = yield executeModuleStep({
|
|
3018
|
+
controller,
|
|
3019
|
+
step,
|
|
3020
|
+
advanced,
|
|
3021
|
+
logger,
|
|
3022
|
+
onSaveScreenshot
|
|
3023
|
+
});
|
|
3024
|
+
break;
|
|
3025
|
+
default:
|
|
3026
|
+
const assertUnreachable = (_x) => {
|
|
3027
|
+
throw "If Typescript complains about the line below, you missed a case or break in the switch above";
|
|
3028
|
+
};
|
|
3029
|
+
return assertUnreachable(step);
|
|
3030
|
+
}
|
|
3031
|
+
results.push(result);
|
|
3032
|
+
yield onUpdateRun({
|
|
3033
|
+
results
|
|
3034
|
+
});
|
|
3035
|
+
if (result.status === "FAILED" /* FAILED */) {
|
|
3036
|
+
failed = true;
|
|
3037
|
+
for (let j = i + 1; j < test.steps.length; j++) {
|
|
3038
|
+
const skippedStep = test.steps[j];
|
|
3039
|
+
if (skippedStep.type === "RESOLVED_MODULE") {
|
|
3040
|
+
const skippedResult = {
|
|
3041
|
+
type: "MODULE" /* MODULE */,
|
|
3042
|
+
moduleId: skippedStep.moduleId,
|
|
3043
|
+
startedAt: /* @__PURE__ */ new Date(),
|
|
3044
|
+
userAgent: ChromeBrowser.USER_AGENT,
|
|
3045
|
+
results: skippedStep.steps.map((s) => {
|
|
3046
|
+
return __spreadProps(__spreadValues({}, s), {
|
|
3047
|
+
status: "CANCELLED" /* CANCELLED */,
|
|
3048
|
+
startedAt: /* @__PURE__ */ new Date(),
|
|
3049
|
+
finishedAt: /* @__PURE__ */ new Date(),
|
|
3050
|
+
userAgent: ChromeBrowser.USER_AGENT,
|
|
3051
|
+
results: []
|
|
3052
|
+
});
|
|
3053
|
+
}),
|
|
3054
|
+
finishedAt: /* @__PURE__ */ new Date(),
|
|
3055
|
+
status: "CANCELLED" /* CANCELLED */
|
|
3056
|
+
};
|
|
3057
|
+
results.push(skippedResult);
|
|
3058
|
+
} else {
|
|
3059
|
+
const skippedResult = __spreadProps(__spreadValues({}, skippedStep), {
|
|
3060
|
+
status: "CANCELLED" /* CANCELLED */,
|
|
3061
|
+
startedAt: /* @__PURE__ */ new Date(),
|
|
3062
|
+
finishedAt: /* @__PURE__ */ new Date(),
|
|
3063
|
+
userAgent: ChromeBrowser.USER_AGENT,
|
|
3064
|
+
results: []
|
|
3065
|
+
});
|
|
3066
|
+
results.push(skippedResult);
|
|
3067
|
+
}
|
|
3068
|
+
}
|
|
3069
|
+
}
|
|
3070
|
+
if (failed) {
|
|
3071
|
+
break;
|
|
3072
|
+
}
|
|
3073
|
+
}
|
|
3074
|
+
yield onUpdateRun({
|
|
3075
|
+
status: failed ? "FAILED" : "PASSED",
|
|
3076
|
+
finishedAt: /* @__PURE__ */ new Date(),
|
|
3077
|
+
results
|
|
3078
|
+
});
|
|
3079
|
+
yield controller.browser.cleanup();
|
|
3080
|
+
return failed;
|
|
3081
|
+
});
|
|
3082
|
+
|
|
3083
|
+
// src/run-test.ts
|
|
3084
|
+
var consoleLogger = {
|
|
3085
|
+
info: console.log,
|
|
3086
|
+
error: console.error,
|
|
3087
|
+
debug: console.debug,
|
|
3088
|
+
warn: console.warn,
|
|
3089
|
+
child: () => consoleLogger,
|
|
3090
|
+
flush: () => {
|
|
3091
|
+
}
|
|
3092
|
+
};
|
|
3093
|
+
function runTest(_0) {
|
|
3094
|
+
return __async(this, arguments, function* ({
|
|
3095
|
+
testId,
|
|
3096
|
+
apiClient,
|
|
3097
|
+
generator
|
|
3098
|
+
}) {
|
|
3099
|
+
const test = yield apiClient.getTest(testId);
|
|
3100
|
+
const browser = yield ChromeBrowser.init(test.baseUrl, consoleLogger);
|
|
3101
|
+
const controller = new AgentController({
|
|
3102
|
+
browser,
|
|
3103
|
+
generator,
|
|
3104
|
+
config: DEFAULT_CONTROLLER_CONFIG,
|
|
3105
|
+
logger: consoleLogger
|
|
3106
|
+
});
|
|
3107
|
+
const run = yield apiClient.createRun({
|
|
3108
|
+
testId
|
|
3109
|
+
});
|
|
3110
|
+
let failed = true;
|
|
3111
|
+
try {
|
|
3112
|
+
failed = yield executeTest({
|
|
3113
|
+
test,
|
|
3114
|
+
runId: run.id,
|
|
3115
|
+
controller,
|
|
3116
|
+
logger: consoleLogger,
|
|
3117
|
+
onSaveScreenshot: (buffer) => __async(this, null, function* () {
|
|
3118
|
+
const { key } = yield apiClient.uploadScreenshot({
|
|
3119
|
+
screenshot: buffer.toString("base64")
|
|
3120
|
+
});
|
|
3121
|
+
return key;
|
|
3122
|
+
}),
|
|
3123
|
+
onUpdateRun: (data) => __async(this, null, function* () {
|
|
3124
|
+
yield apiClient.updateRun(run.id, data);
|
|
3125
|
+
})
|
|
3126
|
+
});
|
|
3127
|
+
} catch (err) {
|
|
3128
|
+
yield apiClient.updateRun(run.id, {
|
|
3129
|
+
status: "FAILED",
|
|
3130
|
+
finishedAt: /* @__PURE__ */ new Date()
|
|
3131
|
+
});
|
|
3132
|
+
}
|
|
3133
|
+
return failed;
|
|
3134
|
+
});
|
|
3135
|
+
}
|
|
3136
|
+
|
|
3137
|
+
// src/cli.ts
|
|
3138
|
+
var program = new import_commander.Command();
|
|
3139
|
+
program.name("momentic").description("Momentic CLI").version(version);
|
|
3140
|
+
program.command("run-tests").addOption(
|
|
3141
|
+
new import_commander.Option(
|
|
3142
|
+
"--tests <tests...>",
|
|
3143
|
+
"specify tests to run"
|
|
3144
|
+
).makeOptionMandatory(true)
|
|
3145
|
+
).addOption(
|
|
3146
|
+
new import_commander.Option(
|
|
3147
|
+
"--start <command>",
|
|
3148
|
+
"specify start command"
|
|
3149
|
+
).makeOptionMandatory(true)
|
|
3150
|
+
).addOption(
|
|
3151
|
+
new import_commander.Option("--wait-on <url>", "specify url to wait on").makeOptionMandatory(
|
|
3152
|
+
true
|
|
3153
|
+
)
|
|
3154
|
+
).addOption(
|
|
3155
|
+
new import_commander.Option(
|
|
3156
|
+
"--wait-on-timeout <timeout>",
|
|
3157
|
+
"specify how long to wait on url"
|
|
3158
|
+
).default(60, "one minute")
|
|
3159
|
+
).addOption(
|
|
3160
|
+
new import_commander.Option("--api-key <key>", "API key for authenticating").env("MOMENTIC_API_KEY").makeOptionMandatory(true)
|
|
3161
|
+
).action((options) => __async(exports, null, function* () {
|
|
3162
|
+
const { tests, start, waitOn, waitOnTimeout, apiKey } = options;
|
|
3163
|
+
console.log({ tests, start, waitOn, waitOnTimeout, apiKey });
|
|
3164
|
+
void (0, import_execa.execa)(start);
|
|
3165
|
+
yield (0, import_wait_on.default)({
|
|
3166
|
+
resources: [waitOn],
|
|
3167
|
+
timeout: waitOnTimeout * 1e3
|
|
3168
|
+
});
|
|
3169
|
+
const apiClient = new APIClient({
|
|
3170
|
+
baseURL: "https://api.momentic.ai",
|
|
3171
|
+
apiKey
|
|
3172
|
+
});
|
|
3173
|
+
const apiGenerator = new APIGenerator({
|
|
3174
|
+
baseURL: "https://api.momentic.ai",
|
|
3175
|
+
apiKey
|
|
3176
|
+
});
|
|
3177
|
+
const promises = tests.map((testId) => {
|
|
3178
|
+
const failed = runTest({
|
|
3179
|
+
testId,
|
|
3180
|
+
apiClient,
|
|
3181
|
+
generator: apiGenerator
|
|
3182
|
+
});
|
|
3183
|
+
return { failed, testId };
|
|
3184
|
+
});
|
|
3185
|
+
const results = yield Promise.all(promises);
|
|
3186
|
+
const failedResults = results.filter((result) => result.failed);
|
|
3187
|
+
if (failedResults.length > 0) {
|
|
3188
|
+
console.log(
|
|
3189
|
+
import_chalk.default.red(
|
|
3190
|
+
`Failed ${failedResults.length} out of ${results.length} tests`
|
|
3191
|
+
)
|
|
3192
|
+
);
|
|
3193
|
+
failedResults.forEach((result) => {
|
|
3194
|
+
console.log(import_chalk.default.red(`- ${result.testId}`));
|
|
3195
|
+
});
|
|
3196
|
+
process.exit(1);
|
|
3197
|
+
}
|
|
3198
|
+
console.log(import_chalk.default.green(`All ${results.length} tests passed!`));
|
|
3199
|
+
}));
|
|
3200
|
+
function main() {
|
|
3201
|
+
return __async(this, null, function* () {
|
|
3202
|
+
yield program.parseAsync(process.argv);
|
|
3203
|
+
});
|
|
3204
|
+
}
|
|
3205
|
+
void main();
|