testdriverai 4.2.20 → 5.0.0-beta.2
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/.github/dependabot.yml +1 -1
- package/.github/workflows/test_interp.yml +1 -1
- package/.github/workflows/testdriver.yml +28 -0
- package/README.md +3 -2
- package/agent.js +90 -24
- package/electron/overlay.html +38 -5
- package/electron/overlay.js +58 -25
- package/electron/td.png +0 -0
- package/index.js +2 -2
- package/lib/commander.js +10 -2
- package/lib/commands.js +94 -50
- package/lib/config.js +3 -1
- package/lib/events.js +4 -0
- package/lib/focus-application.js +16 -0
- package/lib/generator.js +16 -0
- package/lib/init.js +59 -21
- package/lib/keymaps/sandbox.js +124 -0
- package/lib/logger.js +4 -1
- package/lib/overlay.js +0 -2
- package/lib/redraw.js +3 -1
- package/lib/sandbox.js +52 -0
- package/lib/speak.js +3 -0
- package/lib/system.js +67 -23
- package/lib/websockets.js +78 -0
- package/package-lock.json +8678 -0
- package/package.json +6 -5
- package/test.md +8 -0
- /package/lib/{keymap.js → keymaps/robot.js} +0 -0
package/.github/dependabot.yml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# To get started with Dependabot version updates, you'll need to specify which
|
|
2
2
|
# package ecosystems to update and where the package manifests are located.
|
|
3
3
|
# Please see the documentation for all configuration options:
|
|
4
|
-
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.
|
|
4
|
+
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yaml-file
|
|
5
5
|
|
|
6
6
|
version: 2
|
|
7
7
|
updates:
|
|
@@ -17,7 +17,7 @@ jobs:
|
|
|
17
17
|
branch: main
|
|
18
18
|
key: ${{secrets.TESTDRIVER_API_KEY}}
|
|
19
19
|
prompt: |
|
|
20
|
-
1. /run test.
|
|
20
|
+
1. /run test.yaml
|
|
21
21
|
prerun: |
|
|
22
22
|
Start-Process "C:/Program Files/Google/Chrome/Application/chrome.exe" -ArgumentList "--start-maximized", "${{ env.WEBSITE_URL }}"
|
|
23
23
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: TestDriver.ai
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: ["main"]
|
|
6
|
+
pull_request:
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
name: "TestDriver"
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- uses: testdriverai/action@main
|
|
15
|
+
with:
|
|
16
|
+
key: ${{secrets.TESTDRIVER_API_KEY}}
|
|
17
|
+
prompt: |
|
|
18
|
+
1. /run testdriver/testdriver.yml
|
|
19
|
+
prerun: |
|
|
20
|
+
cd $env:TEMP
|
|
21
|
+
npm init -y
|
|
22
|
+
npm install dashcam-chrome
|
|
23
|
+
Start-Process "C:/Program Files/Google/Chrome/Application/chrome.exe" -ArgumentList "--start-maximized", "--load-extension=$(pwd)/node_modules/dashcam-chrome/build", "${{ env.WEBSITE_URL }}"
|
|
24
|
+
exit
|
|
25
|
+
env:
|
|
26
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
27
|
+
FORCE_COLOR: "3"
|
|
28
|
+
WEBSITE_URL: "https://example.com" # Define the website URL here
|
package/README.md
CHANGED
|
@@ -60,7 +60,7 @@ npm install testdriverai -g
|
|
|
60
60
|
Let's show TestDriver what we want to test. Run the following command:
|
|
61
61
|
|
|
62
62
|
```sh
|
|
63
|
-
testdriverai .testdriver/test.
|
|
63
|
+
testdriverai .testdriver/test.yaml
|
|
64
64
|
```
|
|
65
65
|
|
|
66
66
|
## Reset the test state
|
|
@@ -131,7 +131,7 @@ If something didn't work, you can use `/undo` to remove all of the test steps ad
|
|
|
131
131
|
Now it's time to make sure the test plan works before we deploy it. Use testdriver run to run the test file you just created with /save .
|
|
132
132
|
|
|
133
133
|
```sh
|
|
134
|
-
testdriverai run testdriver/test.
|
|
134
|
+
testdriverai run testdriver/test.yaml
|
|
135
135
|
```
|
|
136
136
|
|
|
137
137
|
Make sure to reset the test state!
|
|
@@ -147,3 +147,4 @@ gh pr create --web
|
|
|
147
147
|
```
|
|
148
148
|
|
|
149
149
|
Your test will run on every commit and the results will be posted as a Dashcam.io video within your GitHub summary! Learn more about deploying on CI [here](https://docs.testdriver.ai/continuous-integration/overview).
|
|
150
|
+
# vscode
|
package/agent.js
CHANGED
|
@@ -31,6 +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
35
|
const speak = require("./lib/speak.js");
|
|
35
36
|
const analytics = require("./lib/analytics.js");
|
|
36
37
|
const log = require("./lib/logger.js");
|
|
@@ -42,6 +43,7 @@ const sdk = require("./lib/sdk.js");
|
|
|
42
43
|
const commands = require("./lib/commands.js");
|
|
43
44
|
const init = require("./lib/init.js");
|
|
44
45
|
const config = require("./lib/config.js");
|
|
46
|
+
const sandbox = require("./lib/sandbox.js");
|
|
45
47
|
|
|
46
48
|
const { showTerminal, hideTerminal } = require("./lib/focus-application.js");
|
|
47
49
|
const isValidVersion = require("./lib/valid-version.js");
|
|
@@ -106,14 +108,14 @@ let getArgs = () => {
|
|
|
106
108
|
fs.mkdirSync(testdriverFolder);
|
|
107
109
|
}
|
|
108
110
|
|
|
109
|
-
args[file] = "testdriver/testdriver.
|
|
111
|
+
args[file] = "testdriver/testdriver.yaml";
|
|
110
112
|
}
|
|
111
113
|
|
|
112
114
|
// turn args[file] into local path
|
|
113
115
|
if (args[file]) {
|
|
114
|
-
args[file] =
|
|
115
|
-
if (!args[file].endsWith(".
|
|
116
|
-
args[file] += ".
|
|
116
|
+
args[file] = path.join(process.cwd(), args[file]);
|
|
117
|
+
if (!args[file].endsWith(".yaml")) {
|
|
118
|
+
args[file] += ".yaml";
|
|
117
119
|
}
|
|
118
120
|
}
|
|
119
121
|
|
|
@@ -167,7 +169,7 @@ function fileCompleter(line) {
|
|
|
167
169
|
}
|
|
168
170
|
|
|
169
171
|
function completer(line) {
|
|
170
|
-
let completions = "/summarize /save /run /quit /assert /undo /manual".split(
|
|
172
|
+
let completions = "/summarize /save /run /quit /assert /undo /manual /yml".split(
|
|
171
173
|
" ",
|
|
172
174
|
);
|
|
173
175
|
if (line.startsWith("/run ")) {
|
|
@@ -266,6 +268,7 @@ const haveAIResolveError = async (error, markdown, depth = 0, undo = true) => {
|
|
|
266
268
|
|
|
267
269
|
speak("thinking...");
|
|
268
270
|
notify("thinking...");
|
|
271
|
+
|
|
269
272
|
logger.info(chalk.dim("thinking..."), true);
|
|
270
273
|
logger.info("");
|
|
271
274
|
|
|
@@ -374,14 +377,16 @@ const runCommand = async (command, depth) => {
|
|
|
374
377
|
let lastCommand = new Date().getTime();
|
|
375
378
|
let csv = [["command,time"]];
|
|
376
379
|
|
|
377
|
-
const executeCommands = async (commands, depth, pushToHistory = false) => {
|
|
380
|
+
const executeCommands = async (commands, depth, pushToHistory = false, dry = false) => {
|
|
378
381
|
if (commands?.length) {
|
|
379
382
|
for (const command of commands) {
|
|
380
383
|
if (pushToHistory) {
|
|
381
384
|
executionHistory[executionHistory.length - 1]?.commands.push(command);
|
|
382
385
|
}
|
|
383
386
|
|
|
384
|
-
|
|
387
|
+
if (!dry) {
|
|
388
|
+
await runCommand(command, depth);
|
|
389
|
+
}
|
|
385
390
|
|
|
386
391
|
let timeToComplete = (new Date().getTime() - lastCommand) / 1000;
|
|
387
392
|
// logger.info(timeToComplete, 'seconds')
|
|
@@ -394,7 +399,7 @@ const executeCommands = async (commands, depth, pushToHistory = false) => {
|
|
|
394
399
|
|
|
395
400
|
// note that commands are run in a recursive loop, so that the AI can respond to the output of the commands
|
|
396
401
|
// like `click-image` and `click-text` for example
|
|
397
|
-
const executeCodeBlocks = async (codeblocks, depth, pushToHistory = false) => {
|
|
402
|
+
const executeCodeBlocks = async (codeblocks, depth, pushToHistory = false, dry = false) => {
|
|
398
403
|
depth = depth + 1;
|
|
399
404
|
|
|
400
405
|
logger.debug("%j", { message: "execute code blocks", depth });
|
|
@@ -412,20 +417,20 @@ const executeCodeBlocks = async (codeblocks, depth, pushToHistory = false) => {
|
|
|
412
417
|
);
|
|
413
418
|
}
|
|
414
419
|
|
|
415
|
-
await executeCommands(commands, depth, pushToHistory);
|
|
420
|
+
await executeCommands(commands, depth, pushToHistory, dry);
|
|
416
421
|
}
|
|
417
422
|
};
|
|
418
423
|
|
|
419
424
|
// this is the main function that interacts with the ai, runs commands, and checks the results
|
|
420
425
|
// notice that depth is 0 here. when this function resolves, the task is considered complete
|
|
421
426
|
// notice the call to `check()` which validates the prompt is complete
|
|
422
|
-
const aiExecute = async (message, validateAndLoop = false) => {
|
|
427
|
+
const aiExecute = async (message, validateAndLoop = false, dry = false) => {
|
|
423
428
|
executionHistory.push({ prompt: lastPrompt, commands: [] });
|
|
424
429
|
|
|
425
430
|
logger.debug("kicking off exploratory loop");
|
|
426
|
-
|
|
431
|
+
|
|
427
432
|
// kick everything off
|
|
428
|
-
await actOnMarkdown(message, 0, true);
|
|
433
|
+
await actOnMarkdown(message, 0, true, dry);
|
|
429
434
|
|
|
430
435
|
if (validateAndLoop) {
|
|
431
436
|
logger.debug("exploratory loop resolved, check your work");
|
|
@@ -509,7 +514,7 @@ const assert = async (expect) => {
|
|
|
509
514
|
logger.info(chalk.dim("thinking..."), true);
|
|
510
515
|
logger.info("");
|
|
511
516
|
|
|
512
|
-
let response = `\`\`\`
|
|
517
|
+
let response = `\`\`\`yaml
|
|
513
518
|
commands:
|
|
514
519
|
- command: assert
|
|
515
520
|
expect: ${expect}
|
|
@@ -522,11 +527,11 @@ commands:
|
|
|
522
527
|
|
|
523
528
|
// this function responds to the result of `promptUser()` which is the user input
|
|
524
529
|
// it kicks off the exploratory loop, which is the main function that interacts with the AI
|
|
525
|
-
const
|
|
530
|
+
const exploratoryLoop = async (currentTask, dry = false, validateAndLoop = false) => {
|
|
526
531
|
lastPrompt = currentTask;
|
|
527
532
|
checkCount = 0;
|
|
528
533
|
|
|
529
|
-
logger.debug("
|
|
534
|
+
logger.debug("exploratoryLoop called");
|
|
530
535
|
|
|
531
536
|
tasks.push(currentTask);
|
|
532
537
|
|
|
@@ -554,11 +559,11 @@ const humanInput = async (currentTask, validateAndLoop = false) => {
|
|
|
554
559
|
);
|
|
555
560
|
mdStream.end();
|
|
556
561
|
|
|
557
|
-
await aiExecute(message.data, validateAndLoop);
|
|
562
|
+
await aiExecute(message.data, validateAndLoop, dry);
|
|
558
563
|
|
|
559
|
-
logger.debug("showing prompt from
|
|
564
|
+
logger.debug("showing prompt from exploratoryLoop response check");
|
|
560
565
|
|
|
561
|
-
await save({ silent:
|
|
566
|
+
await save({ silent: false });
|
|
562
567
|
};
|
|
563
568
|
|
|
564
569
|
const generate = async (type, count, baseYaml, skipYaml = false) => {
|
|
@@ -663,7 +668,7 @@ ${yml}
|
|
|
663
668
|
};
|
|
664
669
|
|
|
665
670
|
// this function is responsible for starting the recursive process of executing codeblocks
|
|
666
|
-
const actOnMarkdown = async (content, depth, pushToHistory = false) => {
|
|
671
|
+
const actOnMarkdown = async (content, depth, pushToHistory = false, dry = false) => {
|
|
667
672
|
logger.debug("%j", {
|
|
668
673
|
message: "actOnMarkdown called",
|
|
669
674
|
depth,
|
|
@@ -678,7 +683,7 @@ const actOnMarkdown = async (content, depth, pushToHistory = false) => {
|
|
|
678
683
|
}
|
|
679
684
|
|
|
680
685
|
if (codeblocks.length) {
|
|
681
|
-
let executions = await executeCodeBlocks(codeblocks, depth, pushToHistory);
|
|
686
|
+
let executions = await executeCodeBlocks(codeblocks, depth, pushToHistory, dry);
|
|
682
687
|
return executions;
|
|
683
688
|
} else {
|
|
684
689
|
return true;
|
|
@@ -720,7 +725,7 @@ const firstPrompt = async () => {
|
|
|
720
725
|
|
|
721
726
|
// this is how we parse user input
|
|
722
727
|
// notice that the AI is only called if the input is not a command
|
|
723
|
-
|
|
728
|
+
const handleInput = async (input) => {
|
|
724
729
|
if (!isInteractive) return;
|
|
725
730
|
if (!input.trim().length) return promptUser();
|
|
726
731
|
|
|
@@ -789,12 +794,22 @@ const firstPrompt = async () => {
|
|
|
789
794
|
} else if (input.indexOf("/generate") == 0) {
|
|
790
795
|
const skipYaml = commands[4] === "--skip-yaml";
|
|
791
796
|
await generate(commands[1], commands[2], commands[3], skipYaml);
|
|
797
|
+
} else if (input.indexOf("/dry") == 0) {
|
|
798
|
+
await exploratoryLoop(input.replace('/dry', ''), true, false);
|
|
799
|
+
} else if (input.indexOf("/yaml") == 0) {
|
|
800
|
+
await runRawYML(commands[1]);
|
|
792
801
|
} else {
|
|
793
|
-
await
|
|
802
|
+
await exploratoryLoop(input, false, true);
|
|
794
803
|
}
|
|
795
804
|
|
|
796
805
|
setTerminalWindowTransparency(false);
|
|
797
806
|
promptUser();
|
|
807
|
+
};
|
|
808
|
+
|
|
809
|
+
rl.on("line", handleInput);
|
|
810
|
+
|
|
811
|
+
wss.addEventListener("input", async (message) => {
|
|
812
|
+
handleInput(message.data);
|
|
798
813
|
});
|
|
799
814
|
|
|
800
815
|
// if file exists, load it
|
|
@@ -923,11 +938,12 @@ let save = async ({ filepath = thisFile, silent = false } = {}) => {
|
|
|
923
938
|
analytics.track("save", { silent });
|
|
924
939
|
|
|
925
940
|
if (!silent) {
|
|
926
|
-
logger.info(chalk.dim(
|
|
941
|
+
logger.info(chalk.dim(`saving as ${filepath}...`), true);
|
|
927
942
|
logger.info("");
|
|
928
943
|
}
|
|
929
944
|
|
|
930
945
|
if (!executionHistory.length) {
|
|
946
|
+
console.log('no exeuction history, not saving')
|
|
931
947
|
return;
|
|
932
948
|
}
|
|
933
949
|
|
|
@@ -936,6 +952,7 @@ let save = async ({ filepath = thisFile, silent = false } = {}) => {
|
|
|
936
952
|
try {
|
|
937
953
|
fs.writeFileSync(filepath, regression);
|
|
938
954
|
} catch (e) {
|
|
955
|
+
console.log(e)
|
|
939
956
|
logger.error(e.message);
|
|
940
957
|
logger.error("%s", e);
|
|
941
958
|
}
|
|
@@ -956,15 +973,33 @@ ${regression}
|
|
|
956
973
|
}
|
|
957
974
|
};
|
|
958
975
|
|
|
976
|
+
let runRawYML = async (yml) => {
|
|
977
|
+
|
|
978
|
+
const tmp = require("tmp");
|
|
979
|
+
let tmpobj = tmp.fileSync();
|
|
980
|
+
|
|
981
|
+
let decoded = decodeURIComponent(yml);
|
|
982
|
+
|
|
983
|
+
// saved the yml to a temp file using tmp
|
|
984
|
+
// and run it with run()
|
|
985
|
+
fs.writeFileSync(tmpobj.name, await generator.rawToFormatted(decoded));
|
|
986
|
+
|
|
987
|
+
await run(tmpobj.name, false, true);
|
|
988
|
+
|
|
989
|
+
}
|
|
990
|
+
|
|
959
991
|
// this will load a regression test from a file location
|
|
960
992
|
// it parses the markdown file and executes the codeblocks exactly as if they were
|
|
961
993
|
// generated by the AI in a single prompt
|
|
962
|
-
let run = async (file, shouldSave = false, shouldExit = true) => {
|
|
994
|
+
let run = async (file = thisFile, shouldSave = false, shouldExit = true) => {
|
|
963
995
|
await newSession();
|
|
964
996
|
|
|
965
997
|
setTerminalWindowTransparency(true);
|
|
966
998
|
emitter.emit(events.interactive, false);
|
|
967
999
|
|
|
1000
|
+
// get the current wowrking directory where this file is being executed
|
|
1001
|
+
let cwd = process.cwd();
|
|
1002
|
+
|
|
968
1003
|
logger.info(chalk.cyan(`running ${file}...`));
|
|
969
1004
|
|
|
970
1005
|
let ymlObj = await loadYML(file);
|
|
@@ -1017,6 +1052,7 @@ ${yaml.dump(step)}
|
|
|
1017
1052
|
};
|
|
1018
1053
|
|
|
1019
1054
|
const promptUser = () => {
|
|
1055
|
+
wss.sendToClients("done");
|
|
1020
1056
|
emitter.emit(events.interactive, true);
|
|
1021
1057
|
rl.prompt(true);
|
|
1022
1058
|
};
|
|
@@ -1122,9 +1158,11 @@ const start = async () => {
|
|
|
1122
1158
|
analytics.track("command", { command: thisCommand, file: thisFile });
|
|
1123
1159
|
|
|
1124
1160
|
if (thisCommand == "edit") {
|
|
1161
|
+
await makeSandbox();
|
|
1125
1162
|
firstPrompt();
|
|
1126
1163
|
} else if (thisCommand == "run") {
|
|
1127
1164
|
errorLimit = 100;
|
|
1165
|
+
await makeSandbox();
|
|
1128
1166
|
run(thisFile);
|
|
1129
1167
|
} else if (thisCommand == "init") {
|
|
1130
1168
|
await init();
|
|
@@ -1132,6 +1170,34 @@ const start = async () => {
|
|
|
1132
1170
|
}
|
|
1133
1171
|
};
|
|
1134
1172
|
|
|
1173
|
+
const makeSandbox = async () => {
|
|
1174
|
+
|
|
1175
|
+
if (config.TD_VM) {
|
|
1176
|
+
|
|
1177
|
+
logger.info(chalk.gray(`- creating linux sandbox...`));
|
|
1178
|
+
await sandbox.boot();
|
|
1179
|
+
logger.info(chalk.gray(`- authenticating...`));
|
|
1180
|
+
await sandbox.send({type: 'authenticate', apiKey: config.TD_API_KEY });
|
|
1181
|
+
logger.info(chalk.gray(`- setting up...`));
|
|
1182
|
+
await sandbox.send({type: 'create', resolution: [1024, 768]});
|
|
1183
|
+
logger.info(chalk.gray(`- starting stream...`));
|
|
1184
|
+
await sandbox.send({type: 'stream.start'});
|
|
1185
|
+
let {url} = await sandbox.send({type: 'stream.getUrl'});
|
|
1186
|
+
logger.info(chalk.gray(`- rendering...`));
|
|
1187
|
+
await sandbox.send({type: 'ready'});
|
|
1188
|
+
logger.info(chalk.gray(`- booting...`));
|
|
1189
|
+
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
1190
|
+
logger.info(chalk.green(``));
|
|
1191
|
+
logger.info(chalk.green(`sandbox runner ready!`));
|
|
1192
|
+
emitter.emit(events.vm.show, {url});
|
|
1193
|
+
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
emitter.emit(events.interactive, false);
|
|
1197
|
+
emitter.emit(events.showWindow)
|
|
1198
|
+
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1135
1201
|
process.on("uncaughtException", async (err) => {
|
|
1136
1202
|
analytics.track("uncaughtException", { err });
|
|
1137
1203
|
logger.error("Uncaught Exception: %s", err);
|
package/electron/overlay.html
CHANGED
|
@@ -2,12 +2,20 @@
|
|
|
2
2
|
<html>
|
|
3
3
|
|
|
4
4
|
<head>
|
|
5
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
6
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
7
|
+
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&display=swap" rel="stylesheet">
|
|
8
|
+
<title>TestDriver</title>
|
|
5
9
|
<style>
|
|
6
10
|
* {
|
|
7
11
|
margin: 0;
|
|
8
12
|
padding: 0;
|
|
9
13
|
overflow: hidden;
|
|
10
14
|
box-sizing: border-box;
|
|
15
|
+
font-family: "IBM Plex Mono", monospace;
|
|
16
|
+
font-weight: 400;
|
|
17
|
+
font-style: normal;
|
|
18
|
+
font-size: 14px;
|
|
11
19
|
}
|
|
12
20
|
|
|
13
21
|
@keyframes animate-glow {
|
|
@@ -64,11 +72,14 @@
|
|
|
64
72
|
position: relative;
|
|
65
73
|
}
|
|
66
74
|
|
|
75
|
+
#main {
|
|
76
|
+
pointer-events: none;
|
|
77
|
+
}
|
|
78
|
+
|
|
67
79
|
.screenshot {
|
|
68
80
|
position: absolute;
|
|
69
81
|
inset: 0;
|
|
70
|
-
|
|
71
|
-
border-radius: 20px;
|
|
82
|
+
z-index: 1;
|
|
72
83
|
opacity: 0;
|
|
73
84
|
animation-duration: 0.3s;
|
|
74
85
|
animation-delay: 0;
|
|
@@ -77,6 +88,7 @@
|
|
|
77
88
|
animation-name: animate-screenshot;
|
|
78
89
|
animation-timing-function: ease;
|
|
79
90
|
animation-fill-mode: forwards;
|
|
91
|
+
background-color: white;
|
|
80
92
|
}
|
|
81
93
|
|
|
82
94
|
.box {
|
|
@@ -90,6 +102,7 @@
|
|
|
90
102
|
animation-name: animate-glow;
|
|
91
103
|
animation-timing-function: ease;
|
|
92
104
|
animation-fill-mode: forwards;
|
|
105
|
+
border-radius: 5px;
|
|
93
106
|
}
|
|
94
107
|
|
|
95
108
|
.container {
|
|
@@ -150,6 +163,7 @@
|
|
|
150
163
|
}
|
|
151
164
|
|
|
152
165
|
#terminal-wrapper {
|
|
166
|
+
pointer-events: none;
|
|
153
167
|
position: absolute;
|
|
154
168
|
left: calc(100vw - 700px);
|
|
155
169
|
top: 0px;
|
|
@@ -159,7 +173,7 @@
|
|
|
159
173
|
opacity: 0;
|
|
160
174
|
z-index: -1;
|
|
161
175
|
overflow-y: auto;
|
|
162
|
-
padding:
|
|
176
|
+
padding: 20px;
|
|
163
177
|
}
|
|
164
178
|
|
|
165
179
|
#terminal {
|
|
@@ -169,11 +183,22 @@
|
|
|
169
183
|
}
|
|
170
184
|
|
|
171
185
|
#boxes {
|
|
186
|
+
pointer-events: none;
|
|
172
187
|
position: absolute;
|
|
173
188
|
top: 0;
|
|
174
189
|
left: 0;
|
|
175
190
|
right: 0;
|
|
176
191
|
bottom: 0;
|
|
192
|
+
z-index: 2;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
#vm-iframe {
|
|
196
|
+
position: absolute;
|
|
197
|
+
top: 0;
|
|
198
|
+
left: 0;
|
|
199
|
+
width: 100%;
|
|
200
|
+
height: 100%;
|
|
201
|
+
border: none;
|
|
177
202
|
}
|
|
178
203
|
</style>
|
|
179
204
|
<link rel="stylesheet" href="terminal/xterm.css" />
|
|
@@ -182,15 +207,19 @@
|
|
|
182
207
|
</head>
|
|
183
208
|
|
|
184
209
|
<body>
|
|
210
|
+
|
|
185
211
|
<div id="screenshot"></div>
|
|
186
212
|
<div id="main" class="container">
|
|
187
213
|
<div id="boxes">
|
|
188
214
|
<div id="pointer"></div>
|
|
189
215
|
</div>
|
|
190
216
|
<div id="terminal-wrapper">
|
|
217
|
+
<img src="td.png" alt="td" style="position: absolute; top: 20; right: 20; height: 40px; z-index: 9999; background-color: black;">
|
|
191
218
|
<div id="terminal"></div>
|
|
192
219
|
</div>
|
|
193
220
|
</div>
|
|
221
|
+
<iframe id="vm-iframe" frameborder="0"></iframe>
|
|
222
|
+
|
|
194
223
|
<script>
|
|
195
224
|
|
|
196
225
|
const { ipcRenderer } = require("electron");
|
|
@@ -226,7 +255,6 @@
|
|
|
226
255
|
});
|
|
227
256
|
}
|
|
228
257
|
|
|
229
|
-
|
|
230
258
|
ipcRenderer.on(events.screenCapture.start, (event, data) => {
|
|
231
259
|
if (data?.silent) return
|
|
232
260
|
// Hide everything whlie the app takes the screenshot
|
|
@@ -279,7 +307,7 @@
|
|
|
279
307
|
cursorInactiveStyle: "none",
|
|
280
308
|
scrollback: 9999999,
|
|
281
309
|
allowProposedApi: true,
|
|
282
|
-
fontSize:
|
|
310
|
+
fontSize: 14
|
|
283
311
|
});
|
|
284
312
|
|
|
285
313
|
const fitAddon = new FitAddon.FitAddon();
|
|
@@ -287,6 +315,11 @@
|
|
|
287
315
|
terminal.open(terminalElement);
|
|
288
316
|
fitAddon.fit();
|
|
289
317
|
|
|
318
|
+
ipcRenderer.on(events.vm.show, (event, data) => {
|
|
319
|
+
const iframe = document.querySelector("#vm-iframe");
|
|
320
|
+
iframe.src = data.url;
|
|
321
|
+
});
|
|
322
|
+
|
|
290
323
|
ipcRenderer.on(events.terminal.stdout, (event, data) =>
|
|
291
324
|
terminal.write(data),
|
|
292
325
|
);
|
package/electron/overlay.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const ipc = require("node-ipc").default;
|
|
2
2
|
const { app, screen, BrowserWindow } = require("electron");
|
|
3
3
|
const { eventsArray } = require("../lib/events.js");
|
|
4
|
+
const config = require("../lib/config.js");
|
|
4
5
|
|
|
5
6
|
ipc.config.id = "testdriverai_overlay";
|
|
6
7
|
ipc.config.retry = 1500;
|
|
@@ -9,47 +10,79 @@ ipc.config.silent = true;
|
|
|
9
10
|
app.whenReady().then(() => {
|
|
10
11
|
app.dock?.hide();
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
13
|
+
let windowOptions;
|
|
14
|
+
|
|
15
|
+
if (config.TD_VM) {
|
|
16
|
+
|
|
17
|
+
windowOptions = {
|
|
18
|
+
width: 1030,
|
|
19
|
+
height: 800,
|
|
20
|
+
closable: true,
|
|
21
|
+
resizable: true,
|
|
22
|
+
show: false,
|
|
23
|
+
alwaysOnTop: true,
|
|
24
|
+
webPreferences: {
|
|
25
|
+
nodeIntegration: true,
|
|
26
|
+
contextIsolation: false,
|
|
27
|
+
},
|
|
28
|
+
autoHideMenuBar: true,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
} else {
|
|
32
|
+
|
|
33
|
+
windowOptions = {
|
|
34
|
+
...screen.getPrimaryDisplay().bounds,
|
|
35
|
+
closable: true,
|
|
36
|
+
resizable: true,
|
|
37
|
+
alwaysOnTop: true,
|
|
38
|
+
enableLargerThanScreen: true,
|
|
39
|
+
frame: false,
|
|
40
|
+
show: false,
|
|
41
|
+
focusable: false,
|
|
42
|
+
fullscreenable: true,
|
|
43
|
+
transparent: true,
|
|
44
|
+
skipTaskbar: true,
|
|
45
|
+
webPreferences: {
|
|
46
|
+
nodeIntegration: true,
|
|
47
|
+
contextIsolation: false,
|
|
48
|
+
},
|
|
49
|
+
autoHideMenuBar: true,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
}
|
|
30
53
|
|
|
31
54
|
if (process.platform !== 'darwin') {
|
|
32
55
|
windowOptions.fullscreen = true;
|
|
33
56
|
}
|
|
34
57
|
|
|
35
58
|
const window = new BrowserWindow(windowOptions);
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
59
|
+
|
|
60
|
+
if (!config.TD_VM) {
|
|
61
|
+
window.setIgnoreMouseEvents(true);
|
|
62
|
+
window.setAlwaysOnTop(true, "screen-saver");
|
|
63
|
+
window.setVisibleOnAllWorkspaces(true, {
|
|
64
|
+
visibleOnFullScreen: true,
|
|
65
|
+
});
|
|
66
|
+
} else {
|
|
67
|
+
window.setBackgroundColor('#000')
|
|
68
|
+
}
|
|
69
|
+
|
|
41
70
|
window.loadFile("overlay.html");
|
|
42
71
|
|
|
43
72
|
window.once('ready-to-show', () => {
|
|
44
|
-
window.showInactive();
|
|
73
|
+
// window.showInactive();
|
|
45
74
|
});
|
|
46
75
|
|
|
47
76
|
// open developer tools
|
|
48
|
-
|
|
77
|
+
window.webContents.openDevTools();
|
|
49
78
|
|
|
50
79
|
ipc.serve(() => {
|
|
51
80
|
for (const event of eventsArray) {
|
|
52
81
|
ipc.server.on(event, (data) => {
|
|
82
|
+
if (event === "show-window") {
|
|
83
|
+
window.showInactive();
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
53
86
|
window?.webContents.send(event, data);
|
|
54
87
|
});
|
|
55
88
|
}
|
package/electron/td.png
ADDED
|
Binary file
|
package/index.js
CHANGED
|
@@ -13,6 +13,7 @@ const { logger } = require("./lib/logger.js");
|
|
|
13
13
|
agent.setTerminalApp(win);
|
|
14
14
|
agent.start();
|
|
15
15
|
} else {
|
|
16
|
+
|
|
16
17
|
// Intercept all stdout and stderr calls (works with console as well)
|
|
17
18
|
const originalStdoutWrite = process.stdout.write.bind(process.stdout);
|
|
18
19
|
process.stdout.write = (...args) => {
|
|
@@ -35,9 +36,8 @@ const { logger } = require("./lib/logger.js");
|
|
|
35
36
|
};
|
|
36
37
|
|
|
37
38
|
require("./lib/overlay.js")
|
|
38
|
-
.electronProcessPromise.then(() => {
|
|
39
|
+
.electronProcessPromise.then(async () => {
|
|
39
40
|
let agent = require("./agent.js");
|
|
40
|
-
agent.setTerminalApp(win);
|
|
41
41
|
agent.start();
|
|
42
42
|
})
|
|
43
43
|
.catch((err) => {
|
package/lib/commander.js
CHANGED
|
@@ -25,6 +25,9 @@ const replaceOutputs = (obj) => {
|
|
|
25
25
|
// object is a json representation of the individual yml command
|
|
26
26
|
// the process turns markdown -> yml -> json -> js function execution
|
|
27
27
|
const run = async (object, depth) => {
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
|
|
28
31
|
logger.debug("%j", { object, depth });
|
|
29
32
|
|
|
30
33
|
// success returns null
|
|
@@ -44,7 +47,7 @@ Our test structure is in YAML format.
|
|
|
44
47
|
|
|
45
48
|
Here is a simple example:
|
|
46
49
|
|
|
47
|
-
\`\`\`
|
|
50
|
+
\`\`\`yaml
|
|
48
51
|
commands:
|
|
49
52
|
- command: press-keys
|
|
50
53
|
keys: [command, space]
|
|
@@ -216,6 +219,11 @@ commands:
|
|
|
216
219
|
]);
|
|
217
220
|
|
|
218
221
|
return response;
|
|
219
|
-
|
|
222
|
+
|
|
223
|
+
} catch (error) {
|
|
224
|
+
logger.error(error);
|
|
225
|
+
throw error;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
220
228
|
|
|
221
229
|
module.exports = { run };
|