testdriverai 7.0.0 → 7.1.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 (112) hide show
  1. package/AGENTS.md +550 -0
  2. package/CODEOWNERS +0 -1
  3. package/README.md +126 -0
  4. package/agent/index.js +43 -18
  5. package/agent/lib/commands.js +794 -135
  6. package/agent/lib/redraw.js +124 -39
  7. package/agent/lib/sandbox.js +10 -1
  8. package/agent/lib/sdk.js +21 -0
  9. package/docs/MIGRATION.md +425 -0
  10. package/docs/PRESETS.md +210 -0
  11. package/docs/docs.json +91 -37
  12. package/docs/guide/best-practices-polling.mdx +154 -0
  13. package/docs/v7/api/dashcam.mdx +497 -0
  14. package/docs/v7/api/doubleClick.mdx +102 -0
  15. package/docs/v7/api/mouseDown.mdx +161 -0
  16. package/docs/v7/api/mouseUp.mdx +164 -0
  17. package/docs/v7/api/rightClick.mdx +123 -0
  18. package/docs/v7/getting-started/configuration.mdx +380 -0
  19. package/docs/v7/getting-started/quickstart.mdx +273 -140
  20. package/docs/v7/guides/best-practices.mdx +486 -0
  21. package/docs/v7/guides/caching-ai.mdx +215 -0
  22. package/docs/v7/guides/caching-selectors.mdx +292 -0
  23. package/docs/v7/guides/caching.mdx +366 -0
  24. package/docs/v7/guides/ci-cd/azure.mdx +587 -0
  25. package/docs/v7/guides/ci-cd/circleci.mdx +523 -0
  26. package/docs/v7/guides/ci-cd/github-actions.mdx +457 -0
  27. package/docs/v7/guides/ci-cd/gitlab.mdx +498 -0
  28. package/docs/v7/guides/ci-cd/jenkins.mdx +664 -0
  29. package/docs/v7/guides/ci-cd/travis.mdx +438 -0
  30. package/docs/v7/guides/debugging.mdx +349 -0
  31. package/docs/v7/guides/faq.mdx +393 -0
  32. package/docs/v7/guides/performance.mdx +517 -0
  33. package/docs/v7/guides/troubleshooting.mdx +526 -0
  34. package/docs/v7/guides/vitest-plugin.mdx +477 -0
  35. package/docs/v7/guides/vitest.mdx +535 -0
  36. package/docs/v7/platforms/linux.mdx +308 -0
  37. package/docs/v7/platforms/macos.mdx +433 -0
  38. package/docs/v7/platforms/windows.mdx +430 -0
  39. package/docs/v7/presets/chrome-extension.mdx +223 -0
  40. package/docs/v7/presets/chrome.mdx +287 -0
  41. package/docs/v7/presets/electron.mdx +435 -0
  42. package/docs/v7/presets/vscode.mdx +398 -0
  43. package/docs/v7/presets/webapp.mdx +396 -0
  44. package/docs/v7/progressive-apis/CORE.md +459 -0
  45. package/docs/v7/progressive-apis/HOOKS.md +360 -0
  46. package/docs/v7/progressive-apis/PROGRESSIVE_DISCLOSURE.md +230 -0
  47. package/docs/v7/progressive-apis/PROVISION.md +266 -0
  48. package/interfaces/vitest-plugin.mjs +186 -100
  49. package/package.json +12 -1
  50. package/sdk.d.ts +335 -42
  51. package/sdk.js +756 -95
  52. package/src/core/Dashcam.js +469 -0
  53. package/src/core/index.d.ts +150 -0
  54. package/src/core/index.js +12 -0
  55. package/src/presets/index.mjs +331 -0
  56. package/src/vitest/extended.mjs +108 -0
  57. package/src/vitest/hooks.d.ts +119 -0
  58. package/src/vitest/hooks.mjs +298 -0
  59. package/src/vitest/index.mjs +64 -0
  60. package/src/vitest/lifecycle.mjs +277 -0
  61. package/src/vitest/utils.mjs +150 -0
  62. package/test/dashcam.test.js +137 -0
  63. package/testdriver/acceptance-sdk/assert.test.mjs +13 -31
  64. package/testdriver/acceptance-sdk/auto-cache-key-demo.test.mjs +56 -0
  65. package/testdriver/acceptance-sdk/chrome-extension.test.mjs +89 -0
  66. package/testdriver/acceptance-sdk/drag-and-drop.test.mjs +7 -19
  67. package/testdriver/acceptance-sdk/element-not-found.test.mjs +6 -19
  68. package/testdriver/acceptance-sdk/exec-js.test.mjs +6 -18
  69. package/testdriver/acceptance-sdk/exec-output.test.mjs +8 -20
  70. package/testdriver/acceptance-sdk/exec-pwsh.test.mjs +13 -25
  71. package/testdriver/acceptance-sdk/focus-window.test.mjs +8 -20
  72. package/testdriver/acceptance-sdk/formatted-logging.test.mjs +5 -20
  73. package/testdriver/acceptance-sdk/hooks-example.test.mjs +38 -0
  74. package/testdriver/acceptance-sdk/hover-image.test.mjs +10 -19
  75. package/testdriver/acceptance-sdk/hover-text-with-description.test.mjs +7 -19
  76. package/testdriver/acceptance-sdk/hover-text.test.mjs +5 -19
  77. package/testdriver/acceptance-sdk/match-image.test.mjs +7 -19
  78. package/testdriver/acceptance-sdk/presets-example.test.mjs +87 -0
  79. package/testdriver/acceptance-sdk/press-keys.test.mjs +5 -19
  80. package/testdriver/acceptance-sdk/prompt.test.mjs +6 -18
  81. package/testdriver/acceptance-sdk/scroll-keyboard.test.mjs +6 -20
  82. package/testdriver/acceptance-sdk/scroll-until-image.test.mjs +6 -18
  83. package/testdriver/acceptance-sdk/scroll-until-text.test.mjs +9 -23
  84. package/testdriver/acceptance-sdk/scroll.test.mjs +12 -21
  85. package/testdriver/acceptance-sdk/setup/testHelpers.mjs +124 -352
  86. package/testdriver/acceptance-sdk/sully-ai.test.mjs +234 -0
  87. package/testdriver/acceptance-sdk/test-console-logs.test.mjs +42 -0
  88. package/testdriver/acceptance-sdk/type.test.mjs +19 -58
  89. package/vitest.config.mjs +1 -0
  90. package/.vscode/mcp.json +0 -9
  91. package/MIGRATION.md +0 -389
  92. package/PLUGIN_MIGRATION.md +0 -222
  93. package/PROMPT_CACHE.md +0 -200
  94. package/SDK_LOGGING.md +0 -222
  95. package/SDK_MIGRATION.md +0 -474
  96. package/SDK_README.md +0 -1122
  97. package/debug-screenshot-1763401388589.png +0 -0
  98. package/examples/run-tests-with-recording.sh +0 -70
  99. package/examples/screenshot-example.js +0 -63
  100. package/examples/sdk-awesome-logs-demo.js +0 -177
  101. package/examples/sdk-cache-thresholds.js +0 -96
  102. package/examples/sdk-element-properties.js +0 -155
  103. package/examples/sdk-simple-example.js +0 -65
  104. package/examples/test-recording-example.test.js +0 -166
  105. package/mcp-server/AI_GUIDELINES.md +0 -57
  106. package/test-find-api.js +0 -73
  107. package/test-prompt-cache.js +0 -96
  108. package/test-sandbox-render.js +0 -28
  109. package/test-sdk-methods.js +0 -15
  110. package/test-sdk-refactor.js +0 -53
  111. package/test-stack-trace.mjs +0 -57
  112. package/testdriver/acceptance-sdk/setup/lifecycleHelpers.mjs +0 -239
@@ -0,0 +1,486 @@
1
+ ---
2
+ title: "Best Practices"
3
+ description: "Patterns and practices for reliable tests"
4
+ icon: "star"
5
+ ---
6
+
7
+ ## Test Structure
8
+
9
+ ### Use beforeAll/afterAll
10
+
11
+ Create one sandbox per test suite:
12
+
13
+ ```javascript
14
+ describe('Login Flow', () => {
15
+ let testdriver;
16
+
17
+ beforeAll(async () => {
18
+ const client = await TestDriver.create({
19
+ apiKey: process.env.TD_API_KEY,
20
+ os: 'linux'
21
+ });
22
+ await client.auth();
23
+ await client.connect({ newSandbox: true });
24
+ testdriver = client;
25
+ });
26
+
27
+ afterAll(async () => {
28
+ await testdriver?.disconnect();
29
+ });
30
+
31
+ it('logs in successfully', async () => {
32
+ // Test code
33
+ });
34
+
35
+ it('shows error on invalid credentials', async () => {
36
+ // Test code
37
+ });
38
+ });
39
+ ```
40
+
41
+ ### Use Presets
42
+
43
+ Presets handle lifecycle automatically:
44
+
45
+ ```javascript
46
+ // ✅ Good - automatic lifecycle
47
+ test('my test', async (context) => {
48
+ const { testdriver } = await chrome(context, { url });
49
+ // Test code
50
+ });
51
+
52
+ // ❌ Avoid - manual lifecycle
53
+ test('my test', async () => {
54
+ const client = new TestDriver(...);
55
+ await client.auth();
56
+ await client.connect();
57
+ // Test code
58
+ await client.disconnect();
59
+ });
60
+ ```
61
+
62
+ ## Element Finding
63
+
64
+ ### Be Specific
65
+
66
+ ```javascript
67
+ // ❌ Too vague
68
+ await testdriver.find('button');
69
+
70
+ // ✅ Specific
71
+ await testdriver.find('blue submit button at bottom of login form');
72
+
73
+ // ✅ Include visual context
74
+ await testdriver.find('red delete button next to user John Doe');
75
+
76
+ // ✅ Use nearby text
77
+ await testdriver.find('button below "Confirm your email" text');
78
+ ```
79
+
80
+ ### Always Check found()
81
+
82
+ ```javascript
83
+ // ❌ Assumes element exists
84
+ const button = await testdriver.find('submit button');
85
+ await button.click();
86
+
87
+ // ✅ Verifies element exists
88
+ const button = await testdriver.find('submit button');
89
+ if (!button.found()) {
90
+ throw new Error('Submit button not found');
91
+ }
92
+ await button.click();
93
+
94
+ // ✅ Or use assertion
95
+ const button = await testdriver.find('submit button');
96
+ expect(button.found()).toBe(true);
97
+ await button.click();
98
+ ```
99
+
100
+ ### Poll for Dynamic Elements
101
+
102
+ ```javascript
103
+ // ✅ Wait for element to appear
104
+ async function waitFor(testdriver, description, timeout = 30000) {
105
+ const start = Date.now();
106
+ while (Date.now() - start < timeout) {
107
+ const element = await testdriver.find(description);
108
+ if (element.found()) return element;
109
+ await new Promise(r => setTimeout(r, 1000));
110
+ }
111
+ throw new Error(`Element not found: ${description}`);
112
+ }
113
+
114
+ const button = await waitFor(testdriver, 'submit button');
115
+ await button.click();
116
+ ```
117
+
118
+ ## Actions
119
+
120
+ ### Use Descriptive Prompts
121
+
122
+ ```javascript
123
+ // ❌ Generic
124
+ await testdriver.ai('login');
125
+
126
+ // ✅ Specific steps
127
+ await testdriver.ai('click the username field and type user@example.com');
128
+ await testdriver.ai('click the password field and type password123');
129
+ await testdriver.ai('click the blue submit button');
130
+ ```
131
+
132
+ ### Chain Actions
133
+
134
+ ```javascript
135
+ // ✅ Find once, use multiple times
136
+ const input = await testdriver.find('username field');
137
+ await input.click();
138
+ await testdriver.type('user@example.com');
139
+ await testdriver.pressKeys(['Tab']);
140
+
141
+ // ✅ Or chain directly
142
+ await testdriver.find('username field').then(el => el.click());
143
+ ```
144
+
145
+ ### Use Keyboard Shortcuts
146
+
147
+ ```javascript
148
+ // ✅ Faster than UI navigation
149
+ await testdriver.pressKeys(['ctrl', 'a']); // Select all
150
+ await testdriver.pressKeys(['ctrl', 'c']); // Copy
151
+ await testdriver.pressKeys(['ctrl', 'v']); // Paste
152
+ await testdriver.pressKeys(['escape']); // Close dialog
153
+ ```
154
+
155
+ ## Assertions
156
+
157
+ ### Verify Key States
158
+
159
+ ```javascript
160
+ // ✅ Assert at checkpoints
161
+ await testdriver.assert('login page is visible');
162
+ await testdriver.find('username').then(el => el.click());
163
+ await testdriver.type('user@example.com');
164
+ await testdriver.find('password').then(el => el.click());
165
+ await testdriver.type('password123');
166
+ await testdriver.find('submit').then(el => el.click());
167
+ await testdriver.assert('dashboard is visible');
168
+ await testdriver.assert('welcome message shows username');
169
+ ```
170
+
171
+ ### Use Vitest Assertions
172
+
173
+ ```javascript
174
+ // ✅ Combine TestDriver and Vitest
175
+ const element = await testdriver.find('error message');
176
+ expect(element.found()).toBe(true);
177
+ expect(element.text).toContain('Invalid credentials');
178
+
179
+ const result = await testdriver.assert('dashboard loaded');
180
+ expect(result).toBe(true);
181
+ ```
182
+
183
+ ## Performance
184
+
185
+ ### Reuse Sandboxes
186
+
187
+ ```javascript
188
+ // ✅ One sandbox per suite
189
+ beforeAll(async () => {
190
+ testdriver = await TestDriver.create(...);
191
+ });
192
+
193
+ afterAll(async () => {
194
+ await testdriver.disconnect();
195
+ });
196
+
197
+ // ❌ One sandbox per test (slow!)
198
+ beforeEach(async () => {
199
+ testdriver = await TestDriver.create(...);
200
+ });
201
+ ```
202
+
203
+ ### Use Caching
204
+
205
+ ```javascript
206
+ // ✅ Enable caching for repeated elements
207
+ await testdriver.find('submit button'); // AI call
208
+ await testdriver.find('submit button'); // Cache hit (fast!)
209
+
210
+ // ✅ Use consistent prompts
211
+ const buttonDesc = 'blue submit button at bottom';
212
+ await testdriver.find(buttonDesc); // Cache hit on reuse
213
+ ```
214
+
215
+ ### Parallel Execution
216
+
217
+ ```javascript
218
+ // vitest.config.mjs
219
+ export default defineConfig({
220
+ test: {
221
+ pool: 'forks',
222
+ maxConcurrency: 5, // Run 5 tests in parallel
223
+ fileParallelism: true
224
+ }
225
+ });
226
+ ```
227
+
228
+ ## Error Handling
229
+
230
+ ### Graceful Failures
231
+
232
+ ```javascript
233
+ test('handles missing elements', async (context) => {
234
+ const { testdriver } = await chrome(context, { url });
235
+
236
+ const optionalButton = await testdriver.find('optional newsletter button');
237
+
238
+ if (optionalButton.found()) {
239
+ await optionalButton.click();
240
+ } else {
241
+ console.log('Newsletter button not present, skipping');
242
+ }
243
+
244
+ // Continue with required elements
245
+ const required = await testdriver.find('continue button');
246
+ expect(required.found()).toBe(true);
247
+ await required.click();
248
+ });
249
+ ```
250
+
251
+ ### Try-Catch for exec()
252
+
253
+ ```javascript
254
+ // ✅ Handle command failures
255
+ try {
256
+ await testdriver.exec('sh', 'risky-command', 30000, false);
257
+ } catch (error) {
258
+ console.log('Command failed, using fallback');
259
+ await testdriver.exec('sh', 'fallback-command', 30000, false);
260
+ }
261
+ ```
262
+
263
+ ### Cleanup in Finally
264
+
265
+ ```javascript
266
+ let testdriver;
267
+
268
+ try {
269
+ testdriver = await TestDriver.create(...);
270
+ await testdriver.auth();
271
+ await testdriver.connect();
272
+
273
+ // Test code
274
+
275
+ } catch (error) {
276
+ console.error('Test failed:', error);
277
+ throw error;
278
+ } finally {
279
+ await testdriver?.disconnect();
280
+ }
281
+ ```
282
+
283
+ ## Code Organization
284
+
285
+ ### Extract Common Patterns
286
+
287
+ ```javascript
288
+ // helpers.mjs
289
+ export async function login(testdriver, email, password) {
290
+ await testdriver.find('username field').then(el => el.click());
291
+ await testdriver.type(email);
292
+ await testdriver.find('password field').then(el => el.click());
293
+ await testdriver.type(password);
294
+ await testdriver.find('submit button').then(el => el.click());
295
+ await testdriver.assert('dashboard is visible');
296
+ }
297
+
298
+ // test file
299
+ import { login } from './helpers.mjs';
300
+
301
+ test('user can access settings', async (context) => {
302
+ const { testdriver } = await chrome(context, { url });
303
+ await login(testdriver, 'user@example.com', 'password123');
304
+ await testdriver.find('settings').then(el => el.click());
305
+ });
306
+ ```
307
+
308
+ ### Use Page Objects
309
+
310
+ ```javascript
311
+ // pages/LoginPage.mjs
312
+ export class LoginPage {
313
+ constructor(testdriver) {
314
+ this.testdriver = testdriver;
315
+ }
316
+
317
+ async login(email, password) {
318
+ await this.testdriver.find('username field').then(el => el.click());
319
+ await this.testdriver.type(email);
320
+ await this.testdriver.find('password field').then(el => el.click());
321
+ await this.testdriver.type(password);
322
+ await this.testdriver.find('submit button').then(el => el.click());
323
+ }
324
+
325
+ async assertVisible() {
326
+ const result = await this.testdriver.assert('login page is visible');
327
+ expect(result).toBe(true);
328
+ }
329
+ }
330
+
331
+ // test file
332
+ import { LoginPage } from './pages/LoginPage.mjs';
333
+
334
+ test('login flow', async (context) => {
335
+ const { testdriver } = await chrome(context, { url });
336
+ const loginPage = new LoginPage(testdriver);
337
+
338
+ await loginPage.assertVisible();
339
+ await loginPage.login('user@example.com', 'password123');
340
+ });
341
+ ```
342
+
343
+ ## Environment Management
344
+
345
+ ### Use Environment Variables
346
+
347
+ ```javascript
348
+ // ✅ Good - configurable
349
+ const testdriver = await TestDriver.create({
350
+ apiKey: process.env.TD_API_KEY,
351
+ os: process.env.TD_OS || 'linux',
352
+ resolution: process.env.TD_RESOLUTION || '1920x1080'
353
+ });
354
+
355
+ // ❌ Bad - hardcoded
356
+ const testdriver = await TestDriver.create({
357
+ apiKey: 'td_1234567890',
358
+ os: 'linux'
359
+ });
360
+ ```
361
+
362
+ ### Separate Test Data
363
+
364
+ ```javascript
365
+ // test-data.json
366
+ {
367
+ "validUser": {
368
+ "email": "user@example.com",
369
+ "password": "password123"
370
+ },
371
+ "adminUser": {
372
+ "email": "admin@example.com",
373
+ "password": "admin123"
374
+ }
375
+ }
376
+
377
+ // test file
378
+ import testData from './test-data.json';
379
+
380
+ test('login as user', async (context) => {
381
+ const { testdriver } = await chrome(context, { url });
382
+ await login(testdriver, testData.validUser.email, testData.validUser.password);
383
+ });
384
+ ```
385
+
386
+ ## Documentation
387
+
388
+ ### Comment Complex Logic
389
+
390
+ ```javascript
391
+ // ✅ Explain why, not what
392
+ // Wait for animation to complete before interacting
393
+ await new Promise(r => setTimeout(r, 1000));
394
+
395
+ // Navigate to nested menu item since direct click doesn't work
396
+ await testdriver.find('menu button').then(el => el.hover());
397
+ await testdriver.find('submenu item').then(el => el.click());
398
+ ```
399
+
400
+ ### Name Tests Clearly
401
+
402
+ ```javascript
403
+ // ✅ Clear intent
404
+ test('user sees error when submitting form with invalid email', async () => {});
405
+ test('admin can delete user accounts from settings page', async () => {});
406
+
407
+ // ❌ Vague
408
+ test('form validation', async () => {});
409
+ test('user deletion', async () => {});
410
+ ```
411
+
412
+ ## Anti-Patterns
413
+
414
+ ### Don't Use Hardcoded Delays
415
+
416
+ ```javascript
417
+ // ❌ Brittle - might be too short or too long
418
+ await new Promise(r => setTimeout(r, 5000));
419
+
420
+ // ✅ Poll for condition
421
+ const element = await waitFor(testdriver, 'success message');
422
+ ```
423
+
424
+ ### Don't Ignore Errors
425
+
426
+ ```javascript
427
+ // ❌ Silently fails
428
+ try {
429
+ await testdriver.find('button').then(el => el.click());
430
+ } catch (error) {
431
+ // Ignore
432
+ }
433
+
434
+ // ✅ Handle gracefully
435
+ const button = await testdriver.find('button');
436
+ if (!button.found()) {
437
+ console.warn('Optional button not found, skipping');
438
+ } else {
439
+ await button.click();
440
+ }
441
+ ```
442
+
443
+ ### Don't Test Implementation Details
444
+
445
+ ```javascript
446
+ // ❌ Too specific to implementation
447
+ await testdriver.find('div with class submit-btn-container').then(el => el.click());
448
+
449
+ // ✅ User-facing behavior
450
+ await testdriver.find('submit button').then(el => el.click());
451
+ ```
452
+
453
+ ## Checklist
454
+
455
+ Before committing tests:
456
+
457
+ - ✅ Tests use `beforeAll`/`afterAll` for sandbox lifecycle
458
+ - ✅ All elements checked with `.found()` before use
459
+ - ✅ Descriptive element descriptions with visual context
460
+ - ✅ Key assertions at important checkpoints
461
+ - ✅ No hardcoded delays (use polling instead)
462
+ - ✅ API keys in environment variables
463
+ - ✅ Test names clearly describe intent
464
+ - ✅ Common patterns extracted to helpers
465
+ - ✅ Error handling for optional elements
466
+ - ✅ Cleanup in `finally` blocks
467
+
468
+ ## See Also
469
+
470
+ <CardGroup cols={2}>
471
+ <Card title="Debugging" icon="bug" href="/v7/guides/debugging">
472
+ Debug failing tests
473
+ </Card>
474
+
475
+ <Card title="Configuration" icon="gear" href="/v7/getting-started/configuration">
476
+ Configure TestDriver
477
+ </Card>
478
+
479
+ <Card title="Caching" icon="bolt" href="/v7/guides/caching-ai">
480
+ Optimize with caching
481
+ </Card>
482
+
483
+ <Card title="Examples" icon="code" href="/v7/presets/chrome">
484
+ See working examples
485
+ </Card>
486
+ </CardGroup>
@@ -0,0 +1,215 @@
1
+ ---
2
+ title: "AI Prompt Caching"
3
+ sidebarTitle: "AI Caching"
4
+ description: "How TestDriver caches AI-generated YAML commands for faster tests"
5
+ icon: "bolt"
6
+ ---
7
+
8
+ ## Overview
9
+
10
+ The Prompt Cache stores AI-generated YAML commands locally, so repeated `.ai()` calls with the same prompt skip the AI entirely.
11
+
12
+ This provides:
13
+ - ⚡ **Instant execution** - No AI call needed
14
+ - 💰 **Cost savings** - Reduces API usage
15
+ - 🔌 **Offline testing** - Works without network
16
+ - 🎯 **Deterministic** - Same prompt = same commands
17
+
18
+ ## How It Works
19
+
20
+ <Steps>
21
+ <Step title="First Call">
22
+ ```javascript
23
+ await testdriver.ai('click the submit button');
24
+ ```
25
+
26
+ - Sends prompt + screenshot to AI
27
+ - Receives YAML commands
28
+ - Saves to `.testdriver/.cache/{prompt-hash}.yaml`
29
+ </Step>
30
+
31
+ <Step title="Subsequent Calls">
32
+ ```javascript
33
+ await testdriver.ai('click the submit button');
34
+ ```
35
+
36
+ - Checks cache first
37
+ - Finds matching cached YAML
38
+ - Uses cached commands (no AI call)
39
+ - Shows `(using cached response)` in output
40
+ </Step>
41
+ </Steps>
42
+
43
+ ## Cache Location
44
+
45
+ Cached prompts are stored locally in your project:
46
+
47
+ ```
48
+ .testdriver/
49
+ .cache/
50
+ click-the-submit-button-a1b2c3d4.yaml
51
+ find-login-form-e5f6a7b8.yaml
52
+ verify-dashboard-c9d0e1f2.yaml
53
+ ```
54
+
55
+ Files are named using:
56
+ - Sanitized prompt (first 50 chars, alphanumeric)
57
+ - MD5 hash of full prompt for uniqueness
58
+
59
+ ## Cache Matching
60
+
61
+ The prompt cache uses **exact text matching**:
62
+ - Case-insensitive comparison
63
+ - Whitespace trimmed
64
+ - No screenshot comparison
65
+
66
+ ```javascript
67
+ // These all match the same cache entry:
68
+ await testdriver.ai('click the submit button');
69
+ await testdriver.ai('CLICK THE SUBMIT BUTTON');
70
+ await testdriver.ai(' click the submit button ');
71
+
72
+ // This creates a new cache entry:
73
+ await testdriver.ai('click the submit btn'); // Different text
74
+ ```
75
+
76
+ ## Disabling Prompt Cache
77
+
78
+ Bypass the cache for a specific call:
79
+
80
+ ```javascript
81
+ // Force fresh AI call, bypass cache
82
+ await testdriver.ai('click the submit button', false);
83
+
84
+ // These use cache (default)
85
+ await testdriver.ai('click the submit button');
86
+ await testdriver.ai('click the submit button', true);
87
+ ```
88
+
89
+ ## Clearing Prompt Cache
90
+
91
+ Clear all cached prompts:
92
+
93
+ ```bash
94
+ rm -rf .testdriver/.cache/*.yaml
95
+ ```
96
+
97
+ Or programmatically:
98
+
99
+ ```javascript
100
+ const promptCache = require('testdriverai/agent/lib/cache.js');
101
+ promptCache.clearCache();
102
+ ```
103
+
104
+ ## Usage Examples
105
+
106
+ ### Basic Caching
107
+
108
+ ```javascript
109
+ import { test } from 'vitest';
110
+ import { chrome } from 'testdriverai/presets';
111
+
112
+ test('login flow', async (context) => {
113
+ const { testdriver } = await chrome(context, {
114
+ url: 'https://myapp.com/login'
115
+ });
116
+
117
+ // First call: AI generates commands, saves to cache
118
+ await testdriver.ai('click the login button');
119
+
120
+ // Run test again - uses cache (instant)
121
+ // Look for: "(using cached response)" in output
122
+ });
123
+ ```
124
+
125
+ ### Bypassing Cache
126
+
127
+ ```javascript
128
+ test('always fresh', async (context) => {
129
+ const { testdriver } = await chrome(context, { url });
130
+
131
+ // Always get fresh AI response
132
+ await testdriver.ai('analyze the current state', false);
133
+ });
134
+ ```
135
+
136
+ ## Best Practices
137
+
138
+ ### 1. Use Consistent Prompts
139
+
140
+ ```javascript
141
+ // ✅ Good - consistent prompt
142
+ await testdriver.ai('fill out the login form');
143
+ await testdriver.ai('fill out the login form'); // Cache hit
144
+
145
+ // ❌ Bad - different prompts
146
+ await testdriver.ai('fill out the login form');
147
+ await testdriver.ai('complete the login form'); // Cache miss
148
+ ```
149
+
150
+ ### 2. Clear Cache When Test Logic Changes
151
+
152
+ If you update your test prompts, clear the cache:
153
+
154
+ ```bash
155
+ rm -rf .testdriver/.cache/*.yaml
156
+ ```
157
+
158
+ ### 3. Don't Commit Cache to Git
159
+
160
+ Add to `.gitignore`:
161
+
162
+ ```gitignore
163
+ .testdriver/.cache/
164
+ ```
165
+
166
+ ### 4. Version Control Consideration
167
+
168
+ While you _can_ commit cached prompts for team consistency, it's generally not recommended because:
169
+ - AI responses may improve over time
170
+ - Different team members may need different cached results
171
+ - Cache files can become stale
172
+
173
+ ## Cache Storage Details
174
+
175
+ | Property | Value |
176
+ |----------|-------|
177
+ | Location | Local (`.testdriver/.cache/`) |
178
+ | Persistence | Until manually cleared |
179
+ | Scope | Per-project |
180
+ | Matching | Exact prompt text (case-insensitive) |
181
+ | Expiration | Never |
182
+
183
+ ## Troubleshooting
184
+
185
+ ### Cache Not Working
186
+
187
+ Check:
188
+ 1. Prompts match exactly (case-insensitive)
189
+ 2. `.testdriver/.cache/` directory exists and is writable
190
+ 3. `TD_NO_PROMPT_CACHE` environment variable is not set
191
+
192
+ ### Stale Cache Data
193
+
194
+ If AI responses seem outdated:
195
+
196
+ ```bash
197
+ # Clear all cached prompts
198
+ rm -rf .testdriver/.cache/*.yaml
199
+ ```
200
+
201
+ Or clear specific prompts:
202
+
203
+ ```bash
204
+ # Find cache files
205
+ ls -la .testdriver/.cache/
206
+
207
+ # Delete specific cache file
208
+ rm .testdriver/.cache/click-the-submit-button-*.yaml
209
+ ```
210
+
211
+ ## See Also
212
+
213
+ - [Selector Caching](/v7/guides/caching-selectors) - Cache element locations
214
+ - [`.ai()` Method](/v7/api/ai) - AI command generation
215
+ - [Vitest Integration](/v7/guides/vitest) - Testing with TestDriver