testdriverai 4.0.1 → 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 +104 -80
- package/index.js +11 -11
- package/lib/focus-application.js +40 -23
- package/lib/logger.js +11 -17
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,134 +1,158 @@
|
|
|
1
|
-
|
|
1
|
+

|
|
2
2
|
|
|
3
|
+
# TestDriver.ai
|
|
3
4
|
|
|
4
|
-
|
|
5
|
+
Next generation autonomous AI agent for end-to-end testing of web & desktop
|
|
5
6
|
|
|
6
|
-
|
|
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
|
-
|
|
9
|
+
----
|
|
9
10
|
|
|
10
|
-
|
|
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
|
-
|
|
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
|
-
|
|
18
|
-
node index.js run testdriver.yml
|
|
19
|
-
```
|
|
17
|
+
### Demo
|
|
20
18
|
|
|
21
|
-
|
|
19
|
+
https://github.com/user-attachments/assets/fba08020-a751-4d9e-9505-50db541fd38b
|
|
22
20
|
|
|
23
|
-
|
|
21
|
+
# Examples
|
|
24
22
|
|
|
25
|
-
|
|
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
|
-
|
|
32
|
+
Check out [the docs](https://docs.testdriver.ai/).
|
|
28
33
|
|
|
29
|
-
|
|
34
|
+
# Workflow
|
|
30
35
|
|
|
31
|
-
|
|
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
|
-
|
|
40
|
+
# Quickstart
|
|
34
41
|
|
|
35
|
-
|
|
42
|
+
## Install TestDriver via NPM
|
|
36
43
|
|
|
37
|
-
|
|
44
|
+
Install testdriverai via NPM. This will make testdriverai available as a global command.
|
|
38
45
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
46
|
+
```sh
|
|
47
|
+
npm install testdriverai -g
|
|
48
|
+
```
|
|
42
49
|
|
|
43
|
-
|
|
50
|
+
## Set up the project
|
|
44
51
|
|
|
45
|
-
|
|
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
|
-
|
|
54
|
+
```sh
|
|
55
|
+
testdriverai init
|
|
56
|
+
```
|
|
48
57
|
|
|
49
|
-
|
|
58
|
+
You're almost ready to deploy your first test!
|
|
50
59
|
|
|
51
|
-
|
|
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
|
-
|
|
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
|
-
|
|
65
|
+
testdriverai .testdriver/test.yml
|
|
62
66
|
```
|
|
63
67
|
|
|
64
|
-
|
|
68
|
+
## Reset the test state
|
|
65
69
|
|
|
66
|
-
|
|
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
|
-
|
|
70
|
-
brew install python-setuptools
|
|
71
|
-
```
|
|
72
|
+
If you have multiple monitors, make sure you do this on your primary display.
|
|
72
73
|
|
|
73
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
80
|
+
Later, try `/explore` to perform higher level objectives like "complete the onboarding."
|
|
82
81
|
|
|
83
|
-
|
|
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
|
-
|
|
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
|
-
|
|
98
|
+
Let's start with focusing on the Google Chrome application
|
|
99
|
+
and clicking on the search bar.
|
|
94
100
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
109
|
-
|
|
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
|
-
|
|
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
|
-
|
|
116
|
-
/save
|
|
142
|
+
testdriverai run testdriver/test.yml
|
|
117
143
|
```
|
|
118
144
|
|
|
119
|
-
|
|
145
|
+
Make sure to reset the test state!
|
|
120
146
|
|
|
121
|
-
|
|
122
|
-
contain the codeblocks the AI generated and ran in linear order.
|
|
147
|
+
## Deploy
|
|
123
148
|
|
|
124
|
-
|
|
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
|
-
|
|
128
|
-
|
|
151
|
+
```sh
|
|
152
|
+
git add .
|
|
153
|
+
git commit -am "Add TestDriver tests"
|
|
154
|
+
gh pr create --web
|
|
155
|
+
```
|
|
129
156
|
|
|
130
|
-
|
|
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
|
-
|
|
582
|
-
}).on('error', (err) => {
|
|
583
|
-
|
|
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
|
-
|
|
591
|
-
}).on('error', (err) => {
|
|
592
|
-
|
|
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
|
-
|
|
597
|
+
console.error('Caught exception:', e);
|
|
598
598
|
}
|
|
599
599
|
}
|
|
600
600
|
|
package/lib/focus-application.js
CHANGED
|
@@ -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
|
|
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
|
|
14
|
-
tell application "
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
94
|
+
if (shouldLog) {
|
|
95
|
+
logger.log({
|
|
96
|
+
level,
|
|
97
|
+
message,
|
|
98
|
+
version: package.version,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
107
101
|
|
|
108
102
|
}
|
|
109
103
|
|