testdriverai 6.2.2 → 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.
- package/.github/workflows/acceptance-linux.yml +75 -0
- package/.github/workflows/acceptance-sdk-tests.yml +133 -0
- package/.vscode/settings.json +5 -1
- package/AGENTS.md +550 -0
- package/CODEOWNERS +0 -1
- package/README.md +126 -0
- package/{testdriver → _testdriver}/acceptance/drag-and-drop.yaml +2 -2
- package/{testdriver → _testdriver}/acceptance/snippets/login.yaml +1 -1
- package/_testdriver/examples/desktop/lifecycle/prerun.yaml +0 -0
- package/{testdriver → _testdriver}/examples/web/lifecycle/prerun.yaml +6 -1
- package/{testdriver → _testdriver}/lifecycle/postrun.yaml +3 -2
- package/_testdriver/lifecycle/prerun.yaml +15 -0
- package/{testdriver → _testdriver}/lifecycle/provision.yaml +7 -2
- package/agent/index.js +300 -85
- package/agent/interface.js +15 -0
- package/agent/lib/cache.js +142 -0
- package/agent/lib/commander.js +1 -39
- package/agent/lib/commands.js +910 -296
- package/agent/lib/redraw.js +129 -41
- package/agent/lib/sandbox.js +29 -6
- package/agent/lib/sdk.js +22 -0
- package/agent/lib/system.js +0 -3
- package/agent/lib/validation.js +1 -7
- package/debug-locate-response.js +82 -0
- package/debugger/index.html +15 -4
- package/docs/ARCHITECTURE.md +424 -0
- package/docs/AWESOME_LOGS_QUICK_REF.md +100 -0
- package/docs/MIGRATION.md +425 -0
- package/docs/PRESETS.md +210 -0
- package/docs/QUICK_START_TEST_RECORDING.md +215 -0
- package/docs/SDK_AWESOME_LOGS.md +468 -0
- package/docs/TEST_RECORDING.md +388 -0
- package/docs/docs.json +286 -152
- package/docs/guide/best-practices-polling.mdx +154 -0
- package/docs/sdk-browser-rendering.md +167 -0
- package/docs/v6/getting-started/self-hosting.mdx +407 -0
- package/docs/{guide → v6/guide}/dashcam.mdx +1 -1
- package/docs/{guide → v6/guide}/environment-variables.mdx +4 -5
- package/docs/{guide → v6/guide}/lifecycle.mdx +1 -1
- package/docs/v6/overview/comparison.mdx +101 -0
- package/docs/v7/README.md +135 -0
- package/docs/v7/api/ai.mdx +205 -0
- package/docs/v7/api/assert.mdx +285 -0
- package/docs/v7/api/assertions.mdx +403 -0
- package/docs/v7/api/click.mdx +287 -0
- package/docs/v7/api/client.mdx +322 -0
- package/docs/v7/api/dashcam.mdx +497 -0
- package/docs/v7/api/doubleClick.mdx +102 -0
- package/docs/v7/api/elements.mdx +479 -0
- package/docs/v7/api/exec.mdx +346 -0
- package/docs/v7/api/find.mdx +316 -0
- package/docs/v7/api/focusApplication.mdx +294 -0
- package/docs/v7/api/hover.mdx +279 -0
- package/docs/v7/api/mouseDown.mdx +161 -0
- package/docs/v7/api/mouseUp.mdx +164 -0
- package/docs/v7/api/pressKeys.mdx +349 -0
- package/docs/v7/api/rightClick.mdx +123 -0
- package/docs/v7/api/sandbox.mdx +404 -0
- package/docs/v7/api/scroll.mdx +300 -0
- package/docs/v7/api/type.mdx +314 -0
- package/docs/v7/commands/assert.mdx +45 -0
- package/docs/v7/commands/exec.mdx +282 -0
- package/docs/v7/commands/focus-application.mdx +44 -0
- package/docs/v7/commands/hover-image.mdx +69 -0
- package/docs/v7/commands/hover-text.mdx +47 -0
- package/docs/v7/commands/if.mdx +53 -0
- package/docs/v7/commands/match-image.mdx +67 -0
- package/docs/v7/commands/press-keys.mdx +87 -0
- package/docs/v7/commands/remember.mdx +49 -0
- package/docs/v7/commands/run.mdx +44 -0
- package/docs/v7/commands/scroll-until-image.mdx +66 -0
- package/docs/v7/commands/scroll-until-text.mdx +60 -0
- package/docs/v7/commands/scroll.mdx +69 -0
- package/docs/v7/commands/type.mdx +45 -0
- package/docs/v7/commands/wait-for-image.mdx +54 -0
- package/docs/v7/commands/wait-for-text.mdx +48 -0
- package/docs/v7/commands/wait.mdx +45 -0
- package/docs/v7/getting-started/configuration.mdx +380 -0
- package/docs/v7/getting-started/quickstart.mdx +332 -0
- package/docs/v7/guides/best-practices.mdx +486 -0
- package/docs/v7/guides/caching-ai.mdx +215 -0
- package/docs/v7/guides/caching-selectors.mdx +292 -0
- package/docs/v7/guides/caching.mdx +366 -0
- package/docs/v7/guides/ci-cd/azure.mdx +587 -0
- package/docs/v7/guides/ci-cd/circleci.mdx +523 -0
- package/docs/v7/guides/ci-cd/github-actions.mdx +457 -0
- package/docs/v7/guides/ci-cd/gitlab.mdx +498 -0
- package/docs/v7/guides/ci-cd/jenkins.mdx +664 -0
- package/docs/v7/guides/ci-cd/travis.mdx +438 -0
- package/docs/v7/guides/debugging.mdx +349 -0
- package/docs/v7/guides/faq.mdx +393 -0
- package/docs/v7/guides/migration.mdx +562 -0
- package/docs/v7/guides/performance.mdx +517 -0
- package/docs/{getting-started → v7/guides}/self-hosting.mdx +11 -12
- package/docs/v7/guides/troubleshooting.mdx +526 -0
- package/docs/v7/guides/vitest-plugin.mdx +477 -0
- package/docs/v7/guides/vitest.mdx +535 -0
- package/docs/v7/platforms/linux.mdx +308 -0
- package/docs/v7/platforms/macos.mdx +433 -0
- package/docs/v7/platforms/windows.mdx +430 -0
- package/docs/v7/playwright.mdx +342 -0
- package/docs/v7/presets/chrome-extension.mdx +223 -0
- package/docs/v7/presets/chrome.mdx +287 -0
- package/docs/v7/presets/electron.mdx +435 -0
- package/docs/v7/presets/vscode.mdx +398 -0
- package/docs/v7/presets/webapp.mdx +396 -0
- package/docs/v7/progressive-apis/CORE.md +459 -0
- package/docs/v7/progressive-apis/HOOKS.md +360 -0
- package/docs/v7/progressive-apis/PROGRESSIVE_DISCLOSURE.md +230 -0
- package/docs/v7/progressive-apis/PROVISION.md +266 -0
- package/eslint.config.js +19 -1
- package/interfaces/cli/lib/base.js +10 -4
- package/interfaces/logger.js +2 -1
- package/interfaces/shared-test-state.mjs +69 -0
- package/interfaces/vitest-plugin.mjs +830 -0
- package/package.json +29 -5
- package/schema.json +8 -29
- package/scripts/view-test-results.mjs +96 -0
- package/sdk-log-formatter.js +714 -0
- package/sdk.d.ts +1028 -0
- package/sdk.js +2567 -0
- package/{.github/workflows/self-hosted.yml → self-hosted.yml} +13 -4
- package/setup/aws/cloudformation.yaml +9 -2
- package/src/core/Dashcam.js +469 -0
- package/src/core/index.d.ts +150 -0
- package/src/core/index.js +12 -0
- package/src/presets/index.mjs +331 -0
- package/src/vitest/extended.mjs +108 -0
- package/src/vitest/hooks.d.ts +119 -0
- package/src/vitest/hooks.mjs +298 -0
- package/src/vitest/index.mjs +64 -0
- package/src/vitest/lifecycle.mjs +277 -0
- package/src/vitest/utils.mjs +150 -0
- package/test/dashcam.test.js +137 -0
- package/test/mcp-example-test.yaml +27 -0
- package/testdriver/acceptance-sdk/QUICK_REFERENCE.md +61 -0
- package/testdriver/acceptance-sdk/README.md +128 -0
- package/testdriver/acceptance-sdk/TEST_REPORTING.md +245 -0
- package/testdriver/acceptance-sdk/assert.test.mjs +26 -0
- package/testdriver/acceptance-sdk/auto-cache-key-demo.test.mjs +56 -0
- package/testdriver/acceptance-sdk/chrome-extension.test.mjs +89 -0
- package/testdriver/acceptance-sdk/drag-and-drop.test.mjs +58 -0
- package/testdriver/acceptance-sdk/element-not-found.test.mjs +25 -0
- package/testdriver/acceptance-sdk/exec-js.test.mjs +43 -0
- package/testdriver/acceptance-sdk/exec-output.test.mjs +59 -0
- package/testdriver/acceptance-sdk/exec-pwsh.test.mjs +57 -0
- package/testdriver/acceptance-sdk/focus-window.test.mjs +36 -0
- package/testdriver/acceptance-sdk/formatted-logging.test.mjs +26 -0
- package/testdriver/acceptance-sdk/hooks-example.test.mjs +38 -0
- package/testdriver/acceptance-sdk/hover-image.test.mjs +34 -0
- package/testdriver/acceptance-sdk/hover-text-with-description.test.mjs +38 -0
- package/testdriver/acceptance-sdk/hover-text.test.mjs +27 -0
- package/testdriver/acceptance-sdk/match-image.test.mjs +36 -0
- package/testdriver/acceptance-sdk/presets-example.test.mjs +87 -0
- package/testdriver/acceptance-sdk/press-keys.test.mjs +50 -0
- package/testdriver/acceptance-sdk/prompt.test.mjs +33 -0
- package/testdriver/acceptance-sdk/scroll-keyboard.test.mjs +38 -0
- package/testdriver/acceptance-sdk/scroll-until-image.test.mjs +39 -0
- package/testdriver/acceptance-sdk/scroll-until-text.test.mjs +28 -0
- package/testdriver/acceptance-sdk/scroll.test.mjs +41 -0
- package/testdriver/acceptance-sdk/setup/globalTeardown.mjs +11 -0
- package/testdriver/acceptance-sdk/setup/testHelpers.mjs +420 -0
- package/testdriver/acceptance-sdk/setup/vitestSetup.mjs +40 -0
- package/testdriver/acceptance-sdk/sully-ai.test.mjs +234 -0
- package/testdriver/acceptance-sdk/test-console-logs.test.mjs +42 -0
- package/testdriver/acceptance-sdk/type-checking-demo.js +49 -0
- package/testdriver/acceptance-sdk/type.test.mjs +45 -0
- package/verify-element-api.js +89 -0
- package/verify-types.js +0 -0
- package/vitest.config.example.js +19 -0
- package/vitest.config.mjs +66 -0
- package/vitest.config.mjs.bak +44 -0
- package/.github/workflows/acceptance-v6.yml +0 -169
- package/.vscode/mcp.json +0 -9
- package/docs/overview/comparison.mdx +0 -82
- package/testdriver/lifecycle/prerun.yaml +0 -17
- /package/{testdriver/examples/desktop/lifecycle/prerun.yaml → .env.example} +0 -0
- /package/{testdriver → _testdriver}/acceptance/assert.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/dashcam.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/embed.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/exec-js.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/exec-output.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/exec-shell.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/focus-window.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/hover-image.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/hover-text-with-description.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/hover-text.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/if-else.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/match-image.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/press-keys.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/prompt.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/remember.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/screenshots/cart.png +0 -0
- /package/{testdriver → _testdriver}/acceptance/scroll-keyboard.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/scroll-until-image.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/scroll-until-text.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/scroll.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/snippets/match-cart.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/type.yaml +0 -0
- /package/{testdriver → _testdriver}/behavior/failure.yaml +0 -0
- /package/{testdriver → _testdriver}/behavior/hover-text.yaml +0 -0
- /package/{testdriver → _testdriver}/behavior/lifecycle/postrun.yaml +0 -0
- /package/{testdriver → _testdriver}/behavior/lifecycle/prerun.yaml +0 -0
- /package/{testdriver → _testdriver}/behavior/lifecycle/provision.yaml +0 -0
- /package/{testdriver → _testdriver}/behavior/secrets.yaml +0 -0
- /package/{testdriver → _testdriver}/edge-cases/dashcam-chrome.yaml +0 -0
- /package/{testdriver → _testdriver}/edge-cases/exec-pwsh-multiline.yaml +0 -0
- /package/{testdriver → _testdriver}/edge-cases/js-exception.yaml +0 -0
- /package/{testdriver → _testdriver}/edge-cases/js-promise.yaml +0 -0
- /package/{testdriver → _testdriver}/edge-cases/lifecycle/postrun.yaml +0 -0
- /package/{testdriver → _testdriver}/edge-cases/prompt-in-middle.yaml +0 -0
- /package/{testdriver → _testdriver}/edge-cases/prompt-nested.yaml +0 -0
- /package/{testdriver → _testdriver}/edge-cases/success-test.yaml +0 -0
- /package/{testdriver → _testdriver}/examples/android/example.yaml +0 -0
- /package/{testdriver → _testdriver}/examples/android/lifecycle/postrun.yaml +0 -0
- /package/{testdriver → _testdriver}/examples/android/lifecycle/provision.yaml +0 -0
- /package/{testdriver → _testdriver}/examples/android/readme.md +0 -0
- /package/{testdriver → _testdriver}/examples/chrome-extension/lifecycle/provision.yaml +0 -0
- /package/{testdriver → _testdriver}/examples/desktop/lifecycle/provision.yaml +0 -0
- /package/{testdriver → _testdriver}/examples/vscode-extension/lifecycle/provision.yaml +0 -0
- /package/{testdriver → _testdriver}/examples/web/lifecycle/postrun.yaml +0 -0
- /package/docs/{account → v6/account}/dashboard.mdx +0 -0
- /package/docs/{account → v6/account}/enterprise.mdx +0 -0
- /package/docs/{account → v6/account}/pricing.mdx +0 -0
- /package/docs/{account → v6/account}/projects.mdx +0 -0
- /package/docs/{account → v6/account}/team.mdx +0 -0
- /package/docs/{action → v6/action}/ami.mdx +0 -0
- /package/docs/{action → v6/action}/performance.mdx +0 -0
- /package/docs/{action → v6/action}/secrets.mdx +0 -0
- /package/docs/{apps → v6/apps}/chrome-extensions.mdx +0 -0
- /package/docs/{apps → v6/apps}/desktop-apps.mdx +0 -0
- /package/docs/{apps → v6/apps}/mobile-apps.mdx +0 -0
- /package/docs/{apps → v6/apps}/static-websites.mdx +0 -0
- /package/docs/{apps → v6/apps}/tauri-apps.mdx +0 -0
- /package/docs/{bugs → v6/bugs}/jira.mdx +0 -0
- /package/docs/{cli → v6/cli}/overview.mdx +0 -0
- /package/docs/{commands → v6/commands}/assert.mdx +0 -0
- /package/docs/{commands → v6/commands}/exec.mdx +0 -0
- /package/docs/{commands → v6/commands}/focus-application.mdx +0 -0
- /package/docs/{commands → v6/commands}/hover-image.mdx +0 -0
- /package/docs/{commands → v6/commands}/hover-text.mdx +0 -0
- /package/docs/{commands → v6/commands}/if.mdx +0 -0
- /package/docs/{commands → v6/commands}/match-image.mdx +0 -0
- /package/docs/{commands → v6/commands}/press-keys.mdx +0 -0
- /package/docs/{commands → v6/commands}/remember.mdx +0 -0
- /package/docs/{commands → v6/commands}/run.mdx +0 -0
- /package/docs/{commands → v6/commands}/scroll-until-image.mdx +0 -0
- /package/docs/{commands → v6/commands}/scroll-until-text.mdx +0 -0
- /package/docs/{commands → v6/commands}/scroll.mdx +0 -0
- /package/docs/{commands → v6/commands}/type.mdx +0 -0
- /package/docs/{commands → v6/commands}/wait-for-image.mdx +0 -0
- /package/docs/{commands → v6/commands}/wait-for-text.mdx +0 -0
- /package/docs/{commands → v6/commands}/wait.mdx +0 -0
- /package/docs/{exporting → v6/exporting}/junit.mdx +0 -0
- /package/docs/{exporting → v6/exporting}/playwright.mdx +0 -0
- /package/docs/{features → v6/features}/auto-healing.mdx +0 -0
- /package/docs/{features → v6/features}/generation.mdx +0 -0
- /package/docs/{features → v6/features}/parallel-testing.mdx +0 -0
- /package/docs/{features → v6/features}/reusable-snippets.mdx +0 -0
- /package/docs/{features → v6/features}/selectorless.mdx +0 -0
- /package/docs/{features → v6/features}/visual-assertions.mdx +0 -0
- /package/docs/{getting-started → v6/getting-started}/ci.mdx +0 -0
- /package/docs/{getting-started → v6/getting-started}/cli.mdx +0 -0
- /package/docs/{getting-started → v6/getting-started}/editing.mdx +0 -0
- /package/docs/{getting-started → v6/getting-started}/playwright.mdx +0 -0
- /package/docs/{getting-started → v6/getting-started}/running.mdx +0 -0
- /package/docs/{getting-started → v6/getting-started}/vscode.mdx +0 -0
- /package/docs/{guide → v6/guide}/assertions.mdx +0 -0
- /package/docs/{guide → v6/guide}/authentication.mdx +0 -0
- /package/docs/{guide → v6/guide}/code.mdx +0 -0
- /package/docs/{guide → v6/guide}/locating.mdx +0 -0
- /package/docs/{guide → v6/guide}/protips.mdx +0 -0
- /package/docs/{guide → v6/guide}/variables.mdx +0 -0
- /package/docs/{guide → v6/guide}/waiting.mdx +0 -0
- /package/docs/{importing → v6/importing}/csv.mdx +0 -0
- /package/docs/{importing → v6/importing}/gherkin.mdx +0 -0
- /package/docs/{importing → v6/importing}/jira.mdx +0 -0
- /package/docs/{importing → v6/importing}/testrail.mdx +0 -0
- /package/docs/{integrations → v6/integrations}/electron.mdx +0 -0
- /package/docs/{integrations → v6/integrations}/netlify.mdx +0 -0
- /package/docs/{integrations → v6/integrations}/vercel.mdx +0 -0
- /package/docs/{interactive → v6/interactive}/explore.mdx +0 -0
- /package/docs/{interactive → v6/interactive}/run.mdx +0 -0
- /package/docs/{interactive → v6/interactive}/save.mdx +0 -0
- /package/docs/{overview → v6/overview}/faq.mdx +0 -0
- /package/docs/{overview → v6/overview}/performance.mdx +0 -0
- /package/docs/{overview → v6/overview}/quickstart.mdx +0 -0
- /package/docs/{overview → v6/overview}/what-is-testdriver.mdx +0 -0
- /package/docs/{scenarios → v6/scenarios}/ai-chatbot.mdx +0 -0
- /package/docs/{scenarios → v6/scenarios}/cookie-banner.mdx +0 -0
- /package/docs/{scenarios → v6/scenarios}/file-upload.mdx +0 -0
- /package/docs/{scenarios → v6/scenarios}/form-filling.mdx +0 -0
- /package/docs/{scenarios → v6/scenarios}/log-in.mdx +0 -0
- /package/docs/{scenarios → v6/scenarios}/pdf-generation.mdx +0 -0
- /package/docs/{scenarios → v6/scenarios}/spell-check.mdx +0 -0
- /package/docs/{security → v6/security}/action.mdx +0 -0
- /package/docs/{security → v6/security}/agent.mdx +0 -0
- /package/docs/{security → v6/security}/platform.mdx +0 -0
- /package/docs/{tutorials → v6/tutorials}/advanced-test.mdx +0 -0
- /package/docs/{tutorials → v6/tutorials}/basic-test.mdx +0 -0
|
@@ -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
|