testdriverai 6.2.2 → 7.0.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/MIGRATION.md +389 -0
- package/PLUGIN_MIGRATION.md +222 -0
- package/PROMPT_CACHE.md +200 -0
- package/SDK_LOGGING.md +222 -0
- package/SDK_MIGRATION.md +474 -0
- package/SDK_README.md +1122 -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 +258 -68
- 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 +143 -188
- package/agent/lib/redraw.js +6 -3
- package/agent/lib/sandbox.js +19 -5
- package/agent/lib/sdk.js +1 -0
- package/agent/lib/system.js +0 -3
- package/agent/lib/validation.js +1 -7
- package/debug-locate-response.js +82 -0
- package/debug-screenshot-1763401388589.png +0 -0
- package/debugger/index.html +15 -4
- package/docs/ARCHITECTURE.md +424 -0
- package/docs/AWESOME_LOGS_QUICK_REF.md +100 -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 +232 -152
- 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/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/pressKeys.mdx +349 -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/quickstart.mdx +199 -0
- package/docs/v7/guides/migration.mdx +562 -0
- package/docs/{getting-started → v7/guides}/self-hosting.mdx +11 -12
- package/docs/v7/playwright.mdx +342 -0
- package/eslint.config.js +19 -1
- 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/lib/base.js +10 -4
- package/interfaces/logger.js +2 -1
- package/interfaces/shared-test-state.mjs +69 -0
- package/interfaces/vitest-plugin.mjs +744 -0
- package/mcp-server/AI_GUIDELINES.md +57 -0
- package/package.json +18 -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 +735 -0
- package/sdk.js +1906 -0
- package/{.github/workflows/self-hosted.yml → self-hosted.yml} +13 -4
- package/setup/aws/cloudformation.yaml +9 -2
- package/test/mcp-example-test.yaml +27 -0
- package/test-find-api.js +73 -0
- package/test-prompt-cache.js +96 -0
- package/test-sandbox-render.js +28 -0
- package/test-sdk-methods.js +15 -0
- package/test-sdk-refactor.js +53 -0
- package/test-stack-trace.mjs +57 -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 +44 -0
- package/testdriver/acceptance-sdk/drag-and-drop.test.mjs +70 -0
- package/testdriver/acceptance-sdk/element-not-found.test.mjs +38 -0
- package/testdriver/acceptance-sdk/exec-js.test.mjs +55 -0
- package/testdriver/acceptance-sdk/exec-output.test.mjs +71 -0
- package/testdriver/acceptance-sdk/exec-pwsh.test.mjs +69 -0
- package/testdriver/acceptance-sdk/focus-window.test.mjs +48 -0
- package/testdriver/acceptance-sdk/formatted-logging.test.mjs +41 -0
- package/testdriver/acceptance-sdk/hover-image.test.mjs +43 -0
- package/testdriver/acceptance-sdk/hover-text-with-description.test.mjs +50 -0
- package/testdriver/acceptance-sdk/hover-text.test.mjs +41 -0
- package/testdriver/acceptance-sdk/match-image.test.mjs +48 -0
- package/testdriver/acceptance-sdk/press-keys.test.mjs +64 -0
- package/testdriver/acceptance-sdk/prompt.test.mjs +45 -0
- package/testdriver/acceptance-sdk/scroll-keyboard.test.mjs +52 -0
- package/testdriver/acceptance-sdk/scroll-until-image.test.mjs +51 -0
- package/testdriver/acceptance-sdk/scroll-until-text.test.mjs +42 -0
- package/testdriver/acceptance-sdk/scroll.test.mjs +50 -0
- package/testdriver/acceptance-sdk/setup/globalTeardown.mjs +11 -0
- package/testdriver/acceptance-sdk/setup/lifecycleHelpers.mjs +239 -0
- package/testdriver/acceptance-sdk/setup/testHelpers.mjs +648 -0
- package/testdriver/acceptance-sdk/setup/vitestSetup.mjs +40 -0
- package/testdriver/acceptance-sdk/type-checking-demo.js +49 -0
- package/testdriver/acceptance-sdk/type.test.mjs +84 -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 +65 -0
- package/vitest.config.mjs.bak +44 -0
- package/.github/workflows/acceptance-v6.yml +0 -169
- 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/SDK_README.md
ADDED
|
@@ -0,0 +1,1122 @@
|
|
|
1
|
+
# TestDriver SDK
|
|
2
|
+
|
|
3
|
+
The TestDriver SDK provides programmatic access to TestDriver's AI-powered testing capabilities. Use it to automate UI testing for web and desktop applications with natural language commands.
|
|
4
|
+
|
|
5
|
+
## ✨ New: AWESOME Logs with Great DX!
|
|
6
|
+
|
|
7
|
+
Your SDK now has **beautiful, emoji-rich logging** that makes test output a joy to read! 🎨
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
[2.34s] 🔍 Found "submit button" · 📍 (682, 189) · ⏱️ 167ms · ⚡ cached
|
|
11
|
+
[2.51s] 👆 Click "submit button"
|
|
12
|
+
[2.89s] ⌨️ Type → hello world
|
|
13
|
+
[3.12s] ✅ Assert "page correct" · ✓ PASSED · ⏱️ 45ms
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
**Features:**
|
|
17
|
+
|
|
18
|
+
- 🎨 Rich emojis for all actions (find, click, type, scroll, etc.)
|
|
19
|
+
- ⚡ Cache hit/miss indicators
|
|
20
|
+
- ⏱️ Color-coded performance timing (green < 100ms, yellow < 500ms, red > 500ms)
|
|
21
|
+
- 📍 Coordinate display for found elements
|
|
22
|
+
- 📊 Beautiful progress bars and summaries
|
|
23
|
+
|
|
24
|
+
See [docs/AWESOME_LOGS_QUICK_REF.md](./docs/AWESOME_LOGS_QUICK_REF.md) for quick reference or [docs/SDK_AWESOME_LOGS.md](./docs/SDK_AWESOME_LOGS.md) for complete documentation.
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm install testdriverai
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Quick Start
|
|
33
|
+
|
|
34
|
+
```javascript
|
|
35
|
+
const TestDriver = require("testdriverai");
|
|
36
|
+
|
|
37
|
+
async function runTest() {
|
|
38
|
+
// Initialize SDK with your API key
|
|
39
|
+
const client = new TestDriver(process.env.TD_API_KEY);
|
|
40
|
+
|
|
41
|
+
// Authenticate and connect to a sandbox
|
|
42
|
+
await client.auth();
|
|
43
|
+
await client.connect();
|
|
44
|
+
|
|
45
|
+
// Use the new find() API
|
|
46
|
+
await client.focusApplication("Google Chrome");
|
|
47
|
+
|
|
48
|
+
const searchBox = await client.find("search box").find();
|
|
49
|
+
await searchBox.click();
|
|
50
|
+
await client.type("testdriver.ai");
|
|
51
|
+
await client.pressKeys(["enter"]);
|
|
52
|
+
|
|
53
|
+
// Poll for element to appear
|
|
54
|
+
let result = client.find("TestDriver heading");
|
|
55
|
+
while (!result.found()) {
|
|
56
|
+
result = await result.find();
|
|
57
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Clean up
|
|
61
|
+
await client.disconnect();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
runTest();
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## New Element Finding API ✨
|
|
68
|
+
|
|
69
|
+
We've introduced a new `find()` API that provides better control over element finding and interaction. See [SDK_MIGRATION.md](./SDK_MIGRATION.md) for full migration guide.
|
|
70
|
+
|
|
71
|
+
### Basic Usage
|
|
72
|
+
|
|
73
|
+
```javascript
|
|
74
|
+
// Find and click an element
|
|
75
|
+
const button = await client.find(
|
|
76
|
+
"the sign in button, black button below password",
|
|
77
|
+
);
|
|
78
|
+
await button.click();
|
|
79
|
+
|
|
80
|
+
// Check if element exists
|
|
81
|
+
if (button.found()) {
|
|
82
|
+
console.log("Button coordinates:", button.getCoordinates());
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Polling Pattern
|
|
87
|
+
|
|
88
|
+
```javascript
|
|
89
|
+
// Wait for element to appear
|
|
90
|
+
let element;
|
|
91
|
+
while (!element?.found()) {
|
|
92
|
+
console.log("waiting for element...");
|
|
93
|
+
element = await client.find("login button");
|
|
94
|
+
if (!element.found()) {
|
|
95
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
await element.click();
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Different Actions
|
|
102
|
+
|
|
103
|
+
```javascript
|
|
104
|
+
const menu = await client.find("File menu").find();
|
|
105
|
+
await menu.hover();
|
|
106
|
+
await menu.rightClick();
|
|
107
|
+
await menu.doubleClick();
|
|
108
|
+
|
|
109
|
+
// Or use the generic click() method with action parameter
|
|
110
|
+
await menu.click("right-click");
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Drag and Drop
|
|
114
|
+
|
|
115
|
+
```javascript
|
|
116
|
+
const source = await client.find("draggable item");
|
|
117
|
+
await source.mouseDown();
|
|
118
|
+
|
|
119
|
+
const target = await client.find("drop zone");
|
|
120
|
+
await target.mouseUp();
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## API Reference
|
|
124
|
+
|
|
125
|
+
### Element Class
|
|
126
|
+
|
|
127
|
+
The `Element` class represents an element found on screen and provides methods for interacting with it.
|
|
128
|
+
|
|
129
|
+
#### Creating Elements
|
|
130
|
+
|
|
131
|
+
##### `client.find(description)`
|
|
132
|
+
|
|
133
|
+
Creates a new Element instance and immediately attempts to locate it.
|
|
134
|
+
|
|
135
|
+
**Parameters:**
|
|
136
|
+
|
|
137
|
+
- `description` (string): Natural language description of the element
|
|
138
|
+
|
|
139
|
+
**Returns:** `Promise<Element>` - Element instance (already located)
|
|
140
|
+
|
|
141
|
+
**Example:**
|
|
142
|
+
|
|
143
|
+
```javascript
|
|
144
|
+
const button = await client.find("the sign in button");
|
|
145
|
+
// Element is automatically located
|
|
146
|
+
if (button.found()) {
|
|
147
|
+
await button.click();
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
#### Element Methods
|
|
152
|
+
|
|
153
|
+
##### `element.find([newDescription])`
|
|
154
|
+
|
|
155
|
+
Locates (or relocates) the element on screen.
|
|
156
|
+
|
|
157
|
+
**Parameters:**
|
|
158
|
+
|
|
159
|
+
- `newDescription` (string, optional): New description to search for
|
|
160
|
+
|
|
161
|
+
**Returns:** `Promise<Element>` - The same Element instance
|
|
162
|
+
|
|
163
|
+
**Example:**
|
|
164
|
+
|
|
165
|
+
```javascript
|
|
166
|
+
// Re-find the same element
|
|
167
|
+
await element.find();
|
|
168
|
+
|
|
169
|
+
// Find with a new description
|
|
170
|
+
await element.find("submit button");
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
##### `element.found()`
|
|
174
|
+
|
|
175
|
+
Check if the element was successfully located.
|
|
176
|
+
|
|
177
|
+
**Returns:** `boolean` - true if element was found
|
|
178
|
+
|
|
179
|
+
**Example:**
|
|
180
|
+
|
|
181
|
+
```javascript
|
|
182
|
+
if (element.found()) {
|
|
183
|
+
console.log("Element located!");
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
##### `element.click([action])`
|
|
188
|
+
|
|
189
|
+
Click on the element.
|
|
190
|
+
|
|
191
|
+
**Parameters:**
|
|
192
|
+
|
|
193
|
+
- `action` (string, optional): Click type - `'click'`, `'right-click'`, `'double-click'`, `'mouseDown'`, `'mouseUp'` (default: `'click'`)
|
|
194
|
+
|
|
195
|
+
**Returns:** `Promise<void>`
|
|
196
|
+
|
|
197
|
+
**Example:**
|
|
198
|
+
|
|
199
|
+
```javascript
|
|
200
|
+
await element.click();
|
|
201
|
+
await element.click("right-click");
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
##### `element.hover()`
|
|
205
|
+
|
|
206
|
+
Hover the mouse over the element.
|
|
207
|
+
|
|
208
|
+
**Returns:** `Promise<void>`
|
|
209
|
+
|
|
210
|
+
**Example:**
|
|
211
|
+
|
|
212
|
+
```javascript
|
|
213
|
+
await element.hover();
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
##### `element.doubleClick()`
|
|
217
|
+
|
|
218
|
+
Double-click on the element. Convenience method for `element.click('double-click')`.
|
|
219
|
+
|
|
220
|
+
**Returns:** `Promise<void>`
|
|
221
|
+
|
|
222
|
+
**Example:**
|
|
223
|
+
|
|
224
|
+
```javascript
|
|
225
|
+
await element.doubleClick();
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
##### `element.rightClick()`
|
|
229
|
+
|
|
230
|
+
Right-click on the element. Convenience method for `element.click('right-click')`.
|
|
231
|
+
|
|
232
|
+
**Returns:** `Promise<void>`
|
|
233
|
+
|
|
234
|
+
**Example:**
|
|
235
|
+
|
|
236
|
+
```javascript
|
|
237
|
+
await element.rightClick();
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
##### `element.mouseDown()`
|
|
241
|
+
|
|
242
|
+
Press the mouse button down on the element (useful for drag operations).
|
|
243
|
+
|
|
244
|
+
**Returns:** `Promise<void>`
|
|
245
|
+
|
|
246
|
+
**Example:**
|
|
247
|
+
|
|
248
|
+
```javascript
|
|
249
|
+
const draggable = await client.find("item to drag");
|
|
250
|
+
await draggable.mouseDown();
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
##### `element.mouseUp()`
|
|
254
|
+
|
|
255
|
+
Release the mouse button on the element (useful for drag operations).
|
|
256
|
+
|
|
257
|
+
**Returns:** `Promise<void>`
|
|
258
|
+
|
|
259
|
+
**Example:**
|
|
260
|
+
|
|
261
|
+
```javascript
|
|
262
|
+
const dropZone = await client.find("drop target");
|
|
263
|
+
await dropZone.mouseUp();
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
##### `element.getCoordinates()`
|
|
267
|
+
|
|
268
|
+
Get the screen coordinates of the element.
|
|
269
|
+
|
|
270
|
+
**Returns:** `{x, y, centerX, centerY} | null` - Coordinates object or null if not found
|
|
271
|
+
|
|
272
|
+
**Example:**
|
|
273
|
+
|
|
274
|
+
```javascript
|
|
275
|
+
const coords = element.getCoordinates();
|
|
276
|
+
if (coords) {
|
|
277
|
+
console.log(`Position: ${coords.x}, ${coords.y}`);
|
|
278
|
+
console.log(`Center: ${coords.centerX}, ${coords.centerY}`);
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
##### `element.getResponse()`
|
|
283
|
+
|
|
284
|
+
Get the full API response data from the locate operation.
|
|
285
|
+
|
|
286
|
+
**Returns:** `Object | null` - Full response with all available data
|
|
287
|
+
|
|
288
|
+
**Example:**
|
|
289
|
+
|
|
290
|
+
```javascript
|
|
291
|
+
const response = element.getResponse();
|
|
292
|
+
console.log("Full response:", response);
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
#### Element Properties
|
|
296
|
+
|
|
297
|
+
Elements expose many read-only properties from the API response:
|
|
298
|
+
|
|
299
|
+
##### Coordinate Properties
|
|
300
|
+
|
|
301
|
+
- `element.x` - X coordinate (top-left corner) or null
|
|
302
|
+
- `element.y` - Y coordinate (top-left corner) or null
|
|
303
|
+
- `element.centerX` - X coordinate of element center or null
|
|
304
|
+
- `element.centerY` - Y coordinate of element center or null
|
|
305
|
+
|
|
306
|
+
##### Dimension Properties
|
|
307
|
+
|
|
308
|
+
- `element.width` - Width of the element or null
|
|
309
|
+
- `element.height` - Height of the element or null
|
|
310
|
+
- `element.boundingBox` - Bounding box object or null
|
|
311
|
+
|
|
312
|
+
##### Match Quality Properties
|
|
313
|
+
|
|
314
|
+
- `element.confidence` - Confidence score (0-1) or null
|
|
315
|
+
- `element.screenshot` - Base64 encoded screenshot or null
|
|
316
|
+
- `element.text` - Text content of the element or null
|
|
317
|
+
- `element.label` - Label/aria-label of the element or null
|
|
318
|
+
|
|
319
|
+
**Example:**
|
|
320
|
+
|
|
321
|
+
```javascript
|
|
322
|
+
const button = await client.find("login button");
|
|
323
|
+
|
|
324
|
+
if (button.found()) {
|
|
325
|
+
console.log({
|
|
326
|
+
position: { x: button.x, y: button.y },
|
|
327
|
+
center: { x: button.centerX, y: button.centerY },
|
|
328
|
+
size: { width: button.width, height: button.height },
|
|
329
|
+
confidence: button.confidence,
|
|
330
|
+
text: button.text,
|
|
331
|
+
label: button.label,
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
// Save screenshot for debugging
|
|
335
|
+
if (button.screenshot) {
|
|
336
|
+
require("fs").writeFileSync(
|
|
337
|
+
"element.png",
|
|
338
|
+
Buffer.from(button.screenshot, "base64"),
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Conditional actions based on properties
|
|
343
|
+
if (button.confidence > 0.8) {
|
|
344
|
+
await button.click();
|
|
345
|
+
} else {
|
|
346
|
+
console.log("Low confidence, skipping click");
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
For more examples, see `examples/sdk-element-properties.js`.
|
|
352
|
+
|
|
353
|
+
### Initialization
|
|
354
|
+
|
|
355
|
+
#### `new TestDriver(apiKey, options)`
|
|
356
|
+
|
|
357
|
+
Creates a new TestDriver SDK instance.
|
|
358
|
+
|
|
359
|
+
**Parameters:**
|
|
360
|
+
|
|
361
|
+
- `apiKey` (string): Your TestDriver API key
|
|
362
|
+
- `options` (object, optional):
|
|
363
|
+
- `apiRoot` (string): API endpoint (default: 'https://v6.testdriver.ai')
|
|
364
|
+
- `resolution` (string): Sandbox resolution (default: '1366x768')
|
|
365
|
+
- `analytics` (boolean): Enable analytics (default: true)
|
|
366
|
+
- `logging` (boolean): Enable console logging output (default: true)
|
|
367
|
+
- `environment` (object): Additional environment variables
|
|
368
|
+
|
|
369
|
+
**Example:**
|
|
370
|
+
|
|
371
|
+
```javascript
|
|
372
|
+
const client = new TestDriver(process.env.TD_API_KEY, {
|
|
373
|
+
resolution: "1920x1080",
|
|
374
|
+
analytics: false,
|
|
375
|
+
logging: true, // See detailed logs
|
|
376
|
+
});
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### Connection Methods
|
|
380
|
+
|
|
381
|
+
#### `auth()`
|
|
382
|
+
|
|
383
|
+
Authenticates with the TestDriver API.
|
|
384
|
+
|
|
385
|
+
**Returns:** `Promise<string>` - Authentication token
|
|
386
|
+
|
|
387
|
+
**Example:**
|
|
388
|
+
|
|
389
|
+
```javascript
|
|
390
|
+
await client.auth();
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
#### `connect(options)`
|
|
394
|
+
|
|
395
|
+
Connects to a sandbox environment.
|
|
396
|
+
|
|
397
|
+
**Parameters:**
|
|
398
|
+
|
|
399
|
+
- `options` (object, optional):
|
|
400
|
+
- `sandboxId` (string): Reconnect to existing sandbox
|
|
401
|
+
- `newSandbox` (boolean): Force creation of new sandbox
|
|
402
|
+
- `ip` (string): Direct IP connection
|
|
403
|
+
- `sandboxAmi` (string): Custom AMI for sandbox
|
|
404
|
+
- `sandboxInstance` (string): Instance type
|
|
405
|
+
- `headless` (boolean): Disable browser window rendering (default: false)
|
|
406
|
+
|
|
407
|
+
**Returns:** `Promise<Object>` - Sandbox instance details
|
|
408
|
+
|
|
409
|
+
**Examples:**
|
|
410
|
+
|
|
411
|
+
```javascript
|
|
412
|
+
// Create new sandbox (opens browser window by default)
|
|
413
|
+
await client.connect({ newSandbox: true });
|
|
414
|
+
|
|
415
|
+
// Create sandbox without opening browser window
|
|
416
|
+
await client.connect({ newSandbox: true, headless: true });
|
|
417
|
+
|
|
418
|
+
// Reconnect to existing sandbox
|
|
419
|
+
await client.connect({ sandboxId: "i-1234567890abcdef0" });
|
|
420
|
+
|
|
421
|
+
// Direct IP connection
|
|
422
|
+
await client.connect({ ip: "192.168.1.100" });
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
**Note:** By default, the SDK will automatically open a browser window showing the live sandbox environment, similar to the CLI behavior. This allows you to watch test execution in real-time. Set `headless: true` to disable this feature.
|
|
426
|
+
|
|
427
|
+
#### `disconnect()`
|
|
428
|
+
|
|
429
|
+
Disconnects from the sandbox.
|
|
430
|
+
|
|
431
|
+
**Returns:** `Promise<void>`
|
|
432
|
+
|
|
433
|
+
### Text Interaction Methods
|
|
434
|
+
|
|
435
|
+
#### `hoverText(text, description, action, method, timeout)`
|
|
436
|
+
|
|
437
|
+
Finds and hovers over text on screen.
|
|
438
|
+
|
|
439
|
+
**Parameters:**
|
|
440
|
+
|
|
441
|
+
- `text` (string): Text to find
|
|
442
|
+
- `description` (string, optional): Additional context
|
|
443
|
+
- `action` (string): Action type (default: 'click')
|
|
444
|
+
- `method` (string): Match method - 'turbo', 'leven', or 'dice' (default: 'turbo')
|
|
445
|
+
- `timeout` (number): Timeout in ms (default: 5000)
|
|
446
|
+
|
|
447
|
+
**Returns:** `Promise<Object>` - Match result with coordinates
|
|
448
|
+
|
|
449
|
+
**Example:**
|
|
450
|
+
|
|
451
|
+
```javascript
|
|
452
|
+
const result = await client.hoverText("Submit", "the submit button");
|
|
453
|
+
console.log(result); // { x: 150, y: 200, ... }
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
#### `type(text, delay)`
|
|
457
|
+
|
|
458
|
+
Types text with optional delay between keystrokes.
|
|
459
|
+
|
|
460
|
+
**Parameters:**
|
|
461
|
+
|
|
462
|
+
- `text` (string): Text to type
|
|
463
|
+
- `delay` (number): Delay in ms between keystrokes (default: 250)
|
|
464
|
+
|
|
465
|
+
**Example:**
|
|
466
|
+
|
|
467
|
+
```javascript
|
|
468
|
+
await client.type("hello@example.com", 100);
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
#### `waitForText(text, timeout, method, invert)`
|
|
472
|
+
|
|
473
|
+
Waits for text to appear on screen.
|
|
474
|
+
|
|
475
|
+
**Parameters:**
|
|
476
|
+
|
|
477
|
+
- `text` (string): Text to wait for
|
|
478
|
+
- `timeout` (number): Timeout in ms (default: 5000)
|
|
479
|
+
- `method` (string): Match method (default: 'turbo')
|
|
480
|
+
- `invert` (boolean): Wait for text to disappear (default: false)
|
|
481
|
+
|
|
482
|
+
**Example:**
|
|
483
|
+
|
|
484
|
+
```javascript
|
|
485
|
+
await client.waitForText("Success!", 10000);
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
#### `scrollUntilText(text, direction, maxDistance, textMatchMethod, method, invert)`
|
|
489
|
+
|
|
490
|
+
Scrolls until text is found.
|
|
491
|
+
|
|
492
|
+
**Parameters:**
|
|
493
|
+
|
|
494
|
+
- `text` (string): Text to find
|
|
495
|
+
- `direction` (string): 'up' or 'down' (default: 'down')
|
|
496
|
+
- `maxDistance` (number): Max pixels to scroll (default: 10000)
|
|
497
|
+
- `textMatchMethod` (string): Text matching method (default: 'turbo')
|
|
498
|
+
- `method` (string): Scroll method - 'mouse' or 'keyboard' (default: 'keyboard')
|
|
499
|
+
- `invert` (boolean): Invert match (default: false)
|
|
500
|
+
|
|
501
|
+
**Example:**
|
|
502
|
+
|
|
503
|
+
```javascript
|
|
504
|
+
await client.scrollUntilText("Terms of Service", "down", 5000);
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
### Image Interaction Methods
|
|
508
|
+
|
|
509
|
+
#### `hoverImage(description, action)`
|
|
510
|
+
|
|
511
|
+
Finds and hovers over an image matching the description.
|
|
512
|
+
|
|
513
|
+
**Parameters:**
|
|
514
|
+
|
|
515
|
+
- `description` (string): Description of the image
|
|
516
|
+
- `action` (string): Action type (default: 'click')
|
|
517
|
+
|
|
518
|
+
**Returns:** `Promise<Object>` - Match result
|
|
519
|
+
|
|
520
|
+
**Example:**
|
|
521
|
+
|
|
522
|
+
```javascript
|
|
523
|
+
await client.hoverImage("the red submit button");
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
#### `matchImage(imagePath, action, invert)`
|
|
527
|
+
|
|
528
|
+
Finds and interacts with an image using template matching.
|
|
529
|
+
|
|
530
|
+
**Parameters:**
|
|
531
|
+
|
|
532
|
+
- `imagePath` (string): Path to template image
|
|
533
|
+
- `action` (string): 'click' or 'hover' (default: 'click')
|
|
534
|
+
- `invert` (boolean): Invert match (default: false)
|
|
535
|
+
|
|
536
|
+
**Example:**
|
|
537
|
+
|
|
538
|
+
```javascript
|
|
539
|
+
await client.matchImage("./templates/login-button.png", "click");
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
#### `waitForImage(description, timeout, invert)`
|
|
543
|
+
|
|
544
|
+
Waits for an image to appear on screen.
|
|
545
|
+
|
|
546
|
+
**Parameters:**
|
|
547
|
+
|
|
548
|
+
- `description` (string): Description of the image
|
|
549
|
+
- `timeout` (number): Timeout in ms (default: 10000)
|
|
550
|
+
- `invert` (boolean): Wait for image to disappear (default: false)
|
|
551
|
+
|
|
552
|
+
**Example:**
|
|
553
|
+
|
|
554
|
+
```javascript
|
|
555
|
+
await client.waitForImage("loading spinner", 5000, true); // Wait for spinner to disappear
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
#### `scrollUntilImage(description, direction, maxDistance, method, path, invert)`
|
|
559
|
+
|
|
560
|
+
Scrolls until an image is found.
|
|
561
|
+
|
|
562
|
+
**Parameters:**
|
|
563
|
+
|
|
564
|
+
- `description` (string): Description of image (use either this or path)
|
|
565
|
+
- `direction` (string): 'up' or 'down' (default: 'down')
|
|
566
|
+
- `maxDistance` (number): Max pixels to scroll (default: 10000)
|
|
567
|
+
- `method` (string): Scroll method (default: 'keyboard')
|
|
568
|
+
- `path` (string): Path to template image
|
|
569
|
+
- `invert` (boolean): Invert match (default: false)
|
|
570
|
+
|
|
571
|
+
**Example:**
|
|
572
|
+
|
|
573
|
+
```javascript
|
|
574
|
+
await client.scrollUntilImage("footer logo", "down", 10000);
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
### Mouse & Keyboard Methods
|
|
578
|
+
|
|
579
|
+
#### `click(x, y, action)`
|
|
580
|
+
|
|
581
|
+
Clicks at specific coordinates.
|
|
582
|
+
|
|
583
|
+
**Parameters:**
|
|
584
|
+
|
|
585
|
+
- `x` (number): X coordinate
|
|
586
|
+
- `y` (number): Y coordinate
|
|
587
|
+
- `action` (string): Click type - 'click', 'right-click', 'double-click', 'middle-click', 'drag-start', 'drag-end' (default: 'click')
|
|
588
|
+
|
|
589
|
+
**Example:**
|
|
590
|
+
|
|
591
|
+
```javascript
|
|
592
|
+
await client.click(500, 300, "double-click");
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
#### `hover(x, y)`
|
|
596
|
+
|
|
597
|
+
Moves mouse to coordinates.
|
|
598
|
+
|
|
599
|
+
**Parameters:**
|
|
600
|
+
|
|
601
|
+
- `x` (number): X coordinate
|
|
602
|
+
- `y` (number): Y coordinate
|
|
603
|
+
|
|
604
|
+
**Example:**
|
|
605
|
+
|
|
606
|
+
```javascript
|
|
607
|
+
await client.hover(200, 150);
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
#### `pressKeys(keys)`
|
|
611
|
+
|
|
612
|
+
Presses keyboard keys (supports combinations).
|
|
613
|
+
|
|
614
|
+
**Parameters:**
|
|
615
|
+
|
|
616
|
+
- `keys` (Array<string>): Array of keys to press
|
|
617
|
+
|
|
618
|
+
**Example:**
|
|
619
|
+
|
|
620
|
+
```javascript
|
|
621
|
+
// Single key
|
|
622
|
+
await client.pressKeys(["enter"]);
|
|
623
|
+
|
|
624
|
+
// Key combination
|
|
625
|
+
await client.pressKeys(["ctrl", "c"]);
|
|
626
|
+
|
|
627
|
+
// Multiple keys in sequence
|
|
628
|
+
await client.pressKeys(["tab", "tab", "enter"]);
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
#### `scroll(direction, amount, method)`
|
|
632
|
+
|
|
633
|
+
Scrolls the page.
|
|
634
|
+
|
|
635
|
+
**Parameters:**
|
|
636
|
+
|
|
637
|
+
- `direction` (string): 'up' or 'down' (default: 'down')
|
|
638
|
+
- `amount` (number): Pixels to scroll (default: 300)
|
|
639
|
+
- `method` (string): 'mouse' or 'keyboard' (default: 'mouse')
|
|
640
|
+
|
|
641
|
+
**Example:**
|
|
642
|
+
|
|
643
|
+
```javascript
|
|
644
|
+
await client.scroll("down", 500, "mouse");
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
### Application Control
|
|
648
|
+
|
|
649
|
+
#### `focusApplication(name)`
|
|
650
|
+
|
|
651
|
+
Focuses an application by name.
|
|
652
|
+
|
|
653
|
+
**Parameters:**
|
|
654
|
+
|
|
655
|
+
- `name` (string): Application name
|
|
656
|
+
|
|
657
|
+
**Example:**
|
|
658
|
+
|
|
659
|
+
```javascript
|
|
660
|
+
await client.focusApplication("Google Chrome");
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
### AI-Powered Methods
|
|
664
|
+
|
|
665
|
+
#### `assert(assertion, async, invert)`
|
|
666
|
+
|
|
667
|
+
Makes an AI-powered assertion about the screen state.
|
|
668
|
+
|
|
669
|
+
**Parameters:**
|
|
670
|
+
|
|
671
|
+
- `assertion` (string): Natural language assertion
|
|
672
|
+
- `async` (boolean): Run asynchronously (default: false)
|
|
673
|
+
- `invert` (boolean): Invert the assertion (default: false)
|
|
674
|
+
|
|
675
|
+
**Example:**
|
|
676
|
+
|
|
677
|
+
```javascript
|
|
678
|
+
await client.assert("The login form is visible");
|
|
679
|
+
await client.assert("The page is showing an error message");
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
#### `remember(description)`
|
|
683
|
+
|
|
684
|
+
Extracts and remembers information from the screen.
|
|
685
|
+
|
|
686
|
+
**Parameters:**
|
|
687
|
+
|
|
688
|
+
- `description` (string): What to remember
|
|
689
|
+
|
|
690
|
+
**Returns:** `Promise<string>` - Extracted information
|
|
691
|
+
|
|
692
|
+
**Example:**
|
|
693
|
+
|
|
694
|
+
```javascript
|
|
695
|
+
const email = await client.remember(
|
|
696
|
+
"What is the user email shown on the profile page?",
|
|
697
|
+
);
|
|
698
|
+
console.log(email); // "user@example.com"
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
### Code Execution
|
|
702
|
+
|
|
703
|
+
#### `exec(language, code, timeout, silent)`
|
|
704
|
+
|
|
705
|
+
Executes code in the sandbox.
|
|
706
|
+
|
|
707
|
+
**Parameters:**
|
|
708
|
+
|
|
709
|
+
- `language` (string): 'js' or 'pwsh'
|
|
710
|
+
- `code` (string): Code to execute
|
|
711
|
+
- `timeout` (number): Timeout in ms
|
|
712
|
+
- `silent` (boolean): Suppress output (default: false)
|
|
713
|
+
|
|
714
|
+
**Returns:** `Promise<string>` - Execution result
|
|
715
|
+
|
|
716
|
+
**Example:**
|
|
717
|
+
|
|
718
|
+
```javascript
|
|
719
|
+
// JavaScript
|
|
720
|
+
const result = await client.exec(
|
|
721
|
+
"js",
|
|
722
|
+
`
|
|
723
|
+
result = { timestamp: Date.now(), platform: process.platform };
|
|
724
|
+
`,
|
|
725
|
+
5000,
|
|
726
|
+
);
|
|
727
|
+
|
|
728
|
+
// PowerShell
|
|
729
|
+
const output = await client.exec(
|
|
730
|
+
"pwsh",
|
|
731
|
+
"Get-Process | Select-Object -First 5",
|
|
732
|
+
10000,
|
|
733
|
+
);
|
|
734
|
+
```
|
|
735
|
+
|
|
736
|
+
### Utility Methods
|
|
737
|
+
|
|
738
|
+
#### `screenshot([scale], [silent], [mouse])`
|
|
739
|
+
|
|
740
|
+
Captures a screenshot of the current screen in the sandbox.
|
|
741
|
+
|
|
742
|
+
**Parameters:**
|
|
743
|
+
|
|
744
|
+
- `scale` (number, optional): Scale factor for the screenshot (default: 1 = original size)
|
|
745
|
+
- `silent` (boolean, optional): Whether to suppress logging (default: false)
|
|
746
|
+
- `mouse` (boolean, optional): Whether to include mouse cursor (default: false)
|
|
747
|
+
|
|
748
|
+
**Returns:** `Promise<string>` - Base64 encoded PNG screenshot
|
|
749
|
+
|
|
750
|
+
**Example:**
|
|
751
|
+
|
|
752
|
+
```javascript
|
|
753
|
+
// Capture a screenshot
|
|
754
|
+
const screenshot = await client.screenshot();
|
|
755
|
+
|
|
756
|
+
// Save to file
|
|
757
|
+
const fs = require("fs");
|
|
758
|
+
fs.writeFileSync("screenshot.png", Buffer.from(screenshot, "base64"));
|
|
759
|
+
|
|
760
|
+
// Capture with mouse cursor visible
|
|
761
|
+
const screenshotWithMouse = await client.screenshot(1, false, true);
|
|
762
|
+
fs.writeFileSync(
|
|
763
|
+
"screenshot-with-mouse.png",
|
|
764
|
+
Buffer.from(screenshotWithMouse, "base64"),
|
|
765
|
+
);
|
|
766
|
+
```
|
|
767
|
+
|
|
768
|
+
#### `wait(timeout)`
|
|
769
|
+
|
|
770
|
+
Waits for specified time.
|
|
771
|
+
|
|
772
|
+
**Parameters:**
|
|
773
|
+
|
|
774
|
+
- `timeout` (number): Time in ms (default: 3000)
|
|
775
|
+
|
|
776
|
+
**Example:**
|
|
777
|
+
|
|
778
|
+
```javascript
|
|
779
|
+
await client.wait(2000); // Wait 2 seconds
|
|
780
|
+
```
|
|
781
|
+
|
|
782
|
+
#### `getInstance()`
|
|
783
|
+
|
|
784
|
+
Gets the current sandbox instance details.
|
|
785
|
+
|
|
786
|
+
**Returns:** `Object|null` - Sandbox instance
|
|
787
|
+
|
|
788
|
+
#### `getSessionId()`
|
|
789
|
+
|
|
790
|
+
Gets the current session ID.
|
|
791
|
+
|
|
792
|
+
**Returns:** `string|null` - Session ID
|
|
793
|
+
|
|
794
|
+
#### `setLogging(enabled)`
|
|
795
|
+
|
|
796
|
+
Enable or disable console logging output.
|
|
797
|
+
|
|
798
|
+
**Parameters:**
|
|
799
|
+
|
|
800
|
+
- `enabled` (boolean): Whether to enable logging
|
|
801
|
+
|
|
802
|
+
**Example:**
|
|
803
|
+
|
|
804
|
+
```javascript
|
|
805
|
+
// Disable logging
|
|
806
|
+
client.setLogging(false);
|
|
807
|
+
|
|
808
|
+
// Enable logging
|
|
809
|
+
client.setLogging(true);
|
|
810
|
+
```
|
|
811
|
+
|
|
812
|
+
#### `getEmitter()`
|
|
813
|
+
|
|
814
|
+
Gets the event emitter for custom event handling.
|
|
815
|
+
|
|
816
|
+
**Returns:** `EventEmitter2` - Event emitter instance
|
|
817
|
+
|
|
818
|
+
**Example:**
|
|
819
|
+
|
|
820
|
+
```javascript
|
|
821
|
+
const emitter = client.getEmitter();
|
|
822
|
+
|
|
823
|
+
// Listen to all log events
|
|
824
|
+
emitter.on("log:*", (message) => {
|
|
825
|
+
console.log("Log:", message);
|
|
826
|
+
});
|
|
827
|
+
|
|
828
|
+
// Listen to error events
|
|
829
|
+
emitter.on("error:*", (data) => {
|
|
830
|
+
console.error("Error:", data);
|
|
831
|
+
});
|
|
832
|
+
|
|
833
|
+
// Listen to command events
|
|
834
|
+
emitter.on("command:start", (data) => {
|
|
835
|
+
console.log("Command started:", data.command);
|
|
836
|
+
});
|
|
837
|
+
|
|
838
|
+
emitter.on("command:success", (data) => {
|
|
839
|
+
console.log(
|
|
840
|
+
"Command succeeded:",
|
|
841
|
+
data.command,
|
|
842
|
+
"Duration:",
|
|
843
|
+
data.duration,
|
|
844
|
+
"ms",
|
|
845
|
+
);
|
|
846
|
+
});
|
|
847
|
+
```
|
|
848
|
+
|
|
849
|
+
## Events
|
|
850
|
+
|
|
851
|
+
The SDK emits various events that you can listen to for detailed execution information:
|
|
852
|
+
|
|
853
|
+
### Log Events
|
|
854
|
+
|
|
855
|
+
- `log:log` - General log messages
|
|
856
|
+
- `log:warn` - Warning messages
|
|
857
|
+
- `log:debug` - Debug messages
|
|
858
|
+
- `log:narration` - Narration text (e.g., "thinking...")
|
|
859
|
+
- `log:markdown:start` - Markdown streaming started
|
|
860
|
+
- `log:markdown:chunk` - Markdown chunk received
|
|
861
|
+
- `log:markdown:end` - Markdown streaming ended
|
|
862
|
+
- `log:markdown` - Static markdown content
|
|
863
|
+
|
|
864
|
+
### Command Events
|
|
865
|
+
|
|
866
|
+
- `command:start` - Command execution started
|
|
867
|
+
- `command:success` - Command completed successfully
|
|
868
|
+
- `command:error` - Command failed
|
|
869
|
+
|
|
870
|
+
### Error Events
|
|
871
|
+
|
|
872
|
+
- `error:fatal` - Fatal error occurred
|
|
873
|
+
- `error:general` - General error
|
|
874
|
+
- `error:sdk` - SDK-related error
|
|
875
|
+
- `error:sandbox` - Sandbox-related error
|
|
876
|
+
|
|
877
|
+
### Sandbox Events
|
|
878
|
+
|
|
879
|
+
- `sandbox:connected` - Connected to sandbox
|
|
880
|
+
- `sandbox:authenticated` - Authenticated with sandbox
|
|
881
|
+
- `sandbox:error` - Sandbox error
|
|
882
|
+
- `sandbox:disconnected` - Disconnected from sandbox
|
|
883
|
+
|
|
884
|
+
### Other Events
|
|
885
|
+
|
|
886
|
+
- `status` - Status update
|
|
887
|
+
- `mouse-click` - Mouse click occurred
|
|
888
|
+
- `mouse-move` - Mouse moved
|
|
889
|
+
- `matches:show` - Match results available
|
|
890
|
+
|
|
891
|
+
**Example: Custom Event Handling**
|
|
892
|
+
|
|
893
|
+
```javascript
|
|
894
|
+
const TestDriver = require("testdriverai");
|
|
895
|
+
|
|
896
|
+
const client = new TestDriver(process.env.TD_API_KEY, {
|
|
897
|
+
logging: false, // Disable default logging
|
|
898
|
+
});
|
|
899
|
+
|
|
900
|
+
const emitter = client.getEmitter();
|
|
901
|
+
|
|
902
|
+
// Custom logging
|
|
903
|
+
emitter.on("log:*", (message) => {
|
|
904
|
+
const timestamp = new Date().toISOString();
|
|
905
|
+
console.log(`[${timestamp}] ${message}`);
|
|
906
|
+
});
|
|
907
|
+
|
|
908
|
+
// Track command performance
|
|
909
|
+
const commandTimes = {};
|
|
910
|
+
emitter.on("command:start", (data) => {
|
|
911
|
+
commandTimes[data.command] = Date.now();
|
|
912
|
+
});
|
|
913
|
+
|
|
914
|
+
emitter.on("command:success", (data) => {
|
|
915
|
+
const duration = Date.now() - commandTimes[data.command];
|
|
916
|
+
console.log(`✓ ${data.command} completed in ${duration}ms`);
|
|
917
|
+
});
|
|
918
|
+
|
|
919
|
+
emitter.on("command:error", (data) => {
|
|
920
|
+
console.error(`✗ ${data.command} failed: ${data.error}`);
|
|
921
|
+
});
|
|
922
|
+
|
|
923
|
+
await client.auth();
|
|
924
|
+
await client.connect();
|
|
925
|
+
await client.hoverText("Submit");
|
|
926
|
+
```
|
|
927
|
+
|
|
928
|
+
## Complete Example
|
|
929
|
+
|
|
930
|
+
```javascript
|
|
931
|
+
const TestDriver = require("testdriverai");
|
|
932
|
+
|
|
933
|
+
async function testLoginFlow() {
|
|
934
|
+
const client = new TestDriver(process.env.TD_API_KEY);
|
|
935
|
+
|
|
936
|
+
try {
|
|
937
|
+
// Setup
|
|
938
|
+
await client.auth();
|
|
939
|
+
await client.connect({ newSandbox: true });
|
|
940
|
+
|
|
941
|
+
// Open browser and navigate
|
|
942
|
+
await client.focusApplication("Google Chrome");
|
|
943
|
+
await client.wait(1000);
|
|
944
|
+
|
|
945
|
+
// Type URL and navigate
|
|
946
|
+
await client.type("https://example.com/login");
|
|
947
|
+
await client.pressKeys(["enter"]);
|
|
948
|
+
await client.waitForText("Login", 5000);
|
|
949
|
+
|
|
950
|
+
// Fill login form
|
|
951
|
+
await client.hoverText("Email");
|
|
952
|
+
await client.type("test@example.com");
|
|
953
|
+
await client.pressKeys(["tab"]);
|
|
954
|
+
await client.type("password123");
|
|
955
|
+
|
|
956
|
+
// Submit form
|
|
957
|
+
await client.hoverText("Sign In");
|
|
958
|
+
await client.pressKeys(["enter"]);
|
|
959
|
+
|
|
960
|
+
// Verify login
|
|
961
|
+
await client.waitForText("Dashboard", 10000);
|
|
962
|
+
await client.assert("User is logged in successfully");
|
|
963
|
+
|
|
964
|
+
// Get user info
|
|
965
|
+
const username = await client.remember(
|
|
966
|
+
"What is the username displayed in the header?",
|
|
967
|
+
);
|
|
968
|
+
console.log("Logged in as:", username);
|
|
969
|
+
} catch (error) {
|
|
970
|
+
console.error("Test failed:", error);
|
|
971
|
+
throw error;
|
|
972
|
+
} finally {
|
|
973
|
+
await client.disconnect();
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
testLoginFlow();
|
|
978
|
+
```
|
|
979
|
+
|
|
980
|
+
## Environment Variables
|
|
981
|
+
|
|
982
|
+
- `TD_API_KEY`: Your TestDriver API key (required)
|
|
983
|
+
- `TD_API_ROOT`: API endpoint (optional, default: https://v6.testdriver.ai)
|
|
984
|
+
- `TD_RESOLUTION`: Sandbox resolution (optional, default: 1366x768)
|
|
985
|
+
- `TD_ANALYTICS`: Enable analytics (optional, default: true)
|
|
986
|
+
- `VERBOSE` / `DEBUG` / `TD_DEBUG`: Enable verbose debug output including cache information
|
|
987
|
+
|
|
988
|
+
## Configuration Options
|
|
989
|
+
|
|
990
|
+
### Cache Thresholds
|
|
991
|
+
|
|
992
|
+
Configure cache sensitivity for element finding operations. Lower thresholds require higher similarity for cache hits.
|
|
993
|
+
|
|
994
|
+
**Global Configuration:**
|
|
995
|
+
|
|
996
|
+
```javascript
|
|
997
|
+
const client = new TestDriver(process.env.TD_API_KEY, {
|
|
998
|
+
cacheThreshold: {
|
|
999
|
+
find: 0.03, // 3% difference = 97% similarity required (stricter)
|
|
1000
|
+
findAll: 0.05, // 5% difference = 95% similarity required (default)
|
|
1001
|
+
},
|
|
1002
|
+
});
|
|
1003
|
+
```
|
|
1004
|
+
|
|
1005
|
+
**Disable Cache Globally:**
|
|
1006
|
+
|
|
1007
|
+
```javascript
|
|
1008
|
+
// Force all find operations to regenerate (never use cache)
|
|
1009
|
+
const client = new TestDriver(process.env.TD_API_KEY, {
|
|
1010
|
+
cache: false,
|
|
1011
|
+
});
|
|
1012
|
+
```
|
|
1013
|
+
|
|
1014
|
+
**Per-Command Configuration:**
|
|
1015
|
+
|
|
1016
|
+
```javascript
|
|
1017
|
+
// Override cache threshold for a specific find
|
|
1018
|
+
const element = await client.find("login button", 0.01); // 99% similarity required
|
|
1019
|
+
|
|
1020
|
+
// Override cache threshold for a specific findAll
|
|
1021
|
+
const items = await client.findAll("list items", 0.1); // 90% similarity required
|
|
1022
|
+
|
|
1023
|
+
// Disable cache for a specific find (always regenerate)
|
|
1024
|
+
const element = await client.find("login button", -1);
|
|
1025
|
+
```
|
|
1026
|
+
|
|
1027
|
+
**Cache Threshold Values:**
|
|
1028
|
+
|
|
1029
|
+
- `0.01` - Very strict (99% similarity required)
|
|
1030
|
+
- `0.03` - Strict (97% similarity required)
|
|
1031
|
+
- `0.05` - Default (95% similarity required)
|
|
1032
|
+
- `0.10` - Relaxed (90% similarity required)
|
|
1033
|
+
|
|
1034
|
+
### Debugging Cache Behavior
|
|
1035
|
+
|
|
1036
|
+
Enable verbose output to see cache hit/miss information:
|
|
1037
|
+
|
|
1038
|
+
```bash
|
|
1039
|
+
VERBOSE=true node your-test.js
|
|
1040
|
+
```
|
|
1041
|
+
|
|
1042
|
+
Debug output includes:
|
|
1043
|
+
|
|
1044
|
+
- Cache hit/miss status
|
|
1045
|
+
- Cache strategy used (image/text)
|
|
1046
|
+
- Similarity scores for cache matches
|
|
1047
|
+
- Response times
|
|
1048
|
+
- Debug images with element highlights
|
|
1049
|
+
|
|
1050
|
+
Example debug output:
|
|
1051
|
+
|
|
1052
|
+
```
|
|
1053
|
+
🔍 Element Found:
|
|
1054
|
+
Description: login button
|
|
1055
|
+
Coordinates: (523, 345)
|
|
1056
|
+
Duration: 1234ms
|
|
1057
|
+
Cache Hit: ✅ YES
|
|
1058
|
+
Cache Strategy: text
|
|
1059
|
+
Similarity: 98.50%
|
|
1060
|
+
Confidence: 95.20%
|
|
1061
|
+
Debug Image: /tmp/testdriver-debug/element-found-1234567890.png
|
|
1062
|
+
```
|
|
1063
|
+
|
|
1064
|
+
## Error Handling
|
|
1065
|
+
|
|
1066
|
+
The SDK throws errors when operations fail. Always wrap your code in try-catch blocks:
|
|
1067
|
+
|
|
1068
|
+
```javascript
|
|
1069
|
+
try {
|
|
1070
|
+
await client.hoverText("Submit");
|
|
1071
|
+
} catch (error) {
|
|
1072
|
+
console.error("Failed to find Submit button:", error.message);
|
|
1073
|
+
// Handle error appropriately
|
|
1074
|
+
}
|
|
1075
|
+
```
|
|
1076
|
+
|
|
1077
|
+
## Best Practices
|
|
1078
|
+
|
|
1079
|
+
1. **Always authenticate and connect before running commands**
|
|
1080
|
+
|
|
1081
|
+
```javascript
|
|
1082
|
+
await client.auth();
|
|
1083
|
+
await client.connect();
|
|
1084
|
+
```
|
|
1085
|
+
|
|
1086
|
+
2. **Use appropriate timeouts for slow operations**
|
|
1087
|
+
|
|
1088
|
+
```javascript
|
|
1089
|
+
await client.waitForText("Loading...", 30000); // 30 second timeout
|
|
1090
|
+
```
|
|
1091
|
+
|
|
1092
|
+
3. **Clean up after tests**
|
|
1093
|
+
|
|
1094
|
+
```javascript
|
|
1095
|
+
try {
|
|
1096
|
+
// Your test code
|
|
1097
|
+
} finally {
|
|
1098
|
+
await client.disconnect();
|
|
1099
|
+
}
|
|
1100
|
+
```
|
|
1101
|
+
|
|
1102
|
+
4. **Use natural language assertions for validation**
|
|
1103
|
+
|
|
1104
|
+
```javascript
|
|
1105
|
+
await client.assert("The form was submitted successfully");
|
|
1106
|
+
```
|
|
1107
|
+
|
|
1108
|
+
5. **Add waits between actions to let UI settle**
|
|
1109
|
+
```javascript
|
|
1110
|
+
await client.click(100, 200);
|
|
1111
|
+
await client.wait(1000); // Let UI respond
|
|
1112
|
+
```
|
|
1113
|
+
|
|
1114
|
+
## Support
|
|
1115
|
+
|
|
1116
|
+
- Documentation: https://docs.testdriver.ai
|
|
1117
|
+
- Discord: https://discord.com/invite/cWDFW8DzPm
|
|
1118
|
+
- GitHub: https://github.com/testdriverai/cli
|
|
1119
|
+
|
|
1120
|
+
## License
|
|
1121
|
+
|
|
1122
|
+
ISC
|