Basic Test Structure
Every TestDriver test follows this simple pattern:Copy
import { test } from 'vitest';
import { TestDriver } from 'testdriverai/vitest/hooks';
test('descriptive test name', async (context) => {
// 1. Setup: Launch and navigate
const testdriver = TestDriver(context, { headless: true });
await testdriver.provision.chrome({ url: 'https://myapp.com' });
// 2. Act: Interact with your application
await testdriver.find('email input').type('[email protected]');
await testdriver.find('submit button').click();
// 3. Assert: Verify expected state
await testdriver.assert('Welcome message is visible');
});
The TestDriver hook handles all setup and cleanup automatically - no need for manual browser management!
Finding Elements
Use natural language to describe elements:- Good Descriptions
- Avoid Vague
Copy
// ✅ Specific and contextual
await testdriver.find('submit button in the login form');
await testdriver.find('email input field');
await testdriver.find('delete button in the top right corner');
await testdriver.find('first product card in the grid');
await testdriver.find('dropdown menu labeled "Country"');
- Specific enough to locate the element
- Include context (location, parent container)
- Use natural human language
Chaining Actions
TestDriver supports method chaining for cleaner code:Copy
// Chain find() with actions
await testdriver.find('Login button').click();
await testdriver.find('email input').type('[email protected]');
await testdriver.find('dropdown menu').hover();
await testdriver.find('menu item').doubleClick();
await testdriver.find('context option').rightClick();
// Or save element reference
const button = await testdriver.find('submit button');
await button.click();
Common Interactions
Clicking Elements
Clicking Elements
Copy
// Regular click
await testdriver.find('button').click();
// Double-click
await testdriver.find('file item').doubleClick();
// Right-click (context menu)
await testdriver.find('text area').rightClick();
// Click at specific coordinates
await testdriver.click(500, 300);
Typing Text
Typing Text
Copy
// Basic typing
await testdriver.find('search input').type('query text');
// Type sensitive data (won't be logged)
await testdriver.find('password input').type('secret', { secret: true });
// Type with delay between keystrokes
await testdriver.find('input').type('slow typing', { delay: 500 });
// Type numbers
await testdriver.find('age input').type(25);
Always use
{ secret: true } for passwords and sensitive data to prevent logging!Keyboard Shortcuts
Keyboard Shortcuts
Copy
// Single keys
await testdriver.pressKeys(['enter']);
await testdriver.pressKeys(['tab']);
await testdriver.pressKeys(['escape']);
// Combinations
await testdriver.pressKeys(['ctrl', 'c']); // Copy
await testdriver.pressKeys(['ctrl', 'v']); // Paste
await testdriver.pressKeys(['ctrl', 's']); // Save
// macOS modifiers
await testdriver.pressKeys(['cmd', 'c']); // macOS copy
await testdriver.pressKeys(['cmd', 'shift', 'p']); // Command palette
Hovering
Hovering
Copy
// Hover over element to reveal dropdown
await testdriver.find('Products menu').hover();
await testdriver.find('Laptops submenu').click();
// Hover at coordinates
await testdriver.hover(400, 200);
Scrolling
Scrolling
Copy
// Scroll down
await testdriver.scroll('down', 500);
// Scroll up
await testdriver.scroll('up', 300);
// Scroll until text appears
await testdriver.scrollUntilText('Footer content');
// Scroll until element is visible
await testdriver.scroll('down', 1000);
await testdriver.find('load more button').click();
Making Assertions
Use AI-powered assertions to verify application state:Copy
// Verify visibility
await testdriver.assert('login page is displayed');
await testdriver.assert('submit button is visible');
await testdriver.assert('loading spinner is not visible');
// Verify content
await testdriver.assert('page title is "Welcome"');
await testdriver.assert('success message says "Account created"');
await testdriver.assert('error message contains "Invalid email"');
// Verify state
await testdriver.assert('checkbox is checked');
await testdriver.assert('dropdown shows "United States"');
await testdriver.assert('button is disabled');
// Verify visual appearance
await testdriver.assert('submit button is blue');
await testdriver.assert('form has red border');
Assertions automatically wait for the condition to be true, making tests more stable.
Working with Multiple Elements
Find and interact with multiple elements:Copy
// Find all matching elements
const products = await testdriver.findAll('product card');
console.log(`Found ${products.length} products`);
// Interact with each
for (const product of products) {
const title = await product.find('title text');
console.log('Product:', title.text);
await product.find('add to cart button').click();
}
// Or find specific element
const firstProduct = products[0];
await firstProduct.click();
Handling Forms
Complete form example:Copy
test('fill out contact form', async (context) => {
const { testdriver } = await chrome(context, {
url: 'https://example.com/contact'
});
// Fill text inputs
await testdriver.find('name input').type('John Doe');
await testdriver.find('email input').type('[email protected]');
await testdriver.find('phone input').type('555-1234');
// Select from dropdown
await testdriver.find('country dropdown').click();
await testdriver.find('United States option').click();
// Check checkbox
await testdriver.find('newsletter checkbox').click();
// Select radio button
await testdriver.find('urgent priority radio button').click();
// Fill textarea
await testdriver.find('message textarea').type('This is my message.');
// Submit
await testdriver.find('send message button').click();
// Verify success
await testdriver.assert('thank you message is displayed');
});
Navigation Patterns
- Multi-Page Flow
- Back Navigation
- Opening Links in New Tab
Copy
test('checkout flow', async (context) => {
const { testdriver } = await chrome(context, {
url: 'https://shop.example.com'
});
// Page 1: Browse products
await testdriver.find('first product').click();
await testdriver.find('add to cart button').click();
// Page 2: Cart
await testdriver.find('cart icon').click();
await testdriver.find('checkout button').click();
// Page 3: Shipping info
await testdriver.find('address input').type('123 Main St');
await testdriver.find('continue button').click();
// Page 4: Payment
await testdriver.find('card number').type('4242424242424242');
await testdriver.find('place order').click();
// Page 5: Confirmation
await testdriver.assert('order confirmation is shown');
});
Best Practices
Write Descriptive Selectors
Copy
// ✅ Good
await testdriver.find('blue submit button in the bottom right');
// ❌ Too vague
await testdriver.find('button');
Use secret: true for Passwords
Copy
// ✅ Secure
await testdriver.type('password', { secret: true });
// ❌ Logged in dashcam
await testdriver.type('password');
Test User Flows, Not Implementation
Copy
// ✅ User-focused
test('user can purchase item', async (context) => {
// Test the complete flow
});
// ❌ Implementation-focused
test('click button #12345', async (context) => {
// Too specific to implementation
});
Use Meaningful Test Names
Copy
// ✅ Clear intent
test('user can reset password via email');
// ❌ Vague
test('test login');
Advanced Patterns
Conditional Logic
Conditional Logic
Copy
// Check if element exists
try {
await testdriver.find('cookie banner', { timeout: 5000 });
// Banner found, dismiss it
await testdriver.find('accept cookies button').click();
} catch (error) {
// Banner not found, continue
console.log('No cookie banner');
}
// Conditional actions
const loginButton = await testdriver.find('login button');
if (loginButton) {
await loginButton.click();
}
Data-Driven Tests
Data-Driven Tests
Copy
const testCases = [
{ email: '[email protected]', password: 'pass123', shouldPass: true },
{ email: 'invalid', password: 'pass123', shouldPass: false },
{ email: '[email protected]', password: 'wrong', shouldPass: false },
];
testCases.forEach(({ email, password, shouldPass }) => {
test(`login with ${email}`, async (context) => {
const { testdriver } = await chrome(context, { url });
await testdriver.find('email').type(email);
await testdriver.find('password').type(password, { secret: true });
await testdriver.find('submit').click();
if (shouldPass) {
await testdriver.assert('dashboard is visible');
} else {
await testdriver.assert('error message is shown');
}
});
});
Reusable Helpers
Reusable Helpers
Copy
// helpers.js
export async function login(testdriver, email, password) {
await testdriver.find('email input').type(email);
await testdriver.find('password input').type(password, { secret: true });
await testdriver.find('login button').click();
await testdriver.assert('dashboard is visible');
}
// test.test.js
import { login } from './helpers';
test('user can view profile', async (context) => {
const { testdriver } = await chrome(context, { url });
await login(testdriver, '[email protected]', 'password');
await testdriver.find('profile link').click();
await testdriver.assert('profile page is displayed');
});
Common Patterns
- Login Flow
- Search
- Shopping Cart
Copy
test('user login', async (context) => {
const { testdriver } = await chrome(context, {
url: 'https://myapp.com/login'
});
await testdriver.find('email input').type('[email protected]');
await testdriver.find('password input').type('secret', { secret: true });
await testdriver.find('Login button').click();
await testdriver.assert('Dashboard is visible');
await testdriver.assert('Welcome message shows user name');
});

