testdriverai 7.0.0 → 7.1.1
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/.env.example +2 -0
- package/.github/workflows/linux-tests.yml +28 -0
- package/README.md +126 -0
- package/agent/index.js +7 -9
- package/agent/interface.js +13 -2
- package/agent/lib/commands.js +795 -136
- package/agent/lib/redraw.js +124 -39
- package/agent/lib/sandbox.js +40 -3
- package/agent/lib/sdk.js +21 -0
- package/agent/lib/valid-version.js +2 -2
- package/debugger/index.html +1 -1
- package/docs/docs.json +86 -71
- package/docs/guide/best-practices-polling.mdx +154 -0
- package/docs/v6/getting-started/self-hosting.mdx +3 -2
- package/docs/v7/_drafts/agents.mdx +852 -0
- package/docs/v7/_drafts/auto-cache-key.mdx +167 -0
- package/docs/v7/_drafts/best-practices.mdx +486 -0
- package/docs/v7/_drafts/caching-ai.mdx +215 -0
- package/docs/v7/_drafts/caching-selectors.mdx +400 -0
- package/docs/v7/_drafts/caching.mdx +366 -0
- package/docs/v7/_drafts/cli-to-sdk-migration.mdx +425 -0
- package/docs/v7/_drafts/core.mdx +459 -0
- package/docs/v7/_drafts/dashcam-title-feature.mdx +89 -0
- package/docs/v7/_drafts/debugging.mdx +349 -0
- package/docs/v7/_drafts/error-handling.mdx +501 -0
- package/docs/v7/_drafts/faq.mdx +393 -0
- package/docs/v7/_drafts/hooks.mdx +360 -0
- package/docs/v7/_drafts/implementation-plan.mdx +994 -0
- package/docs/v7/_drafts/init-command.mdx +95 -0
- package/docs/v7/_drafts/optimal-sdk-design.mdx +1348 -0
- package/docs/v7/_drafts/performance.mdx +517 -0
- package/docs/v7/_drafts/presets.mdx +210 -0
- package/docs/v7/_drafts/progressive-disclosure.mdx +230 -0
- package/docs/v7/_drafts/provision.mdx +266 -0
- package/docs/{QUICK_START_TEST_RECORDING.md → v7/_drafts/quick-start-test-recording.mdx} +3 -3
- package/docs/v7/_drafts/sdk-v7-complete.mdx +345 -0
- package/docs/v7/{guides → _drafts}/self-hosting.mdx +1 -1
- package/docs/v7/_drafts/troubleshooting.mdx +526 -0
- package/docs/v7/_drafts/vitest-plugin.mdx +477 -0
- package/docs/v7/_drafts/vitest.mdx +535 -0
- package/docs/v7/api/{ai.mdx → act.mdx} +24 -24
- package/docs/v7/api/client.mdx +1 -1
- package/docs/v7/api/dashcam.mdx +497 -0
- package/docs/v7/api/doubleClick.mdx +102 -0
- package/docs/v7/api/elements.mdx +143 -41
- package/docs/v7/api/find.mdx +258 -0
- package/docs/v7/api/mouseDown.mdx +161 -0
- package/docs/v7/api/mouseUp.mdx +164 -0
- package/docs/v7/api/rightClick.mdx +123 -0
- package/docs/v7/api/type.mdx +51 -7
- package/docs/v7/features/ai-native.mdx +427 -0
- package/docs/v7/features/easy-to-write.mdx +351 -0
- package/docs/v7/features/enterprise.mdx +540 -0
- package/docs/v7/features/fast.mdx +424 -0
- package/docs/v7/features/observable.mdx +623 -0
- package/docs/v7/features/powerful.mdx +531 -0
- package/docs/v7/features/scalable.mdx +417 -0
- package/docs/v7/features/stable.mdx +514 -0
- package/docs/v7/getting-started/configuration.mdx +380 -0
- package/docs/v7/getting-started/generating-tests.mdx +525 -0
- package/docs/v7/getting-started/installation.mdx +486 -0
- package/docs/v7/getting-started/quickstart.mdx +320 -141
- package/docs/v7/getting-started/running-and-debugging.mdx +511 -0
- package/docs/v7/getting-started/setting-up-in-ci.mdx +612 -0
- package/docs/v7/getting-started/writing-tests.mdx +535 -0
- package/docs/v7/overview/what-is-testdriver.mdx +398 -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 +3 -3
- package/docs/v7/presets/chrome-extension.mdx +223 -0
- package/docs/v7/presets/chrome.mdx +303 -0
- package/docs/v7/presets/electron.mdx +453 -0
- package/docs/v7/presets/vscode.mdx +417 -0
- package/docs/v7/presets/webapp.mdx +396 -0
- package/examples/run-tests-with-recording.sh +2 -2
- package/interfaces/cli/commands/init.js +358 -0
- package/interfaces/vitest-plugin.mjs +393 -103
- package/lib/core/Dashcam.js +506 -0
- package/lib/core/index.d.ts +150 -0
- package/lib/core/index.js +12 -0
- package/lib/presets/index.mjs +331 -0
- package/lib/vitest/hooks.d.ts +119 -0
- package/lib/vitest/hooks.mjs +316 -0
- package/lib/vitest/setup.mjs +44 -0
- package/package.json +13 -3
- package/sdk.d.ts +350 -44
- package/sdk.js +818 -105
- package/{self-hosted.yml → setup/aws/self-hosted.yml} +1 -1
- package/test/manual/test-console-logs.test.mjs +42 -0
- package/test/manual/test-init.sh +54 -0
- package/test/manual/test-provision-auth.mjs +22 -0
- package/test/testdriver/assert.test.mjs +41 -0
- package/test/testdriver/auto-cache-key-demo.test.mjs +56 -0
- package/test/testdriver/chrome-extension.test.mjs +89 -0
- package/{testdriver/acceptance-sdk → test/testdriver}/drag-and-drop.test.mjs +7 -19
- package/{testdriver/acceptance-sdk → test/testdriver}/element-not-found.test.mjs +6 -19
- package/{testdriver/acceptance-sdk → test/testdriver}/exec-js.test.mjs +6 -18
- package/{testdriver/acceptance-sdk → test/testdriver}/exec-output.test.mjs +9 -21
- package/{testdriver/acceptance-sdk → test/testdriver}/exec-pwsh.test.mjs +14 -26
- package/{testdriver/acceptance-sdk → test/testdriver}/focus-window.test.mjs +8 -20
- package/{testdriver/acceptance-sdk → test/testdriver}/formatted-logging.test.mjs +5 -20
- package/{testdriver/acceptance-sdk → test/testdriver}/hover-image.test.mjs +10 -19
- package/{testdriver/acceptance-sdk → test/testdriver}/hover-text-with-description.test.mjs +7 -19
- package/{testdriver/acceptance-sdk → test/testdriver}/hover-text.test.mjs +5 -19
- package/{testdriver/acceptance-sdk → test/testdriver}/match-image.test.mjs +7 -19
- package/{testdriver/acceptance-sdk → test/testdriver}/press-keys.test.mjs +5 -19
- package/{testdriver/acceptance-sdk → test/testdriver}/prompt.test.mjs +7 -19
- package/{testdriver/acceptance-sdk → test/testdriver}/scroll-keyboard.test.mjs +6 -20
- package/{testdriver/acceptance-sdk → test/testdriver}/scroll-until-image.test.mjs +6 -18
- package/test/testdriver/scroll-until-text.test.mjs +28 -0
- package/{testdriver/acceptance-sdk → test/testdriver}/scroll.test.mjs +12 -21
- package/test/testdriver/setup/lifecycleHelpers.mjs +262 -0
- package/{testdriver/acceptance-sdk → test/testdriver}/setup/testHelpers.mjs +25 -20
- package/test/testdriver/type.test.mjs +45 -0
- package/vitest.config.mjs +11 -56
- package/.github/dependabot.yml +0 -11
- package/.github/workflows/acceptance-linux.yml +0 -75
- package/.github/workflows/acceptance-sdk-tests.yml +0 -133
- package/.github/workflows/acceptance-tests.yml +0 -130
- package/.github/workflows/lint.yml +0 -27
- package/.github/workflows/publish-canary.yml +0 -40
- package/.github/workflows/publish-latest.yml +0 -61
- package/.github/workflows/test-install.yml +0 -29
- package/.vscode/extensions.json +0 -3
- package/.vscode/launch.json +0 -22
- package/.vscode/mcp.json +0 -9
- package/.vscode/settings.json +0 -14
- package/CODEOWNERS +0 -3
- package/MIGRATION.md +0 -389
- package/SDK_README.md +0 -1122
- package/_testdriver/acceptance/assert.yaml +0 -7
- package/_testdriver/acceptance/dashcam.yaml +0 -9
- package/_testdriver/acceptance/drag-and-drop.yaml +0 -49
- package/_testdriver/acceptance/embed.yaml +0 -9
- package/_testdriver/acceptance/exec-js.yaml +0 -29
- package/_testdriver/acceptance/exec-output.yaml +0 -43
- package/_testdriver/acceptance/exec-shell.yaml +0 -40
- package/_testdriver/acceptance/focus-window.yaml +0 -16
- package/_testdriver/acceptance/hover-image.yaml +0 -18
- package/_testdriver/acceptance/hover-text-with-description.yaml +0 -29
- package/_testdriver/acceptance/hover-text.yaml +0 -14
- package/_testdriver/acceptance/if-else.yaml +0 -31
- package/_testdriver/acceptance/match-image.yaml +0 -15
- package/_testdriver/acceptance/press-keys.yaml +0 -35
- package/_testdriver/acceptance/prompt.yaml +0 -11
- package/_testdriver/acceptance/remember.yaml +0 -27
- package/_testdriver/acceptance/screenshots/cart.png +0 -0
- package/_testdriver/acceptance/scroll-keyboard.yaml +0 -34
- package/_testdriver/acceptance/scroll-until-image.yaml +0 -26
- package/_testdriver/acceptance/scroll-until-text.yaml +0 -20
- package/_testdriver/acceptance/scroll.yaml +0 -33
- package/_testdriver/acceptance/snippets/login.yaml +0 -29
- package/_testdriver/acceptance/snippets/match-cart.yaml +0 -8
- package/_testdriver/acceptance/type.yaml +0 -29
- package/_testdriver/behavior/failure.yaml +0 -7
- package/_testdriver/behavior/hover-text.yaml +0 -13
- package/_testdriver/behavior/lifecycle/postrun.yaml +0 -10
- package/_testdriver/behavior/lifecycle/prerun.yaml +0 -8
- package/_testdriver/behavior/lifecycle/provision.yaml +0 -8
- package/_testdriver/behavior/secrets.yaml +0 -7
- package/_testdriver/edge-cases/dashcam-chrome.yaml +0 -8
- package/_testdriver/edge-cases/exec-pwsh-multiline.yaml +0 -10
- package/_testdriver/edge-cases/js-exception.yaml +0 -8
- package/_testdriver/edge-cases/js-promise.yaml +0 -19
- package/_testdriver/edge-cases/lifecycle/postrun.yaml +0 -10
- package/_testdriver/edge-cases/prompt-in-middle.yaml +0 -23
- package/_testdriver/edge-cases/prompt-nested.yaml +0 -7
- package/_testdriver/edge-cases/success-test.yaml +0 -9
- package/_testdriver/examples/android/example.yaml +0 -12
- package/_testdriver/examples/android/lifecycle/postrun.yaml +0 -11
- package/_testdriver/examples/android/lifecycle/provision.yaml +0 -47
- package/_testdriver/examples/android/readme.md +0 -7
- package/_testdriver/examples/chrome-extension/lifecycle/provision.yaml +0 -74
- package/_testdriver/examples/desktop/lifecycle/prerun.yaml +0 -0
- package/_testdriver/examples/desktop/lifecycle/provision.yaml +0 -64
- package/_testdriver/examples/vscode-extension/lifecycle/provision.yaml +0 -73
- package/_testdriver/examples/web/lifecycle/postrun.yaml +0 -7
- package/_testdriver/examples/web/lifecycle/prerun.yaml +0 -22
- package/_testdriver/lifecycle/postrun.yaml +0 -8
- package/_testdriver/lifecycle/prerun.yaml +0 -15
- package/_testdriver/lifecycle/provision.yaml +0 -25
- package/debug-screenshot-1763401388589.png +0 -0
- package/mcp-server/AI_GUIDELINES.md +0 -57
- package/scripts/view-test-results.mjs +0 -96
- package/styles/.vale-config/2-MDX.ini +0 -5
- package/styles/Microsoft/AMPM.yml +0 -9
- package/styles/Microsoft/Accessibility.yml +0 -30
- package/styles/Microsoft/Acronyms.yml +0 -64
- package/styles/Microsoft/Adverbs.yml +0 -272
- package/styles/Microsoft/Auto.yml +0 -11
- package/styles/Microsoft/Avoid.yml +0 -14
- package/styles/Microsoft/Contractions.yml +0 -50
- package/styles/Microsoft/Dashes.yml +0 -13
- package/styles/Microsoft/DateFormat.yml +0 -8
- package/styles/Microsoft/DateNumbers.yml +0 -40
- package/styles/Microsoft/DateOrder.yml +0 -8
- package/styles/Microsoft/Ellipses.yml +0 -9
- package/styles/Microsoft/FirstPerson.yml +0 -16
- package/styles/Microsoft/Foreign.yml +0 -13
- package/styles/Microsoft/Gender.yml +0 -8
- package/styles/Microsoft/GenderBias.yml +0 -42
- package/styles/Microsoft/GeneralURL.yml +0 -11
- package/styles/Microsoft/HeadingAcronyms.yml +0 -7
- package/styles/Microsoft/HeadingColons.yml +0 -8
- package/styles/Microsoft/HeadingPunctuation.yml +0 -13
- package/styles/Microsoft/Headings.yml +0 -28
- package/styles/Microsoft/Hyphens.yml +0 -14
- package/styles/Microsoft/Negative.yml +0 -13
- package/styles/Microsoft/Ordinal.yml +0 -13
- package/styles/Microsoft/OxfordComma.yml +0 -8
- package/styles/Microsoft/Passive.yml +0 -183
- package/styles/Microsoft/Percentages.yml +0 -7
- package/styles/Microsoft/Plurals.yml +0 -7
- package/styles/Microsoft/Quotes.yml +0 -7
- package/styles/Microsoft/RangeTime.yml +0 -13
- package/styles/Microsoft/Semicolon.yml +0 -8
- package/styles/Microsoft/SentenceLength.yml +0 -6
- package/styles/Microsoft/Spacing.yml +0 -8
- package/styles/Microsoft/Suspended.yml +0 -7
- package/styles/Microsoft/Terms.yml +0 -42
- package/styles/Microsoft/URLFormat.yml +0 -9
- package/styles/Microsoft/Units.yml +0 -16
- package/styles/Microsoft/Vocab.yml +0 -25
- package/styles/Microsoft/We.yml +0 -11
- package/styles/Microsoft/Wordiness.yml +0 -127
- package/styles/Microsoft/meta.json +0 -4
- package/styles/alex/Ablist.yml +0 -274
- package/styles/alex/Condescending.yml +0 -16
- package/styles/alex/Gendered.yml +0 -110
- package/styles/alex/LGBTQ.yml +0 -55
- package/styles/alex/OCD.yml +0 -10
- package/styles/alex/Press.yml +0 -12
- package/styles/alex/ProfanityLikely.yml +0 -1289
- package/styles/alex/ProfanityMaybe.yml +0 -282
- package/styles/alex/ProfanityUnlikely.yml +0 -251
- package/styles/alex/README.md +0 -27
- package/styles/alex/Race.yml +0 -85
- package/styles/alex/Suicide.yml +0 -26
- package/styles/alex/meta.json +0 -4
- package/styles/config/vocabularies/Docs/accept.txt +0 -47
- package/styles/config/vocabularies/Docs/reject.txt +0 -4
- package/styles/proselint/Airlinese.yml +0 -8
- package/styles/proselint/AnimalLabels.yml +0 -48
- package/styles/proselint/Annotations.yml +0 -9
- package/styles/proselint/Apologizing.yml +0 -8
- package/styles/proselint/Archaisms.yml +0 -52
- package/styles/proselint/But.yml +0 -8
- package/styles/proselint/Cliches.yml +0 -782
- package/styles/proselint/CorporateSpeak.yml +0 -30
- package/styles/proselint/Currency.yml +0 -5
- package/styles/proselint/Cursing.yml +0 -15
- package/styles/proselint/DateCase.yml +0 -7
- package/styles/proselint/DateMidnight.yml +0 -7
- package/styles/proselint/DateRedundancy.yml +0 -10
- package/styles/proselint/DateSpacing.yml +0 -7
- package/styles/proselint/DenizenLabels.yml +0 -52
- package/styles/proselint/Diacritical.yml +0 -95
- package/styles/proselint/GenderBias.yml +0 -45
- package/styles/proselint/GroupTerms.yml +0 -39
- package/styles/proselint/Hedging.yml +0 -8
- package/styles/proselint/Hyperbole.yml +0 -6
- package/styles/proselint/Jargon.yml +0 -11
- package/styles/proselint/LGBTOffensive.yml +0 -13
- package/styles/proselint/LGBTTerms.yml +0 -15
- package/styles/proselint/Malapropisms.yml +0 -8
- package/styles/proselint/Needless.yml +0 -358
- package/styles/proselint/Nonwords.yml +0 -38
- package/styles/proselint/Oxymorons.yml +0 -22
- package/styles/proselint/P-Value.yml +0 -6
- package/styles/proselint/RASSyndrome.yml +0 -30
- package/styles/proselint/README.md +0 -12
- package/styles/proselint/Skunked.yml +0 -13
- package/styles/proselint/Spelling.yml +0 -17
- package/styles/proselint/Typography.yml +0 -11
- package/styles/proselint/Uncomparables.yml +0 -50
- package/styles/proselint/Very.yml +0 -6
- package/styles/proselint/meta.json +0 -15
- package/styles/write-good/Cliches.yml +0 -702
- package/styles/write-good/E-Prime.yml +0 -32
- package/styles/write-good/Illusions.yml +0 -11
- package/styles/write-good/Passive.yml +0 -183
- package/styles/write-good/README.md +0 -27
- package/styles/write-good/So.yml +0 -5
- package/styles/write-good/ThereIs.yml +0 -6
- package/styles/write-good/TooWordy.yml +0 -221
- package/styles/write-good/Weasel.yml +0 -29
- package/styles/write-good/meta.json +0 -4
- package/test/mcp-example-test.yaml +0 -27
- package/test/test_parser.js +0 -47
- package/testdriver/acceptance-sdk/QUICK_REFERENCE.md +0 -61
- package/testdriver/acceptance-sdk/README.md +0 -128
- package/testdriver/acceptance-sdk/TEST_REPORTING.md +0 -245
- package/testdriver/acceptance-sdk/assert.test.mjs +0 -44
- package/testdriver/acceptance-sdk/scroll-until-text.test.mjs +0 -42
- package/testdriver/acceptance-sdk/setup/lifecycleHelpers.mjs +0 -239
- package/testdriver/acceptance-sdk/type-checking-demo.js +0 -49
- package/testdriver/acceptance-sdk/type.test.mjs +0 -84
- package/vale.ini +0 -18
- package/vitest.config.example.js +0 -19
- package/vitest.config.mjs.bak +0 -44
- /package/docs/{ARCHITECTURE.md → v7/_drafts/architecture.mdx} +0 -0
- /package/docs/{AWESOME_LOGS_QUICK_REF.md → v7/_drafts/awesome-logs-quick-ref.mdx} +0 -0
- /package/{CONTRIBUTING.md → docs/v7/_drafts/contributing.mdx} +0 -0
- /package/docs/v7/{guides → _drafts}/migration.mdx +0 -0
- /package/{PLUGIN_MIGRATION.md → docs/v7/_drafts/plugin-migration.mdx} +0 -0
- /package/{PROMPT_CACHE.md → docs/v7/_drafts/prompt-cache.mdx} +0 -0
- /package/docs/{SDK_AWESOME_LOGS.md → v7/_drafts/sdk-awesome-logs.mdx} +0 -0
- /package/docs/{sdk-browser-rendering.md → v7/_drafts/sdk-browser-rendering.mdx} +0 -0
- /package/{SDK_LOGGING.md → docs/v7/_drafts/sdk-logging.mdx} +0 -0
- /package/{SDK_MIGRATION.md → docs/v7/_drafts/sdk-migration.mdx} +0 -0
- /package/docs/{TEST_RECORDING.md → v7/_drafts/test-recording.mdx} +0 -0
- /package/docs/v7/{README.md → overview/readme.mdx} +0 -0
- /package/{debug-locate-response.js → test/manual/debug-locate-response.js} +0 -0
- /package/{test-find-api.js → test/manual/test-find-api.js} +0 -0
- /package/{test-prompt-cache.js → test/manual/test-prompt-cache.js} +0 -0
- /package/{test-sandbox-render.js → test/manual/test-sandbox-render.js} +0 -0
- /package/{test-sdk-methods.js → test/manual/test-sdk-methods.js} +0 -0
- /package/{test-sdk-refactor.js → test/manual/test-sdk-refactor.js} +0 -0
- /package/{test-stack-trace.mjs → test/manual/test-stack-trace.mjs} +0 -0
- /package/{verify-element-api.js → test/manual/verify-element-api.js} +0 -0
- /package/{verify-types.js → test/manual/verify-types.js} +0 -0
- /package/{testdriver/acceptance-sdk → test/testdriver}/setup/globalTeardown.mjs +0 -0
- /package/{testdriver/acceptance-sdk → test/testdriver}/setup/vitestSetup.mjs +0 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# Auto-Generated Cache Keys from File Hash
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
When you create a TestDriver instance without providing an explicit `cacheKey`, the SDK will automatically generate one based on the SHA-256 hash of the calling file. This provides automatic cache invalidation when your test file changes, while enabling cache hits for identical test runs.
|
|
6
|
+
|
|
7
|
+
## How It Works
|
|
8
|
+
|
|
9
|
+
1. **Stack Trace Analysis**: When `TestDriver()` is called, the SDK analyzes the call stack to find the caller file
|
|
10
|
+
2. **File Hashing**: The content of the caller file is hashed using SHA-256
|
|
11
|
+
3. **Cache Key Generation**: The first 16 characters of the hash are used as the cache key
|
|
12
|
+
4. **Automatic Updates**: When the test file is modified, the hash changes, automatically invalidating the cache
|
|
13
|
+
|
|
14
|
+
## Benefits
|
|
15
|
+
|
|
16
|
+
- ✅ **No Manual Cache Management**: Cache keys are automatically generated and updated
|
|
17
|
+
- ✅ **File-Scoped Caching**: All tests in the same file share the same cache
|
|
18
|
+
- ✅ **Automatic Invalidation**: Cache is invalidated when the test file changes
|
|
19
|
+
- ✅ **Explicit Override**: You can still provide a manual `cacheKey` if needed
|
|
20
|
+
|
|
21
|
+
## Usage Examples
|
|
22
|
+
|
|
23
|
+
### Automatic Cache Key (Recommended)
|
|
24
|
+
|
|
25
|
+
```javascript
|
|
26
|
+
import { TestDriver } from 'testdriverai/vitest/hooks';
|
|
27
|
+
|
|
28
|
+
test('login test', async (context) => {
|
|
29
|
+
// No cacheKey provided - will be auto-generated from this file's hash
|
|
30
|
+
const testdriver = TestDriver(context, {
|
|
31
|
+
headless: true
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Cache key is automatically set based on this file
|
|
35
|
+
console.log(testdriver.options.cacheKey); // e.g., "4cae7be040f293b9"
|
|
36
|
+
|
|
37
|
+
const button = await testdriver.find('login button');
|
|
38
|
+
// Subsequent calls in this test file will use the same cache
|
|
39
|
+
});
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Explicit Cache Key (Override)
|
|
43
|
+
|
|
44
|
+
```javascript
|
|
45
|
+
import { TestDriver } from 'testdriverai/vitest/hooks';
|
|
46
|
+
|
|
47
|
+
test('login test', async (context) => {
|
|
48
|
+
// Explicit cacheKey provided - auto-generation is skipped
|
|
49
|
+
const testdriver = TestDriver(context, {
|
|
50
|
+
headless: true,
|
|
51
|
+
cacheKey: 'my-custom-key-v1'
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
console.log(testdriver.options.cacheKey); // "my-custom-key-v1"
|
|
55
|
+
|
|
56
|
+
const button = await testdriver.find('login button');
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Cache Behavior
|
|
61
|
+
|
|
62
|
+
### With Auto-Generated Key
|
|
63
|
+
|
|
64
|
+
1. **First Run**: Creates cache entries with key = hash of test file
|
|
65
|
+
2. **Subsequent Runs** (file unchanged): Cache hits
|
|
66
|
+
3. **After File Modification**: New hash = new cache key = cache miss
|
|
67
|
+
|
|
68
|
+
### Cache Hit Example
|
|
69
|
+
|
|
70
|
+
```javascript
|
|
71
|
+
// File: login.test.mjs (hash: 4cae7be040f293b9)
|
|
72
|
+
|
|
73
|
+
const testdriver = TestDriver(context);
|
|
74
|
+
// Auto-generated cacheKey: "4cae7be040f293b9"
|
|
75
|
+
|
|
76
|
+
const button1 = await testdriver.find('login button'); // Cache MISS (first time)
|
|
77
|
+
const button2 = await testdriver.find('login button'); // Cache HIT (same file, same test run)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
After modifying the file (adding a comment, changing test logic, etc.):
|
|
81
|
+
|
|
82
|
+
```javascript
|
|
83
|
+
// File: login.test.mjs (hash: 7f3d9a2b1c5e8f6a) <- changed!
|
|
84
|
+
|
|
85
|
+
const testdriver = TestDriver(context);
|
|
86
|
+
// Auto-generated cacheKey: "7f3d9a2b1c5e8f6a" <- different from before
|
|
87
|
+
|
|
88
|
+
const button1 = await testdriver.find('login button'); // Cache MISS (new hash)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Debug Mode
|
|
92
|
+
|
|
93
|
+
To see the auto-generated cache key in debug logs:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
# Set environment variable
|
|
97
|
+
export TD_DEBUG=1
|
|
98
|
+
|
|
99
|
+
# Or in your test
|
|
100
|
+
process.env.TD_DEBUG = '1';
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
With debug mode enabled, you'll see:
|
|
104
|
+
```
|
|
105
|
+
🔍 find() threshold: 0.05 (cache ENABLED, cacheKey: 4cae7be040f293b9 (auto-generated from file hash))
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Implementation Details
|
|
109
|
+
|
|
110
|
+
### Stack Trace Filtering
|
|
111
|
+
|
|
112
|
+
The auto-generation skips the following in the stack trace to find the actual test file:
|
|
113
|
+
- `sdk.js` (TestDriver SDK)
|
|
114
|
+
- `hooks.mjs` / `hooks.js` (Vitest hooks)
|
|
115
|
+
- `node_modules` (dependencies)
|
|
116
|
+
- `node:internal` (Node.js internals)
|
|
117
|
+
|
|
118
|
+
### File Path Handling
|
|
119
|
+
|
|
120
|
+
The implementation handles both:
|
|
121
|
+
- Regular file paths: `/Users/you/project/test.mjs`
|
|
122
|
+
- File URL format: `file:///Users/you/project/test.mjs`
|
|
123
|
+
|
|
124
|
+
### Hash Format
|
|
125
|
+
|
|
126
|
+
- Algorithm: SHA-256
|
|
127
|
+
- Output: First 16 hexadecimal characters (e.g., `4cae7be040f293b9`)
|
|
128
|
+
- Collision probability: Effectively zero for practical purposes
|
|
129
|
+
|
|
130
|
+
## When to Use Manual Cache Keys
|
|
131
|
+
|
|
132
|
+
Consider using manual `cacheKey` values when:
|
|
133
|
+
|
|
134
|
+
1. **Cross-File Caching**: You want to share cache across multiple test files
|
|
135
|
+
2. **Version-Based Caching**: You want explicit control over cache invalidation
|
|
136
|
+
3. **CI/CD Integration**: You want to tie caching to build numbers or git commits
|
|
137
|
+
|
|
138
|
+
Example:
|
|
139
|
+
|
|
140
|
+
```javascript
|
|
141
|
+
const cacheKey = \`test-suite-\${process.env.GIT_COMMIT}\`;
|
|
142
|
+
const testdriver = TestDriver(context, { cacheKey });
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Migration from Manual Keys
|
|
146
|
+
|
|
147
|
+
If you currently use manual cache keys:
|
|
148
|
+
|
|
149
|
+
### Before
|
|
150
|
+
```javascript
|
|
151
|
+
const testdriver = TestDriver(context, {
|
|
152
|
+
cacheKey: 'login-test-v1'
|
|
153
|
+
});
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### After (automatic)
|
|
157
|
+
```javascript
|
|
158
|
+
// Just remove the cacheKey - it will be auto-generated!
|
|
159
|
+
const testdriver = TestDriver(context);
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
The cache will automatically invalidate when the test file changes, which is usually the desired behavior.
|
|
163
|
+
|
|
164
|
+
## See Also
|
|
165
|
+
|
|
166
|
+
- [SDK_README.md](./SDK_README.md) - Cache configuration options
|
|
167
|
+
- [CACHE_ARCHITECTURE.md](../api/CACHE_ARCHITECTURE.md) - Cache system architecture
|
|
@@ -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>
|