testdriverai 4.1.12 → 4.1.14

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
@@ -72,8 +72,6 @@ const args = process.argv.slice(2);
72
72
 
73
73
  const commandHistoryFile = path.join(os.homedir(), ".testdriver_history");
74
74
 
75
- const delay = (t) => new Promise((resolve) => setTimeout(resolve, t));
76
-
77
75
  let getArgs = () => {
78
76
  let command = 0;
79
77
  let file = 1;
@@ -256,6 +254,7 @@ const haveAIResolveError = async (error, markdown, depth = 0, undo = false) => {
256
254
  speak("thinking...");
257
255
  notify("thinking...");
258
256
  log.log("info", chalk.dim("thinking..."), true);
257
+ log.log("info", "");
259
258
 
260
259
  let response = await sdk.req("error", {
261
260
  description: eMessage,
@@ -280,9 +279,9 @@ const check = async () => {
280
279
  return await exit(true);
281
280
  }
282
281
 
283
- await delay(3000);
284
-
282
+ log.log("info", "");
285
283
  log.log("info", chalk.dim("checking..."), "testdriver");
284
+ log.log("info", "");
286
285
 
287
286
  let thisScreenshot = await system.captureScreenBase64();
288
287
  let images = [lastScreenshot, thisScreenshot];
@@ -452,6 +451,7 @@ const assert = async (expect) => {
452
451
  speak("thinking...");
453
452
  notify("thinking...");
454
453
  log.log("info", chalk.dim("thinking..."), true);
454
+ log.log("info", "");
455
455
 
456
456
  let response = `\`\`\`yml
457
457
  commands:
@@ -477,7 +477,6 @@ const humanInput = async (currentTask, validateAndLoop = false) => {
477
477
  speak("thinking...");
478
478
  notify("thinking...");
479
479
  log.log("info", chalk.dim("thinking..."), true);
480
-
481
480
  log.log("info", "");
482
481
 
483
482
  lastScreenshot = await system.captureScreenBase64();
@@ -492,6 +491,7 @@ const humanInput = async (currentTask, validateAndLoop = false) => {
492
491
  image: lastScreenshot,
493
492
  },
494
493
  (chunk) => {
494
+
495
495
  if (chunk.type === "data") {
496
496
  mdStream.log(chunk.data);
497
497
  }
@@ -513,7 +513,6 @@ const generate = async (type, count) => {
513
513
  notify("thinking...");
514
514
 
515
515
  log.log("info", chalk.dim("thinking..."), true);
516
-
517
516
  log.log("info", "");
518
517
 
519
518
  let image = await system.captureScreenBase64();
@@ -649,8 +648,7 @@ const firstPrompt = async () => {
649
648
  if (!input.trim().length) return promptUser();
650
649
 
651
650
  emitter.emit(events.interactive, false);
652
- await setTerminalApp();
653
- // setTerminalWindowTransparency(true);
651
+ setTerminalWindowTransparency(true);
654
652
  errorCounts = {};
655
653
 
656
654
  // append this to commandHistoryFile
@@ -683,7 +681,7 @@ const firstPrompt = async () => {
683
681
  await humanInput(input, true);
684
682
  }
685
683
 
686
- // setTerminalWindowTransparency(false);
684
+ setTerminalWindowTransparency(false);
687
685
  promptUser();
688
686
  });
689
687
 
@@ -726,6 +724,7 @@ New commands will be appended.
726
724
  };
727
725
 
728
726
  let setTerminalWindowTransparency = async (hide) => {
727
+
729
728
  if (hide) {
730
729
  try {
731
730
  http
@@ -752,6 +751,7 @@ let setTerminalWindowTransparency = async (hide) => {
752
751
  return;
753
752
  }
754
753
 
754
+
755
755
  try {
756
756
  if (hide) {
757
757
  if (terminalApp) {
@@ -775,7 +775,6 @@ let summarize = async (error = null) => {
775
775
 
776
776
  log.log("info", "");
777
777
 
778
- log.log("info", chalk.cyan("summarizing"));
779
778
  log.log("info", chalk.dim("reviewing test..."), true);
780
779
 
781
780
  // let text = prompts.summarize(tasks, error);
@@ -937,9 +936,9 @@ const promptUser = () => {
937
936
  rl.prompt(true);
938
937
  };
939
938
 
940
- const setTerminalApp = async () => {
939
+ const setTerminalApp = async (win) => {
940
+
941
941
  if (terminalApp) return;
942
- let win = await system.activeWin();
943
942
  if (process.platform === "win32") {
944
943
  terminalApp = win?.title || "";
945
944
  } else {
@@ -999,7 +998,8 @@ const embed = async (file, depth) => {
999
998
  log.log("info", `${file} (end)`);
1000
999
  };
1001
1000
 
1002
- (async () => {
1001
+ const start = async () => {
1002
+
1003
1003
  // console.log(await system.getPrimaryDisplay());
1004
1004
 
1005
1005
  // @todo add-auth
@@ -1043,7 +1043,6 @@ const embed = async (file, depth) => {
1043
1043
  console.log("");
1044
1044
  }
1045
1045
 
1046
- await setTerminalApp();
1047
1046
 
1048
1047
  // should be start of new session
1049
1048
  const sessionRes = await sdk.req("session/start", {
@@ -1064,7 +1063,7 @@ const embed = async (file, depth) => {
1064
1063
  } else if (thisCommand == "init") {
1065
1064
  init();
1066
1065
  }
1067
- })();
1066
+ };
1068
1067
 
1069
1068
  process.on("uncaughtException", async (err) => {
1070
1069
  analytics.track("uncaughtException", { err });
@@ -1079,3 +1078,8 @@ process.on("unhandledRejection", async (reason, promise) => {
1079
1078
  // Optionally, you might want to exit the process
1080
1079
  await exit(true);
1081
1080
  });
1081
+
1082
+ module.exports = {
1083
+ setTerminalApp,
1084
+ start
1085
+ };
@@ -39,7 +39,10 @@ app.whenReady().then(() => {
39
39
  visibleOnFullScreen: true,
40
40
  });
41
41
  window.loadFile("overlay.html");
42
- window.show();
42
+
43
+ window.once('ready-to-show', () => {
44
+ window.showInactive();
45
+ });
43
46
 
44
47
  // open developer tools
45
48
  // window.webContents.openDevTools();
package/index.js CHANGED
@@ -1,37 +1,49 @@
1
1
  #!/usr/bin/env node
2
2
  const config = require("./lib/config.js");
3
+ const system = require("./lib/system.js");
3
4
  const { emitter, events } = require("./lib/events.js");
4
5
 
5
- if (!config.TD_OVERLAY) {
6
- require("./agent.js");
7
- } else {
8
- // Intercept all stdout and stderr calls (works with console as well)
9
- const originalStdoutWrite = process.stdout.write.bind(process.stdout);
10
- process.stdout.write = (...args) => {
11
- const [data, encoding] = args;
12
- emitter.emit(
13
- events.terminal.stdout,
14
- data.toString(typeof encoding === "string" ? encoding : undefined),
15
- );
16
- originalStdoutWrite(...args);
17
- };
6
+ (async () => {
18
7
 
19
- const originalStderrWrite = process.stderr.write.bind(process.stderr);
20
- process.stderr.write = (...args) => {
21
- const [data, encoding] = args;
22
- emitter.emit(
23
- events.terminal.stderr,
24
- data.toString(typeof encoding === "string" ? encoding : undefined),
25
- );
26
- originalStderrWrite(...args);
27
- };
8
+ let win = await system.activeWin();
28
9
 
29
- require("./lib/overlay.js")
30
- .electronProcessPromise.then(() => {
31
- require("./agent.js");
32
- })
33
- .catch((err) => {
34
- console.error(err);
35
- process.exit(1);
36
- });
37
- }
10
+ if (!config.TD_OVERLAY) {
11
+ let agent = require("./agent.js");
12
+ agent.setTerminalApp(win);
13
+ agent.start();
14
+ } else {
15
+ // Intercept all stdout and stderr calls (works with console as well)
16
+ const originalStdoutWrite = process.stdout.write.bind(process.stdout);
17
+ process.stdout.write = (...args) => {
18
+ const [data, encoding] = args;
19
+ emitter.emit(
20
+ events.terminal.stdout,
21
+ data.toString(typeof encoding === "string" ? encoding : undefined),
22
+ );
23
+ originalStdoutWrite(...args);
24
+ };
25
+
26
+ const originalStderrWrite = process.stderr.write.bind(process.stderr);
27
+ process.stderr.write = (...args) => {
28
+ const [data, encoding] = args;
29
+ emitter.emit(
30
+ events.terminal.stderr,
31
+ data.toString(typeof encoding === "string" ? encoding : undefined),
32
+ );
33
+ originalStderrWrite(...args);
34
+ };
35
+
36
+ require("./lib/overlay.js")
37
+ .electronProcessPromise.then(() => {
38
+ let agent = require("./agent.js");
39
+ agent.setTerminalApp(win);
40
+ agent.start();
41
+ })
42
+ .catch((err) => {
43
+ console.error(err);
44
+ process.exit(1);
45
+ });
46
+ }
47
+
48
+
49
+ })()
package/lib/commander.js CHANGED
@@ -7,6 +7,7 @@ const speak = require("./speak");
7
7
  const notify = require("./notify");
8
8
  const analytics = require("./analytics");
9
9
  const marky = require("marky");
10
+ const sdk = require("./sdk");
10
11
 
11
12
  // object is a json representation of the individual yml command
12
13
  // the process turns markdown -> yml -> json -> js function execution
@@ -181,7 +182,11 @@ commands:
181
182
  }
182
183
 
183
184
  let timing = marky.stop(object.command);
184
- await analytics.track("command", { data: object, depth, timing });
185
+
186
+ await Promise.all([
187
+ sdk.req('ran', { command: object.command, data: object }),
188
+ analytics.track("command", { data: object, depth, timing })
189
+ ]);
185
190
 
186
191
  return response;
187
192
  };
package/lib/commands.js CHANGED
@@ -139,6 +139,7 @@ const assert = async (assertion, shouldThrow = false, async = false) => {
139
139
  // take a screenshot
140
140
  log("info", "");
141
141
  log("info", chalk.dim("thinking..."), true);
142
+ log("info", "");
142
143
 
143
144
  if (async) {
144
145
  await sdk
@@ -200,6 +201,7 @@ const click = async (x, y, button = "left", click = "single") => {
200
201
  await delay(1000); // wait for the mouse to move
201
202
  robot.mouseClick(button, double);
202
203
  emitter.emit(events.mouseClick, { x, y, button, click });
204
+
203
205
  await redraw.wait(5000);
204
206
  return;
205
207
  };
@@ -211,6 +213,7 @@ const hover = async (x, y) => {
211
213
  y = parseInt(y);
212
214
 
213
215
  await robot.moveMouseSmooth(x, y, 0.1);
216
+
214
217
  await redraw.wait(2500);
215
218
 
216
219
  return;
@@ -237,6 +240,7 @@ let commands = {
237
240
 
238
241
  log("info", "");
239
242
  log("info", chalk.dim("thinking..."), true);
243
+ log("info", "");
240
244
 
241
245
  const mdStream = logger.createMarkdownStreamLogger();
242
246
  let response = await sdk.req(
@@ -277,6 +281,7 @@ let commands = {
277
281
  // take a screenshot
278
282
  log("info", "");
279
283
  log("info", chalk.dim("thinking..."), true);
284
+ log("info", "");
280
285
 
281
286
  const mdStream = logger.createMarkdownStreamLogger();
282
287
  let response = await sdk.req(
package/lib/config.js CHANGED
@@ -28,7 +28,7 @@ const config = {
28
28
  TD_API_ROOT: "https://api.testdriver.ai",
29
29
  TD_DEV: parseValue(process.env["DEV"]),
30
30
  TD_PROFILE: false,
31
- TD_OVERLAY: true,
31
+ TD_OVERLAY: true
32
32
  };
33
33
 
34
34
  // Find all env vars starting with TD_
@@ -3,20 +3,25 @@ const path = require("path");
3
3
  const { execSync } = require("child_process");
4
4
  const { platform } = require("./system");
5
5
  const scriptPath = path.join(__dirname, "focusWindow.ps1");
6
+ const robot = require("robotjs");
6
7
 
7
8
  // apple script that focuses on a window
8
- const appleScriptFocus = (windowName) => `
9
+ const appleScriptSetFrontmost = (windowName) => `
9
10
  tell application "System Events" to tell process "${windowName}"
10
11
  set frontmost to true
11
12
  end tell`;
12
13
 
13
- const appleScriptMin = (windowName) => `
14
- tell application "System Events"
15
- set value of attribute "AXMinimized" of every window of application process "${windowName}" to true
16
- end tell`;
14
+ // const appleScriptAXMinimized = (windowName) => `
15
+ // tell application "System Events"
16
+ // set value of attribute "AXMinimized" of every window of application process "${windowName}" to true
17
+ // end tell`;
18
+
19
+ // const appleScriptActivate = (windowName) => `
20
+ // tell application "${windowName}" to activate
21
+ // `;
17
22
 
18
- const appleScriptMax = (windowName) => `
19
- tell application "${windowName}" to activate
23
+ const appleScriptOpen = (windowName) => `
24
+ open -a "${windowName}"
20
25
  `;
21
26
 
22
27
  const runPwsh = (appName, method) => {
@@ -27,7 +32,7 @@ const runPwsh = (appName, method) => {
27
32
  async function focusApplication(appName) {
28
33
  try {
29
34
  if (platform() == "mac") {
30
- return await execSync(`osascript -e '${appleScriptFocus(appName)}'`);
35
+ return await execSync(`osascript -e '${appleScriptSetFrontmost(appName)}'`);
31
36
  } else if (platform() == "linux") {
32
37
  // TODO: This needs fixing
33
38
  return;
@@ -42,7 +47,7 @@ async function focusApplication(appName) {
42
47
  async function hideTerminal(appName) {
43
48
  try {
44
49
  if (platform() == "mac") {
45
- return await execSync(`osascript -e '${appleScriptMin(appName)}'`);
50
+ robot.keyTap('m', ['command']);
46
51
  } else if (platform() == "windows") {
47
52
  return runPwsh(appName, "Minimize");
48
53
  }
@@ -54,7 +59,7 @@ async function hideTerminal(appName) {
54
59
  async function showTerminal(appName) {
55
60
  try {
56
61
  if (platform() == "mac") {
57
- return await execSync(`osascript -e '${appleScriptMax(appName)}'`);
62
+ return await execSync(appleScriptOpen(appName));
58
63
  } else if (platform() == "windows") {
59
64
  return runPwsh(appName, "Restore");
60
65
  }
package/lib/logger.js CHANGED
@@ -44,7 +44,7 @@ const markedParsePartial = (markdown, start = 0, end = 0) => {
44
44
 
45
45
  const createMarkdownStreamLogger = () => {
46
46
  let buffer = "";
47
- console.log("");
47
+
48
48
  return {
49
49
  log: (chunk) => {
50
50
  if (typeof chunk !== "string") {
@@ -65,10 +65,11 @@ const createMarkdownStreamLogger = () => {
65
65
  }
66
66
  },
67
67
  end() {
68
- const previousConsoleOutput = markedParsePartial(buffer, 0, -1);
69
68
 
70
- const consoleOutput = markedParsePartial(buffer);
69
+ const previousConsoleOutput = markedParsePartial(buffer, 0, -1);
70
+ const consoleOutput = markedParsePartial(buffer, 0, Infinity);
71
71
  const diff = consoleOutput.replace(previousConsoleOutput, "");
72
+
72
73
  if (diff) {
73
74
  process.stdout.write(diff);
74
75
  }
package/lib/overlay.js CHANGED
@@ -10,7 +10,7 @@ ipc.config.silent = true;
10
10
 
11
11
  let electronProcess;
12
12
 
13
- console.log("Starting overlay process...");
13
+ console.log("Spawning GUI...");
14
14
 
15
15
  try {
16
16
  // Resolve the path to Electron CLI
package/lib/redraw.js CHANGED
@@ -2,6 +2,7 @@ const { captureScreenPNG } = require("./system");
2
2
  const os = require("os");
3
3
  const path = require("path");
4
4
  const { compare } = require("odiff-bin");
5
+ const logger = require("./logger");
5
6
 
6
7
  // network
7
8
  const si = require('systeminformation');
@@ -60,16 +61,6 @@ async function updateNetwork() {
60
61
  } else {
61
62
  networkSettled = false;
62
63
  }
63
-
64
- if (process.env["DEV"]) {
65
-
66
- if (!networkSettled) {
67
- console.log(chalk.red(new Date().getTime(), `,${zIndexRx}`, `,${zIndexTx}`));
68
- } else {
69
- console.log(new Date().getTime(), `,${zIndexRx}`, `,${zIndexTx}`);
70
- }
71
-
72
- }
73
64
 
74
65
  });
75
66
  }
@@ -125,10 +116,10 @@ async function checkCondition(resolve, startTime, timeoutMs) {
125
116
  let networkText = networkSettled ? chalk.green(`y`) : chalk.dim(`${Math.trunc((diffRxBytes + diffTxBytes) / networkUpdateInterval)}b/s`);
126
117
  let timeoutText = isTimeout ? chalk.green(`y`) : chalk.dim(`${Math.floor((timeElapsed)/1000)}/${(timeoutMs / 1000)}s`);
127
118
 
128
- console.log(` `, chalk.dim('redraw='), redrawText, chalk.dim('network='), networkText, chalk.dim('timeout='), timeoutText);
119
+ logger.log("debug", ` ` + chalk.dim('redraw=') + redrawText + chalk.dim(' network=') + networkText + chalk.dim(' timeout=') + timeoutText);
129
120
 
130
121
  if ((screenHasRedrawn && networkSettled) || isTimeout) {
131
- console.log('')
122
+ logger.log("debug", ` `);
132
123
  resolve("true");
133
124
  } else {
134
125
  checkCondition(resolve, startTime, timeoutMs);
@@ -136,7 +127,7 @@ async function checkCondition(resolve, startTime, timeoutMs) {
136
127
  }
137
128
 
138
129
  function wait(timeoutMs) {
139
- console.log("")
130
+ logger.log("debug", ` `);
140
131
  return new Promise((resolve) => {
141
132
  const startTime = Date.now();
142
133
  checkCondition(resolve, startTime, timeoutMs);
package/lib/sdk.js CHANGED
@@ -1,10 +1,12 @@
1
1
  const config = require("./config");
2
2
  const chalk = require("chalk");
3
3
  const session = require("./session");
4
- const package = require("../package.json");
5
- const version = package.version;
4
+ const version = 'v4.1.0';
6
5
 
7
6
  const root = config["TD_API_ROOT"];
7
+ const axios = require('axios');
8
+
9
+ const log = require('./logger');
8
10
 
9
11
  // let token = null;
10
12
 
@@ -26,7 +28,7 @@ const parseBody = async (response, body) => {
26
28
  if (!contentType.includes("json") && !contentType.includes("text")) {
27
29
  return await response.arrayBuffer();
28
30
  }
29
- body = await response.text();
31
+ body = response.data;
30
32
  }
31
33
 
32
34
  if (typeof body === "string") {
@@ -83,7 +85,7 @@ let auth = async () => {
83
85
  };
84
86
 
85
87
  try {
86
- await fetch(url, config);
88
+ await axios(url, config);
87
89
  // token = res.data.token;
88
90
  } catch (error) {
89
91
  await outputError(error);
@@ -101,83 +103,81 @@ const req = async (path, data, onChunk) => {
101
103
 
102
104
  const url = path.startsWith("/api")
103
105
  ? [root, path].join("")
104
- : [root, "api", "v" + version, "testdriver", path].join("/");
106
+ : [root, "api", version, "testdriver", path].join("/");
107
+
108
+ log.log("debug", `making request to ${url}`);
105
109
 
106
110
  const config = {
107
111
  method: "post",
108
- headers: { "Content-Type": "application/json" },
109
- body: JSON.stringify({
112
+ headers: { "Content-Type": "application/json" },
113
+ responseType: typeof onChunk === "function" ? "stream" : "json",
114
+ data: {
110
115
  ...data,
111
116
  session: session.get()?.id,
112
117
  stream: typeof onChunk === "function",
113
- }),
118
+ },
114
119
  };
115
120
 
116
121
  try {
117
- const response = await fetch(url, config);
118
- if (response.status === 301) {
119
- const redirectUrl = await response.text();
120
- return req(redirectUrl, data, onChunk);
121
- }
122
- if (response.status >= 300) {
123
- throw response;
124
- }
125
- const contentType = response.headers.get("Content-Type")?.toLowerCase();
122
+ let response;
123
+
124
+ response = await axios(url, config);
125
+
126
+ const contentType = response.headers["content-type"]?.toLowerCase();
126
127
  const isJsonl = contentType === "application/jsonl";
127
128
  let result;
129
+
128
130
  if (onChunk) {
129
131
  result = "";
130
132
  let lastLineIndex = -1;
131
- const reader = response.clone().body.getReader();
132
- while (true) {
133
- const { done, value } = await reader.read().catch((err) => {
134
- console.error("Body read failed with error:", err);
135
- return { done: true };
136
- });
137
- if (done) {
138
- break;
139
- }
140
133
 
141
- let chunk = new TextDecoder().decode(value);
134
+ await new Promise((resolve, reject) => {
142
135
 
143
- result += chunk;
144
- let events = [chunk];
145
- if (isJsonl) {
136
+ // theres some kind of race condition here that makes things resolve
137
+ // before the stream is done
138
+
139
+ response.data.on('data', (chunk) => {
140
+
141
+ result += chunk.toString();
146
142
  const lines = result.split("\n");
147
- events = lines
143
+
144
+ const events = lines
148
145
  .slice(lastLineIndex + 1, lines.length - 1)
149
146
  .filter((line) => line.length)
150
- .map((line) => JSON.parse(line));
147
+ .map((line) => JSON.parse(line));
148
+
149
+ for (const event of events) {
150
+ onChunk(event);
151
+ }
151
152
 
152
153
  lastLineIndex = lines.length - 2;
153
- }
154
- for (const chunk of events) {
155
- await onChunk(chunk);
156
- }
157
- }
154
+ });
158
155
 
159
- if (isJsonl) {
160
- const events = result
161
- .split("\n")
162
- .slice(lastLineIndex + 1)
163
- .filter((line) => line.length)
164
- .map((line) => JSON.parse(line));
165
- for (const event of events) {
166
- await onChunk(event);
167
- }
168
- }
156
+ response.data.on('end', () => {
157
+
158
+ if (isJsonl) {
159
+ const events = result
160
+ .split("\n")
161
+ .slice(lastLineIndex + 2)
162
+ .filter((line) => line.length)
163
+ .map((line) => JSON.parse(line));
164
+
165
+ for (const event of events) {
166
+ onChunk(event);
167
+ }
168
+ }
169
+
170
+ resolve();
171
+ });
172
+
173
+ response.data.on('error', (error) => {
174
+ reject(error);
175
+ });
176
+ });
169
177
  }
170
178
 
171
179
  const value = await parseBody(response, result);
172
180
 
173
- if (!path.includes("analytics")) {
174
- if (!value) {
175
- throw new Error("Unexpected empty response body");
176
- }
177
- if (!("data" in value)) {
178
- throw new Error("Missing data property in response body");
179
- }
180
- }
181
181
  return value;
182
182
  } catch (error) {
183
183
  await outputError(error);
package/lib/system.js CHANGED
@@ -46,10 +46,6 @@ const captureAndResize = async (scale = 1, silent = false) => {
46
46
  let step1 = tmpFilename();
47
47
  let step2 = tmpFilename();
48
48
 
49
- if (process.env["DEV"]) {
50
- console.log(step2);
51
- }
52
-
53
49
  await screenshot({ filename: step1, format: "png" });
54
50
 
55
51
  // Fetch the mouse position
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testdriverai",
3
- "version": "4.1.12",
3
+ "version": "4.1.14",
4
4
  "description": "Next generation autonomous AI agent for end-to-end testing of web & desktop",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -17,6 +17,7 @@
17
17
  "dependencies": {
18
18
  "@electerm/strip-ansi": "^1.0.0",
19
19
  "active-win": "^8.2.1",
20
+ "axios": "^1.7.7",
20
21
  "chalk": "^4.1.2",
21
22
  "cli-progress": "^3.12.0",
22
23
  "datadog-winston": "^1.6.0",