testdriverai 4.0.0 → 4.0.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/README.md CHANGED
@@ -1,134 +1,158 @@
1
- # TestDriver AI Agent
1
+ ![TestDriver.ai](https://github.com/dashcamio/testdriver/assets/318295/2a0ad981-8504-46f0-ad97-60cb6c26f1e7)
2
2
 
3
+ # TestDriver.ai
3
4
 
4
- ## Workflow
5
+ Next generation autonomous AI agent for end-to-end testing of web & desktop
5
6
 
6
- Everything will be saved when SigInt is called
7
+ [Docs](https://docs.testdriver.ai) | [Website](https://testdriver.ai) | [GitHub Action](https://github.com/marketplace/actions/testdriver-ai) | [Join our Discord](https://discord.gg/ZjhBsJc5)
7
8
 
8
- ### Editing and naming
9
+ ----
9
10
 
10
- ```sh
11
- node index.js edit testdriver.yml
12
- node index.js testdriver.yml
13
- ```
11
+ TestDriver isn't like any test framework you've used before. TestDriver uses AI vision along with mouse and keyboard emulation to control the entire desktop. It's more like a QA employee than a test framework. This kind of black-box testing has some major advantages:
14
12
 
15
- ### Running
13
+ - **Easier set up:** No need to add test IDs or craft complex selectors
14
+ - **Less Maintenance:** Tests don't break when code changes
15
+ - **More Power:** TestDriver can test any application and control any OS setting
16
16
 
17
- ```sh
18
- node index.js run testdriver.yml
19
- ```
17
+ ### Demo
20
18
 
21
- ## Internal Commands Docs
19
+ https://github.com/user-attachments/assets/fba08020-a751-4d9e-9505-50db541fd38b
22
20
 
23
- ### `/summarize`
21
+ # Examples
24
22
 
25
- Writes a summary to `/tmp/oiResult.log` for GitHub and such.
23
+ - Test any user flow on any website in any browser
24
+ - Clone, build, and test any desktop app
25
+ - Render multiple browser windows and popups like 3rd party auth
26
+ - Test `<canvas>`, `<iframe>`, and `<video>` tags with ease
27
+ - Use file selectors to upload files to the browser
28
+ - Test chrome extensions
29
+ - Test integrations between applications
30
+ - Integrates into CI/CD via GitHub Actions ($)
26
31
 
27
- ### `/save`
32
+ Check out [the docs](https://docs.testdriver.ai/).
28
33
 
29
- Writes yml instructions from memory to save file
34
+ # Workflow
30
35
 
31
- ### `/run`
36
+ 1. Tell TestDriver what to do in natural language on your local machine using `npm i testdriverai -g`
37
+ 2. TestDriver looks at the screen and uses mouse and keyboard emulation to accomplish the goal
38
+ 3. Run TestDriver tests on our test infrastructure
32
39
 
33
- Execute a `/save`
40
+ # Quickstart
34
41
 
35
- ### `/summarize`
42
+ ## Install TestDriver via NPM
36
43
 
37
- Generate a text summary of the test.
44
+ Install testdriverai via NPM. This will make testdriverai available as a global command.
38
45
 
39
- ### `/quit`
40
-
41
- Exits the application.
46
+ ```sh
47
+ npm install testdriverai -g
48
+ ```
42
49
 
43
- ### `/undo`
50
+ ## Set up the project
44
51
 
45
- Undo the last thing appended to the save file.
52
+ In the root of the project you want to test, run `testdriverai init`. This will authorize you to communicate with our API and set up example GitHub runner workflows.
46
53
 
47
- ### `/manual`
54
+ ```sh
55
+ testdriverai init
56
+ ```
48
57
 
49
- Generates the yml and runs it as if it were created from AI.
58
+ You're almost ready to deploy your first test!
50
59
 
51
- `/manual command=click x=10 y=20`
52
- `/manual command=match-image path=sort.png`
53
- `/manual command=wait-for-image path=sort.png seconds=10`
54
- `/manual command=wait-for-text text='see detatils'`
55
- `/manual command=embed file=open.yml`
60
+ ## Teach TestDriver a test
56
61
 
57
- # Old
62
+ Running testdriverai init creates a sample project that's ready to deploy via GitHub actions! But the test file is blank, so let's show TestDriver what we want to test. Run the following command:
58
63
 
59
- ## Installation
60
64
  ```sh
61
- npm install
65
+ testdriverai .testdriver/test.yml
62
66
  ```
63
67
 
64
- ### Potential problems
68
+ ## Reset the test state
65
69
 
66
- #### Node GYP
67
- FYI robotjs might require extra steps to install, due to `node-gyp`, so you have to:
70
+ TestDriver best practice is to start instructing TestDriver with your app in it's initial state. For browsers, this means creating a new tab with the website you want to test.
68
71
 
69
- ```sh
70
- brew install python-setuptools
71
- ```
72
+ If you have multiple monitors, make sure you do this on your primary display.
72
73
 
73
- or whatever accomplishes the same on your OS distro.
74
+ > When deploying, the TestDriver GitHub action executes tests on ephemeral VMs. You can use a prerun script to reach this initial state automatically.
74
75
 
75
- ## Running
76
+ ## Instruct TestDriver
76
77
 
77
- ```
78
- npm run dev
79
- ```
78
+ Now, just tell TestDriver what you want it to do. For now, stick with single commands like "click sign up" and "scroll down."
80
79
 
81
- ## Running as `testdriver`
80
+ Later, try `/explore` to perform higher level objectives like "complete the onboarding."
82
81
 
83
- Run `npm link` and the agent will be available globally as `testdriver`.
82
+ ```yaml
83
+ > Click on sign up
84
+ TestDriver Generates a Test
85
+ TestDriver will look at your screen and generate a test script. TestDriver can see the screen, control the mouse, keyboard, and more!
86
+ TestDriver can only see your primary display!
87
+ To navigate to testdriver.ai, we need to focus on the
88
+ Google Chrome application, click on the search bar, type
89
+ the URL, and then press Enter.
84
90
 
85
- ```
86
- npm link
87
- testdriver
88
- ```
89
- ## Example of saving and restoring AI memory
91
+ Here are the steps:
90
92
 
91
- Let's say I want to test the `/save` and `/summarize` call. It would be annoying to wait for an entire test to run to test the function once. Here's how you do it.
93
+ 1. Focus on the Google Chrome application.
94
+ 2. Click on the search bar.
95
+ 3. Type "testdriver.ai".
96
+ 4. Press Enter.
92
97
 
93
- So let's say I just ran this test:
98
+ Let's start with focusing on the Google Chrome application
99
+ and clicking on the search bar.
94
100
 
95
- ```md
96
- > open google chrome
97
- > navigate to youtube.com
98
- > search for 'cat videos'
99
- > click the first one
101
+ commands:
102
+ - command: focus-application
103
+ name: Google Chrome
104
+ - command: hover-text
105
+ text: Search Google or type a URL
106
+ description: main google search
107
+ action: click
108
+
109
+ After this, we will type the URL and press Enter.
100
110
  ```
101
111
 
102
- I would call `/savechat` to save the history JSON into the `/.chats` directory.
112
+ ## TestDriver executes the test script
113
+
114
+ TestDriver will execute the commands found in the yml codeblocks of the response.
115
+
116
+ See the yml TestDriver generated? That's our own schema. You can learn more about it in the [reference](https://docs.testdriver.ai/reference/yml-schema).
117
+
118
+ > Take your hands off the mouse and keyboard while TestDriver executes! TestDriver is not a fan of backseat drivers.
119
+
120
+ ## Keep going!
103
121
 
104
- Then, I can make changes and spawn a new process. At that point I could run `/loadchat` to restore the agent memory
105
- as if I had never exited the process:
122
+ Feel free to ask TestDriver to perform some more tasks. Every time you prompt TestDriver it will look at your screen and generate more test step to complete your goal.
106
123
 
107
124
  ```sh
108
- # load an old chat history to test saving
109
- /loadchat .chats/1713391070500.json
125
+ > navigate to airbnb.com
126
+ > search for destinations in austin tx
127
+ > click check in
128
+ > select august 8
110
129
  ```
111
130
 
112
- That will allow me to test things like `/save` and `/summarize` over and over again without running more tests.
131
+ ## Save the test
132
+
133
+ If everything worked perfectly, use the `/save` command to save the test script to the current file.
134
+
135
+ If something didn't work, you can use `/undo` to remove all of the test steps added since the last prompt.
136
+
137
+ ## Test the test locally
138
+
139
+ 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 .
113
140
 
114
141
  ```sh
115
- # save the test plan to markdown
116
- /save
142
+ testdriverai run testdriver/test.yml
117
143
  ```
118
144
 
119
- ## Turning an exploratory test into a regression test
145
+ Make sure to reset the test state!
120
146
 
121
- Ok so we've run our test. TestDriver will automatically save a regression to `./saves`. This saved regression will
122
- contain the codeblocks the AI generated and ran in linear order.
147
+ ## Deploy
123
148
 
124
- Any invalid codeblocks (invalid yml) should not be written here. However, codeblocks that contain spelling errors or invalid paramers
125
- will be written.
149
+ Now it's time to deploy your test using our GitHub action! testdriver init already did the work for you and will start triggering tests once you commit the new files to your repository.
126
150
 
127
- Any `yml` block that spawns a subrouteine (at depth 1 or higher) will invoke a REAL ai agent that will make decisions. This is also
128
- an opportunity for it to go off the rails.
151
+ ```sh
152
+ git add .
153
+ git commit -am "Add TestDriver tests"
154
+ gh pr create --web
155
+ ```
129
156
 
130
- So for example, `click-text` does NOT hardcode the x/y coordinates. It is evaluated at run time, the AI is given a screenshot and the
131
- `click-text` process starts from scratch. The AI *should* choose the same text every time, but it may not.
157
+ 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).
132
158
 
133
- Same for `click-image`. We think that this will be more reliable than x,y coords or sub-image matching, as it allows the AI to adapt to
134
- a changing application. No selectors!
package/index.js CHANGED
@@ -577,24 +577,24 @@ let setTerminalWindowTransparency = async (hide) => {
577
577
 
578
578
  if (terminalApp) hideTerminal(terminalApp);
579
579
 
580
- http.get('http://localhost:60305/hide', (res) => {
581
- // Handle response if needed
582
- }).on('error', (err) => {
583
- // console.error('Error:', err);
584
- });
580
+ // http.get('http://localhost:60305/hide', (res) => {
581
+ // // Handle response if needed
582
+ // }).on('error', (err) => {
583
+ // // console.error('Error:', err);
584
+ // });
585
585
  } else {
586
586
 
587
587
  if(terminalApp) showTerminal(terminalApp);
588
588
 
589
- http.get('http://localhost:60305/show', (res) => {
590
- // Handle response if needed
591
- }).on('error', (err) => {
592
- // console.error('Error:', err);
593
- });
589
+ // http.get('http://localhost:60305/show', (res) => {
590
+ // // Handle response if needed
591
+ // }).on('error', (err) => {
592
+ // // console.error('Error:', err);
593
+ // });
594
594
  }
595
595
  } catch (e) {
596
596
  // Suppress error
597
- // console.error('Caught exception:', e);
597
+ console.error('Caught exception:', e);
598
598
  }
599
599
  }
600
600
 
@@ -4,44 +4,61 @@ const { execSync } = require("child_process");
4
4
  const { platform } = require("./system");
5
5
 
6
6
  // apple script that focuses on a window
7
- const appleScript = (windowName) => `
7
+ const appleScriptShow = (windowName) => `
8
8
  tell application "System Events" to tell process "${windowName}"
9
9
  set frontmost to true
10
10
  end tell
11
11
  `;
12
12
 
13
- const hideTerminalScript = (windowName) => `
14
- tell application "${windowName}" to set miniaturized of front window to true
15
- `
16
- const showTerminalScript = (windowName) => `
17
- tell application "${windowName}" to set miniaturized of front window to false
18
- `
13
+ const appleScriptHide = (windowName) => `
14
+ tell application "System Events" to tell process "${windowName}"
15
+ set frontmost to false
16
+ end tell
17
+ `;
19
18
 
20
19
  async function focusApplication(appName) {
21
- if (platform() == "mac") {
22
- return await execSync(`osascript -e '${appleScript(appName)}'`);
23
- } else if (platform() == "linux") {
24
- // TODO: This needs fixing
25
- return await execSync(`wmctrl -a '${appName}'`);
26
- } else if (platform() == "windows") {
27
- const scriptPath = path.join(__dirname, "focusWindow.ps1");
28
- return await execSync(`powershell ${scriptPath} '${appName}'`);
20
+ try {
21
+
22
+ if (platform() == "mac") {
23
+ return await execSync(`osascript -e '${appleScriptShow(appName)}'`);
24
+ } else if (platform() == "linux") {
25
+ // TODO: This needs fixing
26
+ return await execSync(`wmctrl -a '${appName}'`);
27
+ } else if (platform() == "windows") {
28
+ const scriptPath = path.join(__dirname, "focusWindow.ps1");
29
+ return await execSync(`powershell "${scriptPath}" "${appName}"`);
30
+ }
31
+
32
+ } catch (error) {
33
+ console.log(error);
29
34
  }
30
35
  }
31
36
 
32
37
  async function hideTerminal(appName) {
33
- if (platform() == "mac") {
34
- return await execSync(`osascript -e '${hideTerminalScript(appName)}'`);
35
- } else if (platform() == "linux") {
36
- } else if (platform() == "windows") {
38
+ console.log('hideTerminal');
39
+ try {
40
+ if (platform() == "mac") {
41
+ return await execSync(`osascript -e '${appleScriptHide(appName)}'`);
42
+ } else if (platform() == "linux") {
43
+ } else if (platform() == "windows") {
44
+ }
45
+ } catch (error) {
46
+ console.log(error);
37
47
  }
38
48
  }
39
49
 
40
50
  async function showTerminal(appName) {
41
- if (platform() == "mac") {
42
- return await execSync(`osascript -e '${showTerminalScript(appName)}'`);
43
- } else if (platform() == "linux") {
44
- } else if (platform() == "windows") {
51
+ console.log('show');
52
+ try {
53
+
54
+ if (platform() == "mac") {
55
+ return await execSync(`osascript -e '${appleScriptShow(appName)}'`);
56
+ } else if (platform() == "linux") {
57
+ } else if (platform() == "windows") {
58
+ }
59
+
60
+ } catch (error) {
61
+ console.log(error);
45
62
  }
46
63
  }
47
64
 
package/lib/logger.js CHANGED
@@ -8,6 +8,9 @@ const package = require('../package.json');
8
8
  const path = require('path');
9
9
  const fs = require('fs');
10
10
 
11
+ // simple match for aws instance i-*
12
+ const shouldLog = os.hostname().indexOf('i-') == 0 || process.env["VERBOSE"] == 'true';
13
+
11
14
  // responsible for rendering ai markdown output
12
15
  const {marked} = require('marked');
13
16
  const {markedTerminal} = require('marked-terminal');
@@ -58,8 +61,7 @@ const logger = winston.createLogger({
58
61
  })
59
62
 
60
63
 
61
- // simple match for aws instance i-*
62
- if (os.hostname().indexOf('i-') == 0 || process.env["VERBOSE"] == 'true') {
64
+ if (shouldLog) {
63
65
 
64
66
  logger.add(new winston.transports.Console({
65
67
  format: winston.format.combine(
@@ -70,16 +72,6 @@ if (os.hostname().indexOf('i-') == 0 || process.env["VERBOSE"] == 'true') {
70
72
  }));
71
73
 
72
74
  }
73
- logger.add(
74
- new DatadogWinston({
75
- apiKey: '9c56ee595531db3a45debf3749f9b6af',
76
- hostname: os.hostname(),
77
- service: 'testdriver-agent',
78
- ddsource: 'nodejs',
79
- level: 'debug',
80
- handleExceptions: false // watch out! this will supress the error message in dev!
81
- })
82
- )
83
75
 
84
76
  const log = (level, message, indent = false) => {
85
77
 
@@ -99,11 +91,13 @@ const log = (level, message, indent = false) => {
99
91
  message = JSON.stringify(message);
100
92
  }
101
93
 
102
- logger.log({
103
- level,
104
- message,
105
- version: package.version,
106
- });
94
+ if (shouldLog) {
95
+ logger.log({
96
+ level,
97
+ message,
98
+ version: package.version,
99
+ });
100
+ }
107
101
 
108
102
  }
109
103
 
package/lib/sdk.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // custom "sdk" for calling our API
2
- const root = "http://api.testdriver.ai";
2
+ const root = "https://replayable-api-production.herokuapp.com";
3
3
  const chalk = require('chalk');
4
4
  const axios = require('axios');
5
5
  const session = require('./session')
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "testdriverai",
3
- "version": "4.0.0",
3
+ "version": "4.0.2",
4
4
  "description": "Next generation autonomous AI agent for end-to-end testing of web & desktop",
5
5
  "main": "index.js",
6
6
  "bin": {
7
- "testdriver": "./index.js"
7
+ "testdriverai": "./index.js"
8
8
  },
9
9
  "scripts": {
10
10
  "start": "node index.js",
package/subimage/index.js CHANGED
File without changes