testdriverai 4.1.11 → 4.1.13
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 +5 -6
- package/lib/commander.js +6 -1
- package/lib/commands.js +5 -0
- package/lib/logger.js +4 -3
- package/lib/redraw.js +4 -13
- package/lib/sdk.js +56 -53
- package/lib/system.js +0 -4
- package/package.json +2 -1
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
|
-
|
|
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();
|
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
|
-
|
|
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/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
|
-
|
|
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
|
|
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/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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
|
88
|
+
await axios(url, config);
|
|
87
89
|
// token = res.data.token;
|
|
88
90
|
} catch (error) {
|
|
89
91
|
await outputError(error);
|
|
@@ -101,80 +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",
|
|
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
|
-
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
134
|
+
await new Promise((resolve, reject) => {
|
|
142
135
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
-
|
|
173
|
-
throw new Error("Unexpected empty response body");
|
|
174
|
-
}
|
|
175
|
-
if (!path.includes("analytics") && !("data" in value)) {
|
|
176
|
-
throw new Error("Missing data property in response body");
|
|
177
|
-
}
|
|
180
|
+
|
|
178
181
|
return value;
|
|
179
182
|
} catch (error) {
|
|
180
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.
|
|
3
|
+
"version": "4.1.13",
|
|
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",
|