testdriverai 7.1.3 → 7.2.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.
- package/.github/workflows/acceptance.yaml +81 -0
- package/.github/workflows/publish.yaml +44 -0
- package/.github/workflows/test-init.yml +145 -0
- package/agent/index.js +18 -19
- package/agent/lib/commander.js +2 -2
- package/agent/lib/commands.js +324 -124
- package/agent/lib/redraw.js +99 -39
- package/agent/lib/sandbox.js +98 -6
- package/agent/lib/sdk.js +25 -0
- package/agent/lib/system.js +2 -1
- package/agent/lib/validation.js +6 -6
- package/docs/docs.json +211 -101
- package/docs/snippets/tests/type-repeated-replay.mdx +1 -1
- package/docs/v7/_drafts/caching-selectors.mdx +24 -0
- package/docs/v7/_drafts/migration.mdx +3 -3
- package/docs/v7/api/act.mdx +2 -2
- package/docs/v7/api/assert.mdx +2 -2
- package/docs/v7/api/assertions.mdx +21 -21
- package/docs/v7/api/elements.mdx +78 -0
- package/docs/v7/api/find.mdx +38 -0
- package/docs/v7/api/focusApplication.mdx +2 -2
- package/docs/v7/api/hover.mdx +2 -2
- package/docs/v7/features/ai-native.mdx +57 -71
- package/docs/v7/features/application-logs.mdx +353 -0
- package/docs/v7/features/browser-logs.mdx +414 -0
- package/docs/v7/features/cache-management.mdx +402 -0
- package/docs/v7/features/continuous-testing.mdx +346 -0
- package/docs/v7/features/coverage.mdx +508 -0
- package/docs/v7/features/data-driven-testing.mdx +441 -0
- package/docs/v7/features/easy-to-write.mdx +2 -73
- package/docs/v7/features/enterprise.mdx +155 -39
- package/docs/v7/features/fast.mdx +63 -81
- package/docs/v7/features/managed-sandboxes.mdx +384 -0
- package/docs/v7/features/network-monitoring.mdx +568 -0
- package/docs/v7/features/observable.mdx +3 -22
- package/docs/v7/features/parallel-execution.mdx +381 -0
- package/docs/v7/features/powerful.mdx +1 -1
- package/docs/v7/features/reports.mdx +414 -0
- package/docs/v7/features/sandbox-customization.mdx +229 -0
- package/docs/v7/features/scalable.mdx +217 -2
- package/docs/v7/features/stable.mdx +106 -147
- package/docs/v7/features/system-performance.mdx +616 -0
- package/docs/v7/features/test-analytics.mdx +373 -0
- package/docs/v7/features/test-cases.mdx +393 -0
- package/docs/v7/features/test-replays.mdx +408 -0
- package/docs/v7/features/test-reports.mdx +308 -0
- package/docs/v7/getting-started/{running-and-debugging.mdx → debugging-tests.mdx} +12 -142
- package/docs/v7/getting-started/quickstart.mdx +22 -305
- package/docs/v7/getting-started/running-tests.mdx +173 -0
- package/docs/v7/overview/readme.mdx +1 -1
- package/docs/v7/overview/what-is-testdriver.mdx +2 -14
- package/docs/v7/presets/chrome-extension.mdx +147 -122
- package/interfaces/cli/commands/init.js +78 -20
- package/interfaces/cli/lib/base.js +3 -2
- package/interfaces/logger.js +0 -2
- package/interfaces/shared-test-state.mjs +0 -5
- package/interfaces/vitest-plugin.mjs +69 -42
- package/lib/core/Dashcam.js +65 -66
- package/lib/vitest/hooks.mjs +42 -50
- package/manual/test-init-command.js +223 -0
- package/package.json +2 -2
- package/schema.json +5 -5
- package/sdk-log-formatter.js +351 -176
- package/sdk.d.ts +8 -8
- package/sdk.js +436 -121
- package/setup/aws/cloudformation.yaml +2 -2
- package/setup/aws/self-hosted.yml +1 -1
- package/test/testdriver/chrome-extension.test.mjs +55 -72
- package/test/testdriver/element-not-found.test.mjs +2 -1
- package/test/testdriver/hover-image.test.mjs +1 -1
- package/test/testdriver/hover-text-with-description.test.mjs +0 -3
- package/test/testdriver/scroll-until-text.test.mjs +10 -6
- package/test/testdriver/setup/lifecycleHelpers.mjs +19 -24
- package/test/testdriver/setup/testHelpers.mjs +18 -23
- package/vitest.config.mjs +3 -3
- package/.github/workflows/linux-tests.yml +0 -28
- package/docs/v7/getting-started/generating-tests.mdx +0 -525
- package/test/testdriver/auto-cache-key-demo.test.mjs +0 -56
|
@@ -75,7 +75,7 @@ v7/
|
|
|
75
75
|
- Basic assertions
|
|
76
76
|
- Negative assertions (invert parameter)
|
|
77
77
|
- Async assertions
|
|
78
|
-
- `
|
|
78
|
+
- `extract()` - Extract information from screen
|
|
79
79
|
- Testing patterns:
|
|
80
80
|
- Polling assertions
|
|
81
81
|
- Multi-step validation
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
title: "What is TestDriver?"
|
|
3
3
|
description: "AI-powered end-to-end testing for web, desktop, and mobile applications"
|
|
4
4
|
icon: "circle-info"
|
|
5
|
+
mode: "wide"
|
|
5
6
|
---
|
|
6
7
|
|
|
7
8
|
TestDriver is an AI-native testing platform that lets you write tests in natural language. It uses computer vision and AI to understand your application like a human would, eliminating brittle selectors and making tests easy to write and maintain.
|
|
@@ -10,7 +11,7 @@ TestDriver is an AI-native testing platform that lets you write tests in natural
|
|
|
10
11
|
|
|
11
12
|
Traditional E2E testing tools require:
|
|
12
13
|
|
|
13
|
-
<CardGroup cols={
|
|
14
|
+
<CardGroup cols={1}>
|
|
14
15
|
<Card title="Brittle Selectors" icon="triangle-exclamation">
|
|
15
16
|
```javascript
|
|
16
17
|
// ❌ Breaks when DOM changes
|
|
@@ -18,19 +19,6 @@ Traditional E2E testing tools require:
|
|
|
18
19
|
```
|
|
19
20
|
</Card>
|
|
20
21
|
|
|
21
|
-
<Card title="Complex Setup" icon="wrench">
|
|
22
|
-
```javascript
|
|
23
|
-
// ❌ Manual lifecycle management
|
|
24
|
-
await browser.launch()
|
|
25
|
-
await page.goto(url)
|
|
26
|
-
try {
|
|
27
|
-
// Test code
|
|
28
|
-
} finally {
|
|
29
|
-
await browser.close()
|
|
30
|
-
}
|
|
31
|
-
```
|
|
32
|
-
</Card>
|
|
33
|
-
|
|
34
22
|
<Card title="Flaky Tests" icon="shuffle">
|
|
35
23
|
```javascript
|
|
36
24
|
// ❌ Arbitrary waits
|
|
@@ -7,50 +7,97 @@ icon: "puzzle-piece"
|
|
|
7
7
|
|
|
8
8
|
## Overview
|
|
9
9
|
|
|
10
|
-
Test Chrome extensions by loading them into Chrome for Testing.
|
|
10
|
+
Test Chrome extensions by loading them into Chrome for Testing. Use `testdriver.provision.chromeExtension()` to launch Chrome with either:
|
|
11
|
+
- A **local unpacked extension** via `extensionPath`
|
|
12
|
+
- A **Chrome Web Store extension** via `extensionId`
|
|
13
|
+
|
|
14
|
+
The dashcam-chrome extension is automatically included on Linux for web log capture.
|
|
11
15
|
|
|
12
16
|
## Quick Start
|
|
13
17
|
|
|
18
|
+
### Using a Local Extension Path
|
|
19
|
+
|
|
14
20
|
```javascript
|
|
15
|
-
import {
|
|
16
|
-
import TestDriver from 'testdriverai';
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
21
|
+
import { describe, it, expect } from 'vitest';
|
|
22
|
+
import { TestDriver } from 'testdriverai/vitest/hooks';
|
|
23
|
+
|
|
24
|
+
describe('Chrome Extension Test', () => {
|
|
25
|
+
it('should load and test a Chrome extension', async (context) => {
|
|
26
|
+
const testdriver = TestDriver(context, { headless: true });
|
|
27
|
+
|
|
28
|
+
// Wait for connection
|
|
29
|
+
await testdriver.ready();
|
|
30
|
+
|
|
31
|
+
// Clone an extension repo
|
|
32
|
+
await testdriver.exec(
|
|
33
|
+
'sh',
|
|
34
|
+
'git clone --depth 1 https://github.com/GoogleChrome/chrome-extensions-samples.git /tmp/chrome-extensions-samples',
|
|
35
|
+
60000,
|
|
36
|
+
true
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
// Launch Chrome with the extension loaded
|
|
40
|
+
await testdriver.provision.chromeExtension({
|
|
41
|
+
extensionPath: '/tmp/chrome-extensions-samples/functional-samples/tutorial.hello-world',
|
|
42
|
+
url: 'https://example.com'
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Your test code here
|
|
46
|
+
const result = await testdriver.assert("the page is visible");
|
|
47
|
+
expect(result).toBeTruthy();
|
|
27
48
|
});
|
|
49
|
+
});
|
|
50
|
+
```
|
|
28
51
|
|
|
29
|
-
|
|
30
|
-
// Extension ID from Chrome Web Store
|
|
31
|
-
await runPrerunChromeExtension(client, "cjpalhdlnbpafiamejdnhcphjbkeiagm");
|
|
52
|
+
### Using a Chrome Web Store Extension ID
|
|
32
53
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
54
|
+
```javascript
|
|
55
|
+
import { describe, it, expect } from 'vitest';
|
|
56
|
+
import { TestDriver } from 'testdriverai/vitest/hooks';
|
|
57
|
+
|
|
58
|
+
describe('Chrome Extension Test', () => {
|
|
59
|
+
it('should load uBlock Origin and verify it works', async (context) => {
|
|
60
|
+
const testdriver = TestDriver(context, { headless: true });
|
|
61
|
+
|
|
62
|
+
// Launch Chrome with uBlock Origin loaded by its Chrome Web Store ID
|
|
63
|
+
await testdriver.provision.chromeExtension({
|
|
64
|
+
extensionId: 'cjpalhdlnbpafiamejdnhcphjbkeiagm', // uBlock Origin
|
|
65
|
+
url: 'https://example.com'
|
|
66
|
+
});
|
|
37
67
|
|
|
38
|
-
|
|
39
|
-
|
|
68
|
+
// Your test code here
|
|
69
|
+
const result = await testdriver.assert("the page is visible");
|
|
70
|
+
expect(result).toBeTruthy();
|
|
71
|
+
});
|
|
40
72
|
});
|
|
41
73
|
```
|
|
42
74
|
|
|
75
|
+
## API Reference
|
|
76
|
+
|
|
77
|
+
### `testdriver.provision.chromeExtension(options)`
|
|
78
|
+
|
|
79
|
+
Launches Chrome for Testing with a custom extension loaded.
|
|
80
|
+
|
|
81
|
+
| Option | Type | Default | Description |
|
|
82
|
+
|--------|------|---------|-------------|
|
|
83
|
+
| `extensionPath` | `string` | - | Local filesystem path to the unpacked extension directory |
|
|
84
|
+
| `extensionId` | `string` | - | Chrome Web Store extension ID (e.g., `cjpalhdlnbpafiamejdnhcphjbkeiagm`) |
|
|
85
|
+
| `url` | `string` | `'http://testdriver-sandbox.vercel.app/'` | URL to navigate to after launch |
|
|
86
|
+
| `maximized` | `boolean` | `true` | Start Chrome maximized |
|
|
87
|
+
|
|
88
|
+
**Note:** Either `extensionPath` or `extensionId` is required. On Linux, the dashcam-chrome extension is automatically loaded alongside your extension for web log capture.
|
|
89
|
+
|
|
43
90
|
## Finding Extension IDs
|
|
44
91
|
|
|
45
92
|
Extension IDs can be found in the Chrome Web Store URL:
|
|
46
93
|
|
|
47
94
|
```
|
|
48
95
|
https://chrome.google.com/webstore/detail/ublock-origin/cjpalhdlnbpafiamejdnhcphjbkeiagm
|
|
49
|
-
|
|
50
|
-
|
|
96
|
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
97
|
+
This is the extension ID
|
|
51
98
|
```
|
|
52
99
|
|
|
53
|
-
|
|
100
|
+
### Popular Extensions
|
|
54
101
|
|
|
55
102
|
| Extension | ID |
|
|
56
103
|
|-----------|---|
|
|
@@ -59,73 +106,67 @@ https://chrome.google.com/webstore/detail/ublock-origin/cjpalhdlnbpafiamejdnhcph
|
|
|
59
106
|
| Redux DevTools | `lmhkpmbekcpmknklioeibfkpmmfibljd` |
|
|
60
107
|
| Bitwarden | `nngceckbapebfimnlniiiahkandclblb` |
|
|
61
108
|
|
|
62
|
-
##
|
|
109
|
+
## Using Your Own Extension
|
|
63
110
|
|
|
64
|
-
|
|
111
|
+
To test your own extension, you can either:
|
|
65
112
|
|
|
113
|
+
1. **Clone from a repository:**
|
|
66
114
|
```javascript
|
|
67
|
-
await
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
115
|
+
await testdriver.ready();
|
|
116
|
+
await testdriver.exec('sh', 'git clone https://github.com/your/extension.git /tmp/my-extension', 60000, true);
|
|
117
|
+
await testdriver.provision.chromeExtension({
|
|
118
|
+
extensionPath: '/tmp/my-extension',
|
|
119
|
+
url: 'https://example.com'
|
|
120
|
+
});
|
|
71
121
|
```
|
|
72
122
|
|
|
123
|
+
2. **Upload your extension files** (if supported by your test setup)
|
|
124
|
+
|
|
125
|
+
3. **Use a pre-built extension** in a known location
|
|
126
|
+
|
|
73
127
|
## Complete Example
|
|
74
128
|
|
|
75
129
|
```javascript
|
|
76
|
-
import { describe, it,
|
|
77
|
-
import TestDriver from 'testdriverai';
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
let dashcamUrl;
|
|
86
|
-
|
|
87
|
-
beforeAll(async () => {
|
|
88
|
-
client = await TestDriver.create({
|
|
89
|
-
apiKey: process.env.TD_API_KEY,
|
|
90
|
-
os: "linux",
|
|
91
|
-
verbosity: 1,
|
|
130
|
+
import { describe, it, expect } from 'vitest';
|
|
131
|
+
import { TestDriver } from 'testdriverai/vitest/hooks';
|
|
132
|
+
|
|
133
|
+
describe('Chrome Extension - Hello World', () => {
|
|
134
|
+
it('should load extension and verify popup', async (context) => {
|
|
135
|
+
const testdriver = TestDriver(context, {
|
|
136
|
+
headless: true,
|
|
137
|
+
newSandbox: true,
|
|
138
|
+
cacheKey: 'chrome-extension-test'
|
|
92
139
|
});
|
|
140
|
+
|
|
141
|
+
// Clone the Chrome extensions samples repo
|
|
142
|
+
await testdriver.exec(
|
|
143
|
+
'sh',
|
|
144
|
+
'git clone --depth 1 https://github.com/GoogleChrome/chrome-extensions-samples.git /tmp/chrome-extensions-samples',
|
|
145
|
+
60000,
|
|
146
|
+
true
|
|
147
|
+
);
|
|
93
148
|
|
|
94
|
-
//
|
|
95
|
-
await
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
if (client) {
|
|
100
|
-
dashcamUrl = await runPostrun(client);
|
|
101
|
-
await client.cleanup();
|
|
102
|
-
}
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
it('should verify extension is loaded', async () => {
|
|
106
|
-
await client.focusApplication("Google Chrome");
|
|
149
|
+
// Launch Chrome with the hello-world extension loaded
|
|
150
|
+
await testdriver.provision.chromeExtension({
|
|
151
|
+
extensionPath: '/tmp/chrome-extensions-samples/functional-samples/tutorial.hello-world',
|
|
152
|
+
url: 'https://testdriver.ai'
|
|
153
|
+
});
|
|
107
154
|
|
|
108
|
-
//
|
|
109
|
-
const
|
|
110
|
-
expect(
|
|
155
|
+
// Verify the page loaded
|
|
156
|
+
const pageResult = await testdriver.assert("the testdriver.ai website is visible");
|
|
157
|
+
expect(pageResult).toBeTruthy();
|
|
111
158
|
|
|
112
|
-
//
|
|
113
|
-
|
|
114
|
-
|
|
159
|
+
// Click on the extensions button in Chrome toolbar
|
|
160
|
+
const extensionsButton = await testdriver.find("Extensions button, puzzle piece icon in Chrome toolbar");
|
|
161
|
+
await extensionsButton.click();
|
|
115
162
|
|
|
116
|
-
|
|
117
|
-
await
|
|
118
|
-
|
|
119
|
-
// Open extension management
|
|
120
|
-
await client.exec(
|
|
121
|
-
"sh",
|
|
122
|
-
`xdotool key --clearmodifiers ctrl+shift+e`,
|
|
123
|
-
5000,
|
|
124
|
-
true
|
|
125
|
-
);
|
|
163
|
+
// Find and click the hello world extension
|
|
164
|
+
const helloExtension = await testdriver.find("Hello World extension in the extensions dropdown");
|
|
165
|
+
await helloExtension.click();
|
|
126
166
|
|
|
127
|
-
//
|
|
128
|
-
|
|
167
|
+
// Verify the extension popup shows
|
|
168
|
+
const popupResult = await testdriver.assert("a popup shows with the text 'Hello Extensions'");
|
|
169
|
+
expect(popupResult).toBeTruthy();
|
|
129
170
|
});
|
|
130
171
|
});
|
|
131
172
|
```
|
|
@@ -139,19 +180,9 @@ Chrome for Testing is pre-installed in the E2B sandbox environment at:
|
|
|
139
180
|
/usr/local/bin/chrome-for-testing (symlink)
|
|
140
181
|
```
|
|
141
182
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
```javascript
|
|
147
|
-
import { launchChromeExtension } from 'testdriverai/testdriver/acceptance-sdk/setup/lifecycleHelpers.mjs';
|
|
148
|
-
|
|
149
|
-
// Launch with specific extension and URL
|
|
150
|
-
await launchChromeExtension(
|
|
151
|
-
client,
|
|
152
|
-
"cjpalhdlnbpafiamejdnhcphjbkeiagm",
|
|
153
|
-
"https://example.com"
|
|
154
|
-
);
|
|
183
|
+
The dashcam-chrome extension is pre-installed at:
|
|
184
|
+
```
|
|
185
|
+
/usr/lib/node_modules/dashcam-chrome/build
|
|
155
186
|
```
|
|
156
187
|
|
|
157
188
|
## Testing Extension Features
|
|
@@ -159,44 +190,39 @@ await launchChromeExtension(
|
|
|
159
190
|
### Test Extension Popup
|
|
160
191
|
|
|
161
192
|
```javascript
|
|
162
|
-
it('opens extension popup', async () => {
|
|
163
|
-
|
|
164
|
-
await client.click('extension icon in toolbar');
|
|
193
|
+
it('opens extension popup', async (context) => {
|
|
194
|
+
const testdriver = TestDriver(context, { headless: true });
|
|
165
195
|
|
|
166
|
-
//
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
### Test Extension Settings
|
|
173
|
-
|
|
174
|
-
```javascript
|
|
175
|
-
it('configures extension settings', async () => {
|
|
176
|
-
// Right-click extension icon
|
|
177
|
-
await client.rightClick('extension icon');
|
|
196
|
+
// Setup extension...
|
|
197
|
+
await testdriver.provision.chromeExtension({
|
|
198
|
+
extensionPath: '/path/to/extension',
|
|
199
|
+
url: 'https://example.com'
|
|
200
|
+
});
|
|
178
201
|
|
|
179
|
-
// Click
|
|
180
|
-
await
|
|
202
|
+
// Click extension icon in toolbar
|
|
203
|
+
const extensionIcon = await testdriver.find('extension icon in toolbar');
|
|
204
|
+
await extensionIcon.click();
|
|
181
205
|
|
|
182
|
-
//
|
|
183
|
-
await
|
|
184
|
-
|
|
206
|
+
// Interact with popup
|
|
207
|
+
const result = await testdriver.assert('extension popup is visible');
|
|
208
|
+
expect(result).toBeTruthy();
|
|
185
209
|
});
|
|
186
210
|
```
|
|
187
211
|
|
|
188
212
|
### Test Content Scripts
|
|
189
213
|
|
|
190
214
|
```javascript
|
|
191
|
-
it('verifies content script injection', async () => {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
await
|
|
195
|
-
|
|
215
|
+
it('verifies content script injection', async (context) => {
|
|
216
|
+
const testdriver = TestDriver(context, { headless: true });
|
|
217
|
+
|
|
218
|
+
await testdriver.provision.chromeExtension({
|
|
219
|
+
extensionPath: '/path/to/extension',
|
|
220
|
+
url: 'https://example.com'
|
|
221
|
+
});
|
|
196
222
|
|
|
197
223
|
// Check for extension-injected elements
|
|
198
|
-
const
|
|
199
|
-
expect(
|
|
224
|
+
const result = await testdriver.assert('element added by extension is visible');
|
|
225
|
+
expect(result).toBeTruthy();
|
|
200
226
|
});
|
|
201
227
|
```
|
|
202
228
|
|
|
@@ -204,9 +230,9 @@ it('verifies content script injection', async () => {
|
|
|
204
230
|
|
|
205
231
|
### Extension Not Loading
|
|
206
232
|
|
|
207
|
-
1. Verify the extension
|
|
233
|
+
1. Verify the extension path exists and contains a valid `manifest.json`
|
|
208
234
|
2. Check Chrome for Testing is installed in the sandbox
|
|
209
|
-
3. Ensure extension is compatible with Chrome for Testing version
|
|
235
|
+
3. Ensure extension is compatible with Chrome for Testing version (Manifest V3 recommended)
|
|
210
236
|
|
|
211
237
|
### Extension Permissions
|
|
212
238
|
|
|
@@ -219,5 +245,4 @@ Some extensions may require additional permissions or setup. You may need to:
|
|
|
219
245
|
## See Also
|
|
220
246
|
|
|
221
247
|
- [Web Apps (Chrome)](/v7/presets/chrome) - Regular Chrome browser testing
|
|
222
|
-
- [Desktop Apps (Electron)](/v7/presets/electron) - Electron app testing
|
|
223
|
-
- [Lifecycle Helpers](/v7/guides/lifecycle) - Prerun/postrun functions
|
|
248
|
+
- [Desktop Apps (Electron)](/v7/presets/electron) - Electron app testing- [Lifecycle Helpers](/v7/guides/lifecycle) - Prerun/postrun functions
|
|
@@ -143,7 +143,10 @@ class InitCommand extends BaseCommand {
|
|
|
143
143
|
},
|
|
144
144
|
keywords: ["testdriver", "testing", "e2e"],
|
|
145
145
|
author: "",
|
|
146
|
-
license: "ISC"
|
|
146
|
+
license: "ISC",
|
|
147
|
+
engines: {
|
|
148
|
+
node: ">=20.19.0"
|
|
149
|
+
}
|
|
147
150
|
};
|
|
148
151
|
|
|
149
152
|
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + "\n");
|
|
@@ -159,6 +162,7 @@ class InitCommand extends BaseCommand {
|
|
|
159
162
|
async createVitestExample() {
|
|
160
163
|
const testDir = path.join(process.cwd(), "tests");
|
|
161
164
|
const testFile = path.join(testDir, "example.test.js");
|
|
165
|
+
const loginSnippetFile = path.join(testDir, "login.js");
|
|
162
166
|
const configFile = path.join(process.cwd(), "vitest.config.js");
|
|
163
167
|
|
|
164
168
|
// Create test directory if it doesn't exist
|
|
@@ -167,23 +171,73 @@ class InitCommand extends BaseCommand {
|
|
|
167
171
|
console.log(chalk.gray(` Created directory: ${testDir}`));
|
|
168
172
|
}
|
|
169
173
|
|
|
170
|
-
// Create
|
|
174
|
+
// Create login snippet file
|
|
175
|
+
const loginSnippetContent = `/**
|
|
176
|
+
* Login snippet - reusable login function
|
|
177
|
+
*
|
|
178
|
+
* This demonstrates how to create reusable test snippets that can be
|
|
179
|
+
* imported and used across multiple test files.
|
|
180
|
+
*/
|
|
181
|
+
export async function login(testdriver) {
|
|
182
|
+
|
|
183
|
+
// The password is displayed on screen, have TestDriver extract it
|
|
184
|
+
const password = await testdriver.extract('the password');
|
|
185
|
+
|
|
186
|
+
// Find the username field
|
|
187
|
+
const usernameField = await testdriver.find(
|
|
188
|
+
'Username, label above the username input field on the login form'
|
|
189
|
+
);
|
|
190
|
+
await usernameField.click();
|
|
191
|
+
|
|
192
|
+
// Type username
|
|
193
|
+
await testdriver.type('standard_user');
|
|
194
|
+
|
|
195
|
+
// Enter password form earlier
|
|
196
|
+
// Marked as secret so it's not logged or stored
|
|
197
|
+
await testdriver.pressKeys(['tab']);
|
|
198
|
+
await testdriver.type(password, { secret: true });
|
|
199
|
+
|
|
200
|
+
// Submit the form
|
|
201
|
+
await testdriver.find('submit button on the login form').click();
|
|
202
|
+
}
|
|
203
|
+
`;
|
|
204
|
+
|
|
205
|
+
fs.writeFileSync(loginSnippetFile, loginSnippetContent);
|
|
206
|
+
console.log(chalk.green(` Created login snippet: ${loginSnippetFile}`));
|
|
207
|
+
|
|
208
|
+
// Create example Vitest test that uses the login snippet
|
|
171
209
|
const vitestContent = `import { test, expect } from 'vitest';
|
|
172
|
-
import {
|
|
210
|
+
import { TestDriver } from 'testdriverai/vitest/hooks';
|
|
211
|
+
import { login } from './login.js';
|
|
212
|
+
|
|
213
|
+
test('should login and add item to cart', async (context) => {
|
|
214
|
+
|
|
215
|
+
// Create TestDriver instance - automatically connects to sandbox
|
|
216
|
+
const testdriver = TestDriver(context);
|
|
217
|
+
|
|
218
|
+
// Launch chrome and navigate to demo app
|
|
219
|
+
await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
|
|
220
|
+
|
|
221
|
+
// Use the login snippet to handle authentication
|
|
222
|
+
// This demonstrates how to reuse test logic across multiple tests
|
|
223
|
+
await login(testdriver);
|
|
173
224
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
});
|
|
225
|
+
// Add item to cart
|
|
226
|
+
const addToCartButton = await testdriver.find(
|
|
227
|
+
'add to cart button under TestDriver Hat'
|
|
228
|
+
);
|
|
229
|
+
await addToCartButton.click();
|
|
180
230
|
|
|
181
|
-
//
|
|
182
|
-
const
|
|
183
|
-
|
|
231
|
+
// Open cart
|
|
232
|
+
const cartButton = await testdriver.find(
|
|
233
|
+
'cart button in the top right corner'
|
|
234
|
+
);
|
|
235
|
+
await cartButton.click();
|
|
184
236
|
|
|
185
|
-
|
|
186
|
-
|
|
237
|
+
// Verify item in cart
|
|
238
|
+
const result = await testdriver.assert('TestDriver Hat is in the cart');
|
|
239
|
+
expect(result).toBeTruthy();
|
|
240
|
+
|
|
187
241
|
});
|
|
188
242
|
`;
|
|
189
243
|
|
|
@@ -194,16 +248,20 @@ test('should navigate to example.com and find elements', async (context) => {
|
|
|
194
248
|
if (!fs.existsSync(configFile)) {
|
|
195
249
|
const configContent = `import { defineConfig } from 'vitest/config';
|
|
196
250
|
import TestDriver from 'testdriverai/vitest';
|
|
197
|
-
import
|
|
251
|
+
import { config } from 'dotenv';
|
|
198
252
|
|
|
199
253
|
// Load environment variables from .env file
|
|
200
|
-
|
|
254
|
+
config();
|
|
201
255
|
|
|
202
256
|
export default defineConfig({
|
|
203
|
-
plugins: [TestDriver()],
|
|
204
257
|
test: {
|
|
205
258
|
testTimeout: 300000,
|
|
206
259
|
hookTimeout: 300000,
|
|
260
|
+
reporters: [
|
|
261
|
+
'default',
|
|
262
|
+
TestDriver(),
|
|
263
|
+
],
|
|
264
|
+
setupFiles: ['testdriverai/vitest/setup'],
|
|
207
265
|
},
|
|
208
266
|
});
|
|
209
267
|
`;
|
|
@@ -290,7 +348,7 @@ jobs:
|
|
|
290
348
|
- name: Run TestDriver.ai tests
|
|
291
349
|
env:
|
|
292
350
|
TD_API_KEY: \${{ secrets.TD_API_KEY }}
|
|
293
|
-
run:
|
|
351
|
+
run: npx vitest run
|
|
294
352
|
|
|
295
353
|
- name: Upload test results
|
|
296
354
|
if: always()
|
|
@@ -337,11 +395,11 @@ jobs:
|
|
|
337
395
|
printNextSteps() {
|
|
338
396
|
console.log(chalk.cyan("Next steps:\n"));
|
|
339
397
|
console.log(" 1. Run your tests:");
|
|
340
|
-
console.log(chalk.gray("
|
|
398
|
+
console.log(chalk.gray(" npx vitest run\n"));
|
|
341
399
|
console.log(" 2. For CI/CD, add TD_API_KEY to your GitHub repository secrets");
|
|
342
400
|
console.log(chalk.gray(" Settings → Secrets → Actions → New repository secret\n"));
|
|
343
401
|
console.log(
|
|
344
|
-
chalk.cyan("Learn more at https://docs.testdriver.ai/getting-started
|
|
402
|
+
chalk.cyan("Learn more at https://docs.testdriver.ai/v7/getting-started/\n"),
|
|
345
403
|
);
|
|
346
404
|
}
|
|
347
405
|
}
|
|
@@ -73,12 +73,13 @@ class BaseCommand extends Command {
|
|
|
73
73
|
};
|
|
74
74
|
|
|
75
75
|
let isConnected = false;
|
|
76
|
+
const debugMode = process.env.VERBOSE || process.env.DEBUG || process.env.TD_DEBUG;
|
|
76
77
|
|
|
77
|
-
// Use pattern matching for log events, but skip log:Debug
|
|
78
|
+
// Use pattern matching for log events, but skip log:Debug unless debug mode is enabled
|
|
78
79
|
this.agent.emitter.on("log:*", (message) => {
|
|
79
80
|
const event = this.agent.emitter.event;
|
|
80
81
|
|
|
81
|
-
if (event === events.log.debug) return;
|
|
82
|
+
if (event === events.log.debug && !debugMode) return;
|
|
82
83
|
|
|
83
84
|
if (event === events.log.narration && isConnected) return;
|
|
84
85
|
console.log(message);
|
package/interfaces/logger.js
CHANGED
|
@@ -21,8 +21,6 @@ class CustomTransport extends Transport {
|
|
|
21
21
|
this.sandbox = require("../agent/lib/sandbox");
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
console.log("CustomTransport log message:", message);
|
|
25
|
-
|
|
26
24
|
if (this.sandbox && this.sandbox.instanceSocketConnected) {
|
|
27
25
|
if (typeof message === "object") {
|
|
28
26
|
console.log(chalk.cyan("protecting against base64 error"));
|
|
@@ -22,11 +22,6 @@ const sharedState = {
|
|
|
22
22
|
* Set the test run information
|
|
23
23
|
*/
|
|
24
24
|
export function setTestRunInfo(info) {
|
|
25
|
-
console.log("[SharedState] Setting test run info:", {
|
|
26
|
-
testRunId: info.testRunId,
|
|
27
|
-
hasToken: !!info.token,
|
|
28
|
-
hasTestRun: !!info.testRun,
|
|
29
|
-
});
|
|
30
25
|
|
|
31
26
|
if (info.testRun) sharedState.testRun = info.testRun;
|
|
32
27
|
if (info.testRunId) sharedState.testRunId = info.testRunId;
|