# Changelog Source: https://docs.testdriver.ai/changelog Product updates and announcements πŸš€ Canary release This release promotes changes from `v7.9.4-test` through `v7.9.51-test` to canary, rolling up all updates since `v7.9.3`. ✨ New features * (Internal) **Autoscaling** * (Internal) **End-to-end distributed tracing** πŸ”§ Improvements * **Faster image matching** * **Storage and infra hardening** * **Sentry environment consistency** * **Chrome extension interaction reliability** * **Docs and onboarding refresh** β€” Updated v7 docs, SDK examples, and VS Code onboarding/init flows. πŸ› Bug fixes * **Windows AMI build stability** β€” Fixed `chrome.exe` startup/build failures. * **Realtime cleanup reliability** β€” Fixed connection cleanup leaks in session shutdown paths. * **Environment and deployment fixes** Releases test.11 through test.22 include version bumps and internal CI improvements across all packages. No new user-facing changes β€” see [v7.8.0-test.10](#v780-test10) for the latest updates in this release stream. πŸ”§ Improvements * **Parallel test concurrency** β€” Concurrency limits are now enforced with atomic slot tracking, eliminating a race condition where multiple tests launching at the same time could all bypass the limit. If you hit your concurrency cap, the SDK waits and retries automatically. * **Dashboard access for all plans** β€” Test recordings and metrics on the [dashboard](https://console.testdriver.ai) are now visible to all users, including those on the Free plan. * **Sandbox message throttling** β€” High-frequency interactions no longer risk hitting connection limits. The SDK automatically paces outgoing messages during fast command sequences. * **Windows Dashcam stability** β€” Web log tracking on Windows sandboxes now handles errors gracefully instead of failing the test. πŸ”§ Improvements * **Stable release promotion** β€” Fixed an issue where promoting a test or canary release to stable could be incorrectly skipped. Stable releases now proceed reliably regardless of the prior pre-release channel. * **Example docs sync** β€” Example documentation now updates more reliably, continuing even when individual example tests fail so that passing examples still get refreshed. πŸ”§ Improvements * **VS Code environment switching** β€” The TestDriver VS Code extension now includes a status bar indicator and a new **TestDriver: Switch Environment** command. You can switch between stable, canary, and test environments directly from VS Code, and your MCP server configuration updates automatically. * **Plan rename: Starter β†’ Pro** β€” The "Starter" plan has been renamed to **Pro**. No changes to pricing or features β€” just a clearer name. The "Self-Hosted" tier is now labeled **Enterprise**. * **SDK network resilience** β€” API requests now automatically retry on transient server errors (500, 502, 503, 504) with exponential backoff, reducing flaky test failures caused by brief infrastructure hiccups. * **Pricing page refresh** β€” The [hosted plans page](/v7/hosted) now shows plan cards with included minutes, concurrency limits, and pricing at a glance. πŸ“š Docs updates * **Quickstart redesign** β€” The [quickstart](/v7/quickstart) now has tabbed setup paths for CLI, GitHub Copilot, and manual installation. * **Deployment section** β€” "Cloud" is now [Hosted](/v7/hosted) and the previous Enterprise page has been consolidated into [Self-Hosted](/v7/self-hosted), which covers both standard and air-gapped deployments. This release includes all changes from v7.8.0-test.6 with version bumps across all packages. No additional user-facing changes. πŸ”§ Improvements * **Sandbox spawning reliability** β€” Sandbox creation now uses a 3-minute timeout to handle slow infrastructure, then immediately switches to a 60-second orphan timeout for fast cleanup. This reduces sandbox creation failures during high-traffic periods. * **Command deadline timeouts** β€” Sandbox commands now enforce a hard deadline that terminates execution if the connection drops mid-command, preventing tests from hanging indefinitely. * **Real-time channel cleanup** β€” Fixed a compatibility issue with the real-time messaging layer that could cause channel cleanup to fail during shutdown. * **Dashboard timestamp tooltips** β€” Extended UTC date tooltips to the test history sidebar. Hovering over any relative timestamp (e.g., "5 minutes ago") across the [dashboard](https://console.testdriver.ai) now shows the full UTC date and time. ✨ New features * **[GitHub Copilot integration](/v7/copilot/running-tests)** β€” Use TestDriver directly from GitHub Copilot in VS Code. The new MCP server lets Copilot launch sandboxes, interact with elements, and run assertions through natural language. Includes guides for [creating tests](/v7/generating-tests), [running tests](/v7/copilot/running-tests), [GitHub Actions integration](/v7/copilot/auto-healing#use-testdriver-in-github), and [auto-healing](/v7/copilot/auto-healing). πŸ”§ Improvements * **Sandbox reliability** β€” Linux sandboxes now use a longer creation timeout to handle slow infrastructure, then immediately switch to a short orphan timeout for fast cleanup of disconnected sessions. * **Command execution timeouts** β€” Sandbox commands now have deadline timeouts that prevent hangs if the connection drops mid-execution. * **Real-time connection stability** β€” Updated the real-time messaging layer for better compatibility and more reliable channel cleanup during shutdown. * **Dashboard date display** β€” Hovering over relative timestamps (e.g., "5 minutes ago") now shows the full UTC date and time. This release includes all changes from v7.8.0-canary.4 with version bumps. ✨ New features * **[Cache API](/v7/cache)** β€” Speed up repeated test runs with screenshot-based caching. The system compares screenshots to cached results and reuses element positions when the screen hasn't changed, reducing AI calls. * **[Custom error classes](/v7/errors)** β€” New `ElementNotFoundError` class with rich debugging info including screenshot paths, pixel diffs, and detailed messages. * **[Events system](/v7/events)** β€” Listen to SDK lifecycle events with wildcard support via `testdriver.emitter`. Uses colon-delimited namespaces (e.g., `command:start`, `log:*`). * **[Provision API](/v7/provision)** β€” Launch browsers, desktop apps, VS Code, and Chrome extensions in your sandbox before tests run. Access via `testdriver.provision.*`. * **[Redraw detection](/v7/redraw)** β€” Wait for screens to stabilize after interactions using two-phase detection with pixel comparison and z-score analysis. * **[Screenshots API](/v7/screenshots)** β€” Capture screenshots manually with `testdriver.screenshot()` or automatically before/after every command. πŸ“š New examples * [Exec output](/v7/examples/exec-output) β€” Capture and use output from PowerShell commands * [Exec PowerShell](/v7/examples/exec-pwsh) β€” Generate dynamic data with PowerShell * [Focus window](/v7/examples/focus-window) β€” Switch focus between application windows πŸ”§ Improvements * Improved console URL mapping for canary and test environments * Enhanced Ably channel cleanup for better resource management * Updated release workflow with improved changelog generation πŸš€ Stable release This release promotes v7.5.25 to stable with deployment channel improvements. ✨ Features * **Deployment channels** β€” SDK now supports stable and canary release channels. Stable releases use the `latest` npm tag, while canary releases use the `canary` tag. Install canary with `npm install testdriverai@canary`. πŸ”§ Improvements * Improved cache hit debugging and logging * Enhanced element location reliability * Better error handling during test execution πŸ› Bug fixes * Fixed restart behavior during auto-updates * Fixed console URL routing for various environments This release includes changes from v7.5.17 through v7.5.25. πŸ› Bug fixes * Fixed restart update behavior πŸ”§ Improvements * Disabled auto-update restart to improve CI stability πŸ”§ Improvements * Added markdown-to-HTML rendering for marketing content πŸ”§ Improvements * Enhanced debugging output for cache hits * Improved element location reliability πŸ”§ Improvements * Improved codespace scaling and development environment setup * Updated VS Code extension version # assert() Source: https://docs.testdriver.ai/v7/assert Make AI-powered assertions about screen state ## Overview Make AI-powered assertions about the current screen state using natural language. The AI analyzes the screen and verifies that your assertion is true. ## Syntax ```javascript theme={null} await testdriver.assert(assertion) await testdriver.assert(assertion, options) ``` ## Parameters Natural language description of what should be true Optional configuration AI sampling configuration for this assert call (overrides global `ai` config from constructor). Controls randomness. `0` = deterministic, higher = more creative. Default: model default. Sampling parameters Top-P (nucleus sampling). Range: 0-1. Top-K sampling. `1` = most deterministic. ## Returns `Promise` - `true` if assertion passes, throws error if assertion fails ## Examples ### Basic Assertions ```javascript theme={null} // Verify page elements await testdriver.assert('the login page is displayed'); await testdriver.assert('submit button is visible'); await testdriver.assert('error message is shown'); // Verify text content await testdriver.assert('the page title is "Welcome"'); await testdriver.assert('username field contains "john.doe"'); await testdriver.assert('success message says "Account created"'); // Verify states await testdriver.assert('the form is empty'); await testdriver.assert('the checkbox is checked'); await testdriver.assert('the dropdown shows "United States"'); // Verify visual appearance await testdriver.assert('the button is blue'); await testdriver.assert('the loading spinner is displayed'); await testdriver.assert('the modal dialog is open'); ``` ## Best Practices **Be specific in assertions** More specific assertions are more reliable: ```javascript theme={null} // ❌ Too vague await testdriver.assert('button is visible'); // βœ… Specific await testdriver.assert('blue submit button is visible below the form'); ``` **Assert state changes** Verify state before and after actions: ```javascript theme={null} // Before await testdriver.assert('cart is empty'); // Action const addBtn = await testdriver.find('add to cart'); await addBtn.click(); // After await testdriver.assert('cart contains 1 item'); ``` **Use with test framework assertions** Combine AI assertions with traditional test assertions: ```javascript theme={null} // AI assertion const result = await testdriver.assert('success message is displayed'); // Framework assertion expect(result).toBeTruthy(); // Extract for detailed comparison const message = await testdriver.extract('the success message text'); expect(message).toContain('successfully'); ``` ## Polling Assertions For conditions that may take time to become true: ```javascript theme={null} async function waitForAssertion(testdriver, assertion, timeout = 30000) { const startTime = Date.now(); while (Date.now() - startTime < timeout) { try { await testdriver.assert(assertion); return true; // Assertion passed } catch (error) { // Assertion failed, wait and retry await new Promise(r => setTimeout(r, 1000)); } } throw new Error(`Assertion timeout: "${assertion}"`); } // Usage await waitForAssertion(testdriver, 'page has finished loading', 30000); await waitForAssertion(testdriver, 'results are displayed', 10000); ``` ## Use Cases ```javascript theme={null} // Try to submit empty form const submitBtn = await testdriver.find('submit button'); await submitBtn.click(); // Verify validation errors await testdriver.assert('email field shows "required" error'); await testdriver.assert('password field shows "required" error'); // Verify form not submitted await testdriver.assert('still on the form page'); ``` ```javascript theme={null} const loginBtn = await testdriver.find('login button'); await loginBtn.click(); // Verify navigation await testdriver.assert('user dashboard is displayed'); await testdriver.assert('welcome message shows user name'); await testdriver.assert('logout button is visible'); ``` ```javascript theme={null} const loadBtn = await testdriver.find('load more button'); await loadBtn.click(); // Poll for content using helper await waitForAssertion(testdriver, 'more than 10 items are shown', 10000); await testdriver.assert('load more button is still visible'); ``` ```javascript theme={null} // Verify hover effect const button = await testdriver.find('primary button'); await button.hover(); await testdriver.assert('button background is darker'); // Verify button is enabled await testdriver.assert('submit button is enabled'); ``` ```javascript theme={null} // Step 1 await testdriver.assert('step 1 is active'); const nextBtn = await testdriver.find('next button'); await nextBtn.click(); // Step 2 await testdriver.assert('step 2 is active'); await testdriver.assert('step 1 is completed'); await nextBtn.click(); // Step 3 await testdriver.assert('step 3 is active'); await testdriver.assert('step 2 is completed'); ``` ## Complete Example ```javascript theme={null} import { beforeAll, afterAll, describe, it, expect } from 'vitest'; import TestDriver from 'testdriverai'; describe('Assertions', () => { let testdriver; beforeAll(async () => { client = new TestDriver(process.env.TD_API_KEY); await testdriver.auth(); await testdriver.connect(); }); afterAll(async () => { await testdriver.disconnect(); }); it('should validate login flow', async () => { await testdriver.focusApplication('Google Chrome'); // Verify initial state await testdriver.assert('the login page is displayed'); await testdriver.assert('username field is empty'); await testdriver.assert('password field is empty'); // Fill form const usernameField = await testdriver.find('username input'); await usernameField.click(); await testdriver.type('testuser'); await testdriver.pressKeys(['tab']); await testdriver.type('password123'); // Verify fields filled await testdriver.assert('username field contains "testuser"'); await testdriver.assert('password field is not empty'); // Submit await testdriver.pressKeys(['enter']); // Poll for success await waitForAssertion(testdriver, 'user dashboard is displayed', 10000); // Verify logged in state await testdriver.assert('welcome message is shown'); await testdriver.assert('logout button is visible'); // Verify login page is gone await testdriver.assert('login form is displayed', false, true); }); it('should show validation errors', async () => { // Try empty submission const submitBtn = await testdriver.find('submit button'); await submitBtn.click(); // Verify multiple errors const errors = [ 'email field shows error', 'name field shows error', 'password field shows error' ]; for (const error of errors) { const result = await testdriver.assert(error); expect(result).toBeTruthy(); } // Verify not submitted await testdriver.assert('confirmation page is shown', false, true); }); }); ``` ## Related Methods * [`extract()`](/v7/extract) - Extract information for detailed assertions * [`find()`](/v7/find) - Locate elements to verify # AWS Setup Guide Source: https://docs.testdriver.ai/v7/aws-setup Deploy TestDriver on your AWS infrastructure using CloudFormation This guide walks you through setting up self-hosted TestDriver instances on AWS. By the end, you'll have fully automated test infrastructure that spawns and terminates instances on-demand. ```mermaid theme={null} graph LR A[Vitest Test] --> B[setup-aws hook] B --> C[Spawns EC2] C --> D[Runs Test] D --> E[Terminates EC2] ``` ## How It Works TestDriver automatically manages AWS EC2 instances for your tests: 1. **Deploy CloudFormation** β€” One-time infrastructure setup 2. **Configure Vitest** β€” Add one line to your config 3. **Run Tests** β€” Instances spawn automatically, run tests, and terminate That's it! No manual instance management needed. # Quickstart One-click AWS setup ```javascript vitest.config.mjs theme={null} setupFiles: ['testdriverai/vitest/setup', 'testdriverai/vitest/setup-aws'] ``` ```bash theme={null} TD_OS=windows AWS_REGION=us-east-2 \ AWS_LAUNCH_TEMPLATE_ID=lt-xxx AMI_ID=ami-xxx \ vitest run ``` ## Overview The setup process is simple: 1. **Deploy CloudFormation** β€” Creates VPC, security groups, IAM roles, and launch templates 2. **Configure Vitest** β€” Add `setup-aws` to automatically manage instance lifecycle 3. **Run Tests** β€” Set `TD_OS=windows` with AWS credentials and instances spawn/terminate automatically ## Prerequisites Before you begin, ensure you have: * AWS account with CloudFormation permissions * [AWS CLI](https://aws.amazon.com/cli/) installed and configured (`aws configure`) * Access to the TestDriver AMI β€” [Contact us](http://testdriver.ai/demo) with your AWS region * A GitHub repository for your tests The TestDriver Golden Image AMI ID is `ami-0504bf50fad62f312`. Contact us to get access in your preferred AWS region. ## Step 1: Deploy CloudFormation Stack Our CloudFormation template creates all the AWS infrastructure you need: * Dedicated VPC with public subnet * Security group with required port access * IAM roles and instance profiles * EC2 launch template for instance creation Click the button below to launch the CloudFormation stack in your AWS Console: Deploy TestDriver infrastructure with one click Configure the stack parameters: * **Stack name**: `testdriver-infrastructure` (or your preferred name) * **ProjectTag**: `testdriver` * **AllowedIngressCidr**: Your IP range (e.g., `203.0.113.0/24`) * **InstanceType**: `c5.xlarge` (recommended) * **CreateKeyPair**: `true` **Security**: Replace `AllowedIngressCidr` with your specific IP ranges to restrict VPC access. Avoid using `0.0.0.0/0` in production. ### Get Your Launch Template ID After the stack creation completes, navigate to the **Outputs** tab to find your `LaunchTemplateId`: Launch Template ID **Save this ID** β€” you'll need it for spawning instances and CI configuration. Download the template from the [TestDriver CLI repository](https://github.com/testdriverai/testdriverai/blob/main/setup/aws/cloudformation.yaml), then deploy: ```bash theme={null} aws cloudformation deploy \ --template-file setup/aws/cloudformation.yaml \ --stack-name testdriver-infrastructure \ --parameter-overrides \ ProjectTag=testdriver \ AllowedIngressCidr=0.0.0.0/0 \ InstanceType=c5.xlarge \ CreateKeyPair=true \ --capabilities CAPABILITY_IAM ``` **Security**: Replace `AllowedIngressCidr=0.0.0.0/0` with your specific IP ranges to restrict VPC access. ### Get Your Launch Template ID After deployment completes, retrieve the launch template ID: ```bash theme={null} aws cloudformation describe-stacks \ --stack-name testdriver-infrastructure \ --query 'Stacks[0].Outputs[?OutputKey==`LaunchTemplateId`].OutputValue' \ --output text ``` **Save this ID** β€” you'll need it for spawning instances and CI configuration. ## Step 2: Configure Vitest Add the AWS setup hook to your `vitest.config.mjs`: ```javascript vitest.config.mjs theme={null} import { defineConfig } from 'vitest/config'; import { config } from 'dotenv'; import TestDriver from 'testdriverai/vitest'; config(); // Load .env file export default defineConfig({ test: { testTimeout: 900000, hookTimeout: 900000, maxConcurrency: 3, reporters: [ 'default', TestDriver(), ['junit', { outputFile: 'test-report.junit.xml' }] ], setupFiles: ['testdriverai/vitest/setup', 'testdriverai/vitest/setup-aws'], }, }); ``` **That's it!** The `setup-aws` hook automatically spawns and terminates instances when `TD_OS=windows` is set. No manual instance management needed. ## Step 3: Write Your Tests Tests should use `context.ip || process.env.TD_IP` for the IP configuration: ```javascript theme={null} import { describe, it } from "vitest"; import { TestDriver } from "testdriverai/vitest/hooks"; describe("My Test", () => { it("should run on self-hosted instance", async (context) => { const testdriver = TestDriver(context, { ip: context.ip || process.env.TD_IP, }); await testdriver.provision.chrome({ url: "https://example.com" }); // ... your test steps }); }); ``` **How it works**: When `TD_OS=windows` with AWS credentials, `context.ip` is automatically set by the setup hook. When running without AWS setup (cloud-hosted), both are undefined and TestDriver uses the cloud. When `TD_IP` is provided manually, it takes precedence. ## Step 4: Run Tests ### Locally ```bash theme={null} TD_OS=windows \ AWS_REGION=us-east-2 \ AWS_LAUNCH_TEMPLATE_ID=lt-xxx \ AMI_ID=ami-0504bf50fad62f312 \ vitest run ``` Each test gets its own fresh EC2 instance that's automatically terminated after completion. ### In GitHub Actions Automate testing with self-hosted instances in your CI/CD pipeline. TestDriver automatically spawns a fresh instance for each test, runs the test, and terminates the instance. ```yaml .github/workflows/test.yml theme={null} name: TestDriver Self-Hosted Windows Tests on: push: branches: [main] pull_request: jobs: test: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' - name: Install dependencies run: npm ci - name: Run Windows tests with self-hosted instances run: npx vitest run examples/*.test.mjs env: TD_API_KEY: ${{ secrets.TD_API_KEY }} TD_OS: windows AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_REGION: us-east-2 AWS_LAUNCH_TEMPLATE_ID: ${{ secrets.AWS_LAUNCH_TEMPLATE_ID }} AMI_ID: ${{ secrets.AMI_ID }} - name: Upload test results if: always() uses: actions/upload-artifact@v4 with: name: test-results path: test-report.junit.xml ``` **Automatic Instance Management**: Setting `TD_OS=windows` with AWS credentials enables automatic instance spawning. Each test gets its own fresh instance that's terminated after the test completes. ### Required GitHub Secrets | Secret | Description | Example | | ------------------------ | -------------------------- | ----------------------------------------------------------- | | `AWS_ACCESS_KEY_ID` | AWS access key | `AKIAIOSFODNN7EXAMPLE` | | `AWS_SECRET_ACCESS_KEY` | AWS secret key | `wJalrXUtnFEMI/K7MDENG...` | | `AWS_REGION` | AWS region | `us-east-2` | | `AWS_LAUNCH_TEMPLATE_ID` | From CloudFormation output | `lt-07c53ce8349b958d1` | | `AMI_ID` | TestDriver AMI ID | `ami-0504bf50fad62f312` | | `TD_API_KEY` | Your TestDriver API key | From [console.testdriver.ai](https://console.testdriver.ai) | Add these as **GitHub Repository Secrets** under Settings β†’ Secrets and variables β†’ Actions ### Example Workflows For complete production examples, see: * **[Scheduled Tests](https://github.com/testdriverai/testdriverai/blob/main/.github/workflows/acceptance-windows-scheduled.yaml)** β€” Daily automated test runs * **[Reusable Workflow](https://github.com/testdriverai/testdriverai/blob/main/.github/workflows/windows-self-hosted.yaml)** β€” Shared configuration for PR and scheduled tests ## Advanced Usage ### Using an Existing Instance If you already have a running instance, you can skip automatic spawning by providing `TD_IP`: ```bash theme={null} TD_OS=windows TD_IP=1.2.3.4 vitest run ``` The `setup-aws` hook will detect `TD_IP` is already set and skip spawning a new instance. ### Manual Instance Management For advanced use cases, you can manually spawn instances using the [`spawn-runner.sh`](https://github.com/testdriverai/testdriverai/blob/main/setup/aws/spawn-runner.sh) script: ```bash theme={null} AWS_REGION=us-east-2 \ AMI_ID=ami-0504bf50fad62f312 \ AWS_LAUNCH_TEMPLATE_ID=lt-xxx \ bash setup/aws/spawn-runner.sh ``` Output: ``` PUBLIC_IP=1.2.3.4 INSTANCE_ID=i-1234567890abcdef0 AWS_REGION=us-east-2 ``` Then manually terminate when done: ```bash theme={null} aws ec2 terminate-instances \ --instance-ids i-1234567890abcdef0 \ --region us-east-2 ``` For complete production examples, see: * **[Scheduled Tests](https://github.com/testdriverai/testdriverai/blob/main/.github/workflows/acceptance-windows-scheduled.yaml)** β€” Daily automated test runs * **[Reusable Workflow](https://github.com/testdriverai/testdriverai/blob/main/.github/workflows/windows-self-hosted.yaml)** β€” Shared configuration for PR and scheduled tests ### Connecting to an Instance You can connect to running instances via: * **RDP** β€” Use the public IP on port 3389 * **VNC** β€” Access via web browser at `http://:5900` * **AWS Console** β€” Use EC2 Instance Connect or Session Manager Stopped instances retain their EBS volumes and can be restarted later. Terminated instances are permanently deleted. Always terminate instances when done to avoid storage costs. ## AMI Customization The TestDriver Golden Image comes pre-configured with: * Windows Server with desktop environment * VNC + web server for remote access * Python, Node.js, Git * TestDriver agent and dependencies ### Creating a Custom AMI You can customize the AMI to include additional software or configurations: Use the default credentials: * **Username**: `testdriver` * **Password**: `wwv9uJ0sqlulbN3` **Critical**: Run the password rotation script immediately: ```powershell theme={null} C:\testdriver\RotateLocalPasswords.ps1 ``` Save the new password securely. Install any additional dependencies, configure settings, or modify the environment as needed. Use the AWS console or CLI to create an AMI from your modified instance. Update your workflow to use the new AMI ID. **Security**: Never use the default password in production. Always rotate passwords before creating custom AMIs. ## Security Best Practices ### Network Security * **Restrict CIDR blocks** β€” Only allow access from known IP ranges * **Use VPC endpoints** β€” For private AWS service communication * **Enable VPC Flow Logs** β€” For network monitoring ### AWS Authentication Use OIDC instead of long-term credentials for GitHub Actions: ```yaml theme={null} permissions: id-token: write contents: read steps: - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsRole aws-region: us-east-2 ``` See [GitHub's OIDC documentation](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect) for setup instructions. ### Instance Security * **Terminate immediately** after tests complete * **Monitor costs** with AWS billing alerts * **Use least-privilege IAM roles** * **Enable CloudTrail** for audit logging ## Troubleshooting ### Instance Not Responding * **Check security groups** β€” Verify required ports are open (RDP 3389, VNC 5900, TestDriver ports) * **Verify status checks** β€” Ensure instance has passed AWS status checks * **Check AMI compatibility** β€” Some instance types don't support certain AMIs ### Connection Timeouts * Verify network connectivity from CI runner to instance * Check VPC routing and internet gateway configuration * Confirm instance is in the correct subnet ### AWS CLI Errors * Validate credentials and permissions * Check service quotas and limits * Verify region consistency across all resources # Learn Source: https://docs.testdriver.ai/v7/caching How TestDriver learns your app and caches what it discovers for instant, deterministic replays Once the agent has [explored your app](/v7/generating-tests), TestDriver remembers what it found. Every element the AI vision agent discovers is cached with a vision fingerprintβ€”a perceptual hash of the screen state where it was located. On the next run, TestDriver matches against that cache instead of calling the AI again. Passing tests replay instantly, deterministically, and cheaply. This learning is what makes TestDriver fast. Intelligent caching delivers up to **1.7x faster** test execution by skipping redundant AI vision analysisβ€”the agent only thinks when it sees something new. ```javascript theme={null} // First run: builds cache await testdriver.find('submit button'); // Second run: exact match await testdriver.find('submit button'); ``` ## Automatic Caching Learning is enabled automatically with zero configuration. The cache keyβ€”the fingerprint TestDriver uses to recognize what it already knowsβ€”is computed from: * **File hash**: SHA-256 hash of the test file contents * **Selector prompt**: The exact text description passed to `find()` * **Screenshot context**: Perceptual hash of the current screen state * **Platform**: Operating system and browser version When you modify your test file, the hash changes automatically, invalidating stale cache entries and ensuring fresh AI analysis with your updated test logic. ```javascript theme={null} import { test } from 'vitest'; import { chrome } from 'testdriverai/presets'; test('auto-cached test', async (context) => { const { testdriver } = await chrome(context, { url: 'https://example.com' }); // First call: AI analyzes screen, saves to cache await testdriver.find('More information link'); // 2.1s // Second call: cache hit, instant response await testdriver.find('More information link'); // 12ms ⚑ }); ``` ## Managing the Cache You can clear the cache within the TestDriver console. There, you'll also find previews of cached elements, the input prompts, as well as analytics on cache hit rates. Manage and clear your test cache from the TestDriver console. ## Debugging Cache Hits and Misses You can track what TestDriver has learned by inspecting cache performance in your tests: ```javascript theme={null} test('monitor cache performance', async (context) => { const { testdriver } = await chrome(context, { url }); const element = await testdriver.find('submit button'); if (element.cacheHit) { console.log('βœ… Cache hit - instant response'); console.log('Strategy:', element.cacheStrategy); // 'exact', 'pixeldiff', or 'template' console.log('Similarity:', `${(element.similarity * 100).toFixed(1)}%`); console.log('Cache age:', element.cacheCreatedAt); } else { console.log('⏱️ Cache miss - AI analysis performed'); console.log('New cache entry created'); } }); ``` ## Configuring the Cache You can configure how TestDriver learns globally when initializing TestDriver: ```javascript theme={null} import { TestDriver } from 'testdriverai'; const testdriver = new TestDriver({ apiKey: process.env.TD_API_KEY, cacheKey: 'my-test-suite', // cache-key for this instance cacheDefaults: { threshold: 0.05, // 95% similarity } }); ``` It's also possible to override cache settings per `find()` call: ```javascript theme={null} // Default: 95% similarity required await testdriver.find('submit button'); // Explicit strict threshold await testdriver.find('submit button', { cacheThreshold: 0.01 // 99% similarity }); ``` ## Caching with Variables Custom cache keys prevent cache pollution when using variables in prompts, dramatically improving cache hit ratesβ€”so TestDriver reuses what it learned even when your data changes. ```javascript theme={null} // ❌ Without cache key - creates new cache for each variable value const email = 'user@example.com'; await testdriver.find(`input for ${email}`); // Cache miss every time // βœ… With cache key - reuses cache regardless of variable const email = 'user@example.com'; await testdriver.find(`input for ${email}`, { cacheKey: 'email-input' }); // Also useful for dynamic IDs, names, or other changing data const orderId = generateOrderId(); await testdriver.find(`order ${orderId} status`, { cacheKey: 'order-status' // Same cache for all orders }); ``` ## Next Now that TestDriver has learned your app, run your tests in CI and locallyβ€”replaying the cache for fast, deterministic results. # captcha() Source: https://docs.testdriver.ai/v7/captcha Solve captchas using 2captcha service # Captcha Solving TestDriver can automatically solve captchas during your tests using the 2captcha service. This feature works on both **Linux** and **Windows** sandboxes. ## Quick Start ```javascript theme={null} const result = await testdriver.captcha({ apiKey: process.env.TWOCAPTCHA_API_KEY, }); console.log(result.success); // true console.log(result.token); // The solved captcha token ``` That's it! TestDriver will automatically: * Detect the captcha type on the page * Extract the sitekey * Solve the captcha via 2captcha * Inject the token into the page * Trigger any callbacks ## Supported Captcha Types | Type | Auto-Detected | Notes | | -------------------- | ------------- | ----------------------- | | reCAPTCHA v2 | βœ… | Including invisible | | reCAPTCHA v3 | βœ… | Action is auto-detected | | hCaptcha | βœ… | | | Cloudflare Turnstile | βœ… | | ## Getting a 2captcha API Key 1. Sign up at [2captcha.com](https://2captcha.com) 2. Add funds to your account 3. Copy your API key from the dashboard ## Configuration Options ```javascript theme={null} const result = await testdriver.captcha({ // Required apiKey: '2CAPTCHA_API_KEY', // Optional - usually auto-detected sitekey: '6Le...', // Override auto-detected sitekey type: 'recaptcha_v3', // Override auto-detected type action: 'submit', // reCAPTCHA v3 action // Timing timeout: 120000, // Max wait time (default: 120s) pollInterval: 5000, // Poll interval (default: 5s) }); ``` ## Full Example ```javascript theme={null} import { describe, expect, it } from "vitest"; import { TestDriver } from "testdriver"; describe("Checkout flow", () => { it("should complete checkout with captcha", async (context) => { const testdriver = TestDriver(context); // Navigate to checkout page await testdriver.provision.chrome({ url: 'https://example.com/checkout', }); // Fill out form await testdriver.type({ text: 'John Doe', selector: '#name' }); await testdriver.type({ text: 'john@example.com', selector: '#email' }); // Solve the captcha const result = await testdriver.captcha({ apiKey: process.env.TWOCAPTCHA_API_KEY, }); expect(result.success).toBe(true); // Submit the form await testdriver.click({ selector: '#submit' }); // Verify success await testdriver.find({ text: 'Order confirmed' }); }, 180000); }); ``` ## Environment Variables You can set your API key as an environment variable: ```bash theme={null} export TWOCAPTCHA_API_KEY=your_api_key_here ``` Then use it in your tests: ```javascript theme={null} const result = await testdriver.captcha({ apiKey: process.env.TWOCAPTCHA_API_KEY, }); ``` ### GitHub Actions Add the key to your repository secrets and expose it in your workflow: ```yaml theme={null} - name: Run Tests run: vitest run env: TWOCAPTCHA_API_KEY: ${{ secrets.TWOCAPTCHA_API_KEY }} ``` ## How It Works 1. **Detection**: Scans the page for captcha elements (`data-sitekey`, script tags, etc.) 2. **Submit**: Sends the captcha challenge to 2captcha's solving service 3. **Poll**: Waits for human solvers to complete the captcha 4. **Inject**: Injects the solved token into the page's hidden fields 5. **Callback**: Triggers any JavaScript callbacks the page expects ## Troubleshooting ### "Could not auto-detect captcha" The captcha element wasn't found on the page. Try: * Waiting for the page to fully load before calling `captcha()` * Providing the `sitekey` and `type` manually ### Timeout errors Captcha solving typically takes 10-30 seconds. If you're getting timeouts: * Increase the `timeout` option * Check your 2captcha balance * Verify the captcha type is correct ### Token not working Some sites validate tokens immediately. Make sure: * The token is injected before form submission * The captcha type matches what the site expects * For reCAPTCHA v3, the `action` parameter matches the site's expected action ## Requirements * Chrome must be launched with remote debugging enabled (automatic on all sandboxes) * A valid 2captcha API key with sufficient balance # Integrating into CI/CD Source: https://docs.testdriver.ai/v7/ci-cd Run TestDriver tests in CI/CD with parallel execution and cross-platform support TestDriver integrates seamlessly with popular CI providers, enabling automated end-to-end testing on every push and pull request. ## Authentication On **GitHub Actions, prefer OIDC** via the published `testdriverai/action` β€” there's no `TD_API_KEY` secret to store, copy, or rotate. The action proves the workflow is running inside your org and TestDriver exchanges that proof for your team's key at run time. See the GitHub Actions tab below. For other CI providers (or self-hosted runners without OIDC), fall back to a stored API key from [console.testdriver.ai/team](https://console.testdriver.ai/team), added as a `TD_API_KEY` secret in your CI provider's settings. Never commit your API key directly in code. Always use OIDC or your CI provider's secrets management. ## CI Provider Examples ### Authenticate with OIDC via `testdriverai/action` (recommended) Use the published [`testdriverai/action`](https://github.com/testdriverai/action) β€” it mints the OIDC token, exchanges it for your team's API key, and exports `TD_API_KEY` for the steps that follow. **No `TD_API_KEY` secret to store or rotate.** One-time setup: authorize the [TestDriver GitHub App](https://console.testdriver.ai) for your org so the org β†’ team binding exists. If your org authorized the App before OIDC support shipped, re-authorize once. If the App isn't authorized, the action fails with a console link (or falls back to the `api-key` secret if you provide one). ```yaml .github/workflows/testdriver.yml theme={null} name: TestDriver Tests on: push: branches: [main] pull_request: branches: [main] jobs: test: runs-on: ubuntu-latest permissions: id-token: write # REQUIRED to mint an OIDC token contents: read steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' - run: npm ci - name: Authenticate to TestDriver uses: testdriverai/action@stable # pin @stable / @canary / @test to your SDK channel with: api-key: ${{ secrets.TD_API_KEY }} # optional fallback if OIDC isn't set up - name: Run TestDriver tests run: npx vitest run ``` ### Stored-key fallback Only if you can't use OIDC (e.g. self-hosted runners without an OIDC provider). Add the key as a secret and pass it via `env`: 1. Navigate to your GitHub repository 2. Go to **Settings** β†’ **Secrets and variables** β†’ **Actions** 3. Click **New repository secret** 4. Name: `TD_API_KEY`, Value: your API key 5. Click **Add secret** ### Basic Workflow Create `.github/workflows/testdriver.yml`: ```yaml .github/workflows/testdriver.yml theme={null} name: TestDriver Tests on: push: branches: [main] pull_request: branches: [main] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' - run: npm ci - name: Run TestDriver tests env: TD_API_KEY: ${{ secrets.TD_API_KEY }} run: vitest --run ``` ### Parallel Execution Use matrix strategy to run tests in parallel: ```yaml .github/workflows/testdriver-parallel.yml theme={null} name: TestDriver Tests (Parallel) on: [push, pull_request] jobs: test: runs-on: ubuntu-latest strategy: fail-fast: false matrix: shard: [1, 2, 3, 4] steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' - run: npm ci - name: Run tests (shard ${{ matrix.shard }}/4) env: TD_API_KEY: ${{ secrets.TD_API_KEY }} run: vitest --run --shard=${{ matrix.shard }}/4 ``` ### Multi-Platform Testing ```yaml .github/workflows/testdriver-multiplatform.yml theme={null} name: TestDriver Tests (Multi-Platform) on: [push, pull_request] jobs: test: runs-on: ubuntu-latest strategy: fail-fast: false matrix: td-os: [linux, windows] steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' - run: npm ci - name: Run tests on ${{ matrix.td-os }} env: TD_API_KEY: ${{ secrets.TD_API_KEY }} TD_OS: ${{ matrix.td-os }} run: vitest --run ``` ### Adding Secrets 1. Go to your GitLab project 2. Navigate to **Settings** β†’ **CI/CD** β†’ **Variables** 3. Click **Add variable** 4. Key: `TD_API_KEY`, Value: your API key 5. Check **Mask variable** and click **Add variable** ### Basic Pipeline Create `.gitlab-ci.yml`: ```yaml .gitlab-ci.yml theme={null} stages: - test testdriver: stage: test image: node:20 cache: paths: - node_modules/ script: - npm ci - vitest --run variables: TD_API_KEY: $TD_API_KEY ``` ### Parallel Execution ```yaml .gitlab-ci.yml theme={null} stages: - test .testdriver-base: stage: test image: node:20 cache: paths: - node_modules/ before_script: - npm ci variables: TD_API_KEY: $TD_API_KEY testdriver-shard-1: extends: .testdriver-base script: - vitest --run --shard=1/4 testdriver-shard-2: extends: .testdriver-base script: - vitest --run --shard=2/4 testdriver-shard-3: extends: .testdriver-base script: - vitest --run --shard=3/4 testdriver-shard-4: extends: .testdriver-base script: - vitest --run --shard=4/4 ``` ### Multi-Platform Testing ```yaml .gitlab-ci.yml theme={null} stages: - test .testdriver-base: stage: test image: node:20 cache: paths: - node_modules/ before_script: - npm ci variables: TD_API_KEY: $TD_API_KEY testdriver-linux: extends: .testdriver-base variables: TD_OS: linux script: - vitest --run testdriver-windows: extends: .testdriver-base variables: TD_OS: windows script: - vitest --run ``` ### Adding Secrets 1. Go to your CircleCI project 2. Click **Project Settings** β†’ **Environment Variables** 3. Click **Add Environment Variable** 4. Name: `TD_API_KEY`, Value: your API key ### Basic Config Create `.circleci/config.yml`: ```yaml .circleci/config.yml theme={null} version: 2.1 jobs: test: docker: - image: cimg/node:20.0 steps: - checkout - restore_cache: keys: - npm-deps-{{ checksum "package-lock.json" }} - run: npm ci - save_cache: key: npm-deps-{{ checksum "package-lock.json" }} paths: - node_modules - run: name: Run TestDriver tests command: vitest --run environment: TD_API_KEY: ${TD_API_KEY} workflows: test: jobs: - test ``` ### Parallel Execution ```yaml .circleci/config.yml theme={null} version: 2.1 jobs: test: docker: - image: cimg/node:20.0 parallelism: 4 steps: - checkout - restore_cache: keys: - npm-deps-{{ checksum "package-lock.json" }} - run: npm ci - save_cache: key: npm-deps-{{ checksum "package-lock.json" }} paths: - node_modules - run: name: Run TestDriver tests command: | vitest --run --shard=$((CIRCLE_NODE_INDEX + 1))/$CIRCLE_NODE_TOTAL environment: TD_API_KEY: ${TD_API_KEY} workflows: test: jobs: - test ``` ### Multi-Platform Testing ```yaml .circleci/config.yml theme={null} version: 2.1 jobs: test: docker: - image: cimg/node:20.0 parameters: td-os: type: string steps: - checkout - run: npm ci - run: name: Run TestDriver tests on << parameters.td-os >> command: vitest --run environment: TD_API_KEY: ${TD_API_KEY} TD_OS: << parameters.td-os >> workflows: test: jobs: - test: td-os: linux - test: td-os: windows ``` ### Adding Secrets 1. Go to your Azure DevOps project 2. Navigate to **Pipelines** β†’ **Library** β†’ **Variable groups** 3. Create a new variable group or edit existing 4. Add variable: `TD_API_KEY` with your API key 5. Click the lock icon to make it secret ### Basic Pipeline Create `azure-pipelines.yml`: ```yaml azure-pipelines.yml theme={null} trigger: - main pool: vmImage: 'ubuntu-latest' steps: - task: NodeTool@0 inputs: versionSpec: '20.x' displayName: 'Setup Node.js' - script: npm ci displayName: 'Install dependencies' - script: vitest --run displayName: 'Run TestDriver tests' env: TD_API_KEY: $(TD_API_KEY) ``` ### Parallel Execution ```yaml azure-pipelines.yml theme={null} trigger: - main pool: vmImage: 'ubuntu-latest' strategy: matrix: shard1: SHARD: '1/4' shard2: SHARD: '2/4' shard3: SHARD: '3/4' shard4: SHARD: '4/4' steps: - task: NodeTool@0 inputs: versionSpec: '20.x' - script: npm ci displayName: 'Install dependencies' - script: vitest --run --shard=$(SHARD) displayName: 'Run TestDriver tests' env: TD_API_KEY: $(TD_API_KEY) ``` ### Multi-Platform Testing ```yaml azure-pipelines.yml theme={null} trigger: - main pool: vmImage: 'ubuntu-latest' strategy: matrix: linux: TD_OS: 'linux' windows: TD_OS: 'windows' steps: - task: NodeTool@0 inputs: versionSpec: '20.x' - script: npm ci displayName: 'Install dependencies' - script: vitest --run displayName: 'Run TestDriver tests on $(TD_OS)' env: TD_API_KEY: $(TD_API_KEY) TD_OS: $(TD_OS) ``` ### Adding Secrets 1. Go to **Manage Jenkins** β†’ **Credentials** 2. Select the appropriate domain 3. Click **Add Credentials** 4. Kind: **Secret text** 5. ID: `td-api-key`, Secret: your API key ### Basic Pipeline Create `Jenkinsfile`: ```groovy Jenkinsfile theme={null} pipeline { agent { docker { image 'node:20' } } environment { TD_API_KEY = credentials('td-api-key') } stages { stage('Install') { steps { sh 'npm ci' } } stage('Test') { steps { sh 'vitest --run' } } } } ``` ### Parallel Execution ```groovy Jenkinsfile theme={null} pipeline { agent none environment { TD_API_KEY = credentials('td-api-key') } stages { stage('Test') { parallel { stage('Shard 1') { agent { docker { image 'node:20' } } steps { sh 'npm ci' sh 'vitest --run --shard=1/4' } } stage('Shard 2') { agent { docker { image 'node:20' } } steps { sh 'npm ci' sh 'vitest --run --shard=2/4' } } stage('Shard 3') { agent { docker { image 'node:20' } } steps { sh 'npm ci' sh 'vitest --run --shard=3/4' } } stage('Shard 4') { agent { docker { image 'node:20' } } steps { sh 'npm ci' sh 'vitest --run --shard=4/4' } } } } } } ``` ### Multi-Platform Testing ```groovy Jenkinsfile theme={null} pipeline { agent none environment { TD_API_KEY = credentials('td-api-key') } stages { stage('Test') { parallel { stage('Linux') { agent { docker { image 'node:20' } } environment { TD_OS = 'linux' } steps { sh 'npm ci' sh 'vitest --run' } } stage('Windows') { agent { docker { image 'node:20' } } environment { TD_OS = 'windows' } steps { sh 'npm ci' sh 'vitest --run' } } } } } } ``` ## Reading Platform in Tests When using multi-platform testing, read the `TD_OS` environment variable in your test: ```javascript tests/cross-platform.test.mjs theme={null} import { describe, expect, it } from "vitest"; import { TestDriver } from "testdriverai/vitest/hooks"; describe("Cross-platform tests", () => { it("should work on both Linux and Windows", async (context) => { const os = process.env.TD_OS || 'linux'; const testdriver = TestDriver(context, { os: os // 'linux' or 'windows' }); await testdriver.provision.chrome({ url: 'https://example.com', }); const result = await testdriver.assert("the page loaded successfully"); expect(result).toBeTruthy(); }); }); ``` ## Concurrency limits Your plan allows a fixed number of sandboxes running at once. When a test asks for a sandbox and you're already at that limit, the request is **queued** rather than failed immediately: the SDK waits for a slot to free up, retrying every 10 seconds, then proceeds automatically once one opens. This is what lets a parallel CI matrix (many jobs starting at once) work on a plan with fewer slots than jobs β€” the extra jobs simply wait their turn instead of erroring. By default the SDK waits up to **60 seconds** for a slot before giving up with a concurrency-limit error. Control that ceiling with `TD_CONCURRENCY_MAX_WAIT`: | Value | Behavior | | ----------------------------- | -------------------------------------------------------- | | *unset* | Wait up to **60 seconds** (the default). | | `TD_CONCURRENCY_MAX_WAIT=300` | Wait up to **300 seconds** (5 minutes) before giving up. | | `TD_CONCURRENCY_MAX_WAIT=0` | **Don't queue** β€” fail on the first denial. | The value is **in seconds** (fractional values are allowed and rounded to the nearest millisecond). Any invalid or negative value falls back to the 60-second default. The wait applies per sandbox request, across both the initial allocation and the realtime slot-approval handshake. ```yaml theme={null} # Example: a large parallel matrix that may queue for a while. # Give each job up to 5 minutes to acquire a slot before failing. - name: Run TestDriver tests env: TD_API_KEY: ${{ secrets.TD_API_KEY }} TD_CONCURRENCY_MAX_WAIT: "300" run: npx vitest run ``` Raise `TD_CONCURRENCY_MAX_WAIT` when you run more parallel jobs than your plan has slots and would rather they queue than fail. Set it to `0` when you'd prefer a job to **fail fast** on a busy account (e.g. a quick smoke test that shouldn't sit waiting). When jobs routinely give up waiting, that's the signal to [add more slots](https://console.testdriver.ai/checkout/pro). ## Viewing Results All test runs are automatically recorded and visible in your TestDriver dashboard at [console.testdriver.ai](https://console.testdriver.ai): * All test runs with pass/fail status * Video replays of each test * Error messages and screenshots on failure * Git commit and branch information * Duration trends over time # click() Source: https://docs.testdriver.ai/v7/click Click at specific coordinates or on elements ## Element Click When called on an Element object, clicks on the located element. ### Syntax ```javascript theme={null} await element.click(action) ``` ### Parameters Type of click action: `'click'`, `'double-click'`, `'right-click'`, `'hover'`, `'mouseDown'`, `'mouseUp'` ### Returns `Promise` ### Examples ```javascript theme={null} // Regular click const button = await testdriver.find('submit button'); await button.click(); // Double-click const file = await testdriver.find('README.txt file'); await file.click('double-click'); // Right-click const item = await testdriver.find('menu item'); await item.click('right-click'); // Hover (same as element.hover()) const tooltip = await testdriver.find('info icon'); await button.click('hover'); ``` ## Coordinate Click Click at specific screen coordinates. ### Syntax ```javascript theme={null} await testdriver.click(x, y, action) ``` ### Parameters X coordinate Y coordinate Type of click: `'click'`, `'double-click'`, `'right-click'`, `'mouseDown'`, `'mouseUp'` ### Returns `Promise` ### Examples ```javascript theme={null} // Click at coordinates await testdriver.click(500, 300); // Double-click at coordinates await testdriver.click(500, 300, 'double-click'); // Right-click at coordinates await testdriver.click(500, 300, 'right-click'); ``` ## Click Actions ### Regular Click Single left-click action. ```javascript theme={null} const button = await testdriver.find('Login button'); await button.click(); ``` ### Double Click Double-click action, commonly used to open files or select text. ```javascript theme={null} const file = await testdriver.find('document.pdf'); await file.click('double-click'); // Or use the dedicated method await file.doubleClick(); ``` ### Right Click Right-click to open context menus. ```javascript theme={null} const folder = await testdriver.find('Documents folder'); await folder.click('right-click'); // Or use the dedicated method await folder.rightClick(); ``` ### Mouse Down / Mouse Up For drag operations or custom click behavior. ```javascript theme={null} const draggable = await testdriver.find('draggable item'); await draggable.click('mouseDown'); // Move to target const dropZone = await testdriver.find('drop zone'); await dropZone.hover(); await dropZone.click('mouseUp'); // Or use dedicated methods await draggable.mouseDown(); await dropZone.mouseUp(); ``` ## Best Practices **Prefer element clicks over coordinate clicks** Element-based clicking is more reliable and resolution-independent: ```javascript theme={null} // βœ… Preferred const button = await testdriver.find('submit button'); await button.click(); // ❌ Avoid (fragile) await testdriver.click(500, 300); ``` **Verify element was found** ```javascript theme={null} const element = await testdriver.find('button'); if (!element.found()) { throw new Error('Element not found'); } await element.click(); ``` **Element must be found before clicking** The `find()` method automatically locates elements, but clicking an element that wasn't found will throw an error: ```javascript theme={null} const element = await testdriver.find('button'); // This will throw if element wasn't found await element.click(); ``` ## Use Cases ```javascript theme={null} const submitBtn = await testdriver.find('submit button'); await submitBtn.click(); const cancelBtn = await testdriver.find('cancel button'); await cancelBtn.click(); ``` ```javascript theme={null} const file = await testdriver.find('report.pdf file icon'); await file.doubleClick(); ``` ```javascript theme={null} const item = await testdriver.find('file item'); await item.rightClick(); // Select menu option const deleteOption = await testdriver.find('Delete option'); await deleteOption.click(); ``` ```javascript theme={null} const source = await testdriver.find('source item'); await source.mouseDown(); const target = await testdriver.find('target zone'); await target.hover(); await target.mouseUp(); ``` ## Complete Example ```javascript theme={null} import { beforeAll, afterAll, describe, it } from 'vitest'; import TestDriver from 'testdriverai'; describe('Click Interactions', () => { let testdriver; beforeAll(async () => { client = new TestDriver(process.env.TD_API_KEY); await testdriver.auth(); await testdriver.connect(); }); afterAll(async () => { await testdriver.disconnect(); }); it('should perform various click actions', async () => { await testdriver.focusApplication('Google Chrome'); // Regular click const loginBtn = await testdriver.find('login button'); await loginBtn.click(); // Right-click for context menu const profileIcon = await testdriver.find('profile icon'); await profileIcon.rightClick(); const settingsOption = await testdriver.find('Settings menu option'); await settingsOption.click(); // Double-click to edit const nameField = await testdriver.find('name display field'); await nameField.doubleClick(); // Verify state await testdriver.assert('name field is now editable'); }); it('should perform drag and drop', async () => { const item = await testdriver.find('draggable item'); await item.mouseDown(); // Drag to new location const dropTarget = await testdriver.find('drop area'); await dropTarget.hover(); await dropTarget.mouseUp(); // Verify await testdriver.assert('item is in the drop area'); }); }); ``` ## Related Methods * [`find()`](/v7/find) - Locate elements to click * [`hover()`](/v7/hover) - Hover without clicking * [`doubleClick()`](/v7/double-click) - Dedicated double-click method * [`rightClick()`](/v7/right-click) - Dedicated right-click method # TestDriver Client Source: https://docs.testdriver.ai/v7/client Initialize and configure the TestDriver SDK client ## Overview The `TestDriver` client is the main entry point for the SDK. It handles authentication, sandbox connection, and provides access to all testing methods. ## Constructor ```javascript theme={null} const testdriver = new TestDriver(apiKey, options) ``` ### Parameters Your TestDriver API key from the [dashboard](https://console.testdriver.ai/team) Configuration options for the client Operating system for the sandbox: `'windows'` or `'linux'` Screen resolution for the sandbox (e.g., `'1920x1080'`, `'1366x768'`). Custom resolutions are only available on Enterprise plans. API endpoint URL (typically only changed for self-hosted deployments) Enable or disable usage analytics Enable or disable console logging Automatically capture screenshots before and after each command. Screenshots are saved to `.testdriver/screenshots//` with descriptive filenames that include the line number and action name. Format: `---L-.png` Force creation of a new sandbox instead of reusing an existing one Reattach to the last used sandbox instead of creating a new one. When `true`, the SDK reads the sandbox id from `.testdriver/last-sandbox` (written automatically on every successful connect) and rejoins that VM. Provision methods (`chrome`, `vscode`, `installer`, etc.) are skipped because the application is already running. The previous sandbox must still be alive β€” see [`keepAlive`](#keepalive) and the [Run guide](/v7/copilot/running-tests#keeping-machines-alive-between-runs). Reattach to a specific sandbox id instead of the one recorded in `.testdriver/last-sandbox`. Use this for CI matrices or to pin a chain of tests to a known VM. Implies `reconnect: true` behavior (provision calls are skipped). Preview mode for live test visualization: * `"browser"` β€” Opens debugger in default browser (default) * `"ide"` β€” Opens preview in IDE panel (VSCode, Cursor - requires TestDriver extension) * `"none"` β€” Headless mode, no visual preview **Deprecated**: Use `preview: "none"` instead. Run in headless mode without opening the debugger. Keep the sandbox alive when a test fails so you can reconnect and debug interactively. The sandbox ID is printed to the console. Direct IP address to connect to a running sandbox instance (for self-hosted deployments) Custom AMI ID for the sandbox instance (AWS deployments, e.g., `'ami-1234'`) EC2 instance type for the sandbox (AWS deployments, e.g., `'i3.metal'`) Enable or disable element caching, or provide advanced threshold configuration. Enable or disable caching Fine-tune cache matching Thresholds for `find()` operations Pixel diff threshold for screen comparison (0-1). `0.05` = 5% diff allowed. OpenCV template match threshold for element matching (0-1). `0.8` = 80% correlation. Pixel diff threshold for `assert()` operations (0-1). `0.05` = 5% diff allowed. Cache key for element finding operations. If provided, enables caching tied to this key. Global default for AI verification of located elements. When `true`, every `find()` runs a second AI check to confirm the returned coordinates actually match the requested element (catching hallucinated or incorrect positions), at the cost of extra latency. Can be overridden per call via `find(description, { verify: true | false })`. Enable or disable Dashcam video recording Enable or disable screen-change (redraw) detection, or provide advanced configuration. Enable or disable redraw detection Threshold configuration Pixel diff threshold (0-1). Set to `false` to disable screen redraw detection. Enable or disable network activity monitoring Additional environment variables to pass to the sandbox Global AI sampling configuration. Controls how the AI model generates responses for `find()` verification and `assert()` calls. Can be overridden per call. Controls randomness in AI responses. `0` = deterministic (best for verification), higher values = more creative. Default: `0` for find verification, model default for assert. Nucleus and top-k sampling parameters Top-P (nucleus sampling). Limits token choices to the smallest set whose cumulative probability exceeds P. Lower values = more focused responses. Range: 0-1. Top-K sampling. Limits token choices to the top K most likely tokens. `1` = always pick the most likely token. `0` = disabled (consider all tokens). ### Example ```javascript theme={null} import TestDriver from 'testdriverai'; // API key is automatically loaded from TD_API_KEY in .env const testdriver = new TestDriver({ os: 'windows', resolution: '1920x1080', logging: true, analytics: true }); // With AI config for stricter verification const testdriver = new TestDriver({ ai: { temperature: 0, top: { p: 0.9, k: 40 } } }); // Or pass API key explicitly const testdriver = new TestDriver('your-api-key', { os: 'windows' }); ``` ## Authentication ### auth() Authenticate with the TestDriver API. ```javascript theme={null} await testdriver.auth() ``` **Returns:** `Promise` - Authentication token **Example:** ```javascript theme={null} await testdriver.auth(); ``` You must call `auth()` before `connect()`. Most examples call both sequentially. ## Connection Management ### connect() Connect to a sandbox environment. This creates or reconnects to a virtual machine where your tests will run. ```javascript theme={null} await testdriver.connect(options) ``` #### Parameters Connection options Force creation of a new sandbox instead of reusing an existing one Existing sandbox ID to reconnect to Direct IP address to connect to (for self-hosted sandboxes) AMI to use for the sandbox (AWS deployments) Instance type for the sandbox (AWS deployments) Preview mode for live test visualization: * `"browser"` - Opens debugger in default browser (default) * `"ide"` - Opens preview in IDE panel (VSCode, Cursor - requires TestDriver extension) * `"none"` - Headless mode, no visual preview **Deprecated**: Use `preview: "none"` instead. Run in headless mode without opening the debugger. Keep sandbox alive for the specified number of milliseconds after disconnect. Set to `0` to terminate immediately on disconnect. Useful for debugging or reconnecting to the same sandbox. **Returns:** `Promise<Object>` - Sandbox instance details including `instanceId`, `ip`, `vncPort`, etc. #### Examples **Basic connection:** ```javascript theme={null} await testdriver.connect(); ``` **Reconnect to existing sandbox:** ```javascript theme={null} const instance = await testdriver.connect({ sandboxId: 'existing-sandbox-id-123' }); ``` **Self-hosted sandbox:** ```javascript theme={null} await testdriver.connect({ ip: '192.168.1.100' }); ``` ### disconnect() Disconnect from the sandbox and clean up resources. ```javascript theme={null} await testdriver.disconnect() ``` **Returns:** `Promise` **Example:** ```javascript theme={null} afterAll(async () => { await testdriver.disconnect(); }); ``` ## Instance Information ### getInstance() Get the current sandbox instance details. ```javascript theme={null} const instance = testdriver.getInstance() ``` **Returns:** `Object | null` - Sandbox instance information **Example:** ```javascript theme={null} const instance = testdriver.getInstance(); console.log('Instance ID:', instance.instanceId); console.log('IP Address:', instance.ip); ``` ### getSessionId() Get the current session ID for tracking and debugging. ```javascript theme={null} const sessionId = testdriver.getSessionId() ``` **Returns:** `string | null` - Session ID **Example:** ```javascript theme={null} const sessionId = testdriver.getSessionId(); console.log('Session:', sessionId); ``` ## Logging & Events ### setLogging() Enable or disable console logging at runtime. ```javascript theme={null} testdriver.setLogging(enabled) ``` **Parameters:** * `enabled` (boolean) - Whether to enable logging **Example:** ```javascript theme={null} // Disable logging for cleanup operations testdriver.setLogging(false); await testdriver.disconnect(); testdriver.setLogging(true); ``` ### getEmitter() Get the event emitter for custom event handling. ```javascript theme={null} const emitter = testdriver.getEmitter() ``` **Returns:** `EventEmitter2` - Event emitter instance **Example:** ```javascript theme={null} const emitter = testdriver.getEmitter(); emitter.on('command:start', (data) => { console.log('Command started:', data); }); emitter.on('command:success', (data) => { console.log('Command succeeded:', data); }); emitter.on('command:error', (error) => { console.error('Command failed:', error); }); ``` ## Complete Example ```javascript theme={null} import { beforeAll, afterAll, describe, it } from 'vitest'; import TestDriver from 'testdriverai'; describe('My Test Suite', () => { let testdriver; beforeAll(async () => { // Initialize client - API key loaded automatically from .env testdriver = new TestDriver({ os: 'windows', resolution: '1366x768', logging: true }); // Set up event listeners const emitter = testdriver.getEmitter(); emitter.on('log:info', (msg) => console.log('[INFO]', msg)); // Authenticate and connect await testdriver.auth(); const instance = await testdriver.connect(); console.log('Connected to sandbox:', instance.instanceId); }); afterAll(async () => { await testdriver.disconnect(); }); it('runs a test', async () => { // Your test code here }); }); ``` ## Best Practices Use `beforeAll`/`afterAll` to create one sandbox per test suite rather than per test. This significantly reduces execution time. Wrap `connect()` in a try-catch block to handle network issues or quota limits: ```javascript theme={null} try { await testdriver.connect(); } catch (error) { console.error('Failed to connect:', error.message); throw error; } ``` Use `afterAll` or try-finally blocks to ensure `disconnect()` is called even if tests fail. This prevents orphaned sandboxes. Never hardcode API keys. The SDK automatically loads `TD_API_KEY` from your `.env` file: ```bash .env theme={null} TD_API_KEY=your_api_key_here ``` ```javascript theme={null} // API key is loaded automatically - no need to pass it! const testdriver = new TestDriver(); ``` # Prevent Source: https://docs.testdriver.ai/v7/copilot/auto-healing Catch regressions automatically with CI, auto-healing, and GitHub integration Prevent regressions by letting TestDriver run and repair itself on every change. Once you've [explored](/v7/generating-tests), [taught it your app](/v7/caching), [run it](/v7/copilot/running-tests), [validated outcomes](/v7/making-assertions), [adapted to your UI](/v7/performing-actions), and learned to [debug with screenshots](/v7/debugging-with-screenshots), the last step is closing the loop: wire TestDriver into your pull requests so regressions never reach production. Your AI agent can run tests on every PR, investigate failures, and propose fixes β€” and you can drive all of it from GitHub itself, whether you're at your desk or on your phone. ## Use TestDriver in GitHub TestDriver works directly in GitHub's web interface and mobile app. The same MCP server that powers VS Code integration also works in GitHub, letting you create and manage tests from anywhere. ### How It Works When you add a TestDriver agent file to your repository at `.github/agents/testdriver.agent.md`, GitHub Copilot can use TestDriver's MCP tools directly in: * GitHub.com (web browser) * GitHub Mobile app (iOS/Android) * Pull request conversations * Issue comments ### Using TestDriver in GitHub Web Open your repository on GitHub.com. Make sure you have the TestDriver agent file at `.github/agents/testdriver.agent.md`. Click the **Copilot icon** in the GitHub interface to open a chat. You can find this in: * The repository's Code tab * Pull request pages * Issue pages Start your message with `@testdriver`: ``` @testdriver Create a test that verifies the homepage loads correctly at https://myapp.com ``` The agent will spawn a sandbox environment and begin executing, just like in VS Code. As the test runs, screenshots appear directly in the chat. You can see what the AI sees and provide guidance if needed. ### Creating Tests from PR Comments You can create tests directly from pull request reviews. Comment on a PR and mention Copilot: ``` @copilot create a TestDriver test that verifies this new feature works. Test the checkout flow with a guest user. ``` Copilot will: 1. Use the TestDriver MCP server 2. Create a test based on your description 3. Commit the test file to the PR branch This is useful for: * Adding test coverage during code review * Verifying bug fixes before merging * Creating regression tests for new features ### Creating Tests from Issues You can also create tests from issue comments: ``` @copilot Use TestDriver to create a test that reproduces this bug. Navigate to /settings, change the theme, and verify it persists after refresh. ``` The test will be created in a new branch and linked to the issue. ### Mobile App Support The GitHub Mobile app supports Copilot chat, which means you can use TestDriver from your phone: 1. Open the GitHub app 2. Navigate to your repository 3. Tap the Copilot icon 4. Type `@testdriver` followed by your request Screenshots and test progress appear in the chat, letting you create and debug tests on the go. ### Example: PR Review Workflow Here's a complete workflow for adding tests during code review: A developer opens a pull request with a new feature. The reviewer comments: ``` @copilot Create a TestDriver test for this user registration flow. Test both successful registration and validation errors. ``` Copilot spawns TestDriver, creates the tests, and commits them to the PR branch. The new tests run automatically in CI, validating the feature works as expected. ### Agent File Reference The agent file at `.github/agents/testdriver.agent.md` contains the configuration for GitHub Copilot to use TestDriver. Here's the structure: ```yaml theme={null} --- name: testdriver description: An expert at creating and refining automated tests using TestDriver.ai mcp-servers: testdriver: command: npx args: - -p - testdriverai - testdriverai-mcp env: TD_API_KEY: ${TD_API_KEY} --- # TestDriver Expert You are an expert at writing automated tests using TestDriver... ``` The `TD_API_KEY` is pulled from your repository secrets when running in GitHub Actions or from your environment when using the web interface. Make sure `TD_API_KEY` is set in your repository secrets for CI workflows. Go to **Settings β†’ Secrets and variables β†’ Actions** to add it. ## Auto-Healing Tests Auto-healing tests automatically fix themselves when your application changes. By integrating an AI coding agent with TestDriver in your CI pipeline, you can have AI investigate test failures and propose fixes. The example below uses GitHub Copilot in GitHub Actions, but the same approach works with any AI agent that can run TestDriver's MCP server. ### How It Works When a test fails in CI: 1. **GitHub Actions detects the failure** 2. **The AI agent spawns TestDriver** with access to the MCP server 3. **The agent investigates** by running the failing test and analyzing what changed 4. **The agent creates a fix** by updating the test code 5. **A pull request is opened** with the proposed changes for review ### Setting Up Auto-Healing Add your TestDriver API key to your repository secrets: 1. Go to **Settings β†’ Secrets and variables β†’ Actions** 2. Click **New repository secret** 3. Add `TD_API_KEY` with your API key value Add a GitHub Actions workflow that triggers on test failures: ```yaml .github/workflows/auto-heal.yml theme={null} name: Auto-Heal Tests on: workflow_run: workflows: ["Tests"] # Your main test workflow types: [completed] branches: [main] jobs: auto-heal: if: ${{ github.event.workflow_run.conclusion == 'failure' }} runs-on: ubuntu-latest permissions: contents: write pull-requests: write steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: "20" - name: Install dependencies run: npm ci - name: Run failing tests and capture output id: tests continue-on-error: true run: | vitest run 2>&1 | tee test-output.txt echo "output<> $GITHUB_OUTPUT cat test-output.txt >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT env: TD_API_KEY: ${{ secrets.TD_API_KEY }} - name: Invoke Copilot to fix tests uses: github/copilot-action@v1 with: prompt: | @testdriver The following tests failed: ${{ steps.tests.outputs.output }} Please investigate each failure by: 1. Running the failing test 2. Analyzing what changed in the UI or behavior 3. Updating the test code to fix the issue Create a commit with your changes. env: TD_API_KEY: ${{ secrets.TD_API_KEY }} - name: Create Pull Request uses: peter-evans/create-pull-request@v5 with: title: "Auto-heal: Fix failing tests" body: | This PR was automatically generated by the auto-heal workflow. ## Changes The following tests were updated to fix failures detected in CI. ## Review Please review the changes carefully before merging. branch: auto-heal/${{ github.run_id }} commit-message: "fix: auto-heal failing tests" ``` Make sure your main test workflow has a name that matches the `workflows` trigger: ```yaml .github/workflows/tests.yml theme={null} name: Tests # Must match the workflow_run trigger on: push: branches: [main] pull_request: jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: "20" - run: npm ci - run: vitest run env: TD_API_KEY: ${{ secrets.TD_API_KEY }} ``` ### Example: Button Text Change Here's what auto-healing looks like when a button's text changes: A developer changes a button's text from "Submit" to "Send": ```html theme={null} ``` The test fails because it's looking for the old text: ```javascript theme={null} // This now fails const submitButton = await testdriver.find("Submit button"); ``` The auto-heal workflow runs, and the AI agent investigates: ``` The test is looking for a "Submit button" but I see a "Send button" on the page. The button functionality is the same, just the text changed. I'll update the test to use the new text. ``` A pull request is opened with the fix: ```javascript theme={null} // Updated by auto-heal const submitButton = await testdriver.find("Send button"); ``` ### Configuration Options #### Selective Auto-Healing You can limit auto-healing to specific test files or patterns: ```yaml theme={null} - name: Run failing tests run: | # Only heal tests in the e2e directory vitest run tests/e2e/ 2>&1 | tee test-output.txt ``` #### Manual Approval For safety, you can require manual approval before auto-heal runs: ```yaml theme={null} jobs: auto-heal: environment: auto-heal # Requires approval ``` Configure the environment in **Settings β†’ Environments** with required reviewers. #### Limiting Changes Add instructions to constrain what the AI can change: ```yaml theme={null} - name: Invoke Copilot to fix tests uses: github/copilot-action@v1 with: prompt: | @testdriver Fix the failing tests. Rules: - Only update element selectors and text matching - Do not change test logic or assertions - Do not add or remove tests - Keep changes minimal ``` ### Best Practices Auto-heal is a tool, not a replacement for human judgment. Review all changes before merging to ensure the test still validates what you intended. Tests with clear, semantic descriptions are easier for the AI to heal: ```javascript theme={null} // βœ… Good - describes purpose await testdriver.find("primary call-to-action button in the hero section"); // ❌ Bad - too vague await testdriver.find("button"); ``` Configure GitHub notifications or Slack integration to be alerted when auto-heal PRs are created. Monitor how often tests need healing. High heal rates may indicate: * Tests are too brittle * Application is changing rapidly * Element descriptions need improvement ### Limitations Auto-healing works best for: * Element text changes * Layout and styling updates * Minor UI restructuring It may struggle with: * Major workflow changes * New features requiring new assertions * Complex multi-step interactions For significant changes, create new tests by going back to [Explore](/v7/generating-tests) and generating them with the TestDriver agent. Open the TestDriver console to monitor test runs, healing PRs, and CI results across your projects. # Run Source: https://docs.testdriver.ai/v7/copilot/running-tests Set up your environment and run TestDriver tests locally and in CI Run the tests you've explored and learned, anywhere. TestDriver tests are plain [Vitest](https://vitest.dev) files, so they run the same way on your machine and in CI β€” across web, desktop, and extensions on real VMs. This page walks through getting set up, configuring **where** and **how** your tests run (the device and app under test, and the sandbox machine), and actually running them locally and in CI. If you haven't written tests yet, start with [Explore](/v7/generating-tests) to generate your first tests and [Learn](/v7/caching) to make them fast and reliable. ## Setup TestDriver integrates with AI coding assistants through the VS Code extension and MCP server. The same MCP server works with GitHub Copilot, Cursor, Claude Desktop, and any other MCP-capable assistant. This section walks you through the complete setup. ### Prerequisites Before you begin, you'll need: * **An MCP-capable AI assistant** β€” Such as GitHub Copilot, Cursor, or Claude Desktop. For Copilot, a [free tier](https://github.com/features/copilot/plans) is available. * **TestDriver Account** β€” Create a free account at [console.testdriver.ai](https://console.testdriver.ai/team) to get your API key. 60 free device minutes, no credit card required. * **VS Code** β€” The TestDriver extension provides the best experience with live preview and integrated test running. ### Setup Steps The extension provides: * One-click sign-in and project initialization * Live preview panel for watching tests execute * MCP server configuration Sign in to connect your account and API key. 1. Open the command palette (`Cmd+Shift+P` or `Ctrl+Shift+P`) 2. Run **TestDriver: Login** 3. Your browser will open to the TestDriver sign-in page 4. Sign in (or create an account) 5. You'll be redirected back to VS Code, now signed in The extension automatically saves your API key to VS Code's secure storage and your workspace `.env` file. Set up TestDriver in your project with a single command. 1. Open the command palette (`Cmd+Shift+P` or `Ctrl+Shift+P`) 2. Run **TestDriver: Init Project** This command: * Creates a `package.json` with TestDriver and Vitest dependencies * Generates a `vitest.config.mjs` with proper timeout settings * Creates example test files in `tests/` * Sets up `.env` with your API key * Creates the TestDriver agent file at `.github/agents/testdriver.agent.md` * Configures the MCP server If you already have a `package.json`, the command will add the necessary dependencies to it. The MCP server enables your AI assistant to control TestDriver sandboxes. After initialization, the MCP configuration is created at `.vscode/mcp.json`: ```json .vscode/mcp.json theme={null} { "servers": { "testdriver": { "command": "npx", "args": ["-p", "testdriverai", "testdriverai-mcp"], "env": { "TD_PREVIEW": "ide", "TD_API_KEY": "your-api-key" } } } } ``` **To start the MCP server:** 1. Open the command palette (`Cmd+Shift+P` or `Ctrl+Shift+P`) 2. Run **MCP: List Servers** 3. Click on the **testdriver** server 4. Select **Start Server** You can also click the MCP icon in the status bar to manage servers. See the [VS Code MCP documentation](https://code.visualstudio.com/docs/copilot/chat/mcp-servers) for more details on managing MCP servers. Make sure your API key is set. The extension uses the key from your sign-in, but you can also set it via the `TD_API_KEY` environment variable. For the best experience running tests, install the Vitest extension: Run tests with GUI mode from the Test Explorer After installation, you'll see a beaker icon in the sidebar for accessing the Test Explorer. ### Verify Your Setup To verify everything is configured correctly: 1. Open the command palette and run **TestDriver: Check Status** 2. You should see: * βœ… Signed in * βœ… MCP server configured * βœ… Project initialized ### The Agent File During initialization, TestDriver creates an agent file at `.github/agents/testdriver.agent.md`. This file tells your AI assistant how to use TestDriver's MCP tools. The agent has access to tools like: * `session_start` β€” Launch a sandbox with Chrome or other apps * `find` / `click` / `type` β€” Interact with elements on screen * `assert` β€” Verify conditions using AI vision * `screenshot` β€” Capture the current screen state ## Configuring the Device Provision methods are the starting point for most tests. They launch applications in your sandbox and prepare the environment for testing β€” a browser, a desktop app, a Chrome extension, or VS Code. ### Chrome Browser The most common starting point for web testing. Launches Chrome browser and navigates to a URL. ```javascript theme={null} await testdriver.provision.chrome({ url: 'https://example.com', }); ``` #### Options | Option | Type | Default | Description | | ----------- | ------- | ----------------------------------------- | --------------------------- | | `url` | string | `'http://testdriver-sandbox.vercel.app/'` | URL to navigate to | | `maximized` | boolean | `true` | Start browser maximized | | `guest` | boolean | `false` | Use guest mode (no profile) | #### Example: Basic Web Test ```javascript theme={null} import { describe, expect, it } from "vitest"; import { TestDriver } from "testdriverai/vitest/hooks"; describe("Login Flow", () => { it("should log in successfully", async (context) => { const testdriver = TestDriver(context); await testdriver.provision.chrome({ url: 'https://myapp.com/login', }); await testdriver.find("Email input").click(); await testdriver.type("user@example.com"); await testdriver.find("Password input").click(); await testdriver.type("password123"); await testdriver.find("Sign In button").click(); const result = await testdriver.assert("the dashboard is visible"); expect(result).toBeTruthy(); }); }); ``` `provision.chrome()` automatically starts Dashcam recording and waits for Chrome to be ready before returning. ### Chrome Extensions Launch Chrome with a custom extension loaded. Supports both local extensions and Chrome Web Store extensions. #### Load from Local Path Clone or create an extension locally, then load it: ```javascript theme={null} // First, get the extension onto the sandbox await testdriver.exec( 'sh', 'git clone https://github.com/user/my-extension.git /tmp/my-extension', 60000 ); // Launch Chrome with the extension await testdriver.provision.chromeExtension({ extensionPath: '/tmp/my-extension', url: 'https://example.com' }); ``` #### Load from Chrome Web Store Load any published extension by its Chrome Web Store ID: ```javascript theme={null} await testdriver.provision.chromeExtension({ extensionId: 'cjpalhdlnbpafiamejdnhcphjbkeiagm', // uBlock Origin url: 'https://example.com' }); ``` Find the extension ID in the Chrome Web Store URL. For example, `https://chrome.google.com/webstore/detail/ublock-origin/cjpalhdlnbpafiamejdnhcphjbkeiagm` β†’ ID is `cjpalhdlnbpafiamejdnhcphjbkeiagm` #### Options | Option | Type | Default | Description | | --------------- | ------- | ------- | ------------------------------------------ | | `extensionPath` | string | - | Local path to unpacked extension directory | | `extensionId` | string | - | Chrome Web Store extension ID | | `url` | string | - | URL to navigate to after launch | | `maximized` | boolean | `true` | Start browser maximized | You must provide either `extensionPath` or `extensionId`, but not both. #### Example: Testing a Chrome Extension ```javascript theme={null} import { describe, expect, it } from "vitest"; import { TestDriver } from "testdriverai/vitest/hooks"; describe("Chrome Extension Test", () => { it("should load and interact with extension", async (context) => { const testdriver = TestDriver(context); // Clone extension from GitHub await testdriver.exec( 'sh', 'git clone https://github.com/user/my-extension.git /tmp/my-extension', 60000, true ); // Launch Chrome with extension loaded await testdriver.provision.chromeExtension({ extensionPath: '/tmp/my-extension', url: 'https://testdriver.ai' }); // Click extensions puzzle icon const extensionsButton = await testdriver.find("puzzle-shaped icon in Chrome toolbar"); await extensionsButton.click(); // Interact with your extension const myExtension = await testdriver.find("My Extension in the dropdown"); await myExtension.click(); const result = await testdriver.assert("extension popup is visible"); expect(result).toBeTruthy(); }); }); ``` ### Desktop Apps Download and install desktop applications. Supports `.deb`, `.rpm`, `.msi`, `.exe`, `.AppImage`, `.dmg`, `.pkg`, and shell scripts. ```javascript theme={null} const filePath = await testdriver.provision.installer({ url: 'https://example.com/app.deb', appName: 'MyApp', // Focus this app after install launch: true, // Auto-launch after install }); ``` #### Options | Option | Type | Default | Description | | ---------- | ------- | ------------- | --------------------------------------- | | `url` | string | **required** | URL to download the installer from | | `filename` | string | auto-detected | Filename to save as | | `appName` | string | - | Application name to focus after install | | `launch` | boolean | `true` | Launch the app after installation | #### Supported File Types | Extension | OS | Install Method | | ----------- | ------- | -------------------------------- | | `.deb` | Linux | `dpkg -i` + `apt-get install -f` | | `.rpm` | Linux | `rpm -i` | | `.AppImage` | Linux | `chmod +x` | | `.sh` | Linux | `chmod +x` + execute | | `.msi` | Windows | `msiexec /i /quiet` | | `.exe` | Windows | Silent install (`/S`) | | `.dmg` | macOS | Mount + copy to Applications | | `.pkg` | macOS | `installer -pkg` | #### Example: Install and Test a Desktop App ```javascript theme={null} import { describe, expect, it } from "vitest"; import { TestDriver } from "testdriverai/vitest/hooks"; describe("Desktop App Test", () => { it("should install and launch app", async (context) => { const testdriver = TestDriver(context); // Download and install const installerPath = await testdriver.provision.installer({ url: 'https://github.com/sharkdp/bat/releases/download/v0.24.0/bat_0.24.0_amd64.deb', }); // Verify installation const output = await testdriver.exec('sh', 'bat --version', 5000); expect(output).toContain('bat'); }); }); ``` #### Example: Windows Installer ```javascript theme={null} import { describe, expect, it } from "vitest"; import { TestDriver } from "testdriverai/vitest/hooks"; describe("Windows App Test", () => { it("should install on Windows", async (context) => { const testdriver = TestDriver(context, { os: 'windows' }); // Download MSI installer const installerPath = await testdriver.provision.installer({ url: 'https://example.com/app.msi', launch: false, // Don't auto-launch }); // Custom installation if needed await testdriver.exec( 'pwsh', `Start-Process msiexec.exe -ArgumentList "/i", "${installerPath}", "/qn" -Wait`, 120000 ); // Verify installation const result = await testdriver.assert("application is installed"); expect(result).toBeTruthy(); }); }); ``` #### Manual Installation Set `launch: false` to download without auto-installing: ```javascript theme={null} const filePath = await testdriver.provision.installer({ url: 'https://example.com/custom-script.sh', launch: false, }); // Run custom install commands await testdriver.exec('sh', `chmod +x "${filePath}"`, 5000); await testdriver.exec('sh', `"${filePath}" --custom-flag`, 60000); ``` ### VS Code Launch Visual Studio Code with optional workspace and extensions. ```javascript theme={null} await testdriver.provision.vscode({ workspace: '/home/testdriver/my-project', extensions: ['ms-python.python', 'esbenp.prettier-vscode'], }); ``` #### Options | Option | Type | Default | Description | | ------------ | --------- | ------- | ----------------------------- | | `workspace` | string | - | Workspace folder to open | | `extensions` | string\[] | `[]` | Extensions to install (by ID) | #### Example: VS Code Extension Test ```javascript theme={null} import { describe, expect, it } from "vitest"; import { TestDriver } from "testdriverai/vitest/hooks"; describe("VS Code Test", () => { it("should open workspace with extensions", async (context) => { const testdriver = TestDriver(context); // Create a test project await testdriver.exec('sh', 'mkdir -p /tmp/test-project && echo "print(1)" > /tmp/test-project/test.py', 10000); // Launch VS Code await testdriver.provision.vscode({ workspace: '/tmp/test-project', extensions: ['ms-python.python'], }); // Verify VS Code is ready const result = await testdriver.assert("VS Code is open with the project"); expect(result).toBeTruthy(); // Open the Python file await testdriver.find("test.py in the explorer").click(); }); }); ``` ### Choosing the Right Provision Method | Use Case | Method | | ------------------------------------------ | --------------------------- | | Testing a website | `provision.chrome` | | Testing a Chrome extension | `provision.chromeExtension` | | Testing a desktop app (needs installation) | `provision.installer` | | Testing VS Code or VS Code extensions | `provision.vscode` | All provision methods automatically start Dashcam recording and wait for the application to be ready before returning. You don't need to call `dashcam.start()` manually. ## Configuring the Machine TestDriver provisions a fresh cloud VM for every test by default. This section covers how to configure Linux and Windows machines, reduce startup time by keeping machines alive between runs, use provision scripts for repeatable setup, and install custom software on the fly. ### Linux Machines Linux is the default operating system. No extra configuration is required. ```javascript theme={null} import { describe, expect, it } from "vitest"; import { TestDriver } from "testdriverai/vitest/hooks"; describe("My Test", () => { it("runs on Linux", async (context) => { const testdriver = TestDriver(context); await testdriver.provision.chrome({ url: "https://example.com" }); const result = await testdriver.assert("the page loaded successfully"); expect(result).toBeTruthy(); }); }); ``` #### Common Linux Options | Option | Type | Default | Description | | --------------- | ------- | ------------ | ----------------------------------------------------------- | | `os` | string | `"linux"` | Operating system | | `resolution` | string | `"1366x768"` | Screen resolution (Enterprise only) | | `e2bTemplateId` | string | β€” | Custom E2B template ID (see [Self-Hosted](/v7/self-hosted)) | | `keepAlive` | number | `60000` | Ms to keep VM alive after disconnect | | `reconnect` | boolean | `false` | Reconnect to last used sandbox | ```javascript theme={null} const testdriver = TestDriver(context, { os: "linux", resolution: "1920x1080", keepAlive: 5 * 60 * 1000, // keep alive 5 minutes }); ``` ### Windows Machines Set `os: "windows"` to provision a Windows VM instead. Everything else works the same way. ```javascript theme={null} const testdriver = TestDriver(context, { os: "windows", }); await testdriver.provision.chrome({ url: "https://example.com" }); ``` Windows sandboxes use EC2 instances and take longer to boot than Linux (E2B) sandboxes β€” typically 1–3 minutes for a cold start. See [Keeping Machines Alive](#keeping-machines-alive-between-runs) below to avoid this cost on repeated runs. #### Common Windows Options | Option | Type | Default | Description | | ----------------- | ------- | ------------ | ------------------------------------ | | `os` | string | β€” | Set to `"windows"` | | `resolution` | string | `"1366x768"` | Screen resolution (Enterprise only) | | `sandboxAmi` | string | β€” | Custom AMI ID (self-hosted) | | `sandboxInstance` | string | β€” | EC2 instance type (self-hosted) | | `keepAlive` | number | `60000` | Ms to keep VM alive after disconnect | | `reconnect` | boolean | `false` | Reconnect to last used sandbox | ```javascript theme={null} const testdriver = TestDriver(context, { os: "windows", resolution: "1920x1080", keepAlive: 10 * 60 * 1000, // keep alive 10 minutes }); ``` ### Keeping Machines Alive Between Runs Windows (and Linux) cold starts can be expensive if you're iterating quickly. Use `keepAlive` + `reconnect` to reuse the same VM across multiple test runs. #### How it works Every time the SDK successfully connects to a sandbox, it records the sandbox id in `.testdriver/last-sandbox` inside your project directory. The next test that opts in with `reconnect: true` reads that file and reattaches automatically β€” no manual id tracking required. Provision calls (`testdriver.provision.chrome(...)`, `vscode(...)`, etc.) are **skipped** when reconnecting, because the application is already running inside the sandbox from the previous run. `.testdriver/last-sandbox` is already covered by the default TestDriver `.gitignore`. Don't commit it. #### Step 1 β€” Start the machine with a long `keepAlive` ```javascript theme={null} // first.test.mjs const testdriver = TestDriver(context, { os: "windows", keepAlive: 30 * 60 * 1000, // keep alive 30 minutes after this test ends }); await testdriver.provision.chrome({ url: "https://example.com" }); // ... your test steps ``` When this test finishes, the sandbox stays running for 30 minutes instead of being terminated immediately. #### Step 2 β€” Reattach automatically with `reconnect: true` ```javascript theme={null} // second.test.mjs const testdriver = TestDriver(context, { os: "windows", reconnect: true, // ← reads .testdriver/last-sandbox keepAlive: 30 * 60 * 1000, }); // No provision call β€” Chrome is already open from the previous run. await testdriver.find("Sign In button").click(); ``` #### Step 2 (alternative) β€” Reattach to an explicit id If you need to pin to a specific sandbox (CI matrix, multiple chains in parallel, etc.) pass the id directly: ```javascript theme={null} await testdriver.connect({ sandboxId: "sandbox-abc123" }); ``` When reattaching to a sandbox: * You reuse a specific running machine directly * You continue from the app state created in the earlier run * You must run within the previous test's `keepAlive` window Use `testdriver.getLastSandboxId()` to read the recorded sandbox id (and optional metadata) for scripting purposes. #### Chaining describe blocks within one test file A common pattern is to break a long flow into focused `describe` blocks that share one sandbox β€” the first block provisions and signs in, later blocks reconnect and continue: ```javascript theme={null} import { describe, expect, it } from "vitest"; import { TestDriver } from "testdriverai/vitest/hooks"; const KEEP_ALIVE_MS = 5 * 60 * 1000; describe("step 1 β€” log in", () => { it("signs in and lands on the dashboard", async (context) => { const testdriver = TestDriver(context, { keepAlive: KEEP_ALIVE_MS }); await testdriver.provision.chrome({ url: "https://example.com/login" }); await testdriver.find("username input").click(); await testdriver.type("standard_user"); await testdriver.pressKeys(["tab"]); await testdriver.type("secret_sauce", { secret: true }); await testdriver.pressKeys(["enter"]); expect(await testdriver.assert("the dashboard is visible")).toBeTruthy(); }); }); describe("step 2 β€” add to cart", () => { it("reuses the logged-in sandbox", async (context) => { const testdriver = TestDriver(context, { reconnect: true, // ← skip provisioning, reattach keepAlive: KEEP_ALIVE_MS, }); await testdriver.find("Add to cart").click(); await testdriver.find("cart icon").click(); expect(await testdriver.assert("the cart has an item")).toBeTruthy(); }); }); describe("step 3 β€” check out", () => { it("continues from the cart state", async (context) => { const testdriver = TestDriver(context, { reconnect: true, keepAlive: 30_000 }); await testdriver.find("Checkout").click(); expect(await testdriver.assert("the checkout form is visible")).toBeTruthy(); }); }); ``` A runnable copy of this pattern lives at [`examples/reconnect-sequential.test.mjs`](https://github.com/testdriverai/mono/blob/main/sdk/examples/reconnect-sequential.test.mjs). Vitest runs **test files** in parallel by default. Within a single file, `describe`/`it` blocks run in source order, so reconnect chaining works as written. To chain across multiple files, run them sequentially (e.g. `vitest run --sequence.concurrent=false` or place them in a single project pool with workers set to 1). #### How `keepAlive` works `keepAlive` is a duration in milliseconds. After the SDK disconnects, the server keeps the VM running for that long before terminating it. The default is `60000` (1 minute). Note: `keepAlive: 0` currently falls back to the default disconnect grace period rather than terminating immediately, so use a positive duration when you want to control the grace window explicitly. ```javascript theme={null} const testdriver = TestDriver(context, { keepAlive: 0, // currently uses the default 1 minute grace period // keepAlive: 60000, // default β€” 1 minute // keepAlive: 600000, // 10 minutes // keepAlive: 3600000, // 1 hour }); ``` Machines kept alive beyond your test session continue to consume credits. Always set a `keepAlive` value appropriate for your workflow. ### Using Provision Scripts Provision scripts let you run arbitrary setup steps before your test starts β€” downloading fixtures, seeding a database, configuring environment variables, and more. Use `testdriver.exec()` to run shell or PowerShell commands directly in the sandbox. Full reference for running shell and PowerShell commands in the sandbox. #### Linux setup script ```javascript theme={null} await testdriver.provision.chrome({ url: "https://myapp.com" }); // Run a setup script from your repo await testdriver.exec("sh", ` curl -s https://myapp.com/api/reset-test-db -X POST echo "Test DB reset" `, 30000); ``` #### Windows setup script (PowerShell) ```javascript theme={null} await testdriver.provision.chrome({ url: "https://myapp.com" }); await testdriver.exec("pwsh", ` $env:API_URL = "https://staging.myapp.com" Write-Host "Environment configured" `, 15000); ``` #### Clone a repo and run a script ```javascript theme={null} await testdriver.exec("sh", ` git clone https://github.com/myorg/test-fixtures.git /tmp/fixtures bash /tmp/fixtures/seed.sh `, 120000); ``` ### Installing Custom Software You can install software at the start of a test using `exec()`. This works for any package available via `apt`, `brew`, `choco`, `winget`, npm, pip, or direct download. #### Linux β€” apt packages ```javascript theme={null} await testdriver.exec("sh", ` sudo apt-get update -qq sudo apt-get install -y ffmpeg imagemagick `, 120000); ``` #### Linux β€” Node.js tools ```javascript theme={null} await testdriver.exec("sh", "npm install -g @playwright/test", 60000); ``` #### Windows β€” winget ```javascript theme={null} await testdriver.exec("pwsh", ` winget install --id=7zip.7zip -e --silent `, 120000); ``` #### Windows β€” Chocolatey ```javascript theme={null} await testdriver.exec("pwsh", ` choco install googlechrome --yes --no-progress `, 180000); ``` #### Download and run an installer ```javascript theme={null} // Linux await testdriver.exec("sh", ` curl -L https://example.com/installer.sh -o /tmp/installer.sh chmod +x /tmp/installer.sh /tmp/installer.sh --silent `, 300000); // Windows await testdriver.exec("pwsh", ` Invoke-WebRequest -Uri "https://example.com/installer.exe" -OutFile "$env:TEMP\\installer.exe" Start-Process "$env:TEMP\\installer.exe" -ArgumentList "/S" -Wait `, 300000); ``` Installing software at test start adds to your test duration. For software you use in every test, consider preloading it into a custom VM image via the Enterprise self-hosted plan. #### Want Software Pre-Installed on Every Machine? Installing packages at runtime works well for occasional or lightweight dependencies. But if you're installing the same 5-minute setup on every test run, you're wasting time and credits. With the **Self-Hosted Enterprise plan** you get access to our golden VM base image and Packer scripts, so you can bake your applications, dependencies, and configuration directly into a custom AMI. Tests spin up with everything already installed β€” zero setup time. Preload software, configure custom hardware, and run unlimited tests with a flat license fee. Our team assists with deployment and setup. ## Running Tests After creating tests with the TestDriver agent, you can re-run them without starting a new MCP session. Tests are saved as standard Vitest files that run independently β€” the same way on your machine and in CI. ### Running from Terminal TestDriver works with [Vitest's](https://vitest.dev) test runner. Use Vitest to run your tests from the command line to see full output: ```bash theme={null} # Run all tests vitest run # Run a specific test file vitest run tests/login.test.mjs # Run in watch mode (re-runs on file changes) vitest ``` Install Vitest globally for best results: `npm install vitest -g` Vitest automatically discovers files matching patterns like `*.test.js`, `*.test.mjs`, or `*.spec.js`. #### Common CLI options ```bash theme={null} # Run multiple specific files vitest run login.test.mjs checkout.test.mjs # Run every test in a folder vitest run tests/e2e/ # Filter tests by name (supports regex) vitest run --grep "login" # Generate a coverage report vitest run --coverage ``` Coverage requires the `@vitest/coverage-v8` package. Install it with `npm install -D @vitest/coverage-v8`. #### Vitest UI For interactive debugging, launch the web-based UI (starts in watch mode): ```bash theme={null} vitest --ui ``` Open [http://localhost:51204](http://localhost:51204) to browse your test tree, see pass/fail states and timing, view inline console output, and re-run individual tests. Combine with `--open` to open the UI in your browser automatically: `vitest --ui --open` ### Running from VS Code (GUI Mode) For a visual testing experience, use the **Vitest extension**: Click the **beaker icon** in the VS Code sidebar to open the Test Explorer. This shows all your test files and test cases. Click the **play button** next to any test file or individual test to run it. You can also: * Run all tests with the "Run All" button * Debug tests with the "Debug" button Test results appear inline: * βœ… Green checkmark for passing tests * ❌ Red X for failing tests * Click on a failing test to see error details VS Code's Test Explorer only shows output for **failing tests**. To see output from passing tests (including screenshots and console logs), run tests from the terminal instead. ### Test Configuration #### Timeouts TestDriver tests require longer timeouts than typical unit tests. Your `vitest.config.mjs` should have: ```javascript vitest.config.mjs theme={null} import { defineConfig } from "vitest/config"; export default defineConfig({ test: { testTimeout: 900000, // 15 minutes hookTimeout: 900000, // 15 minutes for setup/teardown }, }); ``` #### Environment Variables Tests use the `TD_API_KEY` environment variable. Set it in your `.env` file: ```env .env theme={null} TD_API_KEY=your-api-key-here ``` Or pass it when running tests: ```bash theme={null} TD_API_KEY=your-key vitest run ``` ### Parallel Execution TestDriver runs each test in its own cloud sandbox, enabling true parallel execution. Run your entire test suite in minutes instead of hours. Your TestDriver plan includes a set number of **license slots** that determine how many tests can run simultaneously. Each running test occupies one slot β€” when the test completes and the sandbox is destroyed, the slot is immediately freed for the next test. Set `maxConcurrency` in your Vitest config to match your license slot limit: ```javascript vitest.config.mjs theme={null} import { defineConfig } from 'vitest/config'; import TestDriver from 'testdriverai/vitest'; export default defineConfig({ test: { testTimeout: 900000, hookTimeout: 900000, maxConcurrency: 5, // Match your license slot limit reporters: ['default', TestDriver()], setupFiles: ['testdriverai/vitest/setup'], }, }); ``` You can also cap concurrency from the CLI: ```bash theme={null} vitest run --maxConcurrency=5 ``` Setting `maxConcurrency` higher than your license slots will cause tests to fail when slots are exhausted. Always match this value to your plan's limit. | Test Suite | Sequential (1 slot) | Parallel (5 slots) | Parallel (10 slots) | | --------------------- | ------------------- | ------------------ | ------------------- | | 10 tests @ 2min each | 20 min | 4 min | 2 min | | 50 tests @ 2min each | 100 min | 20 min | 10 min | | 100 tests @ 2min each | 200 min | 40 min | 20 min | View your available slots at [console.testdriver.ai](https://console.testdriver.ai). Upgrade anytime to increase parallelization. ### Viewing Test Reports After each test run, TestDriver provides a link to the full test report: ``` TESTDRIVER_RUN_URL=https://console.testdriver.ai/runs/abc123 ``` The report includes: * Video recording of the test * Screenshots at each step * Network logs and performance metrics * Console output and errors Access all your test runs and recordings in the TestDriver console ### Iterating on Tests When tests fail or need updates, you have two options: #### Option 1: Ask the AI Assistant (Recommended) For discovering updated element descriptions or debugging failures, chat with the TestDriver agent through your AI assistant: ``` The login test is failing because the form layout changed. Update tests/login.test.mjs to work with the new design. ``` The agent will: 1. Start a new session 2. Navigate to the page 3. Analyze the current state 4. Update the test code This is the best approach when: * Element text or layout has changed * You need to see what's currently on screen * The failure reason isn't obvious from the error message #### Option 2: Edit the Code Directly For simple changes, edit the test files directly: ```javascript theme={null} // Change the element description const button = await testdriver.find("Submit Order button"); // Add a wait await testdriver.wait(2000); // Update assertions const result = await testdriver.assert("order confirmation shows order ID"); ``` This is faster for: * Updating text strings * Adjusting timeouts * Fixing typos When a test fails because the UI shifted, let TestDriver fix it for you automatically. See [Prevent](/v7/copilot/auto-healing) for auto-healing, and [Debug](/v7/debugging-with-screenshots) for inspecting screenshots when a run goes wrong. ## Next Now that your tests run anywhere, learn how to make strong assertions that catch real bugs. # Dashcam Source: https://docs.testdriver.ai/v7/dashcam Record test execution with video and logs ## Overview Dashcam provides automatic video recording and log aggregation for your tests. It captures screen recordings, application logs, and test execution details that can be reviewed later. ## Basic Usage ### With Presets Most presets automatically include Dashcam: ```javascript theme={null} import { test } from 'vitest'; import { chrome } from 'testdriverai/presets'; test('my test', async (context) => { const { testdriver, dashcam } = await chrome(context, { url: 'https://example.com' }); // Test executes with recording automatically await testdriver.find('login button').then(el => el.click()); // Dashcam URL available after test console.log('Replay:', dashcam.url); }); ``` ### Manual Setup For more control, create a Dashcam instance directly: ```javascript theme={null} import TestDriver from 'testdriverai'; import Dashcam from 'testdriverai/lib/core/Dashcam.js'; const client = await TestDriver.create({ os: 'linux' }); const dashcam = new Dashcam(client, { apiKey: process.env.DASHCAM_API_KEY }); await dashcam.auth(); await dashcam.start(); // Run your tests const url = await dashcam.stop(); console.log('Replay URL:', url); ``` ## Constructor Create a new Dashcam instance: ```javascript theme={null} new Dashcam(client, options) ``` ### Parameters TestDriver client instance Configuration options Dashcam API key for authentication. Set via `TD_API_KEY` environment variable. Automatically start recording after authentication Log configurations to add automatically ## Methods ### auth() Authenticate with Dashcam service: ```javascript theme={null} await dashcam.auth(apiKey) ``` Override the API key set in constructor **Returns:** `Promise` **Example:** ```javascript theme={null} await dashcam.auth('your-api-key'); ``` ### start() Start recording: ```javascript theme={null} await dashcam.start() ``` **Returns:** `Promise` **Example:** ```javascript theme={null} await dashcam.start(); console.log('Recording started'); ``` ### stop() Stop recording and retrieve replay URL: ```javascript theme={null} await dashcam.stop() ``` **Returns:** `Promise` - Replay URL if available **Example:** ```javascript theme={null} const url = await dashcam.stop(); if (url) { console.log('Watch replay:', url); } else { console.log('No replay URL available'); } ``` ### addFileLog() Track a log file in the recording: ```javascript theme={null} await dashcam.addFileLog(path, name) ``` Path to the log file Display name for the log in Dashcam **Returns:** `Promise` **Example:** ```javascript theme={null} // Linux/Mac await dashcam.addFileLog('/tmp/app.log', 'Application Log'); // Windows await dashcam.addFileLog('C:\\logs\\app.log', 'Application Log'); ``` ### addApplicationLog() Track application-specific logs: ```javascript theme={null} await dashcam.addApplicationLog(application, name) ``` Application name to track Display name for the log **Returns:** `Promise` **Example:** ```javascript theme={null} await dashcam.addApplicationLog('Google Chrome', 'Browser Logs'); ``` ### addWebLog() Track web request logs by URL pattern: ```javascript theme={null} await dashcam.addWebLog(pattern, name) ``` URL pattern to match (e.g., `"*example.com*"`) Display name for the log **Returns:** `Promise` **Example:** ```javascript theme={null} await dashcam.addWebLog('*example.com*', 'Web Logs'); ``` ### addLog() Generic method to add any type of log: ```javascript theme={null} await dashcam.addLog(config) ``` Log configuration Display name for the log Log type: `'file'`, `'application'`, or `'web'` File path (required for type='file') Application name (required for type='application') URL pattern to match (required for type='web', e.g., `"*example.com*"`) **Returns:** `Promise` **Example:** ```javascript theme={null} await dashcam.addLog({ name: 'Test Output', type: 'file', path: '/tmp/test.log' }); await dashcam.addLog({ name: 'Chrome Logs', type: 'application', application: 'Google Chrome' }); await dashcam.addLog({ name: 'Web Logs', type: 'web', pattern: '*example.com*' }); ``` ### isRecording() Check if currently recording: ```javascript theme={null} await dashcam.isRecording() ``` **Returns:** `Promise` - True if recording is active **Example:** ```javascript theme={null} if (await dashcam.isRecording()) { console.log('Recording in progress'); } ``` ## Properties ### recording Current recording state: ```javascript theme={null} dashcam.recording // boolean ``` ### apiKey Configured API key: ```javascript theme={null} dashcam.apiKey // string ``` ### client Associated TestDriver client: ```javascript theme={null} dashcam.client // TestDriver instance ``` ## Complete Examples ### Basic Recording ```javascript theme={null} import { test } from 'vitest'; import TestDriver from 'testdriverai'; import Dashcam from 'testdriverai/lib/core/Dashcam.js'; test('record test execution', async () => { const client = await TestDriver.create({ os: 'linux' }); const dashcam = new Dashcam(client); await dashcam.auth(); await dashcam.start(); // Run your test await client.find('button').then(el => el.click()); const url = await dashcam.stop(); console.log('Replay:', url); await client.cleanup(); }); ``` ### With Log Tracking ```javascript theme={null} test('record with logs', async () => { const client = await TestDriver.create({ os: 'linux' }); const dashcam = new Dashcam(client); await dashcam.auth(); // Add log files before starting await dashcam.addFileLog('/tmp/testdriver.log', 'TestDriver Log'); await dashcam.addFileLog('/tmp/app.log', 'Application Log'); await dashcam.start(); // Test execution await client.find('login button').then(el => el.click()); const url = await dashcam.stop(); console.log('Replay with logs:', url); await client.cleanup(); }); ``` ### Auto-start Configuration ```javascript theme={null} test('auto-start recording', async () => { const client = await TestDriver.create({ os: 'linux' }); const dashcam = new Dashcam(client, { autoStart: true, logs: [ { name: 'App Log', type: 'file', path: '/tmp/app.log' } ] }); await dashcam.auth(); // Automatically starts recording // Test execution await client.find('submit button').then(el => el.click()); const url = await dashcam.stop(); console.log('Replay:', url); await client.cleanup(); }); ``` ### Using with Presets ```javascript theme={null} import { chrome } from 'testdriverai/presets'; test('preset with dashcam', async (context) => { const { testdriver, dashcam } = await chrome(context, { url: 'https://example.com', dashcam: true // Enabled by default }); // Test runs with automatic recording await testdriver.find('button').then(el => el.click()); // URL automatically available console.log('Replay:', dashcam.url); }); ``` ### Disabling Dashcam in Presets ```javascript theme={null} test('without dashcam', async (context) => { const { testdriver } = await chrome(context, { url: 'https://example.com', dashcam: false // Disable recording }); // Test runs without recording (faster) await testdriver.find('button').then(el => el.click()); }); ``` ## Platform Differences ### Windows On Windows, Dashcam uses PowerShell commands and installs via npm: ```javascript theme={null} // Windows-specific paths await dashcam.addFileLog( 'C:\\Users\\testdriver\\Documents\\testdriver.log', 'TestDriver Log' ); ``` ### Linux/Mac On Linux/Mac, Dashcam uses shell commands: ```javascript theme={null} // Unix-specific paths await dashcam.addFileLog('/tmp/testdriver.log', 'TestDriver Log'); ``` # Debug Source: https://docs.testdriver.ai/v7/debugging-with-screenshots Diagnose failing tests with screenshots, replays, and logs ## Overview When a test fails, debug it by seeing exactly what happened β€” don't guess. TestDriver captures screenshots, video replays, and logs as your test runs, so you can replay the moment of failure instead of squinting at a stack trace. TestDriver MCP provides powerful commands to view and analyze the screenshots saved during test execution, enabling rapid debugging, test development, and comparison workflows without manually opening image files. **Automatic Screenshots (Default: Enabled)**: TestDriver automatically captures screenshots before and after every command. Screenshots are named with the line number and action, making it easy to trace exactly which line of code produced each screenshot. For example: `001-click-before-L42-submit-button.png` ## MCP Commands ### list\_local\_screenshots List and filter screenshots saved in the `.testdriver/screenshots/` directory: ``` list_local_screenshots() ``` **Filter Parameters:** Filter screenshots by test file or subdirectory (e.g., "login.test", "mcp-screenshots"). If omitted, lists all screenshots. Filter by exact line number from test file (e.g., 42 matches L42 in filename). Filter by line number range. Example: `{ start: 10, end: 20 }` matches screenshots from lines 10-20. Filter by action type: `click`, `find`, `type`, `assert`, `provision`, `scroll`, `hover`, etc. Filter by phase: `"before"` (state before action) or `"after"` (state after action). Regex pattern to match against filename. Example: `"login|signin"` or `"button.*click"`. Filter by exact sequence number. Filter by sequence range. Example: `{ start: 1, end: 10 }` matches first 10 screenshots. Maximum number of results to return (default: 50). Sort results by: `"modified"` (newest first, default), `"sequence"` (execution order), or `"line"` (line number). **Returns:** Array of screenshot metadata including: * `path` - Full absolute path to the screenshot file * `relativePath` - Path relative to `.testdriver/screenshots/` * `name` - Screenshot filename * `sizeBytes` - File size in bytes * `modified` - Last modification timestamp * `sequence` - Sequential number (from auto-screenshots) * `action` - Action type (click, find, etc.) * `phase` - Before/after phase * `lineNumber` - Line number from test file * `description` - Element or action description **Example Responses:** ```json theme={null} // Basic listing [ { "path": "/Users/user/project/.testdriver/screenshots/login.test/001-click-before-L42-submit-button.png", "relativePath": "login.test/001-click-before-L42-submit-button.png", "name": "001-click-before-L42-submit-button.png", "sizeBytes": 145632, "modified": "2026-01-23T10:00:00.000Z", "sequence": 1, "action": "click", "phase": "before", "lineNumber": 42, "description": "submit-button" } ] ``` ### view\_local\_screenshot View a specific screenshot from the list: ``` view_local_screenshot({ path: "/full/path/to/screenshot.png" }) ``` **Parameters:** Full absolute path to the screenshot file (as returned by `list_local_screenshots`) **Returns:** * Image content (displayed to both AI and user via MCP App) * Screenshot metadata * Success/error status ## Common Workflows ### Test Debugging After Failures When a test fails, you don't have to wonder what went wrong β€” use powerful filtering to quickly find the screenshots that show exactly what happened: **1. Find screenshots at the failing line:** ``` // If test failed at line 42 list_local_screenshots({ line: 42 }) // View before and after states at that line view_local_screenshot({ path: ".testdriver/screenshots/login.test/005-click-before-L42-submit-button.png" }) view_local_screenshot({ path: ".testdriver/screenshots/login.test/006-click-after-L42-submit-button.png" }) ``` **2. See what happened leading up to the failure:** ``` // Get screenshots from lines 35-45 to see context list_local_screenshots({ directory: "login.test", lineRange: { start: 35, end: 45 } }) ``` **3. Find all assertion screenshots:** ``` // See what the screen looked like during assertions list_local_screenshots({ action: "assert" }) ``` **4. View the final state before failure:** ``` // Get the last 5 screenshots in execution order list_local_screenshots({ directory: "login.test", sortBy: "sequence", limit: 5 }) ``` ### Finding Specific Actions When debugging element interactions: ``` // Find all click actions list_local_screenshots({ action: "click" }) // Find what the screen looked like BEFORE each click list_local_screenshots({ action: "click", phase: "before" }) // Find screenshots related to a specific element using regex list_local_screenshots({ pattern: "submit|button" }) // Find all type actions (for form filling issues) list_local_screenshots({ action: "type" }) ``` ### Understanding Test Flow View screenshots in execution order to trace test behavior: ``` // Get screenshots in execution order list_local_screenshots({ directory: "checkout.test", sortBy: "sequence" }) // Get just the first 10 actions list_local_screenshots({ sequenceRange: { start: 1, end: 10 }, sortBy: "sequence" }) // Get just the last 10 actions list_local_screenshots({ directory: "checkout.test", sortBy: "sequence", limit: 10 }) ``` ### Interactive Test Development While building tests using MCP tools, view screenshots to verify your test logic: 1. **After a test run**, filter screenshots to see specific actions: ``` // See all assertions list_local_screenshots({ action: "assert" }) // See what happened at a specific line you're debugging list_local_screenshots({ line: 25 }) ``` 2. **Review key points** in the test execution: ``` view_local_screenshot({ path: ".testdriver/screenshots/my-test.test/after-login.png" }) ``` 3. **Verify element locations and states** before adding assertions 4. **Iterate** - adjust your test code based on what you see in the screenshots ### Comparison and Analysis Compare screenshots to identify issues: **Using phase filtering for before/after comparison:** ``` // See state before all clicks list_local_screenshots({ action: "click", phase: "before" }) // See state after all clicks list_local_screenshots({ action: "click", phase: "after" }) ``` **Using line-based debugging:** ``` // Something went wrong around line 50 list_local_screenshots({ lineRange: { start: 45, end: 55 } }) ``` **Using regex patterns:** ``` // Find screenshots related to login functionality list_local_screenshots({ pattern: "login|signin|email|password" }) ``` ## Best Practices When saving screenshots in tests, use descriptive names to make them easier to identify: ```javascript theme={null} await testdriver.screenshot("initial-page-load"); await testdriver.screenshot("after-login-click"); await testdriver.screenshot("dashboard-loaded"); ``` Then when listing screenshots, you can quickly identify key moments without viewing every image. Always call `list_local_screenshots` first to see what's available. The list is sorted by modification time (newest first), making it easy to find recent test runs. When debugging a specific test, use the `directory` parameter to filter screenshots: ``` list_local_screenshots({ directory: "problematic-test.test" }) ``` This avoids clutter from other tests. When a test fails (especially with assertions), look at screenshots immediately before the failure. They show exactly what the AI or test "saw" at that moment, helping you understand why an assertion failed or why an element wasn't found. TestDriver test reports include screenshots in the timeline. Use MCP screenshot viewing for interactive debugging during development, and test reports for post-run analysis and team sharing. Remember that each test run clears its screenshot folder. If you need to preserve screenshots for comparison: ```bash theme={null} # Copy screenshots before next run cp -r .testdriver/screenshots/my-test.test .testdriver/screenshots-backup/ ``` ## Screenshot File Organization Understanding the directory structure helps with efficient screenshot viewing: ``` .testdriver/ screenshots/ login.test/ # Test file name (without .mjs extension) 001-find-before-L15-email-input.png # Auto: before find() at line 15 002-find-after-L15-email-input.png # Auto: after find() at line 15 003-click-before-L16-email-input.png # Auto: before click() at line 16 004-click-after-L16-email-input.png # Auto: after click() at line 16 login-complete.png # Manual: screenshot("login-complete") checkout.test/ 001-find-before-L12-add-to-cart.png 002-find-after-L12-add-to-cart.png ... ``` ### Automatic Screenshot Naming Format `---L-.png` | Component | Description | Example | | ------------- | -------------------------- | -------------------------- | | `seq` | Sequential number | `001`, `002` | | `action` | Command name | `click`, `type`, `find` | | `phase` | Before, after, or error | `before`, `after`, `error` | | `L` | Line number from test file | `L42` | | `description` | Element/action description | `submit-button` | ### Key Points * Each test file gets its own subdirectory * Automatic screenshots include line numbers for easy tracing * Manual `screenshot()` calls use custom names you provide * Folders are cleared at the start of each test run * All screenshots are PNG format * Disable automatic screenshots with `autoScreenshots: false` if needed ## Interaction List Sidebar (Source of Truth) When viewing a test run in the TestDriver console, the **interaction list sidebar** displays a screenshot for each interaction call (find, click, type, assert, etc.). These screenshots show exactly what was on the screen at the time each interaction was executed. **The sidebar screenshots are the source of truth.** If a test is behaving unexpectedly, check the screenshot attached to the specific interaction in the sidebar β€” it shows precisely what the AI saw when making its decision. This is more reliable than inferring screen state from test logs or local screenshots alone. Use the interaction list to: * **Verify what the AI saw** β€” confirm the correct page/state was visible when `find()` or `assert()` ran * **Debug misclicks** β€” see whether the target element was actually on screen * **Identify timing issues** β€” spot cases where the UI hadn't finished loading before an interaction fired * **Compare runs** β€” review interaction screenshots across multiple runs to catch flaky behavior ## Integration with Test Development ### During MCP Interactive Development When using TestDriver MCP tools (`session_start`, `find_and_click`, etc.), screenshots are automatically captured and displayed. Additionally, you can view previously saved screenshots: ``` # After test development session list_local_screenshots({ directory: "my-new-test.test" }) view_local_screenshot({ path: ".testdriver/screenshots/my-new-test.test/login-page.png" }) ``` This helps verify your test logic before running the full test file. ### After Test Runs When tests fail or behave unexpectedly, replay what happened step by step: 1. **Run the test** with `vitest run tests/my-test.test.mjs` 2. **List screenshots** using `list_local_screenshots` 3. **View relevant screenshots** to diagnose the issue 4. **Update test code** based on what you see 5. **Re-run and verify** the fix ## Troubleshooting If `list_local_screenshots` returns an empty array: * Ensure your test includes `await testdriver.screenshot()` calls * Verify the test actually ran (check test output) * Check that `.testdriver/screenshots/` directory exists * Confirm you're in the correct project directory If `view_local_screenshot` returns an error: * Verify the path is exactly as returned by `list_local_screenshots` * Check file permissions - ensure the screenshot file is readable * Confirm the file hasn't been deleted or moved If you have hundreds of screenshots making it hard to find what you need, use filtering: * Filter by test file: `list_local_screenshots({ directory: "my-test.test" })` * Filter by line number: `list_local_screenshots({ line: 42 })` or `list_local_screenshots({ lineRange: { start: 40, end: 50 } })` * Filter by action: `list_local_screenshots({ action: "click" })` * Filter by phase: `list_local_screenshots({ phase: "before" })` * Use regex: `list_local_screenshots({ pattern: "submit|login" })` * Limit results: `list_local_screenshots({ limit: 10 })` * Sort by line: `list_local_screenshots({ sortBy: "line" })` * Clean up old folders: `rm -rf .testdriver/screenshots/*` Remember that screenshot folders are cleared at the start of each test run. If you see old screenshots: * The test may not have run recently * Or the test failed before reaching the clearing logic * Manually clear: `rm -rf .testdriver/screenshots//` ## Where this fits in the Guide Debugging is what you reach for when a [Run](/v7/copilot/running-tests) goes sideways or a [Validate](/v7/making-assertions) assertion fails β€” the screenshots show you precisely what the AI saw before it acted. Once you've diagnosed the failure, the next step is to stop it from recurring. ## Related * [screenshot()](/v7/screenshot) - Capture screenshots during test execution * [Dashcam](/v7/dashcam) - Record full test sessions with video and logs * [assert()](/v7/assert) - Make AI-powered assertions that benefit from screenshot context ## Next You've seen what went wrong β€” now keep it from happening again. Let auto-healing repair flaky tests automatically before they fail your suite. # doubleClick Source: https://docs.testdriver.ai/v7/double-click Perform a double-click action on an element or at specific coordinates ## Overview The `doubleClick()` method performs a double-click action on an element. You can either call it on an [`Element`](/v7/core-concepts/elements) instance or use it directly with a selector. ## Syntax ```javascript theme={null} // Double-click on an element await element.doubleClick(); // Double-click using a selector await ai.doubleClick('selector'); ``` ## Parameters When called on an `Element`, no parameters are required. When called directly on the AI client: | Parameter | Type | Description | | ---------- | -------- | --------------------------------------------------- | | `selector` | `string` | The selector describing the element to double-click | ## Returns Returns a `Promise` that resolves when the double-click action completes. ## Examples ### Double-Click on Found Element ```javascript theme={null} const fileItem = await ai.find('README.md file'); await fileItem.doubleClick(); ``` ### Direct Double-Click with Selector ```javascript theme={null} await ai.doubleClick('README.md in the file list'); ``` ### Opening Files in VS Code ```javascript theme={null} import { test } from 'vitest'; import { vscode } from '@testdriver/sdk'; test('opens a file by double-clicking', async () => { const { ai } = await vscode(); // Double-click to open a file in the explorer await ai.doubleClick('package.json in the file explorer'); // Verify the file opened const editor = await ai.find('text editor showing package.json'); expect(editor).toBeTruthy(); }); ``` ### Opening Folders in File Manager ```javascript theme={null} import { test } from 'vitest'; import { chrome } from '@testdriver/sdk'; test('navigates folders in Google Drive', async () => { const { ai } = await chrome('https://drive.google.com'); // Double-click to open a folder await ai.doubleClick('Documents folder'); // Wait for folder to open await ai.find('breadcrumb showing Documents'); }); ``` ### Selecting Text with Double-Click ```javascript theme={null} // Double-click to select a word await ai.doubleClick('word "TestDriver" in the paragraph'); // Verify selection const selectedText = await ai.exec('window.getSelection().toString()'); expect(selectedText).toBe('TestDriver'); ``` ## Related Methods * [`click()`](/v7/click) - Single click on an element * [`rightClick()`](/v7/right-click) - Right-click to open context menu * [`mouseDown()`](/v7/mouse-down) - Press mouse button without releasing * [`mouseUp()`](/v7/mouse-up) - Release mouse button * [`hover()`](/v7/hover) - Move mouse over element without clicking # Element Finding Source: https://docs.testdriver.ai/v7/elements Locate and interact with UI elements using AI ## Overview TestDriver's element finding system uses AI to locate elements on screen using natural language descriptions. The `find()` method returns an `Element` object that you can interact with. ## Finding Elements ### find() Locate an element on screen using a natural language description. ```javascript theme={null} const element = await testdriver.find(description) ``` **Parameters:** * `description` (string) - Natural language description of the element to find **Returns:** `Promise` - Element instance that has been located **Example:** ```javascript theme={null} // Find a button const submitButton = await testdriver.find('the submit button'); // Find an input field with context const emailField = await testdriver.find('email input field in the login form'); // Find an element by visual characteristics const redButton = await testdriver.find('red button in the top right corner'); ``` Be specific in your descriptions. Include visual details, location context, or nearby text to improve accuracy. ## Element Class The `Element` class represents a located (or to-be-located) UI element. It provides methods for interaction and properties for element information. ### Methods #### found() Check if the element was successfully located. ```javascript theme={null} element.found() ``` **Returns:** `boolean` - True if element coordinates were found **Example:** ```javascript theme={null} const element = await testdriver.find('login button'); if (element.found()) { await element.click(); } else { console.log('Element not found'); } ``` #### find() Re-locate the element, optionally with a new description. ```javascript theme={null} await element.find(newDescription) ``` **Parameters:** * `newDescription` (string, optional) - New description to search for **Returns:** `Promise` - This element instance **Example:** ```javascript theme={null} // Re-locate if the UI changed const element = await testdriver.find('submit button'); // ... page updates ... await element.find(); // Re-locate with same description // Or update the description await element.find('blue submit button'); // Now looking for blue button ``` #### click() Click on the element. ```javascript theme={null} await element.click(action) ``` **Parameters:** * `action` (string, optional) - Type of click: `'click'` (default), `'double-click'`, `'right-click'`, `'hover'`, `'mouseDown'`, `'mouseUp'` **Returns:** `Promise` **Example:** ```javascript theme={null} const button = await testdriver.find('submit button'); await button.click(); // Regular click const file = await testdriver.find('document.txt'); await file.click('double-click'); // Double-click const menu = await testdriver.find('settings icon'); await menu.click('right-click'); // Right-click ``` The element must be found before clicking. The `find()` method automatically locates the element. #### hover() Hover over the element without clicking. ```javascript theme={null} await element.hover() ``` **Returns:** `Promise` **Example:** ```javascript theme={null} const tooltip = await testdriver.find('info icon'); await tooltip.hover(); // Wait to see tooltip await new Promise(resolve => setTimeout(resolve, 1000)); ``` #### doubleClick() Double-click on the element. ```javascript theme={null} await element.doubleClick() ``` **Returns:** `Promise` **Example:** ```javascript theme={null} const file = await testdriver.find('README.txt file icon'); await file.doubleClick(); ``` #### rightClick() Right-click on the element to open context menu. ```javascript theme={null} await element.rightClick() ``` **Returns:** `Promise` **Example:** ```javascript theme={null} const folder = await testdriver.find('Documents folder'); await folder.rightClick(); ``` #### mouseDown() / mouseUp() Press or release mouse button on the element (for drag operations). ```javascript theme={null} await element.mouseDown() await element.mouseUp() ``` **Returns:** `Promise` **Example:** ```javascript theme={null} // Drag and drop const item = await testdriver.find('draggable item'); await item.mouseDown(); // Move to drop target (using coordinates or another element) const target = await testdriver.find('drop zone'); await target.hover(); await target.mouseUp(); ``` ### Properties Element properties provide additional information about located elements. Properties are available after a successful `find()` call. #### coordinates Get the element's coordinates object containing all position information. ```javascript theme={null} const coords = element.getCoordinates() // or access directly element.coordinates ``` **Returns:** `Object | null` - Coordinate object with `{ x, y, centerX, centerY }` **Example:** ```javascript theme={null} const button = await testdriver.find('submit button'); const coords = button.coordinates; if (coords) { console.log(`Top-left: (${coords.x}, ${coords.y})`); console.log(`Center: (${coords.centerX}, ${coords.centerY})`); } ``` #### x, y, centerX, centerY Direct access to coordinate values. Always available after successful `find()`. ```javascript theme={null} element.x // Top-left X coordinate (number) element.y // Top-left Y coordinate (number) element.centerX // Center X coordinate (number) element.centerY // Center Y coordinate (number) ``` **Example:** ```javascript theme={null} const button = await testdriver.find('submit button'); console.log(`Button at: (${button.x}, ${button.y})`); console.log(`Button center: (${button.centerX}, ${button.centerY})`); // Use for custom mouse operations await testdriver.click(button.centerX, button.centerY); ``` #### width, height Element dimensions in pixels. Available when AI detects element bounds. ```javascript theme={null} element.width // Width in pixels (number | null) element.height // Height in pixels (number | null) ``` **Example:** ```javascript theme={null} const button = await testdriver.find('submit button'); if (button.width && button.height) { console.log(`Button size: ${button.width}x${button.height}px`); // Check if button is large enough if (button.width < 50) { console.warn('Button might be too small'); } } ``` #### boundingBox Complete bounding box information including position and dimensions. ```javascript theme={null} element.boundingBox ``` **Returns:** `Object | null` - Bounding box with all dimension data ```typescript theme={null} { x: number, // Top-left X y: number, // Top-left Y width: number, // Width in pixels height: number // Height in pixels } ``` **Example:** ```javascript theme={null} const element = await testdriver.find('dialog box'); if (element.boundingBox) { const { x, y, width, height } = element.boundingBox; console.log(`Dialog: ${width}x${height} at (${x}, ${y})`); // Calculate if element is in viewport const rightEdge = x + width; const bottomEdge = y + height; console.log(`Element extends to (${rightEdge}, ${bottomEdge})`); } ``` #### screenshot Base64-encoded PNG screenshot of the screen when element was found. Only available in DEBUG mode or when an error occurs. ```javascript theme={null} element.screenshot ``` **Returns:** `string | null` - Base64-encoded PNG image **Example:** ```javascript theme={null} const element = await testdriver.find('error message'); if (element.screenshot) { // Save screenshot to file const fs = require('fs'); const base64Data = element.screenshot.replace(/^data:image\/\w+;base64,/, ''); fs.writeFileSync('element-screenshot.png', Buffer.from(base64Data, 'base64')); console.log('Screenshot saved'); } ``` Screenshots can be large. They're automatically excluded from error messages to prevent memory issues. #### text Text content extracted from the element by AI (if available). ```javascript theme={null} element.text ``` **Returns:** `string | null` - Element's text content **Example:** ```javascript theme={null} const message = await testdriver.find('notification message'); if (message.text) { console.log('Message says:', message.text); // Use text content in assertions if (message.text.includes('success')) { console.log('Success message detected'); } } // Another example - extracting button label const button = await testdriver.find('blue button'); console.log('Button text:', button.text); // "Submit" ``` #### label Accessible label or name of the element (if available). Useful for verifying accessibility. ```javascript theme={null} element.label ``` **Returns:** `string | null` - Accessible label **Example:** ```javascript theme={null} const input = await testdriver.find('first input field'); if (input.label) { console.log('Input label:', input.label); // "Email Address" } ``` #### confidence AI confidence score for the element match (0-1, where 1 is perfect confidence). ```javascript theme={null} element.confidence ``` **Returns:** `number | null` - Confidence score between 0 and 1 **Example:** ```javascript theme={null} const element = await testdriver.find('submit button'); if (element.confidence !== null) { const percentage = (element.confidence * 100).toFixed(1); console.log(`Match confidence: ${percentage}%`); if (element.confidence < 0.8) { console.warn('⚠️ Low confidence match - element might not be correct'); } else if (element.confidence > 0.95) { console.log('βœ… High confidence match'); } } ``` Confidence scores below 0.8 may indicate the element description was ambiguous or the wrong element was found. ### Property Availability | Property | When Available | | ------------------------------ | ------------------------------------ | | `x`, `y`, `centerX`, `centerY` | βœ… Always after successful `find()` | | `coordinates` | βœ… Always after successful `find()` | | `width`, `height` | ⚠️ When AI detects element bounds | | `boundingBox` | ⚠️ When AI detects element bounds | | `text` | ⚠️ When AI extracts text content | | `label` | ⚠️ When element has accessible label | | `confidence` | βœ… Always after AI element finding | | `screenshot` | ⚠️ Only in DEBUG mode or on errors | Properties marked with ⚠️ may be `null` depending on what the AI could detect from the screenshot. ## JSON Serialization Element objects can be safely serialized using `JSON.stringify()` for logging, debugging, and data storage. Circular references are automatically removed: ```javascript theme={null} const element = await testdriver.find('login button'); // Safe to stringify - no circular reference errors! console.log(JSON.stringify(element, null, 2)); ``` **Serialized output includes:** ```json theme={null} { "description": "login button", "coordinates": { "x": 100, "y": 200, "centerX": 150, "centerY": 225 }, "found": true, "threshold": 0.01, "x": 100, "y": 200, "cache": { "hit": true, "strategy": "pixel-diff", "createdAt": "2025-12-09T10:30:00.000Z", "diffPercent": 0.0023, "imageUrl": "https://cache.testdriver.ai/..." }, "similarity": 0.98, "confidence": 0.95, "selector": "button#login", "aiResponse": "Found the blue login button in the center of the form..." } ``` **Serialized properties:** | Property | Type | Description | | ------------------- | ------- | ------------------------------------------------- | | `description` | string | Element search description | | `coordinates` | object | Full coordinate object `{x, y, centerX, centerY}` | | `found` | boolean | Whether element was located | | `threshold` | number | Cache threshold used for this find | | `x`, `y` | number | Top-left coordinates | | `cache.hit` | boolean | Whether cache was used | | `cache.strategy` | string | Cache strategy (e.g., "pixel-diff") | | `cache.createdAt` | string | ISO timestamp when cache was created | | `cache.diffPercent` | number | Pixel difference from cached image | | `cache.imageUrl` | string | URL to cached screenshot | | `similarity` | number | Similarity score (0-1) | | `confidence` | number | AI confidence score (0-1) | | `selector` | string | CSS/XPath selector if available | | `aiResponse` | string | AI's explanation of what it found | **Use cases:** ```javascript theme={null} // Debugging element detection const element = await testdriver.find('submit button'); if (!element.found()) { console.error('Element not found:', JSON.stringify(element, null, 2)); } // Logging cache performance const data = JSON.parse(JSON.stringify(element)); if (data.cache.hit) { console.log(`Cache hit! Diff: ${(data.cache.diffPercent * 100).toFixed(2)}%`); } // Sharing element data across processes const elementData = JSON.stringify(element); // Send to another process, log to file, etc. ``` Use JSON serialization when you need to log element data or when debugging why an element wasn't found. The serialized output excludes large binary data (screenshots) and circular references. ## Examples ### Basic Element Interaction ```javascript theme={null} // Find and click const submitButton = await testdriver.find('submit button'); await submitButton.click(); // Find, verify, then interact const emailInput = await testdriver.find('email input field'); if (emailInput.found()) { await emailInput.click(); await testdriver.type('user@example.com'); } ``` ### Working with Forms ```javascript theme={null} // Fill out a multi-field form const nameField = await testdriver.find('name input field'); await nameField.click(); await testdriver.type('John Doe'); const emailField = await testdriver.find('email input field'); await emailField.click(); await testdriver.type('john@example.com'); const submitButton = await testdriver.find('submit button'); await submitButton.click(); ``` ### Conditional Interactions ```javascript theme={null} // Check if element exists before interacting const closeButton = await testdriver.find('close popup button'); if (closeButton.found()) { await closeButton.click(); console.log('Popup closed'); } else { console.log('No popup to close'); } ``` ### Re-locating Dynamic Elements ```javascript theme={null} // Element that moves or changes const notification = await testdriver.find('success notification'); // Do something that might cause it to move await testdriver.scroll('down', 300); // Re-locate the element await notification.find(); if (notification.found()) { await notification.click(); } ``` ## Best Practices Include visual details, position context, and nearby text: ```javascript theme={null} // ❌ Too vague await testdriver.find('button'); // βœ… Specific await testdriver.find('blue submit button below the email field'); ``` Always verify elements were located before interacting: ```javascript theme={null} const element = await testdriver.find('submit button'); if (!element.found()) { throw new Error('Submit button not found'); } await element.click(); ``` If you need to interact with the same element multiple times, reuse the reference: ```javascript theme={null} const input = await testdriver.find('search input'); await input.click(); await testdriver.type('first search'); await testdriver.pressKeys(['enter']); // Re-use the same element reference await input.click(); await testdriver.pressKeys(['ctrl', 'a']); // Select all await testdriver.type('second search'); ``` # Assert Test Example Source: https://docs.testdriver.ai/v7/examples/assert Example test that uses assert to verify a page loads. ## Demo Test Run Watch this test execute in a real sandbox environment: