4 mins read
In today’s fast-paced development cycles, automated testing isn’t just helpful—it’s essential. But as your app scales, so does your test suite. That’s where Data-Driven Testing (DDT) comes in. It’s a technique that makes your tests more flexible, reusable, and easier to maintain—especially when you’re working with tools like Playwright and TypeScript.
In this guide, we’ll break down what data-driven testing is, why it’s useful, and how to implement it in a Playwright project with real-world examples using arrays, JSON, and CSV files.
At its core, data-driven testing is about separating your test logic from your test data. Instead of hardcoding input values in your tests, you feed them data from external sources—allowing you to run the same test multiple times with different inputs.
This approach keeps your tests clean, your data manageable, and your testing process scalable.
Here’s why developers and QA teams love DDT:
✅ Write your test logic once, reuse it across different scenarios
✅ Keep test data organised in separate files
✅ Cover more test cases with less code
✅ Make your tests easier to update when data changes
Playwright makes it easy to pull in data from various sources like:
Inline arrays (great for small datasets)
JSON files (structured and readable)
CSV files (great for large sets of tabular data)
Databases (for real-world or dynamic data sets)
Let’s walk through a few examples.
The simplest way to get started is by defining your test data directly in your test file.
// tests/login-data-driven.spec.ts import { test, expect } from '@playwright/test'; const loginData = [ { username: 'admin', password: 'admin123' }, { username: 'user', password: 'user123' }, { username: 'test', password: 'wrongPass' }, ]; loginData.forEach(({ username, password }) => { test(`Login test - ${username}`, async ({ page }) => { await page.goto('https://example.com/login'); await page.fill('#username', username); await page.fill('#password', password); await page.click('#login'); // Replace with actual validation logic const currentUrl = page.url(); expect(currentUrl).toContain('/dashboard'); // adjust this based on expected behavior }); });
This approach is quick and great for small, fixed datasets.
As your data grows or changes frequently, moving it to a .json file keeps things cleaner.
// data/loginData.json [ { "username": "admin", "password": "admin123" }, { "username": "user", "password": "user123" }, { "username": "test", "password": "wrongPass" } ]
// tests/login-json.spec.ts import { test, expect } from '@playwright/test'; import * as loginData from '../data/loginData.json'; (loginData as Array<{ username: string; password: string }>).forEach(({ username, password }) => { test(`Login test (JSON) - ${username}`, async ({ page }) => { await page.goto('https://example.com/login'); await page.fill('#username', username); await page.fill('#password', password); await page.click('#login'); const currentUrl = page.url(); expect(currentUrl).toContain('/dashboard'); }); });
Now your data is external and easily editable, even by non-developers.
For teams working with spreadsheets or large tabular data, CSV is a popular choice. You can use a CSV parser like csv-parse to load the data.
// login.csv username,password admin,admin123 user,user123 test,wrongPass
// utils/csvReader.ts import * as fs from 'fs'; import * as path from 'path'; import { parse } from 'csv-parse/sync'; export function readCsvData(filePath: string) { const content = fs.readFileSync(path.resolve(filePath)); return parse(content, { columns: true, skip_empty_lines: true, }) as Array<{ username: string; password: string }>; }
// tests/login-csv.spec.ts import { test, expect } from '@playwright/test'; import { readCsvData } from '../utils/csvReader'; const data = readCsvData('./data/loginData.csv'); data.forEach(({ username, password }) => { test(`Login test (CSV) - ${username}`, async ({ page }) => { await page.goto('https://example.com/login'); await page.fill('#username', username); await page.fill('#password', password); await page.click('#login'); const currentUrl = page.url(); expect(currentUrl).toContain('/dashboard'); }); });
This method is especially useful when you need to run hundreds of test cases from spreadsheet-style data.
Externalize your data: Use JSON or CSV to make data easier to update and manage.
Use descriptive test titles: Include data points (e.g., usernames) in your test names for easier debugging.
Cover a variety of scenarios: Include valid, invalid, and edge-case inputs.
Don’t repeat yourself (DRY): Keep the logic clean and reusable.
Hook it into CI/CD: Automate these tests as part of your deployment pipeline.
If you’re writing more complex tests (e.g., with setup/teardown, multiple roles, or dynamic environments), look into Playwright’s fixtures system. It allows you to inject data or logic directly into test contexts for even more control.
Data-driven testing isn't just a “nice-to-have”—it’s a smart way to build more resilient and flexible test automation.
With Playwright and TypeScript, you’ve got all the tools you need to implement it quickly, whether your data lives in a JSON file, CSV spreadsheet, or even a database. Start by moving a few hardcoded values into structured data, and you'll see your test coverage and maintainability improve dramatically.
Refactor one of your existing test files to use JSON-based input.
Try loading data from a CSV using the csv-parse library.
Set up your Playwright suite to run on different data sets in your CI pipeline.