testdriverai 4.0.81 → 4.1.1
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/electron/overlay.html +302 -0
- package/electron/overlay.js +58 -0
- package/electron/terminal/xterm-fit.js +2 -0
- package/electron/terminal/xterm.css +209 -0
- package/electron/terminal/xterm.js +2 -0
- package/eslint.config.js +1 -1
- package/index.js +58 -20
- package/lib/commander.js +3 -0
- package/lib/commands.js +80 -44
- package/lib/config.js +6 -6
- package/lib/events.js +38 -0
- package/lib/overlay.js +36 -0
- package/lib/redraw.js +25 -27
- package/lib/sdk.js +56 -12
- package/lib/system.js +56 -32
- package/main.js +37 -0
- package/package.json +8 -6
package/eslint.config.js
CHANGED
|
@@ -16,6 +16,6 @@ module.exports = [
|
|
|
16
16
|
{
|
|
17
17
|
// this needs to be it's own object for some reason
|
|
18
18
|
// https://github.com/eslint/eslint/issues/17400
|
|
19
|
-
ignores: ["lib/subimage/opencv.js", "node_modules/**", ".git"],
|
|
19
|
+
ignores: ["lib/subimage/opencv.js", "node_modules/**", ".git", "electron/terminal/xterm*"],
|
|
20
20
|
},
|
|
21
21
|
];
|
package/index.js
CHANGED
|
@@ -46,6 +46,7 @@ const { showTerminal, hideTerminal } = require("./lib/focus-application");
|
|
|
46
46
|
const isValidVersion = require("./lib/valid-version");
|
|
47
47
|
const session = require("./lib/session");
|
|
48
48
|
const notify = require("./lib/notify");
|
|
49
|
+
const { emitter, events } = require("./lib/events.js");
|
|
49
50
|
|
|
50
51
|
let lastPrompt = "";
|
|
51
52
|
let terminalApp = "";
|
|
@@ -54,7 +55,7 @@ let executionHistory = [];
|
|
|
54
55
|
let errorCounts = {};
|
|
55
56
|
let errorLimit = 3;
|
|
56
57
|
let checkCount = 0;
|
|
57
|
-
let checkLimit =
|
|
58
|
+
let checkLimit = 7;
|
|
58
59
|
let lastScreenshot = null;
|
|
59
60
|
let rl;
|
|
60
61
|
|
|
@@ -160,8 +161,9 @@ function fileCompleter(line) {
|
|
|
160
161
|
}
|
|
161
162
|
|
|
162
163
|
function completer(line) {
|
|
163
|
-
let completions =
|
|
164
|
-
"
|
|
164
|
+
let completions = "/summarize /save /run /quit /assert /undo /manual".split(
|
|
165
|
+
" ",
|
|
166
|
+
);
|
|
165
167
|
if (line.startsWith("/run ")) {
|
|
166
168
|
return fileCompleter(line);
|
|
167
169
|
} else {
|
|
@@ -256,8 +258,8 @@ const haveAIResolveError = async (error, markdown, depth = 0, undo = false) => {
|
|
|
256
258
|
image,
|
|
257
259
|
});
|
|
258
260
|
|
|
259
|
-
if (response) {
|
|
260
|
-
return await actOnMarkdown(response, depth, true);
|
|
261
|
+
if (response?.data) {
|
|
262
|
+
return await actOnMarkdown(response.data, depth, true);
|
|
261
263
|
}
|
|
262
264
|
};
|
|
263
265
|
|
|
@@ -275,19 +277,33 @@ const check = async () => {
|
|
|
275
277
|
|
|
276
278
|
await delay(3000);
|
|
277
279
|
|
|
278
|
-
console.log("");
|
|
279
280
|
log.log("info", chalk.dim("checking..."), "testdriver");
|
|
280
281
|
|
|
281
|
-
let thisScreenshot =await system.captureScreenBase64();
|
|
282
|
+
let thisScreenshot = await system.captureScreenBase64();
|
|
282
283
|
let images = [lastScreenshot, thisScreenshot];
|
|
283
284
|
let mousePosition = await system.getMousePosition();
|
|
284
285
|
let activeWindow = await system.activeWin();
|
|
285
286
|
|
|
286
|
-
|
|
287
|
+
const mdStream = log.createMarkdownStreamLogger();
|
|
288
|
+
let response = await sdk.req(
|
|
289
|
+
"check",
|
|
290
|
+
{
|
|
291
|
+
tasks,
|
|
292
|
+
images,
|
|
293
|
+
mousePosition,
|
|
294
|
+
activeWindow,
|
|
295
|
+
},
|
|
296
|
+
(chunk) => {
|
|
297
|
+
if (chunk.type === "data") {
|
|
298
|
+
mdStream.log(chunk.data);
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
);
|
|
302
|
+
mdStream.end();
|
|
287
303
|
|
|
288
304
|
lastScreenshot = thisScreenshot;
|
|
289
305
|
|
|
290
|
-
return response;
|
|
306
|
+
return response.data;
|
|
291
307
|
};
|
|
292
308
|
|
|
293
309
|
// command is transformed from a single yml entry generated by the AI into a JSON object
|
|
@@ -395,9 +411,6 @@ const aiExecute = async (message, validateAndLoop = false) => {
|
|
|
395
411
|
|
|
396
412
|
let response = await check();
|
|
397
413
|
|
|
398
|
-
console.log("");
|
|
399
|
-
log.prettyMarkdown(response);
|
|
400
|
-
|
|
401
414
|
let checkCodeblocks = [];
|
|
402
415
|
try {
|
|
403
416
|
checkCodeblocks = await parser.findCodeBlocks(response);
|
|
@@ -473,11 +486,15 @@ const humanInput = async (currentTask, validateAndLoop = false) => {
|
|
|
473
486
|
activeWindow: await system.activeWin(),
|
|
474
487
|
image: lastScreenshot,
|
|
475
488
|
},
|
|
476
|
-
(chunk) =>
|
|
489
|
+
(chunk) => {
|
|
490
|
+
if (chunk.type === "data") {
|
|
491
|
+
mdStream.log(chunk.data);
|
|
492
|
+
}
|
|
493
|
+
},
|
|
477
494
|
);
|
|
478
495
|
mdStream.end();
|
|
479
496
|
|
|
480
|
-
await aiExecute(message, validateAndLoop);
|
|
497
|
+
await aiExecute(message.data, validateAndLoop);
|
|
481
498
|
|
|
482
499
|
log.log("debug", "showing prompt from humanInput response check");
|
|
483
500
|
|
|
@@ -505,11 +522,15 @@ const generate = async (type, count) => {
|
|
|
505
522
|
activeWindow: await system.activeWin(),
|
|
506
523
|
count,
|
|
507
524
|
},
|
|
508
|
-
(chunk) =>
|
|
525
|
+
(chunk) => {
|
|
526
|
+
if (chunk.type === "data") {
|
|
527
|
+
mdStream.log(chunk.data);
|
|
528
|
+
}
|
|
529
|
+
},
|
|
509
530
|
);
|
|
510
531
|
mdStream.end();
|
|
511
532
|
|
|
512
|
-
let testPrompts = await parser.findGenerativePrompts(message);
|
|
533
|
+
let testPrompts = await parser.findGenerativePrompts(message.data);
|
|
513
534
|
|
|
514
535
|
// for each testPrompt
|
|
515
536
|
for (const testPrompt of testPrompts) {
|
|
@@ -619,6 +640,7 @@ const firstPrompt = async () => {
|
|
|
619
640
|
// this is how we parse user input
|
|
620
641
|
// notice that the AI is only called if the input is not a command
|
|
621
642
|
rl.on("line", async (input) => {
|
|
643
|
+
emitter.emit(events.interactive, false);
|
|
622
644
|
await setTerminalApp();
|
|
623
645
|
|
|
624
646
|
setTerminalWindowTransparency(true);
|
|
@@ -762,7 +784,11 @@ let summarize = async (error = null) => {
|
|
|
762
784
|
error: error?.toString(),
|
|
763
785
|
tasks,
|
|
764
786
|
},
|
|
765
|
-
(chunk) =>
|
|
787
|
+
(chunk) => {
|
|
788
|
+
if (chunk.type === "data") {
|
|
789
|
+
mdStream.log(chunk.data);
|
|
790
|
+
}
|
|
791
|
+
},
|
|
766
792
|
);
|
|
767
793
|
mdStream.end();
|
|
768
794
|
|
|
@@ -771,7 +797,7 @@ let summarize = async (error = null) => {
|
|
|
771
797
|
resultFile = "/Windows/Temp/oiResult.log.log";
|
|
772
798
|
}
|
|
773
799
|
// write reply to /tmp/oiResult.log.log
|
|
774
|
-
fs.writeFileSync(resultFile, reply);
|
|
800
|
+
fs.writeFileSync(resultFile, reply.data);
|
|
775
801
|
};
|
|
776
802
|
|
|
777
803
|
// this function is responsible for saving the regression test script to a file
|
|
@@ -900,6 +926,7 @@ ${yaml.dump(step)}
|
|
|
900
926
|
};
|
|
901
927
|
|
|
902
928
|
const promptUser = () => {
|
|
929
|
+
emitter.emit(events.interactive, true);
|
|
903
930
|
rl.prompt(true);
|
|
904
931
|
};
|
|
905
932
|
|
|
@@ -964,7 +991,18 @@ const embed = async (file, depth) => {
|
|
|
964
991
|
log.log("info", `${file} (end)`);
|
|
965
992
|
};
|
|
966
993
|
|
|
994
|
+
emitter.on(events.interactive, (data) => {
|
|
995
|
+
if (!terminalApp) return;
|
|
996
|
+
if (data) {
|
|
997
|
+
showTerminal(terminalApp);
|
|
998
|
+
} else {
|
|
999
|
+
hideTerminal(terminalApp);
|
|
1000
|
+
}
|
|
1001
|
+
});
|
|
1002
|
+
|
|
967
1003
|
(async () => {
|
|
1004
|
+
// console.log(await system.getPrimaryDisplay());
|
|
1005
|
+
|
|
968
1006
|
// @todo add-auth
|
|
969
1007
|
// if (!process.env.DASHCAM_API_KEY) {
|
|
970
1008
|
// log.log('info', chalk.red(`You must supply an API key`), 'system')
|
|
@@ -1009,13 +1047,13 @@ const embed = async (file, depth) => {
|
|
|
1009
1047
|
await setTerminalApp();
|
|
1010
1048
|
|
|
1011
1049
|
// should be start of new session
|
|
1012
|
-
|
|
1050
|
+
const sessionRes = await sdk.req("session/start", {
|
|
1013
1051
|
systemInformationOsInfo: await system.getSystemInformationOsInfo(),
|
|
1014
1052
|
mousePosition: await system.getMousePosition(),
|
|
1015
1053
|
activeWindow: await system.activeWin(),
|
|
1016
1054
|
});
|
|
1017
1055
|
|
|
1018
|
-
session.set(sessionRes);
|
|
1056
|
+
session.set(sessionRes.data);
|
|
1019
1057
|
|
|
1020
1058
|
analytics.track("command", { command: thisCommand, file: thisFile });
|
|
1021
1059
|
|
package/lib/commander.js
CHANGED
|
@@ -11,6 +11,9 @@ const marky = require("marky");
|
|
|
11
11
|
// object is a json representation of the individual yml command
|
|
12
12
|
// the process turns markdown -> yml -> json -> js function execution
|
|
13
13
|
const run = async (object, depth) => {
|
|
14
|
+
|
|
15
|
+
log("debug", { object, depth });
|
|
16
|
+
|
|
14
17
|
// success returns null
|
|
15
18
|
// if this is set, it means that we need to take more action and a new thread is spawned
|
|
16
19
|
let response = null;
|
package/lib/commands.js
CHANGED
|
@@ -20,6 +20,7 @@ const os = require("os");
|
|
|
20
20
|
const cliProgress = require("cli-progress");
|
|
21
21
|
const redraw = require("./redraw");
|
|
22
22
|
const logger = require("./logger");
|
|
23
|
+
const { emitter, events } = require("./events.js");
|
|
23
24
|
|
|
24
25
|
const niceSeconds = (ms) => {
|
|
25
26
|
return Math.round(ms / 1000);
|
|
@@ -138,7 +139,6 @@ const assert = async (assertion, shouldThrow = false, async = false) => {
|
|
|
138
139
|
// take a screenshot
|
|
139
140
|
log("info", "");
|
|
140
141
|
log("info", chalk.dim("thinking..."), true);
|
|
141
|
-
log("info", "");
|
|
142
142
|
|
|
143
143
|
if (async) {
|
|
144
144
|
await sdk
|
|
@@ -147,7 +147,7 @@ const assert = async (assertion, shouldThrow = false, async = false) => {
|
|
|
147
147
|
image: await captureScreenBase64(),
|
|
148
148
|
})
|
|
149
149
|
.then((response) => {
|
|
150
|
-
return handleAssertResponse(response);
|
|
150
|
+
return handleAssertResponse(response.data);
|
|
151
151
|
});
|
|
152
152
|
|
|
153
153
|
return true;
|
|
@@ -156,7 +156,7 @@ const assert = async (assertion, shouldThrow = false, async = false) => {
|
|
|
156
156
|
expect: assertion,
|
|
157
157
|
image: await captureScreenBase64(),
|
|
158
158
|
});
|
|
159
|
-
return handleAssertResponse(response);
|
|
159
|
+
return handleAssertResponse(response.data);
|
|
160
160
|
}
|
|
161
161
|
};
|
|
162
162
|
const scroll = async (direction = "down", amount = 300) => {
|
|
@@ -167,19 +167,19 @@ const scroll = async (direction = "down", amount = 300) => {
|
|
|
167
167
|
switch (direction) {
|
|
168
168
|
case "up":
|
|
169
169
|
await robot.keyTap("pageup");
|
|
170
|
-
await redraw.wait(
|
|
170
|
+
await redraw.wait(2500);
|
|
171
171
|
break;
|
|
172
172
|
case "down":
|
|
173
173
|
await robot.keyTap("pagedown");
|
|
174
|
-
await redraw.wait(
|
|
174
|
+
await redraw.wait(2500);
|
|
175
175
|
break;
|
|
176
176
|
case "left":
|
|
177
177
|
await robot.scrollMouse(amount * -1, 0);
|
|
178
|
-
await redraw.wait(
|
|
178
|
+
await redraw.wait(2500);
|
|
179
179
|
break;
|
|
180
180
|
case "right":
|
|
181
181
|
await robot.scrollMouse(amount, 0);
|
|
182
|
-
await redraw.wait(
|
|
182
|
+
await redraw.wait(2500);
|
|
183
183
|
break;
|
|
184
184
|
default:
|
|
185
185
|
throw new AiError("Direction not found");
|
|
@@ -199,7 +199,8 @@ const click = async (x, y, button = "left", click = "single") => {
|
|
|
199
199
|
robot.moveMouseSmooth(x, y, 0.1);
|
|
200
200
|
await delay(1000); // wait for the mouse to move
|
|
201
201
|
robot.mouseClick(button, double);
|
|
202
|
-
|
|
202
|
+
emitter.emit(events.mouseClick, { x, y, button, click });
|
|
203
|
+
await redraw.wait(5000);
|
|
203
204
|
return;
|
|
204
205
|
};
|
|
205
206
|
|
|
@@ -210,7 +211,7 @@ const hover = async (x, y) => {
|
|
|
210
211
|
y = parseInt(y);
|
|
211
212
|
|
|
212
213
|
await robot.moveMouseSmooth(x, y, 0.1);
|
|
213
|
-
await redraw.wait(
|
|
214
|
+
await redraw.wait(2500);
|
|
214
215
|
|
|
215
216
|
return;
|
|
216
217
|
};
|
|
@@ -229,14 +230,13 @@ let commands = {
|
|
|
229
230
|
action = "click",
|
|
230
231
|
button = "left",
|
|
231
232
|
clickType = "single",
|
|
232
|
-
method = "
|
|
233
|
+
method = "turbo",
|
|
233
234
|
) => {
|
|
234
|
-
text = text.toString();
|
|
235
|
+
text = text ? text.toString() : null;
|
|
235
236
|
description = description ? description.toString() : null;
|
|
236
237
|
|
|
237
238
|
log("info", "");
|
|
238
239
|
log("info", chalk.dim("thinking..."), true);
|
|
239
|
-
log("info", "");
|
|
240
240
|
|
|
241
241
|
const mdStream = logger.createMarkdownStreamLogger();
|
|
242
242
|
let response = await sdk.req(
|
|
@@ -251,14 +251,20 @@ let commands = {
|
|
|
251
251
|
description,
|
|
252
252
|
displayMultiple: 1,
|
|
253
253
|
},
|
|
254
|
-
(chunk) =>
|
|
254
|
+
(chunk) => {
|
|
255
|
+
if (chunk.type === "data") {
|
|
256
|
+
mdStream.log(chunk.data);
|
|
257
|
+
} else if (chunk.type === "closeMatches") {
|
|
258
|
+
emitter.emit(events.matches.show, chunk.data);
|
|
259
|
+
}
|
|
260
|
+
},
|
|
255
261
|
);
|
|
256
262
|
mdStream.end();
|
|
257
263
|
|
|
258
|
-
if (!response) {
|
|
264
|
+
if (!response.data) {
|
|
259
265
|
throw new AiError("No text on screen matches description", true);
|
|
260
266
|
} else {
|
|
261
|
-
return response;
|
|
267
|
+
return response.data;
|
|
262
268
|
}
|
|
263
269
|
},
|
|
264
270
|
// uses our api to find all images on screen
|
|
@@ -271,22 +277,32 @@ let commands = {
|
|
|
271
277
|
// take a screenshot
|
|
272
278
|
log("info", "");
|
|
273
279
|
log("info", chalk.dim("thinking..."), true);
|
|
274
|
-
log("info", "");
|
|
275
280
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
image
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
281
|
+
const mdStream = logger.createMarkdownStreamLogger();
|
|
282
|
+
let response = await sdk.req(
|
|
283
|
+
"hover/image",
|
|
284
|
+
{
|
|
285
|
+
needle: description,
|
|
286
|
+
image: await captureScreenBase64(),
|
|
287
|
+
intent: action,
|
|
288
|
+
button,
|
|
289
|
+
clickType,
|
|
290
|
+
displayMultiple: 1,
|
|
291
|
+
},
|
|
292
|
+
(chunk) => {
|
|
293
|
+
|
|
294
|
+
if (chunk.type === "data") {
|
|
295
|
+
mdStream.log(chunk.data);
|
|
296
|
+
} else if (chunk.type === "closeMatches") {
|
|
297
|
+
emitter.emit(events.matches.show, chunk.data);
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
);
|
|
301
|
+
mdStream.end();
|
|
284
302
|
|
|
285
303
|
if (!response?.data) {
|
|
286
304
|
throw new AiError("No text on screen matches description", true);
|
|
287
305
|
} else {
|
|
288
|
-
log("info", "");
|
|
289
|
-
logger.prettyMarkdown(response.data);
|
|
290
306
|
return response.data;
|
|
291
307
|
}
|
|
292
308
|
},
|
|
@@ -315,7 +331,7 @@ let commands = {
|
|
|
315
331
|
await redraw.start();
|
|
316
332
|
string = string.toString();
|
|
317
333
|
await robot.typeString(string);
|
|
318
|
-
await redraw.wait(
|
|
334
|
+
await redraw.wait(5000);
|
|
319
335
|
return;
|
|
320
336
|
},
|
|
321
337
|
// press keys
|
|
@@ -373,7 +389,7 @@ let commands = {
|
|
|
373
389
|
// finally, press the keys
|
|
374
390
|
robot.keyTap(keysPressed[0], modsToPress);
|
|
375
391
|
|
|
376
|
-
await redraw.wait(
|
|
392
|
+
await redraw.wait(5000);
|
|
377
393
|
|
|
378
394
|
// keyTap will release the normal keys, but will not release modifier keys
|
|
379
395
|
// so we need to release the modifier keys manually
|
|
@@ -402,10 +418,11 @@ let commands = {
|
|
|
402
418
|
let passed = false;
|
|
403
419
|
|
|
404
420
|
while (durationPassed < timeout && !passed) {
|
|
405
|
-
|
|
421
|
+
const response = await sdk.req("assert", {
|
|
406
422
|
needle: description,
|
|
407
423
|
image: await captureScreenBase64(),
|
|
408
424
|
});
|
|
425
|
+
passed = response.data;
|
|
409
426
|
|
|
410
427
|
durationPassed = new Date().getTime() - startTime;
|
|
411
428
|
if (!passed) {
|
|
@@ -433,7 +450,7 @@ let commands = {
|
|
|
433
450
|
);
|
|
434
451
|
}
|
|
435
452
|
},
|
|
436
|
-
"wait-for-text": async (text, timeout = 5000, method = "
|
|
453
|
+
"wait-for-text": async (text, timeout = 5000, method = "turbo") => {
|
|
437
454
|
await redraw.start();
|
|
438
455
|
|
|
439
456
|
log("info", chalk.dim(`waiting for text: "${text}"...`), true);
|
|
@@ -444,12 +461,21 @@ let commands = {
|
|
|
444
461
|
let passed = false;
|
|
445
462
|
|
|
446
463
|
while (durationPassed < timeout && !passed) {
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
464
|
+
const response = await sdk.req(
|
|
465
|
+
"assert/text",
|
|
466
|
+
{
|
|
467
|
+
needle: text,
|
|
468
|
+
method: method,
|
|
469
|
+
image: await captureScreenBase64(),
|
|
470
|
+
},
|
|
471
|
+
(chunk) => {
|
|
472
|
+
if (chunk.type === "closeMatches") {
|
|
473
|
+
emitter.emit(events.matches.show, chunk.data);
|
|
474
|
+
}
|
|
475
|
+
},
|
|
476
|
+
);
|
|
452
477
|
|
|
478
|
+
passed = response.data;
|
|
453
479
|
durationPassed = new Date().getTime() - startTime;
|
|
454
480
|
if (!passed && durationPassed > timeout) {
|
|
455
481
|
log(
|
|
@@ -459,7 +485,7 @@ let commands = {
|
|
|
459
485
|
),
|
|
460
486
|
true,
|
|
461
487
|
);
|
|
462
|
-
await redraw.wait(
|
|
488
|
+
await redraw.wait(5000);
|
|
463
489
|
}
|
|
464
490
|
}
|
|
465
491
|
|
|
@@ -477,7 +503,7 @@ let commands = {
|
|
|
477
503
|
text,
|
|
478
504
|
direction = "down",
|
|
479
505
|
maxDistance = 1200,
|
|
480
|
-
method = "
|
|
506
|
+
method = "turbo",
|
|
481
507
|
) => {
|
|
482
508
|
await redraw.start();
|
|
483
509
|
|
|
@@ -488,7 +514,7 @@ let commands = {
|
|
|
488
514
|
await robot.keyTap("f", commandOrControl);
|
|
489
515
|
// type the text
|
|
490
516
|
await robot.typeString(text);
|
|
491
|
-
await redraw.wait(
|
|
517
|
+
await redraw.wait(5000);
|
|
492
518
|
await robot.keyTap("escape");
|
|
493
519
|
} catch (e) {
|
|
494
520
|
console.log(e);
|
|
@@ -503,12 +529,21 @@ let commands = {
|
|
|
503
529
|
let passed = false;
|
|
504
530
|
|
|
505
531
|
while (scrollDistance <= maxDistance && !passed) {
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
532
|
+
const response = await sdk.req(
|
|
533
|
+
"assert/text",
|
|
534
|
+
{
|
|
535
|
+
needle: text,
|
|
536
|
+
method: method,
|
|
537
|
+
image: await captureScreenBase64(),
|
|
538
|
+
},
|
|
539
|
+
(chunk) => {
|
|
540
|
+
if (chunk.type === "closeMatches") {
|
|
541
|
+
emitter.emit(events.matches.show, chunk.data);
|
|
542
|
+
}
|
|
543
|
+
},
|
|
544
|
+
);
|
|
511
545
|
|
|
546
|
+
passed = response.data;
|
|
512
547
|
if (!passed) {
|
|
513
548
|
log(
|
|
514
549
|
"info",
|
|
@@ -548,11 +583,12 @@ let commands = {
|
|
|
548
583
|
let passed = false;
|
|
549
584
|
|
|
550
585
|
while (scrollDistance < maxDistance && !passed) {
|
|
551
|
-
|
|
586
|
+
const response = await sdk.req("assert", {
|
|
552
587
|
needle: `An image matching the description "${description}" appears on screen.`,
|
|
553
588
|
image: await captureScreenBase64(),
|
|
554
589
|
});
|
|
555
590
|
|
|
591
|
+
passed = response.data;
|
|
556
592
|
if (!passed) {
|
|
557
593
|
log(
|
|
558
594
|
"info",
|
package/lib/config.js
CHANGED
|
@@ -9,12 +9,11 @@ require("dotenv").config();
|
|
|
9
9
|
|
|
10
10
|
// Parse out true and false string values
|
|
11
11
|
function parseValue(value) {
|
|
12
|
-
if (value === "
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
return true;
|
|
12
|
+
if (typeof value === "string") {
|
|
13
|
+
const normalizedValue = value.toLowerCase().trim();
|
|
14
|
+
if (["true", "false"].includes(normalizedValue)) {
|
|
15
|
+
return JSON.parse(normalizedValue);
|
|
16
|
+
}
|
|
18
17
|
}
|
|
19
18
|
|
|
20
19
|
return value;
|
|
@@ -29,6 +28,7 @@ const config = {
|
|
|
29
28
|
TD_API_ROOT: "https://api.testdriver.ai",
|
|
30
29
|
TD_DEV: parseValue(process.env["DEV"]),
|
|
31
30
|
TD_PROFILE: false,
|
|
31
|
+
TD_OVERLAY: true,
|
|
32
32
|
};
|
|
33
33
|
|
|
34
34
|
// Find all env vars starting with TD_
|
package/lib/events.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const { EventEmitter } = require("events");
|
|
2
|
+
|
|
3
|
+
const emitter = new EventEmitter();
|
|
4
|
+
|
|
5
|
+
const events = {
|
|
6
|
+
mouseClick: "mouse-click",
|
|
7
|
+
screenCapture: {
|
|
8
|
+
start: "screen-capture:start",
|
|
9
|
+
end: "screen-capture:end",
|
|
10
|
+
error: "screen-capture:error",
|
|
11
|
+
},
|
|
12
|
+
interactive: "interactive",
|
|
13
|
+
terminal: {
|
|
14
|
+
stdout: "terminal:stdout",
|
|
15
|
+
stderr: "terminal:stderr",
|
|
16
|
+
},
|
|
17
|
+
matches: {
|
|
18
|
+
show: "matches:show",
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const getValues = (obj) => {
|
|
23
|
+
if (["string", "number"].includes(typeof obj)) {
|
|
24
|
+
return obj;
|
|
25
|
+
}
|
|
26
|
+
if (Array.isArray(obj)) {
|
|
27
|
+
return obj.map(getValues);
|
|
28
|
+
}
|
|
29
|
+
if ([undefined, null].includes(obj)) {
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return Object.values(obj).map(getValues).flat();
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const eventsArray = getValues(events);
|
|
37
|
+
|
|
38
|
+
module.exports = { events, emitter, eventsArray };
|
package/lib/overlay.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const path = require("path");
|
|
2
|
+
const ipc = require("node-ipc").default;
|
|
3
|
+
const { fork } = require("child_process");
|
|
4
|
+
|
|
5
|
+
const { emitter, eventsArray } = require("./events.js");
|
|
6
|
+
|
|
7
|
+
ipc.config.id = "testdriverai";
|
|
8
|
+
ipc.config.retry = 50;
|
|
9
|
+
ipc.config.silent = true;
|
|
10
|
+
|
|
11
|
+
const electronProcess = fork(
|
|
12
|
+
path.join(__dirname, "../node_modules/electron/cli.js"),
|
|
13
|
+
[path.join(__dirname, "../electron/overlay.js")],
|
|
14
|
+
{ stdio: "ignore" },
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
electronProcess.on("exit", (code) => {
|
|
18
|
+
process.exit(code);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
process.on("exit", () => electronProcess.kill("SIGTERM"));
|
|
22
|
+
process.on("SIGINT", () => electronProcess.kill("SIGTERM")); // Ctrl+C
|
|
23
|
+
process.on("SIGTERM", () => electronProcess.kill("SIGTERM")); // Termination signal
|
|
24
|
+
process.on("uncaughtException", () => electronProcess.kill("SIGTERM"));
|
|
25
|
+
|
|
26
|
+
module.exports.electronProcessPromise = new Promise((resolve) => {
|
|
27
|
+
ipc.connectTo("testdriverai_overlay");
|
|
28
|
+
ipc.of.testdriverai_overlay.on("connect", () => {
|
|
29
|
+
eventsArray.forEach((event) =>
|
|
30
|
+
emitter.on(event, (data) =>
|
|
31
|
+
ipc.of.testdriverai_overlay.emit(event, data),
|
|
32
|
+
),
|
|
33
|
+
);
|
|
34
|
+
resolve(electronProcess);
|
|
35
|
+
});
|
|
36
|
+
});
|