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 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 { server } = require("./lib/ipc.js");
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 the application calling `testdriverai`.",
769
+ "You must enable screen capture permissions for this terminal application within System Settings.",
785
770
  );
786
771
  logger.info(
787
- "Read More: https://docs.testdriver.ai/faq/screen-recording-permissions-mac-only",
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
- let markdown = `\`\`\`yaml
928
- ${yml}
929
- \`\`\``;
930
-
931
- logger.info(`Loaded test script ${thisFile}\n`);
932
-
933
- log.prettyMarkdown(`
912
+ if (yml) {
934
913
 
935
- ${markdown}
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
- New commands will be appended.
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, true);
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(true);
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=true` to run in a secure 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 {server} = require("./ipc");
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
@@ -2,7 +2,7 @@
2
2
  const sdk = require("./sdk");
3
3
  const vm = require("vm");
4
4
  const chalk = require("chalk");
5
- const server = require("./ipc").server;
5
+ const server = require("./ipc");
6
6
  const {
7
7
  captureScreenBase64,
8
8
  captureScreenPNG,
package/lib/config.js CHANGED
@@ -34,7 +34,8 @@ const config = {
34
34
  TD_SECRET: null,
35
35
  TD_VM: false,
36
36
  TD_OVERLAY_ID: null,
37
- TD_VM_RESOLUTION: [1024, 768]
37
+ TD_VM_RESOLUTION: [1024, 768],
38
+ TD_IPC_ID: `testdriverai_${process.pid}`,
38
39
  };
39
40
 
40
41
  // Find all env vars starting with TD_
package/lib/ipc.js CHANGED
@@ -1,50 +1,17 @@
1
- const { EventEmitter } = require("events");
2
- const { default: nodeIPC } = require("node-ipc");
3
- const { IPC } = nodeIPC;
1
+ let { emitter } = require("./events");
4
2
 
5
- class IPCServerSingleton extends EventEmitter {
6
- ipc = new IPC();
7
- interactive = false;
3
+ process.on("connect", () => {
4
+ broadcast("connect", {});
5
+ });
8
6
 
9
- /**
10
- * @type {Map<string, Function[]>}
11
- */
12
- eventListeners;
7
+ process.on("message", (raw) => {
8
+ let { event, data } = JSON.parse(raw);
9
+ emitter.emit(event, data);
10
+ });
13
11
 
14
- constructor() {
15
- if (!IPCServerSingleton.instance) {
16
- super();
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
- const server = new IPCServerSingleton();
50
- module.exports = { server };
17
+ module.exports = { broadcast, emitter };
package/lib/logger.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // central logger for the bot
2
2
  const winston = require("winston");
3
3
  const os = require("os");
4
- const { server } = require("./ipc");
4
+ const server = require("./ipc");
5
5
 
6
6
  // simple match for aws instance i-*
7
7
  const shouldLog =
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(() => null);
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
- return await activeWindow?.();
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testdriverai",
3
- "version": "5.4.1",
3
+ "version": "5.5.0",
4
4
  "description": "Next generation autonomous AI agent for end-to-end testing of web & desktop",
5
5
  "main": "index.js",
6
6
  "bin": {
package/schema.json CHANGED
@@ -70,26 +70,26 @@
70
70
  "keys": {
71
71
  "type": "array",
72
72
  "items": {
73
- "type": "string"
74
- },
75
- "enum": [
76
- [
77
- "backspace", "delete", "enter", "tab", "escape", "up", "down", "right", "left",
78
- "home", "end", "pageup", "pagedown", "f1", "f2", "f3", "f4", "f5", "f6", "f7",
79
- "f8", "f9", "f10", "f11", "f12", "f13", "f14", "f15", "f16", "f17", "f18", "f19",
80
- "f20", "f21", "f22", "f23", "f24", "capslock", "command", "alt", "right_alt",
81
- "control", "left_control", "right_control", "shift", "right_shift", "space",
82
- "printscreen", "insert", "menu", "audio_mute", "audio_vol_down", "audio_vol_up",
83
- "audio_play", "audio_stop", "audio_pause", "audio_prev", "audio_next",
84
- "audio_rewind", "audio_forward", "audio_repeat", "audio_random", "numpad_lock",
85
- "numpad_0", "numpad_1", "numpad_2", "numpad_3", "numpad_4", "numpad_5",
86
- "numpad_6", "numpad_7", "numpad_8", "numpad_9", "numpad_+", "numpad_-",
87
- "numpad_*", "numpad_/", "numpad_.", "lights_mon_up", "lights_mon_down",
88
- "lights_kbd_toggle", "lights_kbd_up", "lights_kbd_down", "a", "b", "c", "d", "e",
89
- "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u",
90
- "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"
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": [