SoftwareTestPilot
Module 05 · Lesson 1intermediate 12 min read Playwright

Data-Driven Testing with Playwright

One test, many datasets. Learn every way to parameterize Playwright — inline arrays, JSON, CSV, generated data — and how to keep 500-row runs fast and readable.

Quick answer

Wrap test() in a for...of that iterates your dataset. Give each iteration a unique title so the reporter shows one row per test. Playwright shards them across workers automatically.

1. Inline arrays — the 90% case

import { test, expect } from '@playwright/test';

const cases = [
  { email: '',           expected: 'Email is required' },
  { email: 'no-at',      expected: 'Enter a valid email' },
  { email: 'a@b',        expected: 'Enter a valid email' },
  { email: 'ok@acme.co', expected: '' },
];

for (const { email, expected } of cases) {
  test(`login validates: "${email || '(empty)'}"`, async ({ page }) => {
    await page.goto('/login');
    await page.getByLabel('Email').fill(email);
    await page.getByLabel('Email').blur();
    if (expected) await expect(page.getByRole('alert')).toContainText(expected);
    else          await expect(page.getByRole('alert')).toBeHidden();
  });
}

2. JSON datasets

data/users.json
ts
[
  { "role": "admin",  "email": "admin@acme.co",  "canDelete": true  },
  { "role": "viewer", "email": "viewer@acme.co", "canDelete": false }
]
tests/rbac.spec.ts
ts
import users from '../data/users.json' with { type: 'json' };

for (const u of users) {
  test(`${u.role} delete permission = ${u.canDelete}`, async ({ page }) => {
    // ... login as u.email, navigate, check button
  });
}

3. CSV datasets

import { readFileSync } from 'node:fs';
import { parse } from 'csv-parse/sync';

const rows = parse(readFileSync('data/products.csv'), {
  columns: true, skip_empty_lines: true,
}) as { sku: string; price: string; taxable: string }[];

for (const row of rows) {
  test(`checkout SKU ${row.sku}`, async ({ page }) => {
    // ...
  });
}

4. Generated data with faker

import { faker } from '@faker-js/faker';

test('sign up 5 unique users', async ({ page }) => {
  for (let i = 0; i < 5; i++) {
    const user = {
      email: `test+${Date.now()}-${i}@example.com`,
      name:  faker.person.fullName(),
      pwd:   faker.internet.password({ length: 14 }),
    };
    // sign up user
  }
});
Uniqueness across parallel workers
Add process.env.TEST_WORKER_INDEX to generated emails. Without it two workers can collide on the same millisecond.

5. Tagging & filtering datasets

for (const c of cases) {
  const tag = c.smoke ? '@smoke' : '@regression';
  test(`${c.title} ${tag}`, async ({ page }) => { /* ... */ });
}

// Run only smoke rows in CI:
// npx playwright test --grep @smoke

6. Keeping large datasets fast

ProblemFix
500 tests, all hitting the same serverSet workers: 4 and use test.describe.configure({ mode: 'parallel' })
Repeated login before every testSave storageState once, reuse it across the run
Full E2E per rowTest only the varying step — mock the rest via request context
Report becomes unreadableGroup rows with test.describe(dataset name, ...)

7. Hands-on task (25 minutes)

  1. 1

    Convert 3 copies to 1 loop

    Find 3 near-identical tests. Merge into a for...of with a dataset.

  2. 2

    Extract to JSON

    Move the dataset to data/*.json, import with with { type: "json" }.

  3. 3

    Tag half as @smoke

    Filter with --grep @smoke. Confirm only tagged rows run.

Continue to Module 6 — API Testing.

Frequently asked questions

1.What is data-driven testing?
Running the same test logic against many datasets. Instead of copy-pasting a test five times, you loop over the data and generate five tests — one per row.
2.Does Playwright have test.each like Jest?
You use a plain `for` loop over your data array and call `test(title, fn)` inside. Playwright reports one test per iteration with the dynamic title you set.
3.Where should test data live?
Small inline datasets — top of the spec. Larger sets — a `data/*.json` or `.csv` file. Sensitive datasets — a fixture that reads from an environment variable or a secret manager. Never commit real user data.
4.How do I generate unique data per run?
Use `faker-js/faker` for names/emails and `Date.now()` or a UUID for uniqueness. Never rely on hard-coded strings that collide across parallel workers.
5.How do I tag a dataset (smoke, regression)?
Include `@smoke` in the test title. Filter with `npx playwright test --grep @smoke`. In config, you can also define projects that run only tagged tests.
6.Are looped tests still parallel?
Yes. Playwright shards tests across workers regardless of how they were created. Each iteration gets its own worker slot.

Related lessons