testdriverai 4.0.69 → 4.0.71
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/index.js +39 -29
- package/lib/commands.js +18 -14
- package/lib/init.js +8 -9
- package/lib/logger.js +45 -0
- package/lib/sdk.js +74 -57
- package/package.json +1 -2
package/index.js
CHANGED
|
@@ -459,14 +459,18 @@ const humanInput = async (currentTask, validateAndLoop = false) => {
|
|
|
459
459
|
log.log("info", "");
|
|
460
460
|
|
|
461
461
|
let image = await system.captureScreenBase64();
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
462
|
+
const mdStream = log.createMarkdownStreamLogger();
|
|
463
|
+
let message = await sdk.req(
|
|
464
|
+
"input",
|
|
465
|
+
{
|
|
466
|
+
input: currentTask,
|
|
467
|
+
mousePosition: await system.getMousePosition(),
|
|
468
|
+
activeWindow: await system.activeWin(),
|
|
469
|
+
image,
|
|
470
|
+
},
|
|
471
|
+
(chunk) => mdStream.log(chunk),
|
|
472
|
+
);
|
|
473
|
+
mdStream.end();
|
|
470
474
|
|
|
471
475
|
await aiExecute(message, validateAndLoop);
|
|
472
476
|
|
|
@@ -486,15 +490,19 @@ const generate = async (type, count) => {
|
|
|
486
490
|
log.log("info", "");
|
|
487
491
|
|
|
488
492
|
let image = await system.captureScreenBase64();
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
493
|
+
const mdStream = log.createMarkdownStreamLogger();
|
|
494
|
+
let message = await sdk.req(
|
|
495
|
+
"generate",
|
|
496
|
+
{
|
|
497
|
+
type,
|
|
498
|
+
image,
|
|
499
|
+
mousePosition: await system.getMousePosition(),
|
|
500
|
+
activeWindow: await system.activeWin(),
|
|
501
|
+
count,
|
|
502
|
+
},
|
|
503
|
+
(chunk) => mdStream.log(chunk),
|
|
504
|
+
);
|
|
505
|
+
mdStream.end();
|
|
498
506
|
|
|
499
507
|
let testPrompts = await parser.findGenerativePrompts(message);
|
|
500
508
|
|
|
@@ -743,11 +751,17 @@ let summarize = async (error = null) => {
|
|
|
743
751
|
|
|
744
752
|
log.log("info", chalk.dim("summarizing..."), true);
|
|
745
753
|
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
754
|
+
const mdStream = log.createMarkdownStreamLogger();
|
|
755
|
+
let reply = await sdk.req(
|
|
756
|
+
"summarize",
|
|
757
|
+
{
|
|
758
|
+
image,
|
|
759
|
+
error: error?.toString(),
|
|
760
|
+
tasks,
|
|
761
|
+
},
|
|
762
|
+
(chunk) => mdStream.log(chunk),
|
|
763
|
+
);
|
|
764
|
+
mdStream.end();
|
|
751
765
|
|
|
752
766
|
let resultFile = "/tmp/oiResult.log.log";
|
|
753
767
|
if (process.platform === "win32") {
|
|
@@ -755,8 +769,6 @@ let summarize = async (error = null) => {
|
|
|
755
769
|
}
|
|
756
770
|
// write reply to /tmp/oiResult.log.log
|
|
757
771
|
fs.writeFileSync(resultFile, reply);
|
|
758
|
-
|
|
759
|
-
log.prettyMarkdown(reply);
|
|
760
772
|
};
|
|
761
773
|
|
|
762
774
|
// this function is responsible for saving the regression test script to a file
|
|
@@ -800,10 +812,9 @@ ${regression}
|
|
|
800
812
|
// this will load a regression test from a file location
|
|
801
813
|
// it parses the markdown file and executes the codeblocks exactly as if they were
|
|
802
814
|
// generated by the AI in a single prompt
|
|
803
|
-
let run = async (file, overwrite = false,
|
|
804
|
-
|
|
815
|
+
let run = async (file, overwrite = false, shouldExit = true) => {
|
|
805
816
|
// parse potential string value for exit
|
|
806
|
-
|
|
817
|
+
shouldExit = shouldExit === "false" ? false : true;
|
|
807
818
|
overwrite = overwrite === "false" ? false : true;
|
|
808
819
|
|
|
809
820
|
setTerminalWindowTransparency(true);
|
|
@@ -879,11 +890,10 @@ ${yaml.dump(step)}
|
|
|
879
890
|
|
|
880
891
|
setTerminalWindowTransparency(false);
|
|
881
892
|
|
|
882
|
-
if (
|
|
893
|
+
if (shouldExit) {
|
|
883
894
|
await summarize();
|
|
884
895
|
await exit(false);
|
|
885
896
|
}
|
|
886
|
-
|
|
887
897
|
};
|
|
888
898
|
|
|
889
899
|
const promptUser = () => {
|
package/lib/commands.js
CHANGED
|
@@ -237,23 +237,27 @@ let commands = {
|
|
|
237
237
|
log("info", "");
|
|
238
238
|
log("info", chalk.dim("thinking..."), true);
|
|
239
239
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
240
|
+
const mdStream = logger.createMarkdownStreamLogger();
|
|
241
|
+
let response = await sdk.req(
|
|
242
|
+
"hover/text",
|
|
243
|
+
{
|
|
244
|
+
needle: text,
|
|
245
|
+
method,
|
|
246
|
+
image: await captureScreenBase64(),
|
|
247
|
+
intent: action,
|
|
248
|
+
button,
|
|
249
|
+
clickType,
|
|
250
|
+
description,
|
|
251
|
+
displayMultiple: 1,
|
|
252
|
+
},
|
|
253
|
+
(chunk) => mdStream.log(chunk),
|
|
254
|
+
);
|
|
255
|
+
mdStream.end();
|
|
250
256
|
|
|
251
|
-
if (!response
|
|
257
|
+
if (!response) {
|
|
252
258
|
throw new AiError("No text on screen matches description", true);
|
|
253
259
|
} else {
|
|
254
|
-
|
|
255
|
-
logger.prettyMarkdown(response.data);
|
|
256
|
-
return response.data;
|
|
260
|
+
return response;
|
|
257
261
|
}
|
|
258
262
|
},
|
|
259
263
|
// uses our api to find all images on screen
|
package/lib/init.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
const decompress = require("decompress");
|
|
2
2
|
const prompts = require("prompts");
|
|
3
3
|
const path = require("path");
|
|
4
|
-
const axios = require("axios");
|
|
5
4
|
const fs = require("fs");
|
|
6
5
|
const isValidVersion = require("./valid-version");
|
|
7
6
|
const os = require("os");
|
|
8
7
|
const chalk = require("chalk");
|
|
8
|
+
const { Readable } = require("stream");
|
|
9
9
|
|
|
10
10
|
async function getLatestRelease(owner, repo) {
|
|
11
11
|
try {
|
|
12
|
-
const response = await
|
|
12
|
+
const response = await fetch(
|
|
13
13
|
`https://api.github.com/repos/${owner}/${repo}/releases`,
|
|
14
14
|
);
|
|
15
|
-
const releases = response.
|
|
15
|
+
const releases = await response.json();
|
|
16
16
|
|
|
17
17
|
// Filter releases that are less than or equal to the version in package.json
|
|
18
18
|
const validReleases = releases.filter((release) => {
|
|
@@ -25,11 +25,7 @@ async function getLatestRelease(owner, repo) {
|
|
|
25
25
|
|
|
26
26
|
// Download the source code
|
|
27
27
|
const sourceUrl = latestRelease.tarball_url;
|
|
28
|
-
const downloadResponse = await
|
|
29
|
-
url: sourceUrl,
|
|
30
|
-
method: "GET",
|
|
31
|
-
responseType: "stream",
|
|
32
|
-
});
|
|
28
|
+
const downloadResponse = await fetch(sourceUrl);
|
|
33
29
|
|
|
34
30
|
const tmpDir = os.tmpdir();
|
|
35
31
|
const path2 = path.join(
|
|
@@ -37,9 +33,12 @@ async function getLatestRelease(owner, repo) {
|
|
|
37
33
|
`${repo}-${latestRelease.tag_name}.tar.gz`,
|
|
38
34
|
);
|
|
39
35
|
const dest = fs.createWriteStream(path2);
|
|
40
|
-
downloadResponse.data.pipe(dest);
|
|
41
36
|
|
|
42
37
|
return new Promise((resolve, reject) => {
|
|
38
|
+
Readable.fromWeb(downloadResponse.body).pipe(dest, {
|
|
39
|
+
end: true,
|
|
40
|
+
});
|
|
41
|
+
|
|
43
42
|
dest.on("finish", () => {
|
|
44
43
|
resolve(path2);
|
|
45
44
|
});
|
package/lib/logger.js
CHANGED
|
@@ -32,6 +32,50 @@ marked.use(
|
|
|
32
32
|
|
|
33
33
|
const spaceChar = " ";
|
|
34
34
|
|
|
35
|
+
const markedParsePartial = (markdown, start = 0, end = 0) => {
|
|
36
|
+
let result = marked
|
|
37
|
+
.parse(markdown)
|
|
38
|
+
.replace(/^/gm, spaceChar)
|
|
39
|
+
.trimEnd()
|
|
40
|
+
.split("\n");
|
|
41
|
+
|
|
42
|
+
if (end <= 0) {
|
|
43
|
+
end = result.length + end;
|
|
44
|
+
}
|
|
45
|
+
return result.slice(start, end).join("\n");
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const createMarkdownStreamLogger = () => {
|
|
49
|
+
let buffer = "";
|
|
50
|
+
return {
|
|
51
|
+
log: (chunk) => {
|
|
52
|
+
if (typeof chunk !== "string") {
|
|
53
|
+
log("error", "markdownStreamLogger's log method requires a string");
|
|
54
|
+
log("error", chunk);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const previousConsoleOutput = markedParsePartial(buffer, 0, -1);
|
|
59
|
+
|
|
60
|
+
buffer += chunk;
|
|
61
|
+
|
|
62
|
+
const consoleOutput = markedParsePartial(buffer, 0, -1);
|
|
63
|
+
|
|
64
|
+
process.stdout.write(consoleOutput.replace(previousConsoleOutput, ""));
|
|
65
|
+
},
|
|
66
|
+
end() {
|
|
67
|
+
const previousConsoleOutput = markedParsePartial(buffer, 0, -1);
|
|
68
|
+
|
|
69
|
+
const consoleOutput = markedParsePartial(buffer);
|
|
70
|
+
|
|
71
|
+
process.stdout.write(consoleOutput.replace(previousConsoleOutput, ""));
|
|
72
|
+
process.stdout.write("\n\n");
|
|
73
|
+
buffer = "";
|
|
74
|
+
log("silly", consoleOutput);
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
|
|
35
79
|
const prettyMarkdown = (markdown) => {
|
|
36
80
|
if (typeof markdown !== "string") {
|
|
37
81
|
log("error", "prettyMarkdown requires a string");
|
|
@@ -100,6 +144,7 @@ let loggy = {
|
|
|
100
144
|
log,
|
|
101
145
|
setDepth,
|
|
102
146
|
prettyMarkdown,
|
|
147
|
+
createMarkdownStreamLogger,
|
|
103
148
|
};
|
|
104
149
|
|
|
105
150
|
module.exports = loggy;
|
package/lib/sdk.js
CHANGED
|
@@ -1,32 +1,44 @@
|
|
|
1
1
|
const config = require("./config");
|
|
2
2
|
const chalk = require("chalk");
|
|
3
|
-
const axios = require("axios");
|
|
4
3
|
const session = require("./session");
|
|
5
4
|
const package = require("../package.json");
|
|
6
5
|
const version = package.version;
|
|
7
6
|
|
|
8
7
|
const root = config["TD_API_ROOT"];
|
|
8
|
+
const shouldStream = config["TD_STREAM_RESPONSES"];
|
|
9
9
|
|
|
10
10
|
// let token = null;
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
const outputError = async (error) => {
|
|
13
|
+
if (error instanceof Response) {
|
|
14
|
+
console.log(chalk.red(error.status), chalk.red(error.statusText));
|
|
15
|
+
await parseBody(error)
|
|
16
|
+
.then((body) => console.log(chalk.red(body)))
|
|
17
|
+
.catch(() => {});
|
|
18
|
+
} else {
|
|
19
|
+
console.error("Error:", error);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
14
22
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
const parseBody = async (response, body) => {
|
|
24
|
+
const contentType = response.headers.get("Content-Type")?.toLowerCase();
|
|
25
|
+
try {
|
|
26
|
+
if (body === null || body === undefined) {
|
|
27
|
+
if (!contentType.includes("json") && !contentType.includes("text")) {
|
|
28
|
+
return await response.arrayBuffer();
|
|
29
|
+
}
|
|
30
|
+
if (contentType.includes("json")) {
|
|
31
|
+
return await response.json();
|
|
32
|
+
}
|
|
33
|
+
return await response.text();
|
|
25
34
|
}
|
|
26
|
-
if (
|
|
27
|
-
|
|
28
|
-
console.log(e.response.data.problems.join("\n"));
|
|
35
|
+
if (typeof body === "string" && contentType.includes("json")) {
|
|
36
|
+
return JSON.parse(body);
|
|
29
37
|
}
|
|
38
|
+
return body;
|
|
39
|
+
} catch (err) {
|
|
40
|
+
console.log(chalk.red("Parsing Error", err));
|
|
41
|
+
throw err;
|
|
30
42
|
}
|
|
31
43
|
};
|
|
32
44
|
|
|
@@ -38,10 +50,9 @@ let auth = async () => {
|
|
|
38
50
|
// process.exit(1);
|
|
39
51
|
// }
|
|
40
52
|
|
|
41
|
-
|
|
53
|
+
const url = [root, "auth/exchange-api-key"].join("/");
|
|
54
|
+
const config = {
|
|
42
55
|
method: "post",
|
|
43
|
-
maxBodyLength: Infinity,
|
|
44
|
-
url: [root, "auth/exchange-api-key"].join("/"),
|
|
45
56
|
headers: {
|
|
46
57
|
"Content-Type": "application/json",
|
|
47
58
|
},
|
|
@@ -49,15 +60,15 @@ let auth = async () => {
|
|
|
49
60
|
};
|
|
50
61
|
|
|
51
62
|
try {
|
|
52
|
-
await
|
|
63
|
+
await fetch(url, config);
|
|
53
64
|
// token = res.data.token;
|
|
54
|
-
} catch (
|
|
55
|
-
outputError(
|
|
65
|
+
} catch (error) {
|
|
66
|
+
await outputError(error);
|
|
56
67
|
process.exit(1);
|
|
57
68
|
}
|
|
58
69
|
};
|
|
59
70
|
|
|
60
|
-
|
|
71
|
+
const req = async (path, data, onChunk) => {
|
|
61
72
|
// for each value of data, if it is empty remove it
|
|
62
73
|
for (let key in data) {
|
|
63
74
|
if (!data[key]) {
|
|
@@ -65,48 +76,54 @@ let req = async (path, data) => {
|
|
|
65
76
|
}
|
|
66
77
|
}
|
|
67
78
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
let dataCopy = JSON.parse(data);
|
|
72
|
-
delete dataCopy.image;
|
|
79
|
+
const url = path.startsWith("/api")
|
|
80
|
+
? [root, path].join("")
|
|
81
|
+
: [root, "api", "v" + version, "testdriver", path].join("/");
|
|
73
82
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
let config = {
|
|
83
|
+
const config = {
|
|
77
84
|
method: "post",
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
},
|
|
84
|
-
data,
|
|
85
|
+
headers: { "Content-Type": "application/json" },
|
|
86
|
+
body: JSON.stringify({
|
|
87
|
+
...data,
|
|
88
|
+
session: session.get()?.id,
|
|
89
|
+
stream: typeof onChunk === "function",
|
|
90
|
+
}),
|
|
85
91
|
};
|
|
86
92
|
|
|
87
|
-
let response;
|
|
88
|
-
let redirect = null;
|
|
89
93
|
try {
|
|
90
|
-
response = await
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
94
|
+
const response = await fetch(url, config);
|
|
95
|
+
if (response.status === 301) {
|
|
96
|
+
const redirectUrl = await response.text();
|
|
97
|
+
return req(redirectUrl, data, onChunk);
|
|
98
|
+
}
|
|
99
|
+
if (response.status >= 300) {
|
|
100
|
+
throw response;
|
|
97
101
|
}
|
|
98
|
-
}
|
|
99
102
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
103
|
+
let result;
|
|
104
|
+
if (onChunk) {
|
|
105
|
+
result = "";
|
|
106
|
+
const reader = response.body.getReader();
|
|
107
|
+
while (true) {
|
|
108
|
+
const { done, value } = await reader.read();
|
|
109
|
+
if (done) {
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const chunk = new TextDecoder().decode(value);
|
|
114
|
+
result += chunk;
|
|
115
|
+
if (shouldStream) {
|
|
116
|
+
await onChunk(chunk);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (!shouldStream) {
|
|
120
|
+
await onChunk(result);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
105
123
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
return response.data;
|
|
124
|
+
return parseBody(response, result);
|
|
125
|
+
} catch (error) {
|
|
126
|
+
await outputError(error);
|
|
110
127
|
}
|
|
111
128
|
};
|
|
112
129
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "testdriverai",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.71",
|
|
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,7 +17,6 @@
|
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@electerm/strip-ansi": "^1.0.0",
|
|
19
19
|
"active-win": "^8.2.1",
|
|
20
|
-
"axios": "^1.6.8",
|
|
21
20
|
"chalk": "^4.1.2",
|
|
22
21
|
"cli-progress": "^3.12.0",
|
|
23
22
|
"datadog-winston": "^1.6.0",
|