Max Schmitt

December 18 2022

Extending Playwright's Built-In Test Method for Super-Clean Test Setups

Today I had the pleasure of using Playwright to write some E2E tests.

What I really enjoyed, was how it allows you to extend the built-in test function.

This is super-useful to, for example, extract out repetitive setup and teardown logic.

Before

This is how I would approach writing test setup/teardown logic coming from a framework like Mocha or Jest:

users.spec.ts

import { test } from '@playwright/test'
import { Db } from './Db'
test.describe('Users', () => {
let db: Db
test.beforeEach(async () => {
db = await Db.connect()
await db.cleanup()
})
test.afterEach(async () => {
await db.disconnect()
})
test('logs the user in', async ({ page }) => {
await db.createUser()
// ...
})
})

After

Using Playwright's test.extend we can make this so much cleaner. Notice how I'm no longer importing test from @playwright/test here:

users.spec.ts

import { test } from './test-helpers/test'
test.describe('Users', () => {
test('logs the user in', async ({ db, page }) => {
await db.createUser()
// ...
})
})

Here's how:

test-helpers/test.ts

import { test as playwrightTest } from '@playwright/test'
import { Db } from '../Db'
interface TestParams {
db: Db
}
export const test = base.extend<TestParams>({
db: async ({}, use) => {
// Previously in test.beforeEach:
const db = await Db.connect()
await db.cleanup()
// Run the test:
await use(db)
// Previously in test.afterEach:
await db.disconnect()
},
})