momentic 0.0.17 → 0.0.18
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 +2060 -710
- package/dist/index.js +853 -361
- package/package.json +8 -2
package/bin/cli.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
3
|
var __defProps = Object.defineProperties;
|
|
4
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
4
|
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
6
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
5
|
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
8
6
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
7
|
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
@@ -32,19 +30,6 @@ var __objRest = (source, exclude) => {
|
|
|
32
30
|
}
|
|
33
31
|
return target;
|
|
34
32
|
};
|
|
35
|
-
var __export = (target, all) => {
|
|
36
|
-
for (var name in all)
|
|
37
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
38
|
-
};
|
|
39
|
-
var __copyProps = (to, from, except, desc) => {
|
|
40
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
41
|
-
for (let key of __getOwnPropNames(from))
|
|
42
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
43
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
44
|
-
}
|
|
45
|
-
return to;
|
|
46
|
-
};
|
|
47
|
-
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
|
|
48
33
|
var __async = (__this, __arguments, generator) => {
|
|
49
34
|
return new Promise((resolve, reject) => {
|
|
50
35
|
var fulfilled = (value) => {
|
|
@@ -67,31 +52,8 @@ var __async = (__this, __arguments, generator) => {
|
|
|
67
52
|
};
|
|
68
53
|
|
|
69
54
|
// src/cli.ts
|
|
70
|
-
import { Command as
|
|
71
|
-
|
|
72
|
-
// package.json
|
|
73
|
-
var version = "1.0.0";
|
|
74
|
-
|
|
75
|
-
// src/install-browsers.ts
|
|
76
|
-
import { registry } from "playwright-core/lib/server";
|
|
77
|
-
function installBrowsers() {
|
|
78
|
-
return __async(this, null, function* () {
|
|
79
|
-
const executables = registry.defaultExecutables();
|
|
80
|
-
yield registry.install(executables, false);
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// src/run-tests-locally.ts
|
|
85
|
-
import exec from "@actions/exec";
|
|
86
|
-
import io from "@actions/io";
|
|
87
|
-
import chalk from "chalk";
|
|
88
|
-
import quote from "quote";
|
|
89
|
-
import parseArgsStringToArgv2 from "string-argv";
|
|
90
|
-
import waitOnFn from "wait-on";
|
|
91
|
-
|
|
92
|
-
// ../../packages/types/src/commands.ts
|
|
93
|
-
import dedent from "dedent";
|
|
94
|
-
import * as z2 from "zod";
|
|
55
|
+
import { Command as Command3, Option as Option2 } from "commander";
|
|
56
|
+
import { existsSync as existsSync4 } from "fs";
|
|
95
57
|
|
|
96
58
|
// ../../packages/types/src/a11y-targets.ts
|
|
97
59
|
import * as z from "zod";
|
|
@@ -102,12 +64,27 @@ var A11yTargetWithCacheSchema = z.object({
|
|
|
102
64
|
// to assist in re-execution
|
|
103
65
|
role: z.string().optional(),
|
|
104
66
|
name: z.string().optional(),
|
|
67
|
+
numChildren: z.number().optional(),
|
|
105
68
|
content: z.string().optional(),
|
|
106
69
|
pathFromRoot: z.string().optional(),
|
|
107
70
|
serializedForm: z.string().optional()
|
|
108
71
|
});
|
|
109
72
|
|
|
73
|
+
// ../../packages/types/src/assertions.ts
|
|
74
|
+
import { z as z2 } from "zod";
|
|
75
|
+
var AIAssertionResultSchema = z2.object({
|
|
76
|
+
thoughts: z2.string(),
|
|
77
|
+
result: z2.boolean(),
|
|
78
|
+
relevantElements: z2.array(z2.number()).optional()
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// ../../packages/types/src/ai-command-generation.ts
|
|
82
|
+
import parseArgsStringToArgv from "string-argv";
|
|
83
|
+
import { z as z4 } from "zod";
|
|
84
|
+
|
|
110
85
|
// ../../packages/types/src/commands.ts
|
|
86
|
+
import dedent from "dedent";
|
|
87
|
+
import * as z3 from "zod";
|
|
111
88
|
var CommandType = /* @__PURE__ */ ((CommandType2) => {
|
|
112
89
|
CommandType2["AI_ASSERTION"] = "AI_ASSERTION";
|
|
113
90
|
CommandType2["CLICK"] = "CLICK";
|
|
@@ -123,130 +100,137 @@ var CommandType = /* @__PURE__ */ ((CommandType2) => {
|
|
|
123
100
|
CommandType2["REFRESH"] = "REFRESH";
|
|
124
101
|
CommandType2["TAB"] = "TAB";
|
|
125
102
|
CommandType2["COOKIE"] = "COOKIE";
|
|
103
|
+
CommandType2["HOVER"] = "HOVER";
|
|
126
104
|
CommandType2["SUCCESS"] = "SUCCESS";
|
|
127
105
|
return CommandType2;
|
|
128
106
|
})(CommandType || {});
|
|
129
|
-
var
|
|
107
|
+
var ElementTargetSchema = z3.object({
|
|
130
108
|
// natural language passed to LLM
|
|
131
|
-
elementDescriptor:
|
|
109
|
+
elementDescriptor: z3.string(),
|
|
132
110
|
// Cached A11y target - when a user creates a preset action, this will not exist
|
|
133
111
|
a11yData: A11yTargetWithCacheSchema.optional()
|
|
134
112
|
});
|
|
135
|
-
var CommonCommandSchema =
|
|
113
|
+
var CommonCommandSchema = z3.object({
|
|
136
114
|
// If the command is suggested by AI, why it did so
|
|
137
|
-
thoughts:
|
|
115
|
+
thoughts: z3.string().optional()
|
|
138
116
|
});
|
|
139
117
|
var NavigateCommandSchema = CommonCommandSchema.merge(
|
|
140
|
-
|
|
141
|
-
type:
|
|
142
|
-
url:
|
|
118
|
+
z3.object({
|
|
119
|
+
type: z3.literal("NAVIGATE" /* NAVIGATE */),
|
|
120
|
+
url: z3.string()
|
|
143
121
|
})
|
|
144
122
|
).describe("NAVIGATE <url> - Go to the specified url");
|
|
145
123
|
var ScrollUpCommandSchema = CommonCommandSchema.merge(
|
|
146
|
-
|
|
147
|
-
type:
|
|
124
|
+
z3.object({
|
|
125
|
+
type: z3.literal("SCROLL_UP" /* SCROLL_UP */)
|
|
148
126
|
})
|
|
149
127
|
).describe("SCROLL_UP - Scroll up one page");
|
|
150
128
|
var ScrollDownCommandSchema = CommonCommandSchema.merge(
|
|
151
|
-
|
|
152
|
-
type:
|
|
129
|
+
z3.object({
|
|
130
|
+
type: z3.literal("SCROLL_DOWN" /* SCROLL_DOWN */)
|
|
153
131
|
})
|
|
154
132
|
).describe("SCROLL_DOWN - Scroll down one page");
|
|
155
133
|
var WaitCommandSchema = CommonCommandSchema.merge(
|
|
156
|
-
|
|
157
|
-
type:
|
|
158
|
-
delay:
|
|
134
|
+
z3.object({
|
|
135
|
+
type: z3.literal("WAIT" /* WAIT */),
|
|
136
|
+
delay: z3.number()
|
|
159
137
|
// seconds
|
|
160
138
|
})
|
|
161
139
|
);
|
|
162
140
|
var RefreshCommandSchema = CommonCommandSchema.merge(
|
|
163
|
-
|
|
164
|
-
type:
|
|
141
|
+
z3.object({
|
|
142
|
+
type: z3.literal("REFRESH" /* REFRESH */)
|
|
165
143
|
})
|
|
166
144
|
);
|
|
167
145
|
var GoBackCommandSchema = CommonCommandSchema.merge(
|
|
168
|
-
|
|
169
|
-
type:
|
|
146
|
+
z3.object({
|
|
147
|
+
type: z3.literal("GO_BACK" /* GO_BACK */)
|
|
170
148
|
})
|
|
171
149
|
);
|
|
172
150
|
var GoForwardCommandSchema = CommonCommandSchema.merge(
|
|
173
|
-
|
|
174
|
-
type:
|
|
151
|
+
z3.object({
|
|
152
|
+
type: z3.literal("GO_FORWARD" /* GO_FORWARD */)
|
|
175
153
|
})
|
|
176
154
|
);
|
|
177
155
|
var ClickCommandSchema = CommonCommandSchema.merge(
|
|
178
|
-
|
|
179
|
-
type:
|
|
180
|
-
target:
|
|
181
|
-
doubleClick:
|
|
182
|
-
rightClick:
|
|
156
|
+
z3.object({
|
|
157
|
+
type: z3.literal("CLICK" /* CLICK */),
|
|
158
|
+
target: ElementTargetSchema,
|
|
159
|
+
doubleClick: z3.boolean().default(false),
|
|
160
|
+
rightClick: z3.boolean().default(false)
|
|
183
161
|
})
|
|
184
162
|
).describe(
|
|
185
163
|
dedent`CLICK <id> - click on the element that has the specified id.
|
|
186
164
|
You are NOT allowed to click on disabled, hidden or StaticText elements.
|
|
187
165
|
Only click on elements on the Current Page.
|
|
188
166
|
Only click on elements with the following tag names: button, input, link, image, generic.
|
|
189
|
-
`.
|
|
167
|
+
`.replaceAll("\n", " ")
|
|
168
|
+
);
|
|
169
|
+
var HoverCommandSchema = CommonCommandSchema.merge(
|
|
170
|
+
z3.object({
|
|
171
|
+
type: z3.literal("HOVER" /* HOVER */),
|
|
172
|
+
target: ElementTargetSchema
|
|
173
|
+
})
|
|
190
174
|
);
|
|
191
175
|
var SelectOptionCommandSchema = CommonCommandSchema.merge(
|
|
192
|
-
|
|
193
|
-
type:
|
|
194
|
-
target:
|
|
195
|
-
option:
|
|
176
|
+
z3.object({
|
|
177
|
+
type: z3.literal("SELECT_OPTION" /* SELECT_OPTION */),
|
|
178
|
+
target: ElementTargetSchema,
|
|
179
|
+
option: z3.string()
|
|
196
180
|
})
|
|
197
181
|
).describe(
|
|
198
182
|
// TODO: if we move to a non-mutative way of selecting elements (e.g. by selector), we should update this description
|
|
199
183
|
`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.`
|
|
200
184
|
);
|
|
201
185
|
var AIAssertionCommandSchema = CommonCommandSchema.merge(
|
|
202
|
-
|
|
203
|
-
type:
|
|
204
|
-
assertion:
|
|
205
|
-
useVision:
|
|
206
|
-
disableCache:
|
|
186
|
+
z3.object({
|
|
187
|
+
type: z3.literal("AI_ASSERTION" /* AI_ASSERTION */),
|
|
188
|
+
assertion: z3.string(),
|
|
189
|
+
useVision: z3.boolean().default(false),
|
|
190
|
+
disableCache: z3.boolean().default(false)
|
|
207
191
|
})
|
|
208
192
|
);
|
|
209
|
-
var TypeOptionsSchema =
|
|
210
|
-
clearContent:
|
|
211
|
-
pressKeysSequentially:
|
|
193
|
+
var TypeOptionsSchema = z3.object({
|
|
194
|
+
clearContent: z3.boolean().default(true),
|
|
195
|
+
pressKeysSequentially: z3.boolean().default(false)
|
|
212
196
|
});
|
|
213
197
|
var TypeCommandSchema = CommonCommandSchema.merge(
|
|
214
|
-
|
|
215
|
-
type:
|
|
216
|
-
target:
|
|
217
|
-
value:
|
|
218
|
-
pressEnter:
|
|
198
|
+
z3.object({
|
|
199
|
+
type: z3.literal("TYPE" /* TYPE */),
|
|
200
|
+
target: ElementTargetSchema,
|
|
201
|
+
value: z3.string(),
|
|
202
|
+
pressEnter: z3.boolean().default(false)
|
|
219
203
|
})
|
|
220
204
|
).merge(TypeOptionsSchema).describe(
|
|
221
205
|
`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.`
|
|
222
206
|
);
|
|
223
207
|
var PressCommandSchema = CommonCommandSchema.merge(
|
|
224
|
-
|
|
225
|
-
type:
|
|
226
|
-
value:
|
|
208
|
+
z3.object({
|
|
209
|
+
type: z3.literal("PRESS" /* PRESS */),
|
|
210
|
+
value: z3.string()
|
|
227
211
|
})
|
|
228
212
|
).describe(
|
|
229
213
|
`PRESS <key> - press the specified key, such as "ArrowLeft", "Enter", or "a". You must specify at least one key.`
|
|
230
214
|
);
|
|
231
215
|
var TabCommandSchema = CommonCommandSchema.merge(
|
|
232
|
-
|
|
233
|
-
type:
|
|
234
|
-
url:
|
|
216
|
+
z3.object({
|
|
217
|
+
type: z3.literal("TAB" /* TAB */),
|
|
218
|
+
url: z3.string()
|
|
235
219
|
})
|
|
236
220
|
);
|
|
237
221
|
var CookieCommandSchema = CommonCommandSchema.merge(
|
|
238
|
-
|
|
239
|
-
type:
|
|
240
|
-
value:
|
|
222
|
+
z3.object({
|
|
223
|
+
type: z3.literal("COOKIE" /* COOKIE */),
|
|
224
|
+
value: z3.string()
|
|
241
225
|
})
|
|
242
226
|
);
|
|
243
227
|
var SuccessCommandSchema = CommonCommandSchema.merge(
|
|
244
|
-
|
|
245
|
-
type:
|
|
228
|
+
z3.object({
|
|
229
|
+
type: z3.literal("SUCCESS" /* SUCCESS */),
|
|
246
230
|
condition: AIAssertionCommandSchema.optional()
|
|
247
231
|
})
|
|
248
232
|
).describe("SUCCESS - the user goal has been successfully achieved");
|
|
249
|
-
var UserEditableAICommandSchema =
|
|
233
|
+
var UserEditableAICommandSchema = z3.discriminatedUnion("type", [
|
|
250
234
|
ClickCommandSchema,
|
|
251
235
|
TypeCommandSchema,
|
|
252
236
|
PressCommandSchema,
|
|
@@ -256,119 +240,144 @@ var UserEditableAICommandSchema = z2.discriminatedUnion("type", [
|
|
|
256
240
|
ScrollUpCommandSchema,
|
|
257
241
|
SuccessCommandSchema
|
|
258
242
|
]);
|
|
259
|
-
var UserEditablePresetCommandSchema =
|
|
243
|
+
var UserEditablePresetCommandSchema = z3.discriminatedUnion("type", [
|
|
260
244
|
GoBackCommandSchema,
|
|
261
245
|
GoForwardCommandSchema,
|
|
262
246
|
RefreshCommandSchema,
|
|
263
247
|
AIAssertionCommandSchema,
|
|
264
248
|
WaitCommandSchema,
|
|
265
249
|
TabCommandSchema,
|
|
266
|
-
CookieCommandSchema
|
|
250
|
+
CookieCommandSchema,
|
|
251
|
+
HoverCommandSchema
|
|
267
252
|
]);
|
|
268
|
-
var CommandSchema =
|
|
253
|
+
var CommandSchema = z3.discriminatedUnion("type", [
|
|
269
254
|
// Commands that can be either specified manually or auto-created by AI in an AI step
|
|
270
255
|
...UserEditableAICommandSchema.options,
|
|
271
256
|
// Commands that can only be specified manually ("preset commands")
|
|
272
257
|
...UserEditablePresetCommandSchema.options
|
|
273
258
|
]);
|
|
274
259
|
var FailureCommandSchema = CommonCommandSchema.merge(
|
|
275
|
-
|
|
276
|
-
type:
|
|
260
|
+
z3.object({
|
|
261
|
+
type: z3.literal("FAILURE")
|
|
277
262
|
})
|
|
278
263
|
).describe(
|
|
279
264
|
"FAILURE - there are no commands to suggest that could make progress that have not already been tried before"
|
|
280
265
|
);
|
|
281
|
-
var AICommandSchema =
|
|
266
|
+
var AICommandSchema = z3.discriminatedUnion("type", [
|
|
282
267
|
...UserEditableAICommandSchema.options,
|
|
283
268
|
FailureCommandSchema
|
|
284
269
|
]);
|
|
285
270
|
|
|
271
|
+
// ../../packages/types/src/errors.ts
|
|
272
|
+
var BrowserExecutionError = class extends Error {
|
|
273
|
+
constructor(message, options = {}) {
|
|
274
|
+
super(message, options);
|
|
275
|
+
this.name = "BrowserExecutionError";
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
var EmptyA11yTreeError = class extends Error {
|
|
279
|
+
constructor(options = {}) {
|
|
280
|
+
super("Got empty a11y tree", options);
|
|
281
|
+
this.name = "EmptyA11yTreeError";
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
// ../../packages/types/src/ai-command-generation.ts
|
|
286
|
+
var LLMOutputSchema = z4.object({
|
|
287
|
+
command: z4.string(),
|
|
288
|
+
thoughts: z4.string()
|
|
289
|
+
});
|
|
290
|
+
var NumericStringSchema = z4.string().pipe(z4.coerce.number());
|
|
291
|
+
|
|
286
292
|
// ../../packages/types/src/steps.ts
|
|
287
|
-
import * as
|
|
293
|
+
import * as z5 from "zod";
|
|
294
|
+
var LATEST_VERSION = "1.0.6";
|
|
288
295
|
var StepType = /* @__PURE__ */ ((StepType2) => {
|
|
289
296
|
StepType2["AI_ACTION"] = "AI_ACTION";
|
|
290
297
|
StepType2["PRESET_ACTION"] = "PRESET_ACTION";
|
|
291
298
|
StepType2["MODULE"] = "MODULE";
|
|
292
299
|
return StepType2;
|
|
293
300
|
})(StepType || {});
|
|
294
|
-
var AIActionSchema =
|
|
295
|
-
type:
|
|
296
|
-
text:
|
|
301
|
+
var AIActionSchema = z5.object({
|
|
302
|
+
type: z5.literal("AI_ACTION" /* AI_ACTION */),
|
|
303
|
+
text: z5.string(),
|
|
297
304
|
// Cached commands for this step
|
|
298
|
-
commands:
|
|
305
|
+
commands: z5.array(UserEditableAICommandSchema).optional()
|
|
299
306
|
});
|
|
300
|
-
var PresetActionSchema =
|
|
301
|
-
type:
|
|
307
|
+
var PresetActionSchema = z5.object({
|
|
308
|
+
type: z5.literal("PRESET_ACTION" /* PRESET_ACTION */),
|
|
302
309
|
command: CommandSchema
|
|
303
310
|
});
|
|
304
|
-
var ModuleStepSchema =
|
|
305
|
-
type:
|
|
306
|
-
moduleId:
|
|
311
|
+
var ModuleStepSchema = z5.object({
|
|
312
|
+
type: z5.literal("MODULE" /* MODULE */),
|
|
313
|
+
moduleId: z5.string().uuid()
|
|
307
314
|
});
|
|
308
|
-
var AllowedModuleStepSchema =
|
|
315
|
+
var AllowedModuleStepSchema = z5.union([
|
|
309
316
|
AIActionSchema,
|
|
310
317
|
PresetActionSchema
|
|
311
318
|
]);
|
|
312
|
-
var ResolvedModuleStepSchema =
|
|
313
|
-
type:
|
|
314
|
-
moduleId:
|
|
315
|
-
name:
|
|
319
|
+
var ResolvedModuleStepSchema = z5.object({
|
|
320
|
+
type: z5.literal("RESOLVED_MODULE"),
|
|
321
|
+
moduleId: z5.string().uuid(),
|
|
322
|
+
name: z5.string(),
|
|
316
323
|
steps: AllowedModuleStepSchema.array()
|
|
317
324
|
});
|
|
318
|
-
var StepSchema =
|
|
325
|
+
var StepSchema = z5.union([
|
|
319
326
|
AIActionSchema,
|
|
320
327
|
PresetActionSchema,
|
|
321
328
|
ModuleStepSchema
|
|
322
329
|
]);
|
|
323
|
-
var ResolvedStepSchema =
|
|
330
|
+
var ResolvedStepSchema = z5.union([
|
|
324
331
|
AIActionSchema,
|
|
325
332
|
PresetActionSchema,
|
|
326
333
|
ResolvedModuleStepSchema
|
|
327
334
|
]);
|
|
328
335
|
|
|
329
|
-
// ../../
|
|
330
|
-
var
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
var BrowserExecutionError = class extends Error {
|
|
353
|
-
constructor(message, options = {}) {
|
|
354
|
-
super(message, options);
|
|
355
|
-
this.name = "BrowserExecutionError";
|
|
356
|
-
}
|
|
336
|
+
// ../../packages/types/src/card-display.ts
|
|
337
|
+
var SELECTABLE_PRESET_COMMAND_OPTIONS_SET = new Set(
|
|
338
|
+
Object.values(CommandType)
|
|
339
|
+
);
|
|
340
|
+
var CARD_DISPLAY_NAMES = {
|
|
341
|
+
["AI_ACTION" /* AI_ACTION */]: "AI action",
|
|
342
|
+
["MODULE" /* MODULE */]: "Module",
|
|
343
|
+
["AI_ASSERTION" /* AI_ASSERTION */]: "AI check",
|
|
344
|
+
["CLICK" /* CLICK */]: "Click",
|
|
345
|
+
["HOVER" /* HOVER */]: "Hover",
|
|
346
|
+
["SELECT_OPTION" /* SELECT_OPTION */]: "Select",
|
|
347
|
+
["TYPE" /* TYPE */]: "Type",
|
|
348
|
+
["PRESS" /* PRESS */]: "Press",
|
|
349
|
+
["NAVIGATE" /* NAVIGATE */]: "Navigate",
|
|
350
|
+
["SCROLL_UP" /* SCROLL_UP */]: "Scroll up",
|
|
351
|
+
["SCROLL_DOWN" /* SCROLL_DOWN */]: "Scroll down",
|
|
352
|
+
["GO_BACK" /* GO_BACK */]: "Go back",
|
|
353
|
+
["GO_FORWARD" /* GO_FORWARD */]: "Go forward",
|
|
354
|
+
["WAIT" /* WAIT */]: "Wait",
|
|
355
|
+
["REFRESH" /* REFRESH */]: "Refresh",
|
|
356
|
+
["TAB" /* TAB */]: "Switch tab",
|
|
357
|
+
["COOKIE" /* COOKIE */]: "Set cookie",
|
|
358
|
+
["SUCCESS" /* SUCCESS */]: "Done"
|
|
357
359
|
};
|
|
358
|
-
var
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
360
|
+
var CARD_DESCRIPTIONS = {
|
|
361
|
+
["AI_ACTION" /* AI_ACTION */]: "Ask AI to plan and execute something on the page.",
|
|
362
|
+
["MODULE" /* MODULE */]: "A list of steps that can be reused in multiple tests.",
|
|
363
|
+
["AI_ASSERTION" /* AI_ASSERTION */]: "Ask AI whether something is true on the page.",
|
|
364
|
+
["CLICK" /* CLICK */]: "Click on an element on the page based on a description.",
|
|
365
|
+
["HOVER" /* HOVER */]: "Hover over an element on the page based on a description.",
|
|
366
|
+
["SELECT_OPTION" /* SELECT_OPTION */]: "Select an option from a dropdown based on a description.",
|
|
367
|
+
["TYPE" /* TYPE */]: "Type the specified text into an element.",
|
|
368
|
+
["PRESS" /* PRESS */]: "Press the specified keys using the keyboard. (e.g. Ctrl+A)",
|
|
369
|
+
["NAVIGATE" /* NAVIGATE */]: "Navigate to the specified URL.",
|
|
370
|
+
["SCROLL_UP" /* SCROLL_UP */]: "Scroll up one page.",
|
|
371
|
+
["SCROLL_DOWN" /* SCROLL_DOWN */]: "Scroll down one page.",
|
|
372
|
+
["GO_BACK" /* GO_BACK */]: "Go back in browser history.",
|
|
373
|
+
["GO_FORWARD" /* GO_FORWARD */]: "Go forward in browser history.",
|
|
374
|
+
["WAIT" /* WAIT */]: "Wait for the specified number of seconds.",
|
|
375
|
+
["REFRESH" /* REFRESH */]: "Refresh the page. This will not clear cookies or session data.",
|
|
376
|
+
["TAB" /* TAB */]: "Switch to different tab in the browser.",
|
|
377
|
+
["COOKIE" /* COOKIE */]: "Set a cookie that will persist throughout the browser session",
|
|
378
|
+
["SUCCESS" /* SUCCESS */]: "Indicate the entire AI action has succeeded, optionally based on a condition."
|
|
363
379
|
};
|
|
364
380
|
|
|
365
|
-
// ../../packages/types/src/ai-command-generation.ts
|
|
366
|
-
var LLMOutputSchema = z5.object({
|
|
367
|
-
command: z5.string(),
|
|
368
|
-
thoughts: z5.string()
|
|
369
|
-
});
|
|
370
|
-
var NumericStringSchema = z5.string().pipe(z5.coerce.number());
|
|
371
|
-
|
|
372
381
|
// ../../packages/types/src/command-results.ts
|
|
373
382
|
import * as z6 from "zod";
|
|
374
383
|
var ResultStatus = /* @__PURE__ */ ((ResultStatus2) => {
|
|
@@ -441,6 +450,105 @@ var ResultSchema = z6.discriminatedUnion("type", [
|
|
|
441
450
|
ModuleResultSchema
|
|
442
451
|
]);
|
|
443
452
|
|
|
453
|
+
// ../../packages/types/src/command-serialization.ts
|
|
454
|
+
function clampText(text, length) {
|
|
455
|
+
if (text.length < length) {
|
|
456
|
+
return text;
|
|
457
|
+
}
|
|
458
|
+
return text.slice(0, length - 3) + "[...]";
|
|
459
|
+
}
|
|
460
|
+
function serializeCommand(command) {
|
|
461
|
+
var _a, _b, _c;
|
|
462
|
+
switch (command.type) {
|
|
463
|
+
case "SUCCESS" /* SUCCESS */:
|
|
464
|
+
if ((_a = command.condition) == null ? void 0 : _a.assertion) {
|
|
465
|
+
return `Check success condition: ${command.condition.assertion}`;
|
|
466
|
+
}
|
|
467
|
+
return `All commands completed`;
|
|
468
|
+
case "NAVIGATE" /* NAVIGATE */:
|
|
469
|
+
return `Go to URL: ${clampText(command.url, 30)}`;
|
|
470
|
+
case "GO_BACK" /* GO_BACK */:
|
|
471
|
+
return `Go back to the previous page`;
|
|
472
|
+
case "GO_FORWARD" /* GO_FORWARD */:
|
|
473
|
+
return `Go forward to the next page`;
|
|
474
|
+
case "SCROLL_DOWN" /* SCROLL_DOWN */:
|
|
475
|
+
return `Scroll down one page`;
|
|
476
|
+
case "SCROLL_UP" /* SCROLL_UP */:
|
|
477
|
+
return `Scroll up one page`;
|
|
478
|
+
case "WAIT" /* WAIT */:
|
|
479
|
+
return `Wait for ${command.delay} seconds`;
|
|
480
|
+
case "REFRESH" /* REFRESH */:
|
|
481
|
+
return `Refresh the page`;
|
|
482
|
+
case "CLICK" /* CLICK */:
|
|
483
|
+
return `Click on '${command.target.elementDescriptor}'`;
|
|
484
|
+
case "TYPE" /* TYPE */: {
|
|
485
|
+
let serializedTarget = "";
|
|
486
|
+
if ((_b = command.target.a11yData) == null ? void 0 : _b.serializedForm) {
|
|
487
|
+
serializedTarget = ` in element ${command.target.a11yData.serializedForm}`;
|
|
488
|
+
} else if (command.target.elementDescriptor.length > 0) {
|
|
489
|
+
serializedTarget = ` in element ${command.target.elementDescriptor}`;
|
|
490
|
+
}
|
|
491
|
+
return `Type${serializedTarget}: '${command.value}'`;
|
|
492
|
+
}
|
|
493
|
+
case "HOVER" /* HOVER */: {
|
|
494
|
+
let serializedTarget = "";
|
|
495
|
+
if ((_c = command.target.a11yData) == null ? void 0 : _c.serializedForm) {
|
|
496
|
+
serializedTarget = ` over element: ${command.target.a11yData.serializedForm}`;
|
|
497
|
+
} else if (command.target.elementDescriptor.length > 0) {
|
|
498
|
+
serializedTarget = ` over element: ${command.target.elementDescriptor}`;
|
|
499
|
+
}
|
|
500
|
+
return `Hover${serializedTarget}`;
|
|
501
|
+
}
|
|
502
|
+
case "PRESS" /* PRESS */:
|
|
503
|
+
return `Press '${command.value}'`;
|
|
504
|
+
case "SELECT_OPTION" /* SELECT_OPTION */:
|
|
505
|
+
return `Select option '${command.option}' in '${command.target.elementDescriptor}'`;
|
|
506
|
+
case "TAB" /* TAB */:
|
|
507
|
+
return `Switch to tab: ${command.url}`;
|
|
508
|
+
case "COOKIE" /* COOKIE */:
|
|
509
|
+
return `Set cookie: ${command.value}`;
|
|
510
|
+
case "AI_ASSERTION" /* AI_ASSERTION */:
|
|
511
|
+
return `${command.useVision ? "Visual assertion" : "Assertion"}: '${command.assertion}'`;
|
|
512
|
+
default:
|
|
513
|
+
const assertUnreachable = (_x) => {
|
|
514
|
+
throw "If Typescript complains about the line below, you missed a case or break in the switch above";
|
|
515
|
+
};
|
|
516
|
+
return assertUnreachable(command);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// ../../packages/types/src/context.ts
|
|
521
|
+
import * as z8 from "zod";
|
|
522
|
+
|
|
523
|
+
// ../../packages/types/src/execute-results.ts
|
|
524
|
+
import * as z7 from "zod";
|
|
525
|
+
var ExecuteCommandHistoryEntrySchema = z7.object({
|
|
526
|
+
// type of command executed
|
|
527
|
+
type: z7.nativeEnum(StepType),
|
|
528
|
+
// if AI step type, what command was executed
|
|
529
|
+
generatedStep: UserEditableAICommandSchema.optional(),
|
|
530
|
+
// human readable descriptor for action taken, including element interacted with
|
|
531
|
+
serializedCommand: z7.string().optional(),
|
|
532
|
+
// human readable descriptor for element interacted with
|
|
533
|
+
elementInteracted: z7.string().optional()
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
// ../../packages/types/src/context.ts
|
|
537
|
+
var DynamicContextSchema = z8.object({
|
|
538
|
+
// user goal or instruction
|
|
539
|
+
goal: z8.string(),
|
|
540
|
+
// current url of the browser
|
|
541
|
+
url: z8.string(),
|
|
542
|
+
// serialized page state
|
|
543
|
+
browserState: z8.string(),
|
|
544
|
+
// serialized history of previous commands
|
|
545
|
+
history: z8.string(),
|
|
546
|
+
// number of previously executed commands
|
|
547
|
+
numPrevious: z8.number(),
|
|
548
|
+
// last executed command, if any
|
|
549
|
+
lastCommand: ExecuteCommandHistoryEntrySchema.or(z8.null())
|
|
550
|
+
});
|
|
551
|
+
|
|
444
552
|
// ../../packages/types/src/cookies.ts
|
|
445
553
|
import { parseString } from "set-cookie-parser";
|
|
446
554
|
function parseCookieString(cookie) {
|
|
@@ -474,51 +582,156 @@ function parseCookieString(cookie) {
|
|
|
474
582
|
return result;
|
|
475
583
|
}
|
|
476
584
|
|
|
477
|
-
// ../../packages/types/src/execute-results.ts
|
|
478
|
-
import * as z7 from "zod";
|
|
479
|
-
var ExecuteCommandHistoryEntrySchema = z7.object({
|
|
480
|
-
// type of command executed
|
|
481
|
-
type: z7.nativeEnum(StepType),
|
|
482
|
-
// if AI step type, what command was executed
|
|
483
|
-
generatedStep: UserEditableAICommandSchema.optional(),
|
|
484
|
-
// human readable descriptor for action taken, including element interacted with
|
|
485
|
-
serializedCommand: z7.string().optional(),
|
|
486
|
-
// human readable descriptor for element interacted with
|
|
487
|
-
elementInteracted: z7.string().optional()
|
|
488
|
-
});
|
|
489
|
-
|
|
490
585
|
// ../../packages/types/src/goal-splitter.ts
|
|
491
|
-
import { z as
|
|
492
|
-
var InstructionsSchema =
|
|
586
|
+
import { z as z9 } from "zod";
|
|
587
|
+
var InstructionsSchema = z9.string().array();
|
|
493
588
|
|
|
494
589
|
// ../../packages/types/src/locator.ts
|
|
495
|
-
import * as
|
|
496
|
-
var AILocatorSchema =
|
|
497
|
-
thoughts:
|
|
590
|
+
import * as z10 from "zod";
|
|
591
|
+
var AILocatorSchema = z10.object({
|
|
592
|
+
thoughts: z10.string(),
|
|
498
593
|
// a11y id
|
|
499
|
-
id:
|
|
594
|
+
id: z10.number().int(),
|
|
500
595
|
// dropdowns should have options
|
|
501
|
-
options:
|
|
596
|
+
options: z10.array(z10.string()).optional()
|
|
502
597
|
});
|
|
503
598
|
|
|
599
|
+
// ../../packages/types/src/logger.ts
|
|
600
|
+
var stringToLogLevel = {
|
|
601
|
+
DEBUG: 0 /* DEBUG */,
|
|
602
|
+
INFO: 1 /* INFO */,
|
|
603
|
+
WARN: 2 /* WARN */,
|
|
604
|
+
ERROR: 3 /* ERROR */
|
|
605
|
+
};
|
|
606
|
+
var LogLevelTags = {
|
|
607
|
+
[0 /* DEBUG */]: "DEBUG",
|
|
608
|
+
[1 /* INFO */]: "INFO",
|
|
609
|
+
[2 /* WARN */]: "WARN",
|
|
610
|
+
[3 /* ERROR */]: "ERROR"
|
|
611
|
+
};
|
|
612
|
+
var LogLevelColors = {
|
|
613
|
+
[0 /* DEBUG */]: "\x1B[90m",
|
|
614
|
+
[1 /* INFO */]: "\x1B[32m",
|
|
615
|
+
[2 /* WARN */]: "\x1B[33m",
|
|
616
|
+
[3 /* ERROR */]: "\x1B[31m"
|
|
617
|
+
};
|
|
618
|
+
var ConsoleLogger = class _ConsoleLogger {
|
|
619
|
+
constructor(minLevel, bindings) {
|
|
620
|
+
this.minLogLevel = minLevel;
|
|
621
|
+
this.logBindings = bindings;
|
|
622
|
+
}
|
|
623
|
+
log(level, ...args) {
|
|
624
|
+
const levelName = LogLevelTags[level];
|
|
625
|
+
let objectArg;
|
|
626
|
+
if (Array.isArray(args[0])) {
|
|
627
|
+
objectArg = args[0];
|
|
628
|
+
args = args.slice(1);
|
|
629
|
+
} else if (typeof args[0] === "object") {
|
|
630
|
+
objectArg = __spreadValues(__spreadValues({}, args[0]), this.logBindings);
|
|
631
|
+
args = args.slice(1);
|
|
632
|
+
}
|
|
633
|
+
const colorSequence = LogLevelColors[level];
|
|
634
|
+
const logTokens = [
|
|
635
|
+
`${colorSequence}[${(/* @__PURE__ */ new Date()).toTimeString().slice(0, 8)}][${levelName}]`
|
|
636
|
+
];
|
|
637
|
+
if (level !== 0 /* DEBUG */) {
|
|
638
|
+
logTokens.push("\x1B[39m");
|
|
639
|
+
}
|
|
640
|
+
logTokens.push(...args);
|
|
641
|
+
console.log(...logTokens);
|
|
642
|
+
if (objectArg && !Array.isArray(objectArg)) {
|
|
643
|
+
for (const [key, value] of Object.entries(objectArg)) {
|
|
644
|
+
let stringifiedValue = value;
|
|
645
|
+
if (typeof value === "object") {
|
|
646
|
+
stringifiedValue = JSON.stringify(value, void 0, 2);
|
|
647
|
+
stringifiedValue = stringifiedValue.split("\n").map(
|
|
648
|
+
(line, index) => index > 0 ? ` ${line}` : line
|
|
649
|
+
).join("\n");
|
|
650
|
+
}
|
|
651
|
+
console.log(
|
|
652
|
+
level === 0 /* DEBUG */ ? `${colorSequence} ${key}:` : ` ${key}:`,
|
|
653
|
+
stringifiedValue
|
|
654
|
+
);
|
|
655
|
+
}
|
|
656
|
+
} else if (objectArg) {
|
|
657
|
+
for (const value of objectArg) {
|
|
658
|
+
let stringifiedValue = value;
|
|
659
|
+
if (typeof value === "object") {
|
|
660
|
+
stringifiedValue = JSON.stringify(value, void 0, 2);
|
|
661
|
+
stringifiedValue = stringifiedValue.split("\n").map(
|
|
662
|
+
(line, index) => index > 0 ? ` ${line}` : line
|
|
663
|
+
).join("\n");
|
|
664
|
+
}
|
|
665
|
+
console.log(
|
|
666
|
+
level === 0 /* DEBUG */ ? `${colorSequence} ` : ` `,
|
|
667
|
+
stringifiedValue
|
|
668
|
+
);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
if (level === 0 /* DEBUG */) {
|
|
672
|
+
process.stdout.write("\x1B[39m");
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
setMinLevel(level) {
|
|
676
|
+
this.minLogLevel = level;
|
|
677
|
+
}
|
|
678
|
+
info(...args) {
|
|
679
|
+
if (1 /* INFO */ < this.minLogLevel) {
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
this.log(1 /* INFO */, ...args);
|
|
683
|
+
}
|
|
684
|
+
debug(...args) {
|
|
685
|
+
if (0 /* DEBUG */ < this.minLogLevel) {
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
this.log(0 /* DEBUG */, ...args);
|
|
689
|
+
}
|
|
690
|
+
warn(...args) {
|
|
691
|
+
if (2 /* WARN */ < this.minLogLevel) {
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
this.log(2 /* WARN */, ...args);
|
|
695
|
+
}
|
|
696
|
+
error(...args) {
|
|
697
|
+
if (3 /* ERROR */ < this.minLogLevel) {
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
this.log(3 /* ERROR */, ...args);
|
|
701
|
+
}
|
|
702
|
+
child(bindings) {
|
|
703
|
+
return new _ConsoleLogger(this.minLogLevel, __spreadValues(__spreadValues({}, this.logBindings), bindings));
|
|
704
|
+
}
|
|
705
|
+
flush() {
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
bindings() {
|
|
709
|
+
return this.logBindings;
|
|
710
|
+
}
|
|
711
|
+
};
|
|
712
|
+
var consoleLogger = new ConsoleLogger(1 /* INFO */, {});
|
|
713
|
+
|
|
504
714
|
// ../../packages/types/src/modules.ts
|
|
505
|
-
import { z as
|
|
506
|
-
var ModuleMetadataSchema =
|
|
507
|
-
id:
|
|
508
|
-
createdAt:
|
|
509
|
-
createdBy:
|
|
510
|
-
organizationId:
|
|
511
|
-
name:
|
|
512
|
-
schemaVersion:
|
|
715
|
+
import { z as z11 } from "zod";
|
|
716
|
+
var ModuleMetadataSchema = z11.object({
|
|
717
|
+
id: z11.string(),
|
|
718
|
+
createdAt: z11.coerce.date(),
|
|
719
|
+
createdBy: z11.string(),
|
|
720
|
+
organizationId: z11.string(),
|
|
721
|
+
name: z11.string(),
|
|
722
|
+
schemaVersion: z11.string(),
|
|
513
723
|
// this is only used in the client and is not stored in the db
|
|
514
|
-
numSteps:
|
|
724
|
+
numSteps: z11.number()
|
|
515
725
|
});
|
|
516
|
-
var ModuleSchema =
|
|
726
|
+
var ModuleSchema = z11.object({
|
|
517
727
|
steps: AllowedModuleStepSchema.array()
|
|
518
728
|
}).merge(ModuleMetadataSchema.omit({ numSteps: true }));
|
|
519
729
|
|
|
730
|
+
// ../../packages/types/src/public-api.ts
|
|
731
|
+
import * as z15 from "zod";
|
|
732
|
+
|
|
520
733
|
// ../../packages/types/src/runs.ts
|
|
521
|
-
import { z as
|
|
734
|
+
import { z as z12 } from "zod";
|
|
522
735
|
var RunTriggerEnum = {
|
|
523
736
|
WEBHOOK: "WEBHOOK",
|
|
524
737
|
CRON: "CRON",
|
|
@@ -532,202 +745,144 @@ var RunStatusEnum = {
|
|
|
532
745
|
FAILED: "FAILED",
|
|
533
746
|
CANCELLED: "CANCELLED"
|
|
534
747
|
};
|
|
535
|
-
var DateOrStringSchema =
|
|
536
|
-
var RunMetadataSchema =
|
|
537
|
-
id:
|
|
748
|
+
var DateOrStringSchema = z12.string().pipe(z12.coerce.date()).or(z12.date());
|
|
749
|
+
var RunMetadataSchema = z12.object({
|
|
750
|
+
id: z12.string(),
|
|
538
751
|
createdAt: DateOrStringSchema,
|
|
539
|
-
createdBy:
|
|
540
|
-
organizationId:
|
|
541
|
-
scheduledAt: DateOrStringSchema.or(
|
|
542
|
-
startedAt: DateOrStringSchema.or(
|
|
543
|
-
finishedAt: DateOrStringSchema.or(
|
|
544
|
-
testId:
|
|
545
|
-
status:
|
|
546
|
-
trigger:
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
752
|
+
createdBy: z12.string(),
|
|
753
|
+
organizationId: z12.string(),
|
|
754
|
+
scheduledAt: DateOrStringSchema.or(z12.null()),
|
|
755
|
+
startedAt: DateOrStringSchema.or(z12.null()),
|
|
756
|
+
finishedAt: DateOrStringSchema.or(z12.null()),
|
|
757
|
+
testId: z12.string().or(z12.null()),
|
|
758
|
+
status: z12.nativeEnum(RunStatusEnum),
|
|
759
|
+
trigger: z12.nativeEnum(RunTriggerEnum),
|
|
760
|
+
attempts: z12.number(),
|
|
761
|
+
test: z12.object({
|
|
762
|
+
name: z12.string(),
|
|
763
|
+
id: z12.string()
|
|
764
|
+
}).or(z12.null())
|
|
551
765
|
});
|
|
552
766
|
var RunWithTestSchema = RunMetadataSchema.merge(
|
|
553
|
-
|
|
767
|
+
z12.object({
|
|
554
768
|
results: ResultSchema.array(),
|
|
555
|
-
test:
|
|
556
|
-
name:
|
|
557
|
-
id:
|
|
558
|
-
baseUrl:
|
|
559
|
-
}).or(
|
|
769
|
+
test: z12.object({
|
|
770
|
+
name: z12.string(),
|
|
771
|
+
id: z12.string(),
|
|
772
|
+
baseUrl: z12.string()
|
|
773
|
+
}).or(z12.null())
|
|
560
774
|
})
|
|
561
775
|
);
|
|
562
776
|
|
|
563
|
-
// ../../packages/types/src/serialization.ts
|
|
564
|
-
function clampText(text, length) {
|
|
565
|
-
if (text.length < length) {
|
|
566
|
-
return text;
|
|
567
|
-
}
|
|
568
|
-
return text.slice(0, length - 3) + "[...]";
|
|
569
|
-
}
|
|
570
|
-
function serializeCommand(command) {
|
|
571
|
-
var _a, _b;
|
|
572
|
-
switch (command.type) {
|
|
573
|
-
case "SUCCESS" /* SUCCESS */:
|
|
574
|
-
if ((_a = command.condition) == null ? void 0 : _a.assertion) {
|
|
575
|
-
return `Check success condition: ${command.condition.assertion}`;
|
|
576
|
-
}
|
|
577
|
-
return `All commands completed`;
|
|
578
|
-
case "NAVIGATE" /* NAVIGATE */:
|
|
579
|
-
return `Go to URL: ${clampText(command.url, 30)}`;
|
|
580
|
-
case "GO_BACK" /* GO_BACK */:
|
|
581
|
-
return `Go back to the previous page`;
|
|
582
|
-
case "GO_FORWARD" /* GO_FORWARD */:
|
|
583
|
-
return `Go forward to the next page`;
|
|
584
|
-
case "SCROLL_DOWN" /* SCROLL_DOWN */:
|
|
585
|
-
return `Scroll down one page`;
|
|
586
|
-
case "SCROLL_UP" /* SCROLL_UP */:
|
|
587
|
-
return `Scroll up one page`;
|
|
588
|
-
case "WAIT" /* WAIT */:
|
|
589
|
-
return `Wait for ${command.delay} seconds`;
|
|
590
|
-
case "REFRESH" /* REFRESH */:
|
|
591
|
-
return `Refresh the page`;
|
|
592
|
-
case "CLICK" /* CLICK */:
|
|
593
|
-
return `Click on '${command.target.elementDescriptor}'`;
|
|
594
|
-
case "TYPE" /* TYPE */:
|
|
595
|
-
let serializedTarget = "";
|
|
596
|
-
if ((_b = command.target.a11yData) == null ? void 0 : _b.serializedForm) {
|
|
597
|
-
serializedTarget = ` in element ${command.target.a11yData.serializedForm}`;
|
|
598
|
-
} else if (command.target.elementDescriptor.length > 0) {
|
|
599
|
-
serializedTarget = ` in element ${command.target.elementDescriptor}`;
|
|
600
|
-
}
|
|
601
|
-
return `Type${serializedTarget}: '${command.value}'`;
|
|
602
|
-
case "PRESS" /* PRESS */:
|
|
603
|
-
return `Press '${command.value}'`;
|
|
604
|
-
case "SELECT_OPTION" /* SELECT_OPTION */:
|
|
605
|
-
return `Select option '${command.option}' in '${command.target.elementDescriptor}'`;
|
|
606
|
-
case "TAB" /* TAB */:
|
|
607
|
-
return `Switch to tab: ${command.url}`;
|
|
608
|
-
case "COOKIE" /* COOKIE */:
|
|
609
|
-
return `Set cookie: ${command.value}`;
|
|
610
|
-
case "AI_ASSERTION" /* AI_ASSERTION */:
|
|
611
|
-
return `${command.useVision ? "Visual assertion" : "Assertion"}: '${command.assertion}'`;
|
|
612
|
-
default:
|
|
613
|
-
const assertUnreachable = (_x) => {
|
|
614
|
-
throw "If Typescript complains about the line below, you missed a case or break in the switch above";
|
|
615
|
-
};
|
|
616
|
-
return assertUnreachable(command);
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
// ../../packages/types/src/card-display.ts
|
|
621
|
-
var SELECTABLE_PRESET_COMMAND_OPTIONS_SET = new Set(
|
|
622
|
-
Object.values(CommandType)
|
|
623
|
-
);
|
|
624
|
-
var CARD_DISPLAY_NAMES = {
|
|
625
|
-
["AI_ACTION" /* AI_ACTION */]: "AI action",
|
|
626
|
-
["MODULE" /* MODULE */]: "Module",
|
|
627
|
-
["AI_ASSERTION" /* AI_ASSERTION */]: "AI check",
|
|
628
|
-
["CLICK" /* CLICK */]: "Click",
|
|
629
|
-
["SELECT_OPTION" /* SELECT_OPTION */]: "Select",
|
|
630
|
-
["TYPE" /* TYPE */]: "Type",
|
|
631
|
-
["PRESS" /* PRESS */]: "Press",
|
|
632
|
-
["NAVIGATE" /* NAVIGATE */]: "Navigate",
|
|
633
|
-
["SCROLL_UP" /* SCROLL_UP */]: "Scroll up",
|
|
634
|
-
["SCROLL_DOWN" /* SCROLL_DOWN */]: "Scroll down",
|
|
635
|
-
["GO_BACK" /* GO_BACK */]: "Go back",
|
|
636
|
-
["GO_FORWARD" /* GO_FORWARD */]: "Go forward",
|
|
637
|
-
["WAIT" /* WAIT */]: "Wait",
|
|
638
|
-
["REFRESH" /* REFRESH */]: "Refresh",
|
|
639
|
-
["TAB" /* TAB */]: "Switch tab",
|
|
640
|
-
["COOKIE" /* COOKIE */]: "Set cookie",
|
|
641
|
-
["SUCCESS" /* SUCCESS */]: "Done"
|
|
642
|
-
};
|
|
643
|
-
var CARD_DESCRIPTIONS = {
|
|
644
|
-
["AI_ACTION" /* AI_ACTION */]: "Ask AI to plan and execute something on the page.",
|
|
645
|
-
["MODULE" /* MODULE */]: "A list of steps that can be reused in multiple tests.",
|
|
646
|
-
["AI_ASSERTION" /* AI_ASSERTION */]: "Ask AI whether something is true on the page.",
|
|
647
|
-
["CLICK" /* CLICK */]: "Click on an element on the page based on a description.",
|
|
648
|
-
["SELECT_OPTION" /* SELECT_OPTION */]: "Select an option from a dropdown based on a description.",
|
|
649
|
-
["TYPE" /* TYPE */]: "Type the specified text into an element.",
|
|
650
|
-
["PRESS" /* PRESS */]: "Press the specified keys using the keyboard. (e.g. Ctrl+A)",
|
|
651
|
-
["NAVIGATE" /* NAVIGATE */]: "Navigate to the specified URL.",
|
|
652
|
-
["SCROLL_UP" /* SCROLL_UP */]: "Scroll up one page.",
|
|
653
|
-
["SCROLL_DOWN" /* SCROLL_DOWN */]: "Scroll down one page.",
|
|
654
|
-
["GO_BACK" /* GO_BACK */]: "Go back in browser history.",
|
|
655
|
-
["GO_FORWARD" /* GO_FORWARD */]: "Go forward in browser history.",
|
|
656
|
-
["WAIT" /* WAIT */]: "Wait for the specified number of seconds.",
|
|
657
|
-
["REFRESH" /* REFRESH */]: "Refresh the page. This will not clear cookies or session data.",
|
|
658
|
-
["TAB" /* TAB */]: "Switch to different tab in the browser.",
|
|
659
|
-
["COOKIE" /* COOKIE */]: "Set a cookie that will persist throughout the browser session",
|
|
660
|
-
["SUCCESS" /* SUCCESS */]: "Indicate the entire AI action has succeeded, optionally based on a condition."
|
|
661
|
-
};
|
|
662
|
-
|
|
663
777
|
// ../../packages/types/src/test.ts
|
|
664
|
-
import { z as
|
|
778
|
+
import { z as z14 } from "zod";
|
|
665
779
|
|
|
666
780
|
// ../../packages/types/src/test-settings.ts
|
|
667
781
|
import { isValidCron } from "cron-validator";
|
|
668
|
-
import { z as
|
|
669
|
-
var TestAdvancedSettingsSchema =
|
|
670
|
-
availableAsModule:
|
|
671
|
-
disableAICaching:
|
|
782
|
+
import { z as z13 } from "zod";
|
|
783
|
+
var TestAdvancedSettingsSchema = z13.object({
|
|
784
|
+
availableAsModule: z13.boolean().default(false),
|
|
785
|
+
disableAICaching: z13.boolean().default(false)
|
|
672
786
|
});
|
|
673
|
-
var ScheduleSettingsSchema =
|
|
674
|
-
cron:
|
|
787
|
+
var ScheduleSettingsSchema = z13.object({
|
|
788
|
+
cron: z13.string().refine(
|
|
675
789
|
(v) => {
|
|
676
790
|
return isValidCron(v);
|
|
677
791
|
},
|
|
678
792
|
{ message: "Invalid cron expression." }
|
|
679
793
|
).default("0 0 */1 * *"),
|
|
680
|
-
enabled:
|
|
681
|
-
timeZone:
|
|
794
|
+
enabled: z13.boolean().default(false),
|
|
795
|
+
timeZone: z13.string().default("America/Los_Angeles"),
|
|
682
796
|
// this is used for removing repeatable jobs (not set by user)
|
|
683
|
-
jobKey:
|
|
797
|
+
jobKey: z13.string().optional()
|
|
684
798
|
});
|
|
685
|
-
var
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
});
|
|
689
|
-
var WebhookSettingsSchema = z12.array(WebhookSchema).default([]);
|
|
690
|
-
var TestSettingsSchema = z12.object({
|
|
691
|
-
name: z12.string().min(1),
|
|
692
|
-
baseUrl: z12.string().url(),
|
|
693
|
-
advanced: TestAdvancedSettingsSchema
|
|
799
|
+
var NotificationSettingsSchema = z13.object({
|
|
800
|
+
onSuccess: z13.boolean().default(false),
|
|
801
|
+
onFailure: z13.boolean().default(true)
|
|
694
802
|
});
|
|
695
803
|
|
|
696
804
|
// ../../packages/types/src/test.ts
|
|
697
|
-
var
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
805
|
+
var TestNameSchema = z14.string().min(1).max(255).superRefine((v, ctx) => {
|
|
806
|
+
try {
|
|
807
|
+
validateTestOrModuleName(v);
|
|
808
|
+
} catch (err) {
|
|
809
|
+
ctx.addIssue({
|
|
810
|
+
code: z14.ZodIssueCode.custom,
|
|
811
|
+
message: err.message,
|
|
812
|
+
fatal: true
|
|
813
|
+
});
|
|
814
|
+
return z14.NEVER;
|
|
815
|
+
}
|
|
816
|
+
});
|
|
817
|
+
var BaseTestMetadataSchema = z14.object({
|
|
818
|
+
id: z14.string(),
|
|
819
|
+
name: TestNameSchema,
|
|
820
|
+
baseUrl: z14.string(),
|
|
821
|
+
schemaVersion: z14.string(),
|
|
707
822
|
advanced: TestAdvancedSettingsSchema,
|
|
708
|
-
|
|
709
|
-
webhooks: WebhookSettingsSchema
|
|
823
|
+
retries: z14.number()
|
|
710
824
|
});
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
numPrevious: z14.number(),
|
|
725
|
-
// last executed command, if any
|
|
726
|
-
lastCommand: ExecuteCommandHistoryEntrySchema.or(z14.null())
|
|
825
|
+
var UserEditableTestSettingsSchema = BaseTestMetadataSchema.pick({
|
|
826
|
+
name: true,
|
|
827
|
+
baseUrl: true,
|
|
828
|
+
retries: true,
|
|
829
|
+
advanced: true
|
|
830
|
+
});
|
|
831
|
+
var ExtendedTestMetadataSchema = z14.object({
|
|
832
|
+
createdAt: z14.coerce.date(),
|
|
833
|
+
updatedAt: z14.coerce.date(),
|
|
834
|
+
schedule: ScheduleSettingsSchema,
|
|
835
|
+
notification: NotificationSettingsSchema,
|
|
836
|
+
createdBy: z14.string(),
|
|
837
|
+
organizationId: z14.string()
|
|
727
838
|
});
|
|
839
|
+
var ResolvedTestSchema = BaseTestMetadataSchema.merge(
|
|
840
|
+
ExtendedTestMetadataSchema
|
|
841
|
+
).merge(
|
|
842
|
+
z14.object({
|
|
843
|
+
steps: z14.array(ResolvedStepSchema)
|
|
844
|
+
})
|
|
845
|
+
);
|
|
846
|
+
var MinimalRunnableResolvedTestSchema = BaseTestMetadataSchema.merge(
|
|
847
|
+
z14.object({
|
|
848
|
+
steps: z14.array(ResolvedStepSchema)
|
|
849
|
+
})
|
|
850
|
+
);
|
|
851
|
+
var UUID_REGEX = /^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/;
|
|
852
|
+
function validateTestOrModuleName(name) {
|
|
853
|
+
name = name.toLowerCase();
|
|
854
|
+
if (name.length === 0 || name.length > 255) {
|
|
855
|
+
throw new Error("Name must be between 1 and 255 characters long");
|
|
856
|
+
}
|
|
857
|
+
const invalidChars = /[<>:"\/\\|?*\x00]/;
|
|
858
|
+
if (invalidChars.test(name)) {
|
|
859
|
+
throw new Error(
|
|
860
|
+
"Name can only contain alphanumeric characters, dashes, and underscores."
|
|
861
|
+
);
|
|
862
|
+
}
|
|
863
|
+
const windowsReservedNames = /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$/i;
|
|
864
|
+
if (windowsReservedNames.test(name)) {
|
|
865
|
+
throw new Error(
|
|
866
|
+
`"${name}" is a reserved name on Windows and cannot be used as a filename.`
|
|
867
|
+
);
|
|
868
|
+
}
|
|
869
|
+
if (/^\.+$/.test(name) || /^\s|\s$/.test(name)) {
|
|
870
|
+
throw new Error("Name cannot start or end with a space or dot.");
|
|
871
|
+
}
|
|
872
|
+
if (name.endsWith(".yaml")) {
|
|
873
|
+
throw new Error('Name cannot end with ".yaml".');
|
|
874
|
+
}
|
|
875
|
+
if (name === "modules") {
|
|
876
|
+
throw new Error(
|
|
877
|
+
"'modules' is a reserved folder name in Momentic. Please choose a different name."
|
|
878
|
+
);
|
|
879
|
+
}
|
|
880
|
+
if (name.match(UUID_REGEX)) {
|
|
881
|
+
throw new Error("Name cannot be a UUID. Please choose a different name.");
|
|
882
|
+
}
|
|
883
|
+
}
|
|
728
884
|
|
|
729
885
|
// ../../packages/types/src/public-api.ts
|
|
730
|
-
import * as z15 from "zod";
|
|
731
886
|
var GeneratorOptionsSchema = z15.object({
|
|
732
887
|
disableCache: z15.boolean()
|
|
733
888
|
});
|
|
@@ -763,14 +918,61 @@ var SplitGoalBodySchema = DynamicContextSchema.pick({
|
|
|
763
918
|
url: true
|
|
764
919
|
}).merge(GeneratorOptionsSchema);
|
|
765
920
|
var SplitGoalResponseSchema = z15.string().array();
|
|
766
|
-
var QueueTestsBodySchema = z15.
|
|
767
|
-
|
|
921
|
+
var QueueTestsBodySchema = z15.union([
|
|
922
|
+
z15.object({
|
|
923
|
+
testPaths: z15.string().array(),
|
|
924
|
+
all: z15.boolean().optional()
|
|
925
|
+
}),
|
|
926
|
+
z15.object({
|
|
927
|
+
testIds: z15.string().array()
|
|
928
|
+
}).describe("deprecated - for backwards compatibility only")
|
|
929
|
+
]);
|
|
930
|
+
var QueueTestsResponseSchema = z15.object({
|
|
931
|
+
message: z15.string(),
|
|
932
|
+
queuedTests: z15.object({
|
|
933
|
+
name: z15.string(),
|
|
934
|
+
id: z15.string()
|
|
935
|
+
}).array()
|
|
768
936
|
});
|
|
769
937
|
var GetTestResponseSchema = ResolvedTestSchema;
|
|
770
|
-
var
|
|
771
|
-
|
|
772
|
-
|
|
938
|
+
var GetAllTestIdsResponseSchema = z15.string().array();
|
|
939
|
+
var ExportTestBodySchema = z15.union([
|
|
940
|
+
z15.object({
|
|
941
|
+
paths: z15.string().array().describe("run specific test paths (e.g. todo-test)")
|
|
942
|
+
}),
|
|
943
|
+
z15.object({
|
|
944
|
+
path: z15.string().describe("deprecated; present for backcompat")
|
|
945
|
+
}),
|
|
946
|
+
z15.object({
|
|
947
|
+
all: z15.boolean().describe("run all tests")
|
|
948
|
+
})
|
|
949
|
+
]);
|
|
950
|
+
var ExportTestResponseSchema = z15.object({
|
|
951
|
+
tests: z15.record(
|
|
952
|
+
z15.string().describe("Test name"),
|
|
953
|
+
z15.string().describe("Test YAML")
|
|
954
|
+
),
|
|
955
|
+
modules: z15.record(
|
|
956
|
+
z15.string().describe("Module name"),
|
|
957
|
+
z15.string().describe("Module YAML")
|
|
958
|
+
)
|
|
773
959
|
});
|
|
960
|
+
var TestWithModulesYAMLSchema = z15.object({
|
|
961
|
+
test: z15.string().describe("test YAML"),
|
|
962
|
+
modules: z15.record(
|
|
963
|
+
z15.string().describe("moduleId"),
|
|
964
|
+
z15.string().describe("module YAML")
|
|
965
|
+
)
|
|
966
|
+
});
|
|
967
|
+
var UpdateTestsBodySchema = TestWithModulesYAMLSchema.array();
|
|
968
|
+
var CreateRunBodySchema = z15.object({
|
|
969
|
+
testPath: z15.string(),
|
|
970
|
+
testId: z15.string()
|
|
971
|
+
}).partial().merge(
|
|
972
|
+
z15.object({
|
|
973
|
+
trigger: z15.nativeEnum(RunTriggerEnum)
|
|
974
|
+
})
|
|
975
|
+
);
|
|
774
976
|
var CreateRunResponseSchema = RunWithTestSchema;
|
|
775
977
|
var GetRunResponseSchema = RunWithTestSchema;
|
|
776
978
|
var UpdateRunBodySchema = z15.object({
|
|
@@ -787,6 +989,416 @@ var CreateScreenshotResponseSchema = z15.object({
|
|
|
787
989
|
key: z15.string()
|
|
788
990
|
});
|
|
789
991
|
|
|
992
|
+
// ../../packages/types/src/step-serialization.ts
|
|
993
|
+
function serializeStep(step) {
|
|
994
|
+
switch (step.type) {
|
|
995
|
+
case "AI_ACTION" /* AI_ACTION */:
|
|
996
|
+
return `AI action: ${step.text}`;
|
|
997
|
+
case "PRESET_ACTION" /* PRESET_ACTION */:
|
|
998
|
+
return serializeCommand(step.command);
|
|
999
|
+
case "RESOLVED_MODULE":
|
|
1000
|
+
return `Module: ${step.moduleId}`;
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
// ../../packages/types/src/test-serialization.ts
|
|
1005
|
+
import { stringify } from "yaml";
|
|
1006
|
+
import { z as z16 } from "zod";
|
|
1007
|
+
var TestSerializationResultSchema = z16.object({
|
|
1008
|
+
test: z16.string().describe("YAML for the test, including metadata and steps"),
|
|
1009
|
+
modules: z16.record(z16.string(), z16.string()).describe("Map of module name to YAML for the module")
|
|
1010
|
+
});
|
|
1011
|
+
var SerializedTestSchema = BaseTestMetadataSchema.merge(
|
|
1012
|
+
z16.object({
|
|
1013
|
+
steps: StepSchema.array(),
|
|
1014
|
+
fileType: z16.literal("momentic/test" /* TEST */)
|
|
1015
|
+
})
|
|
1016
|
+
);
|
|
1017
|
+
var SerializedModuleSchema = ResolvedModuleStepSchema.omit({
|
|
1018
|
+
type: true
|
|
1019
|
+
}).merge(
|
|
1020
|
+
z16.object({
|
|
1021
|
+
schemaVersion: z16.string(),
|
|
1022
|
+
fileType: z16.literal("momentic/module")
|
|
1023
|
+
})
|
|
1024
|
+
);
|
|
1025
|
+
var DeserializedTestSchema = BaseTestMetadataSchema.merge(
|
|
1026
|
+
z16.object({
|
|
1027
|
+
steps: z16.array(z16.record(z16.string(), z16.unknown()))
|
|
1028
|
+
})
|
|
1029
|
+
);
|
|
1030
|
+
var DeserializedModuleSchema = z16.object({
|
|
1031
|
+
moduleId: z16.string().uuid(),
|
|
1032
|
+
name: z16.string(),
|
|
1033
|
+
schemaVersion: z16.string(),
|
|
1034
|
+
steps: z16.array(z16.record(z16.string(), z16.unknown()))
|
|
1035
|
+
});
|
|
1036
|
+
|
|
1037
|
+
// src/api-client.ts
|
|
1038
|
+
var API_VERSION = "v1";
|
|
1039
|
+
var APIClient = class {
|
|
1040
|
+
constructor(params) {
|
|
1041
|
+
this.baseURL = params.baseURL;
|
|
1042
|
+
this.apiKey = params.apiKey;
|
|
1043
|
+
}
|
|
1044
|
+
getRun(runId) {
|
|
1045
|
+
return __async(this, null, function* () {
|
|
1046
|
+
const result = yield this.sendRequest(`/${API_VERSION}/runs/${runId}`, {
|
|
1047
|
+
method: "GET"
|
|
1048
|
+
});
|
|
1049
|
+
return GetRunResponseSchema.parse(result);
|
|
1050
|
+
});
|
|
1051
|
+
}
|
|
1052
|
+
createRun(body) {
|
|
1053
|
+
return __async(this, null, function* () {
|
|
1054
|
+
const result = yield this.sendRequest(`/${API_VERSION}/runs`, {
|
|
1055
|
+
method: "POST",
|
|
1056
|
+
body
|
|
1057
|
+
});
|
|
1058
|
+
return CreateRunResponseSchema.parse(result);
|
|
1059
|
+
});
|
|
1060
|
+
}
|
|
1061
|
+
updateRun(runId, body) {
|
|
1062
|
+
return __async(this, null, function* () {
|
|
1063
|
+
yield this.sendRequest(`/${API_VERSION}/runs/${runId}`, {
|
|
1064
|
+
method: "PATCH",
|
|
1065
|
+
body
|
|
1066
|
+
});
|
|
1067
|
+
});
|
|
1068
|
+
}
|
|
1069
|
+
getTest(testPath) {
|
|
1070
|
+
return __async(this, null, function* () {
|
|
1071
|
+
const result = yield this.sendRequest(`/${API_VERSION}/tests/${testPath}`, {
|
|
1072
|
+
method: "GET"
|
|
1073
|
+
});
|
|
1074
|
+
return GetTestResponseSchema.parse(result);
|
|
1075
|
+
});
|
|
1076
|
+
}
|
|
1077
|
+
getAllTestIds() {
|
|
1078
|
+
return __async(this, null, function* () {
|
|
1079
|
+
const result = yield this.sendRequest(`/${API_VERSION}/tests`, {
|
|
1080
|
+
method: "GET"
|
|
1081
|
+
});
|
|
1082
|
+
return GetAllTestIdsResponseSchema.parse(result);
|
|
1083
|
+
});
|
|
1084
|
+
}
|
|
1085
|
+
getTestYAMLExport(body) {
|
|
1086
|
+
return __async(this, null, function* () {
|
|
1087
|
+
const result = yield this.sendRequest(`/${API_VERSION}/tests/export`, {
|
|
1088
|
+
method: "POST",
|
|
1089
|
+
body
|
|
1090
|
+
});
|
|
1091
|
+
return ExportTestResponseSchema.parse(result);
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
updateTestWithYAML(body) {
|
|
1095
|
+
return __async(this, null, function* () {
|
|
1096
|
+
yield this.sendRequest(`/${API_VERSION}/tests/update`, {
|
|
1097
|
+
method: "POST",
|
|
1098
|
+
body
|
|
1099
|
+
});
|
|
1100
|
+
});
|
|
1101
|
+
}
|
|
1102
|
+
queueTests(body) {
|
|
1103
|
+
return __async(this, null, function* () {
|
|
1104
|
+
const result = yield this.sendRequest(`/${API_VERSION}/tests/queue`, {
|
|
1105
|
+
method: "POST",
|
|
1106
|
+
body
|
|
1107
|
+
});
|
|
1108
|
+
return QueueTestsResponseSchema.parse(result);
|
|
1109
|
+
});
|
|
1110
|
+
}
|
|
1111
|
+
uploadScreenshot(body) {
|
|
1112
|
+
return __async(this, null, function* () {
|
|
1113
|
+
const result = yield this.sendRequest(`/${API_VERSION}/screenshots`, {
|
|
1114
|
+
method: "POST",
|
|
1115
|
+
body
|
|
1116
|
+
});
|
|
1117
|
+
return CreateScreenshotResponseSchema.parse(result);
|
|
1118
|
+
});
|
|
1119
|
+
}
|
|
1120
|
+
sendRequest(path3, options) {
|
|
1121
|
+
return __async(this, null, function* () {
|
|
1122
|
+
const response = yield fetch(`${this.baseURL}${path3}`, {
|
|
1123
|
+
method: options.method,
|
|
1124
|
+
body: options.body ? JSON.stringify(options.body) : void 0,
|
|
1125
|
+
headers: {
|
|
1126
|
+
"Content-Type": "application/json",
|
|
1127
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
1128
|
+
}
|
|
1129
|
+
});
|
|
1130
|
+
if (!response.ok) {
|
|
1131
|
+
let body;
|
|
1132
|
+
try {
|
|
1133
|
+
body = yield response.json();
|
|
1134
|
+
} catch (err) {
|
|
1135
|
+
body = yield response.text();
|
|
1136
|
+
}
|
|
1137
|
+
throw new Error(
|
|
1138
|
+
`Request to ${path3} failed with status ${response.status}: ${JSON.stringify(body)}`
|
|
1139
|
+
);
|
|
1140
|
+
}
|
|
1141
|
+
if (response.status === 204) {
|
|
1142
|
+
return response.text();
|
|
1143
|
+
}
|
|
1144
|
+
return response.json();
|
|
1145
|
+
});
|
|
1146
|
+
}
|
|
1147
|
+
};
|
|
1148
|
+
|
|
1149
|
+
// src/install-browsers.ts
|
|
1150
|
+
import { registry } from "playwright-core/lib/server";
|
|
1151
|
+
function installBrowsers() {
|
|
1152
|
+
return __async(this, null, function* () {
|
|
1153
|
+
const executables = registry.defaultExecutables();
|
|
1154
|
+
yield registry.installDeps(executables, false);
|
|
1155
|
+
yield registry.install(executables, false);
|
|
1156
|
+
});
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
// src/options.ts
|
|
1160
|
+
import { Argument, Option } from "commander";
|
|
1161
|
+
|
|
1162
|
+
// src/constants.ts
|
|
1163
|
+
var DEFAULT_FOLDER_PATH = "momentic";
|
|
1164
|
+
var MODULES_FOLDER_NAME = "modules";
|
|
1165
|
+
|
|
1166
|
+
// src/options.ts
|
|
1167
|
+
var apiKeyOption = new Option(
|
|
1168
|
+
"--api-key <key>",
|
|
1169
|
+
"API key for authentication. If not supplied, attempts to read the MOMENTIC_API_KEY env var."
|
|
1170
|
+
).env("MOMENTIC_API_KEY").makeOptionMandatory(true);
|
|
1171
|
+
var serverAddressOption = new Option(
|
|
1172
|
+
"--server <server>",
|
|
1173
|
+
"Momentic server to use. Leave unchanged unless using Momentic on-premise."
|
|
1174
|
+
).default("https://api.momentic.ai");
|
|
1175
|
+
var yesOption = new Option("-y, --yes", "Skip confirmation prompts.");
|
|
1176
|
+
var testPathsVariadicArgument = new Argument(
|
|
1177
|
+
"<tests...>",
|
|
1178
|
+
"One or more test paths. A test path is a lowercased version of your test name where spaces are replaced with underscores (e.g. `hello-world`)"
|
|
1179
|
+
).argOptional();
|
|
1180
|
+
var testsOrFilesVariadicArgument = new Argument(
|
|
1181
|
+
"<tests...>",
|
|
1182
|
+
"One or more test identifiers. To use tests stored on your local file system, specify file paths to Momentic YAML files or folders to search (this requires the --local option as well). To use tests stored remotely, you may pass test UUIDs or test paths. A test path is a lowercased version of your test name where spaces are replaced with underscores (e.g. `hello-world`)."
|
|
1183
|
+
).argRequired();
|
|
1184
|
+
var filePathsVariadicArgument = new Argument(
|
|
1185
|
+
"<paths...>",
|
|
1186
|
+
"File paths pointing to one or more YAML files containing Momentic tests, or a directory of Momentic YAML files."
|
|
1187
|
+
);
|
|
1188
|
+
var outDirOption = new Option(
|
|
1189
|
+
"-o --out-dir <outDir>",
|
|
1190
|
+
"Root directory to write output files to. Can be absolute or relative to the current directory. Defaults to `momentic`."
|
|
1191
|
+
).default(DEFAULT_FOLDER_PATH);
|
|
1192
|
+
var allOption = new Option(
|
|
1193
|
+
"-a --all",
|
|
1194
|
+
"Select all tests from the cloud Momentic server. Cannot be used together with <tests> arguments."
|
|
1195
|
+
).default(false).preset(true);
|
|
1196
|
+
|
|
1197
|
+
// src/package.json
|
|
1198
|
+
var version = "0.0.18";
|
|
1199
|
+
|
|
1200
|
+
// src/prompt.ts
|
|
1201
|
+
import chalk from "chalk";
|
|
1202
|
+
import readline from "readline/promises";
|
|
1203
|
+
function promptForConfirmation(question, yellow) {
|
|
1204
|
+
return __async(this, null, function* () {
|
|
1205
|
+
if (process.env.CI)
|
|
1206
|
+
return true;
|
|
1207
|
+
const rl = readline.createInterface({
|
|
1208
|
+
input: process.stdin,
|
|
1209
|
+
output: process.stdout
|
|
1210
|
+
});
|
|
1211
|
+
question = `${question} (y/N) `;
|
|
1212
|
+
const questionContent = yellow ? chalk.bold.yellow(question) : question;
|
|
1213
|
+
const answer = yield rl.question(questionContent);
|
|
1214
|
+
rl.close();
|
|
1215
|
+
if (answer.toLowerCase() === "y") {
|
|
1216
|
+
return true;
|
|
1217
|
+
} else {
|
|
1218
|
+
return false;
|
|
1219
|
+
}
|
|
1220
|
+
});
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
// src/pull-test.ts
|
|
1224
|
+
import { existsSync, mkdirSync, writeFileSync } from "fs";
|
|
1225
|
+
import { join } from "path";
|
|
1226
|
+
function fetchAndSaveTestToDisk(_0) {
|
|
1227
|
+
return __async(this, arguments, function* ({
|
|
1228
|
+
testPaths,
|
|
1229
|
+
client,
|
|
1230
|
+
outDir,
|
|
1231
|
+
all
|
|
1232
|
+
}) {
|
|
1233
|
+
const rootDir = outDir != null ? outDir : DEFAULT_FOLDER_PATH;
|
|
1234
|
+
if (!existsSync(rootDir)) {
|
|
1235
|
+
mkdirSync(rootDir, { recursive: true });
|
|
1236
|
+
}
|
|
1237
|
+
const rootModuleDir = join(rootDir, MODULES_FOLDER_NAME);
|
|
1238
|
+
if (!existsSync(rootModuleDir)) {
|
|
1239
|
+
mkdirSync(rootModuleDir, { recursive: true });
|
|
1240
|
+
}
|
|
1241
|
+
const { tests, modules } = yield client.getTestYAMLExport({
|
|
1242
|
+
paths: testPaths,
|
|
1243
|
+
all
|
|
1244
|
+
});
|
|
1245
|
+
const numTests = Object.keys(tests).length;
|
|
1246
|
+
for (const [testName, testYAML] of Object.entries(tests)) {
|
|
1247
|
+
const testFilePath = join(rootDir, nameToYAMLFileName(testName));
|
|
1248
|
+
if (!(yield checkAndPromptForOverwrite(testFilePath))) {
|
|
1249
|
+
consoleLogger.error("Pull cancelled");
|
|
1250
|
+
return;
|
|
1251
|
+
}
|
|
1252
|
+
writeFileSync(testFilePath, testYAML, "utf-8");
|
|
1253
|
+
consoleLogger.info(`Wrote '${testFilePath}'`);
|
|
1254
|
+
}
|
|
1255
|
+
const numModules = Object.keys(modules).length;
|
|
1256
|
+
for (const [moduleName, moduleYAML] of Object.entries(modules)) {
|
|
1257
|
+
const modulePath = join(rootModuleDir, nameToYAMLFileName(moduleName));
|
|
1258
|
+
if (!(yield checkAndPromptForOverwrite(modulePath))) {
|
|
1259
|
+
consoleLogger.error("Pull cancelled");
|
|
1260
|
+
return;
|
|
1261
|
+
}
|
|
1262
|
+
writeFileSync(modulePath, moduleYAML, "utf-8");
|
|
1263
|
+
consoleLogger.info(`Wrote '${modulePath}'`);
|
|
1264
|
+
}
|
|
1265
|
+
consoleLogger.info(
|
|
1266
|
+
`Pulled ${numTests} test${numTests > 1 ? "s" : ""}${numModules ? ` and ${numModules} module${numModules > 1 ? "s" : ""}` : ""}!`
|
|
1267
|
+
);
|
|
1268
|
+
});
|
|
1269
|
+
}
|
|
1270
|
+
function checkAndPromptForOverwrite(path3) {
|
|
1271
|
+
return __async(this, null, function* () {
|
|
1272
|
+
if (!existsSync(path3)) {
|
|
1273
|
+
return true;
|
|
1274
|
+
}
|
|
1275
|
+
return promptForConfirmation(
|
|
1276
|
+
`File '${path3.replace(/(\s+)/g, "\\$1")}' already exists. Overwrite?`,
|
|
1277
|
+
true
|
|
1278
|
+
);
|
|
1279
|
+
});
|
|
1280
|
+
}
|
|
1281
|
+
function nameToYAMLFileName(name) {
|
|
1282
|
+
return `${name.toLowerCase().replaceAll(" ", "-")}.yaml`;
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
// src/push-test.ts
|
|
1286
|
+
import { existsSync as existsSync2, readFileSync, readdirSync, statSync } from "fs";
|
|
1287
|
+
import path from "path";
|
|
1288
|
+
function saveTestToServer(params) {
|
|
1289
|
+
return __async(this, null, function* () {
|
|
1290
|
+
const rootTestPaths = /* @__PURE__ */ new Set();
|
|
1291
|
+
for (const testPathOrDir of params.paths) {
|
|
1292
|
+
const fullPath = path.resolve(testPathOrDir);
|
|
1293
|
+
if (fullPath && existsSync2(fullPath) && statSync(fullPath).isDirectory()) {
|
|
1294
|
+
const files = readdirSync(fullPath);
|
|
1295
|
+
for (const file of files) {
|
|
1296
|
+
if (file.endsWith(".yaml")) {
|
|
1297
|
+
rootTestPaths.add(path.join(fullPath, file));
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
if (fullPath.endsWith(".yaml")) {
|
|
1302
|
+
if (!existsSync2(fullPath) || !statSync(fullPath).isFile()) {
|
|
1303
|
+
throw new Error(`File not found or unreadable: ${fullPath}`);
|
|
1304
|
+
}
|
|
1305
|
+
rootTestPaths.add(fullPath);
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
consoleLogger.info(`Found ${rootTestPaths.size} test(s) to push:`);
|
|
1309
|
+
rootTestPaths.forEach((testPath) => consoleLogger.info(` - ${testPath}`));
|
|
1310
|
+
consoleLogger.info(`Loading file contents and resolving dependent modules`);
|
|
1311
|
+
const testUpdates = Array.from(rootTestPaths).map(readTestWithModules);
|
|
1312
|
+
const moduleIds = new Set(
|
|
1313
|
+
testUpdates.flatMap((update) => Object.keys(update.modules))
|
|
1314
|
+
);
|
|
1315
|
+
consoleLogger.info(
|
|
1316
|
+
`Resolved ${rootTestPaths.size} test(s) and ${moduleIds.size} module(s)`
|
|
1317
|
+
);
|
|
1318
|
+
if (!params.yes) {
|
|
1319
|
+
const warning = "Pushing tests overwrites tests on production and will instantly affect scheduled runs. Continue?";
|
|
1320
|
+
if (!(yield promptForConfirmation(warning, true))) {
|
|
1321
|
+
consoleLogger.error("Push cancelled");
|
|
1322
|
+
return;
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
yield params.client.updateTestWithYAML(testUpdates);
|
|
1326
|
+
consoleLogger.info("Update successful!");
|
|
1327
|
+
});
|
|
1328
|
+
}
|
|
1329
|
+
function readTestWithModules(testFilePath) {
|
|
1330
|
+
let testContent;
|
|
1331
|
+
try {
|
|
1332
|
+
testContent = readFileSync(testFilePath, "utf8");
|
|
1333
|
+
testContent = testContent.replace(/\r\n|\r/g, "\n");
|
|
1334
|
+
} catch (err) {
|
|
1335
|
+
throw new Error(`Could not read test file ${testFilePath}: ${err}`);
|
|
1336
|
+
}
|
|
1337
|
+
const moduleIds = /* @__PURE__ */ new Set();
|
|
1338
|
+
const moduleIdRegex = /moduleId: (.*)/g;
|
|
1339
|
+
let moduleIdMatch;
|
|
1340
|
+
while ((moduleIdMatch = moduleIdRegex.exec(testContent)) !== null) {
|
|
1341
|
+
moduleIds.add(moduleIdMatch[1].trim());
|
|
1342
|
+
}
|
|
1343
|
+
const modules = {};
|
|
1344
|
+
if (moduleIds.size > 0) {
|
|
1345
|
+
const modulesDir = findModulesDir(testFilePath);
|
|
1346
|
+
moduleIds.forEach((moduleId) => {
|
|
1347
|
+
if (!modules[moduleId]) {
|
|
1348
|
+
modules[moduleId] = getModuleFile(modulesDir, moduleId);
|
|
1349
|
+
}
|
|
1350
|
+
});
|
|
1351
|
+
}
|
|
1352
|
+
return {
|
|
1353
|
+
test: testContent,
|
|
1354
|
+
modules
|
|
1355
|
+
};
|
|
1356
|
+
}
|
|
1357
|
+
function findModulesDir(startDir) {
|
|
1358
|
+
let currentDir = startDir;
|
|
1359
|
+
while (currentDir !== "/") {
|
|
1360
|
+
const modulesDir = path.join(currentDir, MODULES_FOLDER_NAME);
|
|
1361
|
+
if (existsSync2(modulesDir)) {
|
|
1362
|
+
return modulesDir;
|
|
1363
|
+
} else {
|
|
1364
|
+
currentDir = path.dirname(currentDir);
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
throw new Error(
|
|
1368
|
+
`No '${MODULES_FOLDER_NAME}' directory found in the path ${startDir} or any of its parents`
|
|
1369
|
+
);
|
|
1370
|
+
}
|
|
1371
|
+
function getModuleFile(dir, moduleId) {
|
|
1372
|
+
const files = readdirSync(dir);
|
|
1373
|
+
for (const file of files) {
|
|
1374
|
+
const filePath = path.join(dir, file);
|
|
1375
|
+
const fileContent = readFileSync(filePath, "utf8");
|
|
1376
|
+
if (fileContent.includes(moduleId)) {
|
|
1377
|
+
return fileContent;
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
throw new Error(
|
|
1381
|
+
`Could not find module file for module ${moduleId} in ${dir}`
|
|
1382
|
+
);
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
// src/run-tests-locally.ts
|
|
1386
|
+
import exec from "@actions/exec";
|
|
1387
|
+
import io from "@actions/io";
|
|
1388
|
+
import { existsSync as existsSync3, statSync as statSync2 } from "fs";
|
|
1389
|
+
import quote from "quote";
|
|
1390
|
+
import parseArgsStringToArgv2 from "string-argv";
|
|
1391
|
+
import waitOnFn from "wait-on";
|
|
1392
|
+
|
|
1393
|
+
// ../../packages/web-agent/src/browsers/chrome.ts
|
|
1394
|
+
import { distance as distance2 } from "fastest-levenshtein";
|
|
1395
|
+
import {
|
|
1396
|
+
chromium,
|
|
1397
|
+
devices
|
|
1398
|
+
} from "playwright";
|
|
1399
|
+
import { addExtra } from "playwright-extra";
|
|
1400
|
+
import pluginStealth from "puppeteer-extra-plugin-stealth";
|
|
1401
|
+
|
|
790
1402
|
// ../../packages/web-agent/src/utils/url.ts
|
|
791
1403
|
var urlChanged = (url1, url2) => {
|
|
792
1404
|
const { hostname, pathname } = new URL(url1);
|
|
@@ -794,20 +1406,49 @@ var urlChanged = (url1, url2) => {
|
|
|
794
1406
|
return hostname !== hostname2 || pathname !== pathname2;
|
|
795
1407
|
};
|
|
796
1408
|
|
|
1409
|
+
// ../../packages/web-agent/src/browsers/a11y.ts
|
|
1410
|
+
import { distance } from "fastest-levenshtein";
|
|
1411
|
+
|
|
1412
|
+
// ../../packages/web-agent/src/browsers/constants.ts
|
|
1413
|
+
var RETINA_WINDOW_SCALE_FACTOR = 2;
|
|
1414
|
+
var MAX_LOAD_TIMEOUT_MS = 8e3;
|
|
1415
|
+
var NETWORK_STABLE_DURATION_MS = 1250;
|
|
1416
|
+
var NETWORK_IDLE_TIMEOUT_MS = 3e3;
|
|
1417
|
+
var CHECK_INTERVAL_MS = 250;
|
|
1418
|
+
var A11Y_LOAD_TIMEOUT_MS = 1e3;
|
|
1419
|
+
var A11Y_STABLE_TIMEOUT_MS = NETWORK_IDLE_TIMEOUT_MS;
|
|
1420
|
+
var A11Y_STABLE_DURATION_MS = NETWORK_STABLE_DURATION_MS;
|
|
1421
|
+
var BROWSER_ACTION_TIMEOUT_MS = MAX_LOAD_TIMEOUT_MS;
|
|
1422
|
+
var COMPLICATED_BROWSER_ACTION_TIMEOUT_MS = MAX_LOAD_TIMEOUT_MS;
|
|
1423
|
+
var HIGHLIGHT_DURATION_MS = 3e3;
|
|
1424
|
+
var MAX_LEVENSHTEIN_DISTANCE = 300;
|
|
1425
|
+
var MAX_LEVENSHTEIN_CHANGE_RATIO = 0.2;
|
|
1426
|
+
var MAX_LEVENSHTEIN_FIELD_CHANGE_RATIO = 0.1;
|
|
1427
|
+
var MIN_SIMILARITY_SCORE_TO_REUSE = 5;
|
|
1428
|
+
var CHROME_INTERNAL_URLS = /* @__PURE__ */ new Set([
|
|
1429
|
+
"about:blank",
|
|
1430
|
+
"chrome-error://chromewebdata/"
|
|
1431
|
+
]);
|
|
1432
|
+
var MAX_BROWSER_ACTION_ATTEMPTS = 2;
|
|
1433
|
+
|
|
797
1434
|
// ../../packages/web-agent/src/browsers/a11y.ts
|
|
798
1435
|
var bannedProperties = /* @__PURE__ */ new Set(["focusable"]);
|
|
799
1436
|
var alwaysInterestingRoles = /* @__PURE__ */ new Set([
|
|
800
1437
|
"textbox",
|
|
801
1438
|
"checkbox",
|
|
1439
|
+
"combobox",
|
|
802
1440
|
"button",
|
|
803
|
-
"link"
|
|
1441
|
+
"link",
|
|
1442
|
+
"combobox"
|
|
804
1443
|
]);
|
|
805
|
-
var rolesToOmitID = /* @__PURE__ */ new Set(["paragraph", "
|
|
1444
|
+
var rolesToOmitID = /* @__PURE__ */ new Set(["paragraph", "option"]);
|
|
806
1445
|
var defaultA11yNodeSerializeParams = {
|
|
807
1446
|
indentLevel: 0,
|
|
808
1447
|
noID: false,
|
|
809
1448
|
noChildren: false,
|
|
810
|
-
noProperties: false
|
|
1449
|
+
noProperties: false,
|
|
1450
|
+
maxLevel: void 0,
|
|
1451
|
+
neighbors: void 0
|
|
811
1452
|
};
|
|
812
1453
|
var ProcessedA11yNode = class {
|
|
813
1454
|
constructor(params) {
|
|
@@ -841,6 +1482,7 @@ var ProcessedA11yNode = class {
|
|
|
841
1482
|
return !!this.name.trim() || !!this.content;
|
|
842
1483
|
}
|
|
843
1484
|
serialize(opts = defaultA11yNodeSerializeParams) {
|
|
1485
|
+
var _a, _b;
|
|
844
1486
|
const { indentLevel, noChildren, noProperties, noID } = Object.assign(
|
|
845
1487
|
{},
|
|
846
1488
|
defaultA11yNodeSerializeParams,
|
|
@@ -878,17 +1520,32 @@ var ProcessedA11yNode = class {
|
|
|
878
1520
|
}
|
|
879
1521
|
});
|
|
880
1522
|
}
|
|
881
|
-
|
|
1523
|
+
const maxLevelExceeded = opts.maxLevel !== void 0 && indentLevel / 2 >= opts.maxLevel;
|
|
1524
|
+
if (this.children.length === 0 || noChildren || maxLevelExceeded) {
|
|
882
1525
|
s += " />\n";
|
|
883
1526
|
return s;
|
|
884
1527
|
} else {
|
|
885
1528
|
s += ">\n";
|
|
1529
|
+
for (const child of this.children) {
|
|
1530
|
+
s += child.serialize(__spreadProps(__spreadValues({}, opts), { indentLevel: indentLevel + 2 }));
|
|
1531
|
+
}
|
|
1532
|
+
s += `${indent}</${this.role}>
|
|
1533
|
+
`;
|
|
886
1534
|
}
|
|
887
|
-
|
|
888
|
-
|
|
1535
|
+
if (opts.neighbors !== void 0 && opts.neighbors > 0 && this.parent) {
|
|
1536
|
+
const currentIndex = this.parent.children.findIndex(
|
|
1537
|
+
(n) => n.id === this.id
|
|
1538
|
+
);
|
|
1539
|
+
const before = currentIndex > 0 ? (_a = this.parent.children[currentIndex - 1]) == null ? void 0 : _a.serialize(__spreadProps(__spreadValues({}, opts), {
|
|
1540
|
+
neighbors: 0
|
|
1541
|
+
})) : "";
|
|
1542
|
+
const after = currentIndex < this.parent.children.length - 1 ? (_b = this.parent.children[currentIndex + 1]) == null ? void 0 : _b.serialize(__spreadProps(__spreadValues({}, opts), {
|
|
1543
|
+
neighbors: 0
|
|
1544
|
+
})) : "";
|
|
1545
|
+
return `${before ? before : ""}
|
|
1546
|
+
${s}
|
|
1547
|
+
${after ? after : ""}`;
|
|
889
1548
|
}
|
|
890
|
-
s += `${indent}</${this.role}>
|
|
891
|
-
`;
|
|
892
1549
|
return s;
|
|
893
1550
|
}
|
|
894
1551
|
};
|
|
@@ -1043,6 +1700,61 @@ function processA11yTree(graph) {
|
|
|
1043
1700
|
}
|
|
1044
1701
|
return new ProcessedA11yTree(processedRoot[0], outputNodeMap);
|
|
1045
1702
|
}
|
|
1703
|
+
var saveNodeDetailsToCache = (node, target) => {
|
|
1704
|
+
target.id = parseInt(node.id);
|
|
1705
|
+
target.content = node.content;
|
|
1706
|
+
target.name = node.name;
|
|
1707
|
+
target.role = node.role;
|
|
1708
|
+
target.numChildren = node.children.length;
|
|
1709
|
+
target.serializedForm = node.serialize({
|
|
1710
|
+
noID: true,
|
|
1711
|
+
maxLevel: 1,
|
|
1712
|
+
neighbors: 1
|
|
1713
|
+
// only 1 neighbor is supported right now
|
|
1714
|
+
});
|
|
1715
|
+
};
|
|
1716
|
+
var getNodeComparisonScore = (node, target) => {
|
|
1717
|
+
var _a;
|
|
1718
|
+
let score = 1;
|
|
1719
|
+
if (node.role === target.role) {
|
|
1720
|
+
score++;
|
|
1721
|
+
}
|
|
1722
|
+
const attrs = ["name", "content"];
|
|
1723
|
+
for (const attr of attrs) {
|
|
1724
|
+
if (!((_a = node[attr]) == null ? void 0 : _a.trim())) {
|
|
1725
|
+
continue;
|
|
1726
|
+
}
|
|
1727
|
+
const fieldChangeRatio = distance(node[attr], target[attr]) / Math.min(node[attr].length, target[attr].length);
|
|
1728
|
+
if (fieldChangeRatio === 0) {
|
|
1729
|
+
score += 2;
|
|
1730
|
+
} else if (fieldChangeRatio <= MAX_LEVENSHTEIN_FIELD_CHANGE_RATIO) {
|
|
1731
|
+
score++;
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
if (target.numChildren !== void 0) {
|
|
1735
|
+
if (node.children.length === target.numChildren && target.numChildren > 0) {
|
|
1736
|
+
score++;
|
|
1737
|
+
} else if (target.numChildren > 0 && node.children.length === 0) {
|
|
1738
|
+
score--;
|
|
1739
|
+
} else if (Math.abs(node.children.length - target.numChildren) > 2) {
|
|
1740
|
+
score--;
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
if (target.serializedForm) {
|
|
1744
|
+
const serializedNode = node.serialize({
|
|
1745
|
+
noID: true,
|
|
1746
|
+
maxLevel: 1,
|
|
1747
|
+
neighbors: 1
|
|
1748
|
+
});
|
|
1749
|
+
const levenshteinRatio = distance(serializedNode, target.serializedForm) / Math.min(serializedNode.length, target.serializedForm.length);
|
|
1750
|
+
if (levenshteinRatio === 0) {
|
|
1751
|
+
score += 2;
|
|
1752
|
+
} else if (levenshteinRatio <= MAX_LEVENSHTEIN_FIELD_CHANGE_RATIO) {
|
|
1753
|
+
score++;
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
return score;
|
|
1757
|
+
};
|
|
1046
1758
|
|
|
1047
1759
|
// ../../packages/web-agent/src/browsers/cdp.ts
|
|
1048
1760
|
var GREEN = { r: 147, g: 196, b: 125, a: 0.55 };
|
|
@@ -1062,24 +1774,6 @@ var NODE_HIGHLIGHT_CONFIG = {
|
|
|
1062
1774
|
shapeMarginColor: GREEN
|
|
1063
1775
|
};
|
|
1064
1776
|
|
|
1065
|
-
// ../../packages/web-agent/src/browsers/constants.ts
|
|
1066
|
-
var RETINA_WINDOW_SCALE_FACTOR = 2;
|
|
1067
|
-
var MAX_LOAD_TIMEOUT_MS = 8e3;
|
|
1068
|
-
var NETWORK_STABLE_DURATION_MS = 1250;
|
|
1069
|
-
var NETWORK_IDLE_TIMEOUT_MS = 3e3;
|
|
1070
|
-
var CHECK_INTERVAL_MS = 250;
|
|
1071
|
-
var A11Y_LOAD_TIMEOUT_MS = 1e3;
|
|
1072
|
-
var A11Y_STABLE_TIMEOUT_MS = NETWORK_IDLE_TIMEOUT_MS;
|
|
1073
|
-
var A11Y_STABLE_DURATION_MS = NETWORK_STABLE_DURATION_MS;
|
|
1074
|
-
var BROWSER_ACTION_TIMEOUT_MS = MAX_LOAD_TIMEOUT_MS;
|
|
1075
|
-
var COMPLICATED_BROWSER_ACTION_TIMEOUT_MS = MAX_LOAD_TIMEOUT_MS;
|
|
1076
|
-
var HIGHLIGHT_DURATION_MS = 3e3;
|
|
1077
|
-
var CHROME_INTERNAL_URLS = /* @__PURE__ */ new Set([
|
|
1078
|
-
"about:blank",
|
|
1079
|
-
"chrome-error://chromewebdata/"
|
|
1080
|
-
]);
|
|
1081
|
-
var MAX_BROWSER_ACTION_ATTEMPTS = 2;
|
|
1082
|
-
|
|
1083
1777
|
// ../../packages/web-agent/src/browsers/utils/time.ts
|
|
1084
1778
|
var sleep = (ms = 1e3) => {
|
|
1085
1779
|
return new Promise((resolve) => setTimeout(() => resolve(), ms));
|
|
@@ -1174,6 +1868,8 @@ function isRequestRelevantForPageLoad(request, currentURL) {
|
|
|
1174
1868
|
}
|
|
1175
1869
|
|
|
1176
1870
|
// ../../packages/web-agent/src/browsers/chrome.ts
|
|
1871
|
+
var chromiumWithExtra = addExtra(chromium);
|
|
1872
|
+
chromiumWithExtra.use(pluginStealth());
|
|
1177
1873
|
function initCDPSession(cdpClient) {
|
|
1178
1874
|
return __async(this, null, function* () {
|
|
1179
1875
|
yield cdpClient.send("Accessibility.enable");
|
|
@@ -1204,7 +1900,10 @@ var _ChromeBrowser = class _ChromeBrowser {
|
|
|
1204
1900
|
*/
|
|
1205
1901
|
static init(_0, _1, _2) {
|
|
1206
1902
|
return __async(this, arguments, function* (baseURL, logger, onScreenshot, timeout = MAX_LOAD_TIMEOUT_MS) {
|
|
1207
|
-
const browser = yield
|
|
1903
|
+
const browser = yield chromiumWithExtra.launch({
|
|
1904
|
+
headless: true,
|
|
1905
|
+
handleSIGTERM: false
|
|
1906
|
+
});
|
|
1208
1907
|
const context = yield browser.newContext({
|
|
1209
1908
|
viewport: {
|
|
1210
1909
|
width: 1920,
|
|
@@ -1212,7 +1911,7 @@ var _ChromeBrowser = class _ChromeBrowser {
|
|
|
1212
1911
|
},
|
|
1213
1912
|
// comment out the below if you are on Mac OS but you're using a monitor
|
|
1214
1913
|
deviceScaleFactor: process.platform === "darwin" ? RETINA_WINDOW_SCALE_FACTOR : 1,
|
|
1215
|
-
userAgent:
|
|
1914
|
+
userAgent: devices["Desktop Chrome"].userAgent,
|
|
1216
1915
|
geolocation: { latitude: 37.7749, longitude: -122.4194 },
|
|
1217
1916
|
// san francisco
|
|
1218
1917
|
locale: "en-US",
|
|
@@ -1367,7 +2066,11 @@ var _ChromeBrowser = class _ChromeBrowser {
|
|
|
1367
2066
|
return __async(this, arguments, function* (text, options = {}) {
|
|
1368
2067
|
const { clearContent = true, pressKeysSequentially = false } = options;
|
|
1369
2068
|
if (clearContent) {
|
|
1370
|
-
|
|
2069
|
+
if (process.platform === "darwin") {
|
|
2070
|
+
yield this.page.keyboard.press("Meta+A");
|
|
2071
|
+
} else {
|
|
2072
|
+
yield this.page.keyboard.press("Control+A");
|
|
2073
|
+
}
|
|
1371
2074
|
yield this.page.keyboard.press("Backspace");
|
|
1372
2075
|
}
|
|
1373
2076
|
if (pressKeysSequentially) {
|
|
@@ -1381,7 +2084,7 @@ var _ChromeBrowser = class _ChromeBrowser {
|
|
|
1381
2084
|
return __async(this, arguments, function* (index, options = {}) {
|
|
1382
2085
|
const node = this.nodeMap.get(`${index}`);
|
|
1383
2086
|
if (!node) {
|
|
1384
|
-
throw new Error(`Could not find node
|
|
2087
|
+
throw new Error(`Could not find DOM node during click: ${index}`);
|
|
1385
2088
|
}
|
|
1386
2089
|
const nodeClicked = yield this.clickUsingCDP(node, options);
|
|
1387
2090
|
yield this.highlightNode(nodeClicked);
|
|
@@ -1392,7 +2095,9 @@ var _ChromeBrowser = class _ChromeBrowser {
|
|
|
1392
2095
|
return __async(this, null, function* () {
|
|
1393
2096
|
const node = this.nodeMap.get(`${index}`);
|
|
1394
2097
|
if (!node) {
|
|
1395
|
-
throw new Error(
|
|
2098
|
+
throw new Error(
|
|
2099
|
+
`Could not find DOM node while selecting option: ${index}`
|
|
2100
|
+
);
|
|
1396
2101
|
}
|
|
1397
2102
|
if (!node.backendNodeID) {
|
|
1398
2103
|
throw new Error(
|
|
@@ -1407,27 +2112,41 @@ var _ChromeBrowser = class _ChromeBrowser {
|
|
|
1407
2112
|
return node.serialize({ noChildren: true, noProperties: true, noID: true });
|
|
1408
2113
|
});
|
|
1409
2114
|
}
|
|
1410
|
-
|
|
1411
|
-
return __async(this, null, function* () {
|
|
1412
|
-
try {
|
|
1413
|
-
yield this.highlightByA11yID(target.id);
|
|
1414
|
-
} catch (err) {
|
|
1415
|
-
this.logger.warn({ err, target }, "Failed to highlight target");
|
|
1416
|
-
}
|
|
1417
|
-
});
|
|
1418
|
-
}
|
|
1419
|
-
highlightByA11yID(index) {
|
|
2115
|
+
scrollIntoView(target) {
|
|
1420
2116
|
return __async(this, null, function* () {
|
|
1421
|
-
const
|
|
2117
|
+
const id = yield this.resolveCachedTargetToID(target);
|
|
2118
|
+
const node = this.nodeMap.get(`${id}`);
|
|
1422
2119
|
if (!node) {
|
|
1423
|
-
throw new Error(`Could not find node in DOM with
|
|
2120
|
+
throw new Error(`Could not find node in DOM with a11y id: ${id}`);
|
|
1424
2121
|
}
|
|
1425
2122
|
if (!node.backendNodeID) {
|
|
1426
2123
|
throw new Error(
|
|
1427
|
-
`
|
|
2124
|
+
`Focus target missing backend node id: ${node.getLogForm()}`
|
|
1428
2125
|
);
|
|
1429
2126
|
}
|
|
1430
|
-
yield this.
|
|
2127
|
+
const locator = yield this.getLocatorFromBackendID(node.backendNodeID);
|
|
2128
|
+
yield locator.scrollIntoViewIfNeeded({
|
|
2129
|
+
timeout: BROWSER_ACTION_TIMEOUT_MS
|
|
2130
|
+
});
|
|
2131
|
+
});
|
|
2132
|
+
}
|
|
2133
|
+
highlight(target) {
|
|
2134
|
+
return __async(this, null, function* () {
|
|
2135
|
+
try {
|
|
2136
|
+
const id = yield this.resolveCachedTargetToID(target);
|
|
2137
|
+
const node = this.nodeMap.get(`${id}`);
|
|
2138
|
+
if (!node) {
|
|
2139
|
+
throw new Error(`Could not find DOM node during highlight: ${id}`);
|
|
2140
|
+
}
|
|
2141
|
+
if (!node.backendNodeID) {
|
|
2142
|
+
throw new Error(
|
|
2143
|
+
`Select target missing backend node id: ${node.getLogForm()}`
|
|
2144
|
+
);
|
|
2145
|
+
}
|
|
2146
|
+
yield this.highlightNode(node);
|
|
2147
|
+
} catch (err) {
|
|
2148
|
+
this.logger.warn({ err, target }, "Failed to highlight target");
|
|
2149
|
+
}
|
|
1431
2150
|
});
|
|
1432
2151
|
}
|
|
1433
2152
|
highlightNode(node) {
|
|
@@ -1438,7 +2157,9 @@ var _ChromeBrowser = class _ChromeBrowser {
|
|
|
1438
2157
|
backendNodeId: node.backendNodeID
|
|
1439
2158
|
});
|
|
1440
2159
|
} catch (err) {
|
|
1441
|
-
this.logger.warn(
|
|
2160
|
+
this.logger.warn(
|
|
2161
|
+
"Failed to add node highlight, a page navigation likely occurred. This is non-fatal for tests."
|
|
2162
|
+
);
|
|
1442
2163
|
}
|
|
1443
2164
|
const hideHighlight = () => __async(this, null, function* () {
|
|
1444
2165
|
try {
|
|
@@ -1469,21 +2190,9 @@ var _ChromeBrowser = class _ChromeBrowser {
|
|
|
1469
2190
|
const requestFiredListener = (request) => {
|
|
1470
2191
|
var _a;
|
|
1471
2192
|
if (!isRequestRelevantForPageLoad(request, this.url)) {
|
|
1472
|
-
this.logger.debug(
|
|
1473
|
-
{
|
|
1474
|
-
uri: serializeRequest(request)
|
|
1475
|
-
},
|
|
1476
|
-
"Ignoring request for page load network stability"
|
|
1477
|
-
);
|
|
1478
2193
|
return;
|
|
1479
2194
|
}
|
|
1480
2195
|
const key = serializeRequest(request);
|
|
1481
|
-
this.logger.debug(
|
|
1482
|
-
{
|
|
1483
|
-
uri: key
|
|
1484
|
-
},
|
|
1485
|
-
"Request fired on page load, delaying network stability"
|
|
1486
|
-
);
|
|
1487
2196
|
firedRequests.set(key, ((_a = firedRequests.get(key)) != null ? _a : 0) + 1);
|
|
1488
2197
|
lastRequestReceived = Date.now();
|
|
1489
2198
|
};
|
|
@@ -1515,7 +2224,6 @@ var _ChromeBrowser = class _ChromeBrowser {
|
|
|
1515
2224
|
let anyDifference = false;
|
|
1516
2225
|
for (const key of firedRequests.keys()) {
|
|
1517
2226
|
if (firedRequests.get(key) !== finishedRequests.get(key)) {
|
|
1518
|
-
this.logger.debug({ uri: key }, "Waiting on request to finish");
|
|
1519
2227
|
anyDifference = true;
|
|
1520
2228
|
unfinishedRequests.add(key);
|
|
1521
2229
|
}
|
|
@@ -1531,11 +2239,13 @@ var _ChromeBrowser = class _ChromeBrowser {
|
|
|
1531
2239
|
return true;
|
|
1532
2240
|
}
|
|
1533
2241
|
}
|
|
1534
|
-
if (!rejected) {
|
|
2242
|
+
if (!rejected && unfinishedRequests.size > 0) {
|
|
1535
2243
|
this.logger.warn(
|
|
1536
2244
|
{
|
|
1537
2245
|
url: this.url,
|
|
1538
|
-
|
|
2246
|
+
unfinishedRequests: JSON.stringify(
|
|
2247
|
+
Array.from(unfinishedRequests.entries())
|
|
2248
|
+
)
|
|
1539
2249
|
},
|
|
1540
2250
|
"Timeout elapsed waiting for network idle, continuing anyways..."
|
|
1541
2251
|
);
|
|
@@ -1550,33 +2260,146 @@ var _ChromeBrowser = class _ChromeBrowser {
|
|
|
1550
2260
|
}
|
|
1551
2261
|
if (!rejected && urlChanged(this.url, startURL)) {
|
|
1552
2262
|
this.logger.debug(
|
|
2263
|
+
{ startURL, newURL: this.url },
|
|
1553
2264
|
`Detected url change in wrapPossibleNavigation, waiting for load state`
|
|
1554
2265
|
);
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
2266
|
+
const remainingTimeout = Math.max(
|
|
2267
|
+
timeoutMS - (Date.now() - startTime),
|
|
2268
|
+
0
|
|
2269
|
+
);
|
|
2270
|
+
if (remainingTimeout > 0) {
|
|
2271
|
+
try {
|
|
2272
|
+
yield this.page.waitForLoadState("load", {
|
|
2273
|
+
timeout: remainingTimeout
|
|
2274
|
+
});
|
|
2275
|
+
} catch (e) {
|
|
2276
|
+
this.logger.warn(
|
|
2277
|
+
{ url: this.url },
|
|
2278
|
+
"Timeout elapsed waiting for load state to fire, continuing anyways..."
|
|
2279
|
+
);
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
return unwrapAndThrowError(retPromise);
|
|
2284
|
+
});
|
|
2285
|
+
}
|
|
2286
|
+
/**
|
|
2287
|
+
* Given a potentially cached a11y ID, resolve it to an actual ID, checking that it is still valid.
|
|
2288
|
+
* If the ID is no longer valid, try to auto-heal with heuristics.
|
|
2289
|
+
* Throws if no auto-healing is possible.
|
|
2290
|
+
*/
|
|
2291
|
+
resolveCachedTargetToID(target) {
|
|
2292
|
+
return __async(this, null, function* () {
|
|
2293
|
+
if (!target.name && !target.role && !target.content) {
|
|
2294
|
+
const node = this.nodeMap.get(`${target.id}`);
|
|
2295
|
+
if (!node) {
|
|
2296
|
+
throw new Error(
|
|
2297
|
+
`Resolving target failed, fresh value did not exist in node map: ${target.id}`
|
|
2298
|
+
);
|
|
2299
|
+
}
|
|
2300
|
+
saveNodeDetailsToCache(node, target);
|
|
2301
|
+
return target.id;
|
|
2302
|
+
}
|
|
2303
|
+
yield this.getA11yTree();
|
|
2304
|
+
const proposedNode = this.nodeMap.get(`${target.id}`);
|
|
2305
|
+
if (proposedNode) {
|
|
2306
|
+
const comparisonScore = getNodeComparisonScore(proposedNode, target);
|
|
2307
|
+
if (comparisonScore >= MIN_SIMILARITY_SCORE_TO_REUSE) {
|
|
2308
|
+
this.logger.debug(
|
|
2309
|
+
{ target, proposedNode: proposedNode.getLogForm(), comparisonScore },
|
|
2310
|
+
"Resolved cached a11y target to node with exact same id"
|
|
2311
|
+
);
|
|
2312
|
+
saveNodeDetailsToCache(proposedNode, target);
|
|
2313
|
+
return target.id;
|
|
2314
|
+
}
|
|
2315
|
+
}
|
|
2316
|
+
let closestLevenshteinDistance = Infinity;
|
|
2317
|
+
let smallestLevenshteinRatio = Infinity;
|
|
2318
|
+
let closestNode;
|
|
2319
|
+
for (const node of this.nodeMap.values()) {
|
|
2320
|
+
const comparisonScore = getNodeComparisonScore(node, target);
|
|
2321
|
+
if (comparisonScore >= MIN_SIMILARITY_SCORE_TO_REUSE) {
|
|
2322
|
+
this.logger.debug(
|
|
2323
|
+
{ newNode: node.getLogForm(), target, comparisonScore },
|
|
2324
|
+
"Resolved cached a11y target to new node with field comparison"
|
|
2325
|
+
);
|
|
2326
|
+
saveNodeDetailsToCache(node, target);
|
|
2327
|
+
return parseInt(node.id);
|
|
2328
|
+
}
|
|
2329
|
+
if (target.serializedForm) {
|
|
2330
|
+
const serializedNode = node.serialize({
|
|
2331
|
+
noID: true,
|
|
2332
|
+
maxLevel: 1,
|
|
2333
|
+
neighbors: 1
|
|
1558
2334
|
});
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
2335
|
+
if (Math.abs(serializedNode.length - target.serializedForm.length) > MAX_LEVENSHTEIN_DISTANCE) {
|
|
2336
|
+
continue;
|
|
2337
|
+
}
|
|
2338
|
+
const levenshteinDistance = distance2(
|
|
2339
|
+
target.serializedForm,
|
|
2340
|
+
serializedNode
|
|
1563
2341
|
);
|
|
2342
|
+
const ratio = levenshteinDistance / Math.min(target.serializedForm.length, serializedNode.length);
|
|
2343
|
+
if (levenshteinDistance < closestLevenshteinDistance && ratio < MAX_LEVENSHTEIN_CHANGE_RATIO) {
|
|
2344
|
+
closestLevenshteinDistance = levenshteinDistance;
|
|
2345
|
+
smallestLevenshteinRatio = ratio;
|
|
2346
|
+
closestNode = node;
|
|
2347
|
+
}
|
|
1564
2348
|
}
|
|
1565
2349
|
}
|
|
1566
|
-
|
|
2350
|
+
if (closestNode && closestLevenshteinDistance < MAX_LEVENSHTEIN_DISTANCE) {
|
|
2351
|
+
this.logger.debug(
|
|
2352
|
+
{
|
|
2353
|
+
newNode: closestNode.getLogForm(),
|
|
2354
|
+
target,
|
|
2355
|
+
distance: closestLevenshteinDistance,
|
|
2356
|
+
ratio: smallestLevenshteinRatio
|
|
2357
|
+
},
|
|
2358
|
+
"Resolved cached a11y target to new node with pure levenshtein distance"
|
|
2359
|
+
);
|
|
2360
|
+
saveNodeDetailsToCache(closestNode, target);
|
|
2361
|
+
return parseInt(closestNode.id);
|
|
2362
|
+
}
|
|
2363
|
+
throw new Error(
|
|
2364
|
+
`Could not find any relevant node given cached target: ${JSON.stringify(
|
|
2365
|
+
target
|
|
2366
|
+
)}`
|
|
2367
|
+
);
|
|
1567
2368
|
});
|
|
1568
2369
|
}
|
|
1569
2370
|
click(_0) {
|
|
1570
2371
|
return __async(this, arguments, function* (target, options = {}) {
|
|
2372
|
+
const id = yield this.resolveCachedTargetToID(target);
|
|
1571
2373
|
const elementInteracted = yield this.wrapPossibleNavigation(
|
|
1572
|
-
() => this.clickByA11yID(
|
|
2374
|
+
() => this.clickByA11yID(id, options)
|
|
1573
2375
|
);
|
|
1574
2376
|
return elementInteracted;
|
|
1575
2377
|
});
|
|
1576
2378
|
}
|
|
2379
|
+
hover(target) {
|
|
2380
|
+
return __async(this, null, function* () {
|
|
2381
|
+
const nodeId = yield this.resolveCachedTargetToID(target);
|
|
2382
|
+
const node = this.nodeMap.get(`${nodeId}`);
|
|
2383
|
+
if (!node) {
|
|
2384
|
+
throw new Error(`Could not find DOM node for hover: ${nodeId}`);
|
|
2385
|
+
}
|
|
2386
|
+
if (!node.backendNodeID) {
|
|
2387
|
+
throw new Error(
|
|
2388
|
+
`Hover target missing backend node id: ${node.getLogForm()}`
|
|
2389
|
+
);
|
|
2390
|
+
}
|
|
2391
|
+
const locator = yield this.getLocatorFromBackendID(node.backendNodeID);
|
|
2392
|
+
yield locator.hover({
|
|
2393
|
+
timeout: BROWSER_ACTION_TIMEOUT_MS
|
|
2394
|
+
});
|
|
2395
|
+
yield this.highlightNode(node);
|
|
2396
|
+
return node.serialize({ noChildren: true, noProperties: true, noID: true });
|
|
2397
|
+
});
|
|
2398
|
+
}
|
|
1577
2399
|
selectOption(target, option) {
|
|
1578
2400
|
return __async(this, null, function* () {
|
|
1579
|
-
|
|
2401
|
+
const id = yield this.resolveCachedTargetToID(target);
|
|
2402
|
+
return this.selectOptionByA11yID(id, option);
|
|
1580
2403
|
});
|
|
1581
2404
|
}
|
|
1582
2405
|
press(key) {
|
|
@@ -1633,7 +2456,7 @@ var _ChromeBrowser = class _ChromeBrowser {
|
|
|
1633
2456
|
);
|
|
1634
2457
|
let accessibilityTreeLoadFired = false;
|
|
1635
2458
|
const accessibilityLoadListener = () => {
|
|
1636
|
-
this.logger.info({ url }, `
|
|
2459
|
+
this.logger.info({ url }, `Load event fired on page`);
|
|
1637
2460
|
accessibilityTreeLoadFired = true;
|
|
1638
2461
|
};
|
|
1639
2462
|
this.cdpClient.addListener(
|
|
@@ -1644,7 +2467,7 @@ var _ChromeBrowser = class _ChromeBrowser {
|
|
|
1644
2467
|
let timeoutTriggered = true;
|
|
1645
2468
|
while (Date.now() - a11yLoadStart < A11Y_STABLE_TIMEOUT_MS) {
|
|
1646
2469
|
yield sleep(CHECK_INTERVAL_MS);
|
|
1647
|
-
if (!accessibilityTreeLoadFired && Date.now() - a11yLoadStart < A11Y_LOAD_TIMEOUT_MS) {
|
|
2470
|
+
if (!accessibilityTreeLoadFired && Date.now() - a11yLoadStart < A11Y_LOAD_TIMEOUT_MS && process.env.NODE_ENV !== "production") {
|
|
1648
2471
|
this.logger.debug({ url }, `A11y tree not loaded yet, waiting...`);
|
|
1649
2472
|
continue;
|
|
1650
2473
|
}
|
|
@@ -1906,7 +2729,7 @@ var _ChromeBrowser = class _ChromeBrowser {
|
|
|
1906
2729
|
});
|
|
1907
2730
|
}
|
|
1908
2731
|
};
|
|
1909
|
-
_ChromeBrowser.USER_AGENT =
|
|
2732
|
+
_ChromeBrowser.USER_AGENT = devices["Desktop Chrome"].userAgent;
|
|
1910
2733
|
var ChromeBrowser = _ChromeBrowser;
|
|
1911
2734
|
|
|
1912
2735
|
// ../../packages/web-agent/src/configs/controller.ts
|
|
@@ -2069,6 +2892,9 @@ var AgentController = class {
|
|
|
2069
2892
|
}
|
|
2070
2893
|
locateElement(description, disableCache) {
|
|
2071
2894
|
return __async(this, null, function* () {
|
|
2895
|
+
if (!description) {
|
|
2896
|
+
throw new Error("Cannot locate element with empty description");
|
|
2897
|
+
}
|
|
2072
2898
|
const locator = yield this.generator.getElementLocation(
|
|
2073
2899
|
{ browserState: yield this.getBrowserState(), goal: description },
|
|
2074
2900
|
disableCache
|
|
@@ -2156,10 +2982,8 @@ var AgentController = class {
|
|
|
2156
2982
|
command,
|
|
2157
2983
|
disableCache
|
|
2158
2984
|
);
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
"Got execution result"
|
|
2162
|
-
);
|
|
2985
|
+
const duration = Date.now() - executionStart;
|
|
2986
|
+
this.logger.debug({ result, duration }, "Got execution result");
|
|
2163
2987
|
} catch (e) {
|
|
2164
2988
|
if (e instanceof Error) {
|
|
2165
2989
|
throw new BrowserExecutionError(`Failed to execute command: ${e}`, {
|
|
@@ -2240,6 +3064,45 @@ var AgentController = class {
|
|
|
2240
3064
|
};
|
|
2241
3065
|
});
|
|
2242
3066
|
}
|
|
3067
|
+
wrapElementTargetingCommand(target, disableCache, action, newlyGenerated = false) {
|
|
3068
|
+
return __async(this, null, function* () {
|
|
3069
|
+
if (!target.a11yData) {
|
|
3070
|
+
target.a11yData = A11yTargetWithCacheSchema.parse(
|
|
3071
|
+
yield this.locateElement(target.elementDescriptor, disableCache)
|
|
3072
|
+
);
|
|
3073
|
+
newlyGenerated = true;
|
|
3074
|
+
}
|
|
3075
|
+
try {
|
|
3076
|
+
const result = yield action(target.a11yData);
|
|
3077
|
+
this.logger.debug(
|
|
3078
|
+
{ target },
|
|
3079
|
+
"Successfully used cached target to perform action"
|
|
3080
|
+
);
|
|
3081
|
+
return result;
|
|
3082
|
+
} catch (err) {
|
|
3083
|
+
if (!newlyGenerated) {
|
|
3084
|
+
this.logger.warn(
|
|
3085
|
+
{ err, target },
|
|
3086
|
+
"Failed to execute action with cached target, retrying with AI"
|
|
3087
|
+
);
|
|
3088
|
+
target.a11yData = void 0;
|
|
3089
|
+
return this.wrapElementTargetingCommand(
|
|
3090
|
+
target,
|
|
3091
|
+
disableCache,
|
|
3092
|
+
action,
|
|
3093
|
+
true
|
|
3094
|
+
);
|
|
3095
|
+
}
|
|
3096
|
+
this.logger.error(
|
|
3097
|
+
{ err, target },
|
|
3098
|
+
"Failed to target element even after all auto-healing"
|
|
3099
|
+
);
|
|
3100
|
+
throw new Error(
|
|
3101
|
+
`Failed to find element with description: ${target.elementDescriptor}. Has your website changed significantly?`
|
|
3102
|
+
);
|
|
3103
|
+
}
|
|
3104
|
+
});
|
|
3105
|
+
}
|
|
2243
3106
|
/**
|
|
2244
3107
|
* Executes a preset command.
|
|
2245
3108
|
* For most cases, the execution result contains metadata about the command executed.
|
|
@@ -2248,7 +3111,7 @@ var AgentController = class {
|
|
|
2248
3111
|
*/
|
|
2249
3112
|
executePresetStep(command, disableCache) {
|
|
2250
3113
|
return __async(this, null, function* () {
|
|
2251
|
-
var _a
|
|
3114
|
+
var _a;
|
|
2252
3115
|
const urlBeforeCommand = this.browser.url;
|
|
2253
3116
|
switch (command.type) {
|
|
2254
3117
|
case "SUCCESS" /* SUCCESS */:
|
|
@@ -2284,24 +3147,13 @@ var AgentController = class {
|
|
|
2284
3147
|
yield this.browser.refresh();
|
|
2285
3148
|
break;
|
|
2286
3149
|
case "CLICK" /* CLICK */: {
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
const locator = yield this.locateElement(
|
|
2292
|
-
command.target.elementDescriptor,
|
|
2293
|
-
disableCache
|
|
2294
|
-
);
|
|
2295
|
-
id = locator.id;
|
|
2296
|
-
}
|
|
2297
|
-
const elementInteracted = yield this.browser.click(
|
|
2298
|
-
{
|
|
2299
|
-
id
|
|
2300
|
-
},
|
|
2301
|
-
{
|
|
3150
|
+
const elementInteracted = yield this.wrapElementTargetingCommand(
|
|
3151
|
+
command.target,
|
|
3152
|
+
disableCache,
|
|
3153
|
+
(target) => this.browser.click(target, {
|
|
2302
3154
|
doubleClick: command.doubleClick,
|
|
2303
3155
|
rightClick: command.rightClick
|
|
2304
|
-
}
|
|
3156
|
+
})
|
|
2305
3157
|
);
|
|
2306
3158
|
const result2 = {
|
|
2307
3159
|
urlAfterCommand: this.browser.url,
|
|
@@ -2315,21 +3167,10 @@ var AgentController = class {
|
|
|
2315
3167
|
return result2;
|
|
2316
3168
|
}
|
|
2317
3169
|
case "SELECT_OPTION" /* SELECT_OPTION */: {
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
const locator = yield this.locateElement(
|
|
2323
|
-
command.target.elementDescriptor,
|
|
2324
|
-
disableCache
|
|
2325
|
-
);
|
|
2326
|
-
id = locator.id;
|
|
2327
|
-
}
|
|
2328
|
-
const elementInteracted = yield this.browser.selectOption(
|
|
2329
|
-
{
|
|
2330
|
-
id
|
|
2331
|
-
},
|
|
2332
|
-
command.option
|
|
3170
|
+
const elementInteracted = yield this.wrapElementTargetingCommand(
|
|
3171
|
+
command.target,
|
|
3172
|
+
disableCache,
|
|
3173
|
+
(targetWithA11yData) => this.browser.selectOption(targetWithA11yData, command.option)
|
|
2333
3174
|
);
|
|
2334
3175
|
return {
|
|
2335
3176
|
succeedImmediately: false,
|
|
@@ -2341,24 +3182,17 @@ var AgentController = class {
|
|
|
2341
3182
|
yield this.browser.switchToPage(command.url);
|
|
2342
3183
|
break;
|
|
2343
3184
|
case "COOKIE" /* COOKIE */:
|
|
3185
|
+
if (!command.value) {
|
|
3186
|
+
break;
|
|
3187
|
+
}
|
|
2344
3188
|
yield this.browser.setCookie(command.value);
|
|
2345
3189
|
break;
|
|
2346
3190
|
case "TYPE" /* TYPE */: {
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
});
|
|
2353
|
-
} else if (target.elementDescriptor.length > 0) {
|
|
2354
|
-
const locator = yield this.locateElement(
|
|
2355
|
-
command.target.elementDescriptor,
|
|
2356
|
-
disableCache
|
|
2357
|
-
);
|
|
2358
|
-
elementInteracted = yield this.browser.click({
|
|
2359
|
-
id: locator.id
|
|
2360
|
-
});
|
|
2361
|
-
}
|
|
3191
|
+
const elementInteracted = yield this.wrapElementTargetingCommand(
|
|
3192
|
+
command.target,
|
|
3193
|
+
disableCache,
|
|
3194
|
+
(target) => this.browser.click(target)
|
|
3195
|
+
);
|
|
2362
3196
|
yield this.browser.type(command.value, {
|
|
2363
3197
|
clearContent: command.clearContent,
|
|
2364
3198
|
pressKeysSequentially: command.pressKeysSequentially
|
|
@@ -2377,6 +3211,18 @@ var AgentController = class {
|
|
|
2377
3211
|
}
|
|
2378
3212
|
return result2;
|
|
2379
3213
|
}
|
|
3214
|
+
case "HOVER" /* HOVER */: {
|
|
3215
|
+
const elementInteracted = yield this.wrapElementTargetingCommand(
|
|
3216
|
+
command.target,
|
|
3217
|
+
disableCache,
|
|
3218
|
+
(target) => this.browser.hover(target)
|
|
3219
|
+
);
|
|
3220
|
+
return {
|
|
3221
|
+
succeedImmediately: false,
|
|
3222
|
+
urlAfterCommand: this.browser.url,
|
|
3223
|
+
elementInteracted
|
|
3224
|
+
};
|
|
3225
|
+
}
|
|
2380
3226
|
case "PRESS" /* PRESS */:
|
|
2381
3227
|
yield this.browser.press(command.value);
|
|
2382
3228
|
const result = {
|
|
@@ -2405,7 +3251,7 @@ var AgentController = class {
|
|
|
2405
3251
|
// ../../packages/web-agent/src/generators/api-generator.ts
|
|
2406
3252
|
import fetchRetry from "fetch-retry";
|
|
2407
3253
|
var fetch2 = fetchRetry(global.fetch);
|
|
2408
|
-
var
|
|
3254
|
+
var API_VERSION2 = "v1";
|
|
2409
3255
|
var APIGenerator = class {
|
|
2410
3256
|
constructor(params) {
|
|
2411
3257
|
this.baseURL = params.baseURL;
|
|
@@ -2414,7 +3260,7 @@ var APIGenerator = class {
|
|
|
2414
3260
|
getElementLocation(context, disableCache) {
|
|
2415
3261
|
return __async(this, null, function* () {
|
|
2416
3262
|
const result = yield this.sendRequest(
|
|
2417
|
-
`/${
|
|
3263
|
+
`/${API_VERSION2}/web-agent/locate-element`,
|
|
2418
3264
|
{
|
|
2419
3265
|
browserState: context.browserState,
|
|
2420
3266
|
goal: context.goal,
|
|
@@ -2429,7 +3275,7 @@ var APIGenerator = class {
|
|
|
2429
3275
|
var _a;
|
|
2430
3276
|
if (useVision) {
|
|
2431
3277
|
const result2 = yield this.sendRequest(
|
|
2432
|
-
`/${
|
|
3278
|
+
`/${API_VERSION2}/web-agent/assertion`,
|
|
2433
3279
|
{
|
|
2434
3280
|
url: context.url,
|
|
2435
3281
|
goal: context.goal,
|
|
@@ -2441,7 +3287,7 @@ var APIGenerator = class {
|
|
|
2441
3287
|
return GetAssertionResponseSchema.parse(result2);
|
|
2442
3288
|
}
|
|
2443
3289
|
const result = yield this.sendRequest(
|
|
2444
|
-
`/${
|
|
3290
|
+
`/${API_VERSION2}/web-agent/assertion`,
|
|
2445
3291
|
{
|
|
2446
3292
|
url: context.url,
|
|
2447
3293
|
browserState: context.browserState,
|
|
@@ -2459,117 +3305,40 @@ var APIGenerator = class {
|
|
|
2459
3305
|
getProposedCommand(context, disableCache) {
|
|
2460
3306
|
return __async(this, null, function* () {
|
|
2461
3307
|
const result = yield this.sendRequest(
|
|
2462
|
-
`/${
|
|
2463
|
-
{
|
|
2464
|
-
url: context.url,
|
|
2465
|
-
browserState: context.browserState,
|
|
2466
|
-
goal: context.goal,
|
|
2467
|
-
history: context.history,
|
|
2468
|
-
numPrevious: context.numPrevious,
|
|
2469
|
-
lastCommand: context.lastCommand,
|
|
2470
|
-
disableCache
|
|
2471
|
-
}
|
|
2472
|
-
);
|
|
2473
|
-
return GetNextCommandResponseSchema.parse(result);
|
|
2474
|
-
});
|
|
2475
|
-
}
|
|
2476
|
-
getGranularGoals(context, disableCache) {
|
|
2477
|
-
return __async(this, null, function* () {
|
|
2478
|
-
const result = yield this.sendRequest(
|
|
2479
|
-
`/${API_VERSION}/web-agent/split-goal`,
|
|
3308
|
+
`/${API_VERSION2}/web-agent/next-command`,
|
|
2480
3309
|
{
|
|
2481
3310
|
url: context.url,
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
const response = yield fetch2(`${this.baseURL}${path}`, {
|
|
2492
|
-
retries: 3,
|
|
2493
|
-
retryDelay: 1e3,
|
|
2494
|
-
method: "POST",
|
|
2495
|
-
body: JSON.stringify(body),
|
|
2496
|
-
headers: {
|
|
2497
|
-
"Content-Type": "application/json",
|
|
2498
|
-
Authorization: `Bearer ${this.apiKey}`
|
|
2499
|
-
}
|
|
2500
|
-
});
|
|
2501
|
-
if (!response.ok) {
|
|
2502
|
-
throw new Error(
|
|
2503
|
-
`Request to ${path} failed with status ${response.status}: ${yield response.text()}`
|
|
2504
|
-
);
|
|
2505
|
-
}
|
|
2506
|
-
return response.json();
|
|
2507
|
-
});
|
|
2508
|
-
}
|
|
2509
|
-
};
|
|
2510
|
-
|
|
2511
|
-
// src/api-client.ts
|
|
2512
|
-
var API_VERSION2 = "v1";
|
|
2513
|
-
var APIClient = class {
|
|
2514
|
-
constructor(params) {
|
|
2515
|
-
this.baseURL = params.baseURL;
|
|
2516
|
-
this.apiKey = params.apiKey;
|
|
2517
|
-
}
|
|
2518
|
-
getRun(runId) {
|
|
2519
|
-
return __async(this, null, function* () {
|
|
2520
|
-
const result = yield this.sendRequest(`/${API_VERSION2}/runs/${runId}`, {
|
|
2521
|
-
method: "GET"
|
|
2522
|
-
});
|
|
2523
|
-
return GetRunResponseSchema.parse(result);
|
|
2524
|
-
});
|
|
2525
|
-
}
|
|
2526
|
-
createRun(body) {
|
|
2527
|
-
return __async(this, null, function* () {
|
|
2528
|
-
const result = yield this.sendRequest(`/${API_VERSION2}/runs`, {
|
|
2529
|
-
method: "POST",
|
|
2530
|
-
body
|
|
2531
|
-
});
|
|
2532
|
-
return CreateRunResponseSchema.parse(result);
|
|
2533
|
-
});
|
|
2534
|
-
}
|
|
2535
|
-
updateRun(runId, body) {
|
|
2536
|
-
return __async(this, null, function* () {
|
|
2537
|
-
yield this.sendRequest(`/${API_VERSION2}/runs/${runId}`, {
|
|
2538
|
-
method: "PATCH",
|
|
2539
|
-
body
|
|
2540
|
-
});
|
|
2541
|
-
});
|
|
2542
|
-
}
|
|
2543
|
-
getTest(testId) {
|
|
2544
|
-
return __async(this, null, function* () {
|
|
2545
|
-
const result = yield this.sendRequest(`/${API_VERSION2}/tests/${testId}`, {
|
|
2546
|
-
method: "GET"
|
|
2547
|
-
});
|
|
2548
|
-
return GetTestResponseSchema.parse(result);
|
|
3311
|
+
browserState: context.browserState,
|
|
3312
|
+
goal: context.goal,
|
|
3313
|
+
history: context.history,
|
|
3314
|
+
numPrevious: context.numPrevious,
|
|
3315
|
+
lastCommand: context.lastCommand,
|
|
3316
|
+
disableCache
|
|
3317
|
+
}
|
|
3318
|
+
);
|
|
3319
|
+
return GetNextCommandResponseSchema.parse(result);
|
|
2549
3320
|
});
|
|
2550
3321
|
}
|
|
2551
|
-
|
|
3322
|
+
getGranularGoals(context, disableCache) {
|
|
2552
3323
|
return __async(this, null, function* () {
|
|
2553
|
-
yield this.sendRequest(
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
3324
|
+
const result = yield this.sendRequest(
|
|
3325
|
+
`/${API_VERSION2}/web-agent/split-goal`,
|
|
3326
|
+
{
|
|
3327
|
+
url: context.url,
|
|
3328
|
+
goal: context.goal,
|
|
3329
|
+
disableCache
|
|
3330
|
+
}
|
|
3331
|
+
);
|
|
3332
|
+
return SplitGoalResponseSchema.parse(result);
|
|
2557
3333
|
});
|
|
2558
3334
|
}
|
|
2559
|
-
|
|
3335
|
+
sendRequest(path3, body) {
|
|
2560
3336
|
return __async(this, null, function* () {
|
|
2561
|
-
const
|
|
3337
|
+
const response = yield fetch2(`${this.baseURL}${path3}`, {
|
|
3338
|
+
retries: 1,
|
|
3339
|
+
retryDelay: 1e3,
|
|
2562
3340
|
method: "POST",
|
|
2563
|
-
body
|
|
2564
|
-
});
|
|
2565
|
-
return CreateScreenshotResponseSchema.parse(result);
|
|
2566
|
-
});
|
|
2567
|
-
}
|
|
2568
|
-
sendRequest(path, options) {
|
|
2569
|
-
return __async(this, null, function* () {
|
|
2570
|
-
const response = yield fetch(`${this.baseURL}${path}`, {
|
|
2571
|
-
method: options.method,
|
|
2572
|
-
body: options.body ? JSON.stringify(options.body) : void 0,
|
|
3341
|
+
body: JSON.stringify(body),
|
|
2573
3342
|
headers: {
|
|
2574
3343
|
"Content-Type": "application/json",
|
|
2575
3344
|
Authorization: `Bearer ${this.apiKey}`
|
|
@@ -2577,17 +3346,68 @@ var APIClient = class {
|
|
|
2577
3346
|
});
|
|
2578
3347
|
if (!response.ok) {
|
|
2579
3348
|
throw new Error(
|
|
2580
|
-
`Request to ${
|
|
3349
|
+
`Request to ${path3} failed with status ${response.status}: ${yield response.text()}`
|
|
2581
3350
|
);
|
|
2582
3351
|
}
|
|
2583
|
-
if (response.status === 204) {
|
|
2584
|
-
return response.text();
|
|
2585
|
-
}
|
|
2586
3352
|
return response.json();
|
|
2587
3353
|
});
|
|
2588
3354
|
}
|
|
2589
3355
|
};
|
|
2590
3356
|
|
|
3357
|
+
// src/get-tests.ts
|
|
3358
|
+
import fs from "fs";
|
|
3359
|
+
import path2 from "path";
|
|
3360
|
+
var bannedDirs = /* @__PURE__ */ new Set([
|
|
3361
|
+
"modules",
|
|
3362
|
+
// this is reserved directory
|
|
3363
|
+
"node_modules",
|
|
3364
|
+
"dist",
|
|
3365
|
+
"bin",
|
|
3366
|
+
".git",
|
|
3367
|
+
"logs",
|
|
3368
|
+
".npm",
|
|
3369
|
+
".next",
|
|
3370
|
+
"out",
|
|
3371
|
+
".yarn",
|
|
3372
|
+
"__pycache__",
|
|
3373
|
+
"build",
|
|
3374
|
+
".env",
|
|
3375
|
+
".venv",
|
|
3376
|
+
"venv",
|
|
3377
|
+
"env",
|
|
3378
|
+
"wheels"
|
|
3379
|
+
]);
|
|
3380
|
+
function getTestFilesRecursively(dir) {
|
|
3381
|
+
var _a;
|
|
3382
|
+
const dirName = (_a = dir.split(path2.sep).pop()) != null ? _a : "";
|
|
3383
|
+
if (bannedDirs.has(dirName)) {
|
|
3384
|
+
if (dirName !== "modules") {
|
|
3385
|
+
consoleLogger.warn(
|
|
3386
|
+
`Skipping directory '${dir}' because it is likely an artifact folder.`
|
|
3387
|
+
);
|
|
3388
|
+
}
|
|
3389
|
+
return [];
|
|
3390
|
+
}
|
|
3391
|
+
const files = fs.readdirSync(dir);
|
|
3392
|
+
let filePathList = [];
|
|
3393
|
+
files.forEach((file) => {
|
|
3394
|
+
const filepath = path2.join(dir, file);
|
|
3395
|
+
if (fs.statSync(filepath).isDirectory()) {
|
|
3396
|
+
filePathList = filePathList.concat(getTestFilesRecursively(filepath));
|
|
3397
|
+
} else if (file.endsWith(".yaml")) {
|
|
3398
|
+
const contents = fs.readFileSync(filepath, "utf-8");
|
|
3399
|
+
if (contents.includes("momentic/test" /* TEST */)) {
|
|
3400
|
+
filePathList.push(filepath);
|
|
3401
|
+
} else {
|
|
3402
|
+
consoleLogger.warn(
|
|
3403
|
+
`Skipping file '${filepath}' because it does not appear to be a valid Momentic test.`
|
|
3404
|
+
);
|
|
3405
|
+
}
|
|
3406
|
+
}
|
|
3407
|
+
});
|
|
3408
|
+
return filePathList;
|
|
3409
|
+
}
|
|
3410
|
+
|
|
2591
3411
|
// ../../packages/execute/src/constants.ts
|
|
2592
3412
|
var MAX_COMMANDS_PER_STEP = 20;
|
|
2593
3413
|
|
|
@@ -2664,7 +3484,9 @@ var executeAIStep = (_a) => __async(void 0, null, function* () {
|
|
|
2664
3484
|
status: "SUCCESS" /* SUCCESS */
|
|
2665
3485
|
};
|
|
2666
3486
|
logger.info(
|
|
2667
|
-
`
|
|
3487
|
+
`Starting sub-command ${commandIndex} within AI step: ${serializeCommand(
|
|
3488
|
+
command
|
|
3489
|
+
)}`
|
|
2668
3490
|
);
|
|
2669
3491
|
try {
|
|
2670
3492
|
const executionResult = yield controller.executeCommand(
|
|
@@ -2672,6 +3494,7 @@ var executeAIStep = (_a) => __async(void 0, null, function* () {
|
|
|
2672
3494
|
advanced.disableAICaching,
|
|
2673
3495
|
useSavedCommands
|
|
2674
3496
|
);
|
|
3497
|
+
logger.info(`AI sub-command ${commandIndex} completed successfully`);
|
|
2675
3498
|
cmdResult.elementInteracted = executionResult.elementInteracted;
|
|
2676
3499
|
(_c = callbacks.onCommandExecuted) == null ? void 0 : _c.call(callbacks, {
|
|
2677
3500
|
commandIndex,
|
|
@@ -2822,7 +3645,8 @@ var executePresetStep = (_a) => __async(void 0, null, function* () {
|
|
|
2822
3645
|
(_b2 = callbacks.onSuccess) == null ? void 0 : _b2.call(callbacks, {
|
|
2823
3646
|
message,
|
|
2824
3647
|
startedAt: startedAt.getTime(),
|
|
2825
|
-
durationMs: finishedAt.getTime() - startedAt.getTime()
|
|
3648
|
+
durationMs: finishedAt.getTime() - startedAt.getTime(),
|
|
3649
|
+
command: step.command
|
|
2826
3650
|
});
|
|
2827
3651
|
return result;
|
|
2828
3652
|
} catch (err) {
|
|
@@ -2882,7 +3706,12 @@ var executeModuleStep = (_a) => __async(void 0, null, function* () {
|
|
|
2882
3706
|
};
|
|
2883
3707
|
for (let i = 0; i < step.steps.length; i++) {
|
|
2884
3708
|
const moduleStep = step.steps[i];
|
|
2885
|
-
logger.
|
|
3709
|
+
logger.debug({ i, moduleStep }, `Starting module step`);
|
|
3710
|
+
logger.info(
|
|
3711
|
+
`Starting module sub-step ${i + 1}/${step.steps.length}: ${serializeStep(
|
|
3712
|
+
moduleStep
|
|
3713
|
+
)}`
|
|
3714
|
+
);
|
|
2886
3715
|
let moduleStepResult;
|
|
2887
3716
|
switch (moduleStep.type) {
|
|
2888
3717
|
case "PRESET_ACTION" /* PRESET_ACTION */:
|
|
@@ -3010,6 +3839,7 @@ var executeTest = (_0) => __async(void 0, [_0], function* ({
|
|
|
3010
3839
|
onUpdateRun,
|
|
3011
3840
|
onSaveScreenshot
|
|
3012
3841
|
}) {
|
|
3842
|
+
var _a;
|
|
3013
3843
|
const advanced = TestAdvancedSettingsSchema.parse(test.advanced);
|
|
3014
3844
|
logger.info(`Starting run ${runId} for test ${test.id}`);
|
|
3015
3845
|
yield onUpdateRun({
|
|
@@ -3018,8 +3848,12 @@ var executeTest = (_0) => __async(void 0, [_0], function* ({
|
|
|
3018
3848
|
});
|
|
3019
3849
|
let failed = false;
|
|
3020
3850
|
const results = [];
|
|
3851
|
+
const runLogger = logger.child({ runId, testId: test.id });
|
|
3021
3852
|
for (let i = 0; i < test.steps.length; i++) {
|
|
3022
3853
|
const step = test.steps[i];
|
|
3854
|
+
runLogger.info(
|
|
3855
|
+
`Starting step ${i + 1}/${test.steps.length}: ${serializeStep(step)}`
|
|
3856
|
+
);
|
|
3023
3857
|
let result;
|
|
3024
3858
|
switch (step.type) {
|
|
3025
3859
|
case "PRESET_ACTION" /* PRESET_ACTION */:
|
|
@@ -3027,7 +3861,7 @@ var executeTest = (_0) => __async(void 0, [_0], function* ({
|
|
|
3027
3861
|
controller,
|
|
3028
3862
|
step,
|
|
3029
3863
|
advanced,
|
|
3030
|
-
logger,
|
|
3864
|
+
logger: runLogger,
|
|
3031
3865
|
onSaveScreenshot
|
|
3032
3866
|
});
|
|
3033
3867
|
break;
|
|
@@ -3036,7 +3870,7 @@ var executeTest = (_0) => __async(void 0, [_0], function* ({
|
|
|
3036
3870
|
controller,
|
|
3037
3871
|
step,
|
|
3038
3872
|
advanced,
|
|
3039
|
-
logger,
|
|
3873
|
+
logger: runLogger,
|
|
3040
3874
|
onSaveScreenshot
|
|
3041
3875
|
});
|
|
3042
3876
|
break;
|
|
@@ -3045,7 +3879,7 @@ var executeTest = (_0) => __async(void 0, [_0], function* ({
|
|
|
3045
3879
|
controller,
|
|
3046
3880
|
step,
|
|
3047
3881
|
advanced,
|
|
3048
|
-
logger,
|
|
3882
|
+
logger: runLogger,
|
|
3049
3883
|
onSaveScreenshot
|
|
3050
3884
|
});
|
|
3051
3885
|
break;
|
|
@@ -3060,6 +3894,13 @@ var executeTest = (_0) => __async(void 0, [_0], function* ({
|
|
|
3060
3894
|
results
|
|
3061
3895
|
});
|
|
3062
3896
|
if (result.status === "FAILED" /* FAILED */) {
|
|
3897
|
+
runLogger.error(`Step ${i + 1}/${test.steps.length} failed`);
|
|
3898
|
+
runLogger.error(
|
|
3899
|
+
{
|
|
3900
|
+
message: (_a = results[results.length - 1]) == null ? void 0 : _a.message
|
|
3901
|
+
},
|
|
3902
|
+
`Last result:`
|
|
3903
|
+
);
|
|
3063
3904
|
failed = true;
|
|
3064
3905
|
for (let j = i + 1; j < test.steps.length; j++) {
|
|
3065
3906
|
const skippedStep = test.steps[j];
|
|
@@ -3093,6 +3934,8 @@ var executeTest = (_0) => __async(void 0, [_0], function* ({
|
|
|
3093
3934
|
results.push(skippedResult);
|
|
3094
3935
|
}
|
|
3095
3936
|
}
|
|
3937
|
+
} else {
|
|
3938
|
+
runLogger.info(`Step ${i + 1}/${test.steps.length} succeeded`);
|
|
3096
3939
|
}
|
|
3097
3940
|
if (failed) {
|
|
3098
3941
|
break;
|
|
@@ -3107,31 +3950,365 @@ var executeTest = (_0) => __async(void 0, [_0], function* ({
|
|
|
3107
3950
|
return failed;
|
|
3108
3951
|
});
|
|
3109
3952
|
|
|
3110
|
-
// src/
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3953
|
+
// ../../packages/test-migrations/src/index.ts
|
|
3954
|
+
import diffLines2 from "diff-lines";
|
|
3955
|
+
import semver from "semver";
|
|
3956
|
+
|
|
3957
|
+
// ../../packages/test-migrations/src/2023-12-28-1.0.5-migrate-to-ai-step-v2.ts
|
|
3958
|
+
var migrateToAIStepV2 = {
|
|
3959
|
+
name: "Migrate to ai step v2",
|
|
3960
|
+
fromVersion: "1.0.4",
|
|
3961
|
+
toVersion: "1.0.5",
|
|
3962
|
+
recursiveKeys: /* @__PURE__ */ new Set(["results", "commands"]),
|
|
3963
|
+
stopOnFailure: true,
|
|
3964
|
+
execute: (steps) => __async(void 0, null, function* () {
|
|
3965
|
+
steps = steps.filter(
|
|
3966
|
+
(step) => !(step.status !== void 0 && step.type === "AI_ACTION")
|
|
3967
|
+
);
|
|
3968
|
+
steps = steps.map((step) => {
|
|
3969
|
+
var _a, _b;
|
|
3970
|
+
if (step.status === void 0) {
|
|
3971
|
+
return step;
|
|
3972
|
+
}
|
|
3973
|
+
if (step.type === "PRESET_ACTION") {
|
|
3974
|
+
step.results = (_b = (_a = step.commands) != null ? _a : step.results) != null ? _b : [];
|
|
3975
|
+
}
|
|
3976
|
+
return step;
|
|
3977
|
+
});
|
|
3978
|
+
return steps;
|
|
3979
|
+
})
|
|
3980
|
+
};
|
|
3981
|
+
|
|
3982
|
+
// ../../packages/test-migrations/src/2024-01-05-1.0.6-ensure-ai-step-has-done.ts
|
|
3983
|
+
var ensureAIStepHasDone = {
|
|
3984
|
+
name: "Make sure ai step v2 has done command",
|
|
3985
|
+
fromVersion: "1.0.5",
|
|
3986
|
+
toVersion: "1.0.6",
|
|
3987
|
+
recursiveKeys: /* @__PURE__ */ new Set(["results", "commands"]),
|
|
3988
|
+
stopOnFailure: true,
|
|
3989
|
+
execute: (steps) => __async(void 0, null, function* () {
|
|
3990
|
+
return steps.map((step) => {
|
|
3991
|
+
if (step.type !== "AI_ACTION") {
|
|
3992
|
+
return step;
|
|
3993
|
+
}
|
|
3994
|
+
if (step.status !== void 0) {
|
|
3995
|
+
return step;
|
|
3996
|
+
}
|
|
3997
|
+
if (!step.commands || !step.commands.length) {
|
|
3998
|
+
return step;
|
|
3999
|
+
}
|
|
4000
|
+
const commands = step.commands;
|
|
4001
|
+
const lastCommand = commands[commands.length - 1];
|
|
4002
|
+
if (lastCommand && lastCommand["type"] !== "SUCCESS") {
|
|
4003
|
+
commands.push({
|
|
4004
|
+
type: "SUCCESS"
|
|
4005
|
+
});
|
|
4006
|
+
}
|
|
4007
|
+
return step;
|
|
4008
|
+
});
|
|
4009
|
+
})
|
|
4010
|
+
};
|
|
4011
|
+
|
|
4012
|
+
// ../../packages/test-migrations/src/migrate-assertions-to-preset.ts
|
|
4013
|
+
var migrateAssertionsToPresetActions = {
|
|
4014
|
+
name: "Migrate AI assertions to preset actions",
|
|
4015
|
+
fromVersion: "1.0.0",
|
|
4016
|
+
toVersion: "1.0.1",
|
|
4017
|
+
recursiveKeys: /* @__PURE__ */ new Set(),
|
|
4018
|
+
execute: (steps) => __async(void 0, null, function* () {
|
|
4019
|
+
return steps.map((record2) => {
|
|
4020
|
+
if (record2["type"] !== "AI_ASSERTION") {
|
|
4021
|
+
return record2;
|
|
4022
|
+
}
|
|
4023
|
+
const assertion = record2["text"];
|
|
4024
|
+
const newPresetStep = {
|
|
4025
|
+
type: "PRESET_ACTION",
|
|
4026
|
+
command: {
|
|
4027
|
+
type: "AI_ASSERTION",
|
|
4028
|
+
assertion,
|
|
4029
|
+
useVision: false,
|
|
4030
|
+
disableCache: true
|
|
4031
|
+
}
|
|
4032
|
+
};
|
|
4033
|
+
const migratedStep = __spreadValues(__spreadValues({}, record2), newPresetStep);
|
|
4034
|
+
delete migratedStep["text"];
|
|
4035
|
+
return migratedStep;
|
|
4036
|
+
});
|
|
4037
|
+
}),
|
|
4038
|
+
stopOnFailure: true
|
|
4039
|
+
};
|
|
4040
|
+
|
|
4041
|
+
// ../../packages/test-migrations/src/migrate-element-descriptor-to-target.ts
|
|
4042
|
+
var targetRequiredCommands = /* @__PURE__ */ new Set([
|
|
4043
|
+
"CLICK",
|
|
4044
|
+
"TYPE",
|
|
4045
|
+
"SELECT_OPTION"
|
|
4046
|
+
]);
|
|
4047
|
+
var migrateElementDescriptorToTarget = {
|
|
4048
|
+
name: "Migrate element descriptor to live in a target object",
|
|
4049
|
+
fromVersion: "1.0.3",
|
|
4050
|
+
toVersion: "1.0.4",
|
|
4051
|
+
recursiveKeys: /* @__PURE__ */ new Set(),
|
|
4052
|
+
execute: (steps) => __async(void 0, null, function* () {
|
|
4053
|
+
return steps.map((step) => {
|
|
4054
|
+
const command = step["command"];
|
|
4055
|
+
const commandType = command == null ? void 0 : command["type"];
|
|
4056
|
+
const elementDescriptor = command == null ? void 0 : command["elementDescriptor"];
|
|
4057
|
+
if (elementDescriptor !== void 0 || targetRequiredCommands.has(commandType)) {
|
|
4058
|
+
command["target"] = {
|
|
4059
|
+
elementDescriptor: elementDescriptor != null ? elementDescriptor : ""
|
|
4060
|
+
};
|
|
4061
|
+
}
|
|
4062
|
+
if (step["commands"] && Array.isArray(step["commands"])) {
|
|
4063
|
+
const commands = step["commands"];
|
|
4064
|
+
commands.forEach((command2) => {
|
|
4065
|
+
const elementDescriptor2 = command2 == null ? void 0 : command2["elementDescriptor"];
|
|
4066
|
+
const commandType2 = command2 == null ? void 0 : command2["type"];
|
|
4067
|
+
if (elementDescriptor2 !== void 0 || targetRequiredCommands.has(commandType2)) {
|
|
4068
|
+
command2["target"] = {
|
|
4069
|
+
elementDescriptor: elementDescriptor2 != null ? elementDescriptor2 : ""
|
|
4070
|
+
};
|
|
4071
|
+
}
|
|
4072
|
+
});
|
|
4073
|
+
}
|
|
4074
|
+
if (step["results"] && Array.isArray(step["results"])) {
|
|
4075
|
+
const commandResults = step["results"];
|
|
4076
|
+
commandResults.forEach((commandResult) => {
|
|
4077
|
+
const command2 = commandResult["command"];
|
|
4078
|
+
const elementDescriptor2 = command2 == null ? void 0 : command2["elementDescriptor"];
|
|
4079
|
+
const commandType2 = command2 == null ? void 0 : command2["type"];
|
|
4080
|
+
if (elementDescriptor2 !== void 0 || targetRequiredCommands.has(commandType2)) {
|
|
4081
|
+
command2["target"] = {
|
|
4082
|
+
elementDescriptor: elementDescriptor2 != null ? elementDescriptor2 : ""
|
|
4083
|
+
};
|
|
4084
|
+
}
|
|
4085
|
+
if (commandResult["commands"] && Array.isArray(commandResult["commands"])) {
|
|
4086
|
+
const commands = commandResult["commands"];
|
|
4087
|
+
commands.forEach((command3) => {
|
|
4088
|
+
const elementDescriptor3 = command3 == null ? void 0 : command3["elementDescriptor"];
|
|
4089
|
+
const commandType3 = command3 == null ? void 0 : command3["type"];
|
|
4090
|
+
if (elementDescriptor3 !== void 0 || targetRequiredCommands.has(commandType3)) {
|
|
4091
|
+
command3["target"] = {
|
|
4092
|
+
elementDescriptor: elementDescriptor3 != null ? elementDescriptor3 : ""
|
|
4093
|
+
};
|
|
4094
|
+
}
|
|
4095
|
+
});
|
|
4096
|
+
}
|
|
4097
|
+
});
|
|
4098
|
+
}
|
|
4099
|
+
return step;
|
|
4100
|
+
});
|
|
4101
|
+
}),
|
|
4102
|
+
stopOnFailure: true
|
|
4103
|
+
};
|
|
4104
|
+
|
|
4105
|
+
// ../../packages/test-migrations/src/migrate-failure-to-failed.ts
|
|
4106
|
+
var migrateFailureToFailed = {
|
|
4107
|
+
name: "Migrate FAILURE status to FAILED",
|
|
4108
|
+
fromVersion: "1.0.1",
|
|
4109
|
+
toVersion: "1.0.2",
|
|
4110
|
+
recursiveKeys: /* @__PURE__ */ new Set(),
|
|
4111
|
+
execute: (steps) => __async(void 0, null, function* () {
|
|
4112
|
+
return steps.map((step) => {
|
|
4113
|
+
const record2 = step;
|
|
4114
|
+
if (record2["status"] === "FAILURE") {
|
|
4115
|
+
record2["status"] = "FAILED";
|
|
4116
|
+
}
|
|
4117
|
+
if (typeof record2.commands === "object" && Array.isArray(record2.commands)) {
|
|
4118
|
+
record2.commands.forEach((command) => {
|
|
4119
|
+
if (command && typeof command === "object") {
|
|
4120
|
+
const commandObject = command;
|
|
4121
|
+
if ((commandObject == null ? void 0 : commandObject.status) === "FAILURE") {
|
|
4122
|
+
commandObject.status = "FAILED";
|
|
4123
|
+
}
|
|
4124
|
+
}
|
|
4125
|
+
});
|
|
4126
|
+
}
|
|
4127
|
+
return record2;
|
|
4128
|
+
});
|
|
4129
|
+
}),
|
|
4130
|
+
stopOnFailure: true
|
|
4131
|
+
};
|
|
4132
|
+
|
|
4133
|
+
// ../../packages/test-migrations/src/migrate-preset-step-type.ts
|
|
4134
|
+
var migratePresetStepTypeToDropPrefix = {
|
|
4135
|
+
name: "Migrate preset step types to use the same",
|
|
4136
|
+
fromVersion: "1.0.2",
|
|
4137
|
+
toVersion: "1.0.3",
|
|
4138
|
+
recursiveKeys: /* @__PURE__ */ new Set(),
|
|
4139
|
+
execute: (steps) => __async(void 0, null, function* () {
|
|
4140
|
+
return steps.map((step) => {
|
|
4141
|
+
const command = step["command"];
|
|
4142
|
+
const commandType = command == null ? void 0 : command["type"];
|
|
4143
|
+
if (commandType == null ? void 0 : commandType.startsWith("PRESET_")) {
|
|
4144
|
+
command["type"] = commandType.slice(7);
|
|
4145
|
+
}
|
|
4146
|
+
if (step["commands"] && Array.isArray(step["commands"])) {
|
|
4147
|
+
const commands = step["commands"];
|
|
4148
|
+
commands.forEach((command2) => {
|
|
4149
|
+
const commandType2 = command2["type"];
|
|
4150
|
+
if (commandType2 == null ? void 0 : commandType2.startsWith("PRESET_")) {
|
|
4151
|
+
command2["type"] = commandType2.slice(7);
|
|
4152
|
+
}
|
|
4153
|
+
});
|
|
4154
|
+
}
|
|
4155
|
+
if (step["results"] && Array.isArray(step["results"])) {
|
|
4156
|
+
const commandResults = step["results"];
|
|
4157
|
+
commandResults.forEach((commandResult) => {
|
|
4158
|
+
const command2 = commandResult["command"];
|
|
4159
|
+
const commandType2 = command2 == null ? void 0 : command2["type"];
|
|
4160
|
+
if (commandType2 == null ? void 0 : commandType2.startsWith("PRESET_")) {
|
|
4161
|
+
command2["type"] = commandType2.slice(7);
|
|
4162
|
+
}
|
|
4163
|
+
if (commandResult["commands"] && Array.isArray(commandResult["commands"])) {
|
|
4164
|
+
const commands = commandResult["commands"];
|
|
4165
|
+
commands.forEach((command3) => {
|
|
4166
|
+
const commandType3 = command3["type"];
|
|
4167
|
+
if (commandType3 == null ? void 0 : commandType3.startsWith("PRESET_")) {
|
|
4168
|
+
command3["type"] = commandType3.slice(7);
|
|
4169
|
+
}
|
|
4170
|
+
});
|
|
4171
|
+
}
|
|
4172
|
+
});
|
|
4173
|
+
}
|
|
4174
|
+
return step;
|
|
4175
|
+
});
|
|
4176
|
+
}),
|
|
4177
|
+
stopOnFailure: true
|
|
3119
4178
|
};
|
|
3120
4179
|
|
|
4180
|
+
// ../../packages/test-migrations/src/index.ts
|
|
4181
|
+
var testMigrations = [
|
|
4182
|
+
migrateAssertionsToPresetActions,
|
|
4183
|
+
migrateFailureToFailed,
|
|
4184
|
+
migratePresetStepTypeToDropPrefix,
|
|
4185
|
+
migrateElementDescriptorToTarget,
|
|
4186
|
+
migrateToAIStepV2,
|
|
4187
|
+
ensureAIStepHasDone
|
|
4188
|
+
// add new migrations here!
|
|
4189
|
+
];
|
|
4190
|
+
if (LATEST_VERSION !== testMigrations[testMigrations.length - 1].toVersion) {
|
|
4191
|
+
throw new Error(
|
|
4192
|
+
`Please bump LATEST_VERSION in types package after adding a migration`
|
|
4193
|
+
);
|
|
4194
|
+
}
|
|
4195
|
+
testMigrations.forEach((migration, index) => {
|
|
4196
|
+
if (!semver.valid(migration.toVersion) || !semver.valid(migration.fromVersion)) {
|
|
4197
|
+
throw new Error(`Migration '${migration.name}' has invalid version`);
|
|
4198
|
+
}
|
|
4199
|
+
if (!semver.gt(migration.toVersion, migration.fromVersion)) {
|
|
4200
|
+
throw new Error(
|
|
4201
|
+
`Migration '${migration.name}' has toVersion <= fromVersion`
|
|
4202
|
+
);
|
|
4203
|
+
}
|
|
4204
|
+
if (index === 0)
|
|
4205
|
+
return;
|
|
4206
|
+
const previousMigration = testMigrations[index - 1];
|
|
4207
|
+
if (previousMigration.toVersion !== migration.fromVersion) {
|
|
4208
|
+
throw new Error(
|
|
4209
|
+
`Migration '${migration.name}' at index ${index} is not contiguous with previous migration`
|
|
4210
|
+
);
|
|
4211
|
+
}
|
|
4212
|
+
});
|
|
4213
|
+
function isArrayOfStepObjects(val) {
|
|
4214
|
+
return val.every((v) => v && typeof v === "object" && !Array.isArray(v));
|
|
4215
|
+
}
|
|
4216
|
+
var runStepMigrations = (_0) => __async(void 0, [_0], function* ({
|
|
4217
|
+
metadata,
|
|
4218
|
+
steps: inputSteps,
|
|
4219
|
+
logger
|
|
4220
|
+
}) {
|
|
4221
|
+
let steps = inputSteps;
|
|
4222
|
+
const { schemaVersion: currentVersion, id } = metadata;
|
|
4223
|
+
const migrationIndex = testMigrations.findIndex(
|
|
4224
|
+
(migration) => semver.gt(migration.toVersion, currentVersion)
|
|
4225
|
+
);
|
|
4226
|
+
if (migrationIndex === -1) {
|
|
4227
|
+
logger.debug({ id }, "Step migrations up to date");
|
|
4228
|
+
return { steps, newVersion: currentVersion };
|
|
4229
|
+
}
|
|
4230
|
+
let newVersion = currentVersion;
|
|
4231
|
+
for (let i = migrationIndex; i < testMigrations.length; i++) {
|
|
4232
|
+
const migration = testMigrations[i];
|
|
4233
|
+
const logIdentifiers = {
|
|
4234
|
+
id,
|
|
4235
|
+
migration: migration.name,
|
|
4236
|
+
toVersion: migration.toVersion
|
|
4237
|
+
};
|
|
4238
|
+
logger.debug(logIdentifiers, "Starting migration");
|
|
4239
|
+
try {
|
|
4240
|
+
steps = yield migrateStepLikeArrayRecursively(steps, migration);
|
|
4241
|
+
newVersion = migration.toVersion;
|
|
4242
|
+
} catch (err) {
|
|
4243
|
+
logger.error(__spreadValues({ err }, logIdentifiers), "Migration failed");
|
|
4244
|
+
throw new Error(`Step migration ${migration.name} failed: ${err}`);
|
|
4245
|
+
}
|
|
4246
|
+
}
|
|
4247
|
+
const diffs = diffLines2(
|
|
4248
|
+
JSON.stringify(inputSteps, void 0, 2),
|
|
4249
|
+
JSON.stringify(steps, void 0, 2),
|
|
4250
|
+
{ n_surrounding: 1 }
|
|
4251
|
+
);
|
|
4252
|
+
logger.debug({ diffs, id }, "Migration diffs");
|
|
4253
|
+
return {
|
|
4254
|
+
newVersion,
|
|
4255
|
+
steps
|
|
4256
|
+
};
|
|
4257
|
+
});
|
|
4258
|
+
function migrateStepLikeArrayRecursively(inputSteps, migration) {
|
|
4259
|
+
return __async(this, null, function* () {
|
|
4260
|
+
const steps = yield migration.execute(inputSteps);
|
|
4261
|
+
for (const step of steps) {
|
|
4262
|
+
for (const key of Object.keys(step)) {
|
|
4263
|
+
if (!migration.recursiveKeys.has(key)) {
|
|
4264
|
+
continue;
|
|
4265
|
+
}
|
|
4266
|
+
const val = step[key];
|
|
4267
|
+
if (!val || !Array.isArray(val)) {
|
|
4268
|
+
continue;
|
|
4269
|
+
}
|
|
4270
|
+
if (!isArrayOfStepObjects(val)) {
|
|
4271
|
+
continue;
|
|
4272
|
+
}
|
|
4273
|
+
step[key] = yield migrateStepLikeArrayRecursively(val, migration);
|
|
4274
|
+
}
|
|
4275
|
+
}
|
|
4276
|
+
return steps;
|
|
4277
|
+
});
|
|
4278
|
+
}
|
|
4279
|
+
|
|
3121
4280
|
// src/run-test.ts
|
|
4281
|
+
import { parse } from "yaml";
|
|
3122
4282
|
function runTest(_0) {
|
|
3123
4283
|
return __async(this, arguments, function* ({
|
|
3124
|
-
|
|
4284
|
+
path: path3,
|
|
3125
4285
|
apiClient,
|
|
3126
4286
|
generator,
|
|
3127
|
-
newBaseURL
|
|
4287
|
+
newBaseURL,
|
|
4288
|
+
useLocalFiles
|
|
3128
4289
|
}) {
|
|
3129
|
-
|
|
4290
|
+
let test;
|
|
4291
|
+
if (useLocalFiles) {
|
|
4292
|
+
consoleLogger.info(`Reading ${path3} from local filesystem`);
|
|
4293
|
+
test = yield resolveLocalTest(path3);
|
|
4294
|
+
} else {
|
|
4295
|
+
consoleLogger.info(
|
|
4296
|
+
`Fetching ${path3} from Momentic server (${apiClient.baseURL})`
|
|
4297
|
+
);
|
|
4298
|
+
test = yield apiClient.getTest(path3);
|
|
4299
|
+
}
|
|
4300
|
+
if (test.schemaVersion > LATEST_VERSION) {
|
|
4301
|
+
consoleLogger.warn(
|
|
4302
|
+
`Test ${path3} has schema version ${test.schemaVersion}, which is greater than what is currently supported by this SDK. Please update your momentic package version to avoid unexpected behavior.`
|
|
4303
|
+
);
|
|
4304
|
+
}
|
|
3130
4305
|
const originalURL = new URL(test.baseUrl);
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
4306
|
+
if (newBaseURL) {
|
|
4307
|
+
const newURL = new URL(newBaseURL);
|
|
4308
|
+
originalURL.hostname = newURL.hostname;
|
|
4309
|
+
originalURL.protocol = newURL.protocol;
|
|
4310
|
+
originalURL.port = newURL.port;
|
|
4311
|
+
}
|
|
3135
4312
|
const browser = yield ChromeBrowser.init(
|
|
3136
4313
|
originalURL.toString(),
|
|
3137
4314
|
consoleLogger
|
|
@@ -3142,10 +4319,16 @@ function runTest(_0) {
|
|
|
3142
4319
|
config: DEFAULT_CONTROLLER_CONFIG,
|
|
3143
4320
|
logger: consoleLogger
|
|
3144
4321
|
});
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
4322
|
+
let run;
|
|
4323
|
+
try {
|
|
4324
|
+
run = yield apiClient.createRun({
|
|
4325
|
+
testId: test.id,
|
|
4326
|
+
trigger: "CLI"
|
|
4327
|
+
});
|
|
4328
|
+
} catch (err) {
|
|
4329
|
+
consoleLogger.info(err);
|
|
4330
|
+
throw new Error(`Are you sure test ${test.name} exists on the server?`);
|
|
4331
|
+
}
|
|
3149
4332
|
let failed = true;
|
|
3150
4333
|
try {
|
|
3151
4334
|
failed = yield executeTest({
|
|
@@ -3173,6 +4356,83 @@ function runTest(_0) {
|
|
|
3173
4356
|
return failed;
|
|
3174
4357
|
});
|
|
3175
4358
|
}
|
|
4359
|
+
function resolveLocalTest(filePath) {
|
|
4360
|
+
return __async(this, null, function* () {
|
|
4361
|
+
const { test, modules: unmigratedModules } = readTestWithModules(filePath);
|
|
4362
|
+
const unmigratedTest = parse(test);
|
|
4363
|
+
if (!unmigratedTest.steps || !Array.isArray(unmigratedTest.steps)) {
|
|
4364
|
+
throw new Error(`Test ${filePath} is missing steps`);
|
|
4365
|
+
}
|
|
4366
|
+
if (!unmigratedTest.schemaVersion || !unmigratedTest.id) {
|
|
4367
|
+
throw new Error(`Test ${filePath} is missing an ID or schema version`);
|
|
4368
|
+
}
|
|
4369
|
+
let steps;
|
|
4370
|
+
if (unmigratedTest.schemaVersion < LATEST_VERSION) {
|
|
4371
|
+
consoleLogger.warn(
|
|
4372
|
+
`Test ${filePath} has schema version ${unmigratedTest.schemaVersion}, which is lower than the version used by this SDK, ${LATEST_VERSION}. Your test will be migrated to the latest version before execution.`
|
|
4373
|
+
);
|
|
4374
|
+
const { steps: migratedSteps } = yield runStepMigrations({
|
|
4375
|
+
metadata: unmigratedTest,
|
|
4376
|
+
steps: unmigratedTest.steps,
|
|
4377
|
+
logger: consoleLogger
|
|
4378
|
+
});
|
|
4379
|
+
steps = StepSchema.array().parse(migratedSteps);
|
|
4380
|
+
} else {
|
|
4381
|
+
steps = StepSchema.array().parse(unmigratedTest.steps);
|
|
4382
|
+
}
|
|
4383
|
+
const migratedModules = {};
|
|
4384
|
+
for (const [moduleId, unmigratedModuleYAML] of Object.entries(
|
|
4385
|
+
unmigratedModules
|
|
4386
|
+
)) {
|
|
4387
|
+
const unmigratedModule = parse(unmigratedModuleYAML);
|
|
4388
|
+
if (!unmigratedModule.schemaVersion || !unmigratedModule.moduleId) {
|
|
4389
|
+
throw new Error(`Module ${moduleId} is missing an ID or schema version`);
|
|
4390
|
+
}
|
|
4391
|
+
if (!unmigratedModule.steps || !Array.isArray(unmigratedModule.steps)) {
|
|
4392
|
+
throw new Error(`Module ${moduleId} is missing steps`);
|
|
4393
|
+
}
|
|
4394
|
+
if (unmigratedModule.schemaVersion < LATEST_VERSION) {
|
|
4395
|
+
consoleLogger.warn(
|
|
4396
|
+
`Module ${moduleId} has schema version ${unmigratedModule.schemaVersion}, which is lower than the version used by this SDK, ${LATEST_VERSION}. Your module will be migrated to the latest version before execution.`
|
|
4397
|
+
);
|
|
4398
|
+
const { steps: migratedSteps } = yield runStepMigrations({
|
|
4399
|
+
metadata: {
|
|
4400
|
+
id: unmigratedModule.moduleId,
|
|
4401
|
+
schemaVersion: unmigratedModule.schemaVersion
|
|
4402
|
+
},
|
|
4403
|
+
steps: unmigratedModule.steps,
|
|
4404
|
+
logger: consoleLogger
|
|
4405
|
+
});
|
|
4406
|
+
migratedModules[moduleId] = __spreadProps(__spreadValues({}, unmigratedModule), {
|
|
4407
|
+
steps: AllowedModuleStepSchema.array().parse(migratedSteps)
|
|
4408
|
+
});
|
|
4409
|
+
} else {
|
|
4410
|
+
migratedModules[moduleId] = unmigratedModule;
|
|
4411
|
+
}
|
|
4412
|
+
}
|
|
4413
|
+
const resolvedTestSteps = steps.map((step) => {
|
|
4414
|
+
if (step.type !== "MODULE" /* MODULE */) {
|
|
4415
|
+
return step;
|
|
4416
|
+
}
|
|
4417
|
+
const resolvedModule = migratedModules[step.moduleId];
|
|
4418
|
+
if (!resolvedModule) {
|
|
4419
|
+
throw new Error(
|
|
4420
|
+
`Could not resolve module ${step.moduleId} required in test ${filePath}`
|
|
4421
|
+
);
|
|
4422
|
+
}
|
|
4423
|
+
const moduleStep = {
|
|
4424
|
+
type: "RESOLVED_MODULE",
|
|
4425
|
+
moduleId: step.moduleId,
|
|
4426
|
+
name: resolvedModule.name,
|
|
4427
|
+
steps: resolvedModule.steps
|
|
4428
|
+
};
|
|
4429
|
+
return moduleStep;
|
|
4430
|
+
});
|
|
4431
|
+
return MinimalRunnableResolvedTestSchema.parse(__spreadProps(__spreadValues({}, unmigratedTest), {
|
|
4432
|
+
steps: resolvedTestSteps
|
|
4433
|
+
}));
|
|
4434
|
+
});
|
|
4435
|
+
}
|
|
3176
4436
|
|
|
3177
4437
|
// src/run-tests-locally.ts
|
|
3178
4438
|
function runTestsLocally(_0) {
|
|
@@ -3181,50 +4441,103 @@ function runTestsLocally(_0) {
|
|
|
3181
4441
|
start,
|
|
3182
4442
|
waitOn,
|
|
3183
4443
|
waitOnTimeout,
|
|
3184
|
-
|
|
3185
|
-
|
|
4444
|
+
client,
|
|
4445
|
+
all,
|
|
4446
|
+
parallelization = 1
|
|
3186
4447
|
}) {
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
4448
|
+
if (start) {
|
|
4449
|
+
consoleLogger.info(`Running start command: ${start}`);
|
|
4450
|
+
yield execCommand(start, false);
|
|
4451
|
+
}
|
|
4452
|
+
if (waitOn) {
|
|
4453
|
+
consoleLogger.info(
|
|
4454
|
+
`Waiting for ${waitOn} to be accessible (timeout: ${waitOnTimeout}s)`
|
|
4455
|
+
);
|
|
4456
|
+
yield waitOnFn({
|
|
4457
|
+
resources: [waitOn],
|
|
4458
|
+
timeout: waitOnTimeout * 1e3
|
|
4459
|
+
});
|
|
4460
|
+
}
|
|
3196
4461
|
const apiGenerator = new APIGenerator({
|
|
3197
|
-
baseURL:
|
|
3198
|
-
apiKey
|
|
4462
|
+
baseURL: client.baseURL,
|
|
4463
|
+
apiKey: client.apiKey
|
|
3199
4464
|
});
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
4465
|
+
let useLocalFiles = false;
|
|
4466
|
+
if (tests.some((testPath) => testPath.endsWith(".yaml"))) {
|
|
4467
|
+
useLocalFiles = true;
|
|
4468
|
+
} else if (tests.some((testPath) => existsSync3(testPath))) {
|
|
4469
|
+
useLocalFiles = true;
|
|
4470
|
+
}
|
|
4471
|
+
let testsToRun = [];
|
|
4472
|
+
if (useLocalFiles) {
|
|
4473
|
+
consoleLogger.info(
|
|
4474
|
+
tests,
|
|
4475
|
+
`Reading tests from the following local file paths:`
|
|
4476
|
+
);
|
|
4477
|
+
tests.forEach((testPath) => {
|
|
4478
|
+
if (!existsSync3(testPath)) {
|
|
4479
|
+
throw new Error(`Path '${testPath}' does not exist.`);
|
|
4480
|
+
}
|
|
4481
|
+
const statResult = statSync2(testPath);
|
|
4482
|
+
if (statResult.isDirectory()) {
|
|
4483
|
+
testsToRun = testsToRun.concat(getTestFilesRecursively(testPath));
|
|
4484
|
+
} else if (testPath.endsWith(".yaml")) {
|
|
4485
|
+
testsToRun.push(testPath);
|
|
4486
|
+
} else {
|
|
4487
|
+
throw new Error(
|
|
4488
|
+
`Path '${testPath}' is not a directory or a .yaml file.`
|
|
4489
|
+
);
|
|
4490
|
+
}
|
|
4491
|
+
});
|
|
4492
|
+
} else {
|
|
4493
|
+
consoleLogger.warn(
|
|
4494
|
+
"The paths you specified are not files or directories that exist locally."
|
|
4495
|
+
);
|
|
4496
|
+
consoleLogger.info(
|
|
4497
|
+
`Fetching tests from remote Momentic server (${client.baseURL})...`
|
|
4498
|
+
);
|
|
4499
|
+
if (all) {
|
|
4500
|
+
testsToRun = yield client.getAllTestIds();
|
|
4501
|
+
} else {
|
|
4502
|
+
testsToRun = tests;
|
|
3211
4503
|
}
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
4504
|
+
}
|
|
4505
|
+
consoleLogger.info(
|
|
4506
|
+
testsToRun,
|
|
4507
|
+
`Identified ${testsToRun.length} tests to run locally:`
|
|
4508
|
+
);
|
|
4509
|
+
let results = [];
|
|
4510
|
+
for (let i = 0; i < testsToRun.length; i += parallelization) {
|
|
4511
|
+
const blockResults = yield Promise.all(
|
|
4512
|
+
testsToRun.slice(i, i + parallelization).map((path3) => __async(this, null, function* () {
|
|
4513
|
+
let failed = true;
|
|
4514
|
+
try {
|
|
4515
|
+
failed = yield runTest({
|
|
4516
|
+
useLocalFiles,
|
|
4517
|
+
path: path3,
|
|
4518
|
+
apiClient: client,
|
|
4519
|
+
generator: apiGenerator,
|
|
4520
|
+
newBaseURL: waitOn
|
|
4521
|
+
});
|
|
4522
|
+
} catch (e) {
|
|
4523
|
+
consoleLogger.error(`${e}`);
|
|
4524
|
+
}
|
|
4525
|
+
return { failed, path: path3 };
|
|
4526
|
+
}))
|
|
4527
|
+
);
|
|
4528
|
+
results = results.concat(blockResults);
|
|
4529
|
+
}
|
|
3215
4530
|
const failedResults = results.filter((result) => result.failed);
|
|
3216
4531
|
if (failedResults.length > 0) {
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
`Failed ${failedResults.length} out of ${results.length} tests:`
|
|
3220
|
-
)
|
|
4532
|
+
consoleLogger.error(
|
|
4533
|
+
`Failed ${failedResults.length} out of ${results.length} tests:`
|
|
3221
4534
|
);
|
|
3222
4535
|
failedResults.forEach((result) => {
|
|
3223
|
-
|
|
4536
|
+
consoleLogger.error(`- ${result.path}`);
|
|
3224
4537
|
});
|
|
3225
4538
|
process.exit(1);
|
|
3226
4539
|
}
|
|
3227
|
-
|
|
4540
|
+
consoleLogger.info(`All ${results.length} tests passed!`);
|
|
3228
4541
|
process.exit(0);
|
|
3229
4542
|
});
|
|
3230
4543
|
}
|
|
@@ -3241,87 +4554,124 @@ function execCommand(fullCommand, waitToFinish = true) {
|
|
|
3241
4554
|
}
|
|
3242
4555
|
|
|
3243
4556
|
// src/run-tests-remotely.ts
|
|
3244
|
-
import chalk2 from "chalk";
|
|
3245
4557
|
function runTestsRemotely(_0) {
|
|
3246
4558
|
return __async(this, arguments, function* ({
|
|
3247
4559
|
tests,
|
|
3248
|
-
|
|
3249
|
-
|
|
4560
|
+
client,
|
|
4561
|
+
all
|
|
3250
4562
|
}) {
|
|
3251
|
-
const
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
});
|
|
3255
|
-
yield apiClient.queueTests({
|
|
3256
|
-
testIds: tests
|
|
4563
|
+
const { queuedTests } = yield client.queueTests({
|
|
4564
|
+
testPaths: tests,
|
|
4565
|
+
all
|
|
3257
4566
|
});
|
|
3258
|
-
|
|
4567
|
+
consoleLogger.info(`Successfully queued ${queuedTests.length} tests!`);
|
|
3259
4568
|
});
|
|
3260
4569
|
}
|
|
3261
4570
|
|
|
3262
4571
|
// src/cli.ts
|
|
3263
|
-
var program = new
|
|
4572
|
+
var program = new Command3();
|
|
3264
4573
|
program.name("momentic").description("Momentic CLI").version(version);
|
|
3265
4574
|
program.command("install-browsers").action(() => __async(void 0, null, function* () {
|
|
3266
4575
|
yield installBrowsers();
|
|
3267
4576
|
}));
|
|
3268
|
-
program.
|
|
3269
|
-
new
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
).addOption(
|
|
3274
|
-
new
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
)
|
|
3279
|
-
).addOption(
|
|
3280
|
-
new Option("--remote", "run tests remotely").default(true).conflicts(["start, waitOn, waitOnTimeout"]).implies({
|
|
4577
|
+
program.addOption(
|
|
4578
|
+
new Option2("--log-level <level>").choices(["debug", "info", "warn", "error"]).default("info")
|
|
4579
|
+
).on("option:log-level", (level) => {
|
|
4580
|
+
consoleLogger.setMinLevel(stringToLogLevel[level.toUpperCase()]);
|
|
4581
|
+
});
|
|
4582
|
+
program.command("run").alias("run-tests").addOption(apiKeyOption).addOption(serverAddressOption).addOption(allOption).addOption(
|
|
4583
|
+
new Option2(
|
|
4584
|
+
"-r, --remote",
|
|
4585
|
+
"Run tests remotely. The production version of this test will be queued for execution."
|
|
4586
|
+
).default(true).conflicts(["start, waitOn, waitOnTimeout, local"]).implies({
|
|
3281
4587
|
local: false
|
|
3282
4588
|
})
|
|
3283
4589
|
).addOption(
|
|
3284
|
-
new
|
|
3285
|
-
|
|
3286
|
-
|
|
4590
|
+
new Option2(
|
|
4591
|
+
"-l, --local",
|
|
4592
|
+
"Run tests locally. Useful for accessing apps on localhost. This option does not control where tests are read from (see <tests> argument documentation)."
|
|
4593
|
+
).implies({
|
|
3287
4594
|
remote: false
|
|
3288
4595
|
})
|
|
3289
|
-
).addOption(
|
|
3290
|
-
new
|
|
4596
|
+
).addOption(
|
|
4597
|
+
new Option2(
|
|
4598
|
+
"--start <command>",
|
|
4599
|
+
"Arbitrary setup command that will run before Momentic steps begin."
|
|
4600
|
+
)
|
|
4601
|
+
).addOption(
|
|
4602
|
+
new Option2(
|
|
4603
|
+
"--wait-on <url>",
|
|
4604
|
+
"URL to wait to become accessible before Momentic tests begin."
|
|
4605
|
+
)
|
|
4606
|
+
).addOption(
|
|
4607
|
+
new Option2(
|
|
3291
4608
|
"--wait-on-timeout <timeout>",
|
|
3292
|
-
"
|
|
4609
|
+
"Max time to wait for the --wait-on URL to become accessible."
|
|
3293
4610
|
).default(60, "one minute")
|
|
3294
|
-
).
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
}
|
|
3305
|
-
if (remote) {
|
|
3306
|
-
yield runTestsRemotely({ tests, apiKey, server });
|
|
3307
|
-
return;
|
|
3308
|
-
}
|
|
4611
|
+
).addOption(
|
|
4612
|
+
new Option2(
|
|
4613
|
+
"-p, --parallel <parallelization>",
|
|
4614
|
+
"When running with the --local flag, the number of tests to run in parallel. Defaults to 1."
|
|
4615
|
+
).default(1)
|
|
4616
|
+
).addArgument(testsOrFilesVariadicArgument).action((tests, options) => __async(void 0, null, function* () {
|
|
4617
|
+
const { apiKey, server, remote, local, all } = options;
|
|
4618
|
+
const client = new APIClient({
|
|
4619
|
+
baseURL: server,
|
|
4620
|
+
apiKey
|
|
4621
|
+
});
|
|
3309
4622
|
if (local) {
|
|
3310
4623
|
try {
|
|
3311
|
-
yield runTestsLocally({
|
|
4624
|
+
yield runTestsLocally(__spreadValues({
|
|
3312
4625
|
tests,
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
waitOnTimeout,
|
|
3316
|
-
server,
|
|
3317
|
-
apiKey
|
|
3318
|
-
});
|
|
4626
|
+
client
|
|
4627
|
+
}, options));
|
|
3319
4628
|
} catch (e) {
|
|
3320
|
-
|
|
4629
|
+
consoleLogger.error(e);
|
|
3321
4630
|
process.exit(1);
|
|
3322
4631
|
}
|
|
3323
4632
|
return;
|
|
3324
4633
|
}
|
|
4634
|
+
if (remote) {
|
|
4635
|
+
if (tests.some((test) => test.endsWith(".yaml") || existsSync4(test))) {
|
|
4636
|
+
const prompt = "A local file path was provided without the --local flag. The production version of the specified test will be queued remotely. Continue?";
|
|
4637
|
+
if (!(yield promptForConfirmation(prompt, true))) {
|
|
4638
|
+
consoleLogger.info("Run cancelled");
|
|
4639
|
+
process.exit(1);
|
|
4640
|
+
}
|
|
4641
|
+
tests = tests.map((test) => {
|
|
4642
|
+
if (test.endsWith(".yaml")) {
|
|
4643
|
+
test = test.slice(0, -5);
|
|
4644
|
+
}
|
|
4645
|
+
return test;
|
|
4646
|
+
});
|
|
4647
|
+
}
|
|
4648
|
+
yield runTestsRemotely({ tests, client, all });
|
|
4649
|
+
return;
|
|
4650
|
+
}
|
|
4651
|
+
throw new Error("One of remote or local must be specified");
|
|
4652
|
+
}));
|
|
4653
|
+
program.command("pull").description(
|
|
4654
|
+
"Fetch one or more tests from your organization and save it in YAML format on local disk."
|
|
4655
|
+
).addOption(apiKeyOption).addOption(serverAddressOption).addOption(outDirOption).addOption(allOption).addArgument(testPathsVariadicArgument).action((tests, options) => __async(void 0, null, function* () {
|
|
4656
|
+
const { apiKey, server, outDir, all } = options;
|
|
4657
|
+
if (!all && !(tests == null ? void 0 : tests.length)) {
|
|
4658
|
+
throw new Error("At least one test name or --all must be provided");
|
|
4659
|
+
}
|
|
4660
|
+
const client = new APIClient({
|
|
4661
|
+
baseURL: server,
|
|
4662
|
+
apiKey
|
|
4663
|
+
});
|
|
4664
|
+
yield fetchAndSaveTestToDisk({ testPaths: tests, client, outDir, all });
|
|
4665
|
+
}));
|
|
4666
|
+
program.command("push").description(
|
|
4667
|
+
"Save a local YAML file containing a test to your cloud workspace."
|
|
4668
|
+
).addOption(yesOption).addOption(apiKeyOption).addOption(serverAddressOption).addArgument(filePathsVariadicArgument).action((paths, options) => __async(void 0, null, function* () {
|
|
4669
|
+
const { apiKey, server, yes } = options;
|
|
4670
|
+
const client = new APIClient({
|
|
4671
|
+
baseURL: server,
|
|
4672
|
+
apiKey
|
|
4673
|
+
});
|
|
4674
|
+
yield saveTestToServer({ paths, client, yes });
|
|
3325
4675
|
}));
|
|
3326
4676
|
function main() {
|
|
3327
4677
|
return __async(this, null, function* () {
|