testdriverai 7.1.4 → 7.2.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.
Files changed (72) hide show
  1. package/.github/workflows/acceptance.yaml +81 -0
  2. package/.github/workflows/publish.yaml +44 -0
  3. package/agent/index.js +18 -19
  4. package/agent/interface.js +4 -0
  5. package/agent/lib/commands.js +321 -121
  6. package/agent/lib/redraw.js +99 -39
  7. package/agent/lib/sandbox.js +98 -6
  8. package/agent/lib/sdk.js +25 -0
  9. package/agent/lib/system.js +2 -1
  10. package/agent/lib/validation.js +6 -6
  11. package/docs/docs.json +211 -101
  12. package/docs/snippets/tests/type-repeated-replay.mdx +1 -1
  13. package/docs/v7/_drafts/caching-selectors.mdx +24 -0
  14. package/docs/v7/api/act.mdx +1 -1
  15. package/docs/v7/api/assert.mdx +1 -1
  16. package/docs/v7/api/assertions.mdx +7 -7
  17. package/docs/v7/api/elements.mdx +78 -0
  18. package/docs/v7/api/find.mdx +38 -0
  19. package/docs/v7/api/focusApplication.mdx +2 -2
  20. package/docs/v7/api/hover.mdx +2 -2
  21. package/docs/v7/features/ai-native.mdx +57 -71
  22. package/docs/v7/features/application-logs.mdx +353 -0
  23. package/docs/v7/features/browser-logs.mdx +414 -0
  24. package/docs/v7/features/cache-management.mdx +402 -0
  25. package/docs/v7/features/continuous-testing.mdx +346 -0
  26. package/docs/v7/features/coverage.mdx +508 -0
  27. package/docs/v7/features/data-driven-testing.mdx +441 -0
  28. package/docs/v7/features/easy-to-write.mdx +2 -73
  29. package/docs/v7/features/enterprise.mdx +155 -39
  30. package/docs/v7/features/fast.mdx +63 -81
  31. package/docs/v7/features/managed-sandboxes.mdx +384 -0
  32. package/docs/v7/features/network-monitoring.mdx +568 -0
  33. package/docs/v7/features/observable.mdx +3 -22
  34. package/docs/v7/features/parallel-execution.mdx +381 -0
  35. package/docs/v7/features/powerful.mdx +1 -1
  36. package/docs/v7/features/reports.mdx +414 -0
  37. package/docs/v7/features/sandbox-customization.mdx +229 -0
  38. package/docs/v7/features/scalable.mdx +217 -2
  39. package/docs/v7/features/stable.mdx +106 -147
  40. package/docs/v7/features/system-performance.mdx +616 -0
  41. package/docs/v7/features/test-analytics.mdx +373 -0
  42. package/docs/v7/features/test-cases.mdx +393 -0
  43. package/docs/v7/features/test-replays.mdx +408 -0
  44. package/docs/v7/features/test-reports.mdx +308 -0
  45. package/docs/v7/getting-started/{running-and-debugging.mdx → debugging-tests.mdx} +12 -142
  46. package/docs/v7/getting-started/quickstart.mdx +22 -305
  47. package/docs/v7/getting-started/running-tests.mdx +173 -0
  48. package/docs/v7/overview/what-is-testdriver.mdx +2 -14
  49. package/docs/v7/presets/chrome-extension.mdx +147 -122
  50. package/interfaces/cli/commands/init.js +3 -3
  51. package/interfaces/cli/lib/base.js +3 -2
  52. package/interfaces/logger.js +0 -2
  53. package/interfaces/shared-test-state.mjs +0 -5
  54. package/interfaces/vitest-plugin.mjs +70 -50
  55. package/lib/core/Dashcam.js +60 -85
  56. package/lib/vitest/hooks.mjs +42 -50
  57. package/package.json +1 -1
  58. package/sdk-log-formatter.js +350 -175
  59. package/sdk.d.ts +36 -3
  60. package/sdk.js +431 -116
  61. package/setup/aws/cloudformation.yaml +2 -2
  62. package/setup/aws/self-hosted.yml +1 -1
  63. package/test/testdriver/chrome-extension.test.mjs +55 -72
  64. package/test/testdriver/element-not-found.test.mjs +2 -1
  65. package/test/testdriver/hover-image.test.mjs +1 -1
  66. package/test/testdriver/scroll-until-text.test.mjs +10 -6
  67. package/test/testdriver/setup/lifecycleHelpers.mjs +19 -24
  68. package/test/testdriver/setup/testHelpers.mjs +18 -23
  69. package/vitest.config.mjs +3 -3
  70. package/.github/workflows/linux-tests.yml +0 -28
  71. package/docs/v7/getting-started/generating-tests.mdx +0 -525
  72. package/test/testdriver/auto-cache-key-demo.test.mjs +0 -56
@@ -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={2}>
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. This preset uses the `launchChromeExtension()` helper to launch Chrome with a specific extension loaded by its Chrome Web Store ID.
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 { test } from 'vitest';
16
- import TestDriver from 'testdriverai';
17
- import {
18
- runPrerunChromeExtension,
19
- runPostrun
20
- } from 'testdriverai/testdriver/acceptance-sdk/setup/lifecycleHelpers.mjs';
21
-
22
- test('test chrome extension', async () => {
23
- const client = await TestDriver.create({
24
- apiKey: process.env.TD_API_KEY,
25
- os: "linux",
26
- verbosity: 1,
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
- // Launch Chrome with extension loaded
30
- // Extension ID from Chrome Web Store
31
- await runPrerunChromeExtension(client, "cjpalhdlnbpafiamejdnhcphjbkeiagm");
52
+ ### Using a Chrome Web Store Extension ID
32
53
 
33
- // Your test code here
34
- await client.focusApplication("Google Chrome");
35
-
36
- // ... test extension functionality
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
- await runPostrun(client);
39
- await client.cleanup();
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
- This is the extension ID
96
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
97
+ This is the extension ID
51
98
  ```
52
99
 
53
- ## Popular Extensions
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
- ## Loading Multiple Extensions
109
+ ## Using Your Own Extension
63
110
 
64
- Load multiple extensions by separating IDs with commas:
111
+ To test your own extension, you can either:
65
112
 
113
+ 1. **Clone from a repository:**
66
114
  ```javascript
67
- await launchChromeExtension(
68
- client,
69
- "cjpalhdlnbpafiamejdnhcphjbkeiagm,nngceckbapebfimnlniiiahkandclblb"
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, beforeAll, afterAll } from 'vitest';
77
- import TestDriver from 'testdriverai';
78
- import {
79
- runPrerunChromeExtension,
80
- runPostrun
81
- } from 'testdriverai/testdriver/acceptance-sdk/setup/lifecycleHelpers.mjs';
82
-
83
- describe('Chrome Extension Testing', () => {
84
- let client;
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
- // Load uBlock Origin extension
95
- await runPrerunChromeExtension(client, "cjpalhdlnbpafiamejdnhcphjbkeiagm");
96
- });
97
-
98
- afterAll(async () => {
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
- // Navigate to a page
109
- const element = await client.find("TestDriver.ai Sandbox");
110
- expect(element.found()).toBe(true);
155
+ // Verify the page loaded
156
+ const pageResult = await testdriver.assert("the testdriver.ai website is visible");
157
+ expect(pageResult).toBeTruthy();
111
158
 
112
- // Test extension-specific functionality
113
- // For example, checking if ads are blocked with uBlock
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
- it('should access extension popup', async () => {
117
- await client.focusApplication("Google Chrome");
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
- // Wait for extensions page
128
- await new Promise((resolve) => setTimeout(resolve, 2000));
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
- ## Direct API Usage
143
-
144
- Use the lower-level API for more control:
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
- // Click extension icon (varies by extension)
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
- // Interact with popup
167
- const popup = await client.find('extension popup window');
168
- expect(popup.found()).toBe(true);
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 options
180
- await client.click('Options');
202
+ // Click extension icon in toolbar
203
+ const extensionIcon = await testdriver.find('extension icon in toolbar');
204
+ await extensionIcon.click();
181
205
 
182
- // Configure settings
183
- await client.click('Enable feature X');
184
- await client.click('Save');
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
- // Navigate to a page
193
- await client.exec('sh', 'xdotool key ctrl+l', 5000, true);
194
- await client.type('https://example.com');
195
- await client.pressKeys('Enter');
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 injected = await client.find('element added by extension');
199
- expect(injected.found()).toBe(true);
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 ID is correct
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
@@ -348,7 +348,7 @@ jobs:
348
348
  - name: Run TestDriver.ai tests
349
349
  env:
350
350
  TD_API_KEY: \${{ secrets.TD_API_KEY }}
351
- run: npm test
351
+ run: npx vitest run
352
352
 
353
353
  - name: Upload test results
354
354
  if: always()
@@ -395,11 +395,11 @@ jobs:
395
395
  printNextSteps() {
396
396
  console.log(chalk.cyan("Next steps:\n"));
397
397
  console.log(" 1. Run your tests:");
398
- console.log(chalk.gray(" npm test\n"));
398
+ console.log(chalk.gray(" npx vitest run\n"));
399
399
  console.log(" 2. For CI/CD, add TD_API_KEY to your GitHub repository secrets");
400
400
  console.log(chalk.gray(" Settings → Secrets → Actions → New repository secret\n"));
401
401
  console.log(
402
- chalk.cyan("Learn more at https://docs.testdriver.ai/getting-started\n"),
402
+ chalk.cyan("Learn more at https://docs.testdriver.ai/v7/getting-started/\n"),
403
403
  );
404
404
  }
405
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);
@@ -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;