testdriverai 4.1.57 → 4.2.0-test.0

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.
@@ -0,0 +1,27 @@
1
+
2
+ name: Example Todo App
3
+
4
+ on:
5
+ push:
6
+ branches: ["main"]
7
+ pull_request:
8
+ workflow_dispatch:
9
+
10
+ jobs:
11
+ run:
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: testdriverai/action@alexrowe/yml-interpolation-test
15
+ with:
16
+ os: windows
17
+ branch: alexrowe/yml-interpolation
18
+ key: ${{secrets.TESTDRIVER_API_KEY}}
19
+ prompt: |
20
+ 1. /run test.yml
21
+ prerun: |
22
+ Start-Process "C:/Program Files/Google/Chrome/Application/chrome.exe" -ArgumentList "--start-maximized", "${{ env.WEBSITE_URL }}"
23
+
24
+ env:
25
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
26
+ WEBSITE_URL: "https://kzmgtp9k2gtuuw2iquk2.lite.vusercontent.net/"
27
+ TD_PASSWORD: "passW0rd!"
@@ -1,8 +1,8 @@
1
1
  name: Trigger Testdriver Dev Workflow
2
-
3
- on:
4
- pull_request:
5
- types: [opened, synchronize, reopened]
2
+ #
3
+ # on:
4
+ # pull_request:
5
+ # types: [opened, synchronize, reopened]
6
6
 
7
7
  env:
8
8
  API_REF: main
package/agent.js CHANGED
@@ -202,11 +202,8 @@ if (!commandHistory.length) {
202
202
  ];
203
203
  }
204
204
 
205
- const exit = async (failed = true, shouldSave = false) => {
206
-
207
- if (shouldSave) {
208
- await save();
209
- }
205
+ const exit = async (failed = true) => {
206
+ await save();
210
207
 
211
208
  analytics.track("exit", { failed });
212
209
 
@@ -644,16 +641,6 @@ const actOnMarkdown = async (content, depth, pushToHistory = false) => {
644
641
  // simple function to backfill the chat history with a prompt and
645
642
  // then call `promptUser()` to get the user input
646
643
  const firstPrompt = async () => {
647
-
648
- // should be start of new session
649
- const sessionRes = await sdk.req("session/start", {
650
- systemInformationOsInfo: await system.getSystemInformationOsInfo(),
651
- mousePosition: await system.getMousePosition(),
652
- activeWindow: await system.activeWin(),
653
- });
654
-
655
- session.set(sessionRes.data.id);
656
-
657
644
  // readline is what allows us to get user input
658
645
  rl = readline.createInterface({
659
646
  terminal: true,
@@ -694,7 +681,7 @@ const firstPrompt = async () => {
694
681
  if (input.indexOf("/summarize") == 0) {
695
682
  await summarize();
696
683
  } else if (input.indexOf("/quit") == 0) {
697
- await exit(false, true);
684
+ await exit();
698
685
  } else if (input.indexOf("/save") == 0) {
699
686
  await save({ filepath: commands[1] });
700
687
  } else if (input.indexOf("/undo") == 0) {
@@ -704,7 +691,7 @@ const firstPrompt = async () => {
704
691
  } else if (input.indexOf("/manual") == 0) {
705
692
  await manualInput(commands.slice(1).join(" "));
706
693
  } else if (input.indexOf("/run") == 0) {
707
- await run(commands[1], commands[2] == "true", commands[3] == "true");
694
+ await run(commands[1], commands[2], commands[3]);
708
695
  } else if (input.indexOf("/generate") == 0) {
709
696
  await generate(commands[1], commands[2]);
710
697
  } else {
@@ -718,7 +705,7 @@ const firstPrompt = async () => {
718
705
  // if file exists, load it
719
706
  if (fs.existsSync(thisFile)) {
720
707
  analytics.track("load");
721
- let object = await generator.hydrateFromYML(
708
+ let object = await generator.ymlToHistory(
722
709
  fs.readFileSync(thisFile, "utf-8"),
723
710
  );
724
711
 
@@ -848,7 +835,7 @@ let save = async ({ filepath = thisFile, silent = false } = {}) => {
848
835
  }
849
836
 
850
837
  // write reply to /tmp/oiResult.log.log
851
- let regression = await generator.dumpToYML(executionHistory);
838
+ let regression = await generator.historyToYml(executionHistory);
852
839
  try {
853
840
  fs.writeFileSync(filepath, regression);
854
841
  } catch (e) {
@@ -875,10 +862,9 @@ ${regression}
875
862
  // this will load a regression test from a file location
876
863
  // it parses the markdown file and executes the codeblocks exactly as if they were
877
864
  // generated by the AI in a single prompt
878
- let run = async (file, shouldSave = false, shouldExit = true) => {
865
+ let run = async (file, overwrite = false, shouldExit = true) => {
879
866
 
880
867
  setTerminalWindowTransparency(true);
881
- emitter.emit(events.interactive, false);
882
868
 
883
869
  log.log("info", chalk.cyan(`running ${file}...`));
884
870
 
@@ -898,9 +884,12 @@ let run = async (file, shouldSave = false, shouldExit = true) => {
898
884
  await exit(true);
899
885
  }
900
886
 
887
+ console.log(yml)
888
+ console.log(process.env)
901
889
  // Inject environment variables into any ${VAR} strings
902
890
  yml = parser.interpolate(yml, process.env);
903
891
 
892
+ console.log(yml)
904
893
  let ymlObj = null;
905
894
  try {
906
895
  ymlObj = await yaml.load(yml);
@@ -948,14 +937,13 @@ ${yaml.dump(step)}
948
937
  await actOnMarkdown(markdown, 0, true);
949
938
  }
950
939
 
951
- if (shouldSave) {
940
+ if (overwrite || overwrite == "true") {
952
941
  await save({ filepath: file });
953
942
  }
954
943
 
955
944
  setTerminalWindowTransparency(false);
956
- emitter.emit(events.interactive, true);
957
945
 
958
- if (shouldExit) {
946
+ if (shouldExit || shouldExit == "true") {
959
947
  await summarize();
960
948
  await exit(false);
961
949
  }
@@ -1071,6 +1059,15 @@ const start = async () => {
1071
1059
  console.log("");
1072
1060
  }
1073
1061
 
1062
+ // should be start of new session
1063
+ const sessionRes = await sdk.req("session/start", {
1064
+ systemInformationOsInfo: await system.getSystemInformationOsInfo(),
1065
+ mousePosition: await system.getMousePosition(),
1066
+ activeWindow: await system.activeWin(),
1067
+ });
1068
+
1069
+ session.set(sessionRes.data);
1070
+
1074
1071
  analytics.track("command", { command: thisCommand, file: thisFile });
1075
1072
 
1076
1073
  if (thisCommand == "edit") {
package/chrome.ps1 ADDED
@@ -0,0 +1,33 @@
1
+ <#
2
+ .SYNOPSIS
3
+ Launches Google Chrome and navigates to a specified URL.
4
+
5
+ .DESCRIPTION
6
+ This PowerShell script will check for the existence of the Google Chrome
7
+ browser at a predefined location, and if found, will open it and navigate to
8
+ the URL provided as an argument to the script.
9
+
10
+ .PARAMETER url
11
+ The URL that Google Chrome will navigate to upon launch.
12
+
13
+ .EXAMPLE
14
+ .\chrome.ps1 -url "http://www.example.com"
15
+ This example will open Google Chrome and navigate to http://www.example.com.
16
+
17
+ .NOTES
18
+ Google Chrome must be installed in the default location for this script
19
+ to function correctly.
20
+
21
+ #>
22
+
23
+ param(
24
+ [string]$url
25
+ )
26
+
27
+ $chromePath = "C:\Program Files\Google\Chrome\Application\chrome.exe"
28
+
29
+ if (Test-Path $chromePath) {
30
+ Start-Process $chromePath $url
31
+ } else {
32
+ Write-Error "Chrome is not installed at the expected location: $chromePath"
33
+ }
package/lib/cli.js ADDED
@@ -0,0 +1,22 @@
1
+ const { program } = require("commander");
2
+
3
+ program.description("");
4
+ program
5
+ .argument("[command]", "Command to run")
6
+ .option(
7
+ "-c, --cwd <string>",
8
+ "working directory to run in"
9
+ )
10
+ .option(
11
+ "-i, --id <string>",
12
+ "ID of server to connect to"
13
+ )
14
+ .option(
15
+ "-o, --output-file <string>",
16
+ "Output file to write the output of the command"
17
+ )
18
+ .option(
19
+ "-e, --env <string>",
20
+ "Extra environment variables"
21
+ )
22
+ .parse();
package/lib/commands.js CHANGED
@@ -160,12 +160,15 @@ const assert = async (assertion, shouldThrow = false, async = false) => {
160
160
  return handleAssertResponse(response.data);
161
161
  }
162
162
  };
163
- const scroll = async (direction = "down", amount = 300, method = "mouse") => {
163
+ const scroll = async (direction = "down", amount = 300, method = "keyboard") => {
164
164
  await redraw.start();
165
165
 
166
166
  amount = parseInt(amount);
167
167
 
168
168
  if (method === "mouse") {
169
+ // after experimenting, 200 is a good default for mouse, mostly as mouse will be called only when the user asks for it
170
+ // and that happens when keyboard scrolling cannot do things when there's a pop up that needs to be scrolled over and
171
+ // pop ups are usually smaller and needs a smaller amount of scrolling
169
172
  amount = 200;
170
173
  }
171
174
 
package/lib/generator.js CHANGED
@@ -2,7 +2,6 @@
2
2
  const yaml = require("js-yaml");
3
3
  const chalk = require("chalk");
4
4
  const package = require("../package.json");
5
- const session = require("./session");
6
5
  // do the actual parsing
7
6
  // this library is very strict
8
7
  // note that errors are sent to the AI will it may self-heal
@@ -53,30 +52,25 @@ const jsonToManual = function (json, colors = true) {
53
52
  return params;
54
53
  };
55
54
 
56
- const dumpToYML = async function (inputArray) {
57
-
55
+ const historyToYml = async function (inputArray) {
58
56
  // use yml dump to convert json to yml
59
57
  let yml = await yaml.dump({
60
58
  version: package.version,
61
- session: session.get(),
62
59
  steps: inputArray,
63
60
  });
64
61
 
65
62
  return yml;
66
63
  };
67
64
 
68
- const hydrateFromYML = async function (yml) {
65
+ const ymlToHistory = async function (yml) {
69
66
  // use yml load to convert yml to json
70
67
  let json = await yaml.load(yml);
71
-
72
- session.set(json.session);
73
-
74
68
  return json;
75
69
  };
76
70
 
77
71
  module.exports = {
78
72
  manualToYml,
79
- dumpToYML,
80
- hydrateFromYML,
73
+ historyToYml,
74
+ ymlToHistory,
81
75
  jsonToManual,
82
76
  };
package/lib/sdk.js CHANGED
@@ -106,7 +106,7 @@ const req = async (path, data, onChunk) => {
106
106
  responseType: typeof onChunk === "function" ? "stream" : "json",
107
107
  data: {
108
108
  ...data,
109
- session: session.get(),
109
+ session: session.get()?.id,
110
110
  stream: typeof onChunk === "function",
111
111
  },
112
112
  };
package/lib/session.js CHANGED
@@ -5,8 +5,6 @@ module.exports = {
5
5
  return session;
6
6
  },
7
7
  set: (s) => {
8
- if (s) {
9
- session = s;
10
- }
8
+ session = s;
11
9
  },
12
10
  };
package/lib/system.js CHANGED
@@ -2,11 +2,12 @@
2
2
  const fs = require("fs");
3
3
  const os = require("os");
4
4
  const path = require("path");
5
+ const screenshot = require("screenshot-desktop");
5
6
  const si = require("systeminformation");
7
+ const activeWindow = require("active-win");
6
8
  const robot = require("robotjs");
7
9
  const sharp = require("sharp");
8
10
  const { emitter, events } = require("./events.js");
9
- const { Monitor } = require("node-screenshots");
10
11
 
11
12
  let primaryDisplay = null;
12
13
 
@@ -47,11 +48,7 @@ const captureAndResize = async (scale = 1, silent = false, mouse = false) => {
47
48
  let step1 = tmpFilename();
48
49
  let step2 = tmpFilename();
49
50
 
50
- const monitors = Monitor.all();
51
- const primaryMonitor = monitors.find(monitor => monitor.isPrimary);
52
- const image = await primaryMonitor.captureImage(); // Capture the image asynchronously
53
- const buffer = await image.toPng(); // Convert the image to PNG format
54
- fs.writeFileSync(step1, buffer); // Save the image to a file
51
+ await screenshot({ filename: step1, format: "png" });
55
52
 
56
53
  // Fetch the mouse position
57
54
  const mousePos = robot.getMousePos();
@@ -114,25 +111,9 @@ const platform = () => {
114
111
  return platform;
115
112
  };
116
113
 
117
- // Import get-windows using dynamic import for ES module compatibility
118
- let activeWindowFn = null;
119
- const initializeActiveWindow = async () => {
120
- if (!activeWindowFn) {
121
- const { activeWindow } = await import('get-windows');
122
- activeWindowFn = activeWindow;
123
- }
124
- return activeWindowFn;
125
- };
126
-
127
114
  // this is the focused window
128
115
  const activeWin = async () => {
129
- try {
130
- const activeWindow = await initializeActiveWindow();
131
- return await activeWindow();
132
- } catch (error) {
133
- console.error('Error getting active window:', error);
134
- return null;
135
- }
116
+ return await activeWindow();
136
117
  };
137
118
 
138
119
  const getMousePosition = async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testdriverai",
3
- "version": "4.1.57",
3
+ "version": "4.2.0-test.0",
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
  "license": "ISC",
18
18
  "dependencies": {
19
19
  "@electerm/strip-ansi": "^1.0.0",
20
+ "active-win": "^8.2.1",
20
21
  "axios": "^1.7.7",
21
22
  "chalk": "^4.1.2",
22
23
  "cli-progress": "^3.12.0",
@@ -24,7 +25,6 @@
24
25
  "decompress": "^4.2.1",
25
26
  "dotenv": "^16.4.5",
26
27
  "electron": "^33.0.2",
27
- "get-windows": "^9.2.0",
28
28
  "jimp": "^0.22.12",
29
29
  "js-yaml": "^4.1.0",
30
30
  "mac-screen-capture-permissions": "^2.1.0",
@@ -34,13 +34,14 @@
34
34
  "marky": "^1.2.5",
35
35
  "node-ipc": "^12.0.0",
36
36
  "node-notifier": "^10.0.1",
37
- "node-screenshots": "0.2.1",
38
37
  "odiff-bin": "^3.1.2",
39
38
  "prompts": "^2.4.2",
40
39
  "remark-parse": "^11.0.0",
40
+ "rimraf": "^5.0.5",
41
41
  "robotjs": "^0.6.0",
42
42
  "sanitize-filename": "^1.6.3",
43
43
  "say": "^0.16.0",
44
+ "screenshot-desktop": "^1.15.0",
44
45
  "semver": "^7.6.2",
45
46
  "sharp": "^0.33.5",
46
47
  "systeminformation": "^5.23.5",
@@ -48,19 +49,16 @@
48
49
  "uuid": "^10.0.0",
49
50
  "winston": "^3.13.0"
50
51
  },
51
- "overrides": {
52
- "glob": "^11.0.1",
53
- "rimraf": "^5.0.10"
54
- },
55
52
  "devDependencies": {
56
53
  "@eslint/js": "^9.10.0",
57
- "chai": "^5.1.2",
58
54
  "esbuild": "0.20.2",
59
55
  "esbuild-plugin-fileloc": "^0.0.6",
60
56
  "eslint": "^9.10.0",
61
57
  "globals": "^15.9.0",
62
- "mocha": "^10.8.2",
63
58
  "node-addon-api": "^8.0.0",
59
+ "node-gyp": "^10.1.0",
60
+ "mocha": "^10.8.2",
61
+ "chai": "^5.1.2",
64
62
  "prettier": "3.3.3"
65
63
  },
66
64
  "optionalDependencies": {
package/shot.png ADDED
Binary file
package/test.js ADDED
@@ -0,0 +1,31 @@
1
+ const screenshot = require("screenshot-desktop");
2
+ const robot = require("robotjs");
3
+ const Jimp = require('jimp');
4
+ const fs = require("fs");
5
+
6
+ async function go () {
7
+ await screenshot({ filename: "shot.png", format: "png" });
8
+ let pos = robot.getMousePos()
9
+ console.log(pos);
10
+ let im = await Jimp.read("shot.png");
11
+
12
+ // write im as a base64 string js file that can be imported in js
13
+ const base64 = await im.getBase64Async(Jimp.MIME_PNG);
14
+ fs.writeFileSync("pointer_image.js", `module.exports = \n"${base64}";\n`);
15
+
16
+ let pointer = await Jimp.read("./cursor.png");
17
+ console.log(im.getWidth(), im.getHeight());
18
+ im.composite(pointer, pos.x * 2 , pos.y * 2);
19
+
20
+ im.write("shot.png");
21
+ };
22
+
23
+ go();
24
+
25
+ const screenshot = require("screenshot-desktop");
26
+
27
+ async function go () {
28
+ await screenshot({ filename: "shot.png", format: "png" });
29
+ };
30
+
31
+ go();
package/test.yml ADDED
@@ -0,0 +1,18 @@
1
+ version: 4.1.40
2
+ steps:
3
+ - prompt: Enter Password n stuff
4
+ commands:
5
+ - command: focus-application
6
+ name: Google Chrome
7
+ - command: hover-text
8
+ text: Username
9
+ description: username field
10
+ action: click
11
+ - command: type
12
+ text: ${TD_PASSWORD}
13
+ - command: hover-text
14
+ text: Password
15
+ description: Password field
16
+ action: click
17
+ - command: type
18
+ text: ${TD_PASSWORD}
@@ -1,11 +0,0 @@
1
- # To get started with Dependabot version updates, you'll need to specify which
2
- # package ecosystems to update and where the package manifests are located.
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.yml-file
5
-
6
- version: 2
7
- updates:
8
- - package-ecosystem: "npm" # See documentation for possible values
9
- directory: "/" # Location of package manifests
10
- schedule:
11
- interval: "weekly"