Test Automation Blog

Tools, Tips and Thoughts for Playwright, Selenium UI and API automated testing

What are Playwright ‘fixtures’ and how to use them?

Fixtures are a feature of the Playwright test runner that allows you to define commonly used components or values for your test automation needs. Examples of fixture use include database connections and authentication logic. Playwright fixtures automatically manage lifecycle (setup and cleanup) and can be overridden or customized per test or test file.

Playwright comes with several built-in fixtures you will already be familiar with, in particular the page fixture:

test('Home page has correct title', async ({ page }) => {
  await page.goto('https://www.mywebsite.com');
  await expect(page).toHaveTitle('Welcome to my website');
});

Other commonly used Playwright fixtures include the context, browser and browserName fixtures.

To create your own Playwright fixture, use the test.extend() method.

Example 1: Simple ‘data value’ fixture

Let’s look at the simplest use of a Playwright fixture to get a clear idea of how the code is structured. In this case we’ll create a fixture that simply provides a fictional company name value: “Fixture Systems Inc.” that can be used in any test.

Create a new source file for fixtures (e.g. custom-fixtures.ts, in the root folder) and into it put the code to extend. Replace the Playwright test object with your fixture extension, which we’ll call companyName.

// custom-fixtures.ts
import { test as baseTest } from '@playwright/test';

export const test = baseTest.extend<{ companyName: string }>({
  companyName: async ({}, use) => {
    const customValue = 'Fixture Systems Inc.';
    await use(customValue); // inject into test
  },
});

export { expect } from '@playwright/test';

To summarize the code: we extend and replace the existing test object by adding a companyName value. We then re-export it so it can be available to tests. The await use(customValue); code allows the fixture to be injected into tests.

For convenience we also re-export expect from @playwright/test in the last line of code so we can import both test and expect from one file – see usage below.

To use the extended companyName fixture in a test:

// import { test, expect } from '@playwright/test';
import { test, expect } from '../custom-fixtures'

test('Show company name', async ({companyName}) => {
  console.log(`Copyright ${companyName}`)
});

NOTE: test and expect are now imported from the new custom-fixtures file instead of @playwright/test.

When you run this test you should see the company name output:

Example 2: Database client fixture

A common fixture created for Playwright tests is a database connection fixture. It is set up once, but can then be used by any test by simply passing the database connection fixture into the test method as a parameter. Let’s go ahead and create a PostgreSQL database client fixture for Playwright and use it in an automated test.

Create a new source file for the dbClient database connection fixture below (or replace the simple fixture created in Example 1 above). It uses PostgreSQL which you can install using npm install pg

// custom-fixtures.ts
import { test as baseTest } from '@playwright/test';
import { Client } from 'pg';

// Extend the base test with a new dbClient fixture
export const test = baseTest.extend<{ dbClient: Client; }>({
  dbClient: async ({}, use) => {
    const client = new Client({
      host: 'localhost',
      port: 5432,
      user: 'test_user',
      password: 'test_password',
      database: 'test_database',
    });

    await client.connect();

    // Optional: Seed database
    await client.query('DELETE FROM users;');
    await client.query("INSERT INTO users (id, name) VALUES (1, 'Mary');");
    await client.query("INSERT INTO users (id, name) VALUES (2, 'John');");

    // Make it available in tests
    await use(client);

    // Cleanup
    await client.end();
  },
});

export { expect } from '@playwright/test';

Then you can use the dbClient fixture in your tests like so:

// import { test, expect } from '@playwright/test';
import { test, expect } from '../custom-fixtures'

test('Test database connection', async ({dbClient}) => {
  const result = await dbClient.query('SELECT * FROM users WHERE name = $1', ['Mary']);
  expect(result.rows.length).toBe(1);
  expect(result.rows[0].name).toBe('Mary');
});

One thing to notice is that the cleanup code await client.end(); doesn’t execute until after the test executes.

Conclusion

Fixtures are an excellent Playwright feature which enable us to write more maintainable test automation code by:

  • having a place to put reusable setup logic
  • DRY (Don’t Repeat Yourself) in your test code
  • cleaner and more maintainable tests
  • automatic cleanup
  • fine-grained control over test environments

And finally….

A use of Playwright fixtures I’ve seen quite often is to create them for each page in your page object model. Then in each test inject the ones you need instead of having to instantiate them within the test code. For example:

test('Check user can log in', async ({homePage, loginPage}) => {
  homePage.goto();
  // etc
});

Personally I’m not a fan of using fixtures for this because on large projects you can have a large number of page objects and therefore fixtures. It has the smell of a short term ‘good idea’ and ‘shiny toy’ syndrome. However with little consideration of how it affects the maintainability of the test automation framework in the longer term.

What are Playwright ‘fixtures’ and how to use them?
Scroll to top