testdriverai 4.2.19 → 5.0.0-beta-8

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/lib/commands.js CHANGED
@@ -7,7 +7,7 @@ const {
7
7
  platform,
8
8
  activeWin,
9
9
  } = require("./system");
10
- const keymap = require("./keymap");
10
+
11
11
  const { focusApplication } = require("./focus-application");
12
12
  const fs = require("fs").promises; // Using the promises version for async operations
13
13
  const robot = require("robotjs");
@@ -18,6 +18,16 @@ const Jimp = require("jimp");
18
18
  const os = require("os");
19
19
  const cliProgress = require("cli-progress");
20
20
  const redraw = require("./redraw");
21
+ const sandbox = require("./sandbox.js");
22
+ const config = require("./config.js");
23
+
24
+ let keymap;
25
+ if (config.TD_VM) {
26
+ keymap = require("./keymaps/sandbox.js");
27
+ } else {
28
+ keymap = require("./keymaps/robot.js");
29
+ }
30
+
21
31
  const {
22
32
  logger,
23
33
  prettyMarkdown,
@@ -177,26 +187,26 @@ const scroll = async (direction = "down", amount = 300, method = "mouse") => {
177
187
  switch (direction) {
178
188
  case "up":
179
189
  if (method === "mouse") {
180
- await robot.scrollMouse(0, amount );
190
+ config.TD_VM ? await sandbox.send({type: "scroll", amount }) : await robot.scrollMouse(0, amount );
181
191
  } else {
182
- await robot.keyTap("pageup");
192
+ config.TD_VM ? await sandbox.send({type: "press", keys: ["pageup"]}) : await robot.keyTap("pageup");
183
193
  }
184
194
  await redraw.wait(2500);
185
195
  break;
186
196
  case "down":
187
197
  if (method === "mouse") {
188
- await robot.scrollMouse(0, amount * -1);
198
+ config.TD_VM ? await sandbox.send({type: "scroll", amount: amount * -1}) : await robot.scrollMouse(0, amount * -1);
189
199
  } else {
190
- await robot.keyTap("pagedown");
200
+ config.TD_VM ? await sandbox.send({type: "press", keys: ["pagedown"] }) : await robot.keyTap("pagedown");
191
201
  }
192
202
  await redraw.wait(2500);
193
203
  break;
194
204
  case "left":
195
- await robot.scrollMouse(amount * -1, 0);
205
+ config.TD_VM ? console.log('Not Supported') : await robot.scrollMouse(amount * -1, 0);
196
206
  await redraw.wait(2500);
197
207
  break;
198
208
  case "right":
199
- await robot.scrollMouse(amount, 0);
209
+ config.TD_VM ? console.log('Not Supported') : await robot.scrollMouse(amount, 0);
200
210
  await redraw.wait(2500);
201
211
  break;
202
212
  default:
@@ -224,21 +234,32 @@ const click = async (x, y, action = "click") => {
224
234
  x = parseInt(x);
225
235
  y = parseInt(y);
226
236
 
227
- robot.moveMouseSmooth(x, y, 0.1);
228
-
237
+ config.TD_VM ? await sandbox.send({type: "moveMouse", x, y }) : await robot.moveMouseSmooth(x, y, 0.1);
229
238
  await delay(1000); // wait for the mouse to move
230
239
 
231
- if (process.platform === "darwin" && action === "right-click") {
240
+ if (!config.TD_VM && process.platform === "darwin" && action === "right-click") {
232
241
  robot.keyToggle('control', 'down', 'control');
233
242
  await delay(250);
234
243
  }
235
244
 
236
245
  if (action !== "hover") {
237
- robot.mouseClick(button, double);
246
+ if (config.TD_VM) {
247
+ if (action === "click" || action === "left-click") {
248
+ await sandbox.send({type: "leftClick" })
249
+ } else if (action === "right-click") {
250
+ await sandbox.send({type: "rightClick" })
251
+ } else if (action === "middle-click") {
252
+ await sandbox.send({type: "middleClick" })
253
+ } else if (action === "double-click") {
254
+ await sandbox.send({type: "doubleClick" })
255
+ }
256
+ } else {
257
+ robot.mouseClick(button, double);
258
+ }
238
259
  emitter.emit(events.mouseClick, { x, y, button, click });
239
260
  }
240
261
 
241
- if (process.platform === "darwin" && action === "right-click") {
262
+ if (!config.TD_VM && process.platform === "darwin" && action === "right-click") {
242
263
  await delay(250);
243
264
  robot.keyToggle('control', 'up', 'control');
244
265
  }
@@ -253,7 +274,7 @@ const hover = async (x, y) => {
253
274
  x = parseInt(x);
254
275
  y = parseInt(y);
255
276
 
256
- await robot.moveMouseSmooth(x, y, 0.1);
277
+ await sandbox.send({type: "moveMouse", x, y });
257
278
 
258
279
  await redraw.wait(2500);
259
280
 
@@ -364,7 +385,13 @@ let commands = {
364
385
  // type a string
365
386
  type: async (string, delay = 500) => {
366
387
  await redraw.start();
388
+
367
389
  string = string.toString();
390
+
391
+ if (config.TD_VM) {
392
+ await sandbox.send({type: "write", text: string });
393
+ } else {
394
+
368
395
  // there is a bug in robotjs that causes repeated characters to only be typed once
369
396
  // so we need to check for repeated characters and type them slowly if so
370
397
  const hasRepeatedChars = /(.)\1/.test(string);
@@ -372,7 +399,8 @@ let commands = {
372
399
  await robot.typeStringDelayed(string, delay);
373
400
  else
374
401
  await robot.typeString(string);
375
- await redraw.wait(5000);
402
+ }
403
+ // await redraw.wait(5000);
376
404
  return;
377
405
  },
378
406
  // press keys
@@ -395,7 +423,7 @@ let commands = {
395
423
  key = "control";
396
424
  }
397
425
 
398
- if (modifierKeys.includes(key)) {
426
+ if (!config.TD_VM && modifierKeys.includes(key)) {
399
427
  modifierKeysPressed.push(key);
400
428
  } else {
401
429
  keysPressed.push(key);
@@ -411,32 +439,40 @@ let commands = {
411
439
  });
412
440
 
413
441
  // only one key can be pressed at a time
414
- if (keysPressed.length > 1) {
442
+ if (!config.TD_VM && keysPressed.length > 1) {
415
443
  throw new AiError(
416
444
  "Only one key can be pressed at a time. However, multiple modifier keys can be pressed at the same time.",
417
445
  );
418
446
  }
419
447
 
420
448
  // make sure modifier keys are valid, multiple are allowed
421
- let modsToPress = modifierKeysPressed.map((key) => {
422
- if (keymap[key] === undefined) {
423
- logger.error(`Modifier key not found: ${key}`);
424
- throw new AiError(`Modifier key not found: ${key}`);
425
- } else {
426
- return keymap[key];
427
- }
428
- });
449
+ let modsToPress = [];
450
+
451
+ if (!config.TD_VM) {
452
+ modifierKeysPressed.map((key) => {
453
+ if (keymap[key] === undefined) {
454
+ logger.error(`Modifier key not found: ${key}`);
455
+ throw new AiError(`Modifier key not found: ${key}`);
456
+ } else {
457
+ return keymap[key];
458
+ }
459
+ });
460
+ robot.keyTap(keysPressed[0], modsToPress);
461
+ } else {
462
+ await sandbox.send({type: "press", keys: keysPressed });
463
+ }
429
464
 
430
465
  // finally, press the keys
431
- robot.keyTap(keysPressed[0], modsToPress);
432
466
 
433
467
  await redraw.wait(5000);
434
468
 
435
469
  // keyTap will release the normal keys, but will not release modifier keys
436
470
  // so we need to release the modifier keys manually
437
- modsToPress.forEach((key) => {
438
- robot.keyToggle(key, "up");
439
- });
471
+ if (!config.TD_VM) {
472
+ modsToPress.forEach((key) => {
473
+ robot.keyToggle(key, "up");
474
+ });
475
+ }
440
476
 
441
477
  return;
442
478
  },
@@ -546,15 +582,18 @@ let commands = {
546
582
 
547
583
  if (method === "keyboard") {
548
584
  try {
549
- // use robot to press CMD+F
550
- await robot.keyTap("f", commandOrControl);
551
- // keyTap will release the normal keys, but will not release modifier keys
552
- // so we need to release the modifier keys manually
553
- robot.keyToggle(commandOrControl, "up");
554
- // type the text
555
- await robot.typeStringDelayed(text, 500);
556
- await redraw.wait(5000);
557
- await robot.keyTap("escape");
585
+
586
+ if (!config.TD_VM) {
587
+ await robot.keyTap("f", commandOrControl);;
588
+ robot.keyToggle(commandOrControl, "up");
589
+ await robot.typeStringDelayed(text, 500);
590
+ } else {
591
+ await sandbox.send({type: "press", keys: ["f", commandOrControl] })
592
+ await sandbox.send({type: "write", text });
593
+ await redraw.wait(5000);
594
+ await sandbox.send({type: "press", keys: ["escape"] });
595
+ }
596
+
558
597
  } catch (e) {
559
598
  logger.error("%s", e);
560
599
  throw new AiError(
@@ -666,23 +705,28 @@ let commands = {
666
705
  },
667
706
  exec: async (cli_command, use_stderr = false, silent = false) => {
668
707
 
669
- let args = {};
670
- if (silent) {
671
- args = { stdio: [] };
672
- }
708
+ if (config.TD_VM) {
709
+ return await sandbox.send({type: "commands.run", commands: cli_command });
710
+ } else {
673
711
 
674
- const { stdout, stderr } = await exec(cli_command, args);
712
+ let args = {};
713
+ if (silent) {
714
+ args = { stdio: [] };
715
+ }
675
716
 
676
- if (!silent) {
677
- logger.info(chalk.dim(stdout), true);
678
- }
717
+ const { stdout, stderr } = await exec(cli_command, args);
679
718
 
680
- if (use_stderr) {
681
- return stderr;
682
- } else {
683
- return stdout;
684
- }
719
+ if (!silent) {
720
+ logger.info(chalk.dim(stdout), true);
721
+ }
685
722
 
723
+ if (use_stderr) {
724
+ return stderr;
725
+ } else {
726
+ return stdout;
727
+ }
728
+
729
+ }
686
730
  }
687
731
  };
688
732
 
package/lib/config.js CHANGED
@@ -26,10 +26,12 @@ const config = {
26
26
  TD_ANALYTICS: true,
27
27
  TD_NOTIFY: false,
28
28
  TD_MINIMIZE: true,
29
- TD_API_ROOT: "https://api.testdriver.ai",
29
+ TD_API_ROOT: "https://replayable-api-production.herokuapp.com",
30
+ TD_API_KEY: null,
30
31
  TD_DEV: parseValue(process.env["DEV"]),
31
32
  TD_PROFILE: false,
32
- TD_OVERLAY: true
33
+ TD_OVERLAY: true,
34
+ TD_VM: true
33
35
  };
34
36
 
35
37
  // Find all env vars starting with TD_
package/lib/events.js CHANGED
@@ -3,6 +3,7 @@ const { EventEmitter } = require("events");
3
3
  const emitter = new EventEmitter();
4
4
 
5
5
  const events = {
6
+ showWindow: "show-window",
6
7
  mouseClick: "mouse-click",
7
8
  screenCapture: {
8
9
  start: "screen-capture:start",
@@ -17,6 +18,9 @@ const events = {
17
18
  matches: {
18
19
  show: "matches:show",
19
20
  },
21
+ vm: {
22
+ show: "vm:show",
23
+ }
20
24
  };
21
25
 
22
26
  const getValues = (obj) => {
@@ -5,6 +5,22 @@ const { platform } = require("./system");
5
5
  const scriptPath = path.join(__dirname, "focusWindow.ps1");
6
6
  const robot = require("robotjs");
7
7
  const { logger } = require("./logger");
8
+ const sandbox = require("./sandbox");
9
+
10
+ async function focusVSCode() {
11
+
12
+ try {
13
+
14
+ if (platform() == "mac") {
15
+ return await execSync('open -a "Visual Studio Code"');
16
+ } else {
17
+ return await execSync('"C:\\Program Files\\Microsoft VS Code\\Code.exe"');
18
+ }
19
+ } catch (error) {
20
+ logger.error(error);
21
+ }
22
+
23
+ }
8
24
 
9
25
  // apple script that focuses on a window
10
26
  const appleScriptSetFrontmost = (windowName) => `
package/lib/generator.js CHANGED
@@ -64,6 +64,21 @@ const dumpToYML = async function (inputArray) {
64
64
 
65
65
  return yml;
66
66
  };
67
+ const rawToFormatted = async function (inputCommands) {
68
+ // inputCommands is raw yml
69
+
70
+ let json = await yaml.load(inputCommands);
71
+ // use yml dump to convert json to yml
72
+ let yml = await yaml.dump({
73
+ version: package.version,
74
+ session: session.get(),
75
+ steps: [
76
+ {commands: json.commands},
77
+ ],
78
+ });
79
+
80
+ return yml;
81
+ };
67
82
 
68
83
  const hydrateFromYML = async function (yml) {
69
84
  // use yml load to convert yml to json
@@ -79,4 +94,5 @@ module.exports = {
79
94
  dumpToYML,
80
95
  hydrateFromYML,
81
96
  jsonToManual,
97
+ rawToFormatted
82
98
  };
package/lib/init.js CHANGED
@@ -8,6 +8,12 @@ const chalk = require("chalk");
8
8
  const { Readable } = require("stream");
9
9
  const { logger } = require("./logger");
10
10
 
11
+ const validateUUID = (uuid) => {
12
+ const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
13
+ return uuidRegex.test(uuid);
14
+ };
15
+
16
+
11
17
  async function getLatestRelease(owner, repo) {
12
18
  try {
13
19
  const response = await fetch(
@@ -69,26 +75,32 @@ module.exports = async () => {
69
75
  logger.info("");
70
76
 
71
77
  const response = await prompts([
72
- // {
73
- // type: 'password',
74
- // name: 'DASHCAM_API_KEY',
75
- // message: 'API KEY (from https://app.dashcam.io/team)',
76
- // // validate: value => (validate(value) ? true : 'Invalid API Key')
77
- // },
78
78
  {
79
79
  type: "confirm",
80
- name: "TD_NOTIFY",
81
- message: "Enable desktop notifications?",
80
+ name: "TD_VM",
81
+ message: "Use Testdriver Runners? (Recommended)",
82
82
  initial: true,
83
83
  },
84
84
  {
85
- type: "confirm",
85
+ type: prev => (prev ? "password" : null),
86
+ name: 'TD_API_KEY',
87
+ message: 'API KEY (from https://app.testdriver.ai/team)',
88
+ validate: value => (validateUUID(value) ? true : 'Invalid API Key')
89
+ },
90
+ {
91
+ type: prev => (prev ? null : "confirm"),
86
92
  name: "TD_MINIMIZE",
87
93
  message: "Minimize terminal app?",
88
94
  initial: true,
89
95
  },
90
96
  {
91
- type: "confirm",
97
+ type: prev => (prev ? null : "confirm"),
98
+ name: "TD_NOTIFY",
99
+ message: "Enable desktop notifications?",
100
+ initial: true,
101
+ },
102
+ {
103
+ type: prev => (prev ? null : "confirm"),
92
104
  name: "TD_SPEAK",
93
105
  message: "Enable text to speech narration?",
94
106
  initial: true,
@@ -98,26 +110,19 @@ module.exports = async () => {
98
110
  name: "TD_ANALYTICS",
99
111
  message: "Send anonymous analytics?",
100
112
  initial: true,
101
- },
102
- {
103
- type: "text",
104
- name: "APPEND",
105
- message: "Where should we append these values?",
106
- initial: ".env",
107
- },
113
+ }
108
114
  ]);
109
115
 
110
116
  logger.info("");
111
- logger.info(chalk.dim(`Writing ${response.APPEND}...`));
117
+ logger.info(chalk.dim(`Writing .env...`));
112
118
  logger.info("");
113
119
  logger.info(`Downloading latest workflow files...`);
114
120
  logger.info("");
115
121
 
116
122
  const env = Object.entries(response)
117
- .filter(([key]) => key !== "APPEND")
118
123
  .map(([key, value]) => `${key}=${value}`)
119
124
  .join("\n");
120
- const append = path.join(process.cwd(), response.APPEND);
125
+ const append = path.join(process.cwd(), '.env');
121
126
 
122
127
  if (!fs.existsSync(append)) {
123
128
  await fs.writeFileSync(append, "");
@@ -153,11 +158,44 @@ module.exports = async () => {
153
158
  if (!fs.existsSync(testdriverGenerateFolder)) {
154
159
  fs.mkdirSync(testdriverGenerateFolder);
155
160
  }
161
+
162
+ const tdScreen = path.join(
163
+ process.cwd(),
164
+ "testdriver",
165
+ "screenshots"
166
+ );
167
+ if (!fs.existsSync(tdScreen)) {
168
+ fs.mkdirSync(tdScreen);
169
+ }
170
+ const tdScreenMac = path.join(
171
+ process.cwd(),
172
+ "testdriver",
173
+ "screenshots", "mac"
174
+ );
175
+ if (!fs.existsSync(tdScreenMac)) {
176
+ fs.mkdirSync(tdScreenMac);
177
+ }
178
+ const tdScreenWindows = path.join(
179
+ process.cwd(),
180
+ "testdriver",
181
+ "screenshots", "windows"
182
+ );
183
+ if (!fs.existsSync(tdScreenWindows)) {
184
+ fs.mkdirSync(tdScreenWindows);
185
+ }
186
+ const tdScreenLinux = path.join(
187
+ process.cwd(),
188
+ "testdriver",
189
+ "screenshots", "linux"
190
+ );
191
+ if (!fs.existsSync(tdScreenLinux)) {
192
+ fs.mkdirSync(tdScreenLinux);
193
+ }
156
194
  }
157
195
 
158
196
  logger.info("");
159
197
  logger.info(chalk.green("Testdriver setup complete!"));
160
198
  logger.info("");
161
199
  logger.info(chalk.yellow("Create a new test by running:"));
162
- logger.info("testdriverai testdriver/test.yml");
200
+ logger.info("testdriverai testdriver/test.yaml");
163
201
  };
@@ -0,0 +1,124 @@
1
+ // the ai somtimes hallucinates keys
2
+ // so this allows us to map common hallucinations to the correct key
3
+ // doesn't seem like we're making use of this yet
4
+ module.exports = module.exports = {
5
+ backspace: "backspace",
6
+ delete: "delete",
7
+ enter: "enter",
8
+ tab: "tab",
9
+ escape: "escape",
10
+ up: "up",
11
+ down: "down",
12
+ right: "right",
13
+ left: "left",
14
+ home: "home",
15
+ end: "end",
16
+ pageup: "pageup",
17
+ pagedown: "pagedown",
18
+ f1: "f1",
19
+ f2: "f2",
20
+ f3: "f3",
21
+ f4: "f4",
22
+ f5: "f5",
23
+ f6: "f6",
24
+ f7: "f7",
25
+ f8: "f8",
26
+ f9: "f9",
27
+ f10: "f10",
28
+ f11: "f11",
29
+ f12: "f12",
30
+ f13: "f13",
31
+ f14: "f14",
32
+ f15: "f15",
33
+ f16: "f16",
34
+ f17: "f17",
35
+ f18: "f18",
36
+ f19: "f19",
37
+ f20: "f20",
38
+ f21: "f21",
39
+ f22: "f22",
40
+ f23: "f23",
41
+ f24: "f24",
42
+ capslock: "capslock",
43
+ command: "command",
44
+ alt: "alt",
45
+ right_alt: "right_alt",
46
+ control: "ctrl",
47
+ left_control: "left_control",
48
+ right_control: "right_control",
49
+ shift: "shift",
50
+ right_shift: "right_shift",
51
+ space: "space",
52
+ printscreen: "printscreen",
53
+ insert: "insert",
54
+ menu: "menu",
55
+ audio_mute: "audio_mute",
56
+ audio_vol_down: "audio_vol_down",
57
+ audio_vol_up: "audio_vol_up",
58
+ audio_play: "audio_play",
59
+ audio_stop: "audio_stop",
60
+ audio_pause: "audio_pause",
61
+ audio_prev: "audio_prev",
62
+ audio_next: "audio_next",
63
+ audio_rewind: "audio_rewind",
64
+ audio_forward: "audio_forward",
65
+ audio_repeat: "audio_repeat",
66
+ audio_random: "audio_random",
67
+ numpad_lock: "numpad_lock",
68
+ numpad_0: "numpad_0",
69
+ numpad_1: "numpad_1",
70
+ numpad_2: "numpad_2",
71
+ numpad_3: "numpad_3",
72
+ numpad_4: "numpad_4",
73
+ numpad_5: "numpad_5",
74
+ numpad_6: "numpad_6",
75
+ numpad_7: "numpad_7",
76
+ numpad_8: "numpad_8",
77
+ numpad_9: "numpad_9",
78
+ "numpad_+": "numpad_+",
79
+ "numpad_-": "numpad_-",
80
+ "numpad_*": "numpad_*",
81
+ "numpad_/": "numpad_/",
82
+ "numpad_.": "numpad_.",
83
+ lights_mon_up: "lights_mon_up",
84
+ lights_mon_down: "lights_mon_down",
85
+ lights_kbd_toggle: "lights_kbd_toggle",
86
+ lights_kbd_up: "lights_kbd_up",
87
+ lights_kbd_down: "lights_kbd_down",
88
+ a: "a",
89
+ b: "b",
90
+ c: "c",
91
+ d: "d",
92
+ e: "e",
93
+ f: "f",
94
+ g: "g",
95
+ h: "h",
96
+ i: "i",
97
+ j: "j",
98
+ k: "k",
99
+ l: "l",
100
+ m: "m",
101
+ n: "n",
102
+ o: "o",
103
+ p: "p",
104
+ q: "q",
105
+ r: "r",
106
+ s: "s",
107
+ t: "t",
108
+ u: "u",
109
+ v: "v",
110
+ w: "w",
111
+ x: "x",
112
+ y: "y",
113
+ z: "z",
114
+ 0: "0",
115
+ 1: "1",
116
+ 2: "2",
117
+ 3: "3",
118
+ 4: "4",
119
+ 5: "5",
120
+ 6: "6",
121
+ 7: "7",
122
+ 8: "8",
123
+ 9: "9",
124
+ };
package/lib/logger.js CHANGED
@@ -1,6 +1,7 @@
1
1
  // central logger for the bot
2
2
  const winston = require("winston");
3
3
  const os = require("os");
4
+ const websockets = require("./websockets");
4
5
 
5
6
  // simple match for aws instance i-*
6
7
  const shouldLog =
@@ -74,7 +75,9 @@ const createMarkdownStreamLogger = () => {
74
75
  log: (chunk) => {
75
76
  if (typeof chunk !== "string") {
76
77
  return;
77
- }
78
+ }
79
+
80
+ websockets.sendToClients("output", chunk);
78
81
 
79
82
  const previousConsoleOutput = markedParsePartial(buffer, 0, -1);
80
83
 
package/lib/overlay.js CHANGED
@@ -11,8 +11,6 @@ ipc.config.silent = true;
11
11
 
12
12
  let electronProcess;
13
13
 
14
- logger.info("Spawning GUI...");
15
-
16
14
  try {
17
15
  // Resolve the path to Electron CLI
18
16
  const electronCliPath = require.resolve('electron/cli.js');
package/lib/redraw.js CHANGED
@@ -146,7 +146,9 @@ async function checkCondition(resolve, startTime, timeoutMs) {
146
146
  logger.debug(` `);
147
147
  resolve("true");
148
148
  } else {
149
- checkCondition(resolve, startTime, timeoutMs);
149
+ setTimeout(() => {
150
+ checkCondition(resolve, startTime, timeoutMs);
151
+ }, 500);
150
152
  }
151
153
  }
152
154