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