testdriverai 7.1.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/agent/index.js +18 -45
- package/agent/interface.js +13 -2
- package/agent/lib/commands.js +1 -1
- package/agent/lib/redraw.js +1 -1
- package/agent/lib/sandbox.js +30 -2
- package/agent/lib/valid-version.js +2 -2
- package/debugger/index.html +1 -1
- package/docs/docs.json +86 -125
- 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/{guides → _drafts}/caching-selectors.mdx +125 -17
- package/docs/v7/_drafts/dashcam-title-feature.mdx +89 -0
- package/docs/v7/_drafts/error-handling.mdx +501 -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/plugin-migration.mdx +222 -0
- package/docs/v7/_drafts/prompt-cache.mdx +200 -0
- package/docs/{QUICK_START_TEST_RECORDING.md → v7/_drafts/quick-start-test-recording.mdx} +3 -3
- package/docs/v7/_drafts/sdk-logging.mdx +222 -0
- package/docs/v7/_drafts/sdk-migration.mdx +474 -0
- package/docs/v7/_drafts/sdk-v7-complete.mdx +345 -0
- package/docs/v7/{guides → _drafts}/self-hosting.mdx +1 -1
- package/docs/v7/{guides → _drafts}/troubleshooting.mdx +2 -2
- package/docs/v7/{guides → _drafts}/vitest-plugin.mdx +4 -4
- package/docs/v7/api/{ai.mdx → act.mdx} +24 -24
- package/docs/v7/api/client.mdx +1 -1
- package/docs/v7/api/dashcam.mdx +2 -2
- package/docs/v7/api/elements.mdx +143 -41
- package/docs/v7/api/find.mdx +258 -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 +1 -1
- 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 +51 -5
- 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/playwright.mdx +3 -3
- package/docs/v7/presets/chrome.mdx +16 -0
- package/docs/v7/presets/electron.mdx +18 -0
- package/docs/v7/presets/vscode.mdx +19 -0
- package/examples/run-tests-with-recording.sh +70 -0
- package/examples/screenshot-example.js +63 -0
- package/examples/sdk-awesome-logs-demo.js +177 -0
- package/examples/sdk-cache-thresholds.js +96 -0
- package/examples/sdk-element-properties.js +155 -0
- package/examples/sdk-simple-example.js +65 -0
- package/examples/test-recording-example.test.js +166 -0
- package/interfaces/cli/commands/init.js +358 -0
- package/interfaces/vitest-plugin.mjs +214 -10
- package/{src → lib}/core/Dashcam.js +41 -4
- package/{src → lib}/vitest/hooks.mjs +118 -100
- package/lib/vitest/setup.mjs +44 -0
- package/package.json +9 -10
- package/sdk.d.ts +15 -2
- package/sdk.js +70 -18
- package/{self-hosted.yml → setup/aws/self-hosted.yml} +1 -1
- package/{testdriver/acceptance-sdk → test/manual}/test-console-logs.test.mjs +1 -1
- package/test/manual/test-find-api.js +73 -0
- package/test/manual/test-init.sh +54 -0
- package/test/manual/test-prompt-cache.js +96 -0
- package/test/manual/test-provision-auth.mjs +22 -0
- package/test/manual/test-sandbox-render.js +28 -0
- package/test/manual/test-sdk-methods.js +15 -0
- package/test/manual/test-sdk-refactor.js +53 -0
- package/test/manual/test-stack-trace.mjs +57 -0
- package/test/testdriver/assert.test.mjs +41 -0
- package/{testdriver/acceptance-sdk → test/testdriver}/auto-cache-key-demo.test.mjs +1 -1
- package/{testdriver/acceptance-sdk → test/testdriver}/drag-and-drop.test.mjs +1 -1
- package/{testdriver/acceptance-sdk → test/testdriver}/element-not-found.test.mjs +1 -1
- package/{testdriver/acceptance-sdk → test/testdriver}/exec-js.test.mjs +1 -1
- package/{testdriver/acceptance-sdk → test/testdriver}/exec-output.test.mjs +3 -3
- package/{testdriver/acceptance-sdk → test/testdriver}/exec-pwsh.test.mjs +3 -3
- package/{testdriver/acceptance-sdk → test/testdriver}/focus-window.test.mjs +1 -1
- package/{testdriver/acceptance-sdk → test/testdriver}/formatted-logging.test.mjs +1 -1
- package/{testdriver/acceptance-sdk → test/testdriver}/hover-image.test.mjs +1 -1
- package/{testdriver/acceptance-sdk → test/testdriver}/hover-text-with-description.test.mjs +1 -1
- package/{testdriver/acceptance-sdk → test/testdriver}/hover-text.test.mjs +1 -1
- package/{testdriver/acceptance-sdk → test/testdriver}/match-image.test.mjs +1 -1
- package/{testdriver/acceptance-sdk → test/testdriver}/press-keys.test.mjs +1 -1
- package/{testdriver/acceptance-sdk → test/testdriver}/prompt.test.mjs +2 -2
- package/{testdriver/acceptance-sdk → test/testdriver}/scroll-keyboard.test.mjs +1 -1
- package/{testdriver/acceptance-sdk → test/testdriver}/scroll-until-image.test.mjs +1 -1
- package/{testdriver/acceptance-sdk → test/testdriver}/scroll-until-text.test.mjs +1 -1
- package/{testdriver/acceptance-sdk → test/testdriver}/scroll.test.mjs +1 -1
- package/{src/vitest/lifecycle.mjs → test/testdriver/setup/lifecycleHelpers.mjs} +84 -99
- package/test/testdriver/setup/testHelpers.mjs +653 -0
- package/{testdriver/acceptance-sdk → test/testdriver}/type.test.mjs +1 -1
- package/vitest.config.mjs +11 -57
- 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/settings.json +0 -14
- package/AGENTS.md +0 -550
- package/CODEOWNERS +0 -2
- 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/docs/v7/guides/ci-cd/azure.mdx +0 -587
- package/docs/v7/guides/ci-cd/circleci.mdx +0 -523
- package/docs/v7/guides/ci-cd/github-actions.mdx +0 -457
- package/docs/v7/guides/ci-cd/gitlab.mdx +0 -498
- package/docs/v7/guides/ci-cd/jenkins.mdx +0 -664
- package/docs/v7/guides/ci-cd/travis.mdx +0 -438
- package/scripts/view-test-results.mjs +0 -96
- package/src/vitest/extended.mjs +0 -108
- package/src/vitest/index.mjs +0 -64
- package/src/vitest/utils.mjs +0 -150
- 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/dashcam.test.js +0 -137
- 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 -26
- package/testdriver/acceptance-sdk/hooks-example.test.mjs +0 -38
- package/testdriver/acceptance-sdk/presets-example.test.mjs +0 -87
- package/testdriver/acceptance-sdk/setup/testHelpers.mjs +0 -420
- package/testdriver/acceptance-sdk/sully-ai.test.mjs +0 -234
- package/testdriver/acceptance-sdk/type-checking-demo.js +0 -49
- 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/docs/v7/{guides → _drafts}/best-practices.mdx +0 -0
- /package/docs/v7/{guides → _drafts}/caching-ai.mdx +0 -0
- /package/docs/v7/{guides → _drafts}/caching.mdx +0 -0
- /package/docs/{MIGRATION.md → v7/_drafts/cli-to-sdk-migration.mdx} +0 -0
- /package/{CONTRIBUTING.md → docs/v7/_drafts/contributing.mdx} +0 -0
- /package/docs/v7/{progressive-apis/CORE.md → _drafts/core.mdx} +0 -0
- /package/docs/v7/{guides → _drafts}/debugging.mdx +0 -0
- /package/docs/v7/{guides → _drafts}/faq.mdx +0 -0
- /package/docs/v7/{progressive-apis/HOOKS.md → _drafts/hooks.mdx} +0 -0
- /package/docs/v7/{guides → _drafts}/migration.mdx +0 -0
- /package/docs/v7/{guides → _drafts}/performance.mdx +0 -0
- /package/docs/{PRESETS.md → v7/_drafts/presets.mdx} +0 -0
- /package/docs/v7/{progressive-apis/PROGRESSIVE_DISCLOSURE.md → _drafts/progressive-disclosure.mdx} +0 -0
- /package/docs/v7/{progressive-apis/PROVISION.md → _drafts/provision.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/docs/{TEST_RECORDING.md → v7/_drafts/test-recording.mdx} +0 -0
- /package/docs/v7/{guides → _drafts}/vitest.mdx +0 -0
- /package/docs/v7/{README.md → overview/readme.mdx} +0 -0
- /package/{src → lib}/core/index.d.ts +0 -0
- /package/{src → lib}/core/index.js +0 -0
- /package/{src → lib}/presets/index.mjs +0 -0
- /package/{src → lib}/vitest/hooks.d.ts +0 -0
- /package/{debug-locate-response.js → test/manual/debug-locate-response.js} +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}/chrome-extension.test.mjs +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,852 @@
|
|
|
1
|
+
# TestDriver AI SDK - Agent Guide
|
|
2
|
+
|
|
3
|
+
This guide is designed for AI agents to understand how to use the TestDriver SDK for automated testing.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
- [Quick Setup](#quick-setup)
|
|
7
|
+
- [Authentication](#authentication)
|
|
8
|
+
- [Provision Methods](#provision-methods)
|
|
9
|
+
- [Core API Methods](#core-api-methods)
|
|
10
|
+
- [Reconnection and Debugging](#reconnection-and-debugging)
|
|
11
|
+
- [Best Practices](#best-practices)
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Quick Setup
|
|
16
|
+
|
|
17
|
+
### 1. Install Dependencies
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install --save-dev testdriverai vitest
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### 2. Configure Vitest Project
|
|
24
|
+
|
|
25
|
+
Create or update `vitest.config.mjs`:
|
|
26
|
+
|
|
27
|
+
```javascript
|
|
28
|
+
import { defineConfig } from 'vitest/config';
|
|
29
|
+
|
|
30
|
+
export default defineConfig({
|
|
31
|
+
test: {
|
|
32
|
+
testTimeout: 120000, // 2 minutes (TestDriver tests can take longer)
|
|
33
|
+
hookTimeout: 120000,
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### 3. Create Test File
|
|
39
|
+
|
|
40
|
+
Create a test file (e.g., `test.test.js`):
|
|
41
|
+
|
|
42
|
+
```javascript
|
|
43
|
+
import { test } from 'vitest';
|
|
44
|
+
import { chrome } from 'testdriverai/presets';
|
|
45
|
+
|
|
46
|
+
test('my first test', async (context) => {
|
|
47
|
+
const { testdriver } = await chrome(context, {
|
|
48
|
+
url: 'https://example.com',
|
|
49
|
+
apiKey: process.env.TD_API_KEY
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
await testdriver.find('More information link').click();
|
|
53
|
+
await testdriver.assert('IANA page is visible');
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Authentication
|
|
60
|
+
|
|
61
|
+
### Getting an API Key
|
|
62
|
+
|
|
63
|
+
1. Go to [console.testdriver.ai](https://console.testdriver.ai)
|
|
64
|
+
2. Sign up or log in
|
|
65
|
+
3. Navigate to your account settings
|
|
66
|
+
4. Generate a new API key (format: `tdai-1234567890abcdef`)
|
|
67
|
+
|
|
68
|
+
### Using API Keys
|
|
69
|
+
|
|
70
|
+
**Recommended: Environment Variables**
|
|
71
|
+
|
|
72
|
+
Create `.env` file (add to `.gitignore`):
|
|
73
|
+
```bash
|
|
74
|
+
TD_API_KEY=tdai-1234567890abcdef
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Then use in tests:
|
|
78
|
+
```javascript
|
|
79
|
+
const { testdriver } = await chrome(context, {
|
|
80
|
+
url: 'https://example.com'
|
|
81
|
+
// apiKey automatically read from process.env.TD_API_KEY
|
|
82
|
+
});
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Alternative: Direct in Code (Not Recommended)**
|
|
86
|
+
```javascript
|
|
87
|
+
const { testdriver } = await chrome(context, {
|
|
88
|
+
url: 'https://example.com',
|
|
89
|
+
apiKey: 'tdai-1234567890abcdef' // DON'T commit to version control!
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Provision Methods
|
|
96
|
+
|
|
97
|
+
TestDriver provides presets to automatically provision different application types.
|
|
98
|
+
|
|
99
|
+
### chrome() - Web Applications
|
|
100
|
+
|
|
101
|
+
Automatically launches Chrome browser and navigates to a URL.
|
|
102
|
+
|
|
103
|
+
```javascript
|
|
104
|
+
import { chrome } from 'testdriverai/presets';
|
|
105
|
+
|
|
106
|
+
test('web app test', async (context) => {
|
|
107
|
+
const { testdriver, dashcam } = await chrome(context, {
|
|
108
|
+
url: 'https://myapp.com',
|
|
109
|
+
maximized: true, // Start maximized (default: true)
|
|
110
|
+
guest: true, // Use incognito mode (default: true)
|
|
111
|
+
dashcam: true, // Enable recording (default: true)
|
|
112
|
+
os: 'linux', // OS: 'linux', 'mac', 'windows' (default: 'linux')
|
|
113
|
+
apiKey: process.env.TD_API_KEY
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// Your test code
|
|
117
|
+
});
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### vscode() - VS Code Extensions
|
|
121
|
+
|
|
122
|
+
Automatically launches VS Code with specified workspace and extensions.
|
|
123
|
+
|
|
124
|
+
```javascript
|
|
125
|
+
import { vscode } from 'testdriverai/presets';
|
|
126
|
+
|
|
127
|
+
test('vscode extension test', async (context) => {
|
|
128
|
+
const { testdriver, dashcam } = await vscode(context, {
|
|
129
|
+
workspace: '/tmp/test-project',
|
|
130
|
+
extensions: ['ms-python.python', 'dbaeumer.vscode-eslint'],
|
|
131
|
+
dashcam: true,
|
|
132
|
+
os: 'linux',
|
|
133
|
+
apiKey: process.env.TD_API_KEY
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// Your test code
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### electron() - Desktop Applications
|
|
141
|
+
|
|
142
|
+
Automatically launches Electron applications.
|
|
143
|
+
|
|
144
|
+
```javascript
|
|
145
|
+
import { electron } from 'testdriverai/presets';
|
|
146
|
+
|
|
147
|
+
test('electron app test', async (context) => {
|
|
148
|
+
const { app, dashcam } = await electron(context, {
|
|
149
|
+
appPath: './dist/my-app',
|
|
150
|
+
args: ['--debug'], // Additional CLI args (optional)
|
|
151
|
+
dashcam: true,
|
|
152
|
+
os: 'linux',
|
|
153
|
+
apiKey: process.env.TD_API_KEY
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// Your test code (app is alias for testdriver)
|
|
157
|
+
});
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Advanced: TestDriver() Hook + provision
|
|
161
|
+
|
|
162
|
+
For more control, use the direct API (recommended for v7.1+):
|
|
163
|
+
|
|
164
|
+
```javascript
|
|
165
|
+
import { TestDriver } from 'testdriverai/vitest/hooks';
|
|
166
|
+
|
|
167
|
+
test('my test', async (context) => {
|
|
168
|
+
const testdriver = TestDriver(context, {
|
|
169
|
+
apiKey: process.env.TD_API_KEY
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// Provision Chrome
|
|
173
|
+
await testdriver.provision.chrome({ url: 'https://example.com' });
|
|
174
|
+
|
|
175
|
+
// Or provision VS Code
|
|
176
|
+
await testdriver.provision.vscode({
|
|
177
|
+
workspace: '/tmp/project',
|
|
178
|
+
extensions: ['ms-python.python']
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// Or provision Electron
|
|
182
|
+
await testdriver.provision.electron({ appPath: './dist/app' });
|
|
183
|
+
|
|
184
|
+
// Your test code
|
|
185
|
+
});
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## Core API Methods
|
|
191
|
+
|
|
192
|
+
All methods use AI-powered natural language descriptions.
|
|
193
|
+
|
|
194
|
+
### find(description) - Locate Elements
|
|
195
|
+
|
|
196
|
+
Find a single element using natural language.
|
|
197
|
+
|
|
198
|
+
```javascript
|
|
199
|
+
// Basic finding
|
|
200
|
+
const button = await testdriver.find('submit button');
|
|
201
|
+
const input = await testdriver.find('email input field');
|
|
202
|
+
const link = await testdriver.find('Contact Us link');
|
|
203
|
+
|
|
204
|
+
// With context
|
|
205
|
+
const field = await testdriver.find('username input in the login form');
|
|
206
|
+
const deleteBtn = await testdriver.find('delete button in the top right corner');
|
|
207
|
+
|
|
208
|
+
// Chainable syntax (recommended)
|
|
209
|
+
await testdriver.find('submit button').click();
|
|
210
|
+
await testdriver.find('email input').type('user@example.com');
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
**Returns:** `Element` object with properties:
|
|
214
|
+
- `coordinates` - Position `{x, y, centerX, centerY}`
|
|
215
|
+
- `text` - Text content (if available)
|
|
216
|
+
- `confidence` - AI confidence score
|
|
217
|
+
- `screenshot` - Base64 screenshot (if available)
|
|
218
|
+
|
|
219
|
+
### findAll(description) - Find Multiple Elements
|
|
220
|
+
|
|
221
|
+
Find all matching elements.
|
|
222
|
+
|
|
223
|
+
```javascript
|
|
224
|
+
const items = await testdriver.findAll('product card');
|
|
225
|
+
console.log(`Found ${items.length} products`);
|
|
226
|
+
|
|
227
|
+
// Interact with each
|
|
228
|
+
for (const item of items) {
|
|
229
|
+
await item.click();
|
|
230
|
+
// Do something
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
**Returns:** `Array<Element>`
|
|
235
|
+
|
|
236
|
+
### click(x?, y?, action?) - Click Elements or Coordinates
|
|
237
|
+
|
|
238
|
+
```javascript
|
|
239
|
+
// Element click (chainable)
|
|
240
|
+
await testdriver.find('Login button').click();
|
|
241
|
+
|
|
242
|
+
// Coordinate click
|
|
243
|
+
await testdriver.click(500, 300);
|
|
244
|
+
|
|
245
|
+
// Different click types
|
|
246
|
+
await element.click(); // Regular click
|
|
247
|
+
await element.click('double-click'); // Double-click
|
|
248
|
+
await element.click('right-click'); // Right-click
|
|
249
|
+
await element.click('mouseDown'); // Press and hold
|
|
250
|
+
await element.click('mouseUp'); // Release
|
|
251
|
+
|
|
252
|
+
// Or use dedicated methods
|
|
253
|
+
await element.doubleClick();
|
|
254
|
+
await element.rightClick();
|
|
255
|
+
await element.mouseDown();
|
|
256
|
+
await element.mouseUp();
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### type(text, options) - Type Text
|
|
260
|
+
|
|
261
|
+
Type text into the currently focused input.
|
|
262
|
+
|
|
263
|
+
```javascript
|
|
264
|
+
// Basic typing
|
|
265
|
+
await testdriver.find('email input').click();
|
|
266
|
+
await testdriver.type('user@example.com');
|
|
267
|
+
|
|
268
|
+
// Type with delay between keystrokes
|
|
269
|
+
await testdriver.type('slow typing', { delay: 500 }); // 500ms between chars
|
|
270
|
+
|
|
271
|
+
// ⚠️ IMPORTANT: Always use secret: true for passwords and sensitive data
|
|
272
|
+
await testdriver.find('password input').click();
|
|
273
|
+
await testdriver.type('MySecureP@ssw0rd', { secret: true });
|
|
274
|
+
// This prevents logging in dashcam, debug info, and console
|
|
275
|
+
|
|
276
|
+
// Type numbers
|
|
277
|
+
await testdriver.type(12345);
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
**Options:**
|
|
281
|
+
- `delay` (number) - Delay between keystrokes in ms (default: 250)
|
|
282
|
+
- `secret` (boolean) - Mark as sensitive data, won't be logged (default: false)
|
|
283
|
+
|
|
284
|
+
### hover(x?, y?) - Hover Over Elements
|
|
285
|
+
|
|
286
|
+
```javascript
|
|
287
|
+
// Element hover
|
|
288
|
+
const menu = await testdriver.find('Products menu');
|
|
289
|
+
await menu.hover();
|
|
290
|
+
await testdriver.find('Laptops submenu item').click();
|
|
291
|
+
|
|
292
|
+
// Coordinate hover
|
|
293
|
+
await testdriver.hover(500, 300);
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### assert(assertion) - AI-Powered Assertions
|
|
297
|
+
|
|
298
|
+
Verify screen state using natural language.
|
|
299
|
+
|
|
300
|
+
```javascript
|
|
301
|
+
// Verify elements
|
|
302
|
+
await testdriver.assert('the login page is displayed');
|
|
303
|
+
await testdriver.assert('submit button is visible');
|
|
304
|
+
|
|
305
|
+
// Verify content
|
|
306
|
+
await testdriver.assert('the page title is "Welcome"');
|
|
307
|
+
await testdriver.assert('success message says "Account created"');
|
|
308
|
+
|
|
309
|
+
// Verify state
|
|
310
|
+
await testdriver.assert('the checkbox is checked');
|
|
311
|
+
await testdriver.assert('the form is empty');
|
|
312
|
+
|
|
313
|
+
// Verify visual appearance
|
|
314
|
+
await testdriver.assert('the button is blue');
|
|
315
|
+
await testdriver.assert('the loading spinner is displayed');
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
**Returns:** `Promise<boolean>` - `true` if passes, throws error if fails
|
|
319
|
+
|
|
320
|
+
### pressKeys(keys) - Keyboard Shortcuts
|
|
321
|
+
|
|
322
|
+
Press one or more keys simultaneously.
|
|
323
|
+
|
|
324
|
+
```javascript
|
|
325
|
+
// Navigation
|
|
326
|
+
await testdriver.pressKeys(['tab']);
|
|
327
|
+
await testdriver.pressKeys(['shift', 'tab']);
|
|
328
|
+
await testdriver.pressKeys(['enter']);
|
|
329
|
+
|
|
330
|
+
// Keyboard shortcuts
|
|
331
|
+
await testdriver.pressKeys(['ctrl', 'c']); // Copy
|
|
332
|
+
await testdriver.pressKeys(['ctrl', 'v']); // Paste
|
|
333
|
+
await testdriver.pressKeys(['ctrl', 's']); // Save
|
|
334
|
+
await testdriver.pressKeys(['ctrl', 'a']); // Select all
|
|
335
|
+
|
|
336
|
+
// Arrow keys
|
|
337
|
+
await testdriver.pressKeys(['up']);
|
|
338
|
+
await testdriver.pressKeys(['down']);
|
|
339
|
+
await testdriver.pressKeys(['left']);
|
|
340
|
+
await testdriver.pressKeys(['right']);
|
|
341
|
+
|
|
342
|
+
// Special keys
|
|
343
|
+
await testdriver.pressKeys(['escape']);
|
|
344
|
+
await testdriver.pressKeys(['backspace']);
|
|
345
|
+
await testdriver.pressKeys(['delete']);
|
|
346
|
+
await testdriver.pressKeys(['home']);
|
|
347
|
+
await testdriver.pressKeys(['end']);
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
### scroll(direction, amount, method) - Scroll Pages
|
|
351
|
+
|
|
352
|
+
```javascript
|
|
353
|
+
// Scroll down (default)
|
|
354
|
+
await testdriver.scroll();
|
|
355
|
+
await testdriver.scroll('down', 500);
|
|
356
|
+
|
|
357
|
+
// Scroll up
|
|
358
|
+
await testdriver.scroll('up', 300);
|
|
359
|
+
|
|
360
|
+
// Horizontal scrolling
|
|
361
|
+
await testdriver.scroll('right', 300);
|
|
362
|
+
await testdriver.scroll('left', 300);
|
|
363
|
+
|
|
364
|
+
// Scroll methods
|
|
365
|
+
await testdriver.scroll('down', 300, 'mouse'); // Mouse wheel (smooth)
|
|
366
|
+
await testdriver.scroll('down', 300, 'keyboard'); // Page Down key
|
|
367
|
+
|
|
368
|
+
// Scroll until text appears
|
|
369
|
+
await testdriver.scrollUntilText('Contact Us');
|
|
370
|
+
await testdriver.scrollUntilText('Footer', 'down', 5000);
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
### exec(language, code, timeout, silent) - Execute Code
|
|
374
|
+
|
|
375
|
+
Execute JavaScript or PowerShell in the sandbox.
|
|
376
|
+
|
|
377
|
+
```javascript
|
|
378
|
+
// JavaScript execution (in browser)
|
|
379
|
+
const title = await testdriver.exec('js', 'document.title', 5000);
|
|
380
|
+
|
|
381
|
+
await testdriver.exec('js', `
|
|
382
|
+
document.querySelector('#username').value = 'testuser';
|
|
383
|
+
`, 5000);
|
|
384
|
+
|
|
385
|
+
// Get element text
|
|
386
|
+
const text = await testdriver.exec('js', `
|
|
387
|
+
document.querySelector('.notification').textContent
|
|
388
|
+
`, 5000);
|
|
389
|
+
|
|
390
|
+
// PowerShell execution (Windows sandbox)
|
|
391
|
+
await testdriver.exec('pwsh', 'npm install -g http-server', 30000);
|
|
392
|
+
await testdriver.exec('pwsh', 'Start-Process notepad.exe', 5000);
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
### focusApplication(appName) - Switch Applications
|
|
396
|
+
|
|
397
|
+
Focus a different application window.
|
|
398
|
+
|
|
399
|
+
```javascript
|
|
400
|
+
await testdriver.focusApplication('Chrome');
|
|
401
|
+
await testdriver.focusApplication('VS Code');
|
|
402
|
+
await testdriver.focusApplication('Notepad');
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
---
|
|
406
|
+
|
|
407
|
+
## Reconnection and Debugging
|
|
408
|
+
|
|
409
|
+
### Reconnecting to a Sandbox
|
|
410
|
+
|
|
411
|
+
If a test fails or you need to try different selectors, you can reconnect to the same sandbox instance.
|
|
412
|
+
|
|
413
|
+
**Automatic Sandbox Tracking:** TestDriver automatically saves the last sandbox ID to `.testdriver/last-sandbox` in your project directory. You can use this to reconnect without manually tracking the ID.
|
|
414
|
+
|
|
415
|
+
**Manual Tracking:** You can also explicitly save the `sandboxId` from the initial connection:
|
|
416
|
+
|
|
417
|
+
```javascript
|
|
418
|
+
import { test } from 'vitest';
|
|
419
|
+
import { TestDriver } from 'testdriverai';
|
|
420
|
+
|
|
421
|
+
test('initial attempt', async () => {
|
|
422
|
+
const testdriver = new TestDriver({
|
|
423
|
+
apiKey: process.env.TD_API_KEY
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
const instance = await testdriver.connect();
|
|
427
|
+
console.log('Sandbox ID:', instance.instanceId);
|
|
428
|
+
// Output: Sandbox ID: i-0abc123def456789
|
|
429
|
+
|
|
430
|
+
// Your test code that might fail
|
|
431
|
+
try {
|
|
432
|
+
await testdriver.find('difficult selector').click();
|
|
433
|
+
} catch (error) {
|
|
434
|
+
console.error('Failed:', error);
|
|
435
|
+
// Don't disconnect yet - keep sandbox alive for debugging
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
test('reconnect to same sandbox', async () => {
|
|
440
|
+
const testdriver = new TestDriver({
|
|
441
|
+
apiKey: process.env.TD_API_KEY
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
// Option 1: Read from saved file
|
|
445
|
+
const lastSandboxId = testdriver.getLastSandboxId();
|
|
446
|
+
|
|
447
|
+
// Option 2: Use the ID from previous test (manual tracking)
|
|
448
|
+
// const sandboxId = 'i-0abc123def456789';
|
|
449
|
+
|
|
450
|
+
// Reconnect using the sandbox ID
|
|
451
|
+
await testdriver.connect({
|
|
452
|
+
sandboxId: lastSandboxId, // or use manually saved ID
|
|
453
|
+
newSandbox: false
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
// Try different selector on the same sandbox state
|
|
457
|
+
try {
|
|
458
|
+
await testdriver.find('alternative selector description').click();
|
|
459
|
+
} catch (error) {
|
|
460
|
+
console.error('Alternative also failed:', error);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
await testdriver.disconnect();
|
|
464
|
+
});
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
### Debugging Failed Finds
|
|
468
|
+
|
|
469
|
+
When `find()` fails, TestDriver provides detailed debug information:
|
|
470
|
+
|
|
471
|
+
```javascript
|
|
472
|
+
try {
|
|
473
|
+
await testdriver.find('non-existent button').click();
|
|
474
|
+
} catch (error) {
|
|
475
|
+
// error.name === 'ElementNotFoundError'
|
|
476
|
+
console.log('Element not found:', error.message);
|
|
477
|
+
console.log('Similarity score:', error.similarity); // How close the match was
|
|
478
|
+
console.log('Debug screenshot:', error.debugScreenshot); // Base64 image
|
|
479
|
+
console.log('Cache info:', error.cacheInfo); // Cache diagnostics
|
|
480
|
+
|
|
481
|
+
// Try alternative description
|
|
482
|
+
await testdriver.find('button with different description').click();
|
|
483
|
+
}
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
### Keep Sandbox Alive for Multiple Attempts
|
|
487
|
+
|
|
488
|
+
```javascript
|
|
489
|
+
test('iterative debugging', async (context) => {
|
|
490
|
+
const testdriver = new TestDriver({
|
|
491
|
+
apiKey: process.env.TD_API_KEY
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
const instance = await testdriver.connect();
|
|
495
|
+
const sandboxId = instance.instanceId;
|
|
496
|
+
|
|
497
|
+
// Try multiple selectors
|
|
498
|
+
const selectors = [
|
|
499
|
+
'submit button',
|
|
500
|
+
'blue submit button',
|
|
501
|
+
'submit button in bottom right',
|
|
502
|
+
'button with "Submit" text'
|
|
503
|
+
];
|
|
504
|
+
|
|
505
|
+
for (const selector of selectors) {
|
|
506
|
+
try {
|
|
507
|
+
console.log(`Trying: ${selector}`);
|
|
508
|
+
const element = await testdriver.find(selector);
|
|
509
|
+
console.log(`✓ Found with: ${selector}`);
|
|
510
|
+
await element.click();
|
|
511
|
+
break; // Success!
|
|
512
|
+
} catch (error) {
|
|
513
|
+
console.log(`✗ Failed with: ${selector}`);
|
|
514
|
+
console.log(` Similarity: ${error.similarity}`);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// Clean up
|
|
519
|
+
await testdriver.disconnect();
|
|
520
|
+
});
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
### Using Vitest Hooks for Reconnection
|
|
524
|
+
|
|
525
|
+
```javascript
|
|
526
|
+
import { test, beforeAll, afterAll } from 'vitest';
|
|
527
|
+
import { TestDriver } from 'testdriverai';
|
|
528
|
+
|
|
529
|
+
let testdriver;
|
|
530
|
+
let sandboxId;
|
|
531
|
+
|
|
532
|
+
beforeAll(async () => {
|
|
533
|
+
testdriver = new TestDriver({
|
|
534
|
+
apiKey: process.env.TD_API_KEY
|
|
535
|
+
});
|
|
536
|
+
const instance = await testdriver.connect();
|
|
537
|
+
sandboxId = instance.instanceId;
|
|
538
|
+
console.log('Sandbox started:', sandboxId);
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
afterAll(async () => {
|
|
542
|
+
await testdriver.disconnect();
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
test('first attempt', async () => {
|
|
546
|
+
await testdriver.find('selector v1').click();
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
test('second attempt - same sandbox', async () => {
|
|
550
|
+
// Continues in same sandbox session
|
|
551
|
+
await testdriver.find('selector v2').click();
|
|
552
|
+
});
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
---
|
|
556
|
+
|
|
557
|
+
## Best Practices
|
|
558
|
+
|
|
559
|
+
### 1. Use Descriptive Selectors
|
|
560
|
+
|
|
561
|
+
```javascript
|
|
562
|
+
// ❌ Too vague
|
|
563
|
+
await testdriver.find('button');
|
|
564
|
+
|
|
565
|
+
// ✅ Specific
|
|
566
|
+
await testdriver.find('blue submit button below the login form');
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
### 2. Always Use secret: true for Sensitive Data
|
|
570
|
+
|
|
571
|
+
```javascript
|
|
572
|
+
// ❌ Password will be logged
|
|
573
|
+
await testdriver.type('MyPassword123');
|
|
574
|
+
|
|
575
|
+
// ✅ Protected from logging
|
|
576
|
+
await testdriver.type('MyPassword123', { secret: true });
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
### 3. Prefer Chainable Syntax
|
|
580
|
+
|
|
581
|
+
```javascript
|
|
582
|
+
// ❌ More verbose
|
|
583
|
+
const button = await testdriver.find('submit button');
|
|
584
|
+
await button.click();
|
|
585
|
+
|
|
586
|
+
// ✅ Cleaner
|
|
587
|
+
await testdriver.find('submit button').click();
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
### 4. Use Environment Variables for API Keys
|
|
591
|
+
|
|
592
|
+
```javascript
|
|
593
|
+
// ❌ Hardcoded (security risk)
|
|
594
|
+
apiKey: 'tdai-1234567890abcdef'
|
|
595
|
+
|
|
596
|
+
// ✅ From environment
|
|
597
|
+
apiKey: process.env.TD_API_KEY
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
### 5. Set Appropriate Timeouts
|
|
601
|
+
|
|
602
|
+
```javascript
|
|
603
|
+
// vitest.config.mjs
|
|
604
|
+
export default defineConfig({
|
|
605
|
+
test: {
|
|
606
|
+
testTimeout: 120000, // 2 minutes for TestDriver tests
|
|
607
|
+
hookTimeout: 120000,
|
|
608
|
+
},
|
|
609
|
+
});
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
### 6. Handle Errors Gracefully
|
|
613
|
+
|
|
614
|
+
```javascript
|
|
615
|
+
try {
|
|
616
|
+
await testdriver.find('optional element').click();
|
|
617
|
+
} catch (error) {
|
|
618
|
+
console.log('Element not found, continuing...');
|
|
619
|
+
// Try alternative path
|
|
620
|
+
}
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
### 7. Use Assertions for Verification
|
|
624
|
+
|
|
625
|
+
```javascript
|
|
626
|
+
// After action, verify result
|
|
627
|
+
await testdriver.find('submit button').click();
|
|
628
|
+
await testdriver.assert('success message is displayed');
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
### 8. Clean Up Resources
|
|
632
|
+
|
|
633
|
+
```javascript
|
|
634
|
+
// Always disconnect when done
|
|
635
|
+
test('my test', async (context) => {
|
|
636
|
+
const { testdriver } = await chrome(context, { url: 'https://example.com' });
|
|
637
|
+
|
|
638
|
+
try {
|
|
639
|
+
// Your test code
|
|
640
|
+
} finally {
|
|
641
|
+
await testdriver.disconnect();
|
|
642
|
+
}
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
// Or use presets which auto-cleanup
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
### 9. Save Sandbox IDs for Debugging
|
|
649
|
+
|
|
650
|
+
```javascript
|
|
651
|
+
const instance = await testdriver.connect();
|
|
652
|
+
console.log('Sandbox ID for debugging:', instance.instanceId);
|
|
653
|
+
// Save this ID to reconnect later if test fails
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
### 10. Use findAll() for Lists
|
|
657
|
+
|
|
658
|
+
```javascript
|
|
659
|
+
// ❌ Finding one at a time
|
|
660
|
+
const item1 = await testdriver.find('first product card');
|
|
661
|
+
const item2 = await testdriver.find('second product card');
|
|
662
|
+
|
|
663
|
+
// ✅ Find all at once
|
|
664
|
+
const items = await testdriver.findAll('product card');
|
|
665
|
+
for (const item of items) {
|
|
666
|
+
await item.click();
|
|
667
|
+
}
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
---
|
|
671
|
+
|
|
672
|
+
## Common Patterns
|
|
673
|
+
|
|
674
|
+
### Login Flow
|
|
675
|
+
|
|
676
|
+
```javascript
|
|
677
|
+
test('user login', async (context) => {
|
|
678
|
+
const { testdriver } = await chrome(context, {
|
|
679
|
+
url: 'https://myapp.com/login'
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
await testdriver.find('email input').type('user@example.com');
|
|
683
|
+
await testdriver.find('password input').type('SecurePass123', { secret: true });
|
|
684
|
+
await testdriver.find('Login button').click();
|
|
685
|
+
|
|
686
|
+
await testdriver.assert('Welcome message is visible');
|
|
687
|
+
});
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
### Form Filling
|
|
691
|
+
|
|
692
|
+
```javascript
|
|
693
|
+
test('contact form', async (context) => {
|
|
694
|
+
const { testdriver } = await chrome(context, {
|
|
695
|
+
url: 'https://example.com/contact'
|
|
696
|
+
});
|
|
697
|
+
|
|
698
|
+
await testdriver.find('name input').type('John Doe');
|
|
699
|
+
await testdriver.find('email input').type('john@example.com');
|
|
700
|
+
await testdriver.find('message textarea').type('Hello, this is a test message.');
|
|
701
|
+
await testdriver.find('submit button').click();
|
|
702
|
+
|
|
703
|
+
await testdriver.assert('Thank you message appears');
|
|
704
|
+
});
|
|
705
|
+
```
|
|
706
|
+
|
|
707
|
+
### Navigation
|
|
708
|
+
|
|
709
|
+
```javascript
|
|
710
|
+
test('multi-page navigation', async (context) => {
|
|
711
|
+
const { testdriver } = await chrome(context, {
|
|
712
|
+
url: 'https://example.com'
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
await testdriver.find('About link').click();
|
|
716
|
+
await testdriver.assert('About page heading is visible');
|
|
717
|
+
|
|
718
|
+
await testdriver.find('Contact link').click();
|
|
719
|
+
await testdriver.assert('Contact form is displayed');
|
|
720
|
+
});
|
|
721
|
+
```
|
|
722
|
+
|
|
723
|
+
### Working with Dropdowns
|
|
724
|
+
|
|
725
|
+
```javascript
|
|
726
|
+
test('dropdown selection', async (context) => {
|
|
727
|
+
const { testdriver } = await chrome(context, {
|
|
728
|
+
url: 'https://example.com/form'
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
await testdriver.find('country dropdown').click();
|
|
732
|
+
await testdriver.find('United States option').click();
|
|
733
|
+
|
|
734
|
+
await testdriver.assert('United States is selected');
|
|
735
|
+
});
|
|
736
|
+
```
|
|
737
|
+
|
|
738
|
+
---
|
|
739
|
+
|
|
740
|
+
## Environment Variables
|
|
741
|
+
|
|
742
|
+
TestDriver supports these environment variables:
|
|
743
|
+
|
|
744
|
+
- `TD_API_KEY` - Your TestDriver API key (recommended)
|
|
745
|
+
- `TD_NO_CACHE` - Set to `"true"` to disable caching
|
|
746
|
+
- `DASHCAM_API_KEY` - Dashcam API key (usually same as TD_API_KEY)
|
|
747
|
+
- `TESTDRIVER_SANDBOX_ID` - Reuse an existing sandbox instance (see Sandbox Management below)
|
|
748
|
+
|
|
749
|
+
---
|
|
750
|
+
|
|
751
|
+
## Sandbox Management
|
|
752
|
+
|
|
753
|
+
TestDriver allows you to create long-running sandbox instances that can be reused across multiple test runs.
|
|
754
|
+
|
|
755
|
+
### Creating a Sandbox
|
|
756
|
+
|
|
757
|
+
Use the CLI to spawn a new sandbox:
|
|
758
|
+
|
|
759
|
+
```bash
|
|
760
|
+
# Spawn a Linux sandbox (default)
|
|
761
|
+
testdriver sandbox spawn
|
|
762
|
+
|
|
763
|
+
# Spawn with a 2-hour lifetime (in milliseconds)
|
|
764
|
+
testdriver sandbox spawn --timeout 7200000
|
|
765
|
+
|
|
766
|
+
# Spawn a Windows sandbox
|
|
767
|
+
testdriver sandbox spawn --os windows
|
|
768
|
+
|
|
769
|
+
# Spawn with custom instance type (requires permission)
|
|
770
|
+
testdriver sandbox spawn --instance-type c5.xlarge
|
|
771
|
+
```
|
|
772
|
+
|
|
773
|
+
The command will output the instance ID and instructions for using it.
|
|
774
|
+
|
|
775
|
+
### Listing Sandboxes
|
|
776
|
+
|
|
777
|
+
View all your team's sandboxes:
|
|
778
|
+
|
|
779
|
+
```bash
|
|
780
|
+
# List all sandboxes
|
|
781
|
+
testdriver sandbox list
|
|
782
|
+
|
|
783
|
+
# List only ready sandboxes
|
|
784
|
+
testdriver sandbox list --status ready
|
|
785
|
+
```
|
|
786
|
+
|
|
787
|
+
### Using a Sandbox in Tests
|
|
788
|
+
|
|
789
|
+
Set the `TESTDRIVER_SANDBOX_ID` environment variable to connect to an existing sandbox:
|
|
790
|
+
|
|
791
|
+
```bash
|
|
792
|
+
export TESTDRIVER_SANDBOX_ID=i-0abc123def456789
|
|
793
|
+
vitest
|
|
794
|
+
```
|
|
795
|
+
|
|
796
|
+
This will:
|
|
797
|
+
- Skip creating a new sandbox
|
|
798
|
+
- Connect to the specified sandbox instance
|
|
799
|
+
- Track which tests run on that sandbox
|
|
800
|
+
- Keep the sandbox alive after tests finish
|
|
801
|
+
|
|
802
|
+
### Stopping a Sandbox
|
|
803
|
+
|
|
804
|
+
When you're done with a sandbox, stop it to avoid unnecessary costs:
|
|
805
|
+
|
|
806
|
+
```bash
|
|
807
|
+
testdriver sandbox stop i-0abc123def456789
|
|
808
|
+
```
|
|
809
|
+
|
|
810
|
+
This will:
|
|
811
|
+
- Terminate the EC2 instance
|
|
812
|
+
- Calculate the total runtime and cost
|
|
813
|
+
- Send billing data to your team
|
|
814
|
+
|
|
815
|
+
### Sandbox Benefits
|
|
816
|
+
|
|
817
|
+
Using explicit sandboxes provides:
|
|
818
|
+
- **Faster test runs** - No sandbox creation time
|
|
819
|
+
- **Clear cost tracking** - Lifetime-based billing
|
|
820
|
+
- **Better debugging** - Keep sandbox alive to investigate failures
|
|
821
|
+
- **Team visibility** - See all active sandboxes in dashboard
|
|
822
|
+
|
|
823
|
+
---
|
|
824
|
+
|
|
825
|
+
## Resources
|
|
826
|
+
|
|
827
|
+
- **Documentation:** [docs.testdriver.ai](https://docs.testdriver.ai)
|
|
828
|
+
- **Dashboard:** [console.testdriver.ai](https://console.testdriver.ai)
|
|
829
|
+
- **API Reference:** Full method documentation in `/docs/v7/api/`
|
|
830
|
+
- **Examples:** See `/examples/` directory in the SDK package
|
|
831
|
+
|
|
832
|
+
---
|
|
833
|
+
|
|
834
|
+
## Quick Reference
|
|
835
|
+
|
|
836
|
+
| Method | Purpose | Example |
|
|
837
|
+
|--------|---------|---------|
|
|
838
|
+
| `find(desc)` | Locate single element | `await testdriver.find('submit button')` |
|
|
839
|
+
| `findAll(desc)` | Find all matching elements | `await testdriver.findAll('list item')` |
|
|
840
|
+
| `click()` | Click element/coordinates | `await element.click()` |
|
|
841
|
+
| `type(text, opts)` | Type text | `await testdriver.type('text', { secret: true })` |
|
|
842
|
+
| `hover()` | Hover over element | `await element.hover()` |
|
|
843
|
+
| `assert(text)` | Verify state | `await testdriver.assert('button is visible')` |
|
|
844
|
+
| `pressKeys(keys)` | Keyboard shortcuts | `await testdriver.pressKeys(['ctrl', 'c'])` |
|
|
845
|
+
| `scroll(dir, amt)` | Scroll page | `await testdriver.scroll('down', 500)` |
|
|
846
|
+
| `exec(lang, code)` | Execute code | `await testdriver.exec('js', 'document.title', 5000)` |
|
|
847
|
+
| `connect(opts)` | Connect to sandbox | `await testdriver.connect({ sandboxId: 'i-123' })` |
|
|
848
|
+
| `disconnect()` | Close connection | `await testdriver.disconnect()` |
|
|
849
|
+
|
|
850
|
+
---
|
|
851
|
+
|
|
852
|
+
**Last Updated:** December 2, 2025
|