|

Data-Driven Test Automation With Playwright: Beyond Static Scripts to Dynamic Frameworks

Modern web applications are dynamic. Your tests should be too. Static, hardcoded test scripts fail at scale because every new test scenario requires a new test file. Data-driven testing generates hundreds of test cases from a single test template.

🎭 Want to master this with real projects? Join the Playwright Automation Mastery course at The Testing Academy.

Contents

JSON-Based Parameterization

// testdata/login-scenarios.json
[
  { "email": "admin@test.com", "password": "valid-pass", "expected": "dashboard", "desc": "valid admin" },
  { "email": "user@test.com", "password": "wrong", "expected": "error", "desc": "invalid password" },
  { "email": "", "password": "pass", "expected": "error", "desc": "empty email" },
  { "email": "admin@test.com", "password": "", "expected": "error", "desc": "empty password" },
  { "email": "sql'injection", "password": "pass", "expected": "error", "desc": "SQL injection in email" }
]

// tests/login.spec.ts
import scenarios from '../testdata/login-scenarios.json';

for (const scenario of scenarios) {
  test('login: ' + scenario.desc, async ({ page }) => {
    const loginPage = new LoginPage(page);
    await loginPage.goto();
    await loginPage.login(scenario.email, scenario.password);
    
    if (scenario.expected === 'dashboard') {
      await expect(page).toHaveURL(/dashboard/);
    } else {
      await expect(page.getByRole('alert')).toBeVisible();
    }
  });
}

CSV Data Sources With Fixtures

import { test } from '@playwright/test';
import fs from 'fs';

function loadCsv(path: string): Record<string, string>[] {
  const content = fs.readFileSync(path, 'utf-8');
  const [header, ...rows] = content.split('\n').filter(Boolean);
  const keys = header.split(',');
  return rows.map(row => {
    const values = row.split(',');
    return Object.fromEntries(keys.map((k, i) => [k.trim(), values[i]?.trim()]));
  });
}

const products = loadCsv('testdata/products.csv');

for (const product of products) {
  test('add to cart: ' + product.name, async ({ page }) => {
    await page.goto('/products/' + product.id);
    await page.getByRole('button', { name: 'Add to cart' }).click();
    await expect(page.getByTestId('cart-count')).toHaveText('1');
  });
}

🚀 Level Up Your Playwright

From locators to CI pipelines — build a production-grade Playwright + TypeScript framework step by step.

API-Driven Test Data Setup

// fixtures/data.fixture.ts
import { test as base } from '@playwright/test';

type TestData = { userId: string; productId: string; orderId: string };

export const test = base.extend<{ testData: TestData }>({
  testData: async ({ request }, use) => {
    // Create test data via API
    const user = await (await request.post('/api/users', { data: { name: 'Test' } })).json();
    const product = await (await request.post('/api/products', { data: { name: 'Widget', price: 29.99 } })).json();
    
    await use({ userId: user.id, productId: product.id, orderId: '' });
    
    // Cleanup
    await request.delete('/api/users/' + user.id);
    await request.delete('/api/products/' + product.id);
  }
});

When to Use Each Approach

ApproachBest ForAvoid When
JSON filesSmall, static datasets (login scenarios, form validations)Data changes frequently
CSV filesLarge datasets, non-technical contributors editing dataComplex nested structures
API fixturesDynamic data, test isolation, parallel executionAPIs are unstable or slow
Database seedingComplex relational data, performance testingNo direct DB access in CI

🎓 Master Playwright End to End

Join hundreds of SDETs building real automation frameworks. Lifetime access, hands-on projects, and a job-ready portfolio.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.