testdriverai 7.1.4 → 7.2.0

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 (70) hide show
  1. package/.github/workflows/acceptance.yaml +81 -0
  2. package/.github/workflows/publish.yaml +44 -0
  3. package/agent/index.js +18 -19
  4. package/agent/lib/commands.js +321 -121
  5. package/agent/lib/redraw.js +99 -39
  6. package/agent/lib/sandbox.js +98 -6
  7. package/agent/lib/sdk.js +25 -0
  8. package/agent/lib/system.js +2 -1
  9. package/agent/lib/validation.js +6 -6
  10. package/docs/docs.json +211 -101
  11. package/docs/snippets/tests/type-repeated-replay.mdx +1 -1
  12. package/docs/v7/_drafts/caching-selectors.mdx +24 -0
  13. package/docs/v7/api/act.mdx +1 -1
  14. package/docs/v7/api/assert.mdx +1 -1
  15. package/docs/v7/api/assertions.mdx +7 -7
  16. package/docs/v7/api/elements.mdx +78 -0
  17. package/docs/v7/api/find.mdx +38 -0
  18. package/docs/v7/api/focusApplication.mdx +2 -2
  19. package/docs/v7/api/hover.mdx +2 -2
  20. package/docs/v7/features/ai-native.mdx +57 -71
  21. package/docs/v7/features/application-logs.mdx +353 -0
  22. package/docs/v7/features/browser-logs.mdx +414 -0
  23. package/docs/v7/features/cache-management.mdx +402 -0
  24. package/docs/v7/features/continuous-testing.mdx +346 -0
  25. package/docs/v7/features/coverage.mdx +508 -0
  26. package/docs/v7/features/data-driven-testing.mdx +441 -0
  27. package/docs/v7/features/easy-to-write.mdx +2 -73
  28. package/docs/v7/features/enterprise.mdx +155 -39
  29. package/docs/v7/features/fast.mdx +63 -81
  30. package/docs/v7/features/managed-sandboxes.mdx +384 -0
  31. package/docs/v7/features/network-monitoring.mdx +568 -0
  32. package/docs/v7/features/observable.mdx +3 -22
  33. package/docs/v7/features/parallel-execution.mdx +381 -0
  34. package/docs/v7/features/powerful.mdx +1 -1
  35. package/docs/v7/features/reports.mdx +414 -0
  36. package/docs/v7/features/sandbox-customization.mdx +229 -0
  37. package/docs/v7/features/scalable.mdx +217 -2
  38. package/docs/v7/features/stable.mdx +106 -147
  39. package/docs/v7/features/system-performance.mdx +616 -0
  40. package/docs/v7/features/test-analytics.mdx +373 -0
  41. package/docs/v7/features/test-cases.mdx +393 -0
  42. package/docs/v7/features/test-replays.mdx +408 -0
  43. package/docs/v7/features/test-reports.mdx +308 -0
  44. package/docs/v7/getting-started/{running-and-debugging.mdx → debugging-tests.mdx} +12 -142
  45. package/docs/v7/getting-started/quickstart.mdx +22 -305
  46. package/docs/v7/getting-started/running-tests.mdx +173 -0
  47. package/docs/v7/overview/what-is-testdriver.mdx +2 -14
  48. package/docs/v7/presets/chrome-extension.mdx +147 -122
  49. package/interfaces/cli/commands/init.js +3 -3
  50. package/interfaces/cli/lib/base.js +3 -2
  51. package/interfaces/logger.js +0 -2
  52. package/interfaces/shared-test-state.mjs +0 -5
  53. package/interfaces/vitest-plugin.mjs +69 -42
  54. package/lib/core/Dashcam.js +65 -66
  55. package/lib/vitest/hooks.mjs +42 -50
  56. package/package.json +1 -1
  57. package/sdk-log-formatter.js +350 -175
  58. package/sdk.js +431 -116
  59. package/setup/aws/cloudformation.yaml +2 -2
  60. package/setup/aws/self-hosted.yml +1 -1
  61. package/test/testdriver/chrome-extension.test.mjs +55 -72
  62. package/test/testdriver/element-not-found.test.mjs +2 -1
  63. package/test/testdriver/hover-image.test.mjs +1 -1
  64. package/test/testdriver/scroll-until-text.test.mjs +10 -6
  65. package/test/testdriver/setup/lifecycleHelpers.mjs +19 -24
  66. package/test/testdriver/setup/testHelpers.mjs +18 -23
  67. package/vitest.config.mjs +3 -3
  68. package/.github/workflows/linux-tests.yml +0 -28
  69. package/docs/v7/getting-started/generating-tests.mdx +0 -525
  70. package/test/testdriver/auto-cache-key-demo.test.mjs +0 -56
@@ -1,11 +1,226 @@
1
1
  ---
2
- title: "Scalable"
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="arrows-split-up-and-left">
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: "Stable & Reliable"
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
- // Custom stability delay
100
- await testdriver.find('animated button', {
101
- stabilityDelay: 500 // Wait 500ms of stability before acting
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
- // Bypass stability (advanced)
105
- await testdriver.find('element', {
106
- stabilityDelay: 0 // Act immediately
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 200ms stability delay works for 95% of applications. Only adjust if you have unusually long animations or need instant actions.
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 Network Conditions">
156
+ <Accordion title="Custom Polling for Slow Elements">
154
157
  ```javascript
155
- // For slow networks or long-running requests
156
- await testdriver.find('submit button', {
157
- timeout: 60000, // Wait up to 60 seconds
158
- networkTimeout: 30000 // Allow 30s for network requests
159
- }).click();
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
- ## Retry Logic
205
+ ## Debugging Element Location
195
206
 
196
- TestDriver includes intelligent retry logic that adapts to your application:
207
+ When elements aren't found, TestDriver provides detailed debugging information to help you understand why:
197
208
 
198
209
  <Steps>
199
- <Step title="Initial Attempt">
200
- Try to locate the element immediately.
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
- // Attempts location immediately
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="Wait for Stability">
209
- If not found, wait for UI stability and retry.
223
+ <Step title="Review Debug Information">
224
+ Access detailed debugging data from the element response.
210
225
 
211
226
  ```javascript
212
- // Waits for:
213
- // - DOM mutations to stop
214
- // - Network requests to complete
215
- // - Animations to finish
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="Retry with Interval">
220
- Continue retrying at regular intervals until timeout.
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
- timeout: 30000, // Try for up to 30 seconds
225
- retryInterval: 500 // Check every 500ms
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="Provide Debug Info">
231
- If still not found, provide detailed debugging information.
255
+ <Step title="Refine Your Description">
256
+ Use debug insights to improve element descriptions.
232
257
 
233
258
  ```javascript
234
- try {
235
- await testdriver.find('missing button');
236
- } catch (error) {
237
- console.log('Debug screenshot:', error.debugScreenshot);
238
- console.log('Similarity score:', error.similarity);
239
- console.log('Cache info:', error.cacheInfo);
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
- ## Configurable Timeouts
246
-
247
- Customize retry behavior for different scenarios:
271
+ ## Polling for Dynamic Elements
248
272
 
249
- <Tabs>
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
- <Tab title="Critical Elements">
270
- ```javascript
271
- // For absolutely critical elements
272
- await testdriver.find('payment confirmation', {
273
- timeout: 60000, // 1 minute
274
- retryInterval: 2000, // Check every 2 seconds
275
- stabilityDelay: 1000 // Wait 1 second of stability
276
- });
277
- ```
278
- </Tab>
279
- </Tabs>
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
- <Check>
321
- **Result: 100% stability** - Eliminated all flaky tests without changing test code.
322
- </Check>
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
- stabilityDelay: 200, // Default stability wait (ms)
335
- maxStabilityWait: 5000, // Maximum time to wait for stability
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('slow element', {
318
+ await testdriver.find('element with animation', {
355
319
  redraw: {
356
- stabilityDelay: 1000, // Wait longer for stability
357
- networkIdle: true
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
- try {
368
- await testdriver.find('unstable element', {
369
- debug: true // Enable debug logging
370
- });
371
- } catch (error) {
372
- console.log('Stability info:', error.stabilityInfo);
373
- // Output:
374
- // {
375
- // domMutations: 15,
376
- // layoutReflows: 3,
377
- // networkRequests: 2,
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
- **Don't:**
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
- **Do:**
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
- **Don't:**
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
- **Do:**
430
+ Do:
472
431
  ```javascript
473
432
  await testdriver.find('button').click();
474
433
  await testdriver.assert('result is visible'); // ✅ Natural