testdriverai 6.0.16-canary.ea858d7.0 → 6.0.16
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/agent/events.js +12 -2
- package/agent/index.js +34 -33
- package/agent/lib/censorship.js +70 -0
- package/agent/lib/commander.js +25 -16
- package/agent/lib/commands.js +23 -19
- package/debugger/index.html +2 -0
- package/docs/_scripts/link-replacer.js +164 -0
- package/docs/account/dashboard.mdx +5 -2
- package/docs/account/pricing.mdx +12 -5
- package/docs/account/projects.mdx +3 -2
- package/docs/account/team.mdx +7 -3
- package/docs/apps/chrome-extensions.mdx +8 -2
- package/docs/apps/desktop-apps.mdx +5 -5
- package/docs/apps/mobile-apps.mdx +7 -2
- package/docs/cli/overview.mdx +6 -6
- package/docs/commands/assert.mdx +9 -6
- package/docs/commands/hover-image.mdx +9 -6
- package/docs/commands/if.mdx +11 -6
- package/docs/commands/match-image.mdx +13 -6
- package/docs/commands/remember.mdx +10 -5
- package/docs/commands/run.mdx +8 -2
- package/docs/commands/type.mdx +11 -5
- package/docs/commands/wait-for-image.mdx +9 -3
- package/docs/commands/wait.mdx +9 -4
- package/docs/features/generation.mdx +1 -1
- package/docs/features/reusable-snippets.mdx +8 -5
- package/docs/features/selectorless.mdx +13 -6
- package/docs/features/visual-assertions.mdx +53 -39
- package/docs/getting-started/editing.mdx +1 -1
- package/docs/guide/assertions.mdx +12 -17
- package/docs/guide/code.mdx +2 -2
- package/docs/guide/lifecycle.mdx +3 -3
- package/docs/guide/locating.mdx +13 -6
- package/docs/guide/waiting.mdx +41 -32
- package/docs/interactive/assert.mdx +11 -0
- package/docs/interactive/generate.mdx +11 -0
- package/docs/interactive/run.mdx +8 -0
- package/docs/interactive/save.mdx +7 -0
- package/docs/interactive/undo.mdx +11 -3
- package/docs/overview/comparison.mdx +50 -49
- package/docs/overview/faq.mdx +40 -2
- package/docs/overview/performance.mdx +25 -25
- package/docs/overview/what-is-testdriver.mdx +24 -22
- package/docs/scenarios/ai-chatbot.mdx +6 -5
- package/docs/scenarios/cookie-banner.mdx +4 -1
- package/docs/scenarios/file-upload.mdx +7 -4
- package/docs/scenarios/form-filling.mdx +4 -1
- package/docs/scenarios/pdf-generation.mdx +5 -2
- package/docs/scenarios/spell-check.mdx +3 -3
- package/docs/security/agent.mdx +14 -2
- package/docs/security/platform.mdx +17 -5
- package/docs/snippets/calendar-link.mdx +4 -1
- package/docs/snippets/comments.mdx +1 -1
- package/docs/snippets/gitignore-warning.mdx +6 -3
- package/docs/snippets/lifecycle-warning.mdx +6 -1
- package/docs/snippets/tests/assert-replay.mdx +5 -1
- package/docs/snippets/tests/exec-js-replay.mdx +5 -1
- package/docs/snippets/tests/exec-shell-replay.mdx +5 -1
- package/docs/snippets/tests/hover-image-replay.mdx +5 -1
- package/docs/snippets/tests/hover-image-yaml.mdx +2 -2
- package/docs/snippets/tests/hover-text-replay.mdx +5 -1
- package/docs/snippets/tests/hover-text-with-description-replay.mdx +5 -2
- package/docs/snippets/tests/hover-text-with-description-yaml.mdx +2 -2
- package/docs/snippets/tests/match-image-replay.mdx +5 -1
- package/docs/snippets/tests/match-image-yaml.mdx +2 -2
- package/docs/snippets/tests/press-keys-replay.mdx +5 -1
- package/docs/snippets/tests/prompt-replay.mdx +5 -1
- package/docs/snippets/tests/prompt-yaml.mdx +1 -1
- package/docs/snippets/tests/remember-replay.mdx +5 -1
- package/docs/snippets/tests/scroll-replay.mdx +5 -1
- package/docs/snippets/tests/scroll-until-text-replay.mdx +5 -1
- package/docs/snippets/tests/type-repeated-replay.mdx +5 -1
- package/docs/snippets/tests/type-replay.mdx +5 -1
- package/docs/snippets/yml-warning.mdx +4 -1
- package/docs/styles.css +6 -0
- package/interfaces/logger.js +1 -19
- package/package.json +6 -2
- package/styles/Microsoft/HeadingAcronyms.yml +1 -1
- package/styles/Microsoft/Headings.yml +1 -1
- package/styles/Microsoft/Quotes.yml +1 -1
- package/styles/Microsoft/Semicolon.yml +1 -1
- package/styles/Microsoft/SentenceLength.yml +0 -1
- package/styles/Microsoft/Spacing.yml +2 -2
- package/styles/Microsoft/Units.yml +4 -4
- package/styles/Microsoft/Vocab.yml +1 -1
- package/styles/alex/Ablist.yml +58 -29
- package/styles/alex/Gendered.yml +4 -2
- package/styles/alex/Press.yml +2 -1
- package/styles/alex/ProfanityLikely.yml +1284 -1284
- package/styles/alex/ProfanityMaybe.yml +1 -1
- package/styles/alex/ProfanityUnlikely.yml +1 -1
- package/styles/alex/Race.yml +4 -2
- package/styles/alex/Suicide.yml +4 -2
- package/styles/alex/meta.json +1 -1
- package/styles/proselint/AnimalLabels.yml +39 -39
- package/styles/proselint/DenizenLabels.yml +44 -44
- package/styles/proselint/GenderBias.yml +37 -37
- package/styles/proselint/GroupTerms.yml +31 -31
- package/styles/proselint/Hedging.yml +1 -1
- package/styles/proselint/Hyperbole.yml +1 -1
- package/styles/proselint/LGBTTerms.yml +8 -8
- package/styles/proselint/Needless.yml +5 -5
- package/styles/proselint/RASSyndrome.yml +3 -3
- package/styles/proselint/Typography.yml +1 -1
- package/styles/proselint/Uncomparables.yml +5 -5
- package/styles/proselint/meta.json +1 -3
- package/testdriver/acceptance/remember.yaml +1 -1
- package/testdriver/behavior/secrets.yaml +7 -0
- /package/{upload-docs-to-openai.js → docs/_scripts/upload-docs-to-openai.js} +0 -0
package/agent/events.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const { EventEmitter2 } = require("eventemitter2");
|
|
2
|
+
const { censorSensitiveDataDeep } = require("./lib/censorship");
|
|
2
3
|
|
|
3
|
-
// Factory function to create a new emitter instance
|
|
4
|
+
// Factory function to create a new emitter instance with censoring middleware
|
|
4
5
|
const createEmitter = () => {
|
|
5
6
|
const emitter = new EventEmitter2({
|
|
6
7
|
wildcard: true,
|
|
@@ -11,6 +12,15 @@ const createEmitter = () => {
|
|
|
11
12
|
verboseMemoryLeak: false,
|
|
12
13
|
ignoreErrors: false,
|
|
13
14
|
});
|
|
15
|
+
|
|
16
|
+
// Override emit to censor sensitive data before emitting
|
|
17
|
+
const originalEmit = emitter.emit.bind(emitter);
|
|
18
|
+
emitter.emit = function (event, ...args) {
|
|
19
|
+
// Censor all arguments passed to emit
|
|
20
|
+
const censoredArgs = args.map(censorSensitiveDataDeep);
|
|
21
|
+
return originalEmit(event, ...censoredArgs);
|
|
22
|
+
};
|
|
23
|
+
|
|
14
24
|
return emitter;
|
|
15
25
|
};
|
|
16
26
|
|
|
@@ -33,7 +43,6 @@ const events = {
|
|
|
33
43
|
vm: {
|
|
34
44
|
show: "vm:show",
|
|
35
45
|
},
|
|
36
|
-
narration: "narration",
|
|
37
46
|
status: "status",
|
|
38
47
|
log: {
|
|
39
48
|
markdown: {
|
|
@@ -45,6 +54,7 @@ const events = {
|
|
|
45
54
|
log: "log:log",
|
|
46
55
|
warn: "log:warn",
|
|
47
56
|
debug: "log:debug",
|
|
57
|
+
narration: "log:narration",
|
|
48
58
|
},
|
|
49
59
|
command: {
|
|
50
60
|
start: "command:start",
|
package/agent/index.js
CHANGED
|
@@ -158,7 +158,7 @@ class TestDriverAgent extends EventEmitter2 {
|
|
|
158
158
|
// single function to handle all program exits
|
|
159
159
|
// allows us to save the current state, run lifecycle hooks, and track analytics
|
|
160
160
|
async exit(failed = true, shouldSave = false, shouldRunPostrun = false) {
|
|
161
|
-
this.emitter.emit(events.log.
|
|
161
|
+
this.emitter.emit(events.log.narration, theme.dim("exiting..."), true);
|
|
162
162
|
|
|
163
163
|
// Clean up redraw interval
|
|
164
164
|
if (this.redraw && this.redraw.cleanup) {
|
|
@@ -290,7 +290,7 @@ class TestDriverAgent extends EventEmitter2 {
|
|
|
290
290
|
image = null;
|
|
291
291
|
}
|
|
292
292
|
|
|
293
|
-
this.emitter.emit(events.log.
|
|
293
|
+
this.emitter.emit(events.log.narration, theme.dim("thinking..."), true);
|
|
294
294
|
|
|
295
295
|
const streamId = `error-${Date.now()}`;
|
|
296
296
|
this.emitter.emit(events.log.markdown.start, streamId);
|
|
@@ -332,14 +332,14 @@ class TestDriverAgent extends EventEmitter2 {
|
|
|
332
332
|
|
|
333
333
|
if (this.checkCount >= this.checkLimit) {
|
|
334
334
|
this.emitter.emit(
|
|
335
|
-
events.log.
|
|
335
|
+
events.log.narration,
|
|
336
336
|
theme.red("Exploratory loop detected. Exiting."),
|
|
337
337
|
);
|
|
338
338
|
await this.summarize("Check loop detected.");
|
|
339
339
|
return await this.exit(true);
|
|
340
340
|
}
|
|
341
341
|
|
|
342
|
-
this.emitter.emit(events.log.
|
|
342
|
+
this.emitter.emit(events.log.narration, theme.dim("checking..."));
|
|
343
343
|
|
|
344
344
|
// check asks the ai if the task is complete
|
|
345
345
|
let thisScreenshot = await this.system.captureScreenBase64(1, false, true);
|
|
@@ -583,8 +583,6 @@ class TestDriverAgent extends EventEmitter2 {
|
|
|
583
583
|
"check thinks more needs to be done",
|
|
584
584
|
);
|
|
585
585
|
|
|
586
|
-
this.emitter.emit(events.log.log, theme.dim("not done yet!"));
|
|
587
|
-
|
|
588
586
|
return await this.aiExecute(response, validateAndLoop);
|
|
589
587
|
} else {
|
|
590
588
|
this.emitter.emit(events.log.debug, "seems complete, returning");
|
|
@@ -719,7 +717,7 @@ class TestDriverAgent extends EventEmitter2 {
|
|
|
719
717
|
}
|
|
720
718
|
}
|
|
721
719
|
|
|
722
|
-
this.emitter.emit(events.log.
|
|
720
|
+
this.emitter.emit(events.log.narration, theme.dim("thinking..."), true);
|
|
723
721
|
|
|
724
722
|
let response = `\`\`\`yaml
|
|
725
723
|
commands:
|
|
@@ -747,7 +745,7 @@ commands:
|
|
|
747
745
|
|
|
748
746
|
this.tasks.push(currentTask);
|
|
749
747
|
|
|
750
|
-
this.emitter.emit(events.log.
|
|
748
|
+
this.emitter.emit(events.log.narration, theme.dim("thinking..."), true);
|
|
751
749
|
|
|
752
750
|
this.lastScreenshot = await this.system.captureScreenBase64();
|
|
753
751
|
|
|
@@ -789,7 +787,7 @@ commands:
|
|
|
789
787
|
async generate(type, count, baseYaml, skipYaml = false) {
|
|
790
788
|
this.emitter.emit(events.log.debug, "generate called, %s", type);
|
|
791
789
|
|
|
792
|
-
this.emitter.emit(events.log.
|
|
790
|
+
this.emitter.emit(events.log.narration, theme.dim("thinking..."), true);
|
|
793
791
|
|
|
794
792
|
if (baseYaml && !skipYaml) {
|
|
795
793
|
await this.runLifecycle("prerun");
|
|
@@ -866,7 +864,7 @@ commands:
|
|
|
866
864
|
|
|
867
865
|
// this is the functinoality for "undo"
|
|
868
866
|
async popFromHistory(fullStep) {
|
|
869
|
-
this.emitter.emit(events.log.
|
|
867
|
+
this.emitter.emit(events.log.narration, theme.dim("undoing..."), true);
|
|
870
868
|
|
|
871
869
|
if (this.executionHistory.length) {
|
|
872
870
|
if (fullStep) {
|
|
@@ -946,12 +944,16 @@ ${yml}
|
|
|
946
944
|
async summarize(error = null) {
|
|
947
945
|
this.analytics.track("summarize");
|
|
948
946
|
|
|
949
|
-
this.emitter.emit(
|
|
947
|
+
this.emitter.emit(
|
|
948
|
+
events.log.narration,
|
|
949
|
+
theme.dim("reviewing test..."),
|
|
950
|
+
true,
|
|
951
|
+
);
|
|
950
952
|
|
|
951
953
|
// let text = prompts.summarize(tasks, error);
|
|
952
954
|
let image = await this.system.captureScreenBase64();
|
|
953
955
|
|
|
954
|
-
this.emitter.emit(events.log.
|
|
956
|
+
this.emitter.emit(events.log.narration, theme.dim("summarizing..."), true);
|
|
955
957
|
|
|
956
958
|
const streamId = `summarize-${Date.now()}`;
|
|
957
959
|
this.emitter.emit(events.log.markdown.start, streamId);
|
|
@@ -1124,7 +1126,7 @@ ${regression}
|
|
|
1124
1126
|
timestamp: fileStartTime,
|
|
1125
1127
|
});
|
|
1126
1128
|
|
|
1127
|
-
this.emitter.emit(events.log.
|
|
1129
|
+
this.emitter.emit(events.log.narration, theme.cyan(`running ${file}...`));
|
|
1128
1130
|
|
|
1129
1131
|
let ymlObj = await this.loadYML(file);
|
|
1130
1132
|
|
|
@@ -1390,7 +1392,7 @@ ${regression}
|
|
|
1390
1392
|
const mtime = new Date(stats.mtime);
|
|
1391
1393
|
const now = new Date();
|
|
1392
1394
|
const diffMinutes = (now - mtime) / (1000 * 60);
|
|
1393
|
-
if (diffMinutes <
|
|
1395
|
+
if (diffMinutes < 10) {
|
|
1394
1396
|
const fileContent = fs.readFileSync(lastSandboxFile, "utf-8").trim();
|
|
1395
1397
|
|
|
1396
1398
|
// Parse sandbox info (supports both old format and new format)
|
|
@@ -1463,8 +1465,8 @@ ${regression}
|
|
|
1463
1465
|
// If instance already exists, do not build environment again
|
|
1464
1466
|
if (this.instance) {
|
|
1465
1467
|
this.emitter.emit(
|
|
1466
|
-
events.log.
|
|
1467
|
-
theme.dim("
|
|
1468
|
+
events.log.narration,
|
|
1469
|
+
theme.dim("sandbox instance already exists, skipping launch."),
|
|
1468
1470
|
);
|
|
1469
1471
|
return;
|
|
1470
1472
|
}
|
|
@@ -1488,7 +1490,7 @@ ${regression}
|
|
|
1488
1490
|
if (!this.config.CI) {
|
|
1489
1491
|
this.emitter.emit(
|
|
1490
1492
|
events.log.log,
|
|
1491
|
-
theme.dim("
|
|
1493
|
+
theme.dim("--`new` flag detected, will create a new sandbox"),
|
|
1492
1494
|
);
|
|
1493
1495
|
}
|
|
1494
1496
|
}
|
|
@@ -1501,14 +1503,14 @@ ${regression}
|
|
|
1501
1503
|
// Set sandbox ID for reconnection (only if not creating new and recent ID exists)
|
|
1502
1504
|
if (!createNew && recentId) {
|
|
1503
1505
|
this.emitter.emit(
|
|
1504
|
-
events.log.
|
|
1505
|
-
theme.dim(
|
|
1506
|
+
events.log.narration,
|
|
1507
|
+
theme.dim(`using recent sandbox: ${recentId}`),
|
|
1506
1508
|
);
|
|
1507
1509
|
this.sandboxId = recentId;
|
|
1508
1510
|
} else if (!createNew) {
|
|
1509
1511
|
this.emitter.emit(
|
|
1510
|
-
events.log.
|
|
1511
|
-
theme.dim(
|
|
1512
|
+
events.log.narration,
|
|
1513
|
+
theme.dim(`no recent sandbox found, creating a new one.`),
|
|
1512
1514
|
);
|
|
1513
1515
|
}
|
|
1514
1516
|
|
|
@@ -1516,8 +1518,8 @@ ${regression}
|
|
|
1516
1518
|
if (this.sandboxId && !this.config.CI && !createNew) {
|
|
1517
1519
|
// Attempt to connect to known instance
|
|
1518
1520
|
this.emitter.emit(
|
|
1519
|
-
events.log.
|
|
1520
|
-
theme.dim(
|
|
1521
|
+
events.log.narration,
|
|
1522
|
+
theme.dim(`connecting to sandbox ${this.sandboxId}...`),
|
|
1521
1523
|
);
|
|
1522
1524
|
|
|
1523
1525
|
try {
|
|
@@ -1539,17 +1541,16 @@ ${regression}
|
|
|
1539
1541
|
}
|
|
1540
1542
|
}
|
|
1541
1543
|
|
|
1542
|
-
this.emitter.emit(events.log.log, theme.dim(`- creating new sandbox...`));
|
|
1543
1544
|
this.emitter.emit(
|
|
1544
|
-
events.log.
|
|
1545
|
-
theme.dim(`
|
|
1545
|
+
events.log.narration,
|
|
1546
|
+
theme.dim(`creating new sandbox (can take up to 2 minutes)...`),
|
|
1546
1547
|
);
|
|
1547
1548
|
// We don't have resiliency/retries baked in, so let's at least give it 1 attempt
|
|
1548
1549
|
// to see if that fixes the issue.
|
|
1549
1550
|
let newSandbox = await this.createNewSandbox().catch(() => {
|
|
1550
1551
|
this.emitter.emit(
|
|
1551
|
-
events.log.
|
|
1552
|
-
theme.dim(`
|
|
1552
|
+
events.log.narration,
|
|
1553
|
+
theme.dim(`double-checking sandbox availability`),
|
|
1553
1554
|
);
|
|
1554
1555
|
|
|
1555
1556
|
return this.createNewSandbox();
|
|
@@ -1576,7 +1577,7 @@ ${regression}
|
|
|
1576
1577
|
if (!debuggerStarted) {
|
|
1577
1578
|
debuggerStarted = true; // Prevent multiple starts, especially when running test in parallel
|
|
1578
1579
|
this.emitter.emit(
|
|
1579
|
-
events.log.
|
|
1580
|
+
events.log.narration,
|
|
1580
1581
|
theme.green(`Starting debugger server...`),
|
|
1581
1582
|
);
|
|
1582
1583
|
debuggerProcess = await createDebuggerProcess(
|
|
@@ -1697,8 +1698,8 @@ ${regression}
|
|
|
1697
1698
|
|
|
1698
1699
|
async connectToSandboxService() {
|
|
1699
1700
|
this.emitter.emit(
|
|
1700
|
-
events.log.
|
|
1701
|
-
theme.
|
|
1701
|
+
events.log.narration,
|
|
1702
|
+
theme.dim(`establishing connection...`),
|
|
1702
1703
|
);
|
|
1703
1704
|
let ableToBoot = await this.sandbox.boot(this.config.TD_API_ROOT);
|
|
1704
1705
|
|
|
@@ -1710,7 +1711,7 @@ Please check your network connection, TD_API_KEY, or the service status.`,
|
|
|
1710
1711
|
);
|
|
1711
1712
|
}
|
|
1712
1713
|
|
|
1713
|
-
this.emitter.emit(events.log.
|
|
1714
|
+
this.emitter.emit(events.log.narration, theme.dim(`authenticating...`));
|
|
1714
1715
|
let ableToAuth = await this.sandbox.auth(this.config.TD_API_KEY);
|
|
1715
1716
|
|
|
1716
1717
|
if (!ableToAuth) {
|
|
@@ -1723,7 +1724,7 @@ Please check your network connection, TD_API_KEY, or the service status.`,
|
|
|
1723
1724
|
}
|
|
1724
1725
|
|
|
1725
1726
|
async connectToSandboxDirect(sandboxId, persist = false) {
|
|
1726
|
-
this.emitter.emit(events.log.
|
|
1727
|
+
this.emitter.emit(events.log.narration, theme.dim(`connecting...`));
|
|
1727
1728
|
let instance = await this.sandbox.connect(sandboxId, persist);
|
|
1728
1729
|
return instance;
|
|
1729
1730
|
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Shared censorship functionality for sensitive data
|
|
2
|
+
// Uses @npmcli/redact for common patterns (URLs, auth headers, etc.)
|
|
3
|
+
// Plus custom logic for environment variables and interpolation vars
|
|
4
|
+
const { redactLog } = require("@npmcli/redact");
|
|
5
|
+
|
|
6
|
+
let interpolationVars = JSON.parse(process.env.TD_INTERPOLATION_VARS || "{}");
|
|
7
|
+
|
|
8
|
+
// Handle local `TD_*` variables
|
|
9
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
10
|
+
if (key.startsWith("TD_") && key !== "TD_INTERPOLATION_VARS") {
|
|
11
|
+
interpolationVars[key] = value;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Function to censor sensitive data in strings using both npmcli/redact and custom logic
|
|
16
|
+
const censorSensitiveData = (message) => {
|
|
17
|
+
if (typeof message !== "string") {
|
|
18
|
+
return message;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// First, use npmcli/redact for common patterns:
|
|
22
|
+
// - URLs with credentials (https://user:pass@host)
|
|
23
|
+
// - Authorization headers
|
|
24
|
+
// - Common secret patterns in logs
|
|
25
|
+
let result = redactLog(message);
|
|
26
|
+
|
|
27
|
+
// Then apply our custom interpolation variable redaction
|
|
28
|
+
// This catches application-specific secrets from TD_* env vars
|
|
29
|
+
for (let value of Object.values(interpolationVars)) {
|
|
30
|
+
// Avoid replacing vars that are 0 or 1 characters
|
|
31
|
+
if (value && value.length >= 2) {
|
|
32
|
+
result = result.replaceAll(value, "****");
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// Function to censor sensitive data in any value (recursive for objects/arrays)
|
|
40
|
+
const censorSensitiveDataDeep = (value) => {
|
|
41
|
+
if (typeof value === "string") {
|
|
42
|
+
return censorSensitiveData(value);
|
|
43
|
+
} else if (Array.isArray(value)) {
|
|
44
|
+
return value.map(censorSensitiveDataDeep);
|
|
45
|
+
} else if (value && typeof value === "object") {
|
|
46
|
+
const result = {};
|
|
47
|
+
for (const [key, val] of Object.entries(value)) {
|
|
48
|
+
result[key] = censorSensitiveDataDeep(val);
|
|
49
|
+
}
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
return value;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// Function to update interpolation variables (for runtime updates)
|
|
56
|
+
const updateInterpolationVars = (newVars) => {
|
|
57
|
+
interpolationVars = { ...interpolationVars, ...newVars };
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// Function to get current interpolation variables (for debugging)
|
|
61
|
+
const getInterpolationVars = () => {
|
|
62
|
+
return { ...interpolationVars };
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
module.exports = {
|
|
66
|
+
censorSensitiveData,
|
|
67
|
+
censorSensitiveDataDeep,
|
|
68
|
+
updateInterpolationVars,
|
|
69
|
+
getInterpolationVars,
|
|
70
|
+
};
|
package/agent/lib/commander.js
CHANGED
|
@@ -80,20 +80,20 @@ commands:
|
|
|
80
80
|
// this will actually interpret the command and execute it
|
|
81
81
|
switch (object.command) {
|
|
82
82
|
case "type":
|
|
83
|
-
emitter.emit(events.narration, `typing ${object.text}`);
|
|
83
|
+
emitter.emit(events.log.narration, `typing ${object.text}`);
|
|
84
84
|
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
85
85
|
response = await commands.type(object.text, object.delay);
|
|
86
86
|
break;
|
|
87
87
|
case "press-keys":
|
|
88
88
|
emitter.emit(
|
|
89
|
-
events.narration,
|
|
89
|
+
events.log.narration,
|
|
90
90
|
`pressing keys ${object.keys.join(",")}`,
|
|
91
91
|
);
|
|
92
92
|
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
93
93
|
response = await commands["press-keys"](object.keys);
|
|
94
94
|
break;
|
|
95
95
|
case "scroll":
|
|
96
|
-
emitter.emit(events.narration, `scrolling ${object.direction}`);
|
|
96
|
+
emitter.emit(events.log.narration, `scrolling ${object.direction}`);
|
|
97
97
|
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
98
98
|
response = await commands.scroll(
|
|
99
99
|
object.direction,
|
|
@@ -102,17 +102,20 @@ commands:
|
|
|
102
102
|
);
|
|
103
103
|
break;
|
|
104
104
|
case "wait":
|
|
105
|
-
emitter.emit(
|
|
105
|
+
emitter.emit(
|
|
106
|
+
events.log.narration,
|
|
107
|
+
`waiting ${object.timeout} seconds`,
|
|
108
|
+
);
|
|
106
109
|
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
107
110
|
response = await commands.wait(object.timeout);
|
|
108
111
|
break;
|
|
109
112
|
case "click":
|
|
110
|
-
emitter.emit(events.narration, `${object.action}`);
|
|
113
|
+
emitter.emit(events.log.narration, `${object.action}`);
|
|
111
114
|
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
112
115
|
response = await commands["click"](object.x, object.y, object.action);
|
|
113
116
|
break;
|
|
114
117
|
case "hover":
|
|
115
|
-
emitter.emit(events.narration, `moving mouse`);
|
|
118
|
+
emitter.emit(events.log.narration, `moving mouse`);
|
|
116
119
|
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
117
120
|
response = await commands["hover"](object.x, object.y);
|
|
118
121
|
break;
|
|
@@ -121,7 +124,10 @@ commands:
|
|
|
121
124
|
response = await commands["drag"](object.x, object.y);
|
|
122
125
|
break;
|
|
123
126
|
case "hover-text":
|
|
124
|
-
emitter.emit(
|
|
127
|
+
emitter.emit(
|
|
128
|
+
events.log.narration,
|
|
129
|
+
`searching for ${object.description}`,
|
|
130
|
+
);
|
|
125
131
|
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
126
132
|
response = await commands["hover-text"](
|
|
127
133
|
object.text,
|
|
@@ -132,7 +138,7 @@ commands:
|
|
|
132
138
|
break;
|
|
133
139
|
case "hover-image":
|
|
134
140
|
emitter.emit(
|
|
135
|
-
events.narration,
|
|
141
|
+
events.log.narration,
|
|
136
142
|
`searching for image of ${object.description}`,
|
|
137
143
|
);
|
|
138
144
|
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
@@ -144,13 +150,16 @@ commands:
|
|
|
144
150
|
case "match-image":
|
|
145
151
|
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
146
152
|
emitter.emit(
|
|
147
|
-
events.narration,
|
|
153
|
+
events.log.narration,
|
|
148
154
|
`${object.action} image ${object.path}`,
|
|
149
155
|
);
|
|
150
156
|
response = await commands["match-image"](object.path, object.action);
|
|
151
157
|
break;
|
|
152
158
|
case "wait-for-image":
|
|
153
|
-
emitter.emit(
|
|
159
|
+
emitter.emit(
|
|
160
|
+
events.log.narration,
|
|
161
|
+
`waiting for ${object.description}`,
|
|
162
|
+
);
|
|
154
163
|
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
155
164
|
response = await commands["wait-for-image"](
|
|
156
165
|
object.description,
|
|
@@ -158,7 +167,7 @@ commands:
|
|
|
158
167
|
);
|
|
159
168
|
break;
|
|
160
169
|
case "wait-for-text":
|
|
161
|
-
emitter.emit(events.narration, `waiting for ${object.text}`);
|
|
170
|
+
emitter.emit(events.log.narration, `waiting for ${object.text}`);
|
|
162
171
|
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
163
172
|
copy.text = "*****";
|
|
164
173
|
response = await commands["wait-for-text"](
|
|
@@ -168,7 +177,7 @@ commands:
|
|
|
168
177
|
);
|
|
169
178
|
break;
|
|
170
179
|
case "scroll-until-text":
|
|
171
|
-
emitter.emit(events.narration, `scrolling until ${object.text}`);
|
|
180
|
+
emitter.emit(events.log.narration, `scrolling until ${object.text}`);
|
|
172
181
|
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
173
182
|
copy.text = "*****";
|
|
174
183
|
response = await commands["scroll-until-text"](
|
|
@@ -181,7 +190,7 @@ commands:
|
|
|
181
190
|
break;
|
|
182
191
|
case "scroll-until-image": {
|
|
183
192
|
const needle = object.description || object.path;
|
|
184
|
-
emitter.emit(events.narration, `scrolling until ${needle}`);
|
|
193
|
+
emitter.emit(events.log.narration, `scrolling until ${needle}`);
|
|
185
194
|
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
186
195
|
response = await commands["scroll-until-image"](
|
|
187
196
|
object.description,
|
|
@@ -193,7 +202,7 @@ commands:
|
|
|
193
202
|
break;
|
|
194
203
|
}
|
|
195
204
|
case "focus-application":
|
|
196
|
-
emitter.emit(events.narration, `focusing ${object.name}`);
|
|
205
|
+
emitter.emit(events.log.narration, `focusing ${object.name}`);
|
|
197
206
|
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
198
207
|
response = await commands["focus-application"](object.name);
|
|
199
208
|
break;
|
|
@@ -205,12 +214,12 @@ commands:
|
|
|
205
214
|
break;
|
|
206
215
|
}
|
|
207
216
|
case "assert":
|
|
208
|
-
emitter.emit(events.narration, `asserting ${object.expect}`);
|
|
217
|
+
emitter.emit(events.log.narration, `asserting ${object.expect}`);
|
|
209
218
|
emitter.emit(events.log.log, generator.jsonToManual(object));
|
|
210
219
|
response = await commands.assert(object.expect, object.async);
|
|
211
220
|
break;
|
|
212
221
|
case "exec":
|
|
213
|
-
emitter.emit(events.narration, `exec`);
|
|
222
|
+
emitter.emit(events.log.narration, `exec`);
|
|
214
223
|
emitter.emit(
|
|
215
224
|
events.log.log,
|
|
216
225
|
generator.jsonToManual({
|
package/agent/lib/commands.js
CHANGED
|
@@ -190,7 +190,7 @@ const createCommands = (
|
|
|
190
190
|
}
|
|
191
191
|
};
|
|
192
192
|
|
|
193
|
-
emitter.emit(events.log.
|
|
193
|
+
emitter.emit(events.log.narration, `thinking...`);
|
|
194
194
|
|
|
195
195
|
if (async) {
|
|
196
196
|
await sdk
|
|
@@ -276,7 +276,7 @@ const createCommands = (
|
|
|
276
276
|
}
|
|
277
277
|
|
|
278
278
|
emitter.emit(
|
|
279
|
-
|
|
279
|
+
events.log.narration,
|
|
280
280
|
theme.dim(`${action} ${button} clicking at ${x}, ${y}...`),
|
|
281
281
|
true,
|
|
282
282
|
);
|
|
@@ -347,7 +347,7 @@ const createCommands = (
|
|
|
347
347
|
|
|
348
348
|
description = description ? description.toString() : null;
|
|
349
349
|
|
|
350
|
-
emitter.emit(events.log.
|
|
350
|
+
emitter.emit(events.log.narration, theme.dim("thinking..."), true);
|
|
351
351
|
|
|
352
352
|
let response = await sdk.req(
|
|
353
353
|
"hover/text",
|
|
@@ -375,7 +375,7 @@ const createCommands = (
|
|
|
375
375
|
// uses our api to find all images on screen
|
|
376
376
|
"hover-image": async (description, action = "click") => {
|
|
377
377
|
// take a screenshot
|
|
378
|
-
emitter.emit(events.log.
|
|
378
|
+
emitter.emit(events.log.narration, theme.dim("thinking..."), true);
|
|
379
379
|
|
|
380
380
|
let response = await sdk.req(
|
|
381
381
|
"hover/image",
|
|
@@ -446,7 +446,7 @@ const createCommands = (
|
|
|
446
446
|
},
|
|
447
447
|
"wait-for-image": async (description, timeout = 10000) => {
|
|
448
448
|
emitter.emit(
|
|
449
|
-
events.log.
|
|
449
|
+
events.log.narration,
|
|
450
450
|
theme.dim(
|
|
451
451
|
`waiting for an image matching description "${description}"...`,
|
|
452
452
|
),
|
|
@@ -467,7 +467,7 @@ const createCommands = (
|
|
|
467
467
|
durationPassed = new Date().getTime() - startTime;
|
|
468
468
|
if (!passed) {
|
|
469
469
|
emitter.emit(
|
|
470
|
-
events.log.
|
|
470
|
+
events.log.narration,
|
|
471
471
|
theme.dim(
|
|
472
472
|
`${niceSeconds(durationPassed)} seconds have passed without finding an image matching the description "${description}"`,
|
|
473
473
|
),
|
|
@@ -479,7 +479,7 @@ const createCommands = (
|
|
|
479
479
|
|
|
480
480
|
if (passed) {
|
|
481
481
|
emitter.emit(
|
|
482
|
-
events.log.
|
|
482
|
+
events.log.narration,
|
|
483
483
|
theme.dim(
|
|
484
484
|
`An image matching the description "${description}" found!`,
|
|
485
485
|
),
|
|
@@ -496,7 +496,7 @@ const createCommands = (
|
|
|
496
496
|
await redraw.start();
|
|
497
497
|
|
|
498
498
|
emitter.emit(
|
|
499
|
-
events.log.
|
|
499
|
+
events.log.narration,
|
|
500
500
|
theme.dim(`waiting for text: "${text}"...`),
|
|
501
501
|
true,
|
|
502
502
|
);
|
|
@@ -525,7 +525,7 @@ const createCommands = (
|
|
|
525
525
|
durationPassed = new Date().getTime() - startTime;
|
|
526
526
|
if (!passed) {
|
|
527
527
|
emitter.emit(
|
|
528
|
-
events.log.
|
|
528
|
+
events.log.narration,
|
|
529
529
|
theme.dim(
|
|
530
530
|
`${niceSeconds(durationPassed)} seconds have passed without finding "${text}"`,
|
|
531
531
|
),
|
|
@@ -536,7 +536,7 @@ const createCommands = (
|
|
|
536
536
|
}
|
|
537
537
|
|
|
538
538
|
if (passed) {
|
|
539
|
-
emitter.emit(events.log.
|
|
539
|
+
emitter.emit(events.log.narration, theme.dim(`"${text}" found!`), true);
|
|
540
540
|
return;
|
|
541
541
|
} else {
|
|
542
542
|
throw new MatchError(
|
|
@@ -554,7 +554,7 @@ const createCommands = (
|
|
|
554
554
|
await redraw.start();
|
|
555
555
|
|
|
556
556
|
emitter.emit(
|
|
557
|
-
events.log.
|
|
557
|
+
events.log.narration,
|
|
558
558
|
theme.dim(`scrolling for text: "${text}"...`),
|
|
559
559
|
true,
|
|
560
560
|
);
|
|
@@ -598,7 +598,7 @@ const createCommands = (
|
|
|
598
598
|
passed = response.data;
|
|
599
599
|
if (!passed) {
|
|
600
600
|
emitter.emit(
|
|
601
|
-
events.log.
|
|
601
|
+
events.log.narration,
|
|
602
602
|
theme.dim(
|
|
603
603
|
`scrolling ${direction} ${incrementDistance}px. ${scrollDistance + incrementDistance}/${maxDistance}px scrolled...`,
|
|
604
604
|
),
|
|
@@ -610,7 +610,7 @@ const createCommands = (
|
|
|
610
610
|
}
|
|
611
611
|
|
|
612
612
|
if (passed) {
|
|
613
|
-
emitter.emit(events.log.
|
|
613
|
+
emitter.emit(events.log.narration, theme.dim(`"${text}" found!`), true);
|
|
614
614
|
return;
|
|
615
615
|
} else {
|
|
616
616
|
throw new MatchError(
|
|
@@ -638,7 +638,7 @@ const createCommands = (
|
|
|
638
638
|
}
|
|
639
639
|
|
|
640
640
|
emitter.emit(
|
|
641
|
-
events.log.
|
|
641
|
+
events.log.narration,
|
|
642
642
|
theme.dim(`scrolling for an image matching "${needle}"...`),
|
|
643
643
|
true,
|
|
644
644
|
);
|
|
@@ -665,7 +665,7 @@ const createCommands = (
|
|
|
665
665
|
|
|
666
666
|
if (!passed) {
|
|
667
667
|
emitter.emit(
|
|
668
|
-
events.log.
|
|
668
|
+
events.log.narration,
|
|
669
669
|
theme.dim(`scrolling ${direction} ${incrementDistance} pixels...`),
|
|
670
670
|
true,
|
|
671
671
|
);
|
|
@@ -675,7 +675,11 @@ const createCommands = (
|
|
|
675
675
|
}
|
|
676
676
|
|
|
677
677
|
if (passed) {
|
|
678
|
-
emitter.emit(
|
|
678
|
+
emitter.emit(
|
|
679
|
+
events.log.narration,
|
|
680
|
+
theme.dim(`"${needle}" found!`),
|
|
681
|
+
true,
|
|
682
|
+
);
|
|
679
683
|
return;
|
|
680
684
|
} else {
|
|
681
685
|
throw new CommandError(
|
|
@@ -705,7 +709,7 @@ const createCommands = (
|
|
|
705
709
|
return await assert(assertion, true, async);
|
|
706
710
|
},
|
|
707
711
|
exec: async (language, code, timeout, silent = false) => {
|
|
708
|
-
emitter.emit(events.log.
|
|
712
|
+
emitter.emit(events.log.narration, theme.dim(`calling exec...`), true);
|
|
709
713
|
|
|
710
714
|
emitter.emit(events.log.log, code);
|
|
711
715
|
|
|
@@ -738,10 +742,10 @@ const createCommands = (
|
|
|
738
742
|
return result.out?.stdout?.trim();
|
|
739
743
|
}
|
|
740
744
|
} else if (language == "js") {
|
|
741
|
-
emitter.emit(events.log.
|
|
745
|
+
emitter.emit(events.log.narration, theme.dim(`running js...`), true);
|
|
742
746
|
|
|
743
747
|
emitter.emit(
|
|
744
|
-
events.log.
|
|
748
|
+
events.log.narration,
|
|
745
749
|
theme.dim(`running value of \`${plat}\` in local JS vm...`),
|
|
746
750
|
true,
|
|
747
751
|
);
|
package/debugger/index.html
CHANGED
|
@@ -670,6 +670,8 @@
|
|
|
670
670
|
// Handle window resize to recalculate scale (throttled)
|
|
671
671
|
window.addEventListener("resize", throttle(resizeOverlay, 100));
|
|
672
672
|
|
|
673
|
+
setInterval(resizeOverlay, 1000);
|
|
674
|
+
|
|
673
675
|
// Handle window blur/focus for screen locking
|
|
674
676
|
window.addEventListener("blur", () => {
|
|
675
677
|
showInteractionOverlay();
|