testdriverai 5.1.1 → 5.2.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
@@ -31,7 +31,7 @@ const sanitizeFilename = require("sanitize-filename");
31
31
  const macScreenPerms = require("mac-screen-capture-permissions");
32
32
 
33
33
  // local modules
34
- const wss = require("./lib/websockets.js");
34
+ const websocketserver = require("./lib/websockets.js");
35
35
  const speak = require("./lib/speak.js");
36
36
  const analytics = require("./lib/analytics.js");
37
37
  const log = require("./lib/logger.js");
@@ -63,6 +63,7 @@ let checkCount = 0;
63
63
  let checkLimit = 7;
64
64
  let lastScreenshot = null;
65
65
  let rl;
66
+ let wss;
66
67
 
67
68
  // list of prompts that the user has given us
68
69
  let tasks = [];
@@ -837,7 +838,7 @@ const firstPrompt = async () => {
837
838
 
838
839
  rl.on("line", handleInput);
839
840
 
840
- wss.addEventListener("input", async (message) => {
841
+ config.TD_VM && wss.addEventListener("input", async (message) => {
841
842
  handleInput(message.data);
842
843
  });
843
844
 
@@ -1081,7 +1082,7 @@ ${yaml.dump(step)}
1081
1082
  };
1082
1083
 
1083
1084
  const promptUser = () => {
1084
- wss.sendToClients("done");
1085
+ config.TD_VM && wss.sendToClients("done");
1085
1086
  emitter.emit(events.interactive, true);
1086
1087
  rl.prompt(true);
1087
1088
  };
@@ -1144,6 +1145,9 @@ const embed = async (file, depth) => {
1144
1145
 
1145
1146
  const buildEnv = async () => {
1146
1147
  let win = await system.activeWin();
1148
+ if (config.TD_VM) {
1149
+ wss = websocketserver.create();
1150
+ }
1147
1151
  setTerminalApp(win);
1148
1152
  await ensureMacScreenPerms();
1149
1153
  await makeSandbox();
@@ -93,9 +93,9 @@
93
93
  }
94
94
 
95
95
  .box {
96
- border: 2px solid #B0CF34;
96
+ border: 1px solid #B0CF34;
97
97
  position: absolute;
98
-
98
+ border-radius: 5px;
99
99
  animation-duration: 5s;
100
100
  animation-delay: 0s;
101
101
  animation-timing-function: cubic-bezier(0.26, 0.53, 0.74, 1.48);
@@ -111,6 +111,29 @@
111
111
  position: absolute;
112
112
  }
113
113
 
114
+ #mouse {
115
+ margin-left: -100px;
116
+ margin-top: -100px;
117
+ width: 50px;
118
+ height: 50px;
119
+ opacity: 50%;
120
+ position: absolute;
121
+ transform: translate(-50%, -50%);
122
+ border-radius: 70%;
123
+ background: #FFD700;
124
+ }
125
+
126
+ #mouse #dot {
127
+ width: 7px;
128
+ height: 7px;
129
+ position: absolute;
130
+ top: 50%;
131
+ left: 50%;
132
+ transform: translate(-50%, -50%);
133
+ border-radius: 50%;
134
+ background-color: black;
135
+ }
136
+
114
137
  #pointer {
115
138
  width: 25px;
116
139
  height: 25px;
@@ -213,6 +236,7 @@
213
236
  <div id="main" class="container">
214
237
  <div id="boxes">
215
238
  <div id="pointer"></div>
239
+ <div id="mouse"><div id="dot"></div></div>
216
240
  </div>
217
241
  <div id="terminal-wrapper">
218
242
  <img src="td.png" alt="td" style="position: absolute; top: 20; right: 20; height: 40px; z-index: 9999; background-color: black;">
@@ -226,6 +250,7 @@
226
250
  const { ipcRenderer } = require("electron");
227
251
  const { events } = require("../lib/events.js");
228
252
 
253
+ const mouse = document.querySelector("#mouse");
229
254
  const pointer = document.querySelector("#pointer");
230
255
  const container = document.querySelector("#main");
231
256
  const screenshotElement = document.querySelector("#screenshot");
@@ -277,9 +302,20 @@
277
302
  container.style.opacity = 1
278
303
  }, 2000)
279
304
  });
305
+ ipcRenderer.on(events.mouseMove,
306
+ (event, { x, y } = {}) => {
307
+
308
+ console.log("mouseMove", x, y)
309
+
310
+ mouse.style.marginLeft = toCss(x);
311
+ mouse.style.marginTop = toCss(y);
312
+
313
+ },
314
+ );
280
315
 
281
316
  ipcRenderer.on(events.mouseClick,
282
317
  (event, { x, y, click = "single" } = {}) => {
318
+
283
319
  pointer.style.marginLeft = toCss(x);
284
320
  pointer.style.marginTop = toCss(y);
285
321
  pointer.setAttribute("class", "");
@@ -75,7 +75,7 @@ app.whenReady().then(() => {
75
75
  });
76
76
 
77
77
  // open developer tools
78
- // window.webContents.openDevTools();
78
+ window.webContents.openDevTools();
79
79
 
80
80
  ipc.serve(() => {
81
81
  for (const event of eventsArray) {
package/lib/commands.js CHANGED
@@ -1,6 +1,5 @@
1
1
  // the actual commands to interact with the system
2
2
  const sdk = require("./sdk");
3
- const vm = require('vm');
4
3
  const chalk = require("chalk");
5
4
  const {
6
5
  captureScreenBase64,
@@ -8,9 +7,10 @@ const {
8
7
  platform,
9
8
  activeWin,
10
9
  } = require("./system");
11
-
10
+ const keymap = require("./keymap");
12
11
  const { focusApplication } = require("./focus-application");
13
12
  const fs = require("fs").promises; // Using the promises version for async operations
13
+ const robot = require("robotjs");
14
14
  const { findTemplateImage } = require("./subimage/index");
15
15
  const { cwd } = require("node:process");
16
16
  const path = require("path");
@@ -18,24 +18,14 @@ 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
- let robot;
24
-
25
- let keymap;
26
- if (config.TD_VM) {
27
- keymap = require("./keymaps/sandbox.js");
28
- } else {
29
- robot = require("robotjs");
30
- keymap = require("./keymaps/robot.js");
31
- }
32
-
33
21
  const {
34
22
  logger,
35
23
  prettyMarkdown,
36
24
  createMarkdownStreamLogger,
37
25
  } = require("./logger");
38
26
  const { emitter, events } = require("./events.js");
27
+ const util = require('util');
28
+ const exec = util.promisify(require('child_process').exec);
39
29
 
40
30
  const niceSeconds = (ms) => {
41
31
  return Math.round(ms / 1000);
@@ -187,26 +177,26 @@ const scroll = async (direction = "down", amount = 300, method = "mouse") => {
187
177
  switch (direction) {
188
178
  case "up":
189
179
  if (method === "mouse") {
190
- config.TD_VM ? await sandbox.send({type: "scroll", amount }) : await robot.scrollMouse(0, amount );
180
+ await robot.scrollMouse(0, amount );
191
181
  } else {
192
- config.TD_VM ? await sandbox.send({type: "press", keys: ["pageup"]}) : await robot.keyTap("pageup");
182
+ await robot.keyTap("pageup");
193
183
  }
194
184
  await redraw.wait(2500);
195
185
  break;
196
186
  case "down":
197
187
  if (method === "mouse") {
198
- config.TD_VM ? await sandbox.send({type: "scroll", amount: amount * -1}) : await robot.scrollMouse(0, amount * -1);
188
+ await robot.scrollMouse(0, amount * -1);
199
189
  } else {
200
- config.TD_VM ? await sandbox.send({type: "press", keys: ["pagedown"] }) : await robot.keyTap("pagedown");
190
+ await robot.keyTap("pagedown");
201
191
  }
202
192
  await redraw.wait(2500);
203
193
  break;
204
194
  case "left":
205
- config.TD_VM ? console.log('Not Supported') : await robot.scrollMouse(amount * -1, 0);
195
+ await robot.scrollMouse(amount * -1, 0);
206
196
  await redraw.wait(2500);
207
197
  break;
208
198
  case "right":
209
- config.TD_VM ? console.log('Not Supported') : await robot.scrollMouse(amount, 0);
199
+ await robot.scrollMouse(amount, 0);
210
200
  await redraw.wait(2500);
211
201
  break;
212
202
  default:
@@ -229,20 +219,22 @@ const click = async (x, y, action = "click") => {
229
219
  double = true;
230
220
  }
231
221
 
232
- logger.debug(chalk.dim(`${click} ${button} clicking at ${x}, ${y}...`), true);
222
+ logger.debug(chalk.dim(`${action} ${button} clicking at ${x}, ${y}...`), true);
233
223
 
234
224
  x = parseInt(x);
235
225
  y = parseInt(y);
236
226
 
237
- config.TD_VM ? await sandbox.send({type: "moveMouse", x, y }) : await robot.moveMouseSmooth(x, y, 0.1);
227
+ robot.moveMouseSmooth(x, y, 0.1);
228
+
238
229
  await delay(1000); // wait for the mouse to move
239
230
 
240
- if (!config.TD_VM && process.platform === "darwin" && action === "right-click") {
231
+ if (process.platform === "darwin" && action === "right-click") {
241
232
  robot.keyToggle('control', 'down', 'control');
242
233
  await delay(250);
243
234
  }
244
235
 
245
236
  if (action !== "hover") {
237
+
246
238
  if (config.TD_VM) {
247
239
  if (action === "click" || action === "left-click") {
248
240
  await sandbox.send({type: "leftClick" })
@@ -252,14 +244,28 @@ const click = async (x, y, action = "click") => {
252
244
  await sandbox.send({type: "middleClick" })
253
245
  } else if (action === "double-click") {
254
246
  await sandbox.send({type: "doubleClick" })
247
+ }else if (action === "drag-start") {
248
+ await sandbox.send({type: "mousePressLeft" })
249
+ }else if (action === "drag-end") {
250
+ await sandbox.send({type: "doubleClick" })
255
251
  }
256
252
  } else {
257
- robot.mouseClick(button, double);
253
+
254
+ if (action === "drag-start") {
255
+ robot.mouseToggle("down", button);
256
+ } else if( action === "drag-end") {
257
+ robot.mouseToggle("up", button);
258
+ } else {
259
+ robot.mouseClick(button, double);
260
+ }
261
+
258
262
  }
263
+
259
264
  emitter.emit(events.mouseClick, { x, y, button, click });
265
+
260
266
  }
261
267
 
262
- if (!config.TD_VM && process.platform === "darwin" && action === "right-click") {
268
+ if (process.platform === "darwin" && action === "right-click") {
263
269
  await delay(250);
264
270
  robot.keyToggle('control', 'up', 'control');
265
271
  }
@@ -274,7 +280,7 @@ const hover = async (x, y) => {
274
280
  x = parseInt(x);
275
281
  y = parseInt(y);
276
282
 
277
- await sandbox.send({type: "moveMouse", x, y });
283
+ await robot.moveMouseSmooth(x, y, 0.1);
278
284
 
279
285
  await redraw.wait(2500);
280
286
 
@@ -385,13 +391,7 @@ let commands = {
385
391
  // type a string
386
392
  type: async (string, delay = 500) => {
387
393
  await redraw.start();
388
-
389
394
  string = string.toString();
390
-
391
- if (config.TD_VM) {
392
- await sandbox.send({type: "write", text: string });
393
- } else {
394
-
395
395
  // there is a bug in robotjs that causes repeated characters to only be typed once
396
396
  // so we need to check for repeated characters and type them slowly if so
397
397
  const hasRepeatedChars = /(.)\1/.test(string);
@@ -399,8 +399,7 @@ let commands = {
399
399
  await robot.typeStringDelayed(string, delay);
400
400
  else
401
401
  await robot.typeString(string);
402
- }
403
- // await redraw.wait(5000);
402
+ await redraw.wait(5000);
404
403
  return;
405
404
  },
406
405
  // press keys
@@ -423,7 +422,7 @@ let commands = {
423
422
  key = "control";
424
423
  }
425
424
 
426
- if (!config.TD_VM && modifierKeys.includes(key)) {
425
+ if (modifierKeys.includes(key)) {
427
426
  modifierKeysPressed.push(key);
428
427
  } else {
429
428
  keysPressed.push(key);
@@ -439,40 +438,32 @@ let commands = {
439
438
  });
440
439
 
441
440
  // only one key can be pressed at a time
442
- if (!config.TD_VM && keysPressed.length > 1) {
441
+ if (keysPressed.length > 1) {
443
442
  throw new AiError(
444
443
  "Only one key can be pressed at a time. However, multiple modifier keys can be pressed at the same time.",
445
444
  );
446
445
  }
447
446
 
448
447
  // make sure modifier keys are valid, multiple are allowed
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
- }
448
+ let modsToPress = modifierKeysPressed.map((key) => {
449
+ if (keymap[key] === undefined) {
450
+ logger.error(`Modifier key not found: ${key}`);
451
+ throw new AiError(`Modifier key not found: ${key}`);
452
+ } else {
453
+ return keymap[key];
454
+ }
455
+ });
464
456
 
465
457
  // finally, press the keys
458
+ robot.keyTap(keysPressed[0], modsToPress);
466
459
 
467
460
  await redraw.wait(5000);
468
461
 
469
462
  // keyTap will release the normal keys, but will not release modifier keys
470
463
  // so we need to release the modifier keys manually
471
- if (!config.TD_VM) {
472
- modsToPress.forEach((key) => {
473
- robot.keyToggle(key, "up");
474
- });
475
- }
464
+ modsToPress.forEach((key) => {
465
+ robot.keyToggle(key, "up");
466
+ });
476
467
 
477
468
  return;
478
469
  },
@@ -582,18 +573,15 @@ let commands = {
582
573
 
583
574
  if (method === "keyboard") {
584
575
  try {
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
-
576
+ // use robot to press CMD+F
577
+ await robot.keyTap("f", commandOrControl);
578
+ // keyTap will release the normal keys, but will not release modifier keys
579
+ // so we need to release the modifier keys manually
580
+ robot.keyToggle(commandOrControl, "up");
581
+ // type the text
582
+ await robot.typeStringDelayed(text, 500);
583
+ await redraw.wait(5000);
584
+ await robot.keyTap("escape");
597
585
  } catch (e) {
598
586
  logger.error("%s", e);
599
587
  throw new AiError(
@@ -703,26 +691,25 @@ let commands = {
703
691
  assert: async (assertion, async = false) => {
704
692
  return await assert(assertion, true, async);
705
693
  },
706
- exec: async (nodejs_code) => {
694
+ exec: async (cli_command, use_stderr = false, silent = false) => {
707
695
 
708
- // must be assigned to `result`
709
- // do not overwrite `result`
710
- // must install locally via npm install
696
+ let args = {};
697
+ if (silent) {
698
+ args = { stdio: [] };
699
+ }
711
700
 
712
- const context = vm.createContext({ require, console, fs, process });
713
- const script = new vm.Script(`
714
- (async () => {
715
- ${nodejs_code}
716
- })();
717
- `);
701
+ const { stdout, stderr } = await exec(cli_command, args);
718
702
 
719
- await script.runInContext(context);
703
+ if (!silent) {
704
+ logger.info(chalk.dim(stdout), true);
705
+ }
720
706
 
721
- // wait for context.result to resolve
722
- const stepResult = await context.result;
707
+ if (use_stderr) {
708
+ return stderr;
709
+ } else {
710
+ return stdout;
711
+ }
723
712
 
724
- return stepResult;
725
-
726
713
  }
727
714
  };
728
715
 
package/lib/events.js CHANGED
@@ -5,6 +5,7 @@ const emitter = new EventEmitter();
5
5
  const events = {
6
6
  showWindow: "show-window",
7
7
  mouseClick: "mouse-click",
8
+ mouseMove: "mouse-move",
8
9
  screenCapture: {
9
10
  start: "screen-capture:start",
10
11
  end: "screen-capture:end",
package/lib/init.js CHANGED
@@ -78,7 +78,7 @@ module.exports = async () => {
78
78
  {
79
79
  type: "confirm",
80
80
  name: "TD_VM",
81
- message: "Use Testdriver Sandbox? (Recommended)",
81
+ message: "Use TestDriver Sandbox Runners? (Recommended)",
82
82
  initial: true,
83
83
  },
84
84
  {
package/lib/logger.js CHANGED
@@ -10,6 +10,7 @@ const shouldLog =
10
10
  // responsible for rendering ai markdown output
11
11
  const { marked } = require("marked");
12
12
  const { markedTerminal } = require("marked-terminal");
13
+ const { config } = require("dotenv");
13
14
 
14
15
  const { printf } = winston.format;
15
16
 
@@ -84,7 +85,9 @@ const createMarkdownStreamLogger = () => {
84
85
  return;
85
86
  }
86
87
 
87
- websockets.sendToClients("output", chunk);
88
+ if (config.TD_VM) {
89
+ websockets.sendToClients("output", chunk);
90
+ }
88
91
 
89
92
  const previousConsoleOutput = markedParsePartial(buffer, 0, -1);
90
93
 
package/lib/overlay.js CHANGED
@@ -29,7 +29,6 @@ try {
29
29
  logger.error("Failed to locate Electron CLI or start process:", error);
30
30
  }
31
31
 
32
-
33
32
  module.exports.electronProcessPromise = new Promise((resolve) => {
34
33
  ipc.connectTo("testdriverai_overlay");
35
34
  ipc.of.testdriverai_overlay.on("connect", () => {
package/lib/speak.js CHANGED
@@ -6,7 +6,6 @@ const websockets = require("./websockets");
6
6
  module.exports = (message) => {
7
7
  if (config["TD_SPEAK"]) {
8
8
  say.stop();
9
- // websockets.sendToClients("output", message);
10
9
  if (process.platform === "darwin") {
11
10
  say.speak(message, "Fred", 1.2);
12
11
  } else {
package/lib/websockets.js CHANGED
@@ -1,7 +1,10 @@
1
1
  const WebSocket = require('ws');
2
+ const config = require('./config');
2
3
 
4
+ let instance;
3
5
  class WebSocketServerSingleton {
4
6
  constructor() {
7
+
5
8
  if (!WebSocketServerSingleton.instance) {
6
9
  this.wss = new WebSocket.Server({ port: 8080 }, () => {
7
10
  });
@@ -72,7 +75,11 @@ class WebSocketServerSingleton {
72
75
  }
73
76
  }
74
77
 
75
- const instance = new WebSocketServerSingleton();
76
- Object.freeze(instance);
77
78
 
78
- module.exports = instance;
79
+ module.exports = {
80
+ create: () => {
81
+ instance = new WebSocketServerSingleton();
82
+ Object.freeze(instance);
83
+ return instance;
84
+ }
85
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testdriverai",
3
- "version": "5.1.1",
3
+ "version": "5.2.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": {