testdriverai 7.1.4 → 7.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/acceptance.yaml +81 -0
- package/.github/workflows/publish.yaml +44 -0
- package/agent/index.js +18 -19
- package/agent/interface.js +4 -0
- package/agent/lib/commands.js +321 -121
- package/agent/lib/redraw.js +99 -39
- package/agent/lib/sandbox.js +98 -6
- package/agent/lib/sdk.js +25 -0
- package/agent/lib/system.js +2 -1
- package/agent/lib/validation.js +6 -6
- package/docs/docs.json +211 -101
- package/docs/snippets/tests/type-repeated-replay.mdx +1 -1
- package/docs/v7/_drafts/caching-selectors.mdx +24 -0
- package/docs/v7/api/act.mdx +1 -1
- package/docs/v7/api/assert.mdx +1 -1
- package/docs/v7/api/assertions.mdx +7 -7
- package/docs/v7/api/elements.mdx +78 -0
- package/docs/v7/api/find.mdx +38 -0
- package/docs/v7/api/focusApplication.mdx +2 -2
- package/docs/v7/api/hover.mdx +2 -2
- package/docs/v7/features/ai-native.mdx +57 -71
- package/docs/v7/features/application-logs.mdx +353 -0
- package/docs/v7/features/browser-logs.mdx +414 -0
- package/docs/v7/features/cache-management.mdx +402 -0
- package/docs/v7/features/continuous-testing.mdx +346 -0
- package/docs/v7/features/coverage.mdx +508 -0
- package/docs/v7/features/data-driven-testing.mdx +441 -0
- package/docs/v7/features/easy-to-write.mdx +2 -73
- package/docs/v7/features/enterprise.mdx +155 -39
- package/docs/v7/features/fast.mdx +63 -81
- package/docs/v7/features/managed-sandboxes.mdx +384 -0
- package/docs/v7/features/network-monitoring.mdx +568 -0
- package/docs/v7/features/observable.mdx +3 -22
- package/docs/v7/features/parallel-execution.mdx +381 -0
- package/docs/v7/features/powerful.mdx +1 -1
- package/docs/v7/features/reports.mdx +414 -0
- package/docs/v7/features/sandbox-customization.mdx +229 -0
- package/docs/v7/features/scalable.mdx +217 -2
- package/docs/v7/features/stable.mdx +106 -147
- package/docs/v7/features/system-performance.mdx +616 -0
- package/docs/v7/features/test-analytics.mdx +373 -0
- package/docs/v7/features/test-cases.mdx +393 -0
- package/docs/v7/features/test-replays.mdx +408 -0
- package/docs/v7/features/test-reports.mdx +308 -0
- package/docs/v7/getting-started/{running-and-debugging.mdx → debugging-tests.mdx} +12 -142
- package/docs/v7/getting-started/quickstart.mdx +22 -305
- package/docs/v7/getting-started/running-tests.mdx +173 -0
- package/docs/v7/overview/what-is-testdriver.mdx +2 -14
- package/docs/v7/presets/chrome-extension.mdx +147 -122
- package/interfaces/cli/commands/init.js +3 -3
- package/interfaces/cli/lib/base.js +3 -2
- package/interfaces/logger.js +0 -2
- package/interfaces/shared-test-state.mjs +0 -5
- package/interfaces/vitest-plugin.mjs +70 -50
- package/lib/core/Dashcam.js +60 -85
- package/lib/vitest/hooks.mjs +42 -50
- package/package.json +1 -1
- package/sdk-log-formatter.js +350 -175
- package/sdk.d.ts +36 -3
- package/sdk.js +431 -116
- package/setup/aws/cloudformation.yaml +2 -2
- package/setup/aws/self-hosted.yml +1 -1
- package/test/testdriver/chrome-extension.test.mjs +55 -72
- package/test/testdriver/element-not-found.test.mjs +2 -1
- package/test/testdriver/hover-image.test.mjs +1 -1
- package/test/testdriver/scroll-until-text.test.mjs +10 -6
- package/test/testdriver/setup/lifecycleHelpers.mjs +19 -24
- package/test/testdriver/setup/testHelpers.mjs +18 -23
- package/vitest.config.mjs +3 -3
- package/.github/workflows/linux-tests.yml +0 -28
- package/docs/v7/getting-started/generating-tests.mdx +0 -525
- package/test/testdriver/auto-cache-key-demo.test.mjs +0 -56
|
@@ -1,11 +1,226 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: "
|
|
2
|
+
title: "Scalability"
|
|
3
3
|
description: "From small projects to enterprise test suites with thousands of tests"
|
|
4
4
|
icon: "arrow-up-right-dots"
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
TestDriver scales effortlessly from your first test to enterprise suites with thousands of tests running in parallel across multiple platforms.
|
|
8
8
|
|
|
9
|
+
## Code Snippets for Reusability
|
|
10
|
+
|
|
11
|
+
Scale your test suite with reusable code snippets to reduce duplication and maintain consistency:
|
|
12
|
+
|
|
13
|
+
<Tabs>
|
|
14
|
+
<Tab title="Helper Functions">
|
|
15
|
+
```javascript test/helpers/auth.js
|
|
16
|
+
// Reusable authentication helpers
|
|
17
|
+
export async function login(testdriver, { email, password }) {
|
|
18
|
+
await testdriver.find('email input').type(email);
|
|
19
|
+
await testdriver.find('password input').type(password);
|
|
20
|
+
await testdriver.find('login button').click();
|
|
21
|
+
await testdriver.assert('logged in successfully');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function logout(testdriver) {
|
|
25
|
+
await testdriver.find('user menu').click();
|
|
26
|
+
await testdriver.find('logout button').click();
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
```javascript test/checkout.test.js
|
|
31
|
+
import { login } from './helpers/auth.js';
|
|
32
|
+
|
|
33
|
+
test('checkout flow', async (context) => {
|
|
34
|
+
const { testdriver } = await chrome(context, { url });
|
|
35
|
+
await login(testdriver, {
|
|
36
|
+
email: 'user@example.com',
|
|
37
|
+
password: 'password123'
|
|
38
|
+
});
|
|
39
|
+
// Continue with checkout test
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
</Tab>
|
|
43
|
+
|
|
44
|
+
<Tab title="Page Objects">
|
|
45
|
+
```javascript test/pages/LoginPage.js
|
|
46
|
+
export class LoginPage {
|
|
47
|
+
constructor(testdriver) {
|
|
48
|
+
this.testdriver = testdriver;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async login(email, password) {
|
|
52
|
+
await this.testdriver.find('email input').type(email);
|
|
53
|
+
await this.testdriver.find('password input').type(password);
|
|
54
|
+
await this.testdriver.find('submit button').click();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async assertError(message) {
|
|
58
|
+
await this.testdriver.assert(`error message shows "${message}"`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
```javascript test/auth.test.js
|
|
64
|
+
import { LoginPage } from './pages/LoginPage.js';
|
|
65
|
+
|
|
66
|
+
test('invalid login shows error', async (context) => {
|
|
67
|
+
const { testdriver } = await chrome(context, { url });
|
|
68
|
+
const loginPage = new LoginPage(testdriver);
|
|
69
|
+
|
|
70
|
+
await loginPage.login('invalid@test.com', 'wrong');
|
|
71
|
+
await loginPage.assertError('Invalid credentials');
|
|
72
|
+
});
|
|
73
|
+
```
|
|
74
|
+
</Tab>
|
|
75
|
+
|
|
76
|
+
<Tab title="Custom Commands">
|
|
77
|
+
```javascript test/commands/navigation.js
|
|
78
|
+
export function addNavigationCommands(testdriver) {
|
|
79
|
+
testdriver.navigateTo = async (section) => {
|
|
80
|
+
await testdriver.find(`${section} nav link`).click();
|
|
81
|
+
await testdriver.assert(`${section} page is loaded`);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
testdriver.searchFor = async (query) => {
|
|
85
|
+
await testdriver.find('search input').type(query);
|
|
86
|
+
await testdriver.find('search button').click();
|
|
87
|
+
await testdriver.assert('search results are displayed');
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
return testdriver;
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
```javascript test/search.test.js
|
|
95
|
+
import { addNavigationCommands } from './commands/navigation.js';
|
|
96
|
+
|
|
97
|
+
test('search functionality', async (context) => {
|
|
98
|
+
let { testdriver } = await chrome(context, { url });
|
|
99
|
+
testdriver = addNavigationCommands(testdriver);
|
|
100
|
+
|
|
101
|
+
await testdriver.searchFor('laptop');
|
|
102
|
+
// Custom command used
|
|
103
|
+
});
|
|
104
|
+
```
|
|
105
|
+
</Tab>
|
|
106
|
+
</Tabs>
|
|
107
|
+
|
|
108
|
+
<Check>
|
|
109
|
+
Reusable snippets reduce test maintenance time by up to 70% in large test suites.
|
|
110
|
+
</Check>
|
|
111
|
+
|
|
112
|
+
## Dynamic Variables for Data-Driven Tests
|
|
113
|
+
|
|
114
|
+
Scale your testing with dynamic data to cover more scenarios:
|
|
115
|
+
|
|
116
|
+
<Tabs>
|
|
117
|
+
<Tab title="Environment Variables">
|
|
118
|
+
```javascript
|
|
119
|
+
import { test } from 'vitest';
|
|
120
|
+
import { chrome } from 'testdriverai/presets';
|
|
121
|
+
|
|
122
|
+
test('multi-environment testing', async (context) => {
|
|
123
|
+
const env = process.env.TEST_ENV || 'staging';
|
|
124
|
+
const urls = {
|
|
125
|
+
dev: 'https://dev.myapp.com',
|
|
126
|
+
staging: 'https://staging.myapp.com',
|
|
127
|
+
production: 'https://myapp.com'
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const { testdriver } = await chrome(context, {
|
|
131
|
+
url: urls[env]
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
await testdriver.assert('app is running');
|
|
135
|
+
});
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
# Run against different environments
|
|
140
|
+
TEST_ENV=dev npx vitest run
|
|
141
|
+
TEST_ENV=staging npx vitest run
|
|
142
|
+
TEST_ENV=production npx vitest run
|
|
143
|
+
```
|
|
144
|
+
</Tab>
|
|
145
|
+
|
|
146
|
+
<Tab title="Test Fixtures">
|
|
147
|
+
```javascript test/fixtures/users.js
|
|
148
|
+
export const testUsers = [
|
|
149
|
+
{ email: 'admin@test.com', role: 'admin' },
|
|
150
|
+
{ email: 'user@test.com', role: 'user' },
|
|
151
|
+
{ email: 'guest@test.com', role: 'guest' }
|
|
152
|
+
];
|
|
153
|
+
|
|
154
|
+
export const products = [
|
|
155
|
+
{ name: 'Laptop', price: 999 },
|
|
156
|
+
{ name: 'Mouse', price: 29 },
|
|
157
|
+
{ name: 'Keyboard', price: 89 }
|
|
158
|
+
];
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
```javascript test/permissions.test.js
|
|
162
|
+
import { test } from 'vitest';
|
|
163
|
+
import { chrome } from 'testdriverai/presets';
|
|
164
|
+
import { testUsers } from './fixtures/users.js';
|
|
165
|
+
|
|
166
|
+
test.each(testUsers)('$role can access dashboard', async ({ email, role }, context) => {
|
|
167
|
+
const { testdriver } = await chrome(context, { url });
|
|
168
|
+
|
|
169
|
+
await testdriver.find('email input').type(email);
|
|
170
|
+
await testdriver.find('password input').type('password123');
|
|
171
|
+
await testdriver.find('login button').click();
|
|
172
|
+
|
|
173
|
+
if (role === 'admin') {
|
|
174
|
+
await testdriver.assert('admin panel is visible');
|
|
175
|
+
} else {
|
|
176
|
+
await testdriver.assert('user dashboard is visible');
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
```
|
|
180
|
+
</Tab>
|
|
181
|
+
|
|
182
|
+
<Tab title="Dynamic Data Generation">
|
|
183
|
+
```javascript
|
|
184
|
+
import { test } from 'vitest';
|
|
185
|
+
import { chrome } from 'testdriverai/presets';
|
|
186
|
+
import { faker } from '@faker-js/faker';
|
|
187
|
+
|
|
188
|
+
test('user registration with dynamic data', async (context) => {
|
|
189
|
+
const { testdriver } = await chrome(context, { url });
|
|
190
|
+
|
|
191
|
+
// Generate unique test data for each run
|
|
192
|
+
const userData = {
|
|
193
|
+
firstName: faker.person.firstName(),
|
|
194
|
+
lastName: faker.person.lastName(),
|
|
195
|
+
email: faker.internet.email(),
|
|
196
|
+
password: faker.internet.password({ length: 12 })
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
await testdriver.find('first name input').type(userData.firstName);
|
|
200
|
+
await testdriver.find('last name input').type(userData.lastName);
|
|
201
|
+
await testdriver.find('email input').type(userData.email);
|
|
202
|
+
await testdriver.find('password input').type(userData.password);
|
|
203
|
+
await testdriver.find('register button').click();
|
|
204
|
+
|
|
205
|
+
await testdriver.assert('registration successful');
|
|
206
|
+
console.log('Registered user:', userData.email);
|
|
207
|
+
});
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
npm install --save-dev @faker-js/faker
|
|
212
|
+
```
|
|
213
|
+
</Tab>
|
|
214
|
+
</Tabs>
|
|
215
|
+
|
|
216
|
+
<Card title="Dynamic Variables Best Practices" icon="lightbulb">
|
|
217
|
+
- **Environment configs:** Store URLs, credentials, and settings in env vars
|
|
218
|
+
- **Test fixtures:** Maintain reusable test data in separate files
|
|
219
|
+
- **Data generators:** Use libraries like Faker for unique test data
|
|
220
|
+
- **Parameterization:** Test multiple scenarios with `test.each()`
|
|
221
|
+
- **CI/CD integration:** Pass dynamic values via environment variables
|
|
222
|
+
</Card>
|
|
223
|
+
|
|
9
224
|
## Works with Vitest
|
|
10
225
|
|
|
11
226
|
Full integration with Vitest's powerful features:
|
|
@@ -25,7 +240,7 @@ export default defineConfig({
|
|
|
25
240
|
```
|
|
26
241
|
|
|
27
242
|
<CardGroup cols={2}>
|
|
28
|
-
<Card title="Parallel Execution" icon="
|
|
243
|
+
<Card title="Parallel Execution" icon="layer-group">
|
|
29
244
|
Run multiple tests simultaneously for maximum throughput:
|
|
30
245
|
|
|
31
246
|
```bash
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: "
|
|
2
|
+
title: "Flake Prevention"
|
|
3
3
|
description: "Anti-flake technology that eliminates test instability"
|
|
4
4
|
icon: "shield-check"
|
|
5
5
|
---
|
|
@@ -51,7 +51,6 @@ TestDriver's redraw detection system automatically waits for the UI to stabilize
|
|
|
51
51
|
<Card title="What Redraw Detects" icon="eye">
|
|
52
52
|
The system monitors multiple stability signals:
|
|
53
53
|
|
|
54
|
-
- **DOM Mutations** - Element additions, removals, or attribute changes
|
|
55
54
|
- **Layout Reflows** - Position or size changes
|
|
56
55
|
- **CSS Animations** - Running animations and transitions
|
|
57
56
|
- **Network Activity** - Pending XHR/fetch requests
|
|
@@ -96,19 +95,23 @@ Fine-tune stability detection for your application's needs:
|
|
|
96
95
|
// Default stability settings (recommended)
|
|
97
96
|
await testdriver.find('button').click();
|
|
98
97
|
|
|
99
|
-
//
|
|
100
|
-
await testdriver.find('
|
|
101
|
-
|
|
98
|
+
// Disable redraw detection for specific actions (advanced)
|
|
99
|
+
await testdriver.find('element', {
|
|
100
|
+
redraw: {
|
|
101
|
+
enabled: false // Skip stability detection
|
|
102
|
+
}
|
|
102
103
|
}).click();
|
|
103
104
|
|
|
104
|
-
//
|
|
105
|
-
await testdriver.find('element', {
|
|
106
|
-
|
|
105
|
+
// Disable only screen monitoring (keep network monitoring)
|
|
106
|
+
await testdriver.find('animated element', {
|
|
107
|
+
redraw: {
|
|
108
|
+
screenRedraw: false // Skip screen stability, still wait for network
|
|
109
|
+
}
|
|
107
110
|
}).click();
|
|
108
111
|
```
|
|
109
112
|
|
|
110
113
|
<Tip>
|
|
111
|
-
The default
|
|
114
|
+
The default redraw settings work for 95% of applications. Only adjust if you have unusually subtle animations or need to bypass stability checks.
|
|
112
115
|
</Tip>
|
|
113
116
|
|
|
114
117
|
## Network Monitoring
|
|
@@ -150,13 +153,21 @@ TestDriver waits for network activity to complete before asserting or locating e
|
|
|
150
153
|
Common loading indicators are recognized and waited for automatically.
|
|
151
154
|
</Accordion>
|
|
152
155
|
|
|
153
|
-
<Accordion title="Custom
|
|
156
|
+
<Accordion title="Custom Polling for Slow Elements">
|
|
154
157
|
```javascript
|
|
155
|
-
// For
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
158
|
+
// For elements that take time to appear, use polling
|
|
159
|
+
async function waitForElement(description, timeoutMs = 60000) {
|
|
160
|
+
const startTime = Date.now();
|
|
161
|
+
while (Date.now() - startTime < timeoutMs) {
|
|
162
|
+
const element = await testdriver.find(description);
|
|
163
|
+
if (element.found()) return element;
|
|
164
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
165
|
+
}
|
|
166
|
+
throw new Error(`Element "${description}" not found`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const button = await waitForElement('submit button');
|
|
170
|
+
await button.click();
|
|
160
171
|
```
|
|
161
172
|
</Accordion>
|
|
162
173
|
</AccordionGroup>
|
|
@@ -191,135 +202,92 @@ await testdriver.find('animated element').click();
|
|
|
191
202
|
```
|
|
192
203
|
</Card>
|
|
193
204
|
|
|
194
|
-
##
|
|
205
|
+
## Debugging Element Location
|
|
195
206
|
|
|
196
|
-
|
|
207
|
+
When elements aren't found, TestDriver provides detailed debugging information to help you understand why:
|
|
197
208
|
|
|
198
209
|
<Steps>
|
|
199
|
-
<Step title="
|
|
200
|
-
|
|
210
|
+
<Step title="Check Element Existence">
|
|
211
|
+
Verify if the element was found before interacting with it.
|
|
201
212
|
|
|
202
213
|
```javascript
|
|
203
214
|
const element = await testdriver.find('button');
|
|
204
|
-
|
|
215
|
+
if (element.found()) {
|
|
216
|
+
await element.click();
|
|
217
|
+
} else {
|
|
218
|
+
console.log('Button not found on page');
|
|
219
|
+
}
|
|
205
220
|
```
|
|
206
221
|
</Step>
|
|
207
222
|
|
|
208
|
-
<Step title="
|
|
209
|
-
|
|
223
|
+
<Step title="Review Debug Information">
|
|
224
|
+
Access detailed debugging data from the element response.
|
|
210
225
|
|
|
211
226
|
```javascript
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
227
|
+
const element = await testdriver.find('submit button');
|
|
228
|
+
|
|
229
|
+
console.log('Found:', element.found());
|
|
230
|
+
console.log('Similarity:', element.aiResponse?.similarity);
|
|
231
|
+
console.log('Cache hit:', element.aiResponse?.cacheHit);
|
|
232
|
+
console.log('Screenshot:', element.screenshotPath);
|
|
216
233
|
```
|
|
217
234
|
</Step>
|
|
218
235
|
|
|
219
|
-
<Step title="
|
|
220
|
-
|
|
236
|
+
<Step title="Analyze Visual Differences">
|
|
237
|
+
Compare what TestDriver saw vs. what you expected.
|
|
221
238
|
|
|
222
239
|
```javascript
|
|
223
|
-
await testdriver.find('button'
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
240
|
+
const element = await testdriver.find('login button');
|
|
241
|
+
|
|
242
|
+
if (!element.found()) {
|
|
243
|
+
// Screenshot shows what TestDriver analyzed
|
|
244
|
+
console.log('Check screenshot:', element.screenshotPath);
|
|
245
|
+
|
|
246
|
+
// Similarity score indicates how close it got
|
|
247
|
+
console.log('Best match similarity:', element.aiResponse?.similarity);
|
|
248
|
+
|
|
249
|
+
// Review the description for clarity
|
|
250
|
+
console.log('Search description:', 'login button');
|
|
251
|
+
}
|
|
227
252
|
```
|
|
228
253
|
</Step>
|
|
229
254
|
|
|
230
|
-
<Step title="
|
|
231
|
-
|
|
255
|
+
<Step title="Refine Your Description">
|
|
256
|
+
Use debug insights to improve element descriptions.
|
|
232
257
|
|
|
233
258
|
```javascript
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
259
|
+
// Too vague
|
|
260
|
+
const btn = await testdriver.find('button');
|
|
261
|
+
|
|
262
|
+
// Better - more specific
|
|
263
|
+
const loginBtn = await testdriver.find('blue login button in header');
|
|
264
|
+
|
|
265
|
+
// Best - unique identifying features
|
|
266
|
+
const submitBtn = await testdriver.find('submit button with arrow icon');
|
|
241
267
|
```
|
|
242
268
|
</Step>
|
|
243
269
|
</Steps>
|
|
244
270
|
|
|
245
|
-
##
|
|
246
|
-
|
|
247
|
-
Customize retry behavior for different scenarios:
|
|
271
|
+
## Polling for Dynamic Elements
|
|
248
272
|
|
|
249
|
-
|
|
250
|
-
<Tab title="Fast Elements">
|
|
251
|
-
```javascript
|
|
252
|
-
// For elements that appear quickly
|
|
253
|
-
await testdriver.find('header logo', {
|
|
254
|
-
timeout: 5000 // 5 seconds (faster failure)
|
|
255
|
-
});
|
|
256
|
-
```
|
|
257
|
-
</Tab>
|
|
258
|
-
|
|
259
|
-
<Tab title="Slow Elements">
|
|
260
|
-
```javascript
|
|
261
|
-
// For elements that take time to load
|
|
262
|
-
await testdriver.find('search results', {
|
|
263
|
-
timeout: 30000, // 30 seconds
|
|
264
|
-
retryInterval: 1000 // Check every second
|
|
265
|
-
});
|
|
266
|
-
```
|
|
267
|
-
</Tab>
|
|
273
|
+
For elements that may take time to appear, implement polling:
|
|
268
274
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
## Real-World Stability Improvements
|
|
282
|
-
|
|
283
|
-
Compare flake rates before and after TestDriver:
|
|
284
|
-
|
|
285
|
-
<CardGroup cols={2}>
|
|
286
|
-
<Card title="Before TestDriver" icon="triangle-exclamation">
|
|
287
|
-
```
|
|
288
|
-
Test Suite: E2E Tests
|
|
289
|
-
Total: 100 tests
|
|
290
|
-
Passed: 87
|
|
291
|
-
Failed: 13
|
|
292
|
-
|
|
293
|
-
Flaky tests:
|
|
294
|
-
- login_test (timing)
|
|
295
|
-
- dropdown_test (animation)
|
|
296
|
-
- modal_test (async content)
|
|
297
|
-
- search_test (debounce)
|
|
298
|
-
|
|
299
|
-
Flake rate: 13%
|
|
300
|
-
CI reruns needed: 3-5x
|
|
301
|
-
```
|
|
302
|
-
</Card>
|
|
303
|
-
|
|
304
|
-
<Card title="After TestDriver" icon="circle-check">
|
|
305
|
-
```
|
|
306
|
-
Test Suite: E2E Tests
|
|
307
|
-
Total: 100 tests
|
|
308
|
-
Passed: 100
|
|
309
|
-
Failed: 0
|
|
310
|
-
|
|
311
|
-
Flaky tests: None
|
|
312
|
-
|
|
313
|
-
Flake rate: 0%
|
|
314
|
-
CI reruns needed: 0
|
|
315
|
-
Stable builds: 100%
|
|
316
|
-
```
|
|
317
|
-
</Card>
|
|
318
|
-
</CardGroup>
|
|
275
|
+
```javascript
|
|
276
|
+
// Standard polling for normal elements
|
|
277
|
+
const timeoutMs = 30000; // 30 seconds
|
|
278
|
+
const startTime = Date.now();
|
|
279
|
+
let results;
|
|
280
|
+
|
|
281
|
+
while (Date.now() - startTime < timeoutMs) {
|
|
282
|
+
results = await testdriver.find('search results');
|
|
283
|
+
if (results.found()) break;
|
|
284
|
+
await new Promise(r => setTimeout(r, 1000)); // Check every second
|
|
285
|
+
}
|
|
319
286
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
287
|
+
if (results.found()) {
|
|
288
|
+
await results.click();
|
|
289
|
+
}
|
|
290
|
+
```
|
|
323
291
|
|
|
324
292
|
## Advanced: Redraw Configuration
|
|
325
293
|
|
|
@@ -330,13 +298,9 @@ The redraw system is configurable through [redraw.js](/agent/lib/redraw.js):
|
|
|
330
298
|
const testdriver = new TestDriver({
|
|
331
299
|
apiKey: process.env.TD_API_KEY,
|
|
332
300
|
redraw: {
|
|
333
|
-
enabled: true,
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
checkInterval: 50, // How often to check for stability
|
|
337
|
-
networkIdle: true, // Wait for network idle
|
|
338
|
-
domIdle: true, // Wait for DOM mutations to stop
|
|
339
|
-
animationIdle: true, // Wait for CSS animations
|
|
301
|
+
enabled: true, // Master switch for redraw detection
|
|
302
|
+
screenRedraw: true, // Enable screen stability detection
|
|
303
|
+
networkMonitor: true, // Enable network activity monitoring
|
|
340
304
|
}
|
|
341
305
|
});
|
|
342
306
|
```
|
|
@@ -347,14 +311,14 @@ const testdriver = new TestDriver({
|
|
|
347
311
|
// Override redraw settings for specific actions
|
|
348
312
|
await testdriver.find('fast element', {
|
|
349
313
|
redraw: {
|
|
350
|
-
enabled: false // Skip redraw detection
|
|
314
|
+
enabled: false // Skip redraw detection entirely
|
|
351
315
|
}
|
|
352
316
|
}).click();
|
|
353
317
|
|
|
354
|
-
await testdriver.find('
|
|
318
|
+
await testdriver.find('element with animation', {
|
|
355
319
|
redraw: {
|
|
356
|
-
|
|
357
|
-
|
|
320
|
+
screenRedraw: false, // Skip screen stability check
|
|
321
|
+
networkMonitor: true // Still wait for network to settle
|
|
358
322
|
}
|
|
359
323
|
}).click();
|
|
360
324
|
```
|
|
@@ -364,22 +328,17 @@ await testdriver.find('slow element', {
|
|
|
364
328
|
If you encounter stability issues, TestDriver provides detailed diagnostics:
|
|
365
329
|
|
|
366
330
|
```javascript
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
// animations: ['fade-in', 'slide-up'],
|
|
379
|
-
// waitedMs: 5000,
|
|
380
|
-
// stableMs: 150
|
|
381
|
-
// }
|
|
382
|
-
}
|
|
331
|
+
// Enable verbose logging to see redraw detection in action
|
|
332
|
+
const testdriver = new TestDriver({
|
|
333
|
+
apiKey: process.env.TD_API_KEY,
|
|
334
|
+
verbosity: 2 // Show detailed redraw logs
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
await testdriver.find('element').click();
|
|
338
|
+
// Console output will show:
|
|
339
|
+
// [redraw] Screen diff: 0.15%
|
|
340
|
+
// [redraw] Network activity: 1250 b/s
|
|
341
|
+
// [redraw] Waiting for stability...
|
|
383
342
|
```
|
|
384
343
|
|
|
385
344
|
## Common Stability Patterns
|
|
@@ -445,13 +404,13 @@ try {
|
|
|
445
404
|
## Best Practices
|
|
446
405
|
|
|
447
406
|
<Card title="Let TestDriver Handle Timing" icon="clock">
|
|
448
|
-
|
|
407
|
+
Don't:
|
|
449
408
|
```javascript
|
|
450
409
|
await testdriver.find('button').click();
|
|
451
410
|
await new Promise(resolve => setTimeout(resolve, 1000)); // ❌
|
|
452
411
|
```
|
|
453
412
|
|
|
454
|
-
|
|
413
|
+
Do:
|
|
455
414
|
```javascript
|
|
456
415
|
await testdriver.find('button').click();
|
|
457
416
|
await testdriver.find('next element').click(); // ✅
|
|
@@ -461,14 +420,14 @@ try {
|
|
|
461
420
|
</Card>
|
|
462
421
|
|
|
463
422
|
<Card title="Use Natural Assertions" icon="check">
|
|
464
|
-
|
|
423
|
+
Don't:
|
|
465
424
|
```javascript
|
|
466
425
|
await testdriver.find('button').click();
|
|
467
426
|
const element = await testdriver.find('result');
|
|
468
427
|
expect(element).toBeTruthy(); // ❌ Redundant
|
|
469
428
|
```
|
|
470
429
|
|
|
471
|
-
|
|
430
|
+
Do:
|
|
472
431
|
```javascript
|
|
473
432
|
await testdriver.find('button').click();
|
|
474
433
|
await testdriver.assert('result is visible'); // ✅ Natural
|