momentic 0.0.6 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +1279 -1432
- package/dist/index.js +1079 -1218
- package/package.json +3 -10
- package/dist/index.mjs +0 -2455
package/bin/cli.js
CHANGED
|
@@ -1,89 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
"use strict";
|
|
3
|
-
var __create = Object.create;
|
|
4
|
-
var __defProp = Object.defineProperty;
|
|
5
|
-
var __defProps = Object.defineProperties;
|
|
6
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
7
|
-
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
8
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
9
|
-
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
10
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
11
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
12
|
-
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
13
|
-
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
14
|
-
var __spreadValues = (a, b) => {
|
|
15
|
-
for (var prop in b || (b = {}))
|
|
16
|
-
if (__hasOwnProp.call(b, prop))
|
|
17
|
-
__defNormalProp(a, prop, b[prop]);
|
|
18
|
-
if (__getOwnPropSymbols)
|
|
19
|
-
for (var prop of __getOwnPropSymbols(b)) {
|
|
20
|
-
if (__propIsEnum.call(b, prop))
|
|
21
|
-
__defNormalProp(a, prop, b[prop]);
|
|
22
|
-
}
|
|
23
|
-
return a;
|
|
24
|
-
};
|
|
25
|
-
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
26
|
-
var __objRest = (source, exclude) => {
|
|
27
|
-
var target = {};
|
|
28
|
-
for (var prop in source)
|
|
29
|
-
if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
|
|
30
|
-
target[prop] = source[prop];
|
|
31
|
-
if (source != null && __getOwnPropSymbols)
|
|
32
|
-
for (var prop of __getOwnPropSymbols(source)) {
|
|
33
|
-
if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
|
|
34
|
-
target[prop] = source[prop];
|
|
35
|
-
}
|
|
36
|
-
return target;
|
|
37
|
-
};
|
|
38
|
-
var __copyProps = (to, from, except, desc) => {
|
|
39
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
40
|
-
for (let key of __getOwnPropNames(from))
|
|
41
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
42
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
43
|
-
}
|
|
44
|
-
return to;
|
|
45
|
-
};
|
|
46
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
47
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
48
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
49
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
50
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
51
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
52
|
-
mod
|
|
53
|
-
));
|
|
54
|
-
var __async = (__this, __arguments, generator) => {
|
|
55
|
-
return new Promise((resolve, reject) => {
|
|
56
|
-
var fulfilled = (value) => {
|
|
57
|
-
try {
|
|
58
|
-
step(generator.next(value));
|
|
59
|
-
} catch (e) {
|
|
60
|
-
reject(e);
|
|
61
|
-
}
|
|
62
|
-
};
|
|
63
|
-
var rejected = (value) => {
|
|
64
|
-
try {
|
|
65
|
-
step(generator.throw(value));
|
|
66
|
-
} catch (e) {
|
|
67
|
-
reject(e);
|
|
68
|
-
}
|
|
69
|
-
};
|
|
70
|
-
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
|
|
71
|
-
step((generator = generator.apply(__this, __arguments)).next());
|
|
72
|
-
});
|
|
73
|
-
};
|
|
74
2
|
|
|
75
3
|
// src/cli.ts
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import { Command as Command4, Option } from "commander";
|
|
6
|
+
import { execa } from "execa";
|
|
7
|
+
import waitOnFn from "wait-on";
|
|
80
8
|
|
|
81
9
|
// ../../packages/types/src/commands.ts
|
|
82
|
-
|
|
83
|
-
|
|
10
|
+
import dedent from "dedent";
|
|
11
|
+
import * as z2 from "zod";
|
|
84
12
|
|
|
85
13
|
// ../../packages/types/src/a11y-targets.ts
|
|
86
|
-
|
|
14
|
+
import * as z from "zod";
|
|
87
15
|
var A11yTargetWithCacheSchema = z.object({
|
|
88
16
|
// a11y ID
|
|
89
17
|
id: z.number().int(),
|
|
@@ -171,7 +99,7 @@ var ClickCommandSchema = CommonCommandSchema.merge(
|
|
|
171
99
|
rightClick: z2.boolean().default(false)
|
|
172
100
|
})
|
|
173
101
|
).describe(
|
|
174
|
-
|
|
102
|
+
dedent`CLICK <id> - click on the element that has the specified id.
|
|
175
103
|
You are NOT allowed to click on disabled, hidden or StaticText elements.
|
|
176
104
|
Only click on elements on the Current Page.
|
|
177
105
|
Only click on elements with the following tag names: button, input, link, image, generic.
|
|
@@ -273,7 +201,7 @@ var AICommandSchema = z2.discriminatedUnion("type", [
|
|
|
273
201
|
]);
|
|
274
202
|
|
|
275
203
|
// ../../packages/types/src/steps.ts
|
|
276
|
-
|
|
204
|
+
import * as z3 from "zod";
|
|
277
205
|
var StepType = /* @__PURE__ */ ((StepType2) => {
|
|
278
206
|
StepType2["AI_ACTION"] = "AI_ACTION";
|
|
279
207
|
StepType2["PRESET_ACTION"] = "PRESET_ACTION";
|
|
@@ -316,18 +244,21 @@ var ResolvedStepSchema = z3.union([
|
|
|
316
244
|
]);
|
|
317
245
|
|
|
318
246
|
// ../../packages/web-agent/src/browsers/chrome.ts
|
|
319
|
-
|
|
247
|
+
import {
|
|
248
|
+
chromium,
|
|
249
|
+
devices
|
|
250
|
+
} from "playwright";
|
|
320
251
|
|
|
321
252
|
// ../../packages/types/src/assertions.ts
|
|
322
|
-
|
|
323
|
-
var AIAssertionResultSchema =
|
|
324
|
-
thoughts:
|
|
325
|
-
result:
|
|
326
|
-
relevantElements:
|
|
253
|
+
import { z as z4 } from "zod";
|
|
254
|
+
var AIAssertionResultSchema = z4.object({
|
|
255
|
+
thoughts: z4.string(),
|
|
256
|
+
result: z4.boolean(),
|
|
257
|
+
relevantElements: z4.array(z4.number()).optional()
|
|
327
258
|
});
|
|
328
259
|
|
|
329
260
|
// ../../packages/types/src/ai-command-generation.ts
|
|
330
|
-
|
|
261
|
+
import { z as z5 } from "zod";
|
|
331
262
|
|
|
332
263
|
// ../../packages/types/src/errors.ts
|
|
333
264
|
var BrowserExecutionError = class extends Error {
|
|
@@ -344,14 +275,14 @@ var EmptyA11yTreeError = class extends Error {
|
|
|
344
275
|
};
|
|
345
276
|
|
|
346
277
|
// ../../packages/types/src/ai-command-generation.ts
|
|
347
|
-
var LLMOutputSchema =
|
|
348
|
-
command:
|
|
349
|
-
thoughts:
|
|
278
|
+
var LLMOutputSchema = z5.object({
|
|
279
|
+
command: z5.string(),
|
|
280
|
+
thoughts: z5.string()
|
|
350
281
|
});
|
|
351
|
-
var NumericStringSchema =
|
|
282
|
+
var NumericStringSchema = z5.string().pipe(z5.coerce.number());
|
|
352
283
|
|
|
353
284
|
// ../../packages/types/src/command-results.ts
|
|
354
|
-
|
|
285
|
+
import * as z6 from "zod";
|
|
355
286
|
var ResultStatus = /* @__PURE__ */ ((ResultStatus2) => {
|
|
356
287
|
ResultStatus2["SUCCESS"] = "SUCCESS";
|
|
357
288
|
ResultStatus2["FAILED"] = "FAILED";
|
|
@@ -423,9 +354,9 @@ var ResultSchema = z6.discriminatedUnion("type", [
|
|
|
423
354
|
]);
|
|
424
355
|
|
|
425
356
|
// ../../packages/types/src/cookies.ts
|
|
426
|
-
|
|
357
|
+
import { parseString } from "set-cookie-parser";
|
|
427
358
|
function parseCookieString(cookie) {
|
|
428
|
-
const parsedCookie =
|
|
359
|
+
const parsedCookie = parseString(cookie);
|
|
429
360
|
if (!parsedCookie.name) {
|
|
430
361
|
throw new Error("Name missing from cookie");
|
|
431
362
|
}
|
|
@@ -448,15 +379,16 @@ function parseCookieString(cookie) {
|
|
|
448
379
|
if (!parsedCookie.path && parsedCookie.domain) {
|
|
449
380
|
parsedCookie.path = "/";
|
|
450
381
|
}
|
|
451
|
-
const result =
|
|
382
|
+
const result = {
|
|
383
|
+
...parsedCookie,
|
|
452
384
|
expires: parsedCookie.expires ? parsedCookie.expires.getTime() / 1e3 : void 0,
|
|
453
385
|
sameSite
|
|
454
|
-
}
|
|
386
|
+
};
|
|
455
387
|
return result;
|
|
456
388
|
}
|
|
457
389
|
|
|
458
390
|
// ../../packages/types/src/execute-results.ts
|
|
459
|
-
|
|
391
|
+
import * as z7 from "zod";
|
|
460
392
|
var ExecuteCommandHistoryEntrySchema = z7.object({
|
|
461
393
|
// type of command executed
|
|
462
394
|
type: z7.nativeEnum(StepType),
|
|
@@ -469,11 +401,11 @@ var ExecuteCommandHistoryEntrySchema = z7.object({
|
|
|
469
401
|
});
|
|
470
402
|
|
|
471
403
|
// ../../packages/types/src/goal-splitter.ts
|
|
472
|
-
|
|
473
|
-
var InstructionsSchema =
|
|
404
|
+
import { z as z8 } from "zod";
|
|
405
|
+
var InstructionsSchema = z8.string().array();
|
|
474
406
|
|
|
475
407
|
// ../../packages/types/src/locator.ts
|
|
476
|
-
|
|
408
|
+
import * as z9 from "zod";
|
|
477
409
|
var AILocatorSchema = z9.object({
|
|
478
410
|
thoughts: z9.string(),
|
|
479
411
|
// a11y id
|
|
@@ -483,23 +415,23 @@ var AILocatorSchema = z9.object({
|
|
|
483
415
|
});
|
|
484
416
|
|
|
485
417
|
// ../../packages/types/src/modules.ts
|
|
486
|
-
|
|
487
|
-
var ModuleMetadataSchema =
|
|
488
|
-
id:
|
|
489
|
-
createdAt:
|
|
490
|
-
createdBy:
|
|
491
|
-
organizationId:
|
|
492
|
-
name:
|
|
493
|
-
schemaVersion:
|
|
418
|
+
import { z as z10 } from "zod";
|
|
419
|
+
var ModuleMetadataSchema = z10.object({
|
|
420
|
+
id: z10.string(),
|
|
421
|
+
createdAt: z10.coerce.date(),
|
|
422
|
+
createdBy: z10.string(),
|
|
423
|
+
organizationId: z10.string().or(z10.null()),
|
|
424
|
+
name: z10.string(),
|
|
425
|
+
schemaVersion: z10.string(),
|
|
494
426
|
// this is only used in the client and is not stored in the db
|
|
495
|
-
numSteps:
|
|
427
|
+
numSteps: z10.number()
|
|
496
428
|
});
|
|
497
|
-
var ModuleSchema =
|
|
429
|
+
var ModuleSchema = z10.object({
|
|
498
430
|
steps: AllowedModuleStepSchema.array()
|
|
499
431
|
}).merge(ModuleMetadataSchema.omit({ numSteps: true }));
|
|
500
432
|
|
|
501
433
|
// ../../packages/types/src/runs.ts
|
|
502
|
-
|
|
434
|
+
import { z as z11 } from "zod";
|
|
503
435
|
var RunTrigger = {
|
|
504
436
|
WEBHOOK: "WEBHOOK",
|
|
505
437
|
CRON: "CRON",
|
|
@@ -512,31 +444,31 @@ var RunStatusEnum = {
|
|
|
512
444
|
FAILED: "FAILED",
|
|
513
445
|
CANCELLED: "CANCELLED"
|
|
514
446
|
};
|
|
515
|
-
var DateOrStringSchema =
|
|
516
|
-
var RunMetadataSchema =
|
|
517
|
-
id:
|
|
447
|
+
var DateOrStringSchema = z11.string().pipe(z11.coerce.date()).or(z11.date());
|
|
448
|
+
var RunMetadataSchema = z11.object({
|
|
449
|
+
id: z11.string(),
|
|
518
450
|
createdAt: DateOrStringSchema,
|
|
519
|
-
createdBy:
|
|
520
|
-
organizationId:
|
|
521
|
-
scheduledAt: DateOrStringSchema.or(
|
|
522
|
-
startedAt: DateOrStringSchema.or(
|
|
523
|
-
finishedAt: DateOrStringSchema.or(
|
|
524
|
-
testId:
|
|
525
|
-
status:
|
|
526
|
-
trigger:
|
|
527
|
-
test:
|
|
528
|
-
name:
|
|
529
|
-
id:
|
|
530
|
-
}).or(
|
|
451
|
+
createdBy: z11.string(),
|
|
452
|
+
organizationId: z11.string().or(z11.null()),
|
|
453
|
+
scheduledAt: DateOrStringSchema.or(z11.null()),
|
|
454
|
+
startedAt: DateOrStringSchema.or(z11.null()),
|
|
455
|
+
finishedAt: DateOrStringSchema.or(z11.null()),
|
|
456
|
+
testId: z11.string().or(z11.null()),
|
|
457
|
+
status: z11.nativeEnum(RunStatusEnum),
|
|
458
|
+
trigger: z11.nativeEnum(RunTrigger),
|
|
459
|
+
test: z11.object({
|
|
460
|
+
name: z11.string(),
|
|
461
|
+
id: z11.string()
|
|
462
|
+
}).or(z11.null())
|
|
531
463
|
});
|
|
532
464
|
var RunWithTestSchema = RunMetadataSchema.merge(
|
|
533
|
-
|
|
465
|
+
z11.object({
|
|
534
466
|
results: ResultSchema.array(),
|
|
535
|
-
test:
|
|
536
|
-
name:
|
|
537
|
-
id:
|
|
538
|
-
baseUrl:
|
|
539
|
-
}).or(
|
|
467
|
+
test: z11.object({
|
|
468
|
+
name: z11.string(),
|
|
469
|
+
id: z11.string(),
|
|
470
|
+
baseUrl: z11.string()
|
|
471
|
+
}).or(z11.null())
|
|
540
472
|
})
|
|
541
473
|
);
|
|
542
474
|
|
|
@@ -641,56 +573,56 @@ var CARD_DESCRIPTIONS = {
|
|
|
641
573
|
};
|
|
642
574
|
|
|
643
575
|
// ../../packages/types/src/test.ts
|
|
644
|
-
|
|
576
|
+
import { z as z13 } from "zod";
|
|
645
577
|
|
|
646
578
|
// ../../packages/types/src/test-settings.ts
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
var TestAdvancedSettingsSchema =
|
|
650
|
-
availableAsModule:
|
|
651
|
-
disableAICaching:
|
|
579
|
+
import { isValidCron } from "cron-validator";
|
|
580
|
+
import { z as z12 } from "zod";
|
|
581
|
+
var TestAdvancedSettingsSchema = z12.object({
|
|
582
|
+
availableAsModule: z12.boolean().default(false),
|
|
583
|
+
disableAICaching: z12.boolean().default(false)
|
|
652
584
|
});
|
|
653
|
-
var ScheduleSettingsSchema =
|
|
654
|
-
cron:
|
|
585
|
+
var ScheduleSettingsSchema = z12.object({
|
|
586
|
+
cron: z12.string().refine(
|
|
655
587
|
(v) => {
|
|
656
|
-
return
|
|
588
|
+
return isValidCron(v);
|
|
657
589
|
},
|
|
658
590
|
{ message: "Invalid cron expression." }
|
|
659
591
|
).default("0 0 */1 * *"),
|
|
660
|
-
enabled:
|
|
661
|
-
timeZone:
|
|
592
|
+
enabled: z12.boolean().default(false),
|
|
593
|
+
timeZone: z12.string().default("America/Los_Angeles"),
|
|
662
594
|
// this is used for removing repeatable jobs (not set by user)
|
|
663
|
-
jobKey:
|
|
595
|
+
jobKey: z12.string().optional()
|
|
664
596
|
});
|
|
665
|
-
var WebhookSchema =
|
|
666
|
-
lastStatus:
|
|
667
|
-
url:
|
|
597
|
+
var WebhookSchema = z12.object({
|
|
598
|
+
lastStatus: z12.number().optional(),
|
|
599
|
+
url: z12.string().url()
|
|
668
600
|
});
|
|
669
|
-
var WebhookSettingsSchema =
|
|
670
|
-
var TestSettingsSchema =
|
|
671
|
-
name:
|
|
672
|
-
baseUrl:
|
|
601
|
+
var WebhookSettingsSchema = z12.array(WebhookSchema).default([]);
|
|
602
|
+
var TestSettingsSchema = z12.object({
|
|
603
|
+
name: z12.string().min(1),
|
|
604
|
+
baseUrl: z12.string().url(),
|
|
673
605
|
advanced: TestAdvancedSettingsSchema
|
|
674
606
|
});
|
|
675
607
|
|
|
676
608
|
// ../../packages/types/src/test.ts
|
|
677
|
-
var ResolvedTestSchema =
|
|
678
|
-
id:
|
|
679
|
-
name:
|
|
680
|
-
baseUrl:
|
|
681
|
-
steps:
|
|
682
|
-
createdAt:
|
|
683
|
-
updatedAt:
|
|
684
|
-
createdBy:
|
|
685
|
-
organizationId:
|
|
686
|
-
schemaVersion:
|
|
609
|
+
var ResolvedTestSchema = z13.object({
|
|
610
|
+
id: z13.string(),
|
|
611
|
+
name: z13.string(),
|
|
612
|
+
baseUrl: z13.string(),
|
|
613
|
+
steps: z13.array(ResolvedStepSchema),
|
|
614
|
+
createdAt: z13.coerce.date(),
|
|
615
|
+
updatedAt: z13.coerce.date(),
|
|
616
|
+
createdBy: z13.string(),
|
|
617
|
+
organizationId: z13.string().or(z13.null()),
|
|
618
|
+
schemaVersion: z13.string(),
|
|
687
619
|
advanced: TestAdvancedSettingsSchema,
|
|
688
620
|
schedule: ScheduleSettingsSchema,
|
|
689
621
|
webhooks: WebhookSettingsSchema
|
|
690
622
|
});
|
|
691
623
|
|
|
692
624
|
// ../../packages/types/src/context.ts
|
|
693
|
-
|
|
625
|
+
import * as z14 from "zod";
|
|
694
626
|
var DynamicContextSchema = z14.object({
|
|
695
627
|
// user goal or instruction
|
|
696
628
|
goal: z14.string(),
|
|
@@ -707,7 +639,7 @@ var DynamicContextSchema = z14.object({
|
|
|
707
639
|
});
|
|
708
640
|
|
|
709
641
|
// ../../packages/types/src/public-api.ts
|
|
710
|
-
|
|
642
|
+
import * as z15 from "zod";
|
|
711
643
|
var GeneratorOptionsSchema = z15.object({
|
|
712
644
|
disableCache: z15.boolean()
|
|
713
645
|
});
|
|
@@ -788,6 +720,18 @@ var defaultA11yNodeSerializeParams = {
|
|
|
788
720
|
noProperties: false
|
|
789
721
|
};
|
|
790
722
|
var ProcessedA11yNode = class {
|
|
723
|
+
id;
|
|
724
|
+
role;
|
|
725
|
+
name;
|
|
726
|
+
content;
|
|
727
|
+
properties;
|
|
728
|
+
// css-like selector from the root of the tree to the current node
|
|
729
|
+
pathFromRoot;
|
|
730
|
+
parent;
|
|
731
|
+
// md5 hash - set lazily in most cases (not used at the moment)
|
|
732
|
+
// md5Sum: string;
|
|
733
|
+
children;
|
|
734
|
+
backendNodeID;
|
|
791
735
|
constructor(params) {
|
|
792
736
|
this.id = params.id;
|
|
793
737
|
this.role = params.role;
|
|
@@ -799,11 +743,10 @@ var ProcessedA11yNode = class {
|
|
|
799
743
|
this.backendNodeID = params.backendNodeID;
|
|
800
744
|
}
|
|
801
745
|
getLogForm() {
|
|
802
|
-
var _a, _b;
|
|
803
746
|
return JSON.stringify({
|
|
804
747
|
id: this.id,
|
|
805
|
-
name:
|
|
806
|
-
role:
|
|
748
|
+
name: this.name ?? "",
|
|
749
|
+
role: this.role ?? "",
|
|
807
750
|
backendNodeId: this.backendNodeID
|
|
808
751
|
});
|
|
809
752
|
}
|
|
@@ -896,7 +839,7 @@ function getNodePathIdentifier(node) {
|
|
|
896
839
|
return `"${node.nodeId}"`;
|
|
897
840
|
}
|
|
898
841
|
function processA11yTreeDFS(node, parent, inputNodeMap, outputNodeMap) {
|
|
899
|
-
var _a, _b, _c, _d, _e, _f
|
|
842
|
+
var _a, _b, _c, _d, _e, _f;
|
|
900
843
|
if (!parent && node.parentId) {
|
|
901
844
|
throw new Error(
|
|
902
845
|
`Got no parent for accessibility node ${node.nodeId}: ${JSON.stringify(
|
|
@@ -924,7 +867,7 @@ function processA11yTreeDFS(node, parent, inputNodeMap, outputNodeMap) {
|
|
|
924
867
|
});
|
|
925
868
|
}
|
|
926
869
|
outputNodeMap.set(processedNode.id, processedNode);
|
|
927
|
-
const children =
|
|
870
|
+
const children = node.childIds ?? [];
|
|
928
871
|
for (const childId of children) {
|
|
929
872
|
if (!childId) {
|
|
930
873
|
continue;
|
|
@@ -949,7 +892,7 @@ function processA11yTreeDFS(node, parent, inputNodeMap, outputNodeMap) {
|
|
|
949
892
|
}
|
|
950
893
|
if (processedNode.children.length === 1 && processedNode.children[0].role === "StaticText") {
|
|
951
894
|
const currentName = processedNode.name;
|
|
952
|
-
const childName = (
|
|
895
|
+
const childName = (_f = processedNode.children[0]) == null ? void 0 : _f.name;
|
|
953
896
|
if (currentName === childName || !childName) {
|
|
954
897
|
processedNode.children = [];
|
|
955
898
|
}
|
|
@@ -1152,14 +1095,20 @@ function isRequestRelevantForPageLoad(request, currentURL) {
|
|
|
1152
1095
|
}
|
|
1153
1096
|
|
|
1154
1097
|
// ../../packages/web-agent/src/browsers/chrome.ts
|
|
1155
|
-
function initCDPSession(cdpClient) {
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
yield cdpClient.send("Overlay.enable");
|
|
1160
|
-
});
|
|
1098
|
+
async function initCDPSession(cdpClient) {
|
|
1099
|
+
await cdpClient.send("Accessibility.enable");
|
|
1100
|
+
await cdpClient.send("DOM.enable");
|
|
1101
|
+
await cdpClient.send("Overlay.enable");
|
|
1161
1102
|
}
|
|
1162
|
-
var
|
|
1103
|
+
var ChromeBrowser = class _ChromeBrowser {
|
|
1104
|
+
browser;
|
|
1105
|
+
context;
|
|
1106
|
+
page;
|
|
1107
|
+
// key is nodeId, according to the a11y tree
|
|
1108
|
+
nodeMap = /* @__PURE__ */ new Map();
|
|
1109
|
+
cdpClient;
|
|
1110
|
+
logger;
|
|
1111
|
+
baseURL;
|
|
1163
1112
|
constructor({
|
|
1164
1113
|
browser,
|
|
1165
1114
|
context,
|
|
@@ -1168,8 +1117,6 @@ var _ChromeBrowser = class _ChromeBrowser {
|
|
|
1168
1117
|
cdpClient,
|
|
1169
1118
|
logger
|
|
1170
1119
|
}) {
|
|
1171
|
-
// key is nodeId, according to the a11y tree
|
|
1172
|
-
this.nodeMap = /* @__PURE__ */ new Map();
|
|
1173
1120
|
this.browser = browser;
|
|
1174
1121
|
this.context = context;
|
|
1175
1122
|
this.page = page;
|
|
@@ -1177,117 +1124,106 @@ var _ChromeBrowser = class _ChromeBrowser {
|
|
|
1177
1124
|
this.cdpClient = cdpClient;
|
|
1178
1125
|
this.logger = logger;
|
|
1179
1126
|
}
|
|
1127
|
+
static USER_AGENT = devices["Desktop Chrome"].userAgent;
|
|
1180
1128
|
/**
|
|
1181
1129
|
* Creates a new browser and waits for navigation to the given test URL.
|
|
1182
1130
|
*/
|
|
1183
|
-
static init(
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
}
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
completed = true;
|
|
1218
|
-
}
|
|
1219
|
-
});
|
|
1220
|
-
void navigateAndInitCDP();
|
|
1221
|
-
const sendScreenshot = () => __async(this, null, function* () {
|
|
1222
|
-
if (!onScreenshot) {
|
|
1223
|
-
return;
|
|
1224
|
-
}
|
|
1225
|
-
try {
|
|
1226
|
-
onScreenshot({
|
|
1227
|
-
viewport: chrome.viewport,
|
|
1228
|
-
buffer: yield chrome.screenshot()
|
|
1229
|
-
});
|
|
1230
|
-
} catch (err) {
|
|
1231
|
-
logger.error({ err }, "Failed to take screenshot");
|
|
1232
|
-
}
|
|
1233
|
-
});
|
|
1234
|
-
void sendScreenshot();
|
|
1235
|
-
const screenshotInterval = setInterval(() => {
|
|
1236
|
-
void sendScreenshot();
|
|
1237
|
-
}, 250);
|
|
1238
|
-
const startTime = Date.now();
|
|
1239
|
-
while (!completed && Date.now() - startTime < timeout) {
|
|
1240
|
-
yield sleep(CHECK_INTERVAL_MS);
|
|
1131
|
+
static async init(baseURL, logger, onScreenshot, timeout = MAX_LOAD_TIMEOUT_MS) {
|
|
1132
|
+
const browser = await chromium.launch({ headless: true });
|
|
1133
|
+
const context = await browser.newContext({
|
|
1134
|
+
viewport: {
|
|
1135
|
+
width: 1920,
|
|
1136
|
+
height: 1080
|
|
1137
|
+
},
|
|
1138
|
+
// comment out the below if you are on Mac OS but you're using a monitor
|
|
1139
|
+
deviceScaleFactor: process.platform === "darwin" ? RETINA_WINDOW_SCALE_FACTOR : 1,
|
|
1140
|
+
userAgent: devices["Desktop Chrome"].userAgent,
|
|
1141
|
+
geolocation: { latitude: 37.7749, longitude: -122.4194 },
|
|
1142
|
+
// san francisco
|
|
1143
|
+
locale: "en-US",
|
|
1144
|
+
timezoneId: "America/Los_Angeles"
|
|
1145
|
+
});
|
|
1146
|
+
const page = await context.newPage();
|
|
1147
|
+
const cdpClient = await context.newCDPSession(page);
|
|
1148
|
+
const chrome = new _ChromeBrowser({
|
|
1149
|
+
browser,
|
|
1150
|
+
context,
|
|
1151
|
+
page,
|
|
1152
|
+
baseURL,
|
|
1153
|
+
cdpClient,
|
|
1154
|
+
logger
|
|
1155
|
+
});
|
|
1156
|
+
let completed = false;
|
|
1157
|
+
const navigateAndInitCDP = async () => {
|
|
1158
|
+
try {
|
|
1159
|
+
await chrome.navigate(baseURL, false);
|
|
1160
|
+
await initCDPSession(cdpClient);
|
|
1161
|
+
} catch (err) {
|
|
1162
|
+
logger.error({ err }, "Failed to initialize chrome browser");
|
|
1163
|
+
} finally {
|
|
1164
|
+
completed = true;
|
|
1241
1165
|
}
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1166
|
+
};
|
|
1167
|
+
void navigateAndInitCDP();
|
|
1168
|
+
const sendScreenshot = async () => {
|
|
1169
|
+
if (!onScreenshot) {
|
|
1170
|
+
return;
|
|
1247
1171
|
}
|
|
1248
|
-
|
|
1249
|
-
|
|
1172
|
+
try {
|
|
1173
|
+
onScreenshot({
|
|
1174
|
+
viewport: chrome.viewport,
|
|
1175
|
+
buffer: await chrome.screenshot()
|
|
1176
|
+
});
|
|
1177
|
+
} catch (err) {
|
|
1178
|
+
logger.error({ err }, "Failed to take screenshot");
|
|
1179
|
+
}
|
|
1180
|
+
};
|
|
1181
|
+
void sendScreenshot();
|
|
1182
|
+
const screenshotInterval = setInterval(() => {
|
|
1183
|
+
void sendScreenshot();
|
|
1184
|
+
}, 250);
|
|
1185
|
+
const startTime = Date.now();
|
|
1186
|
+
while (!completed && Date.now() - startTime < timeout) {
|
|
1187
|
+
await sleep(CHECK_INTERVAL_MS);
|
|
1188
|
+
}
|
|
1189
|
+
clearInterval(screenshotInterval);
|
|
1190
|
+
if (!completed) {
|
|
1191
|
+
logger.warn(
|
|
1192
|
+
"Timeout elapsed waiting for browser to initialize - are you sure this page is accessible?"
|
|
1193
|
+
);
|
|
1194
|
+
}
|
|
1195
|
+
return chrome;
|
|
1250
1196
|
}
|
|
1251
1197
|
// Things to do on every page load
|
|
1252
|
-
pageSetup() {
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
yield this.page.evaluate(addIDsScript);
|
|
1256
|
-
});
|
|
1198
|
+
async pageSetup() {
|
|
1199
|
+
await this.page.evaluate(addCursorScript);
|
|
1200
|
+
await this.page.evaluate(addIDsScript);
|
|
1257
1201
|
}
|
|
1258
|
-
wait(timeoutMs) {
|
|
1259
|
-
|
|
1260
|
-
yield this.page.waitForTimeout(timeoutMs);
|
|
1261
|
-
});
|
|
1202
|
+
async wait(timeoutMs) {
|
|
1203
|
+
await this.page.waitForTimeout(timeoutMs);
|
|
1262
1204
|
}
|
|
1263
|
-
cleanup() {
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
yield this.browser.close();
|
|
1268
|
-
});
|
|
1205
|
+
async cleanup() {
|
|
1206
|
+
await this.page.close();
|
|
1207
|
+
await this.context.close();
|
|
1208
|
+
await this.browser.close();
|
|
1269
1209
|
}
|
|
1270
1210
|
get closed() {
|
|
1271
1211
|
return this.page.isClosed() || !this.browser.isConnected();
|
|
1272
1212
|
}
|
|
1273
|
-
html() {
|
|
1274
|
-
return
|
|
1275
|
-
return yield this.page.content();
|
|
1276
|
-
});
|
|
1213
|
+
async html() {
|
|
1214
|
+
return await this.page.content();
|
|
1277
1215
|
}
|
|
1278
1216
|
get url() {
|
|
1279
1217
|
return this.page.url();
|
|
1280
1218
|
}
|
|
1281
|
-
screenshot(quality = 100, scale = "device") {
|
|
1282
|
-
return
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
caret: "initial"
|
|
1290
|
-
});
|
|
1219
|
+
async screenshot(quality = 100, scale = "device") {
|
|
1220
|
+
return await this.page.screenshot({
|
|
1221
|
+
fullPage: false,
|
|
1222
|
+
quality,
|
|
1223
|
+
scale,
|
|
1224
|
+
type: "jpeg",
|
|
1225
|
+
// allow the blinking text cursor thing to remain there
|
|
1226
|
+
caret: "initial"
|
|
1291
1227
|
});
|
|
1292
1228
|
}
|
|
1293
1229
|
get viewport() {
|
|
@@ -1297,595 +1233,539 @@ var _ChromeBrowser = class _ChromeBrowser {
|
|
|
1297
1233
|
}
|
|
1298
1234
|
return viewport;
|
|
1299
1235
|
}
|
|
1300
|
-
navigate(url, wrapPossibleNavigation = true) {
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
"Timeout elapsed waiting for page to load, continuing anyways..."
|
|
1317
|
-
);
|
|
1318
|
-
}
|
|
1319
|
-
});
|
|
1320
|
-
if (wrapPossibleNavigation) {
|
|
1321
|
-
yield this.wrapPossibleNavigation(doNav);
|
|
1322
|
-
} else {
|
|
1323
|
-
yield doNav();
|
|
1324
|
-
}
|
|
1325
|
-
if (CHROME_INTERNAL_URLS.has(this.url) && process.env.NODE_ENV === "production") {
|
|
1326
|
-
throw new Error(
|
|
1327
|
-
`${url} took too long to load \u{1F61E}. Please ensure the site and your internet are working.`
|
|
1236
|
+
async navigate(url, wrapPossibleNavigation = true) {
|
|
1237
|
+
this.logger.debug(`Navigating to ${url}`);
|
|
1238
|
+
const startTime = Date.now();
|
|
1239
|
+
const doNav = async () => {
|
|
1240
|
+
try {
|
|
1241
|
+
await this.page.goto(url, {
|
|
1242
|
+
timeout: MAX_LOAD_TIMEOUT_MS
|
|
1243
|
+
});
|
|
1244
|
+
this.logger.debug(
|
|
1245
|
+
{ url },
|
|
1246
|
+
`Got load event in ${Math.floor(Date.now() - startTime)}ms`
|
|
1247
|
+
);
|
|
1248
|
+
} catch (e) {
|
|
1249
|
+
this.logger.warn(
|
|
1250
|
+
{ url, type: "navigate", err: e },
|
|
1251
|
+
"Timeout elapsed waiting for page to load, continuing anyways..."
|
|
1328
1252
|
);
|
|
1329
1253
|
}
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1254
|
+
};
|
|
1255
|
+
if (wrapPossibleNavigation) {
|
|
1256
|
+
await this.wrapPossibleNavigation(doNav);
|
|
1257
|
+
} else {
|
|
1258
|
+
await doNav();
|
|
1259
|
+
}
|
|
1260
|
+
if (CHROME_INTERNAL_URLS.has(this.url) && process.env.NODE_ENV === "production") {
|
|
1261
|
+
throw new Error(
|
|
1262
|
+
`${url} took too long to load \u{1F61E}. Please ensure the site and your internet are working.`
|
|
1263
|
+
);
|
|
1264
|
+
}
|
|
1265
|
+
await this.pageSetup();
|
|
1266
|
+
this.logger.debug({ url }, "Navigation complete");
|
|
1267
|
+
}
|
|
1268
|
+
async fill(target, text, options = {}) {
|
|
1269
|
+
const element = await this.click(target, {
|
|
1270
|
+
doubleClick: false,
|
|
1271
|
+
rightClick: false
|
|
1272
|
+
});
|
|
1273
|
+
await this.type(text, options);
|
|
1274
|
+
return element;
|
|
1275
|
+
}
|
|
1276
|
+
async type(text, options = {}) {
|
|
1277
|
+
const { clearContent = true, pressKeysSequentially = false } = options;
|
|
1278
|
+
if (clearContent) {
|
|
1279
|
+
await this.page.keyboard.press("Meta+A");
|
|
1280
|
+
await this.page.keyboard.press("Backspace");
|
|
1281
|
+
}
|
|
1282
|
+
if (pressKeysSequentially) {
|
|
1283
|
+
await this.page.keyboard.type(text);
|
|
1284
|
+
} else {
|
|
1285
|
+
await this.page.keyboard.insertText(text);
|
|
1286
|
+
}
|
|
1333
1287
|
}
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1288
|
+
async clickByA11yID(index, options = {}) {
|
|
1289
|
+
const node = this.nodeMap.get(`${index}`);
|
|
1290
|
+
if (!node) {
|
|
1291
|
+
throw new Error(`Could not find node in DOM with index: ${index}`);
|
|
1292
|
+
}
|
|
1293
|
+
const nodeClicked = await this.clickUsingCDP(node, options);
|
|
1294
|
+
await this.highlightNode(nodeClicked);
|
|
1295
|
+
return node.serialize({ noChildren: true, noProperties: true, noID: true });
|
|
1296
|
+
}
|
|
1297
|
+
async selectOptionByA11yID(index, option) {
|
|
1298
|
+
const node = this.nodeMap.get(`${index}`);
|
|
1299
|
+
if (!node) {
|
|
1300
|
+
throw new Error(`Could not find node in DOM with index: ${index}`);
|
|
1301
|
+
}
|
|
1302
|
+
if (!node.backendNodeID) {
|
|
1303
|
+
throw new Error(
|
|
1304
|
+
`Select target missing backend node id: ${node.getLogForm()}`
|
|
1305
|
+
);
|
|
1306
|
+
}
|
|
1307
|
+
const locator = await this.getLocatorFromBackendID(node.backendNodeID);
|
|
1308
|
+
await locator.selectOption(option, {
|
|
1309
|
+
timeout: COMPLICATED_BROWSER_ACTION_TIMEOUT_MS
|
|
1342
1310
|
});
|
|
1311
|
+
await this.highlightNode(node);
|
|
1312
|
+
return node.serialize({ noChildren: true, noProperties: true, noID: true });
|
|
1343
1313
|
}
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
}
|
|
1351
|
-
if (pressKeysSequentially) {
|
|
1352
|
-
yield this.page.keyboard.type(text);
|
|
1353
|
-
} else {
|
|
1354
|
-
yield this.page.keyboard.insertText(text);
|
|
1355
|
-
}
|
|
1356
|
-
});
|
|
1314
|
+
async highlight(target) {
|
|
1315
|
+
try {
|
|
1316
|
+
await this.highlightByA11yID(target.id);
|
|
1317
|
+
} catch (err) {
|
|
1318
|
+
this.logger.warn({ err, target }, "Failed to highlight target");
|
|
1319
|
+
}
|
|
1357
1320
|
}
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
}
|
|
1321
|
+
async highlightByA11yID(index) {
|
|
1322
|
+
const node = this.nodeMap.get(`${index}`);
|
|
1323
|
+
if (!node) {
|
|
1324
|
+
throw new Error(`Could not find node in DOM with index: ${index}`);
|
|
1325
|
+
}
|
|
1326
|
+
if (!node.backendNodeID) {
|
|
1327
|
+
throw new Error(
|
|
1328
|
+
`Select target missing backend node id: ${node.getLogForm()}`
|
|
1329
|
+
);
|
|
1330
|
+
}
|
|
1331
|
+
await this.highlightNode(node);
|
|
1368
1332
|
}
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
}
|
|
1375
|
-
if (!node.backendNodeID) {
|
|
1376
|
-
throw new Error(
|
|
1377
|
-
`Select target missing backend node id: ${node.getLogForm()}`
|
|
1378
|
-
);
|
|
1379
|
-
}
|
|
1380
|
-
const locator = yield this.getLocatorFromBackendID(node.backendNodeID);
|
|
1381
|
-
yield locator.selectOption(option, {
|
|
1382
|
-
timeout: COMPLICATED_BROWSER_ACTION_TIMEOUT_MS
|
|
1333
|
+
async highlightNode(node) {
|
|
1334
|
+
try {
|
|
1335
|
+
await this.cdpClient.send("Overlay.highlightNode", {
|
|
1336
|
+
highlightConfig: NODE_HIGHLIGHT_CONFIG,
|
|
1337
|
+
backendNodeId: node.backendNodeID
|
|
1383
1338
|
});
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
}
|
|
1387
|
-
|
|
1388
|
-
highlight(target) {
|
|
1389
|
-
return __async(this, null, function* () {
|
|
1390
|
-
try {
|
|
1391
|
-
yield this.highlightByA11yID(target.id);
|
|
1392
|
-
} catch (err) {
|
|
1393
|
-
this.logger.warn({ err, target }, "Failed to highlight target");
|
|
1394
|
-
}
|
|
1395
|
-
});
|
|
1396
|
-
}
|
|
1397
|
-
highlightByA11yID(index) {
|
|
1398
|
-
return __async(this, null, function* () {
|
|
1399
|
-
const node = this.nodeMap.get(`${index}`);
|
|
1400
|
-
if (!node) {
|
|
1401
|
-
throw new Error(`Could not find node in DOM with index: ${index}`);
|
|
1402
|
-
}
|
|
1403
|
-
if (!node.backendNodeID) {
|
|
1404
|
-
throw new Error(
|
|
1405
|
-
`Select target missing backend node id: ${node.getLogForm()}`
|
|
1406
|
-
);
|
|
1407
|
-
}
|
|
1408
|
-
yield this.highlightNode(node);
|
|
1409
|
-
});
|
|
1410
|
-
}
|
|
1411
|
-
highlightNode(node) {
|
|
1412
|
-
return __async(this, null, function* () {
|
|
1339
|
+
} catch (err) {
|
|
1340
|
+
this.logger.warn({ err }, "Failed to add node highlight");
|
|
1341
|
+
}
|
|
1342
|
+
const hideHighlight = async () => {
|
|
1413
1343
|
try {
|
|
1414
|
-
|
|
1415
|
-
highlightConfig: NODE_HIGHLIGHT_CONFIG,
|
|
1344
|
+
await this.cdpClient.send("Overlay.hideHighlight", {
|
|
1416
1345
|
backendNodeId: node.backendNodeID
|
|
1417
1346
|
});
|
|
1418
1347
|
} catch (err) {
|
|
1419
|
-
this.logger.
|
|
1348
|
+
this.logger.debug({ err }, "Failed to remove node highlight");
|
|
1420
1349
|
}
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
const startURL = this.url;
|
|
1439
|
-
let lastRequestReceived = Date.now();
|
|
1440
|
-
const firedRequests = /* @__PURE__ */ new Map();
|
|
1441
|
-
const finishedRequests = /* @__PURE__ */ new Map();
|
|
1442
|
-
const requestFinishedListener = (request) => {
|
|
1443
|
-
var _a;
|
|
1444
|
-
const key = serializeRequest(request);
|
|
1445
|
-
finishedRequests.set(key, ((_a = finishedRequests.get(key)) != null ? _a : 0) + 1);
|
|
1446
|
-
};
|
|
1447
|
-
const requestFiredListener = (request) => {
|
|
1448
|
-
var _a;
|
|
1449
|
-
if (!isRequestRelevantForPageLoad(request, this.url)) {
|
|
1450
|
-
this.logger.debug(
|
|
1451
|
-
{
|
|
1452
|
-
uri: serializeRequest(request)
|
|
1453
|
-
},
|
|
1454
|
-
"Ignoring request for page load network stability"
|
|
1455
|
-
);
|
|
1456
|
-
return;
|
|
1457
|
-
}
|
|
1458
|
-
const key = serializeRequest(request);
|
|
1350
|
+
};
|
|
1351
|
+
setTimeout(() => {
|
|
1352
|
+
void hideHighlight();
|
|
1353
|
+
}, HIGHLIGHT_DURATION_MS);
|
|
1354
|
+
}
|
|
1355
|
+
async wrapPossibleNavigation(fn, timeoutMS = MAX_LOAD_TIMEOUT_MS) {
|
|
1356
|
+
const startTime = Date.now();
|
|
1357
|
+
const startURL = this.url;
|
|
1358
|
+
let lastRequestReceived = Date.now();
|
|
1359
|
+
const firedRequests = /* @__PURE__ */ new Map();
|
|
1360
|
+
const finishedRequests = /* @__PURE__ */ new Map();
|
|
1361
|
+
const requestFinishedListener = (request) => {
|
|
1362
|
+
const key = serializeRequest(request);
|
|
1363
|
+
finishedRequests.set(key, (finishedRequests.get(key) ?? 0) + 1);
|
|
1364
|
+
};
|
|
1365
|
+
const requestFiredListener = (request) => {
|
|
1366
|
+
if (!isRequestRelevantForPageLoad(request, this.url)) {
|
|
1459
1367
|
this.logger.debug(
|
|
1460
1368
|
{
|
|
1461
|
-
uri:
|
|
1369
|
+
uri: serializeRequest(request)
|
|
1462
1370
|
},
|
|
1463
|
-
"
|
|
1371
|
+
"Ignoring request for page load network stability"
|
|
1464
1372
|
);
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
this.
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1373
|
+
return;
|
|
1374
|
+
}
|
|
1375
|
+
const key = serializeRequest(request);
|
|
1376
|
+
this.logger.debug(
|
|
1377
|
+
{
|
|
1378
|
+
uri: key
|
|
1379
|
+
},
|
|
1380
|
+
"Request fired on page load, delaying network stability"
|
|
1381
|
+
);
|
|
1382
|
+
firedRequests.set(key, (firedRequests.get(key) ?? 0) + 1);
|
|
1383
|
+
lastRequestReceived = Date.now();
|
|
1384
|
+
};
|
|
1385
|
+
this.page.on("requestfinished", requestFinishedListener);
|
|
1386
|
+
this.page.on("request", requestFiredListener);
|
|
1387
|
+
let rejected = false;
|
|
1388
|
+
const retPromise = fn().catch((e) => {
|
|
1389
|
+
rejected = true;
|
|
1390
|
+
if (e instanceof Error)
|
|
1391
|
+
return e;
|
|
1392
|
+
return new Error(`${e}`);
|
|
1393
|
+
});
|
|
1394
|
+
await sleep(CHECK_INTERVAL_MS);
|
|
1395
|
+
const unwrapAndThrowError = async (p) => {
|
|
1396
|
+
const v = await p;
|
|
1397
|
+
if (v instanceof Error) {
|
|
1398
|
+
throw v;
|
|
1399
|
+
}
|
|
1400
|
+
return v;
|
|
1401
|
+
};
|
|
1402
|
+
let unfinishedRequests = /* @__PURE__ */ new Set();
|
|
1403
|
+
const waitForNetworkIdle = async () => {
|
|
1404
|
+
while (!rejected && Date.now() - startTime < timeoutMS) {
|
|
1405
|
+
unfinishedRequests = /* @__PURE__ */ new Set();
|
|
1406
|
+
await sleep(CHECK_INTERVAL_MS);
|
|
1407
|
+
if (Date.now() - lastRequestReceived <= NETWORK_STABLE_DURATION_MS) {
|
|
1408
|
+
continue;
|
|
1482
1409
|
}
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
yield sleep(CHECK_INTERVAL_MS);
|
|
1490
|
-
if (Date.now() - lastRequestReceived <= NETWORK_STABLE_DURATION_MS) {
|
|
1491
|
-
continue;
|
|
1492
|
-
}
|
|
1493
|
-
let anyDifference = false;
|
|
1494
|
-
for (const key of firedRequests.keys()) {
|
|
1495
|
-
if (firedRequests.get(key) !== finishedRequests.get(key)) {
|
|
1496
|
-
this.logger.debug({ uri: key }, "Waiting on request to finish");
|
|
1497
|
-
anyDifference = true;
|
|
1498
|
-
unfinishedRequests.add(key);
|
|
1499
|
-
}
|
|
1500
|
-
}
|
|
1501
|
-
if (!anyDifference) {
|
|
1502
|
-
this.logger.debug(
|
|
1503
|
-
{
|
|
1504
|
-
url: this.url,
|
|
1505
|
-
requests: JSON.stringify(Array.from(firedRequests.entries()))
|
|
1506
|
-
},
|
|
1507
|
-
`Network idle in ${Math.floor(Date.now() - startTime)}ms`
|
|
1508
|
-
);
|
|
1509
|
-
return true;
|
|
1410
|
+
let anyDifference = false;
|
|
1411
|
+
for (const key of firedRequests.keys()) {
|
|
1412
|
+
if (firedRequests.get(key) !== finishedRequests.get(key)) {
|
|
1413
|
+
this.logger.debug({ uri: key }, "Waiting on request to finish");
|
|
1414
|
+
anyDifference = true;
|
|
1415
|
+
unfinishedRequests.add(key);
|
|
1510
1416
|
}
|
|
1511
1417
|
}
|
|
1512
|
-
if (!
|
|
1513
|
-
this.logger.
|
|
1418
|
+
if (!anyDifference) {
|
|
1419
|
+
this.logger.debug(
|
|
1514
1420
|
{
|
|
1515
1421
|
url: this.url,
|
|
1516
|
-
requests: JSON.stringify(Array.from(
|
|
1422
|
+
requests: JSON.stringify(Array.from(firedRequests.entries()))
|
|
1517
1423
|
},
|
|
1518
|
-
|
|
1424
|
+
`Network idle in ${Math.floor(Date.now() - startTime)}ms`
|
|
1519
1425
|
);
|
|
1426
|
+
return true;
|
|
1520
1427
|
}
|
|
1521
|
-
return false;
|
|
1522
|
-
});
|
|
1523
|
-
const waitResult = yield waitForNetworkIdle();
|
|
1524
|
-
this.page.off("requestfinished", requestFinishedListener);
|
|
1525
|
-
this.page.off("request", requestFiredListener);
|
|
1526
|
-
if (!waitResult) {
|
|
1527
|
-
return unwrapAndThrowError(retPromise);
|
|
1528
1428
|
}
|
|
1529
|
-
if (!rejected
|
|
1530
|
-
this.logger.
|
|
1531
|
-
|
|
1429
|
+
if (!rejected) {
|
|
1430
|
+
this.logger.warn(
|
|
1431
|
+
{
|
|
1432
|
+
url: this.url,
|
|
1433
|
+
requests: JSON.stringify(Array.from(unfinishedRequests.entries()))
|
|
1434
|
+
},
|
|
1435
|
+
"Timeout elapsed waiting for network idle, continuing anyways..."
|
|
1532
1436
|
);
|
|
1533
|
-
try {
|
|
1534
|
-
yield this.page.waitForLoadState("load", {
|
|
1535
|
-
timeout: timeoutMS - (Date.now() - startTime)
|
|
1536
|
-
});
|
|
1537
|
-
} catch (e) {
|
|
1538
|
-
this.logger.warn(
|
|
1539
|
-
{ url: this.url },
|
|
1540
|
-
"Timeout elapsed waiting for load state to fire, continuing anyways..."
|
|
1541
|
-
);
|
|
1542
|
-
}
|
|
1543
1437
|
}
|
|
1438
|
+
return false;
|
|
1439
|
+
};
|
|
1440
|
+
const waitResult = await waitForNetworkIdle();
|
|
1441
|
+
this.page.off("requestfinished", requestFinishedListener);
|
|
1442
|
+
this.page.off("request", requestFiredListener);
|
|
1443
|
+
if (!waitResult) {
|
|
1544
1444
|
return unwrapAndThrowError(retPromise);
|
|
1545
|
-
}
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
const elementInteracted = yield this.wrapPossibleNavigation(
|
|
1550
|
-
() => this.clickByA11yID(target.id, options)
|
|
1445
|
+
}
|
|
1446
|
+
if (!rejected && urlChanged(this.url, startURL)) {
|
|
1447
|
+
this.logger.debug(
|
|
1448
|
+
`Detected url change in wrapPossibleNavigation, waiting for load state`
|
|
1551
1449
|
);
|
|
1552
|
-
|
|
1553
|
-
|
|
1450
|
+
try {
|
|
1451
|
+
await this.page.waitForLoadState("load", {
|
|
1452
|
+
timeout: timeoutMS - (Date.now() - startTime)
|
|
1453
|
+
});
|
|
1454
|
+
} catch (e) {
|
|
1455
|
+
this.logger.warn(
|
|
1456
|
+
{ url: this.url },
|
|
1457
|
+
"Timeout elapsed waiting for load state to fire, continuing anyways..."
|
|
1458
|
+
);
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
return unwrapAndThrowError(retPromise);
|
|
1554
1462
|
}
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1463
|
+
async click(target, options = {}) {
|
|
1464
|
+
const elementInteracted = await this.wrapPossibleNavigation(
|
|
1465
|
+
() => this.clickByA11yID(target.id, options)
|
|
1466
|
+
);
|
|
1467
|
+
return elementInteracted;
|
|
1559
1468
|
}
|
|
1560
|
-
|
|
1561
|
-
return
|
|
1562
|
-
yield this.wrapPossibleNavigation(() => this.page.keyboard.press(key));
|
|
1563
|
-
});
|
|
1469
|
+
async selectOption(target, option) {
|
|
1470
|
+
return this.selectOptionByA11yID(target.id, option);
|
|
1564
1471
|
}
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
yield this.page.reload();
|
|
1568
|
-
yield this.pageSetup();
|
|
1569
|
-
});
|
|
1472
|
+
async press(key) {
|
|
1473
|
+
await this.wrapPossibleNavigation(() => this.page.keyboard.press(key));
|
|
1570
1474
|
}
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1475
|
+
async refresh() {
|
|
1476
|
+
await this.page.reload();
|
|
1477
|
+
await this.pageSetup();
|
|
1478
|
+
}
|
|
1479
|
+
async getA11yTree() {
|
|
1480
|
+
let processedTree = null;
|
|
1481
|
+
let attempt = 0;
|
|
1482
|
+
const url = this.url;
|
|
1483
|
+
while (!processedTree) {
|
|
1484
|
+
try {
|
|
1485
|
+
this.logger.debug(`Getting a11y tree at ${url}`);
|
|
1486
|
+
const graph = await this.getRawA11yTree();
|
|
1487
|
+
if (!graph.root || graph.allNodes.length === 0) {
|
|
1488
|
+
throw new Error("No a11y tree found on page");
|
|
1489
|
+
}
|
|
1490
|
+
processedTree = processA11yTree(graph);
|
|
1491
|
+
} catch (e) {
|
|
1492
|
+
this.logger.error({ err: e, url }, "Error fetching a11y tree");
|
|
1493
|
+
if (attempt === 0) {
|
|
1494
|
+
await sleep(1e3);
|
|
1495
|
+
attempt++;
|
|
1496
|
+
} else {
|
|
1497
|
+
throw new Error(`Max retries exceeded fetching a11y tree: ${e}`);
|
|
1592
1498
|
}
|
|
1593
1499
|
}
|
|
1594
|
-
|
|
1595
|
-
|
|
1500
|
+
}
|
|
1501
|
+
if (!processedTree.root) {
|
|
1502
|
+
this.logger.warn("A11y tree was pruned entirely");
|
|
1503
|
+
}
|
|
1504
|
+
this.nodeMap = processedTree.nodeMap;
|
|
1505
|
+
return processedTree;
|
|
1506
|
+
}
|
|
1507
|
+
async getRawA11yTree() {
|
|
1508
|
+
const url = this.page.url();
|
|
1509
|
+
let lastTreeUpdateTimestamp = Date.now();
|
|
1510
|
+
const treeUpdateListener = () => {
|
|
1511
|
+
lastTreeUpdateTimestamp = Date.now();
|
|
1512
|
+
};
|
|
1513
|
+
this.cdpClient.addListener(
|
|
1514
|
+
"Accessibility.nodesUpdated",
|
|
1515
|
+
treeUpdateListener
|
|
1516
|
+
);
|
|
1517
|
+
let accessibilityTreeLoadFired = false;
|
|
1518
|
+
const accessibilityLoadListener = () => {
|
|
1519
|
+
this.logger.info({ url }, `A11y tree load event fired`);
|
|
1520
|
+
accessibilityTreeLoadFired = true;
|
|
1521
|
+
};
|
|
1522
|
+
this.cdpClient.addListener(
|
|
1523
|
+
"Accessibility.loadComplete",
|
|
1524
|
+
accessibilityLoadListener
|
|
1525
|
+
);
|
|
1526
|
+
const a11yLoadStart = Date.now();
|
|
1527
|
+
let timeoutTriggered = true;
|
|
1528
|
+
while (Date.now() - a11yLoadStart < A11Y_STABLE_TIMEOUT_MS) {
|
|
1529
|
+
await sleep(CHECK_INTERVAL_MS);
|
|
1530
|
+
if (!accessibilityTreeLoadFired && Date.now() - a11yLoadStart < A11Y_LOAD_TIMEOUT_MS) {
|
|
1531
|
+
this.logger.debug({ url }, `A11y tree not loaded yet, waiting...`);
|
|
1532
|
+
continue;
|
|
1533
|
+
}
|
|
1534
|
+
if (Date.now() - lastTreeUpdateTimestamp >= A11Y_STABLE_DURATION_MS) {
|
|
1535
|
+
this.logger.debug({ url }, `A11y tree not stable yet, waiting...`);
|
|
1536
|
+
continue;
|
|
1596
1537
|
}
|
|
1597
|
-
|
|
1598
|
-
|
|
1538
|
+
timeoutTriggered = false;
|
|
1539
|
+
break;
|
|
1540
|
+
}
|
|
1541
|
+
this.logger.debug(
|
|
1542
|
+
{
|
|
1543
|
+
duration: Date.now() - a11yLoadStart,
|
|
1544
|
+
eventReceived: accessibilityTreeLoadFired,
|
|
1545
|
+
timeoutTriggered
|
|
1546
|
+
},
|
|
1547
|
+
"A11y wait phase completed"
|
|
1548
|
+
);
|
|
1549
|
+
const { node: root } = await this.cdpClient.send(
|
|
1550
|
+
"Accessibility.getRootAXNode"
|
|
1551
|
+
);
|
|
1552
|
+
const { nodes } = await this.cdpClient.send("Accessibility.queryAXTree", {
|
|
1553
|
+
backendNodeId: root.backendDOMNodeId
|
|
1599
1554
|
});
|
|
1555
|
+
this.cdpClient.removeListener(
|
|
1556
|
+
"Accessibility.loadComplete",
|
|
1557
|
+
accessibilityLoadListener
|
|
1558
|
+
);
|
|
1559
|
+
this.cdpClient.removeListener(
|
|
1560
|
+
"Accessibility.nodesUpdated",
|
|
1561
|
+
treeUpdateListener
|
|
1562
|
+
);
|
|
1563
|
+
return {
|
|
1564
|
+
root,
|
|
1565
|
+
allNodes: nodes
|
|
1566
|
+
};
|
|
1600
1567
|
}
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
lastTreeUpdateTimestamp = Date.now();
|
|
1607
|
-
};
|
|
1608
|
-
this.cdpClient.addListener(
|
|
1609
|
-
"Accessibility.nodesUpdated",
|
|
1610
|
-
treeUpdateListener
|
|
1568
|
+
async clickUsingVisualCoordinates(backendNodeId) {
|
|
1569
|
+
const location = await this.getElementLocation(backendNodeId);
|
|
1570
|
+
if (!location) {
|
|
1571
|
+
throw new Error(
|
|
1572
|
+
`Could not find element location with backend node id: ${backendNodeId}`
|
|
1611
1573
|
);
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1574
|
+
}
|
|
1575
|
+
this.logger.debug({ location }, "Executing mouse click");
|
|
1576
|
+
await this.page.mouse.click(location.centerX, location.centerY);
|
|
1577
|
+
}
|
|
1578
|
+
// Get the "id" attribute value from an HTML element.
|
|
1579
|
+
async getIDAttributeUsingCDP(objectId) {
|
|
1580
|
+
await this.cdpClient.send("DOM.getDocument", { depth: 0 });
|
|
1581
|
+
const cdpNodeResult = await this.cdpClient.send("DOM.requestNode", {
|
|
1582
|
+
objectId
|
|
1583
|
+
});
|
|
1584
|
+
const attrResult = await this.cdpClient.send("DOM.getAttributes", {
|
|
1585
|
+
nodeId: cdpNodeResult.nodeId
|
|
1586
|
+
});
|
|
1587
|
+
const attributes = attrResult.attributes;
|
|
1588
|
+
const indexAttr = attributes.findIndex((s) => s === "data-momentic-id");
|
|
1589
|
+
if (indexAttr === -1) {
|
|
1590
|
+
return "";
|
|
1591
|
+
}
|
|
1592
|
+
return attributes[indexAttr + 1] || "";
|
|
1593
|
+
}
|
|
1594
|
+
async getLocatorFromBackendID(backendNodeId) {
|
|
1595
|
+
await this.page.evaluate(addIDsScript);
|
|
1596
|
+
const cdpResolveResult = await this.cdpClient.send("DOM.resolveNode", {
|
|
1597
|
+
backendNodeId
|
|
1598
|
+
});
|
|
1599
|
+
if (!cdpResolveResult || !cdpResolveResult.object.objectId) {
|
|
1600
|
+
throw new Error(`Could not resolve backend node ${backendNodeId}`);
|
|
1601
|
+
}
|
|
1602
|
+
try {
|
|
1603
|
+
const id = await this.getIDAttributeUsingCDP(
|
|
1604
|
+
cdpResolveResult.object.objectId
|
|
1620
1605
|
);
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
while (Date.now() - a11yLoadStart < A11Y_STABLE_TIMEOUT_MS) {
|
|
1624
|
-
yield sleep(CHECK_INTERVAL_MS);
|
|
1625
|
-
if (!accessibilityTreeLoadFired && Date.now() - a11yLoadStart < A11Y_LOAD_TIMEOUT_MS) {
|
|
1626
|
-
this.logger.debug({ url }, `A11y tree not loaded yet, waiting...`);
|
|
1627
|
-
continue;
|
|
1628
|
-
}
|
|
1629
|
-
if (Date.now() - lastTreeUpdateTimestamp >= A11Y_STABLE_DURATION_MS) {
|
|
1630
|
-
this.logger.debug({ url }, `A11y tree not stable yet, waiting...`);
|
|
1631
|
-
continue;
|
|
1632
|
-
}
|
|
1633
|
-
timeoutTriggered = false;
|
|
1634
|
-
break;
|
|
1606
|
+
if (!id) {
|
|
1607
|
+
throw new Error("Failed getting data-momentic-id attribute using CDP");
|
|
1635
1608
|
}
|
|
1636
|
-
this.
|
|
1609
|
+
return this.page.locator(`[data-momentic-id="${id}"]`);
|
|
1610
|
+
} catch (err) {
|
|
1611
|
+
this.logger.error(
|
|
1637
1612
|
{
|
|
1638
|
-
|
|
1639
|
-
eventReceived: accessibilityTreeLoadFired,
|
|
1640
|
-
timeoutTriggered
|
|
1613
|
+
err
|
|
1641
1614
|
},
|
|
1642
|
-
"
|
|
1615
|
+
"Failed to get ID attribute"
|
|
1643
1616
|
);
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
);
|
|
1647
|
-
const { nodes } = yield this.cdpClient.send("Accessibility.queryAXTree", {
|
|
1648
|
-
backendNodeId: root.backendDOMNodeId
|
|
1649
|
-
});
|
|
1650
|
-
this.cdpClient.removeListener(
|
|
1651
|
-
"Accessibility.loadComplete",
|
|
1652
|
-
accessibilityLoadListener
|
|
1653
|
-
);
|
|
1654
|
-
this.cdpClient.removeListener(
|
|
1655
|
-
"Accessibility.nodesUpdated",
|
|
1656
|
-
treeUpdateListener
|
|
1657
|
-
);
|
|
1658
|
-
return {
|
|
1659
|
-
root,
|
|
1660
|
-
allNodes: nodes
|
|
1661
|
-
};
|
|
1662
|
-
});
|
|
1617
|
+
throw err;
|
|
1618
|
+
}
|
|
1663
1619
|
}
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1620
|
+
async clickUsingCDP(originalNode, options = {}) {
|
|
1621
|
+
let clickAttempts = 0;
|
|
1622
|
+
let candidateNode = originalNode;
|
|
1623
|
+
while (clickAttempts < MAX_BROWSER_ACTION_ATTEMPTS) {
|
|
1624
|
+
if (!candidateNode || candidateNode.role === "RootWebArea") {
|
|
1668
1625
|
throw new Error(
|
|
1669
|
-
`
|
|
1626
|
+
`Attempted to click node with no clickable surrounding elements: ${originalNode.getLogForm()}`
|
|
1670
1627
|
);
|
|
1671
1628
|
}
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
}
|
|
1676
|
-
// Get the "id" attribute value from an HTML element.
|
|
1677
|
-
getIDAttributeUsingCDP(objectId) {
|
|
1678
|
-
return __async(this, null, function* () {
|
|
1679
|
-
yield this.cdpClient.send("DOM.getDocument", { depth: 0 });
|
|
1680
|
-
const cdpNodeResult = yield this.cdpClient.send("DOM.requestNode", {
|
|
1681
|
-
objectId
|
|
1682
|
-
});
|
|
1683
|
-
const attrResult = yield this.cdpClient.send("DOM.getAttributes", {
|
|
1684
|
-
nodeId: cdpNodeResult.nodeId
|
|
1685
|
-
});
|
|
1686
|
-
const attributes = attrResult.attributes;
|
|
1687
|
-
const indexAttr = attributes.findIndex((s) => s === "data-momentic-id");
|
|
1688
|
-
if (indexAttr === -1) {
|
|
1689
|
-
return "";
|
|
1629
|
+
if (candidateNode.role === "StaticText") {
|
|
1630
|
+
candidateNode = candidateNode.parent;
|
|
1631
|
+
continue;
|
|
1690
1632
|
}
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
});
|
|
1700
|
-
if (!cdpResolveResult || !cdpResolveResult.object.objectId) {
|
|
1701
|
-
throw new Error(`Could not resolve backend node ${backendNodeId}`);
|
|
1633
|
+
const candidateNodeID = candidateNode.backendNodeID;
|
|
1634
|
+
if (!candidateNodeID) {
|
|
1635
|
+
this.logger.warn(
|
|
1636
|
+
{ node: candidateNode.getLogForm() },
|
|
1637
|
+
"Click candidate had no backend node ID"
|
|
1638
|
+
);
|
|
1639
|
+
candidateNode = candidateNode.parent;
|
|
1640
|
+
continue;
|
|
1702
1641
|
}
|
|
1703
1642
|
try {
|
|
1704
|
-
const
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1643
|
+
const locator = await this.getLocatorFromBackendID(candidateNodeID);
|
|
1644
|
+
if (options.doubleClick) {
|
|
1645
|
+
await locator.dblclick({
|
|
1646
|
+
timeout: BROWSER_ACTION_TIMEOUT_MS
|
|
1647
|
+
});
|
|
1648
|
+
} else {
|
|
1649
|
+
await locator.click({
|
|
1650
|
+
timeout: BROWSER_ACTION_TIMEOUT_MS,
|
|
1651
|
+
button: options.rightClick ? "right" : "left"
|
|
1652
|
+
});
|
|
1653
|
+
}
|
|
1654
|
+
if (candidateNode.id !== originalNode.id) {
|
|
1655
|
+
this.logger.info(
|
|
1656
|
+
{
|
|
1657
|
+
oldNode: originalNode.getLogForm(),
|
|
1658
|
+
newNode: candidateNode.getLogForm()
|
|
1659
|
+
},
|
|
1660
|
+
`Redirected click successfully to new element`
|
|
1661
|
+
);
|
|
1709
1662
|
}
|
|
1710
|
-
return
|
|
1663
|
+
return candidateNode;
|
|
1711
1664
|
} catch (err) {
|
|
1712
1665
|
this.logger.error(
|
|
1713
|
-
{
|
|
1714
|
-
|
|
1715
|
-
},
|
|
1716
|
-
"Failed to get ID attribute"
|
|
1666
|
+
{ err, node: candidateNode.getLogForm() },
|
|
1667
|
+
"Failed click or click timed out"
|
|
1717
1668
|
);
|
|
1718
|
-
|
|
1669
|
+
clickAttempts++;
|
|
1670
|
+
candidateNode = candidateNode.parent;
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
throw new Error(
|
|
1674
|
+
`Max click redirection attempts exhausted on original element: ${originalNode.getLogForm()}`
|
|
1675
|
+
);
|
|
1676
|
+
}
|
|
1677
|
+
/**
|
|
1678
|
+
* Currently unused, but could be useful for vision model integration.
|
|
1679
|
+
* Gets x/y position of an a11y node.
|
|
1680
|
+
*/
|
|
1681
|
+
async getElementLocation(backendNodeId) {
|
|
1682
|
+
const tree = await this.cdpClient.send("DOMSnapshot.captureSnapshot", {
|
|
1683
|
+
computedStyles: [],
|
|
1684
|
+
includeDOMRects: true,
|
|
1685
|
+
includePaintOrder: true
|
|
1686
|
+
});
|
|
1687
|
+
let devicePixelRatio = await this.page.evaluate(
|
|
1688
|
+
() => window.devicePixelRatio
|
|
1689
|
+
);
|
|
1690
|
+
if (process.platform === "darwin" && devicePixelRatio === 1) {
|
|
1691
|
+
devicePixelRatio = RETINA_WINDOW_SCALE_FACTOR;
|
|
1692
|
+
}
|
|
1693
|
+
const document2 = tree["documents"][0];
|
|
1694
|
+
const layout = document2["layout"];
|
|
1695
|
+
const nodes = document2["nodes"];
|
|
1696
|
+
const nodeNames = nodes["nodeName"] || [];
|
|
1697
|
+
const backendNodeIds = nodes["backendNodeId"] || [];
|
|
1698
|
+
const layoutNodeIndex = layout["nodeIndex"];
|
|
1699
|
+
const bounds = layout["bounds"];
|
|
1700
|
+
let cursor2 = -1;
|
|
1701
|
+
for (let i = 0; i < nodeNames.length; i++) {
|
|
1702
|
+
if (backendNodeIds[i] === backendNodeId) {
|
|
1703
|
+
cursor2 = layoutNodeIndex.indexOf(i);
|
|
1704
|
+
break;
|
|
1719
1705
|
}
|
|
1706
|
+
}
|
|
1707
|
+
if (cursor2 === -1) {
|
|
1708
|
+
throw new Error(
|
|
1709
|
+
`Could not find any backend node with ID ${backendNodeId}`
|
|
1710
|
+
);
|
|
1711
|
+
}
|
|
1712
|
+
let [x = 0, y = 0, width = 0, height = 0] = bounds[cursor2];
|
|
1713
|
+
x /= devicePixelRatio;
|
|
1714
|
+
y /= devicePixelRatio;
|
|
1715
|
+
width /= devicePixelRatio;
|
|
1716
|
+
height /= devicePixelRatio;
|
|
1717
|
+
const centerX = x + width / 2;
|
|
1718
|
+
const centerY = y + height / 2;
|
|
1719
|
+
return { centerX, centerY };
|
|
1720
|
+
}
|
|
1721
|
+
async scrollUp() {
|
|
1722
|
+
await this.page.evaluate(() => {
|
|
1723
|
+
(document.scrollingElement || document.body).scrollTop = (document.scrollingElement || document.body).scrollTop - window.innerHeight;
|
|
1724
|
+
});
|
|
1725
|
+
await this.page.evaluate(() => {
|
|
1726
|
+
(document.scrollingElement || document.body).scrollTop = (document.scrollingElement || document.body).scrollTop + window.innerHeight;
|
|
1720
1727
|
});
|
|
1721
1728
|
}
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
let candidateNode = originalNode;
|
|
1726
|
-
while (clickAttempts < MAX_BROWSER_ACTION_ATTEMPTS) {
|
|
1727
|
-
if (!candidateNode || candidateNode.role === "RootWebArea") {
|
|
1728
|
-
throw new Error(
|
|
1729
|
-
`Attempted to click node with no clickable surrounding elements: ${originalNode.getLogForm()}`
|
|
1730
|
-
);
|
|
1731
|
-
}
|
|
1732
|
-
if (candidateNode.role === "StaticText") {
|
|
1733
|
-
candidateNode = candidateNode.parent;
|
|
1734
|
-
continue;
|
|
1735
|
-
}
|
|
1736
|
-
const candidateNodeID = candidateNode.backendNodeID;
|
|
1737
|
-
if (!candidateNodeID) {
|
|
1738
|
-
this.logger.warn(
|
|
1739
|
-
{ node: candidateNode.getLogForm() },
|
|
1740
|
-
"Click candidate had no backend node ID"
|
|
1741
|
-
);
|
|
1742
|
-
candidateNode = candidateNode.parent;
|
|
1743
|
-
continue;
|
|
1744
|
-
}
|
|
1745
|
-
try {
|
|
1746
|
-
const locator = yield this.getLocatorFromBackendID(candidateNodeID);
|
|
1747
|
-
if (options.doubleClick) {
|
|
1748
|
-
yield locator.dblclick({
|
|
1749
|
-
timeout: BROWSER_ACTION_TIMEOUT_MS
|
|
1750
|
-
});
|
|
1751
|
-
} else {
|
|
1752
|
-
yield locator.click({
|
|
1753
|
-
timeout: BROWSER_ACTION_TIMEOUT_MS,
|
|
1754
|
-
button: options.rightClick ? "right" : "left"
|
|
1755
|
-
});
|
|
1756
|
-
}
|
|
1757
|
-
if (candidateNode.id !== originalNode.id) {
|
|
1758
|
-
this.logger.info(
|
|
1759
|
-
{
|
|
1760
|
-
oldNode: originalNode.getLogForm(),
|
|
1761
|
-
newNode: candidateNode.getLogForm()
|
|
1762
|
-
},
|
|
1763
|
-
`Redirected click successfully to new element`
|
|
1764
|
-
);
|
|
1765
|
-
}
|
|
1766
|
-
return candidateNode;
|
|
1767
|
-
} catch (err) {
|
|
1768
|
-
this.logger.error(
|
|
1769
|
-
{ err, node: candidateNode.getLogForm() },
|
|
1770
|
-
"Failed click or click timed out"
|
|
1771
|
-
);
|
|
1772
|
-
clickAttempts++;
|
|
1773
|
-
candidateNode = candidateNode.parent;
|
|
1774
|
-
}
|
|
1775
|
-
}
|
|
1776
|
-
throw new Error(
|
|
1777
|
-
`Max click redirection attempts exhausted on original element: ${originalNode.getLogForm()}`
|
|
1778
|
-
);
|
|
1729
|
+
async scrollDown() {
|
|
1730
|
+
await this.page.evaluate(() => {
|
|
1731
|
+
(document.scrollingElement || document.body).scrollTop = (document.scrollingElement || document.body).scrollTop + window.innerHeight;
|
|
1779
1732
|
});
|
|
1780
1733
|
}
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
return __async(this, null, function* () {
|
|
1787
|
-
const tree = yield this.cdpClient.send("DOMSnapshot.captureSnapshot", {
|
|
1788
|
-
computedStyles: [],
|
|
1789
|
-
includeDOMRects: true,
|
|
1790
|
-
includePaintOrder: true
|
|
1791
|
-
});
|
|
1792
|
-
let devicePixelRatio = yield this.page.evaluate(
|
|
1793
|
-
() => window.devicePixelRatio
|
|
1794
|
-
);
|
|
1795
|
-
if (process.platform === "darwin" && devicePixelRatio === 1) {
|
|
1796
|
-
devicePixelRatio = RETINA_WINDOW_SCALE_FACTOR;
|
|
1797
|
-
}
|
|
1798
|
-
const document2 = tree["documents"][0];
|
|
1799
|
-
const layout = document2["layout"];
|
|
1800
|
-
const nodes = document2["nodes"];
|
|
1801
|
-
const nodeNames = nodes["nodeName"] || [];
|
|
1802
|
-
const backendNodeIds = nodes["backendNodeId"] || [];
|
|
1803
|
-
const layoutNodeIndex = layout["nodeIndex"];
|
|
1804
|
-
const bounds = layout["bounds"];
|
|
1805
|
-
let cursor2 = -1;
|
|
1806
|
-
for (let i = 0; i < nodeNames.length; i++) {
|
|
1807
|
-
if (backendNodeIds[i] === backendNodeId) {
|
|
1808
|
-
cursor2 = layoutNodeIndex.indexOf(i);
|
|
1809
|
-
break;
|
|
1810
|
-
}
|
|
1811
|
-
}
|
|
1812
|
-
if (cursor2 === -1) {
|
|
1813
|
-
throw new Error(
|
|
1814
|
-
`Could not find any backend node with ID ${backendNodeId}`
|
|
1815
|
-
);
|
|
1816
|
-
}
|
|
1817
|
-
let [x = 0, y = 0, width = 0, height = 0] = bounds[cursor2];
|
|
1818
|
-
x /= devicePixelRatio;
|
|
1819
|
-
y /= devicePixelRatio;
|
|
1820
|
-
width /= devicePixelRatio;
|
|
1821
|
-
height /= devicePixelRatio;
|
|
1822
|
-
const centerX = x + width / 2;
|
|
1823
|
-
const centerY = y + height / 2;
|
|
1824
|
-
return { centerX, centerY };
|
|
1825
|
-
});
|
|
1826
|
-
}
|
|
1827
|
-
scrollUp() {
|
|
1828
|
-
return __async(this, null, function* () {
|
|
1829
|
-
yield this.page.evaluate(() => {
|
|
1830
|
-
(document.scrollingElement || document.body).scrollTop = (document.scrollingElement || document.body).scrollTop - window.innerHeight;
|
|
1831
|
-
});
|
|
1832
|
-
yield this.page.evaluate(() => {
|
|
1833
|
-
(document.scrollingElement || document.body).scrollTop = (document.scrollingElement || document.body).scrollTop + window.innerHeight;
|
|
1834
|
-
});
|
|
1835
|
-
});
|
|
1836
|
-
}
|
|
1837
|
-
scrollDown() {
|
|
1838
|
-
return __async(this, null, function* () {
|
|
1839
|
-
yield this.page.evaluate(() => {
|
|
1840
|
-
(document.scrollingElement || document.body).scrollTop = (document.scrollingElement || document.body).scrollTop + window.innerHeight;
|
|
1841
|
-
});
|
|
1842
|
-
});
|
|
1843
|
-
}
|
|
1844
|
-
goForward() {
|
|
1845
|
-
return __async(this, null, function* () {
|
|
1846
|
-
yield this.wrapPossibleNavigation(
|
|
1847
|
-
() => this.page.goForward({ timeout: MAX_LOAD_TIMEOUT_MS })
|
|
1848
|
-
);
|
|
1849
|
-
yield this.pageSetup();
|
|
1850
|
-
});
|
|
1851
|
-
}
|
|
1852
|
-
goBack() {
|
|
1853
|
-
return __async(this, null, function* () {
|
|
1854
|
-
yield this.wrapPossibleNavigation(
|
|
1855
|
-
() => this.page.goBack({ timeout: MAX_LOAD_TIMEOUT_MS })
|
|
1856
|
-
);
|
|
1857
|
-
yield this.pageSetup();
|
|
1858
|
-
});
|
|
1734
|
+
async goForward() {
|
|
1735
|
+
await this.wrapPossibleNavigation(
|
|
1736
|
+
() => this.page.goForward({ timeout: MAX_LOAD_TIMEOUT_MS })
|
|
1737
|
+
);
|
|
1738
|
+
await this.pageSetup();
|
|
1859
1739
|
}
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1740
|
+
async goBack() {
|
|
1741
|
+
await this.wrapPossibleNavigation(
|
|
1742
|
+
() => this.page.goBack({ timeout: MAX_LOAD_TIMEOUT_MS })
|
|
1743
|
+
);
|
|
1744
|
+
await this.pageSetup();
|
|
1745
|
+
}
|
|
1746
|
+
async switchToPage(urlSubstring) {
|
|
1747
|
+
const allPages = await this.context.pages();
|
|
1748
|
+
for (let i = 0; i < allPages.length; i++) {
|
|
1749
|
+
const page = allPages[i];
|
|
1750
|
+
if (page.url().includes(urlSubstring)) {
|
|
1751
|
+
this.page = page;
|
|
1752
|
+
await page.waitForLoadState("load", {
|
|
1753
|
+
timeout: MAX_LOAD_TIMEOUT_MS
|
|
1754
|
+
});
|
|
1755
|
+
await this.pageSetup();
|
|
1756
|
+
this.cdpClient = await this.context.newCDPSession(page);
|
|
1757
|
+
await initCDPSession(this.cdpClient);
|
|
1758
|
+
this.logger.info(`Switching to tab ${i} with url ${page.url()}`);
|
|
1759
|
+
return;
|
|
1876
1760
|
}
|
|
1877
|
-
|
|
1878
|
-
});
|
|
1761
|
+
}
|
|
1762
|
+
throw new Error(`Could not find page with url containing ${urlSubstring}`);
|
|
1879
1763
|
}
|
|
1880
|
-
setCookie(cookie) {
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
yield this.context.addCookies([cookieSettings]);
|
|
1884
|
-
});
|
|
1764
|
+
async setCookie(cookie) {
|
|
1765
|
+
const cookieSettings = parseCookieString(cookie);
|
|
1766
|
+
await this.context.addCookies([cookieSettings]);
|
|
1885
1767
|
}
|
|
1886
1768
|
};
|
|
1887
|
-
_ChromeBrowser.USER_AGENT = import_playwright.devices["Desktop Chrome"].userAgent;
|
|
1888
|
-
var ChromeBrowser = _ChromeBrowser;
|
|
1889
1769
|
|
|
1890
1770
|
// ../../packages/web-agent/src/configs/controller.ts
|
|
1891
1771
|
var A11Y_CONTROLLER_CONFIG = {
|
|
@@ -1897,10 +1777,22 @@ var A11Y_CONTROLLER_CONFIG = {
|
|
|
1897
1777
|
var DEFAULT_CONTROLLER_CONFIG = A11Y_CONTROLLER_CONFIG;
|
|
1898
1778
|
|
|
1899
1779
|
// ../../packages/web-agent/src/controller.ts
|
|
1900
|
-
|
|
1901
|
-
|
|
1780
|
+
import dedent2 from "dedent";
|
|
1781
|
+
import diffLines from "diff-lines";
|
|
1902
1782
|
var MAX_HISTORY_CHAR_LENGTH = 1e4;
|
|
1903
1783
|
var AgentController = class {
|
|
1784
|
+
// Instance of browser to interact with
|
|
1785
|
+
browser;
|
|
1786
|
+
// Stack of queued-up instructions
|
|
1787
|
+
pendingInstructions;
|
|
1788
|
+
// manager for all AI generation
|
|
1789
|
+
generator;
|
|
1790
|
+
// Stack of commands previously executed.
|
|
1791
|
+
// Top of stack can be a pending command that hasn't been executed yet.
|
|
1792
|
+
// Should not contain intermediate successes due to granular commands.
|
|
1793
|
+
commandHistory;
|
|
1794
|
+
config;
|
|
1795
|
+
logger;
|
|
1904
1796
|
constructor({ browser, config, generator, logger }) {
|
|
1905
1797
|
this.browser = browser;
|
|
1906
1798
|
this.generator = generator;
|
|
@@ -1935,20 +1827,16 @@ var AgentController = class {
|
|
|
1935
1827
|
/**
|
|
1936
1828
|
* Reset controller and browser state.
|
|
1937
1829
|
*/
|
|
1938
|
-
resetState() {
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
yield this.browser.navigate(this.browser.baseURL);
|
|
1942
|
-
});
|
|
1830
|
+
async resetState() {
|
|
1831
|
+
this.resetHistory();
|
|
1832
|
+
await this.browser.navigate(this.browser.baseURL);
|
|
1943
1833
|
}
|
|
1944
1834
|
/**
|
|
1945
1835
|
* Get the browser state as a string
|
|
1946
1836
|
*/
|
|
1947
|
-
getBrowserState() {
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
return a11yTree.serialize();
|
|
1951
|
-
});
|
|
1837
|
+
async getBrowserState() {
|
|
1838
|
+
const a11yTree = await this.browser.getA11yTree();
|
|
1839
|
+
return a11yTree.serialize();
|
|
1952
1840
|
}
|
|
1953
1841
|
getSerializedHistory(url, currentBrowserState) {
|
|
1954
1842
|
let history;
|
|
@@ -1959,105 +1847,99 @@ var AgentController = class {
|
|
|
1959
1847
|
}
|
|
1960
1848
|
return history;
|
|
1961
1849
|
}
|
|
1962
|
-
splitUserGoal(type, goal, disableCache) {
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
}
|
|
1973
|
-
});
|
|
1850
|
+
async splitUserGoal(type, goal, disableCache) {
|
|
1851
|
+
if (type === "AI_ACTION" /* AI_ACTION */ && goal.match(/[,!;.]|(?:and)|(?:then)/) && this.config.useGoalSplitter) {
|
|
1852
|
+
const granularInstructions = await this.generator.getGranularGoals(
|
|
1853
|
+
{ goal, url: this.browser.url },
|
|
1854
|
+
disableCache
|
|
1855
|
+
);
|
|
1856
|
+
this.pendingInstructions = granularInstructions.reverse();
|
|
1857
|
+
} else {
|
|
1858
|
+
this.pendingInstructions = [goal];
|
|
1859
|
+
}
|
|
1974
1860
|
}
|
|
1975
1861
|
/**
|
|
1976
1862
|
* Given previously executed commands, generate command for the current prompt.
|
|
1977
1863
|
* Should only be used for AI action.
|
|
1978
1864
|
*/
|
|
1979
|
-
promptToCommand(type, goal, disableCache) {
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1865
|
+
async promptToCommand(type, goal, disableCache) {
|
|
1866
|
+
if (this.pendingInstructions.length === 0) {
|
|
1867
|
+
await this.splitUserGoal(type, goal, disableCache);
|
|
1868
|
+
}
|
|
1869
|
+
const currInstruction = this.pendingInstructions[this.pendingInstructions.length - 1];
|
|
1870
|
+
this.logger.info({ goal: currInstruction }, "Starting prompt translation");
|
|
1871
|
+
const getBrowserStateStart = Date.now();
|
|
1872
|
+
const url = this.browser.url;
|
|
1873
|
+
const browserState = await this.getBrowserState();
|
|
1874
|
+
this.logger.info(
|
|
1875
|
+
{
|
|
1876
|
+
duration: Date.now() - getBrowserStateStart,
|
|
1877
|
+
url
|
|
1878
|
+
},
|
|
1879
|
+
"Got browser state"
|
|
1880
|
+
);
|
|
1881
|
+
const numPrevious = this.commandHistory.length;
|
|
1882
|
+
this.commandHistory.push({
|
|
1883
|
+
state: "PENDING",
|
|
1884
|
+
browserStateBeforeCommand: browserState,
|
|
1885
|
+
urlBeforeCommand: url,
|
|
1886
|
+
type
|
|
1887
|
+
});
|
|
1888
|
+
const history = this.getSerializedHistory(url, browserState);
|
|
1889
|
+
const getCommandProposalStart = Date.now();
|
|
1890
|
+
const proposedCommand = await this.generator.getProposedCommand(
|
|
1891
|
+
{
|
|
1892
|
+
url,
|
|
1893
|
+
numPrevious,
|
|
1894
|
+
browserState,
|
|
1895
|
+
history,
|
|
1896
|
+
goal: currInstruction,
|
|
1897
|
+
lastCommand: this.lastExecutedCommand
|
|
1898
|
+
},
|
|
1899
|
+
disableCache
|
|
1900
|
+
);
|
|
1901
|
+
this.logger.info(
|
|
1902
|
+
{ duration: Date.now() - getCommandProposalStart },
|
|
1903
|
+
"Got proposed command"
|
|
1904
|
+
);
|
|
1905
|
+
if (proposedCommand.type === "SUCCESS" /* SUCCESS */) {
|
|
1906
|
+
const finishedInstruction = this.pendingInstructions.pop();
|
|
1989
1907
|
this.logger.info(
|
|
1990
1908
|
{
|
|
1991
|
-
|
|
1992
|
-
|
|
1909
|
+
finishedInstruction,
|
|
1910
|
+
remainingInstructions: this.pendingInstructions
|
|
1993
1911
|
},
|
|
1994
|
-
"
|
|
1912
|
+
"Removing pending instruction due to SUCCESS"
|
|
1995
1913
|
);
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
const proposedCommand = yield this.generator.getProposedCommand(
|
|
1914
|
+
if (this.pendingInstructions.length !== 0) {
|
|
1915
|
+
this.commandHistory.pop();
|
|
1916
|
+
return this.promptToCommand(type, "", disableCache);
|
|
1917
|
+
}
|
|
1918
|
+
} else if (
|
|
1919
|
+
// on failure, we don't continue to execute
|
|
1920
|
+
proposedCommand.type === "FAILURE"
|
|
1921
|
+
) {
|
|
1922
|
+
this.logger.info(
|
|
2006
1923
|
{
|
|
2007
|
-
|
|
2008
|
-
numPrevious,
|
|
2009
|
-
browserState,
|
|
2010
|
-
history,
|
|
2011
|
-
goal: currInstruction,
|
|
2012
|
-
lastCommand: this.lastExecutedCommand
|
|
1924
|
+
remainingInstructions: this.pendingInstructions
|
|
2013
1925
|
},
|
|
2014
|
-
|
|
2015
|
-
);
|
|
2016
|
-
this.logger.info(
|
|
2017
|
-
{ duration: Date.now() - getCommandProposalStart },
|
|
2018
|
-
"Got proposed command"
|
|
1926
|
+
"Removing pending instructions due to FAILURE"
|
|
2019
1927
|
);
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
{
|
|
2024
|
-
finishedInstruction,
|
|
2025
|
-
remainingInstructions: this.pendingInstructions
|
|
2026
|
-
},
|
|
2027
|
-
"Removing pending instruction due to SUCCESS"
|
|
2028
|
-
);
|
|
2029
|
-
if (this.pendingInstructions.length !== 0) {
|
|
2030
|
-
this.commandHistory.pop();
|
|
2031
|
-
return this.promptToCommand(type, "", disableCache);
|
|
2032
|
-
}
|
|
2033
|
-
} else if (
|
|
2034
|
-
// on failure, we don't continue to execute
|
|
2035
|
-
proposedCommand.type === "FAILURE"
|
|
2036
|
-
) {
|
|
2037
|
-
this.logger.info(
|
|
2038
|
-
{
|
|
2039
|
-
remainingInstructions: this.pendingInstructions
|
|
2040
|
-
},
|
|
2041
|
-
"Removing pending instructions due to FAILURE"
|
|
2042
|
-
);
|
|
2043
|
-
this.pendingInstructions = [];
|
|
2044
|
-
}
|
|
2045
|
-
return proposedCommand;
|
|
2046
|
-
});
|
|
1928
|
+
this.pendingInstructions = [];
|
|
1929
|
+
}
|
|
1930
|
+
return proposedCommand;
|
|
2047
1931
|
}
|
|
2048
|
-
locateElement(description, disableCache) {
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
1932
|
+
async locateElement(description, disableCache) {
|
|
1933
|
+
const locator = await this.generator.getElementLocation(
|
|
1934
|
+
{ browserState: await this.getBrowserState(), goal: description },
|
|
1935
|
+
disableCache
|
|
1936
|
+
);
|
|
1937
|
+
if (locator.id < 0) {
|
|
1938
|
+
throw new Error(
|
|
1939
|
+
`Unable to locate element with description: ${description}`
|
|
2053
1940
|
);
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
`Unable to locate element with description: ${description}`
|
|
2057
|
-
);
|
|
2058
|
-
}
|
|
2059
|
-
return locator;
|
|
2060
|
-
});
|
|
1941
|
+
}
|
|
1942
|
+
return locator;
|
|
2061
1943
|
}
|
|
2062
1944
|
/**
|
|
2063
1945
|
* Construct a detailed history that can be passed to the LLM.
|
|
@@ -2084,7 +1966,7 @@ var AgentController = class {
|
|
|
2084
1966
|
` URL CHANGE: '${log.urlBeforeCommand}' -> '${currentURL}'`
|
|
2085
1967
|
);
|
|
2086
1968
|
} else {
|
|
2087
|
-
const browserStateDiff = (
|
|
1969
|
+
const browserStateDiff = diffLines(
|
|
2088
1970
|
log.browserStateBeforeCommand,
|
|
2089
1971
|
currentPageState,
|
|
2090
1972
|
{
|
|
@@ -2107,7 +1989,7 @@ var AgentController = class {
|
|
|
2107
1989
|
return historyLines.join("\n");
|
|
2108
1990
|
}
|
|
2109
1991
|
getListHistory() {
|
|
2110
|
-
return
|
|
1992
|
+
return dedent2`Here are the commands that you have successfully executed:
|
|
2111
1993
|
${this.commandHistory.filter((cmd) => cmd.type === "AI_ACTION" /* AI_ACTION */).map((cmd) => `- ${cmd.serializedCommand}`).join("\n")}`;
|
|
2112
1994
|
}
|
|
2113
1995
|
/**
|
|
@@ -2115,108 +1997,104 @@ var AgentController = class {
|
|
|
2115
1997
|
* @param [stateless=false] Execute this command in a stateless fashion, without modifying any controller state such as
|
|
2116
1998
|
* pending instructions. Useful when executing cached instructions.
|
|
2117
1999
|
*/
|
|
2118
|
-
executeCommand(command, disableCache, stateless = false) {
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
if (!
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
"Executing command but there is no pending entry in the history"
|
|
2125
|
-
);
|
|
2126
|
-
}
|
|
2127
|
-
} else {
|
|
2128
|
-
yield this.browser.getA11yTree();
|
|
2129
|
-
}
|
|
2130
|
-
let result;
|
|
2131
|
-
try {
|
|
2132
|
-
const executionStart = Date.now();
|
|
2133
|
-
result = yield this.executePresetStep(
|
|
2134
|
-
command,
|
|
2135
|
-
disableCache
|
|
2136
|
-
);
|
|
2137
|
-
this.logger.info(
|
|
2138
|
-
{ result, duration: Date.now() - executionStart },
|
|
2139
|
-
"Got execution result"
|
|
2140
|
-
);
|
|
2141
|
-
} catch (e) {
|
|
2142
|
-
if (e instanceof Error) {
|
|
2143
|
-
throw new BrowserExecutionError(`Failed to execute command: ${e}`, {
|
|
2144
|
-
cause: e
|
|
2145
|
-
});
|
|
2146
|
-
}
|
|
2147
|
-
throw new BrowserExecutionError(
|
|
2148
|
-
`Unexpected throw from executing command`,
|
|
2149
|
-
{
|
|
2150
|
-
cause: new Error(`${e}`)
|
|
2151
|
-
}
|
|
2000
|
+
async executeCommand(command, disableCache, stateless = false) {
|
|
2001
|
+
const pendingHistory = this.commandHistory[this.commandHistory.length - 1];
|
|
2002
|
+
if (!stateless) {
|
|
2003
|
+
if (!pendingHistory || pendingHistory.state !== "PENDING") {
|
|
2004
|
+
throw new Error(
|
|
2005
|
+
"Executing command but there is no pending entry in the history"
|
|
2152
2006
|
);
|
|
2153
2007
|
}
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
command
|
|
2162
|
-
|
|
2163
|
-
if (!stateless) {
|
|
2164
|
-
pendingHistory.generatedStep = command;
|
|
2165
|
-
pendingHistory.serializedCommand = serializeCommand(command);
|
|
2166
|
-
pendingHistory.state = "DONE";
|
|
2167
|
-
}
|
|
2168
|
-
return result;
|
|
2169
|
-
});
|
|
2170
|
-
}
|
|
2171
|
-
executeAssertion(urlBeforeCommand, command) {
|
|
2172
|
-
return __async(this, null, function* () {
|
|
2173
|
-
let params;
|
|
2174
|
-
if (command.useVision) {
|
|
2175
|
-
params = {
|
|
2176
|
-
goal: command.assertion,
|
|
2177
|
-
url: urlBeforeCommand,
|
|
2178
|
-
// used for vision only
|
|
2179
|
-
screenshot: yield this.browser.screenshot(),
|
|
2180
|
-
// unused for visual assertion
|
|
2181
|
-
browserState: "",
|
|
2182
|
-
history: "",
|
|
2183
|
-
numPrevious: -1,
|
|
2184
|
-
lastCommand: null
|
|
2185
|
-
};
|
|
2186
|
-
} else {
|
|
2187
|
-
const browserState = yield this.getBrowserState();
|
|
2188
|
-
const history = this.getSerializedHistory(urlBeforeCommand, browserState);
|
|
2189
|
-
params = {
|
|
2190
|
-
goal: command.assertion,
|
|
2191
|
-
url: urlBeforeCommand,
|
|
2192
|
-
// used for text only
|
|
2193
|
-
browserState,
|
|
2194
|
-
history,
|
|
2195
|
-
lastCommand: this.lastExecutedCommand,
|
|
2196
|
-
numPrevious: this.commandHistory.length
|
|
2197
|
-
};
|
|
2198
|
-
}
|
|
2199
|
-
const assertionEval = yield this.generator.getAssertionResult(
|
|
2200
|
-
params,
|
|
2201
|
-
command.useVision,
|
|
2202
|
-
command.disableCache
|
|
2008
|
+
} else {
|
|
2009
|
+
await this.browser.getA11yTree();
|
|
2010
|
+
}
|
|
2011
|
+
let result;
|
|
2012
|
+
try {
|
|
2013
|
+
const executionStart = Date.now();
|
|
2014
|
+
result = await this.executePresetStep(
|
|
2015
|
+
command,
|
|
2016
|
+
disableCache
|
|
2203
2017
|
);
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2018
|
+
this.logger.info(
|
|
2019
|
+
{ result, duration: Date.now() - executionStart },
|
|
2020
|
+
"Got execution result"
|
|
2021
|
+
);
|
|
2022
|
+
} catch (e) {
|
|
2023
|
+
if (e instanceof Error) {
|
|
2024
|
+
throw new BrowserExecutionError(`Failed to execute command: ${e}`, {
|
|
2025
|
+
cause: e
|
|
2026
|
+
});
|
|
2210
2027
|
}
|
|
2211
|
-
|
|
2212
|
-
throw
|
|
2028
|
+
throw new BrowserExecutionError(
|
|
2029
|
+
`Unexpected throw from executing command`,
|
|
2030
|
+
{
|
|
2031
|
+
cause: new Error(`${e}`)
|
|
2032
|
+
}
|
|
2033
|
+
);
|
|
2034
|
+
}
|
|
2035
|
+
if (result.succeedImmediately && !stateless) {
|
|
2036
|
+
this.pendingInstructions.pop();
|
|
2037
|
+
if (this.pendingInstructions.length > 0) {
|
|
2038
|
+
result.succeedImmediately = false;
|
|
2213
2039
|
}
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2040
|
+
}
|
|
2041
|
+
if (result.elementInteracted && "target" in command && !command.target.elementDescriptor) {
|
|
2042
|
+
command.target.elementDescriptor = result.elementInteracted.trim();
|
|
2043
|
+
}
|
|
2044
|
+
if (!stateless) {
|
|
2045
|
+
pendingHistory.generatedStep = command;
|
|
2046
|
+
pendingHistory.serializedCommand = serializeCommand(command);
|
|
2047
|
+
pendingHistory.state = "DONE";
|
|
2048
|
+
}
|
|
2049
|
+
return result;
|
|
2050
|
+
}
|
|
2051
|
+
async executeAssertion(urlBeforeCommand, command) {
|
|
2052
|
+
let params;
|
|
2053
|
+
if (command.useVision) {
|
|
2054
|
+
params = {
|
|
2055
|
+
goal: command.assertion,
|
|
2056
|
+
url: urlBeforeCommand,
|
|
2057
|
+
// used for vision only
|
|
2058
|
+
screenshot: await this.browser.screenshot(),
|
|
2059
|
+
// unused for visual assertion
|
|
2060
|
+
browserState: "",
|
|
2061
|
+
history: "",
|
|
2062
|
+
numPrevious: -1,
|
|
2063
|
+
lastCommand: null
|
|
2218
2064
|
};
|
|
2219
|
-
}
|
|
2065
|
+
} else {
|
|
2066
|
+
const browserState = await this.getBrowserState();
|
|
2067
|
+
const history = this.getSerializedHistory(urlBeforeCommand, browserState);
|
|
2068
|
+
params = {
|
|
2069
|
+
goal: command.assertion,
|
|
2070
|
+
url: urlBeforeCommand,
|
|
2071
|
+
// used for text only
|
|
2072
|
+
browserState,
|
|
2073
|
+
history,
|
|
2074
|
+
lastCommand: this.lastExecutedCommand,
|
|
2075
|
+
numPrevious: this.commandHistory.length
|
|
2076
|
+
};
|
|
2077
|
+
}
|
|
2078
|
+
const assertionEval = await this.generator.getAssertionResult(
|
|
2079
|
+
params,
|
|
2080
|
+
command.useVision,
|
|
2081
|
+
command.disableCache
|
|
2082
|
+
);
|
|
2083
|
+
if (assertionEval.relevantElements) {
|
|
2084
|
+
void Promise.all(
|
|
2085
|
+
assertionEval.relevantElements.map(
|
|
2086
|
+
(id) => this.browser.highlight({ id })
|
|
2087
|
+
)
|
|
2088
|
+
);
|
|
2089
|
+
}
|
|
2090
|
+
if (!assertionEval.result) {
|
|
2091
|
+
throw new Error(assertionEval.thoughts);
|
|
2092
|
+
}
|
|
2093
|
+
return {
|
|
2094
|
+
succeedImmediately: false,
|
|
2095
|
+
thoughts: assertionEval.thoughts,
|
|
2096
|
+
urlAfterCommand: urlBeforeCommand
|
|
2097
|
+
};
|
|
2220
2098
|
}
|
|
2221
2099
|
/**
|
|
2222
2100
|
* Executes a preset command.
|
|
@@ -2224,265 +2102,255 @@ var AgentController = class {
|
|
|
2224
2102
|
* For assertions, an AssertionResult with thoughts is returned.
|
|
2225
2103
|
* Throws on failure.
|
|
2226
2104
|
*/
|
|
2227
|
-
executePresetStep(command, disableCache) {
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
return this.executeAssertion(urlBeforeCommand, command.condition);
|
|
2235
|
-
}
|
|
2236
|
-
return {
|
|
2237
|
-
succeedImmediately: false,
|
|
2238
|
-
urlAfterCommand: this.browser.url
|
|
2239
|
-
};
|
|
2240
|
-
case "AI_ASSERTION" /* AI_ASSERTION */: {
|
|
2241
|
-
return this.executeAssertion(urlBeforeCommand, command);
|
|
2105
|
+
async executePresetStep(command, disableCache) {
|
|
2106
|
+
var _a, _b, _c;
|
|
2107
|
+
const urlBeforeCommand = this.browser.url;
|
|
2108
|
+
switch (command.type) {
|
|
2109
|
+
case "SUCCESS" /* SUCCESS */:
|
|
2110
|
+
if ((_a = command.condition) == null ? void 0 : _a.assertion.trim()) {
|
|
2111
|
+
return this.executeAssertion(urlBeforeCommand, command.condition);
|
|
2242
2112
|
}
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
{
|
|
2280
|
-
doubleClick: command.doubleClick,
|
|
2281
|
-
rightClick: command.rightClick
|
|
2282
|
-
}
|
|
2113
|
+
return {
|
|
2114
|
+
succeedImmediately: false,
|
|
2115
|
+
urlAfterCommand: this.browser.url
|
|
2116
|
+
};
|
|
2117
|
+
case "AI_ASSERTION" /* AI_ASSERTION */: {
|
|
2118
|
+
return this.executeAssertion(urlBeforeCommand, command);
|
|
2119
|
+
}
|
|
2120
|
+
case "NAVIGATE" /* NAVIGATE */:
|
|
2121
|
+
await this.browser.navigate(command.url);
|
|
2122
|
+
break;
|
|
2123
|
+
case "GO_BACK" /* GO_BACK */:
|
|
2124
|
+
await this.browser.goBack();
|
|
2125
|
+
break;
|
|
2126
|
+
case "GO_FORWARD" /* GO_FORWARD */:
|
|
2127
|
+
await this.browser.goForward();
|
|
2128
|
+
break;
|
|
2129
|
+
case "SCROLL_DOWN" /* SCROLL_DOWN */:
|
|
2130
|
+
await this.browser.scrollDown();
|
|
2131
|
+
break;
|
|
2132
|
+
case "SCROLL_UP" /* SCROLL_UP */:
|
|
2133
|
+
await this.browser.scrollUp();
|
|
2134
|
+
break;
|
|
2135
|
+
case "WAIT" /* WAIT */:
|
|
2136
|
+
await this.browser.wait(command.delay * 1e3);
|
|
2137
|
+
break;
|
|
2138
|
+
case "REFRESH" /* REFRESH */:
|
|
2139
|
+
await this.browser.refresh();
|
|
2140
|
+
break;
|
|
2141
|
+
case "CLICK" /* CLICK */: {
|
|
2142
|
+
let id;
|
|
2143
|
+
if (command.target.a11yData) {
|
|
2144
|
+
id = (_b = command.target.a11yData) == null ? void 0 : _b.id;
|
|
2145
|
+
} else {
|
|
2146
|
+
const locator = await this.locateElement(
|
|
2147
|
+
command.target.elementDescriptor,
|
|
2148
|
+
disableCache
|
|
2283
2149
|
);
|
|
2284
|
-
|
|
2285
|
-
urlAfterCommand: this.browser.url,
|
|
2286
|
-
succeedImmediately: false,
|
|
2287
|
-
elementInteracted
|
|
2288
|
-
};
|
|
2289
|
-
if (urlChanged(urlBeforeCommand, result2.urlAfterCommand)) {
|
|
2290
|
-
result2.succeedImmediately = true;
|
|
2291
|
-
result2.succeedImmediatelyReason = "URL changed";
|
|
2292
|
-
}
|
|
2293
|
-
return result2;
|
|
2150
|
+
id = locator.id;
|
|
2294
2151
|
}
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
disableCache
|
|
2303
|
-
);
|
|
2304
|
-
id = locator.id;
|
|
2152
|
+
const elementInteracted = await this.browser.click(
|
|
2153
|
+
{
|
|
2154
|
+
id
|
|
2155
|
+
},
|
|
2156
|
+
{
|
|
2157
|
+
doubleClick: command.doubleClick,
|
|
2158
|
+
rightClick: command.rightClick
|
|
2305
2159
|
}
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2160
|
+
);
|
|
2161
|
+
const result2 = {
|
|
2162
|
+
urlAfterCommand: this.browser.url,
|
|
2163
|
+
succeedImmediately: false,
|
|
2164
|
+
elementInteracted
|
|
2165
|
+
};
|
|
2166
|
+
if (urlChanged(urlBeforeCommand, result2.urlAfterCommand)) {
|
|
2167
|
+
result2.succeedImmediately = true;
|
|
2168
|
+
result2.succeedImmediatelyReason = "URL changed";
|
|
2169
|
+
}
|
|
2170
|
+
return result2;
|
|
2171
|
+
}
|
|
2172
|
+
case "SELECT_OPTION" /* SELECT_OPTION */: {
|
|
2173
|
+
let id;
|
|
2174
|
+
if (command.target.a11yData) {
|
|
2175
|
+
id = (_c = command.target.a11yData) == null ? void 0 : _c.id;
|
|
2176
|
+
} else {
|
|
2177
|
+
const locator = await this.locateElement(
|
|
2178
|
+
command.target.elementDescriptor,
|
|
2179
|
+
disableCache
|
|
2311
2180
|
);
|
|
2312
|
-
|
|
2313
|
-
succeedImmediately: false,
|
|
2314
|
-
urlAfterCommand: this.browser.url,
|
|
2315
|
-
elementInteracted
|
|
2316
|
-
};
|
|
2181
|
+
id = locator.id;
|
|
2317
2182
|
}
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2183
|
+
const elementInteracted = await this.browser.selectOption(
|
|
2184
|
+
{
|
|
2185
|
+
id
|
|
2186
|
+
},
|
|
2187
|
+
command.option
|
|
2188
|
+
);
|
|
2189
|
+
return {
|
|
2190
|
+
succeedImmediately: false,
|
|
2191
|
+
urlAfterCommand: this.browser.url,
|
|
2192
|
+
elementInteracted
|
|
2193
|
+
};
|
|
2194
|
+
}
|
|
2195
|
+
case "TAB" /* TAB */:
|
|
2196
|
+
await this.browser.switchToPage(command.url);
|
|
2197
|
+
break;
|
|
2198
|
+
case "COOKIE" /* COOKIE */:
|
|
2199
|
+
await this.browser.setCookie(command.value);
|
|
2200
|
+
break;
|
|
2201
|
+
case "TYPE" /* TYPE */: {
|
|
2202
|
+
let elementInteracted;
|
|
2203
|
+
const target = command.target;
|
|
2204
|
+
if (target.a11yData) {
|
|
2205
|
+
elementInteracted = await this.browser.click({
|
|
2206
|
+
id: target.a11yData.id
|
|
2207
|
+
});
|
|
2208
|
+
} else if (target.elementDescriptor.length > 0) {
|
|
2209
|
+
const locator = await this.locateElement(
|
|
2210
|
+
command.target.elementDescriptor,
|
|
2211
|
+
disableCache
|
|
2212
|
+
);
|
|
2213
|
+
elementInteracted = await this.browser.click({
|
|
2214
|
+
id: locator.id
|
|
2343
2215
|
});
|
|
2344
|
-
if (command.pressEnter) {
|
|
2345
|
-
yield this.browser.press("Enter");
|
|
2346
|
-
}
|
|
2347
|
-
const result2 = {
|
|
2348
|
-
urlAfterCommand: this.browser.url,
|
|
2349
|
-
succeedImmediately: false,
|
|
2350
|
-
elementInteracted
|
|
2351
|
-
};
|
|
2352
|
-
if (urlChanged(urlBeforeCommand, result2.urlAfterCommand)) {
|
|
2353
|
-
result2.succeedImmediately = true;
|
|
2354
|
-
result2.succeedImmediatelyReason = "URL changed";
|
|
2355
|
-
}
|
|
2356
|
-
return result2;
|
|
2357
2216
|
}
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2217
|
+
await this.browser.type(command.value, {
|
|
2218
|
+
clearContent: command.clearContent,
|
|
2219
|
+
pressKeysSequentially: command.pressKeysSequentially
|
|
2220
|
+
});
|
|
2221
|
+
if (command.pressEnter) {
|
|
2222
|
+
await this.browser.press("Enter");
|
|
2223
|
+
}
|
|
2224
|
+
const result2 = {
|
|
2225
|
+
urlAfterCommand: this.browser.url,
|
|
2226
|
+
succeedImmediately: false,
|
|
2227
|
+
elementInteracted
|
|
2228
|
+
};
|
|
2229
|
+
if (urlChanged(urlBeforeCommand, result2.urlAfterCommand)) {
|
|
2230
|
+
result2.succeedImmediately = true;
|
|
2231
|
+
result2.succeedImmediatelyReason = "URL changed";
|
|
2232
|
+
}
|
|
2233
|
+
return result2;
|
|
2374
2234
|
}
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2235
|
+
case "PRESS" /* PRESS */:
|
|
2236
|
+
await this.browser.press(command.value);
|
|
2237
|
+
const result = {
|
|
2238
|
+
urlAfterCommand: this.browser.url,
|
|
2239
|
+
succeedImmediately: false
|
|
2240
|
+
};
|
|
2241
|
+
if (urlChanged(urlBeforeCommand, result.urlAfterCommand)) {
|
|
2242
|
+
result.succeedImmediately = true;
|
|
2243
|
+
result.succeedImmediatelyReason = "URL changed";
|
|
2244
|
+
}
|
|
2245
|
+
return result;
|
|
2246
|
+
default:
|
|
2247
|
+
const assertUnreachable = (_x) => {
|
|
2248
|
+
throw "If Typescript complains about the line below, you missed a case or break in the switch above";
|
|
2249
|
+
};
|
|
2250
|
+
return assertUnreachable(command);
|
|
2251
|
+
}
|
|
2252
|
+
return {
|
|
2253
|
+
succeedImmediately: false,
|
|
2254
|
+
urlAfterCommand: this.browser.url
|
|
2255
|
+
};
|
|
2380
2256
|
}
|
|
2381
2257
|
};
|
|
2382
2258
|
|
|
2383
2259
|
// ../../packages/web-agent/src/generators/api-generator.ts
|
|
2384
|
-
|
|
2385
|
-
var fetch2 = (
|
|
2260
|
+
import fetchRetry from "fetch-retry";
|
|
2261
|
+
var fetch2 = fetchRetry(global.fetch);
|
|
2386
2262
|
var API_VERSION = "v1";
|
|
2387
2263
|
var APIGenerator = class {
|
|
2264
|
+
baseURL;
|
|
2265
|
+
apiKey;
|
|
2388
2266
|
constructor(params) {
|
|
2389
2267
|
this.baseURL = params.baseURL;
|
|
2390
2268
|
this.apiKey = params.apiKey;
|
|
2391
2269
|
}
|
|
2392
|
-
getElementLocation(context, disableCache) {
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
disableCache
|
|
2400
|
-
}
|
|
2401
|
-
);
|
|
2402
|
-
return LocateResponseSchema.parse(result);
|
|
2403
|
-
});
|
|
2404
|
-
}
|
|
2405
|
-
getAssertionResult(context, useVision, disableCache) {
|
|
2406
|
-
return __async(this, null, function* () {
|
|
2407
|
-
var _a;
|
|
2408
|
-
if (useVision) {
|
|
2409
|
-
const result2 = yield this.sendRequest(
|
|
2410
|
-
`/${API_VERSION}/web-agent/assertion`,
|
|
2411
|
-
{
|
|
2412
|
-
url: context.url,
|
|
2413
|
-
goal: context.goal,
|
|
2414
|
-
screenshot: (_a = context.screenshot) == null ? void 0 : _a.toString("base64"),
|
|
2415
|
-
disableCache,
|
|
2416
|
-
vision: true
|
|
2417
|
-
}
|
|
2418
|
-
);
|
|
2419
|
-
return GetAssertionResponseSchema.parse(result2);
|
|
2270
|
+
async getElementLocation(context, disableCache) {
|
|
2271
|
+
const result = await this.sendRequest(
|
|
2272
|
+
`/${API_VERSION}/web-agent/locate-element`,
|
|
2273
|
+
{
|
|
2274
|
+
browserState: context.browserState,
|
|
2275
|
+
goal: context.goal,
|
|
2276
|
+
disableCache
|
|
2420
2277
|
}
|
|
2421
|
-
|
|
2278
|
+
);
|
|
2279
|
+
return LocateResponseSchema.parse(result);
|
|
2280
|
+
}
|
|
2281
|
+
async getAssertionResult(context, useVision, disableCache) {
|
|
2282
|
+
var _a;
|
|
2283
|
+
if (useVision) {
|
|
2284
|
+
const result2 = await this.sendRequest(
|
|
2422
2285
|
`/${API_VERSION}/web-agent/assertion`,
|
|
2423
2286
|
{
|
|
2424
2287
|
url: context.url,
|
|
2425
|
-
browserState: context.browserState,
|
|
2426
2288
|
goal: context.goal,
|
|
2427
|
-
|
|
2428
|
-
numPrevious: context.numPrevious,
|
|
2429
|
-
lastCommand: context.lastCommand,
|
|
2289
|
+
screenshot: (_a = context.screenshot) == null ? void 0 : _a.toString("base64"),
|
|
2430
2290
|
disableCache,
|
|
2431
|
-
vision:
|
|
2432
|
-
}
|
|
2433
|
-
);
|
|
2434
|
-
return GetAssertionResponseSchema.parse(result);
|
|
2435
|
-
});
|
|
2436
|
-
}
|
|
2437
|
-
getProposedCommand(context, disableCache) {
|
|
2438
|
-
return __async(this, null, function* () {
|
|
2439
|
-
const result = yield this.sendRequest(
|
|
2440
|
-
`/${API_VERSION}/web-agent/next-command`,
|
|
2441
|
-
{
|
|
2442
|
-
url: context.url,
|
|
2443
|
-
browserState: context.browserState,
|
|
2444
|
-
goal: context.goal,
|
|
2445
|
-
history: context.history,
|
|
2446
|
-
numPrevious: context.numPrevious,
|
|
2447
|
-
lastCommand: context.lastCommand,
|
|
2448
|
-
disableCache
|
|
2449
|
-
}
|
|
2450
|
-
);
|
|
2451
|
-
return GetNextCommandResponseSchema.parse(result);
|
|
2452
|
-
});
|
|
2453
|
-
}
|
|
2454
|
-
getGranularGoals(context, disableCache) {
|
|
2455
|
-
return __async(this, null, function* () {
|
|
2456
|
-
const result = yield this.sendRequest(
|
|
2457
|
-
`/${API_VERSION}/web-agent/split-goal`,
|
|
2458
|
-
{
|
|
2459
|
-
url: context.url,
|
|
2460
|
-
goal: context.goal,
|
|
2461
|
-
disableCache
|
|
2291
|
+
vision: true
|
|
2462
2292
|
}
|
|
2463
2293
|
);
|
|
2464
|
-
return
|
|
2465
|
-
}
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2294
|
+
return GetAssertionResponseSchema.parse(result2);
|
|
2295
|
+
}
|
|
2296
|
+
const result = await this.sendRequest(
|
|
2297
|
+
`/${API_VERSION}/web-agent/assertion`,
|
|
2298
|
+
{
|
|
2299
|
+
url: context.url,
|
|
2300
|
+
browserState: context.browserState,
|
|
2301
|
+
goal: context.goal,
|
|
2302
|
+
history: context.history,
|
|
2303
|
+
numPrevious: context.numPrevious,
|
|
2304
|
+
lastCommand: context.lastCommand,
|
|
2305
|
+
disableCache,
|
|
2306
|
+
vision: false
|
|
2307
|
+
}
|
|
2308
|
+
);
|
|
2309
|
+
return GetAssertionResponseSchema.parse(result);
|
|
2310
|
+
}
|
|
2311
|
+
async getProposedCommand(context, disableCache) {
|
|
2312
|
+
const result = await this.sendRequest(
|
|
2313
|
+
`/${API_VERSION}/web-agent/next-command`,
|
|
2314
|
+
{
|
|
2315
|
+
url: context.url,
|
|
2316
|
+
browserState: context.browserState,
|
|
2317
|
+
goal: context.goal,
|
|
2318
|
+
history: context.history,
|
|
2319
|
+
numPrevious: context.numPrevious,
|
|
2320
|
+
lastCommand: context.lastCommand,
|
|
2321
|
+
disableCache
|
|
2322
|
+
}
|
|
2323
|
+
);
|
|
2324
|
+
return GetNextCommandResponseSchema.parse(result);
|
|
2325
|
+
}
|
|
2326
|
+
async getGranularGoals(context, disableCache) {
|
|
2327
|
+
const result = await this.sendRequest(
|
|
2328
|
+
`/${API_VERSION}/web-agent/split-goal`,
|
|
2329
|
+
{
|
|
2330
|
+
url: context.url,
|
|
2331
|
+
goal: context.goal,
|
|
2332
|
+
disableCache
|
|
2333
|
+
}
|
|
2334
|
+
);
|
|
2335
|
+
return SplitGoalResponseSchema.parse(result);
|
|
2336
|
+
}
|
|
2337
|
+
async sendRequest(path, body) {
|
|
2338
|
+
const response = await fetch2(`${this.baseURL}${path}`, {
|
|
2339
|
+
retries: 3,
|
|
2340
|
+
retryDelay: 1e3,
|
|
2341
|
+
method: "POST",
|
|
2342
|
+
body: JSON.stringify(body),
|
|
2343
|
+
headers: {
|
|
2344
|
+
"Content-Type": "application/json",
|
|
2345
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
2483
2346
|
}
|
|
2484
|
-
return response.json();
|
|
2485
2347
|
});
|
|
2348
|
+
if (!response.ok) {
|
|
2349
|
+
throw new Error(
|
|
2350
|
+
`Request to ${path} failed with status ${response.status}: ${await response.text()}`
|
|
2351
|
+
);
|
|
2352
|
+
}
|
|
2353
|
+
return response.json();
|
|
2486
2354
|
}
|
|
2487
2355
|
};
|
|
2488
2356
|
|
|
@@ -2492,72 +2360,62 @@ var version = "1.0.0";
|
|
|
2492
2360
|
// src/api-client.ts
|
|
2493
2361
|
var API_VERSION2 = "v1";
|
|
2494
2362
|
var APIClient = class {
|
|
2363
|
+
baseURL;
|
|
2364
|
+
apiKey;
|
|
2495
2365
|
constructor(params) {
|
|
2496
2366
|
this.baseURL = params.baseURL;
|
|
2497
2367
|
this.apiKey = params.apiKey;
|
|
2498
2368
|
}
|
|
2499
|
-
getRun(runId) {
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
method: "GET"
|
|
2503
|
-
});
|
|
2504
|
-
return GetRunResponseSchema.parse(result);
|
|
2369
|
+
async getRun(runId) {
|
|
2370
|
+
const result = await this.sendRequest(`/${API_VERSION2}/runs/${runId}`, {
|
|
2371
|
+
method: "GET"
|
|
2505
2372
|
});
|
|
2373
|
+
return GetRunResponseSchema.parse(result);
|
|
2506
2374
|
}
|
|
2507
|
-
createRun(body) {
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
body
|
|
2512
|
-
});
|
|
2513
|
-
return CreateRunResponseSchema.parse(result);
|
|
2375
|
+
async createRun(body) {
|
|
2376
|
+
const result = await this.sendRequest(`/${API_VERSION2}/runs`, {
|
|
2377
|
+
method: "POST",
|
|
2378
|
+
body
|
|
2514
2379
|
});
|
|
2380
|
+
return CreateRunResponseSchema.parse(result);
|
|
2515
2381
|
}
|
|
2516
|
-
updateRun(runId, body) {
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
body
|
|
2521
|
-
});
|
|
2382
|
+
async updateRun(runId, body) {
|
|
2383
|
+
await this.sendRequest(`/${API_VERSION2}/runs/${runId}`, {
|
|
2384
|
+
method: "PATCH",
|
|
2385
|
+
body
|
|
2522
2386
|
});
|
|
2523
2387
|
}
|
|
2524
|
-
getTest(testId) {
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
method: "GET"
|
|
2528
|
-
});
|
|
2529
|
-
return GetTestResponseSchema.parse(result);
|
|
2388
|
+
async getTest(testId) {
|
|
2389
|
+
const result = await this.sendRequest(`/${API_VERSION2}/tests/${testId}`, {
|
|
2390
|
+
method: "GET"
|
|
2530
2391
|
});
|
|
2392
|
+
return GetTestResponseSchema.parse(result);
|
|
2531
2393
|
}
|
|
2532
|
-
uploadScreenshot(body) {
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
body
|
|
2537
|
-
});
|
|
2538
|
-
return CreateScreenshotResponseSchema.parse(result);
|
|
2394
|
+
async uploadScreenshot(body) {
|
|
2395
|
+
const result = await this.sendRequest(`/${API_VERSION2}/screenshots`, {
|
|
2396
|
+
method: "POST",
|
|
2397
|
+
body
|
|
2539
2398
|
});
|
|
2399
|
+
return CreateScreenshotResponseSchema.parse(result);
|
|
2540
2400
|
}
|
|
2541
|
-
sendRequest(path, options) {
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
Authorization: `Bearer ${this.apiKey}`
|
|
2549
|
-
}
|
|
2550
|
-
});
|
|
2551
|
-
if (!response.ok) {
|
|
2552
|
-
throw new Error(
|
|
2553
|
-
`Request to ${path} failed with status ${response.status}: ${yield response.text()}`
|
|
2554
|
-
);
|
|
2555
|
-
}
|
|
2556
|
-
if (response.status === 204) {
|
|
2557
|
-
return response.text();
|
|
2401
|
+
async sendRequest(path, options) {
|
|
2402
|
+
const response = await fetch(`${this.baseURL}${path}`, {
|
|
2403
|
+
method: options.method,
|
|
2404
|
+
body: options.body ? JSON.stringify(options.body) : void 0,
|
|
2405
|
+
headers: {
|
|
2406
|
+
"Content-Type": "application/json",
|
|
2407
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
2558
2408
|
}
|
|
2559
|
-
return response.json();
|
|
2560
2409
|
});
|
|
2410
|
+
if (!response.ok) {
|
|
2411
|
+
throw new Error(
|
|
2412
|
+
`Request to ${path} failed with status ${response.status}: ${await response.text()}`
|
|
2413
|
+
);
|
|
2414
|
+
}
|
|
2415
|
+
if (response.status === 204) {
|
|
2416
|
+
return response.text();
|
|
2417
|
+
}
|
|
2418
|
+
return response.json();
|
|
2561
2419
|
}
|
|
2562
2420
|
};
|
|
2563
2421
|
|
|
@@ -2565,29 +2423,25 @@ var APIClient = class {
|
|
|
2565
2423
|
var MAX_COMMANDS_PER_STEP = 20;
|
|
2566
2424
|
|
|
2567
2425
|
// ../../packages/execute/src/steps/ai.ts
|
|
2568
|
-
var executeAIStep =
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
"logger",
|
|
2578
|
-
"advanced"
|
|
2579
|
-
]);
|
|
2580
|
-
var _a2, _b2, _c, _d, _e, _f, _g;
|
|
2581
|
-
(_a2 = callbacks.onStarted) == null ? void 0 : _a2.call(callbacks);
|
|
2426
|
+
var executeAIStep = async ({
|
|
2427
|
+
controller,
|
|
2428
|
+
step,
|
|
2429
|
+
logger,
|
|
2430
|
+
advanced,
|
|
2431
|
+
...callbacks
|
|
2432
|
+
}) => {
|
|
2433
|
+
var _a, _b, _c, _d, _e, _f;
|
|
2434
|
+
(_a = callbacks.onStarted) == null ? void 0 : _a.call(callbacks);
|
|
2582
2435
|
controller.resetHistory();
|
|
2583
|
-
const result =
|
|
2436
|
+
const result = {
|
|
2437
|
+
...step,
|
|
2584
2438
|
startedAt: /* @__PURE__ */ new Date(),
|
|
2585
2439
|
userAgent: ChromeBrowser.USER_AGENT,
|
|
2586
2440
|
// placeholder values
|
|
2587
2441
|
finishedAt: /* @__PURE__ */ new Date(),
|
|
2588
2442
|
results: [],
|
|
2589
2443
|
status: "SUCCESS" /* SUCCESS */
|
|
2590
|
-
}
|
|
2444
|
+
};
|
|
2591
2445
|
try {
|
|
2592
2446
|
let commandIndex = 0;
|
|
2593
2447
|
let useSavedCommands = step.commands && step.commands.length > 0;
|
|
@@ -2599,8 +2453,8 @@ var executeAIStep = (_a) => __async(void 0, null, function* () {
|
|
|
2599
2453
|
}
|
|
2600
2454
|
let command;
|
|
2601
2455
|
const startedAt = /* @__PURE__ */ new Date();
|
|
2602
|
-
const beforeScreenshotBuffer =
|
|
2603
|
-
const beforeScreenshot =
|
|
2456
|
+
const beforeScreenshotBuffer = await controller.browser.screenshot();
|
|
2457
|
+
const beforeScreenshot = await callbacks.onSaveScreenshot(
|
|
2604
2458
|
beforeScreenshotBuffer
|
|
2605
2459
|
);
|
|
2606
2460
|
if (useSavedCommands) {
|
|
@@ -2611,7 +2465,7 @@ var executeAIStep = (_a) => __async(void 0, null, function* () {
|
|
|
2611
2465
|
);
|
|
2612
2466
|
}
|
|
2613
2467
|
} else {
|
|
2614
|
-
command =
|
|
2468
|
+
command = await controller.promptToCommand(
|
|
2615
2469
|
step.type,
|
|
2616
2470
|
step.text,
|
|
2617
2471
|
advanced.disableAICaching
|
|
@@ -2623,7 +2477,7 @@ var executeAIStep = (_a) => __async(void 0, null, function* () {
|
|
|
2623
2477
|
result.message = command.thoughts;
|
|
2624
2478
|
break;
|
|
2625
2479
|
}
|
|
2626
|
-
(
|
|
2480
|
+
(_b = callbacks.onCommandGenerated) == null ? void 0 : _b.call(callbacks, {
|
|
2627
2481
|
commandIndex,
|
|
2628
2482
|
message: CARD_DISPLAY_NAMES[command.type] || `Unknown command (${command.type})`
|
|
2629
2483
|
});
|
|
@@ -2640,7 +2494,7 @@ var executeAIStep = (_a) => __async(void 0, null, function* () {
|
|
|
2640
2494
|
`Executing command ${commandIndex}: ${serializeCommand(command)}`
|
|
2641
2495
|
);
|
|
2642
2496
|
try {
|
|
2643
|
-
const executionResult =
|
|
2497
|
+
const executionResult = await controller.executeCommand(
|
|
2644
2498
|
command,
|
|
2645
2499
|
advanced.disableAICaching,
|
|
2646
2500
|
useSavedCommands
|
|
@@ -2651,8 +2505,8 @@ var executeAIStep = (_a) => __async(void 0, null, function* () {
|
|
|
2651
2505
|
message: serializeCommand(command),
|
|
2652
2506
|
command
|
|
2653
2507
|
});
|
|
2654
|
-
const afterScreenshotBuffer =
|
|
2655
|
-
const afterScreenshot =
|
|
2508
|
+
const afterScreenshotBuffer = await controller.browser.screenshot();
|
|
2509
|
+
const afterScreenshot = await callbacks.onSaveScreenshot(
|
|
2656
2510
|
afterScreenshotBuffer
|
|
2657
2511
|
);
|
|
2658
2512
|
cmdResult.afterScreenshot = afterScreenshot;
|
|
@@ -2670,7 +2524,7 @@ var executeAIStep = (_a) => __async(void 0, null, function* () {
|
|
|
2670
2524
|
if (command.type === "SUCCESS" /* SUCCESS */) {
|
|
2671
2525
|
result.finishedAt = /* @__PURE__ */ new Date();
|
|
2672
2526
|
result.status = "SUCCESS" /* SUCCESS */;
|
|
2673
|
-
result.message =
|
|
2527
|
+
result.message = executionResult.thoughts ?? "All commands completed.";
|
|
2674
2528
|
break;
|
|
2675
2529
|
}
|
|
2676
2530
|
if (executionResult.succeedImmediately && !useSavedCommands) {
|
|
@@ -2680,14 +2534,15 @@ var executeAIStep = (_a) => __async(void 0, null, function* () {
|
|
|
2680
2534
|
command = {
|
|
2681
2535
|
type: "SUCCESS" /* SUCCESS */
|
|
2682
2536
|
};
|
|
2683
|
-
(
|
|
2537
|
+
(_d = callbacks.onCommandExecuted) == null ? void 0 : _d.call(callbacks, {
|
|
2684
2538
|
commandIndex: commandIndex + 1,
|
|
2685
2539
|
message: serializeCommand(command),
|
|
2686
2540
|
command
|
|
2687
2541
|
});
|
|
2688
|
-
result.results.push(
|
|
2542
|
+
result.results.push({
|
|
2543
|
+
...presetActionResult,
|
|
2689
2544
|
command
|
|
2690
|
-
})
|
|
2545
|
+
});
|
|
2691
2546
|
break;
|
|
2692
2547
|
}
|
|
2693
2548
|
} catch (err) {
|
|
@@ -2724,57 +2579,54 @@ var executeAIStep = (_a) => __async(void 0, null, function* () {
|
|
|
2724
2579
|
result.status = "FAILED" /* FAILED */;
|
|
2725
2580
|
}
|
|
2726
2581
|
if (result.status === "SUCCESS" /* SUCCESS */) {
|
|
2727
|
-
(
|
|
2582
|
+
(_e = callbacks.onSuccess) == null ? void 0 : _e.call(callbacks, {
|
|
2728
2583
|
message: result.message || "AI step succeeded.",
|
|
2729
2584
|
startedAt: result.startedAt.getTime(),
|
|
2730
2585
|
durationMs: result.finishedAt.getTime() - result.startedAt.getTime()
|
|
2731
2586
|
});
|
|
2732
2587
|
} else {
|
|
2733
|
-
(
|
|
2588
|
+
(_f = callbacks.onFailure) == null ? void 0 : _f.call(callbacks, {
|
|
2734
2589
|
message: result.message || "AI step errored.",
|
|
2735
2590
|
startedAt: result.startedAt.getTime(),
|
|
2736
2591
|
durationMs: result.finishedAt.getTime() - result.startedAt.getTime()
|
|
2737
2592
|
});
|
|
2738
2593
|
}
|
|
2739
2594
|
return result;
|
|
2740
|
-
}
|
|
2595
|
+
};
|
|
2741
2596
|
|
|
2742
2597
|
// ../../packages/execute/src/steps/preset.ts
|
|
2743
|
-
var executePresetStep =
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
"advanced"
|
|
2752
|
-
]);
|
|
2753
|
-
var _a2, _b2, _c;
|
|
2754
|
-
(_a2 = callbacks.onStarted) == null ? void 0 : _a2.call(callbacks);
|
|
2598
|
+
var executePresetStep = async ({
|
|
2599
|
+
controller,
|
|
2600
|
+
step,
|
|
2601
|
+
advanced,
|
|
2602
|
+
...callbacks
|
|
2603
|
+
}) => {
|
|
2604
|
+
var _a, _b, _c;
|
|
2605
|
+
(_a = callbacks.onStarted) == null ? void 0 : _a.call(callbacks);
|
|
2755
2606
|
const startedAt = /* @__PURE__ */ new Date();
|
|
2756
2607
|
const beforeUrl = controller.browser.url;
|
|
2757
|
-
const beforeScreenshotBuffer =
|
|
2758
|
-
const beforeScreenshot =
|
|
2608
|
+
const beforeScreenshotBuffer = await controller.browser.screenshot();
|
|
2609
|
+
const beforeScreenshot = await callbacks.onSaveScreenshot(
|
|
2759
2610
|
beforeScreenshotBuffer
|
|
2760
2611
|
);
|
|
2761
2612
|
try {
|
|
2762
|
-
const execResult =
|
|
2613
|
+
const execResult = await controller.executePresetStep(
|
|
2763
2614
|
step.command,
|
|
2764
2615
|
advanced.disableAICaching
|
|
2765
2616
|
);
|
|
2766
|
-
const afterScreenshotBuffer =
|
|
2767
|
-
const afterScreenshot =
|
|
2617
|
+
const afterScreenshotBuffer = await controller.browser.screenshot();
|
|
2618
|
+
const afterScreenshot = await callbacks.onSaveScreenshot(
|
|
2768
2619
|
afterScreenshotBuffer
|
|
2769
2620
|
);
|
|
2770
2621
|
const finishedAt = /* @__PURE__ */ new Date();
|
|
2771
|
-
const result =
|
|
2622
|
+
const result = {
|
|
2623
|
+
...step,
|
|
2772
2624
|
startedAt,
|
|
2773
2625
|
finishedAt,
|
|
2774
2626
|
// placeholder values
|
|
2775
2627
|
status: "SUCCESS" /* SUCCESS */,
|
|
2776
2628
|
results: []
|
|
2777
|
-
}
|
|
2629
|
+
};
|
|
2778
2630
|
let message = "Successfully executed preset action.";
|
|
2779
2631
|
if (step.command.type === "AI_ASSERTION" /* AI_ASSERTION */) {
|
|
2780
2632
|
message = execResult.thoughts || "Assertion passed.";
|
|
@@ -2792,7 +2644,7 @@ var executePresetStep = (_a) => __async(void 0, null, function* () {
|
|
|
2792
2644
|
result.status = "SUCCESS" /* SUCCESS */;
|
|
2793
2645
|
result.results = [cmdMetadata];
|
|
2794
2646
|
result.message = message;
|
|
2795
|
-
(
|
|
2647
|
+
(_b = callbacks.onSuccess) == null ? void 0 : _b.call(callbacks, {
|
|
2796
2648
|
message,
|
|
2797
2649
|
startedAt: startedAt.getTime(),
|
|
2798
2650
|
durationMs: finishedAt.getTime() - startedAt.getTime()
|
|
@@ -2800,7 +2652,8 @@ var executePresetStep = (_a) => __async(void 0, null, function* () {
|
|
|
2800
2652
|
return result;
|
|
2801
2653
|
} catch (err) {
|
|
2802
2654
|
const finishedAt = /* @__PURE__ */ new Date();
|
|
2803
|
-
const result =
|
|
2655
|
+
const result = {
|
|
2656
|
+
...step,
|
|
2804
2657
|
startedAt,
|
|
2805
2658
|
finishedAt,
|
|
2806
2659
|
status: "FAILED" /* FAILED */,
|
|
@@ -2818,7 +2671,7 @@ var executePresetStep = (_a) => __async(void 0, null, function* () {
|
|
|
2818
2671
|
message: `${err}`
|
|
2819
2672
|
}
|
|
2820
2673
|
]
|
|
2821
|
-
}
|
|
2674
|
+
};
|
|
2822
2675
|
(_c = callbacks.onFailure) == null ? void 0 : _c.call(callbacks, {
|
|
2823
2676
|
message: `${err}`,
|
|
2824
2677
|
startedAt: startedAt.getTime(),
|
|
@@ -2826,23 +2679,18 @@ var executePresetStep = (_a) => __async(void 0, null, function* () {
|
|
|
2826
2679
|
});
|
|
2827
2680
|
return result;
|
|
2828
2681
|
}
|
|
2829
|
-
}
|
|
2682
|
+
};
|
|
2830
2683
|
|
|
2831
2684
|
// ../../packages/execute/src/steps/module.ts
|
|
2832
|
-
var executeModuleStep =
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
"advanced",
|
|
2842
|
-
"logger"
|
|
2843
|
-
]);
|
|
2844
|
-
var _a2, _b2, _c;
|
|
2845
|
-
(_a2 = callbacks.onStarted) == null ? void 0 : _a2.call(callbacks);
|
|
2685
|
+
var executeModuleStep = async ({
|
|
2686
|
+
controller,
|
|
2687
|
+
step,
|
|
2688
|
+
advanced,
|
|
2689
|
+
logger,
|
|
2690
|
+
...callbacks
|
|
2691
|
+
}) => {
|
|
2692
|
+
var _a, _b, _c;
|
|
2693
|
+
(_a = callbacks.onStarted) == null ? void 0 : _a.call(callbacks);
|
|
2846
2694
|
const result = {
|
|
2847
2695
|
type: "MODULE" /* MODULE */,
|
|
2848
2696
|
moduleId: step.moduleId,
|
|
@@ -2859,19 +2707,19 @@ var executeModuleStep = (_a) => __async(void 0, null, function* () {
|
|
|
2859
2707
|
let moduleStepResult;
|
|
2860
2708
|
switch (moduleStep.type) {
|
|
2861
2709
|
case "PRESET_ACTION" /* PRESET_ACTION */:
|
|
2862
|
-
moduleStepResult =
|
|
2710
|
+
moduleStepResult = await executePresetStep({
|
|
2863
2711
|
controller,
|
|
2864
2712
|
step: moduleStep,
|
|
2865
2713
|
advanced,
|
|
2866
2714
|
logger,
|
|
2867
2715
|
onSaveScreenshot: callbacks.onSaveScreenshot,
|
|
2868
2716
|
onStarted() {
|
|
2869
|
-
var
|
|
2870
|
-
(
|
|
2717
|
+
var _a2;
|
|
2718
|
+
(_a2 = callbacks.onStepStarted) == null ? void 0 : _a2.call(callbacks, { index: i });
|
|
2871
2719
|
},
|
|
2872
2720
|
onSuccess({ message, startedAt, durationMs }) {
|
|
2873
|
-
var
|
|
2874
|
-
(
|
|
2721
|
+
var _a2;
|
|
2722
|
+
(_a2 = callbacks.onStepSuccess) == null ? void 0 : _a2.call(callbacks, {
|
|
2875
2723
|
index: i,
|
|
2876
2724
|
message,
|
|
2877
2725
|
startedAt,
|
|
@@ -2879,8 +2727,8 @@ var executeModuleStep = (_a) => __async(void 0, null, function* () {
|
|
|
2879
2727
|
});
|
|
2880
2728
|
},
|
|
2881
2729
|
onFailure({ message, startedAt, durationMs }) {
|
|
2882
|
-
var
|
|
2883
|
-
(
|
|
2730
|
+
var _a2;
|
|
2731
|
+
(_a2 = callbacks.onStepFailure) == null ? void 0 : _a2.call(callbacks, {
|
|
2884
2732
|
index: i,
|
|
2885
2733
|
message,
|
|
2886
2734
|
startedAt,
|
|
@@ -2890,19 +2738,19 @@ var executeModuleStep = (_a) => __async(void 0, null, function* () {
|
|
|
2890
2738
|
});
|
|
2891
2739
|
break;
|
|
2892
2740
|
case "AI_ACTION" /* AI_ACTION */:
|
|
2893
|
-
moduleStepResult =
|
|
2741
|
+
moduleStepResult = await executeAIStep({
|
|
2894
2742
|
controller,
|
|
2895
2743
|
step: moduleStep,
|
|
2896
2744
|
advanced,
|
|
2897
2745
|
logger,
|
|
2898
2746
|
onSaveScreenshot: callbacks.onSaveScreenshot,
|
|
2899
2747
|
onStarted() {
|
|
2900
|
-
var
|
|
2901
|
-
(
|
|
2748
|
+
var _a2;
|
|
2749
|
+
(_a2 = callbacks.onStepStarted) == null ? void 0 : _a2.call(callbacks, { index: i });
|
|
2902
2750
|
},
|
|
2903
2751
|
onSuccess({ message, startedAt, durationMs }) {
|
|
2904
|
-
var
|
|
2905
|
-
(
|
|
2752
|
+
var _a2;
|
|
2753
|
+
(_a2 = callbacks.onStepSuccess) == null ? void 0 : _a2.call(callbacks, {
|
|
2906
2754
|
index: i,
|
|
2907
2755
|
message,
|
|
2908
2756
|
startedAt,
|
|
@@ -2910,8 +2758,8 @@ var executeModuleStep = (_a) => __async(void 0, null, function* () {
|
|
|
2910
2758
|
});
|
|
2911
2759
|
},
|
|
2912
2760
|
onFailure({ message, startedAt, durationMs }) {
|
|
2913
|
-
var
|
|
2914
|
-
(
|
|
2761
|
+
var _a2;
|
|
2762
|
+
(_a2 = callbacks.onStepFailure) == null ? void 0 : _a2.call(callbacks, {
|
|
2915
2763
|
index: i,
|
|
2916
2764
|
message,
|
|
2917
2765
|
startedAt,
|
|
@@ -2919,12 +2767,12 @@ var executeModuleStep = (_a) => __async(void 0, null, function* () {
|
|
|
2919
2767
|
});
|
|
2920
2768
|
},
|
|
2921
2769
|
onCommandGenerated({ commandIndex, message }) {
|
|
2922
|
-
var
|
|
2923
|
-
(
|
|
2770
|
+
var _a2;
|
|
2771
|
+
(_a2 = callbacks.onCommandGenerated) == null ? void 0 : _a2.call(callbacks, { index: i, commandIndex, message });
|
|
2924
2772
|
},
|
|
2925
2773
|
onCommandExecuted({ commandIndex, message, command }) {
|
|
2926
|
-
var
|
|
2927
|
-
(
|
|
2774
|
+
var _a2;
|
|
2775
|
+
(_a2 = callbacks.onCommandExecuted) == null ? void 0 : _a2.call(callbacks, {
|
|
2928
2776
|
index: i,
|
|
2929
2777
|
commandIndex,
|
|
2930
2778
|
message,
|
|
@@ -2945,21 +2793,22 @@ var executeModuleStep = (_a) => __async(void 0, null, function* () {
|
|
|
2945
2793
|
result.finishedAt = /* @__PURE__ */ new Date();
|
|
2946
2794
|
for (let j = i + 1; j < step.steps.length; j++) {
|
|
2947
2795
|
const skippedStep = step.steps[j];
|
|
2948
|
-
const skippedResult =
|
|
2796
|
+
const skippedResult = {
|
|
2797
|
+
...skippedStep,
|
|
2949
2798
|
status: "CANCELLED" /* CANCELLED */,
|
|
2950
2799
|
startedAt: /* @__PURE__ */ new Date(),
|
|
2951
2800
|
finishedAt: /* @__PURE__ */ new Date(),
|
|
2952
2801
|
userAgent: ChromeBrowser.USER_AGENT,
|
|
2953
2802
|
results: [],
|
|
2954
2803
|
message: "Cancelled due to previous failure."
|
|
2955
|
-
}
|
|
2804
|
+
};
|
|
2956
2805
|
result.results.push(skippedResult);
|
|
2957
2806
|
}
|
|
2958
2807
|
break;
|
|
2959
2808
|
}
|
|
2960
2809
|
}
|
|
2961
2810
|
if (result.status === "SUCCESS" /* SUCCESS */) {
|
|
2962
|
-
(
|
|
2811
|
+
(_b = callbacks.onSuccess) == null ? void 0 : _b.call(callbacks, {
|
|
2963
2812
|
message: "Executed module step.",
|
|
2964
2813
|
startedAt: result.startedAt.getTime(),
|
|
2965
2814
|
durationMs: result.finishedAt.getTime() - result.startedAt.getTime()
|
|
@@ -2972,20 +2821,20 @@ var executeModuleStep = (_a) => __async(void 0, null, function* () {
|
|
|
2972
2821
|
});
|
|
2973
2822
|
}
|
|
2974
2823
|
return result;
|
|
2975
|
-
}
|
|
2824
|
+
};
|
|
2976
2825
|
|
|
2977
2826
|
// ../../packages/execute/src/test.ts
|
|
2978
|
-
var executeTest =
|
|
2827
|
+
var executeTest = async ({
|
|
2979
2828
|
test,
|
|
2980
2829
|
runId,
|
|
2981
2830
|
controller,
|
|
2982
2831
|
logger,
|
|
2983
2832
|
onUpdateRun,
|
|
2984
2833
|
onSaveScreenshot
|
|
2985
|
-
}) {
|
|
2834
|
+
}) => {
|
|
2986
2835
|
const advanced = TestAdvancedSettingsSchema.parse(test.advanced);
|
|
2987
2836
|
logger.info(`Starting run ${runId} for test ${test.id}`);
|
|
2988
|
-
|
|
2837
|
+
await onUpdateRun({
|
|
2989
2838
|
status: "RUNNING",
|
|
2990
2839
|
startedAt: /* @__PURE__ */ new Date()
|
|
2991
2840
|
});
|
|
@@ -2996,7 +2845,7 @@ var executeTest = (_0) => __async(void 0, [_0], function* ({
|
|
|
2996
2845
|
let result;
|
|
2997
2846
|
switch (step.type) {
|
|
2998
2847
|
case "PRESET_ACTION" /* PRESET_ACTION */:
|
|
2999
|
-
result =
|
|
2848
|
+
result = await executePresetStep({
|
|
3000
2849
|
controller,
|
|
3001
2850
|
step,
|
|
3002
2851
|
advanced,
|
|
@@ -3005,7 +2854,7 @@ var executeTest = (_0) => __async(void 0, [_0], function* ({
|
|
|
3005
2854
|
});
|
|
3006
2855
|
break;
|
|
3007
2856
|
case "AI_ACTION" /* AI_ACTION */:
|
|
3008
|
-
result =
|
|
2857
|
+
result = await executeAIStep({
|
|
3009
2858
|
controller,
|
|
3010
2859
|
step,
|
|
3011
2860
|
advanced,
|
|
@@ -3014,7 +2863,7 @@ var executeTest = (_0) => __async(void 0, [_0], function* ({
|
|
|
3014
2863
|
});
|
|
3015
2864
|
break;
|
|
3016
2865
|
case "RESOLVED_MODULE":
|
|
3017
|
-
result =
|
|
2866
|
+
result = await executeModuleStep({
|
|
3018
2867
|
controller,
|
|
3019
2868
|
step,
|
|
3020
2869
|
advanced,
|
|
@@ -3029,7 +2878,7 @@ var executeTest = (_0) => __async(void 0, [_0], function* ({
|
|
|
3029
2878
|
return assertUnreachable(step);
|
|
3030
2879
|
}
|
|
3031
2880
|
results.push(result);
|
|
3032
|
-
|
|
2881
|
+
await onUpdateRun({
|
|
3033
2882
|
results
|
|
3034
2883
|
});
|
|
3035
2884
|
if (result.status === "FAILED" /* FAILED */) {
|
|
@@ -3043,26 +2892,28 @@ var executeTest = (_0) => __async(void 0, [_0], function* ({
|
|
|
3043
2892
|
startedAt: /* @__PURE__ */ new Date(),
|
|
3044
2893
|
userAgent: ChromeBrowser.USER_AGENT,
|
|
3045
2894
|
results: skippedStep.steps.map((s) => {
|
|
3046
|
-
return
|
|
2895
|
+
return {
|
|
2896
|
+
...s,
|
|
3047
2897
|
status: "CANCELLED" /* CANCELLED */,
|
|
3048
2898
|
startedAt: /* @__PURE__ */ new Date(),
|
|
3049
2899
|
finishedAt: /* @__PURE__ */ new Date(),
|
|
3050
2900
|
userAgent: ChromeBrowser.USER_AGENT,
|
|
3051
2901
|
results: []
|
|
3052
|
-
}
|
|
2902
|
+
};
|
|
3053
2903
|
}),
|
|
3054
2904
|
finishedAt: /* @__PURE__ */ new Date(),
|
|
3055
2905
|
status: "CANCELLED" /* CANCELLED */
|
|
3056
2906
|
};
|
|
3057
2907
|
results.push(skippedResult);
|
|
3058
2908
|
} else {
|
|
3059
|
-
const skippedResult =
|
|
2909
|
+
const skippedResult = {
|
|
2910
|
+
...skippedStep,
|
|
3060
2911
|
status: "CANCELLED" /* CANCELLED */,
|
|
3061
2912
|
startedAt: /* @__PURE__ */ new Date(),
|
|
3062
2913
|
finishedAt: /* @__PURE__ */ new Date(),
|
|
3063
2914
|
userAgent: ChromeBrowser.USER_AGENT,
|
|
3064
2915
|
results: []
|
|
3065
|
-
}
|
|
2916
|
+
};
|
|
3066
2917
|
results.push(skippedResult);
|
|
3067
2918
|
}
|
|
3068
2919
|
}
|
|
@@ -3071,14 +2922,14 @@ var executeTest = (_0) => __async(void 0, [_0], function* ({
|
|
|
3071
2922
|
break;
|
|
3072
2923
|
}
|
|
3073
2924
|
}
|
|
3074
|
-
|
|
2925
|
+
await onUpdateRun({
|
|
3075
2926
|
status: failed ? "FAILED" : "PASSED",
|
|
3076
2927
|
finishedAt: /* @__PURE__ */ new Date(),
|
|
3077
2928
|
results
|
|
3078
2929
|
});
|
|
3079
|
-
|
|
2930
|
+
await controller.browser.cleanup();
|
|
3080
2931
|
return failed;
|
|
3081
|
-
}
|
|
2932
|
+
};
|
|
3082
2933
|
|
|
3083
2934
|
// src/run-test.ts
|
|
3084
2935
|
var consoleLogger = {
|
|
@@ -3090,79 +2941,77 @@ var consoleLogger = {
|
|
|
3090
2941
|
flush: () => {
|
|
3091
2942
|
}
|
|
3092
2943
|
};
|
|
3093
|
-
function runTest(
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
2944
|
+
async function runTest({
|
|
2945
|
+
testId,
|
|
2946
|
+
apiClient,
|
|
2947
|
+
generator
|
|
2948
|
+
}) {
|
|
2949
|
+
const test = await apiClient.getTest(testId);
|
|
2950
|
+
const browser = await ChromeBrowser.init(test.baseUrl, consoleLogger);
|
|
2951
|
+
const controller = new AgentController({
|
|
2952
|
+
browser,
|
|
2953
|
+
generator,
|
|
2954
|
+
config: DEFAULT_CONTROLLER_CONFIG,
|
|
2955
|
+
logger: consoleLogger
|
|
2956
|
+
});
|
|
2957
|
+
const run = await apiClient.createRun({
|
|
2958
|
+
testId
|
|
2959
|
+
});
|
|
2960
|
+
let failed = true;
|
|
2961
|
+
try {
|
|
2962
|
+
failed = await executeTest({
|
|
2963
|
+
test,
|
|
2964
|
+
runId: run.id,
|
|
2965
|
+
controller,
|
|
2966
|
+
logger: consoleLogger,
|
|
2967
|
+
onSaveScreenshot: async (buffer) => {
|
|
2968
|
+
const { key } = await apiClient.uploadScreenshot({
|
|
2969
|
+
screenshot: buffer.toString("base64")
|
|
2970
|
+
});
|
|
2971
|
+
return key;
|
|
2972
|
+
},
|
|
2973
|
+
onUpdateRun: async (data) => {
|
|
2974
|
+
await apiClient.updateRun(run.id, data);
|
|
2975
|
+
}
|
|
3106
2976
|
});
|
|
3107
|
-
|
|
3108
|
-
|
|
2977
|
+
} catch (err) {
|
|
2978
|
+
await apiClient.updateRun(run.id, {
|
|
2979
|
+
status: "FAILED",
|
|
2980
|
+
finishedAt: /* @__PURE__ */ new Date()
|
|
3109
2981
|
});
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
failed = yield executeTest({
|
|
3113
|
-
test,
|
|
3114
|
-
runId: run.id,
|
|
3115
|
-
controller,
|
|
3116
|
-
logger: consoleLogger,
|
|
3117
|
-
onSaveScreenshot: (buffer) => __async(this, null, function* () {
|
|
3118
|
-
const { key } = yield apiClient.uploadScreenshot({
|
|
3119
|
-
screenshot: buffer.toString("base64")
|
|
3120
|
-
});
|
|
3121
|
-
return key;
|
|
3122
|
-
}),
|
|
3123
|
-
onUpdateRun: (data) => __async(this, null, function* () {
|
|
3124
|
-
yield apiClient.updateRun(run.id, data);
|
|
3125
|
-
})
|
|
3126
|
-
});
|
|
3127
|
-
} catch (err) {
|
|
3128
|
-
yield apiClient.updateRun(run.id, {
|
|
3129
|
-
status: "FAILED",
|
|
3130
|
-
finishedAt: /* @__PURE__ */ new Date()
|
|
3131
|
-
});
|
|
3132
|
-
}
|
|
3133
|
-
return failed;
|
|
3134
|
-
});
|
|
2982
|
+
}
|
|
2983
|
+
return failed;
|
|
3135
2984
|
}
|
|
3136
2985
|
|
|
3137
2986
|
// src/cli.ts
|
|
3138
|
-
var program = new
|
|
2987
|
+
var program = new Command4();
|
|
3139
2988
|
program.name("momentic").description("Momentic CLI").version(version);
|
|
3140
2989
|
program.command("run-tests").addOption(
|
|
3141
|
-
new
|
|
2990
|
+
new Option(
|
|
3142
2991
|
"--tests <tests...>",
|
|
3143
2992
|
"specify tests to run"
|
|
3144
2993
|
).makeOptionMandatory(true)
|
|
3145
2994
|
).addOption(
|
|
3146
|
-
new
|
|
2995
|
+
new Option(
|
|
3147
2996
|
"--start <command>",
|
|
3148
2997
|
"specify start command"
|
|
3149
2998
|
).makeOptionMandatory(true)
|
|
3150
2999
|
).addOption(
|
|
3151
|
-
new
|
|
3000
|
+
new Option("--wait-on <url>", "specify url to wait on").makeOptionMandatory(
|
|
3152
3001
|
true
|
|
3153
3002
|
)
|
|
3154
3003
|
).addOption(
|
|
3155
|
-
new
|
|
3004
|
+
new Option(
|
|
3156
3005
|
"--wait-on-timeout <timeout>",
|
|
3157
3006
|
"specify how long to wait on url"
|
|
3158
3007
|
).default(60, "one minute")
|
|
3159
3008
|
).addOption(
|
|
3160
|
-
new
|
|
3161
|
-
).action((options) =>
|
|
3009
|
+
new Option("--api-key <key>", "API key for authenticating").env("MOMENTIC_API_KEY").makeOptionMandatory(true)
|
|
3010
|
+
).action(async (options) => {
|
|
3162
3011
|
const { tests, start, waitOn, waitOnTimeout, apiKey } = options;
|
|
3163
3012
|
console.log({ tests, start, waitOn, waitOnTimeout, apiKey });
|
|
3164
|
-
void
|
|
3165
|
-
|
|
3013
|
+
void execa(start);
|
|
3014
|
+
await waitOnFn({
|
|
3166
3015
|
resources: [waitOn],
|
|
3167
3016
|
timeout: waitOnTimeout * 1e3
|
|
3168
3017
|
});
|
|
@@ -3182,24 +3031,22 @@ program.command("run-tests").addOption(
|
|
|
3182
3031
|
});
|
|
3183
3032
|
return { failed, testId };
|
|
3184
3033
|
});
|
|
3185
|
-
const results =
|
|
3034
|
+
const results = await Promise.all(promises);
|
|
3186
3035
|
const failedResults = results.filter((result) => result.failed);
|
|
3187
3036
|
if (failedResults.length > 0) {
|
|
3188
3037
|
console.log(
|
|
3189
|
-
|
|
3038
|
+
chalk.red(
|
|
3190
3039
|
`Failed ${failedResults.length} out of ${results.length} tests`
|
|
3191
3040
|
)
|
|
3192
3041
|
);
|
|
3193
3042
|
failedResults.forEach((result) => {
|
|
3194
|
-
console.log(
|
|
3043
|
+
console.log(chalk.red(`- ${result.testId}`));
|
|
3195
3044
|
});
|
|
3196
3045
|
process.exit(1);
|
|
3197
3046
|
}
|
|
3198
|
-
console.log(
|
|
3199
|
-
})
|
|
3200
|
-
function main() {
|
|
3201
|
-
|
|
3202
|
-
yield program.parseAsync(process.argv);
|
|
3203
|
-
});
|
|
3047
|
+
console.log(chalk.green(`All ${results.length} tests passed!`));
|
|
3048
|
+
});
|
|
3049
|
+
async function main() {
|
|
3050
|
+
await program.parseAsync(process.argv);
|
|
3204
3051
|
}
|
|
3205
3052
|
void main();
|