1. Parameterized tests
The simplest form of data-driven testing: a plain for loop around test(). Playwright registers one independent test per iteration.
import { test, expect } from '@playwright/test';
const users = [
{ name: 'Alice', role: 'admin' },
{ name: 'Bob', role: 'viewer' },
];
for (const user of users) {
test(`dashboard shows correct role for ${user.name}`, async ({ page }) => {
await page.goto('/');
await expect(page.getByText(user.role)).toBeVisible();
});
}2. JSON fixtures
Keep your data out of the spec. JSON is the fastest path — TypeScript loads it natively.
import users from './fixtures/users.json' assert { type: 'json' };
for (const user of users) {
test(`login as ${user.email}`, async ({ page }) => {
// ...
});
}3. CSV parsing
Use csv-parse when data comes from product/business teams — they live in spreadsheets.
import fs from 'node:fs';
import { parse } from 'csv-parse/sync';
const records = parse(fs.readFileSync('fixtures/users.csv'), {
columns: true,
skip_empty_lines: true,
});
for (const row of records) {
test(`signup ${row.email}`, async ({ page }) => { /* ... */ });
}4. Environment variables
Use .env + dotenv for per-environment switches (staging vs prod, feature flags, secrets). Read them via process.env.
// playwright.config.ts
import 'dotenv/config';
export default defineConfig({
use: { baseURL: process.env.BASE_URL ?? 'http://localhost:3000' },
});5. Test data builders
Factory functions generate unique data per run, so tests don't collide on duplicates (e.g. "email already in use").
import { randomUUID } from 'node:crypto';
export const makeUser = (overrides: Partial<User> = {}): User => ({
email: `qa+${randomUUID()}@example.com`,
password: 'Passw0rd!',
...overrides,
});
// usage
const user = makeUser({ role: 'admin' });6. Hands-on task
Create a users.csv with 50 rows. Loop over it and run a login test per row, asserting the correct landing page based on each user's role.
for (const row of records) {
test(`login ${row.email} lands on ${row.expected_page}`, async ({ page }) => {
await loginAs(page, row.email, row.password);
await expect(page).toHaveURL(new RegExp(row.expected_page));
});
}7. What's next
Next, move into Module 08: CI/CD with GitHub Actions to run your suite on every push and ship reports your team will actually read.
Up next in the learning path
Module 08: CI/CD with GitHub Actions