@testdriverai/agent 7.11.229-canary → 7.11.232-test

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 (100) hide show
  1. package/docs/_data/examples-manifest.json +16 -16
  2. package/docs/_scripts/generate-examples.js +38 -45
  3. package/docs/agent.mdx +174 -93
  4. package/docs/assert.mdx +73 -269
  5. package/docs/aws-setup.mdx +196 -314
  6. package/docs/bring-your-own-machine.mdx +139 -206
  7. package/docs/cache.mdx +144 -151
  8. package/docs/caching.mdx +99 -89
  9. package/docs/captcha.mdx +126 -111
  10. package/docs/ci-cd.mdx +154 -543
  11. package/docs/click.mdx +74 -229
  12. package/docs/client.mdx +177 -231
  13. package/docs/copilot/auto-healing.mdx +86 -370
  14. package/docs/copilot/running-tests.mdx +121 -930
  15. package/docs/customizing-devices.mdx +74 -251
  16. package/docs/dashcam.mdx +72 -405
  17. package/docs/debugging-with-screenshots.mdx +105 -371
  18. package/docs/docs.json +79 -22
  19. package/docs/double-click.mdx +48 -68
  20. package/docs/errors.mdx +102 -176
  21. package/docs/events.mdx +170 -327
  22. package/docs/examples/{hover-text-with-description.mdx → add-to-cart.mdx} +30 -35
  23. package/docs/examples/assert.mdx +28 -41
  24. package/docs/examples/click-button.mdx +71 -0
  25. package/docs/examples/{hover-image.mdx → click-icon.mdx} +32 -28
  26. package/docs/examples/element-not-found.mdx +27 -26
  27. package/docs/examples/findall-coffee-icons.mdx +28 -32
  28. package/docs/examples/installer.mdx +61 -25
  29. package/docs/examples/launch-vscode-linux.mdx +28 -51
  30. package/docs/examples/navigate-and-scroll.mdx +82 -0
  31. package/docs/examples/parse.mdx +24 -28
  32. package/docs/examples/press-keys.mdx +28 -25
  33. package/docs/examples/scroll.mdx +25 -27
  34. package/docs/examples/type.mdx +26 -21
  35. package/docs/exec.mdx +92 -256
  36. package/docs/extract.mdx +69 -170
  37. package/docs/find.mdx +140 -736
  38. package/docs/focus-application.mdx +69 -246
  39. package/docs/generating-tests.mdx +103 -137
  40. package/docs/guide/best-practices-polling.mdx +65 -141
  41. package/docs/hosted.mdx +80 -144
  42. package/docs/hover.mdx +62 -235
  43. package/docs/locating-elements.mdx +74 -369
  44. package/docs/making-assertions.mdx +116 -74
  45. package/docs/mcp-setup.mdx +80 -63
  46. package/docs/mouse-down.mdx +48 -125
  47. package/docs/mouse-up.mdx +47 -129
  48. package/docs/options.mdx +75 -48
  49. package/docs/parse.mdx +95 -184
  50. package/docs/performing-actions.mdx +104 -94
  51. package/docs/press-keys.mdx +59 -299
  52. package/docs/provision.mdx +137 -182
  53. package/docs/quickstart-claude.mdx +85 -0
  54. package/docs/quickstart-github.mdx +62 -32
  55. package/docs/quickstart-manual.mdx +60 -33
  56. package/docs/quickstart-vscode.mdx +97 -0
  57. package/docs/quickstart.mdx +26 -9
  58. package/docs/redraw.mdx +101 -165
  59. package/docs/reusable-code.mdx +97 -200
  60. package/docs/right-click.mdx +47 -89
  61. package/docs/screenshot.mdx +47 -201
  62. package/docs/screenshots.mdx +87 -137
  63. package/docs/scroll.mdx +71 -154
  64. package/docs/secrets.mdx +96 -91
  65. package/docs/self-hosted.mdx +83 -142
  66. package/docs/snippets/enterprise.mdx +15 -0
  67. package/docs/styles.css +68 -0
  68. package/docs/test-results-json.mdx +167 -204
  69. package/docs/type.mdx +56 -304
  70. package/docs/variables.mdx +121 -81
  71. package/docs/wait.mdx +33 -31
  72. package/docs/what-is-testdriver.mdx +80 -32
  73. package/examples/{hover-text-with-description.test.mjs → add-to-cart.test.mjs} +4 -5
  74. package/examples/assert.test.mjs +3 -20
  75. package/examples/{hover-text.test.mjs → click-button.test.mjs} +3 -4
  76. package/examples/{hover-image.test.mjs → click-icon.test.mjs} +5 -6
  77. package/examples/element-not-found.test.mjs +3 -5
  78. package/examples/findall-coffee-icons.test.mjs +3 -3
  79. package/examples/installer.test.mjs +1 -1
  80. package/examples/launch-vscode-linux.test.mjs +6 -33
  81. package/examples/{scroll-keyboard.test.mjs → navigate-and-scroll.test.mjs} +4 -5
  82. package/examples/parse.test.mjs +1 -2
  83. package/examples/press-keys.test.mjs +4 -4
  84. package/examples/scroll.test.mjs +3 -6
  85. package/examples/type.test.mjs +5 -2
  86. package/lib/provision.js +6 -2
  87. package/package.json +1 -1
  88. package/sdk.d.ts +4 -2
  89. package/sdk.js +1 -1
  90. package/docs/claude-mcp-plugin.mdx +0 -244
  91. package/docs/elements.mdx +0 -606
  92. package/docs/examples/ai.mdx +0 -78
  93. package/docs/examples/captcha-api.mdx +0 -92
  94. package/docs/examples/chrome-extension.mdx +0 -156
  95. package/docs/examples/drag-and-drop.mdx +0 -100
  96. package/docs/examples/formatted-logging.mdx +0 -74
  97. package/docs/examples/hover-text.mdx +0 -70
  98. package/docs/examples/match-image.mdx +0 -97
  99. package/docs/examples/scroll-keyboard.mdx +0 -80
  100. package/docs/examples/scroll-until-text.mdx +0 -109
package/docs/type.mdx CHANGED
@@ -1,358 +1,110 @@
1
1
  ---
2
2
  title: "type()"
3
3
  sidebarTitle: "type"
4
- description: "Type text into focused input fields"
4
+ description: "Type text into the field that has keyboard focus, with optional masking for secrets."
5
5
  icon: "keyboard"
6
6
  ---
7
7
 
8
- ## Overview
8
+ Type text into whatever has keyboard focus. Click the field first:
9
9
 
10
- Type text or numbers into the input field that has focus. You can set an optional delay between the keystrokes.
10
+ ```javascript
11
+ await testdriver.find('Email input field').click();
12
+ await testdriver.type('user@example.com');
13
+ ```
11
14
 
12
- ## Syntax
15
+ `type()` is a client method. Elements don't have a `type()` method.
13
16
 
14
- ```javascript
15
- await testdriver.type(text, options)
17
+ ## Signature
18
+
19
+ ```typescript
20
+ testdriver.type(text: string | number, options?: { secret?: boolean }): Promise<void>
16
21
  ```
17
22
 
18
23
  ## Parameters
19
24
 
20
25
  <ParamField path="text" type="string | number" required>
21
- Text to type (can be a string or number)
26
+ The text to type. Numbers are converted to strings.
22
27
  </ParamField>
23
28
 
24
- <ParamField path="options" type="object | number">
25
- Typing options (or legacy delay number)
26
-
29
+ <ParamField path="options" type="object">
27
30
  <Expandable title="properties">
28
- <ParamField path="delay" type="number" default={250}>
29
- Delay between keystrokes in milliseconds
30
- </ParamField>
31
-
32
- <ParamField path="secret" type="boolean" default={false}>
33
- If `true`, TestDriver makes the text sensitive data. It does not log or store it in the debug info or in dashcam.
31
+ <ParamField path="secret" type="boolean" default="false">
32
+ Keep the text out of TestDriver's logs and test results. They show `****` instead. Use it for passwords, tokens and other sensitive values.
34
33
  </ParamField>
35
34
  </Expandable>
36
35
  </ParamField>
37
36
 
38
- ## Returns
39
-
40
- `Promise<void>`
41
-
42
- ## Examples
43
-
44
- ### Basic Typing
45
-
46
- ```javascript
47
- // Type text
48
- await testdriver.type('hello@example.com');
49
-
50
- // Type numbers
51
- await testdriver.type(12345);
52
-
53
- // Type with custom delay (legacy syntax)
54
- await testdriver.type('slow typing', 500); // 500ms between each character
55
-
56
- // Type with options object
57
- await testdriver.type('text', { delay: 500 });
58
- ```
59
-
60
- ### Password/Secret Handling
61
-
62
- ```javascript
63
- // ✅ SECURE - Mark as secret to prevent logging
64
- const passwordField = await testdriver.find('password input');
65
- await passwordField.click();
66
- await testdriver.type('MySecureP@ssw0rd', { secret: true });
67
- // Password NOT logged in dashcam or debug output
68
-
69
- // ❌ INSECURE - Password will be logged
70
- await testdriver.type('MySecureP@ssw0rd');
71
- // Password appears in logs, dashcam replay, and debug info
72
-
73
- // Use secret for any sensitive data
74
- await testdriver.find('api key input').click();
75
- await testdriver.type('sk-1234567890abcdef', { secret: true });
76
-
77
- await testdriver.find('credit card input').click();
78
- await testdriver.type('4111111111111111', { secret: true });
79
- ```
80
-
81
37
  <Warning>
82
- **Always use `secret: true` for passwords and sensitive data.**
83
-
84
- If you do not use this option, the typed text shows in these:
85
- - Dashcam video replays
86
- - TestDriver logs
87
- - Debug screenshots
88
- - Error messages
38
+ `secret: true` doesn't hide what appears on screen. If the field shows its contents, the text is visible in the Dashcam recording.
89
39
  </Warning>
90
40
 
91
- ### Form Filling
41
+ ## Errors
92
42
 
93
- ```javascript
94
- // Focus field and type
95
- const emailField = await testdriver.find('email input');
96
- await emailField.click();
97
- await testdriver.type('user@example.com');
43
+ Throws an error if `text` is `undefined` or `null`. This usually means an environment variable isn't set.
98
44
 
99
- // Tab to next field and type
100
- await testdriver.pressKeys(['tab']);
101
- await testdriver.type('John Doe');
45
+ ## Examples
102
46
 
103
- // Type password securely
104
- await testdriver.pressKeys(['tab']);
105
- await testdriver.type('MySecureP@ssw0rd', { secret: true });
106
- ```
47
+ ### Type a password
107
48
 
108
- ### Clearing and Replacing Text
49
+ Read secrets from environment variables, not from your test code. See [Secrets](/secrets).
109
50
 
110
51
  ```javascript
111
- const searchBox = await testdriver.find('search input');
112
- await searchBox.click();
113
-
114
- // Clear existing text
115
- await testdriver.pressKeys(['ctrl', 'a']); // Select all
116
- await testdriver.type('new search query');
52
+ await testdriver.find('Password input field').click();
53
+ await testdriver.type(process.env.TD_PASSWORD, { secret: true });
117
54
  ```
118
55
 
119
- ## Best Practices
120
-
121
- <Check>
122
- **Focus the field first**
123
-
124
- Always click the input field or navigate to it before typing:
125
-
126
- ```javascript
127
- const input = await testdriver.find('username input');
128
- await input.click();
129
- await testdriver.type('testuser');
130
- ```
131
- </Check>
56
+ ### Replace the text in a field
132
57
 
133
- <Check>
134
- **Use Tab for navigation**
135
-
136
- Navigate between fields using Tab instead of clicking each one:
137
-
138
- ```javascript
139
- const firstField = await testdriver.find('first name');
140
- await firstField.click();
141
- await testdriver.type('John');
142
-
143
- await testdriver.pressKeys(['tab']);
144
- await testdriver.type('Doe');
145
-
146
- await testdriver.pressKeys(['tab']);
147
- await testdriver.type('john@example.com');
148
- ```
149
- </Check>
150
-
151
- <Check>
152
- **Clear fields before typing**
153
-
154
- Clear existing content to avoid appending:
155
-
156
- ```javascript
157
- const input = await testdriver.find('search field');
158
- await input.click();
159
- await testdriver.pressKeys(['ctrl', 'a']); // Select all
160
- await testdriver.type('new search');
161
- ```
162
- </Check>
163
-
164
- <Warning>
165
- **Field must be focused**
166
-
167
- Typing will only work if an input field is currently focused. If no field is focused, the text may be lost or trigger unexpected keyboard shortcuts.
168
- </Warning>
169
-
170
- ## Use Cases
171
-
172
- <AccordionGroup>
173
- <Accordion title="Login Forms">
174
- ```javascript
175
- await testdriver.focusApplication('Google Chrome');
176
-
177
- const usernameField = await testdriver.find('username input');
178
- await usernameField.click();
179
- await testdriver.type('testuser@example.com');
180
-
181
- await testdriver.pressKeys(['tab']);
182
- await testdriver.type('MyP@ssword123', { secret: true });
183
-
184
- await testdriver.pressKeys(['enter']);
185
- ```
186
- </Accordion>
187
-
188
- <Accordion title="Search Fields">
189
- ```javascript
190
- const searchBox = await testdriver.find('search input');
191
- await searchBox.click();
192
- await testdriver.type('laptop computers');
193
- await testdriver.pressKeys(['enter']);
194
-
195
- // Wait for results
196
- await new Promise(r => setTimeout(r, 2000));
197
- ```
198
- </Accordion>
199
-
200
- <Accordion title="Multi-Field Forms">
201
- ```javascript
202
- // First field
203
- const nameField = await testdriver.find('full name input');
204
- await nameField.click();
205
- await testdriver.type('Jane Smith');
206
-
207
- // Navigate with Tab
208
- await testdriver.pressKeys(['tab']);
209
- await testdriver.type('jane.smith@example.com');
210
-
211
- await testdriver.pressKeys(['tab']);
212
- await testdriver.type('+1-555-0123');
213
-
214
- await testdriver.pressKeys(['tab']);
215
- await testdriver.type('123 Main Street');
216
- ```
217
- </Accordion>
218
-
219
- <Accordion title="Text Editors">
220
- ```javascript
221
- const editor = await testdriver.find('text editor area');
222
- await editor.click();
223
-
224
- await testdriver.type('# My Document', 100);
225
- await testdriver.pressKeys(['enter', 'enter']);
226
- await testdriver.type('This is the first paragraph.', 50);
227
- ```
228
- </Accordion>
229
-
230
- <Accordion title="Numeric Input">
231
- ```javascript
232
- const quantityField = await testdriver.find('quantity input');
233
- await quantityField.click();
234
-
235
- // Clear field
236
- await testdriver.pressKeys(['ctrl', 'a']);
237
-
238
- // Type number
239
- await testdriver.type(5);
240
- ```
241
- </Accordion>
242
- </AccordionGroup>
243
-
244
- ## Typing Speed
245
-
246
- Adjust the delay parameter based on your needs:
58
+ `type()` adds to what's already in the field. Select the old text first to replace it:
247
59
 
248
60
  ```javascript
249
- // Fast typing (100ms delay)
250
- await testdriver.type('quick entry', 100);
251
-
252
- // Normal typing (250ms - default)
253
- await testdriver.type('standard speed');
254
-
255
- // Slow typing (500ms delay) - useful for fields with live validation
256
- await testdriver.type('slow and steady', 500);
257
-
258
- // Very slow (1000ms delay) - for problematic fields
259
- await testdriver.type('one by one', 1000);
61
+ await testdriver.find('Search input').click();
62
+ await testdriver.pressKeys(['ctrl', 'a']);
63
+ await testdriver.type('wireless headphones');
260
64
  ```
261
65
 
262
- <Note>
263
- Some applications with live validation or autocomplete may require slower typing speeds to avoid race conditions.
264
- </Note>
66
+ ### Type several lines
265
67
 
266
- ## Special Characters
68
+ `type()` doesn't press Enter for you. Use [`pressKeys()`](/press-keys) between lines:
267
69
 
268
70
  ```javascript
269
- // Email addresses
270
- await testdriver.type('user@example.com');
271
-
272
- // URLs
273
- await testdriver.type('https://example.com/path?query=value');
274
-
275
- // Passwords with special characters
276
- await testdriver.type('P@ssw0rd!#$%');
277
-
278
- // Paths
279
- await testdriver.type('C:\\Users\\Documents\\file.txt');
280
-
281
- // Multi-line text (use pressKeys for Enter)
282
- await testdriver.type('Line 1');
71
+ await testdriver.find('Comment text area').click();
72
+ await testdriver.type('First line');
283
73
  await testdriver.pressKeys(['enter']);
284
- await testdriver.type('Line 2');
74
+ await testdriver.type('Second line');
285
75
  ```
286
76
 
287
- ## Complete Example
77
+ ## Complete example
288
78
 
289
- ```javascript
290
- import { beforeAll, afterAll, describe, it, expect } from 'vitest';
291
- import TestDriver from 'testdriverai';
79
+ ```javascript tests/login.test.mjs
80
+ import { describe, expect, it } from 'vitest';
81
+ import { TestDriver } from 'testdriverai/vitest/hooks';
292
82
 
293
- describe('Form Filling with Type', () => {
294
- let testdriver;
83
+ describe('Login', () => {
84
+ it('signs in with a username and password', async (context) => {
85
+ const testdriver = TestDriver(context);
86
+ await testdriver.provision.chrome({ url: 'https://testdriver-sandbox.vercel.app/login' });
295
87
 
296
- beforeAll(async () => {
297
- client = new TestDriver(process.env.TD_API_KEY);
298
- await testdriver.auth();
299
- await testdriver.connect();
300
- });
88
+ // The sandbox login page shows its test password on screen
89
+ const password = await testdriver.extract('the password');
301
90
 
302
- afterAll(async () => {
303
- await testdriver.disconnect();
304
- });
91
+ await testdriver.find('Username input field').click();
92
+ await testdriver.type('standard_user');
305
93
 
306
- it('should fill out registration form', async () => {
307
- await testdriver.focusApplication('Google Chrome');
308
-
309
- // Email field
310
- const emailField = await testdriver.find('email input field');
311
- await emailField.click();
312
- await testdriver.type('john.doe@example.com');
313
-
314
- // Tab through form fields
315
- await testdriver.pressKeys(['tab']);
316
- await testdriver.type('John');
317
-
318
- await testdriver.pressKeys(['tab']);
319
- await testdriver.type('Doe');
320
-
321
- await testdriver.pressKeys(['tab']);
322
- await testdriver.type('MySecureP@ssword123');
323
-
324
- await testdriver.pressKeys(['tab']);
325
- await testdriver.type('MySecureP@ssword123'); // Confirm password
326
-
327
- // Verify fields were filled
328
- const result = await testdriver.assert('all form fields are filled');
329
- expect(result).toBeTruthy();
330
- });
94
+ await testdriver.find('Password input field').click();
95
+ await testdriver.type(password, { secret: true });
96
+
97
+ await testdriver.find('Sign in button below the password field').click();
331
98
 
332
- it('should update search query', async () => {
333
- const searchBox = await testdriver.find('search input');
334
- await searchBox.click();
335
-
336
- // Type initial search
337
- await testdriver.type('laptops');
338
- await testdriver.pressKeys(['enter']);
339
-
340
- await new Promise(r => setTimeout(r, 2000));
341
-
342
- // Update search
343
- await searchBox.click();
344
- await testdriver.pressKeys(['ctrl', 'a']); // Select all
345
- await testdriver.type('gaming laptops');
346
- await testdriver.pressKeys(['enter']);
347
-
348
- // Verify new search
349
- await testdriver.assert('search results for "gaming laptops" are shown');
99
+ const result = await testdriver.assert('the product list is shown');
100
+ expect(result).toBe(true);
350
101
  });
351
102
  });
352
103
  ```
353
104
 
354
- ## Related Methods
105
+ ## Related
355
106
 
356
- - [`pressKeys()`](/press-keys) - Press keyboard keys and shortcuts
357
- - [`find()`](/find) - Locate input fields
358
- - [`click()`](/click) - Focus input fields
107
+ - [pressKeys()](/press-keys): press Enter, Tab and shortcuts
108
+ - [click()](/click): focus a field before you type
109
+ - [Secrets](/secrets): keep credentials out of your test code
110
+ - [Perform actions](/performing-actions): every kind of action in one guide
@@ -1,111 +1,151 @@
1
1
  ---
2
- title: "Using Variables"
3
- description: "Use dynamic data and secure secrets in your tests"
2
+ title: "Use variables and test data"
3
+ sidebarTitle: "Using variables"
4
+ description: "Drive tests with environment variables, shared fixtures, and generated data."
4
5
  icon: "square-root-variable"
5
6
  ---
6
7
 
7
- Make your tests larger with dynamic data and secure secrets. Select the correct method for your test needs.
8
+ Keep the values your tests type and visit out of the test code. There are three common sources:
8
9
 
9
- ## Environment Variables
10
+ - **Environment variables** for settings that change between environments, such as the app URL.
11
+ - **Fixtures** for a known set of test data you reuse across tests.
12
+ - **Generated data** for values that must be unique on every run.
10
13
 
11
- Use environment variables for **configuration that changes between environments** (dev, staging, production). Also use them for **secrets that you must not commit to the code**. Use this method when you must run the same tests against different servers or with different credentials.
14
+ To keep passwords and tokens out of logs as well, see [Use secrets](/secrets).
12
15
 
13
- ```javascript
14
- import { test } from 'vitest';
15
- import { chrome } from 'testdriverai/presets';
16
-
17
- test('multi-environment testing', async (context) => {
18
- const env = process.env.TEST_ENV || 'staging';
19
- const urls = {
20
- dev: 'https://dev.myapp.com',
21
- staging: 'https://staging.myapp.com',
22
- production: 'https://myapp.com'
23
- };
24
-
25
- const { testdriver } = await chrome(context, {
26
- url: urls[env]
27
- });
28
-
29
- await testdriver.assert('app is running');
16
+ ## Read environment variables
17
+
18
+ Read settings from `process.env` so the same test runs against dev, staging, or production.
19
+
20
+ ```javascript tests/home.test.mjs
21
+ import { expect, it } from 'vitest';
22
+ import { TestDriver } from 'testdriverai/vitest/hooks';
23
+
24
+ const appUrl = process.env.APP_URL ?? 'https://testdriver-sandbox.vercel.app';
25
+
26
+ it('shows the login form', async (context) => {
27
+ const testdriver = TestDriver(context);
28
+ await testdriver.provision.chrome({ url: `${appUrl}/login` });
29
+
30
+ const result = await testdriver.assert('a login form is visible');
31
+ expect(result).toBe(true);
30
32
  });
31
33
  ```
32
34
 
35
+ Set the variable when you run the tests:
36
+
33
37
  ```bash
34
- # Run against different environments
35
- TEST_ENV=dev vitest run
36
- TEST_ENV=staging vitest run
37
- TEST_ENV=production vitest run
38
+ APP_URL=https://staging.example.com npx vitest run
38
39
  ```
39
40
 
40
- ## Test Fixtures
41
+ TestDriver loads a `.env` file in your project root automatically, so you can also keep local values there:
41
42
 
42
- Test fixtures work best when you have **structured, reusable test data** that needs to be shared across multiple tests. Use fixtures when testing different user roles, product catalogs, or any scenario where you want to parameterize tests with a known set of data.
43
+ ```bash .env
44
+ APP_URL=https://staging.example.com
45
+ ```
43
46
 
44
- ```javascript test/fixtures/users.js
45
- export const testUsers = [
46
- { email: 'admin@test.com', role: 'admin' },
47
- { email: 'user@test.com', role: 'user' },
48
- { email: 'guest@test.com', role: 'guest' }
49
- ];
47
+ ## Reuse test data with fixtures
50
48
 
51
- export const products = [
52
- { name: 'Laptop', price: 999 },
53
- { name: 'Mouse', price: 29 },
54
- { name: 'Keyboard', price: 89 }
49
+ Put shared test data in its own module and import it wherever you need it.
50
+
51
+ ```javascript tests/fixtures/usernames.js
52
+ export const usernames = [
53
+ { label: 'a typical username', value: 'standard_user' },
54
+ { label: 'a username with a dot', value: 'jane.doe' },
55
+ { label: 'a 32-character username', value: 'user_with_a_32_character_name_01' },
55
56
  ];
56
57
  ```
57
58
 
58
- ```javascript test/permissions.test.js
59
- import { test } from 'vitest';
60
- import { chrome } from 'testdriverai/presets';
61
- import { testUsers } from './fixtures/users.js';
62
-
63
- test.each(testUsers)('$role can access dashboard', async ({ email, role }, context) => {
64
- const { testdriver } = await chrome(context, { url });
65
-
66
- await testdriver.find('email input').type(email);
67
- await testdriver.find('password input').type('password123');
68
- await testdriver.find('login button').click();
69
-
70
- if (role === 'admin') {
71
- await testdriver.assert('admin panel is visible');
72
- } else {
73
- await testdriver.assert('user dashboard is visible');
74
- }
59
+ Use Vitest's `it.for()` to run the same test once per fixture. Unlike `it.each()`, `it.for()` passes the test `context` as the second argument, which `TestDriver(context)` needs.
60
+
61
+ ```javascript tests/login.test.mjs
62
+ import { expect, it } from 'vitest';
63
+ import { TestDriver } from 'testdriverai/vitest/hooks';
64
+ import { usernames } from './fixtures/usernames.js';
65
+
66
+ it.for(usernames)('accepts $label', async (username, context) => {
67
+ const testdriver = TestDriver(context);
68
+ await testdriver.provision.chrome({ url: 'https://testdriver-sandbox.vercel.app/login' });
69
+
70
+ const usernameField = await testdriver.find('Username input field');
71
+ await usernameField.click();
72
+ await testdriver.type(username.value);
73
+
74
+ const result = await testdriver.assert(`the username field contains "${username.value}"`);
75
+ expect(result).toBe(true);
75
76
  });
76
77
  ```
77
78
 
78
- ## Dynamic Data Generation
79
+ Each fixture becomes its own test with its own sandbox.
79
80
 
80
- Dynamic data generation is perfect for **creating unique test data on each run**, avoiding conflicts with existing records, and **testing edge cases with realistic data**. Use libraries like Faker when you need fresh emails, names, or other data that won't collide with previous test runs.
81
+ ## Generate unique data
81
82
 
82
- ```javascript
83
- import { test } from 'vitest';
84
- import { chrome } from 'testdriverai/presets';
83
+ When a test creates records, such as new accounts, generate fresh values on every run so they don't collide with earlier runs. [Faker](https://fakerjs.dev) is a common choice:
84
+
85
+ ```bash
86
+ npm install --save-dev @faker-js/faker
87
+ ```
88
+
89
+ ```javascript tests/signup.test.mjs
85
90
  import { faker } from '@faker-js/faker';
91
+ import { expect, it } from 'vitest';
92
+ import { TestDriver } from 'testdriverai/vitest/hooks';
93
+
94
+ it('creates an account', async (context) => {
95
+ const testdriver = TestDriver(context);
96
+ await testdriver.provision.chrome({ url: `${process.env.APP_URL}/signup` });
97
+
98
+ const email = faker.internet.email();
99
+
100
+ const emailField = await testdriver.find('Email input field');
101
+ await emailField.click();
102
+ await testdriver.type(email);
86
103
 
87
- test('user registration with dynamic data', async (context) => {
88
- const { testdriver } = await chrome(context, { url });
89
-
90
- // Generate unique test data for each run
91
- const userData = {
92
- firstName: faker.person.firstName(),
93
- lastName: faker.person.lastName(),
94
- email: faker.internet.email(),
95
- password: faker.internet.password({ length: 12 })
96
- };
97
-
98
- await testdriver.find('first name input').type(userData.firstName);
99
- await testdriver.find('last name input').type(userData.lastName);
100
- await testdriver.find('email input').type(userData.email);
101
- await testdriver.find('password input').type(userData.password);
102
- await testdriver.find('register button').click();
103
-
104
- await testdriver.assert('registration successful');
105
- console.log('Registered user:', userData.email);
104
+ const passwordField = await testdriver.find('Password input field');
105
+ await passwordField.click();
106
+ await testdriver.type(faker.internet.password({ length: 12 }), { secret: true });
107
+
108
+ const createAccountButton = await testdriver.find('Create account button below the form');
109
+ await createAccountButton.click();
110
+
111
+ const result = await testdriver.assert('a welcome message is visible');
112
+ expect(result).toBe(true);
106
113
  });
107
114
  ```
108
115
 
109
- ```bash
110
- npm install --save-dev @faker-js/faker
116
+ ## Keep values out of descriptions
117
+
118
+ Pass changing values to [`type()`](/type), not to [`find()`](/find).
119
+
120
+ The [cache](/caching) matches `find()` descriptions exactly. A description that contains a generated value misses the cache on every run:
121
+
122
+ ```javascript
123
+ // Misses the cache whenever the email changes
124
+ const emailField = await testdriver.find(`input next to ${email}`);
111
125
  ```
126
+
127
+ Describe the element itself, and type the value after you click it:
128
+
129
+ ```javascript
130
+ // Same description every run, so it can be cached
131
+ const emailField = await testdriver.find('Email input field');
132
+ await emailField.click();
133
+ await testdriver.type(email);
134
+ ```
135
+
136
+ ## Next steps
137
+
138
+ <CardGroup cols={2}>
139
+ <Card title="Use secrets" icon="key" href="/secrets">
140
+ Keep passwords and tokens out of code and logs.
141
+ </Card>
142
+ <Card title="Reuse code" icon="recycle" href="/reusable-code">
143
+ Share helpers like a login step across tests.
144
+ </Card>
145
+ <Card title="Run in CI/CD" icon="code-branch" href="/ci-cd">
146
+ Pass environment variables to tests in your pipeline.
147
+ </Card>
148
+ <Card title="Caching" icon="brain" href="/caching">
149
+ Write descriptions that reuse cached locations.
150
+ </Card>
151
+ </CardGroup>