testdriverai 5.4.1 → 5.5.0
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.js +68 -45
- package/index.js +3 -0
- package/lib/commander.js +1 -1
- package/lib/commands.js +1 -1
- package/lib/config.js +2 -1
- package/lib/ipc.js +12 -45
- package/lib/logger.js +1 -1
- package/lib/system.js +11 -2
- package/package.json +1 -1
- package/schema.json +19 -19
package/agent.js
CHANGED
|
@@ -30,7 +30,7 @@ const yaml = require("js-yaml");
|
|
|
30
30
|
const sanitizeFilename = require("sanitize-filename");
|
|
31
31
|
|
|
32
32
|
// local modules
|
|
33
|
-
const
|
|
33
|
+
const server = require("./lib/ipc.js");
|
|
34
34
|
const speak = require("./lib/speak.js");
|
|
35
35
|
const analytics = require("./lib/analytics.js");
|
|
36
36
|
const log = require("./lib/logger.js");
|
|
@@ -53,6 +53,7 @@ const { emitter, events } = require("./lib/events.js");
|
|
|
53
53
|
|
|
54
54
|
const logger = log.logger;
|
|
55
55
|
|
|
56
|
+
let thisFile;
|
|
56
57
|
let lastPrompt = "";
|
|
57
58
|
let terminalApp = "";
|
|
58
59
|
let commandHistory = [];
|
|
@@ -110,12 +111,6 @@ let getArgs = () => {
|
|
|
110
111
|
}
|
|
111
112
|
|
|
112
113
|
if (!args[file]) {
|
|
113
|
-
// make testdriver directory if it doesn't exist
|
|
114
|
-
let testdriverFolder = path.join(workingDir, "testdriver");
|
|
115
|
-
if (!fs.existsSync(testdriverFolder)) {
|
|
116
|
-
fs.mkdirSync(testdriverFolder);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
114
|
args[file] = "testdriver/testdriver.yaml";
|
|
120
115
|
}
|
|
121
116
|
|
|
@@ -130,21 +125,6 @@ let getArgs = () => {
|
|
|
130
125
|
return { command: args[command], file: args[file] };
|
|
131
126
|
};
|
|
132
127
|
|
|
133
|
-
let a = getArgs();
|
|
134
|
-
|
|
135
|
-
let thisFile = a.file;
|
|
136
|
-
const thisCommand = a.command;
|
|
137
|
-
|
|
138
|
-
logger.info(chalk.green(`Howdy! I'm TestDriver v${package.version}`));
|
|
139
|
-
logger.info(`This is beta software!`);
|
|
140
|
-
logger.info("");
|
|
141
|
-
logger.info(chalk.yellow(`Join our Discord for help`));
|
|
142
|
-
logger.info(`https://discord.com/invite/cWDFW8DzPm`);
|
|
143
|
-
logger.info("");
|
|
144
|
-
|
|
145
|
-
// individual run ID for this session
|
|
146
|
-
// let runID = new Date().getTime();
|
|
147
|
-
|
|
148
128
|
function fileCompleter(line) {
|
|
149
129
|
line = line.slice(5); // remove /run
|
|
150
130
|
const lastSepIndex = line.lastIndexOf(path.sep);
|
|
@@ -407,14 +387,13 @@ const executeCommands = async (
|
|
|
407
387
|
executionHistory[executionHistory.length - 1]?.commands.push(command);
|
|
408
388
|
}
|
|
409
389
|
|
|
410
|
-
if (!dry) {
|
|
411
|
-
await runCommand(command, depth, shouldSave);
|
|
412
|
-
}
|
|
413
|
-
|
|
414
390
|
if (shouldSave) {
|
|
415
391
|
await save({ silent: true });
|
|
416
392
|
}
|
|
417
393
|
|
|
394
|
+
if (!dry) {
|
|
395
|
+
await runCommand(command, depth, shouldSave);
|
|
396
|
+
}
|
|
418
397
|
let timeToComplete = (new Date().getTime() - lastCommand) / 1000;
|
|
419
398
|
// logger.info(timeToComplete, 'seconds')
|
|
420
399
|
|
|
@@ -526,6 +505,10 @@ const loadYML = async (file) => {
|
|
|
526
505
|
process.env["TD_INTERPOLATION_VARS"] || "{}",
|
|
527
506
|
);
|
|
528
507
|
|
|
508
|
+
if (!yml) {
|
|
509
|
+
return {};
|
|
510
|
+
}
|
|
511
|
+
|
|
529
512
|
yml = await parser.validateYAML(yml);
|
|
530
513
|
|
|
531
514
|
// Inject environment variables into any ${VAR} strings
|
|
@@ -770,8 +753,10 @@ const actOnMarkdown = async (
|
|
|
770
753
|
};
|
|
771
754
|
|
|
772
755
|
const ensureMacScreenPerms = async () => {
|
|
756
|
+
|
|
773
757
|
// if os is mac, check for screen capture permissions
|
|
774
758
|
if (
|
|
759
|
+
!config.TD_OVERLAY_ID &&
|
|
775
760
|
!config.TD_VM &&
|
|
776
761
|
process.platform === "darwin"
|
|
777
762
|
) {
|
|
@@ -781,10 +766,10 @@ const ensureMacScreenPerms = async () => {
|
|
|
781
766
|
|
|
782
767
|
logger.info(chalk.red("Screen capture permissions not enabled."));
|
|
783
768
|
logger.info(
|
|
784
|
-
"You must enable screen capture permissions for
|
|
769
|
+
"You must enable screen capture permissions for this terminal application within System Settings.",
|
|
785
770
|
);
|
|
786
771
|
logger.info(
|
|
787
|
-
"
|
|
772
|
+
"Navigate to System Settings > Privacy & Security > Screen Recording and enable screen recording permissions for the terminal you are using to run this command.",
|
|
788
773
|
);
|
|
789
774
|
analytics.track("noMacPermissions");
|
|
790
775
|
return exit();
|
|
@@ -797,6 +782,7 @@ const ensureMacScreenPerms = async () => {
|
|
|
797
782
|
// simple function to backfill the chat history with a prompt and
|
|
798
783
|
// then call `promptUser()` to get the user input
|
|
799
784
|
const firstPrompt = async () => {
|
|
785
|
+
|
|
800
786
|
// readline is what allows us to get user input
|
|
801
787
|
rl = readline.createInterface({
|
|
802
788
|
terminal: true,
|
|
@@ -807,8 +793,6 @@ const firstPrompt = async () => {
|
|
|
807
793
|
completer,
|
|
808
794
|
});
|
|
809
795
|
|
|
810
|
-
analytics.track("first prompt");
|
|
811
|
-
|
|
812
796
|
rl.on("SIGINT", async () => {
|
|
813
797
|
analytics.track("sigint");
|
|
814
798
|
await exit(false);
|
|
@@ -902,11 +886,12 @@ const firstPrompt = async () => {
|
|
|
902
886
|
}
|
|
903
887
|
|
|
904
888
|
setTerminalWindowTransparency(false);
|
|
889
|
+
|
|
905
890
|
promptUser();
|
|
906
891
|
};
|
|
907
892
|
|
|
908
893
|
rl.on("line", handleInput);
|
|
909
|
-
server.on("input", handleInput);
|
|
894
|
+
server.emitter.on("input", handleInput);
|
|
910
895
|
|
|
911
896
|
// if file exists, load it
|
|
912
897
|
if (fs.existsSync(thisFile)) {
|
|
@@ -924,18 +909,17 @@ const firstPrompt = async () => {
|
|
|
924
909
|
|
|
925
910
|
let yml = fs.readFileSync(thisFile, "utf-8");
|
|
926
911
|
|
|
927
|
-
|
|
928
|
-
${yml}
|
|
929
|
-
\`\`\``;
|
|
930
|
-
|
|
931
|
-
logger.info(`Loaded test script ${thisFile}\n`);
|
|
932
|
-
|
|
933
|
-
log.prettyMarkdown(`
|
|
912
|
+
if (yml) {
|
|
934
913
|
|
|
935
|
-
|
|
914
|
+
let markdown = `\`\`\`yaml
|
|
915
|
+
${yml}\`\`\``;
|
|
916
|
+
|
|
917
|
+
logger.info(`Loaded test script ${thisFile}\n`);
|
|
918
|
+
log.prettyMarkdown(markdown);
|
|
919
|
+
logger.info("New commands will be appended.")
|
|
920
|
+
console.log('')
|
|
936
921
|
|
|
937
|
-
|
|
938
|
-
`);
|
|
922
|
+
}
|
|
939
923
|
}
|
|
940
924
|
|
|
941
925
|
promptUser();
|
|
@@ -1072,6 +1056,7 @@ let runRawYML = async (yml) => {
|
|
|
1072
1056
|
// add the root key steps: with array of commands:
|
|
1073
1057
|
if (ymlObj && !ymlObj.steps) {
|
|
1074
1058
|
ymlObj = {
|
|
1059
|
+
version: package.version,
|
|
1075
1060
|
steps: [ymlObj],
|
|
1076
1061
|
};
|
|
1077
1062
|
}
|
|
@@ -1080,7 +1065,7 @@ let runRawYML = async (yml) => {
|
|
|
1080
1065
|
fs.writeFileSync(tmpobj.name, yaml.dump(ymlObj));
|
|
1081
1066
|
|
|
1082
1067
|
// and run it with run()
|
|
1083
|
-
await run(tmpobj.name, false,
|
|
1068
|
+
await run(tmpobj.name, false, false);
|
|
1084
1069
|
};
|
|
1085
1070
|
|
|
1086
1071
|
// this will load a regression test from a file location
|
|
@@ -1156,7 +1141,7 @@ ${yaml.dump(step)}
|
|
|
1156
1141
|
|
|
1157
1142
|
const promptUser = () => {
|
|
1158
1143
|
emitter.emit(events.interactive, true);
|
|
1159
|
-
rl.prompt(
|
|
1144
|
+
process.nextTick(() => rl.prompt());
|
|
1160
1145
|
};
|
|
1161
1146
|
|
|
1162
1147
|
const setTerminalApp = async (win) => {
|
|
@@ -1235,6 +1220,43 @@ const start = async () => {
|
|
|
1235
1220
|
// process.exit();
|
|
1236
1221
|
// }
|
|
1237
1222
|
|
|
1223
|
+
let a = getArgs();
|
|
1224
|
+
|
|
1225
|
+
// make testdriver directory if it doesn't exist
|
|
1226
|
+
let testdriverFolder = path.join(workingDir, "testdriver");
|
|
1227
|
+
if (!fs.existsSync(testdriverFolder)) {
|
|
1228
|
+
|
|
1229
|
+
fs.mkdirSync(testdriverFolder);
|
|
1230
|
+
// log
|
|
1231
|
+
logger.info(chalk.dim(`Created testdriver directory`));
|
|
1232
|
+
console.log(
|
|
1233
|
+
chalk.dim(`Created testdriver directory: ${testdriverFolder}`),
|
|
1234
|
+
);
|
|
1235
|
+
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
// if testdriver.yaml doesn't exist, make it
|
|
1239
|
+
let testdriverFile = path.join(testdriverFolder, "testdriver.yaml");
|
|
1240
|
+
if (!fs.existsSync(testdriverFile)) {
|
|
1241
|
+
fs.writeFileSync(testdriverFile, "");
|
|
1242
|
+
logger.info(chalk.dim(`Created testdriver.yaml`));
|
|
1243
|
+
console.log(chalk.dim(`Created testdriver.yaml: ${testdriverFile}`));
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
thisFile = a.file;
|
|
1247
|
+
const thisCommand = a.command;
|
|
1248
|
+
|
|
1249
|
+
logger.info(chalk.green(`Howdy! I'm TestDriver v${package.version}`));
|
|
1250
|
+
logger.info(`This is beta software!`);
|
|
1251
|
+
logger.info("");
|
|
1252
|
+
logger.info(chalk.yellow(`Join our Discord for help`));
|
|
1253
|
+
logger.info(`https://discord.com/invite/cWDFW8DzPm`);
|
|
1254
|
+
logger.info("");
|
|
1255
|
+
|
|
1256
|
+
// individual run ID for this session
|
|
1257
|
+
// let runID = new Date().getTime();
|
|
1258
|
+
|
|
1259
|
+
|
|
1238
1260
|
// await sdk.auth();
|
|
1239
1261
|
if (thisCommand !== "run") {
|
|
1240
1262
|
speak("Howdy! I am TestDriver version " + package.version);
|
|
@@ -1242,6 +1264,7 @@ const start = async () => {
|
|
|
1242
1264
|
|
|
1243
1265
|
if (thisCommand !== "init" && thisCommand !== "upload-secrets") {
|
|
1244
1266
|
logger.info(chalk.dim(`Working on ${thisFile}`));
|
|
1267
|
+
console.log("");
|
|
1245
1268
|
|
|
1246
1269
|
loadYML(thisFile)
|
|
1247
1270
|
|
|
@@ -1249,11 +1272,11 @@ const start = async () => {
|
|
|
1249
1272
|
logger.info(
|
|
1250
1273
|
chalk.red("Warning! ") +
|
|
1251
1274
|
chalk.dim(
|
|
1252
|
-
"Local mode sends screenshots of the desktop to our API. Set `TD_VM
|
|
1275
|
+
"Local mode sends screenshots of the desktop to our API. Set `TD_VM` to run in a secure VM.",
|
|
1253
1276
|
),
|
|
1254
1277
|
);
|
|
1255
1278
|
logger.info(
|
|
1256
|
-
chalk.dim("https://docs.testdriver.ai/security-and-privacy/agent"),
|
|
1279
|
+
chalk.dim("Read More: https://docs.testdriver.ai/security-and-privacy/agent"),
|
|
1257
1280
|
);
|
|
1258
1281
|
logger.info("");
|
|
1259
1282
|
}
|
package/index.js
CHANGED
|
@@ -9,7 +9,9 @@ if (process.argv[2] === "--help" || process.argv[2] === "-h") {
|
|
|
9
9
|
console.log("Command: testdriverai [init, run, edit] [yaml filepath]");
|
|
10
10
|
process.exit(0);
|
|
11
11
|
}
|
|
12
|
+
|
|
12
13
|
if (process.argv[2] === "--renderer") {
|
|
14
|
+
|
|
13
15
|
const {
|
|
14
16
|
// connectToOverlay,
|
|
15
17
|
createOverlayProcess,
|
|
@@ -38,6 +40,7 @@ if (process.argv[2] === "--renderer") {
|
|
|
38
40
|
})();
|
|
39
41
|
} else {
|
|
40
42
|
(async () => {
|
|
43
|
+
|
|
41
44
|
if (!config.TD_OVERLAY) {
|
|
42
45
|
let agent = require("./agent.js");
|
|
43
46
|
await agent.start();
|
package/lib/commander.js
CHANGED
|
@@ -9,7 +9,7 @@ const analytics = require("./analytics");
|
|
|
9
9
|
const marky = require("marky");
|
|
10
10
|
const sdk = require("./sdk");
|
|
11
11
|
const outputs = require("./outputs");
|
|
12
|
-
const
|
|
12
|
+
const server = require("./ipc");
|
|
13
13
|
|
|
14
14
|
// replace all occurances of ${OUTPUT.ls} with outputs.get("ls") in every possible property of the `object`
|
|
15
15
|
// this is a recursive function that will go through all the properties of the object
|
package/lib/commands.js
CHANGED
package/lib/config.js
CHANGED
package/lib/ipc.js
CHANGED
|
@@ -1,50 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
const { default: nodeIPC } = require("node-ipc");
|
|
3
|
-
const { IPC } = nodeIPC;
|
|
1
|
+
let { emitter } = require("./events");
|
|
4
2
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
process.on("connect", () => {
|
|
4
|
+
broadcast("connect", {});
|
|
5
|
+
});
|
|
8
6
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
process.on("message", (raw) => {
|
|
8
|
+
let { event, data } = JSON.parse(raw);
|
|
9
|
+
emitter.emit(event, data);
|
|
10
|
+
});
|
|
13
11
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
this.ipc = new IPC();
|
|
18
|
-
this.eventListeners = new Map();
|
|
19
|
-
|
|
20
|
-
this.ipc.config.id = `testdriverai_${process.pid}`;
|
|
21
|
-
this.ipc.config.retry = 50;
|
|
22
|
-
this.ipc.config.silent = true;
|
|
23
|
-
|
|
24
|
-
this.ipc.serve(() => {
|
|
25
|
-
this.ipc.server.on("connect", (socket) => {
|
|
26
|
-
this.ipc.server.emit(socket, "interactive", this.interactive);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
this.ipc.server.on("message", ({ event, data }) => {
|
|
30
|
-
this.emit(event, data);
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
this.ipc.server.start();
|
|
35
|
-
|
|
36
|
-
IPCServerSingleton.instance = this;
|
|
37
|
-
}
|
|
38
|
-
return IPCServerSingleton.instance;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
broadcast(event, data) {
|
|
42
|
-
if (event === "interactive") {
|
|
43
|
-
this.interactive = data;
|
|
44
|
-
}
|
|
45
|
-
this.ipc.server.broadcast("message", { event, data });
|
|
46
|
-
}
|
|
12
|
+
let broadcast = (event, data) => {
|
|
13
|
+
let d = {event, data};
|
|
14
|
+
process.send(d);
|
|
47
15
|
}
|
|
48
16
|
|
|
49
|
-
|
|
50
|
-
module.exports = { server };
|
|
17
|
+
module.exports = { broadcast, emitter };
|
package/lib/logger.js
CHANGED
package/lib/system.js
CHANGED
|
@@ -162,7 +162,9 @@ const initializeActiveWindow = async () => {
|
|
|
162
162
|
// For some reason, the import fails in certain situations
|
|
163
163
|
const activeWindow = await import("get-windows")
|
|
164
164
|
.then(({ activeWindow }) => activeWindow)
|
|
165
|
-
.catch(() =>
|
|
165
|
+
.catch(() => {
|
|
166
|
+
return null;
|
|
167
|
+
});
|
|
166
168
|
activeWindowFn = activeWindow;
|
|
167
169
|
}
|
|
168
170
|
return activeWindowFn;
|
|
@@ -174,7 +176,14 @@ const activeWin = async () => {
|
|
|
174
176
|
return "error getting active window, proceed normally";
|
|
175
177
|
} else {
|
|
176
178
|
const activeWindow = await initializeActiveWindow();
|
|
177
|
-
|
|
179
|
+
let windows;
|
|
180
|
+
try {
|
|
181
|
+
windows = await activeWindow?.();
|
|
182
|
+
} catch (e) {
|
|
183
|
+
console.error("Error getting active window", e);
|
|
184
|
+
return "error getting active window, proceed normally";
|
|
185
|
+
}
|
|
186
|
+
return windows;
|
|
178
187
|
}
|
|
179
188
|
};
|
|
180
189
|
|
package/package.json
CHANGED
package/schema.json
CHANGED
|
@@ -70,26 +70,26 @@
|
|
|
70
70
|
"keys": {
|
|
71
71
|
"type": "array",
|
|
72
72
|
"items": {
|
|
73
|
-
"type": "string"
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
73
|
+
"type": "string",
|
|
74
|
+
"enum": [
|
|
75
|
+
[
|
|
76
|
+
"backspace", "delete", "enter", "tab", "escape", "up", "down", "right", "left",
|
|
77
|
+
"home", "end", "pageup", "pagedown", "f1", "f2", "f3", "f4", "f5", "f6", "f7",
|
|
78
|
+
"f8", "f9", "f10", "f11", "f12", "f13", "f14", "f15", "f16", "f17", "f18", "f19",
|
|
79
|
+
"f20", "f21", "f22", "f23", "f24", "capslock", "command", "alt", "right_alt",
|
|
80
|
+
"control", "left_control", "right_control", "shift", "right_shift", "space",
|
|
81
|
+
"printscreen", "insert", "menu", "audio_mute", "audio_vol_down", "audio_vol_up",
|
|
82
|
+
"audio_play", "audio_stop", "audio_pause", "audio_prev", "audio_next",
|
|
83
|
+
"audio_rewind", "audio_forward", "audio_repeat", "audio_random", "numpad_lock",
|
|
84
|
+
"numpad_0", "numpad_1", "numpad_2", "numpad_3", "numpad_4", "numpad_5",
|
|
85
|
+
"numpad_6", "numpad_7", "numpad_8", "numpad_9", "numpad_+", "numpad_-",
|
|
86
|
+
"numpad_*", "numpad_/", "numpad_.", "lights_mon_up", "lights_mon_down",
|
|
87
|
+
"lights_kbd_toggle", "lights_kbd_up", "lights_kbd_down", "a", "b", "c", "d", "e",
|
|
88
|
+
"f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u",
|
|
89
|
+
"v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"
|
|
90
|
+
]
|
|
91
91
|
]
|
|
92
|
-
|
|
92
|
+
}
|
|
93
93
|
}
|
|
94
94
|
},
|
|
95
95
|
"required": [
|