@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.
- package/docs/_data/examples-manifest.json +16 -16
- package/docs/_scripts/generate-examples.js +38 -45
- package/docs/agent.mdx +174 -93
- package/docs/assert.mdx +73 -269
- package/docs/aws-setup.mdx +196 -314
- package/docs/bring-your-own-machine.mdx +139 -206
- package/docs/cache.mdx +144 -151
- package/docs/caching.mdx +99 -89
- package/docs/captcha.mdx +126 -111
- package/docs/ci-cd.mdx +154 -543
- package/docs/click.mdx +74 -229
- package/docs/client.mdx +177 -231
- package/docs/copilot/auto-healing.mdx +86 -370
- package/docs/copilot/running-tests.mdx +121 -930
- package/docs/customizing-devices.mdx +74 -251
- package/docs/dashcam.mdx +72 -405
- package/docs/debugging-with-screenshots.mdx +105 -371
- package/docs/docs.json +79 -22
- package/docs/double-click.mdx +48 -68
- package/docs/errors.mdx +102 -176
- package/docs/events.mdx +170 -327
- package/docs/examples/{hover-text-with-description.mdx → add-to-cart.mdx} +30 -35
- package/docs/examples/assert.mdx +28 -41
- package/docs/examples/click-button.mdx +71 -0
- package/docs/examples/{hover-image.mdx → click-icon.mdx} +32 -28
- package/docs/examples/element-not-found.mdx +27 -26
- package/docs/examples/findall-coffee-icons.mdx +28 -32
- package/docs/examples/installer.mdx +61 -25
- package/docs/examples/launch-vscode-linux.mdx +28 -51
- package/docs/examples/navigate-and-scroll.mdx +82 -0
- package/docs/examples/parse.mdx +24 -28
- package/docs/examples/press-keys.mdx +28 -25
- package/docs/examples/scroll.mdx +25 -27
- package/docs/examples/type.mdx +26 -21
- package/docs/exec.mdx +92 -256
- package/docs/extract.mdx +69 -170
- package/docs/find.mdx +140 -736
- package/docs/focus-application.mdx +69 -246
- package/docs/generating-tests.mdx +103 -137
- package/docs/guide/best-practices-polling.mdx +65 -141
- package/docs/hosted.mdx +80 -144
- package/docs/hover.mdx +62 -235
- package/docs/locating-elements.mdx +74 -369
- package/docs/making-assertions.mdx +116 -74
- package/docs/mcp-setup.mdx +80 -63
- package/docs/mouse-down.mdx +48 -125
- package/docs/mouse-up.mdx +47 -129
- package/docs/options.mdx +75 -48
- package/docs/parse.mdx +95 -184
- package/docs/performing-actions.mdx +104 -94
- package/docs/press-keys.mdx +59 -299
- package/docs/provision.mdx +137 -182
- package/docs/quickstart-claude.mdx +85 -0
- package/docs/quickstart-github.mdx +62 -32
- package/docs/quickstart-manual.mdx +60 -33
- package/docs/quickstart-vscode.mdx +97 -0
- package/docs/quickstart.mdx +26 -9
- package/docs/redraw.mdx +101 -165
- package/docs/reusable-code.mdx +97 -200
- package/docs/right-click.mdx +47 -89
- package/docs/screenshot.mdx +47 -201
- package/docs/screenshots.mdx +87 -137
- package/docs/scroll.mdx +71 -154
- package/docs/secrets.mdx +96 -91
- package/docs/self-hosted.mdx +83 -142
- package/docs/snippets/enterprise.mdx +15 -0
- package/docs/styles.css +68 -0
- package/docs/test-results-json.mdx +167 -204
- package/docs/type.mdx +56 -304
- package/docs/variables.mdx +121 -81
- package/docs/wait.mdx +33 -31
- package/docs/what-is-testdriver.mdx +80 -32
- package/examples/{hover-text-with-description.test.mjs → add-to-cart.test.mjs} +4 -5
- package/examples/assert.test.mjs +3 -20
- package/examples/{hover-text.test.mjs → click-button.test.mjs} +3 -4
- package/examples/{hover-image.test.mjs → click-icon.test.mjs} +5 -6
- package/examples/element-not-found.test.mjs +3 -5
- package/examples/findall-coffee-icons.test.mjs +3 -3
- package/examples/installer.test.mjs +1 -1
- package/examples/launch-vscode-linux.test.mjs +6 -33
- package/examples/{scroll-keyboard.test.mjs → navigate-and-scroll.test.mjs} +4 -5
- package/examples/parse.test.mjs +1 -2
- package/examples/press-keys.test.mjs +4 -4
- package/examples/scroll.test.mjs +3 -6
- package/examples/type.test.mjs +5 -2
- package/lib/provision.js +6 -2
- package/package.json +1 -1
- package/sdk.d.ts +4 -2
- package/sdk.js +1 -1
- package/docs/claude-mcp-plugin.mdx +0 -244
- package/docs/elements.mdx +0 -606
- package/docs/examples/ai.mdx +0 -78
- package/docs/examples/captcha-api.mdx +0 -92
- package/docs/examples/chrome-extension.mdx +0 -156
- package/docs/examples/drag-and-drop.mdx +0 -100
- package/docs/examples/formatted-logging.mdx +0 -74
- package/docs/examples/hover-text.mdx +0 -70
- package/docs/examples/match-image.mdx +0 -97
- package/docs/examples/scroll-keyboard.mdx +0 -80
- 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
|
|
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
|
-
|
|
8
|
+
Type text into whatever has keyboard focus. Click the field first:
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
```javascript
|
|
11
|
+
await testdriver.find('Email input field').click();
|
|
12
|
+
await testdriver.type('user@example.com');
|
|
13
|
+
```
|
|
11
14
|
|
|
12
|
-
|
|
15
|
+
`type()` is a client method. Elements don't have a `type()` method.
|
|
13
16
|
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
26
|
+
The text to type. Numbers are converted to strings.
|
|
22
27
|
</ParamField>
|
|
23
28
|
|
|
24
|
-
<ParamField path="options" type="object
|
|
25
|
-
Typing options (or legacy delay number)
|
|
26
|
-
|
|
29
|
+
<ParamField path="options" type="object">
|
|
27
30
|
<Expandable title="properties">
|
|
28
|
-
<ParamField path="
|
|
29
|
-
|
|
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
|
-
|
|
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
|
-
|
|
41
|
+
## Errors
|
|
92
42
|
|
|
93
|
-
|
|
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
|
-
|
|
100
|
-
await testdriver.pressKeys(['tab']);
|
|
101
|
-
await testdriver.type('John Doe');
|
|
45
|
+
## Examples
|
|
102
46
|
|
|
103
|
-
|
|
104
|
-
await testdriver.pressKeys(['tab']);
|
|
105
|
-
await testdriver.type('MySecureP@ssw0rd', { secret: true });
|
|
106
|
-
```
|
|
47
|
+
### Type a password
|
|
107
48
|
|
|
108
|
-
|
|
49
|
+
Read secrets from environment variables, not from your test code. See [Secrets](/secrets).
|
|
109
50
|
|
|
110
51
|
```javascript
|
|
111
|
-
|
|
112
|
-
await
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
250
|
-
await testdriver.
|
|
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
|
-
|
|
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
|
-
|
|
68
|
+
`type()` doesn't press Enter for you. Use [`pressKeys()`](/press-keys) between lines:
|
|
267
69
|
|
|
268
70
|
```javascript
|
|
269
|
-
|
|
270
|
-
await testdriver.type('
|
|
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('
|
|
74
|
+
await testdriver.type('Second line');
|
|
285
75
|
```
|
|
286
76
|
|
|
287
|
-
## Complete
|
|
77
|
+
## Complete example
|
|
288
78
|
|
|
289
|
-
```javascript
|
|
290
|
-
import {
|
|
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('
|
|
294
|
-
|
|
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
|
-
|
|
297
|
-
|
|
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
|
-
|
|
303
|
-
await testdriver.
|
|
304
|
-
});
|
|
91
|
+
await testdriver.find('Username input field').click();
|
|
92
|
+
await testdriver.type('standard_user');
|
|
305
93
|
|
|
306
|
-
|
|
307
|
-
await testdriver.
|
|
308
|
-
|
|
309
|
-
|
|
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
|
-
|
|
333
|
-
|
|
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
|
|
105
|
+
## Related
|
|
355
106
|
|
|
356
|
-
- [
|
|
357
|
-
- [
|
|
358
|
-
- [
|
|
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
|
package/docs/variables.mdx
CHANGED
|
@@ -1,111 +1,151 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: "
|
|
3
|
-
|
|
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
|
-
|
|
8
|
+
Keep the values your tests type and visit out of the test code. There are three common sources:
|
|
8
9
|
|
|
9
|
-
|
|
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
|
-
|
|
14
|
+
To keep passwords and tokens out of logs as well, see [Use secrets](/secrets).
|
|
12
15
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
test
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
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
|
-
|
|
41
|
+
TestDriver loads a `.env` file in your project root automatically, so you can also keep local values there:
|
|
41
42
|
|
|
42
|
-
|
|
43
|
+
```bash .env
|
|
44
|
+
APP_URL=https://staging.example.com
|
|
45
|
+
```
|
|
43
46
|
|
|
44
|
-
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
import {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
await testdriver.
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
79
|
+
Each fixture becomes its own test with its own sandbox.
|
|
79
80
|
|
|
80
|
-
|
|
81
|
+
## Generate unique data
|
|
81
82
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
110
|
-
|
|
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>
|