Playwright Data Driven Testing Tutorial: Complete Guide with JSON, CSV, Fixtures, POM, and API Data

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

    +

Test Data

    ↓

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 testingData driven testing
Duplicate test codeReusable test logic
Data embedded in testsData separated from logic
Hard to add casesEasy to add cases
More maintenanceLess maintenance
Limited coverageBetter 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

Install Playwright browsers

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

Test data

Page objects

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

JSONCSV
Better for nested dataBetter for tabular data
Developer-friendlySpreadsheet-friendly
Supports objectsRows and columns
Good for complex test dataGood for large datasets

Playwright Data Driven Testing with TypeScript

TypeScript becomes particularly useful when test data has a defined structure.

Create an interface:

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

     ↓

Page Object

     ↓

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”

  }

]

Test both roles.

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

ErrorPossible causeSolution
JSON import failsTypeScript configurationEnable resolveJsonModule
CSV parsing failsInvalid CSV formatValidate headers and rows
Test names are unclearPoor parameter namingInclude data identifier
Tests interfereShared test dataCreate isolated data
Random failuresUnstable generated dataMake data deterministic
CI cannot find data fileIncorrect relative pathUse project-relative paths
Authentication failsInvalid credentialsUse CI secrets
Too many duplicate testsPoor data designSeparate scenarios from data
Fixture data unavailableIncorrect fixture scopeReview fixture definition
API data missingSetup request failedValidate 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:

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.

Leave a Comment

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