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
package/AGENTS.md
ADDED
|
@@ -0,0 +1,550 @@
|
|
|
1
|
+
# TestDriver Agent Instructions
|
|
2
|
+
|
|
3
|
+
This guide teaches AI agents how to write modular, iteratively-debuggable Vitest tests using the TestDriver SDK.
|
|
4
|
+
|
|
5
|
+
## Core Principles
|
|
6
|
+
|
|
7
|
+
### 1. One Action Per Step
|
|
8
|
+
|
|
9
|
+
Each `it()` block performs exactly **ONE state-changing action** plus optional assertions.
|
|
10
|
+
|
|
11
|
+
**State-changing actions:**
|
|
12
|
+
- `click()` - clicking on elements
|
|
13
|
+
- `type()` - typing text
|
|
14
|
+
- `pressKeys()` - keyboard shortcuts
|
|
15
|
+
- `scroll()` - scrolling the page
|
|
16
|
+
|
|
17
|
+
**NOT state-changing (can combine with actions):**
|
|
18
|
+
- `find()` - locating elements
|
|
19
|
+
- `assert()` - verifying state
|
|
20
|
+
- `exists()` - checking element presence
|
|
21
|
+
- `screenshot()` - capturing screen
|
|
22
|
+
|
|
23
|
+
```javascript
|
|
24
|
+
// ✅ CORRECT: One action per step
|
|
25
|
+
it("step01: click the login button", async () => {
|
|
26
|
+
const button = await testdriver.find("Login button");
|
|
27
|
+
await button.click();
|
|
28
|
+
// Optional: verify the action worked
|
|
29
|
+
const form = await testdriver.find("Login form");
|
|
30
|
+
expect(form.exists()).toBe(true);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("step02: type username", async () => {
|
|
34
|
+
await testdriver.type("testuser");
|
|
35
|
+
await testdriver.assert("username field contains 'testuser'");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// ❌ WRONG: Multiple actions in one step
|
|
39
|
+
it("login flow", async () => {
|
|
40
|
+
await testdriver.find("Login").click();
|
|
41
|
+
await testdriver.type("user"); // second action!
|
|
42
|
+
await testdriver.type("password"); // third action!
|
|
43
|
+
await testdriver.find("Submit").click(); // fourth action!
|
|
44
|
+
});
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### 2. Step Naming Convention
|
|
48
|
+
|
|
49
|
+
Use zero-padded step numbers for proper sorting and easy `--testNamePattern` filtering:
|
|
50
|
+
|
|
51
|
+
```javascript
|
|
52
|
+
it("step01: open application", async () => { ... });
|
|
53
|
+
it("step02: click login button", async () => { ... });
|
|
54
|
+
it("step03: enter username", async () => { ... });
|
|
55
|
+
it("step04: enter password", async () => { ... });
|
|
56
|
+
it("step05: submit form", async () => { ... });
|
|
57
|
+
it("step06: verify dashboard loaded", async () => { ... });
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### 3. All Steps in One File
|
|
61
|
+
|
|
62
|
+
Write all steps for a test flow in the same file. This allows:
|
|
63
|
+
- Running the entire flow: `vitest path/to/test.test.js`
|
|
64
|
+
- Debugging a single step: `vitest --testNamePattern "step03"`
|
|
65
|
+
|
|
66
|
+
### 4. Optional Assertions
|
|
67
|
+
|
|
68
|
+
Assertions verify the action worked. Use them when appropriate:
|
|
69
|
+
|
|
70
|
+
```javascript
|
|
71
|
+
// After clicking - verify navigation or state change
|
|
72
|
+
it("step01: click submit button", async () => {
|
|
73
|
+
await testdriver.find("Submit button").click();
|
|
74
|
+
await testdriver.assert("form was submitted successfully");
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// After typing - verify text appeared (optional, typing is deterministic)
|
|
78
|
+
it("step02: type search query", async () => {
|
|
79
|
+
await testdriver.type("search term");
|
|
80
|
+
// assertion optional for typing
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Use exists() for element presence
|
|
84
|
+
it("step03: verify modal appeared", async () => {
|
|
85
|
+
const modal = await testdriver.find("Confirmation modal");
|
|
86
|
+
expect(modal.exists()).toBe(true);
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Test Structure
|
|
91
|
+
|
|
92
|
+
### Basic Template
|
|
93
|
+
|
|
94
|
+
```javascript
|
|
95
|
+
import { describe, it, expect } from "vitest";
|
|
96
|
+
import { TestDriver } from "testdriverai/vitest";
|
|
97
|
+
|
|
98
|
+
describe("Feature Name", () => {
|
|
99
|
+
it("step01: provision and first action", async (context) => {
|
|
100
|
+
const testdriver = TestDriver(context);
|
|
101
|
+
await testdriver.provision.chrome({ url: 'https://example.com' });
|
|
102
|
+
|
|
103
|
+
// First action + optional assertion
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("step02: second action", async (context) => {
|
|
107
|
+
const testdriver = TestDriver(context);
|
|
108
|
+
|
|
109
|
+
// action + optional assertion
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**Key Points:**
|
|
115
|
+
- Each test gets its own `testdriver` via `TestDriver(context)`
|
|
116
|
+
- Call `provision.chrome({ url })` in the first step to launch the browser
|
|
117
|
+
- Subsequent steps don't need to provision - the sandbox persists
|
|
118
|
+
|
|
119
|
+
## Shared Helpers
|
|
120
|
+
|
|
121
|
+
### Reusing Common Flows
|
|
122
|
+
|
|
123
|
+
Before writing steps for common flows (login, navigation, setup), check if a helper already exists.
|
|
124
|
+
|
|
125
|
+
**Helper Location:** `testdriver/helpers/`
|
|
126
|
+
|
|
127
|
+
### Creating a Helper
|
|
128
|
+
|
|
129
|
+
```javascript
|
|
130
|
+
// testdriver/helpers/login.js
|
|
131
|
+
export async function login(testdriver, username = "testuser", password = "password") {
|
|
132
|
+
const loginButton = await testdriver.find("Login button");
|
|
133
|
+
await loginButton.click();
|
|
134
|
+
|
|
135
|
+
await testdriver.type(username);
|
|
136
|
+
await testdriver.pressKeys(["Tab"]);
|
|
137
|
+
await testdriver.type(password);
|
|
138
|
+
|
|
139
|
+
const submitButton = await testdriver.find("Submit button");
|
|
140
|
+
await submitButton.click();
|
|
141
|
+
|
|
142
|
+
await testdriver.assert("user is logged in");
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Using a Helper
|
|
147
|
+
|
|
148
|
+
```javascript
|
|
149
|
+
import { describe, it, expect } from "vitest";
|
|
150
|
+
import { TestDriver } from "testdriverai/vitest";
|
|
151
|
+
import { login } from "./helpers/login.js";
|
|
152
|
+
|
|
153
|
+
describe("Dashboard Tests", () => {
|
|
154
|
+
it("step01: provision and login", async (context) => {
|
|
155
|
+
const testdriver = TestDriver(context);
|
|
156
|
+
await testdriver.provision.chrome({ url: 'https://example.com' });
|
|
157
|
+
await login(testdriver);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("step02: navigate to settings", async (context) => {
|
|
161
|
+
const testdriver = TestDriver(context);
|
|
162
|
+
const settingsLink = await testdriver.find("Settings link in sidebar");
|
|
163
|
+
await settingsLink.click();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// ... more steps
|
|
167
|
+
});
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### When to Create Helpers
|
|
171
|
+
|
|
172
|
+
Create a helper when:
|
|
173
|
+
- Flow is used in multiple tests (login, logout, navigation)
|
|
174
|
+
- Flow has 3+ steps that are always done together
|
|
175
|
+
- Flow requires specific credentials or configuration
|
|
176
|
+
|
|
177
|
+
## Iterative Development Workflow
|
|
178
|
+
|
|
179
|
+
**CRITICAL: Write and run tests ONE STEP AT A TIME.** Do not write the entire test file upfront. Build it incrementally.
|
|
180
|
+
|
|
181
|
+
### The Loop
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
┌─────────────────────────────────────────────────────────┐
|
|
185
|
+
│ 1. Write ONE step │
|
|
186
|
+
│ 2. Run that step: vitest --testNamePattern "stepNN" │
|
|
187
|
+
│ 3. Did it pass? │
|
|
188
|
+
│ ├─ YES → Go to step 1, write next step │
|
|
189
|
+
│ └─ NO → Fix the step, go to step 2 │
|
|
190
|
+
│ 4. When all steps pass, run entire file │
|
|
191
|
+
└─────────────────────────────────────────────────────────┘
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Detailed Workflow
|
|
195
|
+
|
|
196
|
+
#### Phase 1: Setup the Test File
|
|
197
|
+
|
|
198
|
+
Create the file with the describe block and NO steps yet:
|
|
199
|
+
|
|
200
|
+
```javascript
|
|
201
|
+
import { describe, it, expect } from "vitest";
|
|
202
|
+
import { TestDriver } from "testdriverai/vitest";
|
|
203
|
+
|
|
204
|
+
describe("My Feature Test", () => {
|
|
205
|
+
// Steps will be added one at a time below
|
|
206
|
+
});
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
#### Phase 2: Write Step 1
|
|
210
|
+
|
|
211
|
+
Add the first step with provisioning:
|
|
212
|
+
|
|
213
|
+
```javascript
|
|
214
|
+
it("step01: open app and click login", async (context) => {
|
|
215
|
+
const testdriver = TestDriver(context);
|
|
216
|
+
await testdriver.provision.chrome({ url: 'https://example.com/login' });
|
|
217
|
+
|
|
218
|
+
const button = await testdriver.find("Login button in the header");
|
|
219
|
+
await button.click();
|
|
220
|
+
});
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
#### Phase 3: Run Step 1
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
vitest --testNamePattern "step01" path/to/test.test.js
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
**If it passes:** Move to Phase 4.
|
|
230
|
+
|
|
231
|
+
**If it fails:**
|
|
232
|
+
- Check the error message
|
|
233
|
+
- Take a screenshot to see actual screen state
|
|
234
|
+
- Common fixes:
|
|
235
|
+
- Improve the `find()` description (be more specific)
|
|
236
|
+
- The element might not be visible yet (add scroll or wait)
|
|
237
|
+
- Wrong element clicked (check coordinates)
|
|
238
|
+
- Edit the step and re-run the same command
|
|
239
|
+
|
|
240
|
+
#### Phase 4: Write Step 2
|
|
241
|
+
|
|
242
|
+
Only after step 1 passes, add step 2:
|
|
243
|
+
|
|
244
|
+
```javascript
|
|
245
|
+
it("step02: type username", async (context) => {
|
|
246
|
+
const testdriver = TestDriver(context);
|
|
247
|
+
await testdriver.type("testuser");
|
|
248
|
+
});
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
#### Phase 5: Run Step 2
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
vitest --testNamePattern "step02" path/to/test.test.js
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
The sandbox persists from step 1, so the app is already in the right state.
|
|
258
|
+
|
|
259
|
+
**If it passes:** Continue to step 3.
|
|
260
|
+
|
|
261
|
+
**If it fails:** Fix and re-run step 2 only.
|
|
262
|
+
|
|
263
|
+
#### Phase 6: Repeat
|
|
264
|
+
|
|
265
|
+
Continue this pattern:
|
|
266
|
+
1. Write step N
|
|
267
|
+
2. Run step N
|
|
268
|
+
3. Fix until passing
|
|
269
|
+
4. Write step N+1
|
|
270
|
+
|
|
271
|
+
#### Phase 7: Run Full Test
|
|
272
|
+
|
|
273
|
+
When all steps pass individually:
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
vitest path/to/test.test.js
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
This runs all steps in sequence to verify the complete flow.
|
|
280
|
+
|
|
281
|
+
### Why One Step at a Time?
|
|
282
|
+
|
|
283
|
+
1. **Faster debugging** - If step 5 fails, you only re-run step 5, not steps 1-4
|
|
284
|
+
2. **Sandbox reuse** - The sandbox persists, so previous steps don't need to re-run
|
|
285
|
+
3. **Immediate feedback** - You see if each action works before moving on
|
|
286
|
+
4. **Easier fixes** - Smaller changes are easier to debug
|
|
287
|
+
|
|
288
|
+
### Example: Building a Test Incrementally
|
|
289
|
+
|
|
290
|
+
**Goal:** Test user login
|
|
291
|
+
|
|
292
|
+
**Iteration 1:** Write and run step01
|
|
293
|
+
```javascript
|
|
294
|
+
it("step01: provision and click login", async (context) => {
|
|
295
|
+
const testdriver = TestDriver(context);
|
|
296
|
+
await testdriver.provision.chrome({ url: 'https://example.com' });
|
|
297
|
+
|
|
298
|
+
const button = await testdriver.find("Login button");
|
|
299
|
+
await button.click();
|
|
300
|
+
});
|
|
301
|
+
```
|
|
302
|
+
```bash
|
|
303
|
+
vitest --testNamePattern "step01" login.test.js
|
|
304
|
+
# ✅ Passed
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
**Iteration 2:** Write and run step02
|
|
308
|
+
```javascript
|
|
309
|
+
it("step02: type username", async (context) => {
|
|
310
|
+
const testdriver = TestDriver(context);
|
|
311
|
+
await testdriver.type("admin");
|
|
312
|
+
});
|
|
313
|
+
```
|
|
314
|
+
```bash
|
|
315
|
+
vitest --testNamePattern "step02" login.test.js
|
|
316
|
+
# ❌ Failed - typing into wrong field
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
**Iteration 2b:** Fix step02 - need to click the field first
|
|
320
|
+
```javascript
|
|
321
|
+
it("step02: click username field", async (context) => {
|
|
322
|
+
const testdriver = TestDriver(context);
|
|
323
|
+
const field = await testdriver.find("Username input field");
|
|
324
|
+
await field.click();
|
|
325
|
+
});
|
|
326
|
+
```
|
|
327
|
+
```bash
|
|
328
|
+
vitest --testNamePattern "step02" login.test.js
|
|
329
|
+
# ✅ Passed
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
**Iteration 3:** Write and run step03
|
|
333
|
+
```javascript
|
|
334
|
+
it("step03: type username", async (context) => {
|
|
335
|
+
const testdriver = TestDriver(context);
|
|
336
|
+
await testdriver.type("admin");
|
|
337
|
+
});
|
|
338
|
+
```
|
|
339
|
+
```bash
|
|
340
|
+
vitest --testNamePattern "step03" login.test.js
|
|
341
|
+
# ✅ Passed
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
Continue until complete...
|
|
345
|
+
|
|
346
|
+
## Sandbox Persistence
|
|
347
|
+
|
|
348
|
+
TestDriver automatically persists sandbox connections:
|
|
349
|
+
|
|
350
|
+
- **Project-local:** `.testdriver-sandbox.json` in current directory
|
|
351
|
+
- **Timeout:** 10 minutes of inactivity
|
|
352
|
+
- **Auto-reconnect:** Automatically reuses recent sandbox on next run
|
|
353
|
+
|
|
354
|
+
During development:
|
|
355
|
+
- Don't call `disconnect()` in `afterAll`
|
|
356
|
+
- Sandbox stays alive between test runs
|
|
357
|
+
- If sandbox expires, a new one is created automatically
|
|
358
|
+
|
|
359
|
+
### One-Time Setup Steps
|
|
360
|
+
|
|
361
|
+
Use `it.once()` for steps that should only run once per sandbox session (app launch, provisioning):
|
|
362
|
+
|
|
363
|
+
```javascript
|
|
364
|
+
import { describe, it, beforeAll, expect } from "testdriverai/vitest";
|
|
365
|
+
import TestDriver from "testdriverai";
|
|
366
|
+
|
|
367
|
+
describe("My Test", () => {
|
|
368
|
+
let testdriver;
|
|
369
|
+
|
|
370
|
+
beforeAll(async () => {
|
|
371
|
+
testdriver = new TestDriver(process.env.TD_API_KEY);
|
|
372
|
+
await testdriver.connect();
|
|
373
|
+
|
|
374
|
+
// Store globally so it.once() can check isReconnected
|
|
375
|
+
globalThis.__testdriver = testdriver;
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
// Only runs once per sandbox session - skipped on reconnect
|
|
379
|
+
it.once("launch the application", async () => {
|
|
380
|
+
await testdriver.exec("sh", "google-chrome https://example.com", 5000);
|
|
381
|
+
await testdriver.assert("application is loaded");
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
// Always runs
|
|
385
|
+
it("click the login button", async () => {
|
|
386
|
+
const button = await testdriver.find("Login button");
|
|
387
|
+
await button.click();
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
// Always runs
|
|
391
|
+
it("type username", async () => {
|
|
392
|
+
await testdriver.type("testuser");
|
|
393
|
+
});
|
|
394
|
+
});
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
**When to use `it.once()`:**
|
|
398
|
+
- Launching the application
|
|
399
|
+
- Navigating to initial URL
|
|
400
|
+
- One-time provisioning steps
|
|
401
|
+
- Any step that sets up state that persists in the sandbox
|
|
402
|
+
|
|
403
|
+
**Regular `it()` steps:**
|
|
404
|
+
- All subsequent actions
|
|
405
|
+
- Steps you're actively developing/debugging
|
|
406
|
+
|
|
407
|
+
This lets you run the full file and one-time steps are automatically skipped on reconnect:
|
|
408
|
+
```bash
|
|
409
|
+
# First run: setup runs, then all steps
|
|
410
|
+
vitest path/to/test.test.js
|
|
411
|
+
|
|
412
|
+
# Second run (within 10 min): setup skipped, runs from first regular step
|
|
413
|
+
vitest path/to/test.test.js
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
**How it works:**
|
|
417
|
+
- The SDK sets `testdriver.isReconnected = true` when it reconnects to an existing sandbox
|
|
418
|
+
- `it.once()` checks `globalThis.__testdriver.isReconnected` to decide whether to skip
|
|
419
|
+
|
|
420
|
+
## SDK Quick Reference
|
|
421
|
+
|
|
422
|
+
### Finding Elements
|
|
423
|
+
|
|
424
|
+
```javascript
|
|
425
|
+
// Find returns an Element with coordinates
|
|
426
|
+
const element = await testdriver.find("description of element");
|
|
427
|
+
|
|
428
|
+
// Chain directly
|
|
429
|
+
await testdriver.find("Submit button").click();
|
|
430
|
+
|
|
431
|
+
// Check if found
|
|
432
|
+
if (element.exists()) { ... }
|
|
433
|
+
|
|
434
|
+
// Get coordinates
|
|
435
|
+
const { x, y, centerX, centerY } = element.getCoordinates();
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
### Actions
|
|
439
|
+
|
|
440
|
+
```javascript
|
|
441
|
+
// Click (on found element)
|
|
442
|
+
await element.click();
|
|
443
|
+
await element.click("double-click");
|
|
444
|
+
await element.click("right-click");
|
|
445
|
+
|
|
446
|
+
// Type text (into focused field)
|
|
447
|
+
await testdriver.type("text to type");
|
|
448
|
+
await testdriver.type("text", 100); // with delay between keys
|
|
449
|
+
|
|
450
|
+
// Keyboard shortcuts
|
|
451
|
+
await testdriver.pressKeys(["ctrl", "c"]);
|
|
452
|
+
await testdriver.pressKeys(["cmd", "shift", "p"]);
|
|
453
|
+
|
|
454
|
+
// Scroll
|
|
455
|
+
await testdriver.scroll("down", 300);
|
|
456
|
+
await testdriver.scroll("up", 500, "keyboard");
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
### Verification
|
|
460
|
+
|
|
461
|
+
```javascript
|
|
462
|
+
// AI-powered assertion (flexible, visual)
|
|
463
|
+
const passed = await testdriver.assert("the form was submitted successfully");
|
|
464
|
+
expect(passed).toBe(true);
|
|
465
|
+
|
|
466
|
+
// Element existence
|
|
467
|
+
const element = await testdriver.find("Success message");
|
|
468
|
+
expect(element.exists()).toBe(true);
|
|
469
|
+
|
|
470
|
+
// Screenshot for debugging
|
|
471
|
+
const screenshot = await testdriver.screenshot();
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
### Remember (Extract Values)
|
|
475
|
+
|
|
476
|
+
```javascript
|
|
477
|
+
// Extract dynamic values from screen
|
|
478
|
+
const orderNumber = await testdriver.remember("the order confirmation number");
|
|
479
|
+
console.log("Order:", orderNumber);
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
## Example: Complete Test File
|
|
483
|
+
|
|
484
|
+
```javascript
|
|
485
|
+
import { describe, it, expect } from "vitest";
|
|
486
|
+
import { TestDriver } from "testdriverai/vitest";
|
|
487
|
+
|
|
488
|
+
describe("User Registration", () => {
|
|
489
|
+
it("step01: provision and navigate to registration", async (context) => {
|
|
490
|
+
const testdriver = TestDriver(context);
|
|
491
|
+
await testdriver.provision.chrome({ url: 'https://example.com' });
|
|
492
|
+
|
|
493
|
+
const signupLink = await testdriver.find("Sign Up link in navigation");
|
|
494
|
+
await signupLink.click();
|
|
495
|
+
await testdriver.assert("registration form is visible");
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
it("step02: enter email address", async (context) => {
|
|
499
|
+
const testdriver = TestDriver(context);
|
|
500
|
+
const emailField = await testdriver.find("Email input field");
|
|
501
|
+
await emailField.click();
|
|
502
|
+
await testdriver.type("test@example.com");
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
it("step03: enter password", async (context) => {
|
|
506
|
+
const testdriver = TestDriver(context);
|
|
507
|
+
const passwordField = await testdriver.find("Password input field");
|
|
508
|
+
await passwordField.click();
|
|
509
|
+
await testdriver.type("SecurePass123!");
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
it("step04: confirm password", async (context) => {
|
|
513
|
+
const testdriver = TestDriver(context);
|
|
514
|
+
const confirmField = await testdriver.find("Confirm password field");
|
|
515
|
+
await confirmField.click();
|
|
516
|
+
await testdriver.type("SecurePass123!");
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
it("step05: accept terms and conditions", async (context) => {
|
|
520
|
+
const testdriver = TestDriver(context);
|
|
521
|
+
const checkbox = await testdriver.find("Terms and conditions checkbox");
|
|
522
|
+
await checkbox.click();
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
it("step06: submit registration", async (context) => {
|
|
526
|
+
const testdriver = TestDriver(context);
|
|
527
|
+
const submitButton = await testdriver.find("Create Account button");
|
|
528
|
+
await submitButton.click();
|
|
529
|
+
await testdriver.assert("account was created successfully");
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
it("step07: verify welcome message", async (context) => {
|
|
533
|
+
const testdriver = TestDriver(context);
|
|
534
|
+
const welcome = await testdriver.find("Welcome message");
|
|
535
|
+
expect(welcome.exists()).toBe(true);
|
|
536
|
+
});
|
|
537
|
+
});
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
## Tips for AI Agents
|
|
541
|
+
|
|
542
|
+
1. **Write ONE step at a time** - Never write the full test upfront. Write step 1, run it, fix it, then write step 2.
|
|
543
|
+
2. **Run after each step** - Use `vitest --testNamePattern "stepNN"` to test each step immediately
|
|
544
|
+
3. **Search before writing** - Check `testdriver/helpers/` for existing flows
|
|
545
|
+
4. **Descriptive find()** - Include location, color, text: "blue Submit button below the form"
|
|
546
|
+
5. **One action at a time** - Easier to debug when things fail
|
|
547
|
+
6. **Assert after important actions** - Especially navigation and form submissions
|
|
548
|
+
7. **Use step numbers** - Makes `--testNamePattern` filtering easy
|
|
549
|
+
8. **Don't disconnect during dev** - Let sandbox persist for faster iteration
|
|
550
|
+
9. **Check screenshots** - When steps fail, look at actual screen state
|
package/CODEOWNERS
CHANGED
package/README.md
CHANGED
|
@@ -10,6 +10,71 @@ Automate and scale QA with computer-use agents.
|
|
|
10
10
|
|
|
11
11
|
[Follow the instructions on our docs for more.](https://docs.testdriver.ai/overview/quickstart).
|
|
12
12
|
|
|
13
|
+
## v7 SDK - Progressive Disclosure
|
|
14
|
+
|
|
15
|
+
TestDriver v7 introduces **three levels of API** to match your experience level:
|
|
16
|
+
|
|
17
|
+
### 🟢 Beginner: Presets (Zero Config)
|
|
18
|
+
|
|
19
|
+
```javascript
|
|
20
|
+
import { test } from 'vitest';
|
|
21
|
+
import { chromePreset } from 'testdriverai/presets';
|
|
22
|
+
|
|
23
|
+
test('login test', async (context) => {
|
|
24
|
+
const { client } = await chromePreset(context, {
|
|
25
|
+
url: 'https://myapp.com'
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
await client.find('Login button').click();
|
|
29
|
+
});
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**Built-in presets:** Chrome, VS Code, Electron, and create your own!
|
|
33
|
+
|
|
34
|
+
### 🟡 Intermediate: Hooks (Flexible)
|
|
35
|
+
|
|
36
|
+
```javascript
|
|
37
|
+
import { test } from 'vitest';
|
|
38
|
+
import { useTestDriver, useDashcam } from 'testdriverai/vitest/hooks';
|
|
39
|
+
|
|
40
|
+
test('my test', async (context) => {
|
|
41
|
+
const client = useTestDriver(context, { os: 'linux' });
|
|
42
|
+
const dashcam = useDashcam(context, client, {
|
|
43
|
+
autoStart: true,
|
|
44
|
+
autoStop: true
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
await client.find('button').click();
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Automatic lifecycle management** - no more forgetting cleanup!
|
|
52
|
+
|
|
53
|
+
### 🔴 Advanced: Core Classes (Full Control)
|
|
54
|
+
|
|
55
|
+
```javascript
|
|
56
|
+
import { test } from 'vitest';
|
|
57
|
+
import { TestDriver, Dashcam } from 'testdriverai/core';
|
|
58
|
+
|
|
59
|
+
test('my test', async () => {
|
|
60
|
+
const client = new TestDriver(apiKey, { os: 'linux' });
|
|
61
|
+
const dashcam = new Dashcam(client);
|
|
62
|
+
|
|
63
|
+
await client.auth();
|
|
64
|
+
await client.connect();
|
|
65
|
+
await dashcam.start();
|
|
66
|
+
|
|
67
|
+
await client.find('button').click();
|
|
68
|
+
|
|
69
|
+
await dashcam.stop();
|
|
70
|
+
await client.disconnect();
|
|
71
|
+
});
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**Full manual control** for advanced scenarios.
|
|
75
|
+
|
|
76
|
+
📖 **Learn more:** [MIGRATION.md](./docs/MIGRATION.md) | [PRESETS.md](./docs/PRESETS.md) | [HOOKS.md](./docs/HOOKS.md)
|
|
77
|
+
|
|
13
78
|
# About
|
|
14
79
|
|
|
15
80
|
TestDriver isn't like any test framework you've used before. TestDriver is an OS Agent for QA. TestDriver uses AI vision along with mouse and keyboard emulation to control the entire desktop. It's more like a QA employee than a test framework. This kind of black-box testing has some major advantages:
|
|
@@ -186,3 +251,64 @@ You can also set the default test file path using environment variables:
|
|
|
186
251
|
export TD_DEFAULT_TEST_FILE="custom/path/test.yaml"
|
|
187
252
|
node your-script.js
|
|
188
253
|
```
|
|
254
|
+
|
|
255
|
+
## MCP Server for AI Agents
|
|
256
|
+
|
|
257
|
+
TestDriver includes a Model Context Protocol (MCP) server that enables AI agents to **interactively create Vitest test files**.
|
|
258
|
+
|
|
259
|
+
### How It Works
|
|
260
|
+
|
|
261
|
+
1. **AI agent connects** to a persistent TestDriver sandbox
|
|
262
|
+
2. **User describes** what they want to test
|
|
263
|
+
3. **AI explores** the application using TestDriver commands
|
|
264
|
+
4. **AI generates** Vitest test code from successful interactions
|
|
265
|
+
|
|
266
|
+
### Quick Start
|
|
267
|
+
|
|
268
|
+
```bash
|
|
269
|
+
cd mcp-server
|
|
270
|
+
npm install && npm run build
|
|
271
|
+
npm run deploy # Install to ~/.mcp/testdriver
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Configuration
|
|
275
|
+
|
|
276
|
+
Add to your MCP client configuration:
|
|
277
|
+
|
|
278
|
+
```json
|
|
279
|
+
{
|
|
280
|
+
"servers": {
|
|
281
|
+
"testdriverai": {
|
|
282
|
+
"type": "stdio",
|
|
283
|
+
"command": "node",
|
|
284
|
+
"args": ["/path/to/cli/mcp-server/dist/index.js"]
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### Example Workflow
|
|
291
|
+
|
|
292
|
+
```
|
|
293
|
+
User: "Create a test that logs into the app"
|
|
294
|
+
|
|
295
|
+
AI: [Connects to sandbox with user's API key]
|
|
296
|
+
AI: [Takes screenshot to see login page]
|
|
297
|
+
AI: [Finds username field: await testdriver_find({ description: "username field" })]
|
|
298
|
+
AI: [Clicks and types: await testdriver_type({ text: "test_user" })]
|
|
299
|
+
AI: [Finds password field, enters password]
|
|
300
|
+
AI: [Clicks login button]
|
|
301
|
+
AI: [Asserts login succeeded]
|
|
302
|
+
AI: [Generates Vitest test file from these steps]
|
|
303
|
+
AI: [Saves test/login.test.mjs]
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Key Features
|
|
307
|
+
|
|
308
|
+
- **Persistent sandbox** - Connection stays alive throughout test creation
|
|
309
|
+
- **Live debugger URL** - User can watch the AI test in real-time
|
|
310
|
+
- **Full SDK access** - All v7 SDK methods available
|
|
311
|
+
- **Code generation** - AI translates interactions into proper Vitest code
|
|
312
|
+
|
|
313
|
+
See [mcp-server/TEST_CREATION_GUIDE.md](mcp-server/TEST_CREATION_GUIDE.md) for the complete guide.
|
|
314
|
+
|
|
@@ -37,11 +37,11 @@ steps:
|
|
|
37
37
|
- command: hover-text
|
|
38
38
|
text: New Text Document
|
|
39
39
|
description: new text document icon in the center of the desktop
|
|
40
|
-
action:
|
|
40
|
+
action: mouseDown
|
|
41
41
|
- command: hover-text
|
|
42
42
|
text: Recycle Bin
|
|
43
43
|
description: recycle bin icon in the top left corner of the desktop
|
|
44
|
-
action:
|
|
44
|
+
action: mouseUp
|
|
45
45
|
|
|
46
46
|
- prompt: '"New Text Document" icon is not on the Desktop'
|
|
47
47
|
commands:
|
|
File without changes
|