---
name: testdriver:reusable-code
description: Build maintainable test suites with reusable code patterns
---
<!-- Generated from reusable-code.mdx. DO NOT EDIT. -->

As your test suite grows, you'll want to extract common patterns into reusable code. This keeps tests DRY, readable, and easy to maintain.

## Helper Functions

The simplest approach is extracting common actions into helper functions. Create a `helpers/` directory for shared utilities:

```javascript test/helpers/auth.js
export async function login(testdriver, { email, password }) {
  const emailInput = await testdriver.find('email input');
  await emailInput.click();
  await testdriver.type(email);
  
  const passwordInput = await testdriver.find('password input');
  await passwordInput.click();
  await testdriver.type(password);
  
  const loginButton = await testdriver.find('login button');
  await loginButton.click();
  
  const result = await testdriver.assert('user is logged in');
  return result;
}

export async function logout(testdriver) {
  const userMenu = await testdriver.find('user menu');
  await userMenu.click();
  
  const logoutButton = await testdriver.find('logout button');
  await logoutButton.click();
}
```

<Warning>
**Avoid hardcoding dynamic values in element descriptions.** Element selectors should describe the *type* of element, not specific content that might change.

**❌ Bad:** `await testdriver.find('profile name TestDriver in the top right')`  
**✅ Good:** `await testdriver.find('user profile name in the top right')`

Hardcoded values like usernames, product names, or prices will cause tests to fail when the data changes. Use generic descriptions that work regardless of the specific content displayed.
</Warning>

Now import and use these helpers in any test:

```javascript test/checkout.test.mjs
import { describe, expect, it } from "vitest";
import { TestDriver } from "testdriverai/lib/vitest/hooks.mjs";
import { login } from './helpers/auth.js';

describe("Checkout", () => {
  it("should complete checkout as logged in user", async (context) => {
    const testdriver = TestDriver(context);
    
    await testdriver.provision.chrome({
      url: 'https://shop.example.com',
    });

    // Use the helper
    await login(testdriver, { 
      email: 'user@example.com', 
      password: 'password123' 
    });

    // Continue with checkout steps...
    const cartButton = await testdriver.find('cart button');
    await cartButton.click();
  });
});
```

## Page Objects

For larger test suites, the Page Object pattern encapsulates all interactions with a specific page or component:

```javascript test/pages/LoginPage.js
export class LoginPage {
  constructor(testdriver) {
    this.td = testdriver;
  }

  async enterEmail(email) {
    const input = await this.td.find('email input');
    await input.click();
    await this.td.type(email);
  }

  async enterPassword(password) {
    const input = await this.td.find('password input');
    await input.click();
    await this.td.type(password);
  }

  async submit() {
    const button = await this.td.find('submit button');
    await button.click();
  }

  async login(email, password) {
    await this.enterEmail(email);
    await this.enterPassword(password);
    await this.submit();
  }

  async assertError(message) {
    return await this.td.assert(`error message shows "${message}"`);
  }

  async assertLoggedIn() {
    return await this.td.assert('user dashboard is visible');
  }
}
```

Use the page object in your tests:

```javascript test/auth.test.mjs
import { describe, expect, it } from "vitest";
import { TestDriver } from "testdriverai/lib/vitest/hooks.mjs";
import { LoginPage } from './pages/LoginPage.js';

describe("Authentication", () => {
  it("should show error for invalid credentials", async (context) => {
    const testdriver = TestDriver(context);
    
    await testdriver.provision.chrome({
      url: 'https://app.example.com/login',
    });

    const loginPage = new LoginPage(testdriver);
    
    await loginPage.login('invalid@test.com', 'wrongpassword');
    
    const hasError = await loginPage.assertError('Invalid credentials');
    expect(hasError).toBeTruthy();
  });

  it("should redirect to dashboard on success", async (context) => {
    const testdriver = TestDriver(context);
    
    await testdriver.provision.chrome({
      url: 'https://app.example.com/login',
    });

    const loginPage = new LoginPage(testdriver);
    
    await loginPage.login('valid@test.com', 'correctpassword');
    
    const isLoggedIn = await loginPage.assertLoggedIn();
    expect(isLoggedIn).toBeTruthy();
  });
});
```

## Shared Test Fixtures

Create reusable fixtures for common test setup scenarios:

```javascript test/fixtures/index.js
export const testUsers = {
  admin: { email: 'admin@example.com', password: 'admin123' },
  regular: { email: 'user@example.com', password: 'user123' },
  guest: { email: 'guest@example.com', password: 'guest123' },
};

export const testUrls = {
  staging: 'https://staging.example.com',
  production: 'https://example.com',
};

export async function setupAuthenticatedSession(testdriver, user = testUsers.regular) {
  const emailInput = await testdriver.find('email input');
  await emailInput.click();
  await testdriver.type(user.email);
  
  const passwordInput = await testdriver.find('password input');
  await passwordInput.click();
  await testdriver.type(user.password);
  
  const loginButton = await testdriver.find('login button');
  await loginButton.click();
  
  await testdriver.assert('user is logged in');
}
```

```javascript test/admin.test.mjs
import { describe, expect, it } from "vitest";
import { TestDriver } from "testdriverai/lib/vitest/hooks.mjs";
import { testUsers, testUrls, setupAuthenticatedSession } from './fixtures/index.js';

describe("Admin Panel", () => {
  it("should access admin settings", async (context) => {
    const testdriver = TestDriver(context);
    
    await testdriver.provision.chrome({
      url: `${testUrls.staging}/login`,
    });

    await setupAuthenticatedSession(testdriver, testUsers.admin);

    const settingsLink = await testdriver.find('admin settings link');
    await settingsLink.click();
    
    const result = await testdriver.assert('admin settings panel is visible');
    expect(result).toBeTruthy();
  });
});
```

## Suggested Project Structure

<FileTree>
  <Folder name="test" defaultOpen>
    <Folder name="fixtures" defaultOpen>
      <File name="index.js" />
    </Folder>
    <Folder name="helpers" defaultOpen>
      <File name="auth.js" />
      <File name="navigation.js" />
      <File name="forms.js" />
    </Folder>
    <Folder name="pages" defaultOpen>
      <File name="LoginPage.js" />
      <File name="DashboardPage.js" />
      <File name="CheckoutPage.js" />
    </Folder>
    <Folder name="specs" defaultOpen>
      <File name="auth.test.mjs" />
      <File name="checkout.test.mjs" />
      <File name="search.test.mjs" />
    </Folder>
  </Folder>
</FileTree>

| Folder | Purpose |
|--------|---------|
| `fixtures/` | Test data and setup utilities |
| `helpers/` | Reusable helper functions |
| `pages/` | Page object classes |
| `specs/` | Test files |

<Tip>
Start simple with helper functions. Only introduce page objects when you find yourself duplicating the same element interactions across multiple tests.
</Tip>
