Introduction
Modern QA automation rarely tests an application with only one set of input values.
A login feature may need valid users, invalid users, locked accounts, administrators, and different roles. A product search may require multiple keywords. A checkout page may need different addresses, payment scenarios, and product combinations.
Writing a separate test for every input creates duplicate code and makes automation difficult to maintain.
Data driven testing solves this problem by separating test logic from test data.
In this playwright data driven testing tutorial, you will learn how to build reusable parameterized tests using Playwright and TypeScript. You will work with arrays, objects, JSON files, CSV files, fixtures, Page Object Model, API-generated data, multiple browsers, parallel execution, reporting, and CI/CD.
Playwright Test does not require a separate parameterization framework. Tests can be generated from JavaScript/TypeScript data structures, while fixtures provide reusable test setup and dependencies.
By the end, you will understand how to build a practical Playwright Data Driven Testing framework suitable for real QA projects and SDET portfolios.
What Is Playwright Data Driven Testing?
Playwright data driven testing is an automation approach where the same test logic executes against multiple sets of test data.
For example, instead of writing three login tests:
Test login with user1
Test login with user2
Test login with user3
you create one test and provide three data sets:
User 1 → username + password
User 2 → username + password
User 3 → username + password
Conceptually:
Test Logic
+
↓
Multiple Test Cases
A simple Playwright parameterized test looks like:
const users = [
{ username: ‘user1@example.com’, password: ‘Password1’ },
{ username: ‘user2@example.com’, password: ‘Password2’ }
];
for (const user of users) {
test(`login with ${user.username}`, async ({ page }) => {
// test logic
});
}
This is the foundation of Data Driven Testing with Playwright.
Why Use Data Driven Testing in Playwright?
Data driven testing is useful because it improves:
- Reusability
- Maintainability
- Test coverage
- Readability
- Scalability
- Debugging
- Test-data management
| Traditional testing | Data driven testing |
| Duplicate test code | Reusable test logic |
| Data embedded in tests | Data separated from logic |
| Hard to add cases | Easy to add cases |
| More maintenance | Less maintenance |
| Limited coverage | Better coverage |
For large Playwright Automation Testing projects, this separation becomes extremely important.
Playwright Data Driven Testing Project Setup
Create a Playwright TypeScript project:
npm init playwright@latest
Select:
TypeScript
tests directory
A useful structure is:
playwright-data-driven/
│
├── tests/
│ ├── login.spec.ts
│ ├── search.spec.ts
│ └── checkout.spec.ts
│
├── data/
│ ├── users.json
│ ├── products.json
│ └── users.csv
│
├── pages/
│ ├── LoginPage.ts
│ ├── SearchPage.ts
│ └── CheckoutPage.ts
│
├── fixtures/
│ └── testData.fixture.ts
│
├── playwright.config.ts
└── package.json
This architecture separates:
Test logic
Fixtures
Configuration
Creating Your First Parameterized Playwright Test
The simplest Playwright parameterized test example uses an array.
import { test, expect } from ‘@playwright/test’;
const searchData = [
‘laptop’,
‘mobile phone’,
‘headphones’
];
for (const keyword of searchData) {
test(`search for ${keyword}`, async ({ page }) => {
await page.goto(‘/products’);
await page.getByPlaceholder(‘Search’).fill(keyword);
await page.getByRole(‘button’, {
name: ‘Search’
}).click();
await expect(
page.locator(‘.search-results’)
).toBeVisible();
});
}
Playwright creates three tests:
search for laptop
search for mobile phone
search for headphones
This is one of the easiest ways to get started with Playwright data driven testing.
Using Arrays and Objects as Test Data
Objects are better when each test needs multiple values.
const users = [
{
username: ‘user1@example.com’,
password: ‘Password123’,
role: ‘customer’
},
{
username: ‘admin@example.com’,
password: ‘Admin123’,
role: ‘admin’
}
];
Use them in tests:
for (const user of users) {
test(`login as ${user.role}`, async ({ page }) => {
await page.goto(‘/login’);
await page.getByLabel(‘Email’).fill(user.username);
await page.getByLabel(‘Password’).fill(user.password);
await page.getByRole(‘button’, {
name: ‘Login’
}).click();
await expect(page).toHaveURL(/dashboard/);
});
}
Why objects are useful
Instead of passing only:
username
you can provide:
username
password
role
expectedUrl
expectedMessage
That makes your test data much more expressive.
Reading Test Data from JSON Files
JSON is one of the most convenient formats for Playwright test data.
Create:
data/users.json
[
{
“username”: “customer@example.com”,
“password”: “Customer123”,
“role”: “customer”
},
{
“username”: “admin@example.com”,
“password”: “Admin123”,
“role”: “admin”
}
]
Import it:
import users from ‘../data/users.json’;
Depending on your TypeScript configuration, enable JSON imports in tsconfig.json:
{
“compilerOptions”: {
“resolveJsonModule”: true,
“esModuleInterop”: true
}
}
Now create tests:
import { test, expect } from ‘@playwright/test’;
import users from ‘../data/users.json’;
for (const user of users) {
test(`login test – ${user.role}`, async ({ page }) => {
await page.goto(‘/login’);
await page.getByLabel(‘Email’).fill(user.username);
await page.getByLabel(‘Password’).fill(user.password);
await page.getByRole(‘button’, {
name: ‘Login’
}).click();
await expect(page).toHaveURL(/dashboard/);
});
}
This is a practical Playwright test data from JSON example.
Advantages of JSON
- Easy to read
- Easy to modify
- Supports nested structures
- Works naturally with TypeScript
- Good for configuration-like test data
Reading Test Data from CSV Files
CSV is useful when test data is maintained in spreadsheets or external data systems.
Example:
username,password,role
customer@example.com,Customer123,customer
admin@example.com,Admin123,admin
manager@example.com,Manager123,manager
You can use a CSV parser such as csv-parse.
Install:
npm install csv-parse
Read the CSV:
import fs from ‘fs’;
import { parse } from ‘csv-parse/sync’;
const csvData = fs.readFileSync(
‘data/users.csv’,
‘utf-8’
);
const users = parse(csvData, {
columns: true,
skip_empty_lines: true
});
Use it:
import { test } from ‘@playwright/test’;
for (const user of users) {
test(`login as ${user.role}`, async ({ page }) => {
await page.goto(‘/login’);
await page.getByLabel(‘Email’).fill(user.username);
await page.getByLabel(‘Password’).fill(user.password);
await page.getByRole(‘button’, {
name: ‘Login’
}).click();
});
}
This provides a practical Playwright CSV test data implementation.
JSON vs CSV
| JSON | CSV |
| Better for nested data | Better for tabular data |
| Developer-friendly | Spreadsheet-friendly |
| Supports objects | Rows and columns |
| Good for complex test data | Good for large datasets |
Playwright Data Driven Testing with TypeScript
TypeScript becomes particularly useful when test data has a defined structure.
interface UserData {
username: string;
password: string;
role: string;
}
Then:
const users: UserData[] = [
{
username: ‘customer@example.com’,
password: ‘Customer123’,
role: ‘customer’
},
{
username: ‘admin@example.com’,
password: ‘Admin123’,
role: ‘admin’
}
];
Now TypeScript can detect invalid properties and types before the tests execute.
This makes Playwright data driven testing with TypeScript safer for large automation frameworks.
Data Driven Testing with Page Object Model
Data driven testing becomes even more powerful when combined with Page Object Model.
Create:
pages/LoginPage.ts
import { Page } from ‘@playwright/test’;
export class LoginPage {
constructor(private page: Page) {}
async login(
username: string,
password: string
) {
await this.page.getByLabel(‘Email’).fill(username);
await this.page.getByLabel(‘Password’).fill(password);
await this.page.getByRole(‘button’, {
name: ‘Login’
}).click();
}
}
Test:
import { test, expect } from ‘@playwright/test’;
import { LoginPage } from ‘../pages/LoginPage’;
const users = [
{
username: ‘customer@example.com’,
password: ‘Customer123’
},
{
username: ‘admin@example.com’,
password: ‘Admin123’
}
];
for (const user of users) {
test(`login – ${user.username}`, async ({ page }) => {
const loginPage = new LoginPage(page);
await page.goto(‘/login’);
await loginPage.login(
user.username,
user.password
);
await expect(page).toHaveURL(/dashboard/);
});
}
Now:
Test Data
↓
Test
↓
↓
Application
This is a strong architecture for enterprise automation.
Using Fixtures for Test Data
Fixtures are useful when multiple test files need the same data or setup.
For example:
import {
test as base
} from ‘@playwright/test’;
type TestData = {
user: {
username: string;
password: string;
};
};
export const test = base.extend<TestData>({
user: async ({}, use) => {
await use({
username: ‘customer@example.com’,
password: ‘Customer123’
});
}
});
Test:
import { test } from ‘../fixtures/testData.fixture’;
test(‘login using fixture data’, async ({
page,
user
}) => {
await page.goto(‘/login’);
await page.getByLabel(‘Email’).fill(user.username);
await page.getByLabel(‘Password’).fill(user.password);
await page.getByRole(‘button’, {
name: ‘Login’
}).click();
});
Fixtures are best for reusable setup and dependencies, while external JSON/CSV files are often better for large data sets.
Playwright’s fixture system is designed to provide isolated, reusable test setup.
Data Driven Login Testing Example
A real login suite might contain:
const loginCases = [
{
name: ‘valid user’,
username: ‘user@example.com’,
password: ‘Valid123’,
expected: ‘success’
},
{
name: ‘invalid password’,
username: ‘user@example.com’,
password: ‘Wrong123’,
expected: ‘failure’
},
{
name: ‘unknown user’,
username: ‘unknown@example.com’,
password: ‘Valid123’,
expected: ‘failure’
}
];
Test:
for (const data of loginCases) {
test(`login – ${data.name}`, async ({ page }) => {
await page.goto(‘/login’);
await page.getByLabel(‘Email’).fill(data.username);
await page.getByLabel(‘Password’).fill(data.password);
await page.getByRole(‘button’, {
name: ‘Login’
}).click();
if (data.expected === ‘success’) {
await expect(page).toHaveURL(/dashboard/);
} else {
await expect(
page.getByText(/invalid credentials/i)
).toBeVisible();
}
});
}
This allows positive and negative scenarios to share the same test logic.
Data Driven Form and Search Testing
Suppose an e-commerce site has different search terms.
const searchCases = [
{
keyword: ‘laptop’,
expected: ‘Laptop’
},
{
keyword: ‘phone’,
expected: ‘Phone’
},
{
keyword: ‘headphones’,
expected: ‘Headphones’
}
];
Test:
for (const data of searchCases) {
test(`search – ${data.keyword}`, async ({ page }) => {
await page.goto(‘/products’);
await page.getByPlaceholder(‘Search’)
.fill(data.keyword);
await page.getByRole(‘button’, {
name: ‘Search’
}).click();
await expect(
page.locator(‘.product-title’)
).toContainText(data.expected);
});
}
The same approach works for:
- Registration forms
- Address forms
- Checkout
- Contact forms
- Search
- Filters
- Sorting
- Product categories
- Payment scenarios
API-Based Test Data and Playwright API Testing
Sometimes test data should be generated through an API rather than stored manually.
For example:
const response = await request.post(‘/api/products’, {
data: {
name: ‘Test Laptop’,
price: 50000
}
});
const product = await response.json();
Then use the generated product in the UI:
await page.goto(‘/products’);
await page.getByPlaceholder(‘Search’)
.fill(product.name);
await page.getByRole(‘button’, {
name: ‘Search’
}).click();
This creates a powerful workflow:
API
↓
Create test data
↓
UI
↓
Execute workflow
↓
Validate result
It is particularly useful when test data must be unique.
Dynamic Test Data Generation
Hard-coded data isn’t always appropriate.
You may need:
- Unique email addresses
- Order numbers
- Product names
- Customer IDs
- Timestamps
Example:
function generateEmail(): string {
return `test-${Date.now()}@example.com`;
}
Use it:
const email = generateEmail();
await page.getByLabel(‘Email’).fill(email);
For predictable tests, generate data using controlled rules.
Avoid random values that make failures impossible to reproduce.
A good strategy is:
Unique
+
Predictable
+
Logged safely
=
Reliable test data
Multiple Browsers and Parallel Execution
Playwright supports multiple browser projects.
For example:
projects: [
{
name: ‘chromium’,
use: {
browserName: ‘chromium’
}
},
{
name: ‘firefox’,
use: {
browserName: ‘firefox’
}
}
]
Your data-driven tests can run against both.
Parallel execution can also reduce execution time:
npx playwright test –workers=4
If you have:
10 login cases
10 search cases
10 checkout cases
you can execute independent tests concurrently.
However, data must be isolated.
Avoid having multiple tests modify the same account, shopping cart, or database record.
Use unique records where necessary.
Playwright Data Driven Testing with CI/CD
A CI/CD pipeline can execute your complete data-driven suite automatically.
Example GitHub Actions workflow:
name: Playwright Data Driven Tests
on:
push:
branches: [main]
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
– uses: actions/checkout@v6
– uses: actions/setup-node@v6
with:
node-version: lts/*
– run: npm ci
– run: npx playwright install –with-deps
– run: npx playwright test
– name: Upload report
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v5
with:
name: playwright-report
path: playwright-report/
Playwright’s CI documentation describes this general workflow for installing dependencies, browsers, executing tests, and publishing artifacts.
For sensitive data, use CI/CD secrets rather than putting credentials inside JSON or CSV files.
Reporting and Debugging Data Driven Tests
Parameterized tests should have descriptive test names.
Good:
test(`login – ${data.name}`, async () => {});
Poor:
test(‘test 1’, async () => {});
When a test fails, you should immediately know which data set caused the failure.
HTML reporting
Run:
npx playwright test
npx playwright show-report
Screenshots on failure
Configure:
use: {
screenshot: ‘only-on-failure’
}
Trace collection
use: {
trace: ‘retain-on-failure’
}
A trace helps identify whether the failure came from:
- Wrong data
- Locator issue
- Application behavior
- Network failure
- Timing issue
- Incorrect expected value
Real-World E-Commerce Playwright Data Driven Testing Project
A strong portfolio project can combine all the techniques discussed in this playwright data driven testing tutorial.
Project structure
ecommerce-data-driven/
│
├── data/
│ ├── users.json
│ ├── products.json
│ ├── search.csv
│ └── checkout.json
│
├── pages/
│ ├── LoginPage.ts
│ ├── HomePage.ts
│ ├── ProductPage.ts
│ ├── CartPage.ts
│ └── CheckoutPage.ts
│
├── fixtures/
│ └── testData.fixture.ts
│
├── tests/
│ ├── login.spec.ts
│ ├── search.spec.ts
│ ├── product.spec.ts
│ ├── cart.spec.ts
│ └── checkout.spec.ts
│
└── playwright.config.ts
Multiple login users
Create:
[
{
“username”: “customer@example.com”,
“password”: “Customer123”,
“role”: “customer”
},
{
“username”: “admin@example.com”,
“password”: “Admin123”,
“role”: “admin”
}
]
Product categories
Use data such as:
[
{
“category”: “electronics”,
“product”: “Laptop”
},
{
“category”: “audio”,
“product”: “Headphones”
}
]
Invalid credentials
Add negative cases:
[
{
“username”: “wrong@example.com”,
“password”: “Wrong123”,
“expected”: “Invalid credentials”
}
]
Checkout data
[
{
“firstName”: “Test”,
“lastName”: “User”,
“city”: “Bengaluru”,
“postalCode”: “560001”
}
]
API-generated products
Create test products through the API and then search for them through the UI.
Parallel execution
Run independent product/search tests in parallel.
Reporting
Generate HTML reports for every CI execution.
Debugging
Collect:
- Screenshots
- Videos when needed
- Traces
- Console errors
- Test reports
This project demonstrates:
Playwright + TypeScript + Data Driven Testing + JSON + CSV + POM + Fixtures + API Testing + Parallel Execution + CI/CD + Reporting.
That is a strong portfolio combination for QA Automation and SDET roles.
Common Playwright Data Driven Testing Errors and Solutions
| Error | Possible cause | Solution |
| JSON import fails | TypeScript configuration | Enable resolveJsonModule |
| CSV parsing fails | Invalid CSV format | Validate headers and rows |
| Test names are unclear | Poor parameter naming | Include data identifier |
| Tests interfere | Shared test data | Create isolated data |
| Random failures | Unstable generated data | Make data deterministic |
| CI cannot find data file | Incorrect relative path | Use project-relative paths |
| Authentication fails | Invalid credentials | Use CI secrets |
| Too many duplicate tests | Poor data design | Separate scenarios from data |
| Fixture data unavailable | Incorrect fixture scope | Review fixture definition |
| API data missing | Setup request failed | Validate response before UI test |
Playwright Data Driven Testing Best Practices
Follow these rules when building a professional framework.
1. Separate test data from test logic
Avoid:
await page.fill(‘#email’, ‘user@example.com’);
throughout every test.
Use data objects instead.
2. Use meaningful test names
Include the scenario or data identifier.
3. Use TypeScript interfaces
They make complex test data safer.
4. Use JSON for structured data
Good for:
- Users
- Products
- Configuration
- Checkout objects
5. Use CSV for tabular data
Good for:
- Large datasets
- Spreadsheet-maintained data
- Simple rows and columns
6. Use fixtures for reusable setup
Fixtures are not simply a replacement for JSON or CSV. They are best for reusable dependencies and setup.
7. Generate API data when necessary
This reduces dependence on manually maintained records.
8. Keep data independent
Parallel tests should not modify the same records.
9. Avoid sensitive credentials in data files
Use environment variables and CI secrets.
10. Make failures reproducible
Random data should be controlled and identifiable.
11. Keep expected results with the relevant test data
For example:
{
keyword: ‘laptop’,
expectedCount: 5
}
12. Run data-driven tests in CI
This ensures new code is tested against multiple scenarios automatically.
Playwright Data Driven Testing Interview Questions with Answers
1. What is Playwright data driven testing?
It is a technique where the same Playwright test logic executes against multiple input datasets.
2. How do I perform data driven testing in Playwright?
Use arrays, objects, JSON, CSV, fixtures, or API-generated data and iterate over the data to create test cases.
3. Does Playwright have a built-in parameterization annotation?
Playwright Test does not require a JUnit-style parameterization annotation. JavaScript/TypeScript iteration can generate tests from datasets.
4. What is the difference between data driven testing and parameterization?
Parameterization is the mechanism for supplying different values to a test.
Data driven testing is the broader approach of separating test logic from test data and executing the same scenario with multiple datasets.
5. Can Playwright read JSON test data?
Yes.
For example:
import users from ‘../data/users.json’;
6. Can Playwright read CSV test data?
Yes. You can use Node.js file APIs together with a CSV parsing library.
7. How do you use POM with data driven testing?
Keep input data in arrays/JSON/CSV and pass the values into reusable Page Object methods.
8. Can data driven tests run in parallel?
Yes. Playwright supports parallel execution, provided the tests and test data are isolated.
9. Why use fixtures with data driven testing?
Fixtures provide reusable setup and dependencies while test data can remain external or parameterized.
10. How do you debug a failed parameterized test?
Give each generated test a descriptive name containing the scenario or data identifier, then use Playwright reports, screenshots, and traces.
Learning Roadmap for Beginners
If you are new to Playwright Data Driven Testing, follow this sequence.
Step 1: Learn Playwright basics
Learn:
- Locators
- Assertions
- Pages
- Browser contexts
- Auto-waiting
Step 2: Learn Playwright TypeScript
Understand:
- Arrays
- Objects
- Interfaces
- Classes
- Async/await
- Modules
Step 3: Learn parameterized tests
Start with:
for (const data of testData) {
test(…);
}
Step 4: Learn external test data
Practice:
JSON
CSV
Step 5: Learn Page Object Model
Separate:
Tests
Pages
Data
Step 6: Learn fixtures
Use fixtures for:
- Authentication
- Test data
- API clients
- Page objects
- Setup
Step 7: Learn API testing
Generate test data through APIs.
Step 8: Learn parallel execution
Understand data isolation and worker execution.
Step 9: Learn CI/CD
Practice GitHub Actions, Docker, reporting, and artifacts.
Step 10: Build a portfolio project
Build the e-commerce framework described above and publish a sanitized version on GitHub.
FAQs: Playwright Data Driven Testing Tutorial
What is Playwright data driven testing?
Playwright data driven testing executes the same automation scenario against multiple sets of test data.
How do I perform data driven testing in Playwright?
Create an array, object, JSON file, CSV file, fixture, or API dataset and use it to generate multiple Playwright tests.
What is the easiest Playwright parameterized test example?
const values = [‘laptop’, ‘phone’, ‘tablet’];
for (const value of values) {
test(`search ${value}`, async ({ page }) => {
// test logic
});
}
Can Playwright use JSON test data?
Yes. JSON is one of the simplest ways to maintain structured Playwright test data.
Can Playwright use CSV?
Yes. Node.js file handling and a CSV parser can be used to load CSV datasets.
Should test data be stored inside test files?
Small datasets can be stored directly in the test. Larger or reusable datasets should generally be separated into dedicated data files or generated through APIs.
What is the difference between fixtures and test data?
Test data represents the values used by the scenario.
Fixtures provide reusable setup, dependencies, or resources needed by tests.
Is data driven testing useful for SDETs?
Yes. It demonstrates test design, framework architecture, maintainability, coverage, and scalable automation.
