@testdriverai/agent 7.11.229-canary → 7.11.231-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 +75 -18
- 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 +25 -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/scroll.mdx
CHANGED
|
@@ -1,206 +1,123 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: "scroll()"
|
|
3
3
|
sidebarTitle: "scroll"
|
|
4
|
-
description: "Scroll
|
|
4
|
+
description: "Scroll the page up or down with the mouse wheel."
|
|
5
5
|
icon: "arrows-up-down"
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Scroll the page up or down with the mouse wheel:
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
```javascript
|
|
11
|
+
await testdriver.scroll('down', { amount: 1000 });
|
|
12
|
+
```
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
**Focus Requirements**
|
|
14
|
-
|
|
15
|
-
A scroll needs focus on the page or on a frame. If an input field or another interactive element has focus, scroll commands can fail. Before you scroll, put the focus on the page:
|
|
16
|
-
- Click a non-interactive area (for example, the page background)
|
|
17
|
-
- Push the Escape key to remove the focus from interactive elements
|
|
18
|
-
- Click outside of input fields or text areas
|
|
19
|
-
|
|
20
|
-
**If the scroll still does not work**, use the Page Down or Page Up keys directly:
|
|
21
|
-
```javascript
|
|
22
|
-
await testdriver.pressKeys(['pagedown']); // Scroll down
|
|
23
|
-
await testdriver.pressKeys(['pageup']); // Scroll up
|
|
24
|
-
```
|
|
25
|
-
</Warning>
|
|
14
|
+
If the page doesn't move, click somewhere on it first to give it focus.
|
|
26
15
|
|
|
27
|
-
##
|
|
16
|
+
## Signature
|
|
28
17
|
|
|
29
|
-
```
|
|
30
|
-
|
|
18
|
+
```typescript
|
|
19
|
+
testdriver.scroll(direction?: 'up' | 'down', options?: { amount?: number }): Promise<void>
|
|
31
20
|
```
|
|
32
21
|
|
|
33
22
|
## Parameters
|
|
34
23
|
|
|
35
|
-
<ParamField path="direction" type="
|
|
36
|
-
|
|
24
|
+
<ParamField path="direction" type="'up' | 'down'" default="down">
|
|
25
|
+
Which way to scroll. `'left'` and `'right'` aren't supported: TestDriver logs an error and doesn't scroll.
|
|
37
26
|
</ParamField>
|
|
38
27
|
|
|
39
28
|
<ParamField path="options" type="object">
|
|
40
29
|
<Expandable title="properties">
|
|
41
30
|
<ParamField path="amount" type="number" default="300">
|
|
42
|
-
|
|
31
|
+
How far to scroll. Larger values scroll further.
|
|
43
32
|
</ParamField>
|
|
44
33
|
</Expandable>
|
|
45
34
|
</ParamField>
|
|
46
35
|
|
|
47
|
-
##
|
|
36
|
+
## Errors
|
|
48
37
|
|
|
49
|
-
`
|
|
38
|
+
Throws an error if `direction` isn't `'up'`, `'down'`, `'left'` or `'right'`.
|
|
50
39
|
|
|
51
|
-
|
|
40
|
+
If the screen doesn't change after the scroll, TestDriver logs a warning instead of throwing. This usually means the page didn't have focus.
|
|
41
|
+
|
|
42
|
+
## Scroll until text appears
|
|
52
43
|
|
|
53
|
-
|
|
44
|
+
`testdriver.scrollUntilText()` scrolls in steps until it finds some text on screen:
|
|
54
45
|
|
|
55
46
|
```javascript
|
|
56
|
-
|
|
57
|
-
await testdriver.
|
|
47
|
+
await testdriver.scrollUntilText({ text: 'Privacy policy' });
|
|
48
|
+
await testdriver.find('Privacy policy link in the footer').click();
|
|
49
|
+
```
|
|
58
50
|
|
|
59
|
-
|
|
60
|
-
|
|
51
|
+
<ParamField path="options" type="object" required>
|
|
52
|
+
<Expandable title="properties">
|
|
53
|
+
<ParamField path="text" type="string" required>
|
|
54
|
+
The text to look for.
|
|
55
|
+
</ParamField>
|
|
56
|
+
<ParamField path="direction" type="'up' | 'down'" default="down">
|
|
57
|
+
Which way to scroll.
|
|
58
|
+
</ParamField>
|
|
59
|
+
<ParamField path="maxDistance" type="number" default="10000">
|
|
60
|
+
How far to scroll in total before giving up. Each step scrolls 500.
|
|
61
|
+
</ParamField>
|
|
62
|
+
<ParamField path="invert" type="boolean" default="false">
|
|
63
|
+
Scroll until the text is no longer on screen.
|
|
64
|
+
</ParamField>
|
|
65
|
+
</Expandable>
|
|
66
|
+
</ParamField>
|
|
61
67
|
|
|
62
|
-
|
|
63
|
-
await testdriver.scroll('up');
|
|
68
|
+
It throws an error if the text doesn't appear within `maxDistance`. `testdriver.scrollUntilImage({ description })` works the same way for an image you describe.
|
|
64
69
|
|
|
65
|
-
|
|
66
|
-
await testdriver.scroll('up', { amount: 2 });
|
|
67
|
-
```
|
|
70
|
+
## Examples
|
|
68
71
|
|
|
69
|
-
###
|
|
72
|
+
### Focus the page, then scroll
|
|
70
73
|
|
|
71
74
|
```javascript
|
|
72
|
-
|
|
73
|
-
await testdriver.scroll('
|
|
74
|
-
|
|
75
|
-
// Scroll left
|
|
76
|
-
await testdriver.scroll('left', { amount: 3 });
|
|
75
|
+
await testdriver.find('the page heading').click();
|
|
76
|
+
await testdriver.scroll('down', { amount: 1000 });
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
-
###
|
|
79
|
+
### Load more results in an infinite list
|
|
80
80
|
|
|
81
81
|
```javascript
|
|
82
|
-
|
|
83
|
-
await testdriver.scroll('down', { amount:
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
await testdriver.pressKeys(['pagedown']);
|
|
82
|
+
for (let i = 0; i < 3; i++) {
|
|
83
|
+
await testdriver.scroll('down', { amount: 1000 });
|
|
84
|
+
}
|
|
85
|
+
await testdriver.assert('more than 20 results are listed');
|
|
87
86
|
```
|
|
88
87
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
**Make sure that the page has focus before you scroll**
|
|
93
|
-
|
|
94
|
-
```javascript
|
|
95
|
-
// After typing in an input, unfocus it first
|
|
96
|
-
await testdriver.find('email input').click();
|
|
97
|
-
await testdriver.type('user@example.com');
|
|
98
|
-
|
|
99
|
-
// Click elsewhere or press Escape before scrolling
|
|
100
|
-
await testdriver.pressKeys(['escape']);
|
|
101
|
-
// Or click a non-interactive area
|
|
102
|
-
// await testdriver.find('page background').click();
|
|
103
|
-
|
|
104
|
-
// Now scroll will work properly
|
|
105
|
-
await testdriver.scroll('down');
|
|
106
|
-
|
|
107
|
-
// If scroll still doesn't work, use Page Down directly
|
|
108
|
-
// await testdriver.pressKeys(['pagedown']);
|
|
109
|
-
```
|
|
110
|
-
</Check>
|
|
111
|
-
|
|
112
|
-
<Check>
|
|
113
|
-
**Control the scroll distance with the options object**
|
|
114
|
-
|
|
115
|
-
```javascript
|
|
116
|
-
// For web pages, mouse scroll works well
|
|
117
|
-
await testdriver.scroll('down', { amount: 3 });
|
|
118
|
-
|
|
119
|
-
// For desktop apps or when mouse doesn't work, use keyboard
|
|
120
|
-
await testdriver.pressKeys(['pagedown']);
|
|
121
|
-
```
|
|
122
|
-
</Check>
|
|
123
|
-
|
|
124
|
-
<Warning>
|
|
125
|
-
**The keyboard scroll uses Page Down and Page Up**
|
|
126
|
-
|
|
127
|
-
A keyboard scroll usually moves one "page" each time. This can be more than the click amount that you set. It is more compatible than a mouse scroll, but it is less precise.
|
|
128
|
-
</Warning>
|
|
129
|
-
|
|
130
|
-
## Use Cases
|
|
131
|
-
|
|
132
|
-
<AccordionGroup>
|
|
133
|
-
<Accordion title="Infinite Scroll">
|
|
134
|
-
```javascript
|
|
135
|
-
// Scroll multiple times for infinite scroll
|
|
136
|
-
for (let i = 0; i < 5; i++) {
|
|
137
|
-
await testdriver.scroll('down', { amount: 5 });
|
|
138
|
-
await new Promise(r => setTimeout(r, 1000)); // Wait for load
|
|
139
|
-
}
|
|
140
|
-
```
|
|
141
|
-
</Accordion>
|
|
142
|
-
|
|
143
|
-
<Accordion title="Horizontal Gallery">
|
|
144
|
-
```javascript
|
|
145
|
-
// Navigate horizontal carousel
|
|
146
|
-
await testdriver.scroll('right', { amount: 3 });
|
|
147
|
-
await new Promise(r => setTimeout(r, 500));
|
|
148
|
-
|
|
149
|
-
const nextImage = await testdriver.find('next image in carousel');
|
|
150
|
-
await nextImage.click();
|
|
151
|
-
```
|
|
152
|
-
</Accordion>
|
|
153
|
-
</AccordionGroup>
|
|
154
|
-
|
|
155
|
-
## Complete Example
|
|
88
|
+
### Use the keyboard instead
|
|
89
|
+
|
|
90
|
+
Some apps don't respond to the mouse wheel. Press Page Down with [`pressKeys()`](/press-keys):
|
|
156
91
|
|
|
157
92
|
```javascript
|
|
158
|
-
|
|
159
|
-
|
|
93
|
+
await testdriver.pressKeys(['pagedown']);
|
|
94
|
+
```
|
|
160
95
|
|
|
161
|
-
|
|
162
|
-
let testdriver;
|
|
96
|
+
## Complete example
|
|
163
97
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
await testdriver.connect();
|
|
168
|
-
});
|
|
98
|
+
```javascript tests/scroll.test.mjs
|
|
99
|
+
import { describe, expect, it } from 'vitest';
|
|
100
|
+
import { TestDriver } from 'testdriverai/vitest/hooks';
|
|
169
101
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
102
|
+
describe('Scroll', () => {
|
|
103
|
+
it('scrolls the heading out of view', async (context) => {
|
|
104
|
+
const testdriver = TestDriver(context);
|
|
105
|
+
await testdriver.provision.chrome({ url: 'https://www.webhamster.com/' });
|
|
173
106
|
|
|
174
|
-
|
|
175
|
-
await testdriver.
|
|
176
|
-
|
|
177
|
-
// Scroll down the page
|
|
178
|
-
await testdriver.scroll('down', { amount: 5 });
|
|
179
|
-
|
|
180
|
-
// Click footer link
|
|
181
|
-
const privacyLink = await testdriver.find('Privacy Policy link');
|
|
182
|
-
await privacyLink.click();
|
|
183
|
-
|
|
184
|
-
await testdriver.assert('privacy policy page is displayed');
|
|
185
|
-
});
|
|
107
|
+
// Click the page so it has focus before scrolling
|
|
108
|
+
await testdriver.find('The Hamster Dance, large heading at the top of the page').click();
|
|
109
|
+
await testdriver.scroll('down', { amount: 1000 });
|
|
186
110
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
for (let i = 0; i < 3; i++) {
|
|
192
|
-
await testdriver.scroll('down', { amount: 5 });
|
|
193
|
-
await new Promise(r => setTimeout(r, 1500)); // Wait for load
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// Verify content loaded
|
|
197
|
-
await testdriver.assert('more than 10 items are visible');
|
|
111
|
+
const result = await testdriver.assert(
|
|
112
|
+
"the text 'The Hamster Dance' isn't visible in the page content",
|
|
113
|
+
);
|
|
114
|
+
expect(result).toBe(true);
|
|
198
115
|
});
|
|
199
116
|
});
|
|
200
117
|
```
|
|
201
118
|
|
|
202
|
-
## Related
|
|
119
|
+
## Related
|
|
203
120
|
|
|
204
|
-
- [
|
|
205
|
-
- [
|
|
206
|
-
- [
|
|
121
|
+
- [pressKeys()](/press-keys): scroll with Page Down, Home and End
|
|
122
|
+
- [find()](/find): locate an element after scrolling to it
|
|
123
|
+
- [Perform actions](/performing-actions): every kind of action in one guide
|
package/docs/secrets.mdx
CHANGED
|
@@ -1,115 +1,120 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: "
|
|
3
|
-
|
|
2
|
+
title: "Use secrets"
|
|
3
|
+
sidebarTitle: "Using secrets"
|
|
4
|
+
description: "Keep passwords and tokens out of your test code and TestDriver's logs."
|
|
4
5
|
icon: "key"
|
|
5
6
|
---
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
Store credentials outside your code, and mark them as secret when you type them.
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
- Read passwords and tokens from environment variables, never from the test file.
|
|
11
|
+
- Type them with `secret: true` so TestDriver doesn't log or store them.
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
## Type a secret
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
import { test } from 'vitest';
|
|
15
|
-
import { chrome } from 'testdriverai/presets';
|
|
15
|
+
Pass `{ secret: true }` to [`type()`](/type) for any sensitive value. Click the field first, then type:
|
|
16
16
|
|
|
17
|
-
test
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
});
|
|
17
|
+
```javascript tests/login.test.mjs
|
|
18
|
+
import { expect, it } from 'vitest';
|
|
19
|
+
import { TestDriver } from 'testdriverai/vitest/hooks';
|
|
21
20
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
await
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
await testdriver.
|
|
21
|
+
it('signs in', async (context) => {
|
|
22
|
+
const testdriver = TestDriver(context);
|
|
23
|
+
await testdriver.provision.chrome({ url: `${process.env.APP_URL}/login` });
|
|
24
|
+
|
|
25
|
+
const usernameField = await testdriver.find('Username input field');
|
|
26
|
+
await usernameField.click();
|
|
27
|
+
await testdriver.type(process.env.TEST_USERNAME);
|
|
28
|
+
|
|
29
|
+
const passwordField = await testdriver.find('Password input field');
|
|
30
|
+
await passwordField.click();
|
|
31
|
+
await testdriver.type(process.env.TEST_PASSWORD, { secret: true });
|
|
32
|
+
|
|
33
|
+
const signInButton = await testdriver.find('Sign in button below the password field');
|
|
34
|
+
await signInButton.click();
|
|
35
|
+
|
|
36
|
+
const result = await testdriver.assert('the dashboard is visible');
|
|
37
|
+
expect(result).toBe(true);
|
|
31
38
|
});
|
|
32
39
|
```
|
|
33
40
|
|
|
34
|
-
|
|
35
|
-
When you set `secret: true`, the typed text shows as `****` in all logs, recordings, and dashcam output.
|
|
36
|
-
</Note>
|
|
41
|
+
## What `secret: true` hides
|
|
37
42
|
|
|
38
|
-
|
|
43
|
+
With `secret: true`, TestDriver replaces the text with `****`:
|
|
39
44
|
|
|
40
|
-
|
|
45
|
+
- In the SDK's console output.
|
|
46
|
+
- In the record of the action that TestDriver stores for the test run.
|
|
41
47
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
Click **New repository secret** and add your secrets:
|
|
48
|
-
- `TD_API_KEY` - Your TestDriver API key
|
|
49
|
-
- `TD_USERNAME` - Test account username
|
|
50
|
-
- `TD_PASSWORD` - Test account password
|
|
51
|
-
</Step>
|
|
52
|
-
<Step title="Use in GitHub Actions">
|
|
53
|
-
Reference secrets in your workflow file:
|
|
54
|
-
```yaml .github/workflows/test.yml
|
|
55
|
-
- name: Run TestDriver tests
|
|
56
|
-
env:
|
|
57
|
-
TD_API_KEY: ${{ secrets.TD_API_KEY }}
|
|
58
|
-
TD_USERNAME: ${{ secrets.TD_USERNAME }}
|
|
59
|
-
TD_PASSWORD: ${{ secrets.TD_PASSWORD }}
|
|
60
|
-
run: vitest run
|
|
61
|
-
```
|
|
62
|
-
</Step>
|
|
63
|
-
</Steps>
|
|
48
|
+
The text is still typed into the sandbox, so `secret: true` can't hide what the screen shows. If the field displays its contents, the value appears in the [Dashcam](/dashcam) recording and in screenshots. Password fields usually mask what you type, but plain text fields don't.
|
|
49
|
+
|
|
50
|
+
<Warning>
|
|
51
|
+
With [`autoScreenshots`](/options#param-auto-screenshots) on, screenshot file names include the first characters of the typed text, even when `secret` is `true`. Leave `autoScreenshots` off in tests that type secrets.
|
|
52
|
+
</Warning>
|
|
64
53
|
|
|
65
|
-
##
|
|
54
|
+
## Store secrets locally
|
|
66
55
|
|
|
67
|
-
|
|
56
|
+
TestDriver loads a `.env` file in your project root automatically. Put your local credentials there:
|
|
68
57
|
|
|
69
58
|
```bash .env
|
|
70
|
-
TD_API_KEY=
|
|
71
|
-
|
|
72
|
-
|
|
59
|
+
TD_API_KEY=your_api_key
|
|
60
|
+
APP_URL=https://staging.example.com
|
|
61
|
+
TEST_USERNAME=qa@example.com
|
|
62
|
+
TEST_PASSWORD=your_test_password
|
|
73
63
|
```
|
|
74
64
|
|
|
75
|
-
|
|
76
|
-
Never commit `.env` files to version control. Add `.env` to your `.gitignore` file.
|
|
77
|
-
</Warning>
|
|
65
|
+
Add `.env` to `.gitignore` so it's never committed:
|
|
78
66
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
Here's a full login test with proper secrets handling:
|
|
82
|
-
|
|
83
|
-
```javascript tests/login.test.js
|
|
84
|
-
import { test, expect } from 'vitest';
|
|
85
|
-
import { chrome } from 'testdriverai/presets';
|
|
86
|
-
|
|
87
|
-
test('secure login flow', async (context) => {
|
|
88
|
-
const { testdriver } = await chrome(context, {
|
|
89
|
-
url: process.env.TD_WEBSITE || 'https://staging.myapp.com'
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
// Enter username (not sensitive)
|
|
93
|
-
await testdriver.find('email input').click();
|
|
94
|
-
await testdriver.type(process.env.TD_USERNAME);
|
|
95
|
-
|
|
96
|
-
// Enter password securely
|
|
97
|
-
await testdriver.find('password input').click();
|
|
98
|
-
await testdriver.type(process.env.TD_PASSWORD, { secret: true });
|
|
99
|
-
|
|
100
|
-
// Submit login
|
|
101
|
-
await testdriver.find('login button').click();
|
|
102
|
-
|
|
103
|
-
// Verify successful login
|
|
104
|
-
const loggedIn = await testdriver.assert('user is logged in');
|
|
105
|
-
expect(loggedIn).toBeTruthy();
|
|
106
|
-
});
|
|
67
|
+
```bash .gitignore
|
|
68
|
+
.env
|
|
107
69
|
```
|
|
108
70
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
71
|
+
## Store secrets in CI
|
|
72
|
+
|
|
73
|
+
In GitHub Actions, store each credential as a repository secret:
|
|
74
|
+
|
|
75
|
+
<Steps>
|
|
76
|
+
<Step title="Open your repository's secrets">
|
|
77
|
+
In GitHub, go to **Settings** > **Secrets and variables** > **Actions**.
|
|
78
|
+
</Step>
|
|
79
|
+
<Step title="Add each secret">
|
|
80
|
+
Click **New repository secret** and add `TEST_USERNAME` and `TEST_PASSWORD`.
|
|
81
|
+
</Step>
|
|
82
|
+
<Step title="Pass them to the test step">
|
|
83
|
+
Map the secrets to environment variables in your workflow:
|
|
84
|
+
|
|
85
|
+
```yaml .github/workflows/testdriver.yml
|
|
86
|
+
- name: Run TestDriver tests
|
|
87
|
+
run: npx vitest run
|
|
88
|
+
env:
|
|
89
|
+
APP_URL: https://staging.example.com
|
|
90
|
+
TEST_USERNAME: ${{ secrets.TEST_USERNAME }}
|
|
91
|
+
TEST_PASSWORD: ${{ secrets.TEST_PASSWORD }}
|
|
92
|
+
```
|
|
93
|
+
</Step>
|
|
94
|
+
</Steps>
|
|
95
|
+
|
|
96
|
+
You don't need to store your TestDriver API key this way. The `testdriverai/action` step authenticates with GitHub OIDC. See [Run in CI/CD](/ci-cd) for the full workflow.
|
|
97
|
+
|
|
98
|
+
## Checklist
|
|
99
|
+
|
|
100
|
+
- Type passwords, tokens, and other sensitive values with `secret: true`.
|
|
101
|
+
- Read credentials from environment variables, not from the test file.
|
|
102
|
+
- Keep `.env` out of version control.
|
|
103
|
+
- Use test accounts with only the access your tests need.
|
|
104
|
+
|
|
105
|
+
## Next steps
|
|
106
|
+
|
|
107
|
+
<CardGroup cols={2}>
|
|
108
|
+
<Card title="Use variables and test data" icon="square-root-variable" href="/variables">
|
|
109
|
+
Drive tests with environment variables and fixtures.
|
|
110
|
+
</Card>
|
|
111
|
+
<Card title="type()" icon="keyboard" href="/type">
|
|
112
|
+
Every option for typing text.
|
|
113
|
+
</Card>
|
|
114
|
+
<Card title="Run in CI/CD" icon="code-branch" href="/ci-cd">
|
|
115
|
+
Authenticate and run tests in your pipeline.
|
|
116
|
+
</Card>
|
|
117
|
+
<Card title="Reuse code" icon="recycle" href="/reusable-code">
|
|
118
|
+
Put your login steps in a shared helper.
|
|
119
|
+
</Card>
|
|
120
|
+
</CardGroup>
|