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.
Files changed (3) hide show
  1. package/bin/cli.js +2060 -710
  2. package/dist/index.js +853 -361
  3. 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 Command4, Option } from "commander";
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 ElementDescriptorSchema = z2.object({
107
+ var ElementTargetSchema = z3.object({
130
108
  // natural language passed to LLM
131
- elementDescriptor: z2.string(),
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 = z2.object({
113
+ var CommonCommandSchema = z3.object({
136
114
  // If the command is suggested by AI, why it did so
137
- thoughts: z2.string().optional()
115
+ thoughts: z3.string().optional()
138
116
  });
139
117
  var NavigateCommandSchema = CommonCommandSchema.merge(
140
- z2.object({
141
- type: z2.literal("NAVIGATE" /* NAVIGATE */),
142
- url: z2.string()
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
- z2.object({
147
- type: z2.literal("SCROLL_UP" /* SCROLL_UP */)
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
- z2.object({
152
- type: z2.literal("SCROLL_DOWN" /* SCROLL_DOWN */)
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
- z2.object({
157
- type: z2.literal("WAIT" /* WAIT */),
158
- delay: z2.number()
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
- z2.object({
164
- type: z2.literal("REFRESH" /* REFRESH */)
141
+ z3.object({
142
+ type: z3.literal("REFRESH" /* REFRESH */)
165
143
  })
166
144
  );
167
145
  var GoBackCommandSchema = CommonCommandSchema.merge(
168
- z2.object({
169
- type: z2.literal("GO_BACK" /* GO_BACK */)
146
+ z3.object({
147
+ type: z3.literal("GO_BACK" /* GO_BACK */)
170
148
  })
171
149
  );
172
150
  var GoForwardCommandSchema = CommonCommandSchema.merge(
173
- z2.object({
174
- type: z2.literal("GO_FORWARD" /* GO_FORWARD */)
151
+ z3.object({
152
+ type: z3.literal("GO_FORWARD" /* GO_FORWARD */)
175
153
  })
176
154
  );
177
155
  var ClickCommandSchema = CommonCommandSchema.merge(
178
- z2.object({
179
- type: z2.literal("CLICK" /* CLICK */),
180
- target: ElementDescriptorSchema,
181
- doubleClick: z2.boolean().default(false),
182
- rightClick: z2.boolean().default(false)
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
- `.replace("\n", " ")
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
- z2.object({
193
- type: z2.literal("SELECT_OPTION" /* SELECT_OPTION */),
194
- target: ElementDescriptorSchema,
195
- option: z2.string()
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
- z2.object({
203
- type: z2.literal("AI_ASSERTION" /* AI_ASSERTION */),
204
- assertion: z2.string(),
205
- useVision: z2.boolean().default(false),
206
- disableCache: z2.boolean().default(false)
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 = z2.object({
210
- clearContent: z2.boolean().default(true),
211
- pressKeysSequentially: z2.boolean().default(false)
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
- z2.object({
215
- type: z2.literal("TYPE" /* TYPE */),
216
- target: ElementDescriptorSchema,
217
- value: z2.string(),
218
- pressEnter: z2.boolean().default(false)
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
- z2.object({
225
- type: z2.literal("PRESS" /* PRESS */),
226
- value: z2.string()
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
- z2.object({
233
- type: z2.literal("TAB" /* TAB */),
234
- url: z2.string()
216
+ z3.object({
217
+ type: z3.literal("TAB" /* TAB */),
218
+ url: z3.string()
235
219
  })
236
220
  );
237
221
  var CookieCommandSchema = CommonCommandSchema.merge(
238
- z2.object({
239
- type: z2.literal("COOKIE" /* COOKIE */),
240
- value: z2.string()
222
+ z3.object({
223
+ type: z3.literal("COOKIE" /* COOKIE */),
224
+ value: z3.string()
241
225
  })
242
226
  );
243
227
  var SuccessCommandSchema = CommonCommandSchema.merge(
244
- z2.object({
245
- type: z2.literal("SUCCESS" /* SUCCESS */),
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 = z2.discriminatedUnion("type", [
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 = z2.discriminatedUnion("type", [
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 = z2.discriminatedUnion("type", [
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
- z2.object({
276
- type: z2.literal("FAILURE")
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 = z2.discriminatedUnion("type", [
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 z3 from "zod";
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 = z3.object({
295
- type: z3.literal("AI_ACTION" /* AI_ACTION */),
296
- text: z3.string(),
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: z3.array(UserEditableAICommandSchema).optional()
305
+ commands: z5.array(UserEditableAICommandSchema).optional()
299
306
  });
300
- var PresetActionSchema = z3.object({
301
- type: z3.literal("PRESET_ACTION" /* PRESET_ACTION */),
307
+ var PresetActionSchema = z5.object({
308
+ type: z5.literal("PRESET_ACTION" /* PRESET_ACTION */),
302
309
  command: CommandSchema
303
310
  });
304
- var ModuleStepSchema = z3.object({
305
- type: z3.literal("MODULE" /* MODULE */),
306
- moduleId: z3.string().uuid()
311
+ var ModuleStepSchema = z5.object({
312
+ type: z5.literal("MODULE" /* MODULE */),
313
+ moduleId: z5.string().uuid()
307
314
  });
308
- var AllowedModuleStepSchema = z3.union([
315
+ var AllowedModuleStepSchema = z5.union([
309
316
  AIActionSchema,
310
317
  PresetActionSchema
311
318
  ]);
312
- var ResolvedModuleStepSchema = z3.object({
313
- type: z3.literal("RESOLVED_MODULE"),
314
- moduleId: z3.string().uuid(),
315
- name: z3.string(),
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 = z3.union([
325
+ var StepSchema = z5.union([
319
326
  AIActionSchema,
320
327
  PresetActionSchema,
321
328
  ModuleStepSchema
322
329
  ]);
323
- var ResolvedStepSchema = z3.union([
330
+ var ResolvedStepSchema = z5.union([
324
331
  AIActionSchema,
325
332
  PresetActionSchema,
326
333
  ResolvedModuleStepSchema
327
334
  ]);
328
335
 
329
- // ../../node_modules/.pnpm/playwright@1.40.1/node_modules/playwright/index.mjs
330
- var playwright_exports = {};
331
- __export(playwright_exports, {
332
- default: () => playwright_default
333
- });
334
- __reExport(playwright_exports, playwright_core_star);
335
- import * as playwright_core_star from "playwright-core";
336
- import playwright from "playwright-core";
337
- var playwright_default = playwright;
338
-
339
- // ../../packages/types/src/assertions.ts
340
- import { z as z4 } from "zod";
341
- var AIAssertionResultSchema = z4.object({
342
- thoughts: z4.string(),
343
- result: z4.boolean(),
344
- relevantElements: z4.array(z4.number()).optional()
345
- });
346
-
347
- // ../../packages/types/src/ai-command-generation.ts
348
- import parseArgsStringToArgv from "string-argv";
349
- import { z as z5 } from "zod";
350
-
351
- // ../../packages/types/src/errors.ts
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 EmptyA11yTreeError = class extends Error {
359
- constructor(options = {}) {
360
- super("Got empty a11y tree", options);
361
- this.name = "EmptyA11yTreeError";
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 z8 } from "zod";
492
- var InstructionsSchema = z8.string().array();
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 z9 from "zod";
496
- var AILocatorSchema = z9.object({
497
- thoughts: z9.string(),
590
+ import * as z10 from "zod";
591
+ var AILocatorSchema = z10.object({
592
+ thoughts: z10.string(),
498
593
  // a11y id
499
- id: z9.number().int(),
594
+ id: z10.number().int(),
500
595
  // dropdowns should have options
501
- options: z9.array(z9.string()).optional()
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 z10 } from "zod";
506
- var ModuleMetadataSchema = z10.object({
507
- id: z10.string(),
508
- createdAt: z10.coerce.date(),
509
- createdBy: z10.string(),
510
- organizationId: z10.string().or(z10.null()),
511
- name: z10.string(),
512
- schemaVersion: z10.string(),
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: z10.number()
724
+ numSteps: z11.number()
515
725
  });
516
- var ModuleSchema = z10.object({
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 z11 } from "zod";
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 = z11.string().pipe(z11.coerce.date()).or(z11.date());
536
- var RunMetadataSchema = z11.object({
537
- id: z11.string(),
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: z11.string(),
540
- organizationId: z11.string().or(z11.null()),
541
- scheduledAt: DateOrStringSchema.or(z11.null()),
542
- startedAt: DateOrStringSchema.or(z11.null()),
543
- finishedAt: DateOrStringSchema.or(z11.null()),
544
- testId: z11.string().or(z11.null()),
545
- status: z11.nativeEnum(RunStatusEnum),
546
- trigger: z11.nativeEnum(RunTriggerEnum),
547
- test: z11.object({
548
- name: z11.string(),
549
- id: z11.string()
550
- }).or(z11.null())
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
- z11.object({
767
+ z12.object({
554
768
  results: ResultSchema.array(),
555
- test: z11.object({
556
- name: z11.string(),
557
- id: z11.string(),
558
- baseUrl: z11.string()
559
- }).or(z11.null())
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 z13 } from "zod";
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 z12 } from "zod";
669
- var TestAdvancedSettingsSchema = z12.object({
670
- availableAsModule: z12.boolean().default(false),
671
- disableAICaching: z12.boolean().default(false)
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 = z12.object({
674
- cron: z12.string().refine(
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: z12.boolean().default(false),
681
- timeZone: z12.string().default("America/Los_Angeles"),
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: z12.string().optional()
797
+ jobKey: z13.string().optional()
684
798
  });
685
- var WebhookSchema = z12.object({
686
- lastStatus: z12.number().optional(),
687
- url: z12.string().url()
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 ResolvedTestSchema = z13.object({
698
- id: z13.string(),
699
- name: z13.string(),
700
- baseUrl: z13.string(),
701
- steps: z13.array(ResolvedStepSchema),
702
- createdAt: z13.coerce.date(),
703
- updatedAt: z13.coerce.date(),
704
- createdBy: z13.string(),
705
- organizationId: z13.string().or(z13.null()),
706
- schemaVersion: z13.string(),
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
- schedule: ScheduleSettingsSchema,
709
- webhooks: WebhookSettingsSchema
823
+ retries: z14.number()
710
824
  });
711
-
712
- // ../../packages/types/src/context.ts
713
- import * as z14 from "zod";
714
- var DynamicContextSchema = z14.object({
715
- // user goal or instruction
716
- goal: z14.string(),
717
- // current url of the browser
718
- url: z14.string(),
719
- // serialized page state
720
- browserState: z14.string(),
721
- // serialized history of previous commands
722
- history: z14.string(),
723
- // number of previously executed commands
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.object({
767
- testIds: z15.string().array()
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 CreateRunBodySchema = z15.object({
771
- testId: z15.string(),
772
- trigger: z15.nativeEnum(RunTriggerEnum)
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", "menuitem", "option"]);
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
- if (this.children.length === 0 || noChildren) {
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
- for (const child of this.children) {
888
- s += child.serialize({ indentLevel: indentLevel + 2 });
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 playwright_exports.chromium.launch({ headless: true });
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: playwright_exports.devices["Desktop Chrome"].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
- yield this.page.keyboard.press("Meta+A");
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 in DOM with index: ${index}`);
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(`Could not find node in DOM with index: ${index}`);
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
- highlight(target) {
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 node = this.nodeMap.get(`${index}`);
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 index: ${index}`);
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
- `Select target missing backend node id: ${node.getLogForm()}`
2124
+ `Focus target missing backend node id: ${node.getLogForm()}`
1428
2125
  );
1429
2126
  }
1430
- yield this.highlightNode(node);
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({ err }, "Failed to add node highlight");
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
- requests: JSON.stringify(Array.from(unfinishedRequests.entries()))
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
- try {
1556
- yield this.page.waitForLoadState("load", {
1557
- timeout: timeoutMS - (Date.now() - startTime)
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
- } catch (e) {
1560
- this.logger.warn(
1561
- { url: this.url },
1562
- "Timeout elapsed waiting for load state to fire, continuing anyways..."
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
- return unwrapAndThrowError(retPromise);
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(target.id, options)
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
- return this.selectOptionByA11yID(target.id, option);
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 }, `A11y tree load event fired`);
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 = playwright_exports.devices["Desktop Chrome"].userAgent;
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
- this.logger.info(
2160
- { result, duration: Date.now() - executionStart },
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, _b, _c;
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
- let id;
2288
- if (command.target.a11yData) {
2289
- id = (_b = command.target.a11yData) == null ? void 0 : _b.id;
2290
- } else {
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
- let id;
2319
- if (command.target.a11yData) {
2320
- id = (_c = command.target.a11yData) == null ? void 0 : _c.id;
2321
- } else {
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
- let elementInteracted;
2348
- const target = command.target;
2349
- if (target.a11yData) {
2350
- elementInteracted = yield this.browser.click({
2351
- id: target.a11yData.id
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 API_VERSION = "v1";
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
- `/${API_VERSION}/web-agent/locate-element`,
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
- `/${API_VERSION}/web-agent/assertion`,
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
- `/${API_VERSION}/web-agent/assertion`,
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
- `/${API_VERSION}/web-agent/next-command`,
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
- goal: context.goal,
2483
- disableCache
2484
- }
2485
- );
2486
- return SplitGoalResponseSchema.parse(result);
2487
- });
2488
- }
2489
- sendRequest(path, body) {
2490
- return __async(this, null, function* () {
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
- queueTests(body) {
3322
+ getGranularGoals(context, disableCache) {
2552
3323
  return __async(this, null, function* () {
2553
- yield this.sendRequest(`/${API_VERSION2}/tests/queue`, {
2554
- method: "POST",
2555
- body
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
- uploadScreenshot(body) {
3335
+ sendRequest(path3, body) {
2560
3336
  return __async(this, null, function* () {
2561
- const result = yield this.sendRequest(`/${API_VERSION2}/screenshots`, {
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 ${path} failed with status ${response.status}: ${yield response.text()}`
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
- `Executing command ${commandIndex}: ${serializeCommand(command)}`
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.info({ i, moduleStep }, `Starting module step`);
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/logger.ts
3111
- var consoleLogger = {
3112
- info: console.log,
3113
- error: console.error,
3114
- debug: console.debug,
3115
- warn: console.warn,
3116
- child: () => consoleLogger,
3117
- flush: () => {
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
- testId,
4284
+ path: path3,
3125
4285
  apiClient,
3126
4286
  generator,
3127
- newBaseURL
4287
+ newBaseURL,
4288
+ useLocalFiles
3128
4289
  }) {
3129
- const test = yield apiClient.getTest(testId);
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
- const newURL = new URL(newBaseURL);
3132
- originalURL.hostname = newURL.hostname;
3133
- originalURL.protocol = newURL.protocol;
3134
- originalURL.port = newURL.port;
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
- const run = yield apiClient.createRun({
3146
- testId,
3147
- trigger: "CLI"
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
- server,
3185
- apiKey
4444
+ client,
4445
+ all,
4446
+ parallelization = 1
3186
4447
  }) {
3187
- yield execCommand(start, false);
3188
- yield waitOnFn({
3189
- resources: [waitOn],
3190
- timeout: waitOnTimeout * 1e3
3191
- });
3192
- const apiClient = new APIClient({
3193
- baseURL: server,
3194
- apiKey
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: server,
3198
- apiKey
4462
+ baseURL: client.baseURL,
4463
+ apiKey: client.apiKey
3199
4464
  });
3200
- const promises = tests.map((testId) => __async(this, null, function* () {
3201
- let failed = true;
3202
- try {
3203
- failed = yield runTest({
3204
- testId,
3205
- apiClient,
3206
- generator: apiGenerator,
3207
- newBaseURL: waitOn
3208
- });
3209
- } catch (e) {
3210
- console.error(e);
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
- return { failed, testId };
3213
- }));
3214
- const results = yield Promise.all(promises);
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
- console.log(
3218
- chalk.red(
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
- console.log(chalk.red(`- ${result.testId}`));
4536
+ consoleLogger.error(`- ${result.path}`);
3224
4537
  });
3225
4538
  process.exit(1);
3226
4539
  }
3227
- console.log(chalk.green(`All ${results.length} tests passed!`));
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
- apiKey,
3249
- server
4560
+ client,
4561
+ all
3250
4562
  }) {
3251
- const apiClient = new APIClient({
3252
- baseURL: server,
3253
- apiKey
3254
- });
3255
- yield apiClient.queueTests({
3256
- testIds: tests
4563
+ const { queuedTests } = yield client.queueTests({
4564
+ testPaths: tests,
4565
+ all
3257
4566
  });
3258
- console.log(chalk2.green(`Successfully queued ${tests.length} tests!`));
4567
+ consoleLogger.info(`Successfully queued ${queuedTests.length} tests!`);
3259
4568
  });
3260
4569
  }
3261
4570
 
3262
4571
  // src/cli.ts
3263
- var program = new Command4();
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.command("run-tests").addOption(
3269
- new Option(
3270
- "--tests <tests...>",
3271
- "specify tests to run"
3272
- ).makeOptionMandatory(true)
3273
- ).addOption(
3274
- new Option("--api-key <key>", "API key for authenticating").env("MOMENTIC_API_KEY").makeOptionMandatory(true)
3275
- ).addOption(
3276
- new Option("--server <server>", "Momentic server to use").default(
3277
- "https://api.momentic.ai"
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 Option("--local", "run tests locally").implies({
3285
- start: "npm run start",
3286
- waitOn: "http://localhost:3000",
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(new Option("--start <command>", "specify start command")).addOption(new Option("--wait-on <url>", "specify URL to wait on")).addOption(
3290
- new Option(
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
- "specify how long to wait on url"
4609
+ "Max time to wait for the --wait-on URL to become accessible."
3293
4610
  ).default(60, "one minute")
3294
- ).action((options) => __async(void 0, null, function* () {
3295
- const {
3296
- tests,
3297
- apiKey,
3298
- server,
3299
- remote,
3300
- local,
3301
- start,
3302
- waitOn,
3303
- waitOnTimeout
3304
- } = options;
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
- start,
3314
- waitOn,
3315
- waitOnTimeout,
3316
- server,
3317
- apiKey
3318
- });
4626
+ client
4627
+ }, options));
3319
4628
  } catch (e) {
3320
- console.error(e);
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* () {