Skip to main content
TestDriver makes writing tests intuitive by using natural language to describe interactions. This guide covers everything you need to know to write effective, maintainable tests.

Basic Test Structure

Every TestDriver test follows this simple pattern:
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
// ✅ 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"');
These descriptions are:
  • Specific enough to locate the element
  • Include context (location, parent container)
  • Use natural human language

Chaining Actions

TestDriver supports method chaining for cleaner code:
// 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

// 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);
// 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!
// 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
// 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);
// 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:
// 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:
// 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:
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');
});
  • Multi-Page Flow
  • Back Navigation
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

// ✅ Good
await testdriver.find('blue submit button in the bottom right');

// ❌ Too vague
await testdriver.find('button');

Use secret: true for Passwords

// ✅ Secure
await testdriver.type('password', { secret: true });

// ❌ Logged in dashcam
await testdriver.type('password');

Test User Flows, Not Implementation

// ✅ 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

// ✅ Clear intent
test('user can reset password via email');

// ❌ Vague
test('test login');

Advanced Patterns

// 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();
}
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');
    }
  });
});
// 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
  • Shopping Cart
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');
});

Next Steps