Data-Driven Testing (DDT) in Playwright allows you to run the same test multiple times with different sets of data. This is particularly useful when you want to test various input combinations or scenarios efficiently. Here’s a comprehensive guide on implementing DDT in Playwright: Approaches for Data-Driven Testing in Playwright Using Test Parameters (test.describe.parallel) You can use test parameters to pass data directly into tests. import { test, expect } from ‘@playwright/test’; const testData = [ { username: ‘user1’, password: ‘pass1’ }, { username: ‘user2’, password: ‘pass2’ }, { username: ‘user3’, password: ‘pass3’ } ]; test.describe.parallel(‘Login Tests’, () => { for (const data of testData) { test(Login test for ${data.username}, async ({ page }) => { await page.goto(‘ https://example.com/login’ ); await page.fill(‘#username’, data.username); await page.fill(‘#password’, data.password); await page.click(‘#loginButton’); const successMessage = await page.locator(‘#successMessage’).textContent(); expect(successMessage).toContain(‘Welcome’); }); } }); 2. Using Playwright’s test.step You can iterate over a data set and wrap each iteration in a test.step for better debugging and reporting. import { test, expect } from ‘@playwright/test’; const testData = [ { username: ‘user1’, password: ‘pass1’ }, { username: ‘user2’, password: ‘pass2’ }, ]; test(‘Login Test’, async ({ page }) => { for (const data of testData) { await test.step(Testing login for ${data.username}, async () => { await page.goto(‘ https://example.com/login’ ); await page.fill(‘#username’, data.username); await page.fill(‘#password’, data.password); await page.click(‘#loginButton’); const successMessage = await page.locator(‘#successMessage’).textContent(); expect(successMessage).toContain(‘Welcome’); }); } }); 3. Using .test.each() Playwright supports test.each() for running tests with different sets of data. import { test, expect } from ‘@playwright/test’; const testData = [ [‘user1’, ‘pass1’], [‘user2’, ‘pass2’], [‘user3’, ‘pass3’], ]; test.describe(‘Data-Driven Login Tests’, () => { test.each(testData)(‘Login test for %s’, async (username, password) => { await page.goto(‘ https://example.com/login’ ); await page.fill(‘#username’, username); await page.fill(‘#password’, password); await page.click(‘#loginButton’); const successMessage = await page.locator(‘#successMessage’).textContent(); expect(successMessage).toContain(‘Welcome’); }); }); 4. Loading Test Data from an External Source You can load data from a file (e.g., JSON, CSV, or Excel) for more complex scenarios. • JSON Example: import { test, expect } from ‘@playwright/test’; import * as testData from ‘./testData.json’; test(‘Login Tests’, async ({ page }) => { for (const data of testData) { await page.goto(‘ https://example.com/login’ ); await page.fill(‘#username’, data.username); await page.fill(‘#password’, data.password); await page.click(‘#loginButton’); const successMessage = await page.locator(‘#successMessage’).textContent(); expect(successMessage).toContain(‘Welcome’); } }); testData.json: [ { “username”: “user1”, “password”: “pass1” }, { “username”: “user2”, “password”: “pass2” } ] • CSV Example: import { test, expect } from ‘@playwright/test’; import * as fs from ‘fs’; import * as csv from ‘csv-parser’; const testData: Array<{ username: string; password: string }> = []; fs.createReadStream(‘./testData.csv’) .pipe(csv()) .on Copying from chatgpt Try adopting any of this approach or any easier solution Answer from Altruistic_Rise_8242 on reddit.com
🌐
Playwright
playwright.dev › playwright test
Playwright Test | Playwright
Note that worker process is restarted on test failures, and afterAll hook runs again in the new worker. Learn more about workers and failures. Playwright will continue running all applicable hooks even if some of them have failed. ... Declares an afterEach hook that is executed after each test.
🌐
Playwright
playwright.dev › parameterize tests
Parameterize tests | Playwright
}, ].forEach(({ name, expected }) => { test(`testing with ${name}`, async ({ page }) => { await page.goto(`https://example.com/greet?name=${name}`); await expect(page.getByRole('heading')).toHaveText(expected); }); }); If you want to have hooks for each test, you can put them inside a describe() - so they are executed for each iteration / each individual test:
Discussions

Can I loop the same test with different data?
Data-Driven Testing (DDT) in Playwright allows you to run the same test multiple times with different sets of data. This is particularly useful when you want to test various input combinations or scenarios efficiently. Here’s a comprehensive guide on implementing DDT in Playwright: Approaches for Data-Driven Testing in Playwright Using Test Parameters (test.describe.parallel) You can use test parameters to pass data directly into tests. import { test, expect } from ‘@playwright/test’; const testData = [ { username: ‘user1’, password: ‘pass1’ }, { username: ‘user2’, password: ‘pass2’ }, { username: ‘user3’, password: ‘pass3’ } ]; test.describe.parallel(‘Login Tests’, () => { for (const data of testData) { test(Login test for ${data.username}, async ({ page }) => { await page.goto(‘ https://example.com/login’ ); await page.fill(‘#username’, data.username); await page.fill(‘#password’, data.password); await page.click(‘#loginButton’); const successMessage = await page.locator(‘#successMessage’).textContent(); expect(successMessage).toContain(‘Welcome’); }); } }); 2. Using Playwright’s test.step You can iterate over a data set and wrap each iteration in a test.step for better debugging and reporting. import { test, expect } from ‘@playwright/test’; const testData = [ { username: ‘user1’, password: ‘pass1’ }, { username: ‘user2’, password: ‘pass2’ }, ]; test(‘Login Test’, async ({ page }) => { for (const data of testData) { await test.step(Testing login for ${data.username}, async () => { await page.goto(‘ https://example.com/login’ ); await page.fill(‘#username’, data.username); await page.fill(‘#password’, data.password); await page.click(‘#loginButton’); const successMessage = await page.locator(‘#successMessage’).textContent(); expect(successMessage).toContain(‘Welcome’); }); } }); 3. Using .test.each() Playwright supports test.each() for running tests with different sets of data. import { test, expect } from ‘@playwright/test’; const testData = [ [‘user1’, ‘pass1’], [‘user2’, ‘pass2’], [‘user3’, ‘pass3’], ]; test.describe(‘Data-Driven Login Tests’, () => { test.each(testData)(‘Login test for %s’, async (username, password) => { await page.goto(‘ https://example.com/login’ ); await page.fill(‘#username’, username); await page.fill(‘#password’, password); await page.click(‘#loginButton’); const successMessage = await page.locator(‘#successMessage’).textContent(); expect(successMessage).toContain(‘Welcome’); }); }); 4. Loading Test Data from an External Source You can load data from a file (e.g., JSON, CSV, or Excel) for more complex scenarios. • JSON Example: import { test, expect } from ‘@playwright/test’; import * as testData from ‘./testData.json’; test(‘Login Tests’, async ({ page }) => { for (const data of testData) { await page.goto(‘ https://example.com/login’ ); await page.fill(‘#username’, data.username); await page.fill(‘#password’, data.password); await page.click(‘#loginButton’); const successMessage = await page.locator(‘#successMessage’).textContent(); expect(successMessage).toContain(‘Welcome’); } }); testData.json: [ { “username”: “user1”, “password”: “pass1” }, { “username”: “user2”, “password”: “pass2” } ] • CSV Example: import { test, expect } from ‘@playwright/test’; import * as fs from ‘fs’; import * as csv from ‘csv-parser’; const testData: Array<{ username: string; password: string }> = []; fs.createReadStream(‘./testData.csv’) .pipe(csv()) .on Copying from chatgpt Try adopting any of this approach or any easier solution More on reddit.com
🌐 r/Playwright
7
3
January 17, 2025
Playwright before each for all spec files
I am very new to Playwright. Due to my test suites, I need to login into my application before running each test. Inside a single spec file that is easy, I can simply call test.beforeEach. My issue... More on stackoverflow.com
🌐 stackoverflow.com
node.js - Run grouped tests sequentially using Playwright - Stack Overflow
Docs for that are here: https://playwright.dev/docs/test-intro#use-test-hooks · There are similar options for teardown, so they could be something simple like going to the login page and logging in (then logging out at the end?), or something more complex like setting up data and clearing it down after the test. ... What actually want is to execute the tests in a BDD manner, with a description of each ... More on stackoverflow.com
🌐 stackoverflow.com
[Feature] Allow to set test.use options per single test
Currently we have 2 ways of using ... only for the whole file or per whole test.describe block and not for a single test. Taking simple example from the official documentation https://playwright.dev/docs/test-fixtures#fixtures-options - having fixture option defined like ... More on github.com
🌐 github.com
10
September 17, 2023
🌐
GitHub
github.com › microsoft › playwright › issues › 7036
[Feature] Add support for test.each / describe.each · Issue #7036 · microsoft/playwright
June 9, 2021 - You can already do each via running test with forEach: [ { a: 1, b: 1, expected: 2 }, { a: 1, b: 2, expected: 3 }, { a: 2, b: 1, expected: 3 }, ].forEach(({ a, b, expected }) => { test(`given ${a} and ${b} as arguments, returns ${expecte...
Author   mxschmitt
🌐
Playwright
playwright.dev › best practices
Best Practices | Playwright
However it is also ok to have a ... } from '@playwright/test'; test.beforeEach(async ({ page }) => { // Runs before each test and signs in each page....
🌐
Playwright
playwright.dev › writing tests
Writing tests | Playwright
Playwright tests are simple: they perform actions and assert the state against expectations. Playwright automatically waits for actionability checks to pass before performing each action.
🌐
Playwright
playwright.dev › command line
Command line | Playwright
# Run all tests npx playwright test # Run a single test file npx playwright test tests/todo-page.spec.ts # Run a set of test files npx playwright test tests/todo-page/ tests/landing-page/ # Run tests at a specific line npx playwright test my-spec.ts:42 # Run tests by title npx playwright test -g "add a todo item" # Run tests in headed browsers npx playwright test --headed # Run tests for a specific project npx playwright test --project=chromium # Get help npx playwright test --help
🌐
Reddit
reddit.com › r/playwright › can i loop the same test with different data?
r/Playwright on Reddit: Can I loop the same test with different data?
January 17, 2025 -

I want to create a checker inside our staging page for any inconsistent data displayed that we didn't spot before uploading the data.

Example:

List to check = an array

Loop:

Test:

Check this ( LIst to check [n] )
Return pass

I tried several of this approach before and keep getting timeouts.

Thank you in advance :)

Top answer
1 of 2
7
Data-Driven Testing (DDT) in Playwright allows you to run the same test multiple times with different sets of data. This is particularly useful when you want to test various input combinations or scenarios efficiently. Here’s a comprehensive guide on implementing DDT in Playwright: Approaches for Data-Driven Testing in Playwright Using Test Parameters (test.describe.parallel) You can use test parameters to pass data directly into tests. import { test, expect } from ‘@playwright/test’; const testData = [ { username: ‘user1’, password: ‘pass1’ }, { username: ‘user2’, password: ‘pass2’ }, { username: ‘user3’, password: ‘pass3’ } ]; test.describe.parallel(‘Login Tests’, () => { for (const data of testData) { test(Login test for ${data.username}, async ({ page }) => { await page.goto(‘ https://example.com/login’ ); await page.fill(‘#username’, data.username); await page.fill(‘#password’, data.password); await page.click(‘#loginButton’); const successMessage = await page.locator(‘#successMessage’).textContent(); expect(successMessage).toContain(‘Welcome’); }); } }); 2. Using Playwright’s test.step You can iterate over a data set and wrap each iteration in a test.step for better debugging and reporting. import { test, expect } from ‘@playwright/test’; const testData = [ { username: ‘user1’, password: ‘pass1’ }, { username: ‘user2’, password: ‘pass2’ }, ]; test(‘Login Test’, async ({ page }) => { for (const data of testData) { await test.step(Testing login for ${data.username}, async () => { await page.goto(‘ https://example.com/login’ ); await page.fill(‘#username’, data.username); await page.fill(‘#password’, data.password); await page.click(‘#loginButton’); const successMessage = await page.locator(‘#successMessage’).textContent(); expect(successMessage).toContain(‘Welcome’); }); } }); 3. Using .test.each() Playwright supports test.each() for running tests with different sets of data. import { test, expect } from ‘@playwright/test’; const testData = [ [‘user1’, ‘pass1’], [‘user2’, ‘pass2’], [‘user3’, ‘pass3’], ]; test.describe(‘Data-Driven Login Tests’, () => { test.each(testData)(‘Login test for %s’, async (username, password) => { await page.goto(‘ https://example.com/login’ ); await page.fill(‘#username’, username); await page.fill(‘#password’, password); await page.click(‘#loginButton’); const successMessage = await page.locator(‘#successMessage’).textContent(); expect(successMessage).toContain(‘Welcome’); }); }); 4. Loading Test Data from an External Source You can load data from a file (e.g., JSON, CSV, or Excel) for more complex scenarios. • JSON Example: import { test, expect } from ‘@playwright/test’; import * as testData from ‘./testData.json’; test(‘Login Tests’, async ({ page }) => { for (const data of testData) { await page.goto(‘ https://example.com/login’ ); await page.fill(‘#username’, data.username); await page.fill(‘#password’, data.password); await page.click(‘#loginButton’); const successMessage = await page.locator(‘#successMessage’).textContent(); expect(successMessage).toContain(‘Welcome’); } }); testData.json: [ { “username”: “user1”, “password”: “pass1” }, { “username”: “user2”, “password”: “pass2” } ] • CSV Example: import { test, expect } from ‘@playwright/test’; import * as fs from ‘fs’; import * as csv from ‘csv-parser’; const testData: Array<{ username: string; password: string }> = []; fs.createReadStream(‘./testData.csv’) .pipe(csv()) .on Copying from chatgpt Try adopting any of this approach or any easier solution
2 of 2
3
Have u tried keeping that data in json file Read json data in ur spec.js file Use for each loop to read the attributes inside and then calling in test
Find elsewhere
🌐
Playwright
playwright.dev › parallelism
Parallelism | Playwright
Now, each test file should import test from our fixtures file instead of @playwright/test.
Top answer
1 of 4
14

Use fixtures.

fixture.js:

const base = require('@playwright/test')
const newTest = base.test.extend({
    login: async({page}, use) => {
        await login();
        await use(page); //runs test here
        //logic after test
    }
})
exports.newTest = newTest
exports.expect = newTest.expect

Then in your tests:

const {newTest} = require('fixture.js')
newTest('mytest', async ({login}) => {
    //test logic
    login.goto(''); // using login here since I pass it as page in the fixture.
})
2 of 4
8

There two aproaches:

Use project dependency

Form Playwright 1.31 with project dependency you can set test what will be executed before any other tests and pass i.e. browser storage between tests.

See working and tested example (entering google search page and accepting policies, and then entering this page again with cookies):

playwright.config.ts:

import { PlaywrightTestConfig } from "@playwright/test";
import path from "path";

//here you save session
export const STORAGE_STATE = path.join(__dirname, 'login.json')
    
const config: PlaywrightTestConfig = {
  timeout: 10 * 1000,
  expect: {
    timeout: 3 * 1000,
  },
  testDir: './tests',
  use:{
    baseURL: 'https://google.com',
    trace: 'retain-on-failure',
    video: 'retain-on-failure',
  },
  // here we set main project and dependent one
  projects: [
    {
      name: 'login',
      grep: /@login/
    },
    {
      name: 'depend e2e',
      grep: /@e2e/,
      dependencies: ['login'],
      use: {
        storageState: STORAGE_STATE
      }
    }

  ]
}; 

export default config;

tests/example.spec.ts:

import { test} from '@playwright/test';
import { STORAGE_STATE } from '../playwright.config';
    
test('login to service @login', async({page}) => {
  await page.goto('/');
  // below example is to reject cookies from google
  await page.waitForLoadState('domcontentloaded');
  await page.keyboard.press('Tab');
  await page.keyboard.press('Enter');
  await page.getByRole('menuitem', { name: "English (United States)" }).press('Enter');
  await page.getByRole('button', { name: 'Reject all' }).click();
  
  // Save storage:
  await page.context().storageState({path: STORAGE_STATE})
})

test('logged in test @e2e', async ({page}) => {
  await page.goto('/');
  await page.waitForLoadState('domcontentloaded');
  // BOOM - you are in!
  // Screenshot shows that settings regarding cookies were saved
  await page.screenshot({ path: 'screenshot.png' })
})

Video with example: https://www.youtube.com/watch?v=PI50YAPTAs4&t=286s

In above example I'm using tags to identify tests: https://playwright.dev/docs/test-annotations#tag-tests

Use fixtures

Let test entering google search page and accepting policies

In file fixtures.ts:

import { test as base } from "@playwright/test";

export const test = base.extend({
  page: async ({ baseURL, page }, use) => {
    // We have a few cases where we need our app to know it's running in Playwright.
    // This is inspired by Cypress that auto-injects window.Cypress.
    await page.addInitScript(() => {
      (window as any).Playwright = true;
    });

   await page.goto("/");
   // below example is to reject cookies from google
   await page.waitForLoadState("domcontentloaded");
   await page.keyboard.press("Tab");
   await page.keyboard.press("Enter");
   await page
     .getByRole("menuitem", { name: "English (United States)" })
     .press("Enter");
   await page.getByRole("button", { name: "Reject all" }).click();

    use(page);
  },
});
export { expect } from "@playwright/test";

And then in test use new test object instead importing it form "@playwright/test"

import { test, expect } from "../fixture";

test("logged in test @e2e", async ({ page }) => {
  await page.goto("/");
  await page.waitForLoadState("domcontentloaded");
  // BOOM - you are in!
  // Screenshot shows that it works
  await page.screenshot({ path: "screenshot.png" });
});

Inspired by: https://github.com/microsoft/playwright/issues/9468#issuecomment-1403214587

If you need example with global request intercepting see here: https://stackoverflow.com/a/76234592/1266040

🌐
Playwright
playwright.dev › fixtures
Fixtures | Playwright
Playwright Test is based on the concept of test fixtures. Test fixtures are used to establish the environment for each test, giving the test everything it needs and nothing else. Test fixtures are isolated between tests.
🌐
npm
npmjs.com › package › @playwright › test
playwright/test
1 month ago - Log in once. Save the authentication state of the context and reuse it in all the tests. This bypasses repetitive log-in operations in each test, yet delivers full isolation of independent tests. Codegen. Generate tests by recording your actions. Save them into any language. Playwright inspector.
      » npm install @playwright/test
    
Published   Apr 01, 2026
Version   1.59.1
🌐
Playwright
playwright.dev › running and debugging tests
Running and debugging tests | Playwright
Once installed you can simply click the green triangle next to the test you want to run or run all tests from the testing sidebar. Check out our Getting Started with VS Code guide for more details. Since Playwright runs in Node.js, you can debug it with your debugger of choice, e.g. using console.log, inside your IDE, or directly in VS Code with the VS Code Extension. Playwright comes with UI Mode, where you can easily walk through each step of the test, see logs, errors, network requests, inspect the DOM snapshot, and more.
Top answer
1 of 7
18

Unfortunately, browsers/OSes have global state such as clipboard contents, so you'll get race conditions if testing such features in parallel.

If all your tests are running in parallel, you've probably enabled parallelism in playwright config:

const config: PlaywrightTestConfig = {
  fullyParallel: true,
  ...

This is good - you should leave fullyParallel: true to execute your tests faster, and then opt-out of running the tests in a single file (or single describe block) serially by adding test.describe.configure({ mode: 'serial' }); like this:

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

test.describe('Add a simple invoice test', () => {
  test.describe.configure({ mode: 'serial' });

  test('01.Login & add an invoice', async ({ page }) => {
    await page.goto("https://someUrl.com");
    await page.fill('input[id="email"]', "someEmailAddress");
    await page.fill('input[ng-model="ctrl.user.password"]', "somePassword");
    await page.click('button[id="login-btn"]');
  });

  test('02.Add an invoice', async ({ page }) => {
    await page.click('[name="invoice"]');
    await page.click('button[id="addInvoiceButton"]');
    await page.click('a[ng-click="ctrl.goToAddInvoice()"]');
    await page.fill('#invoiceTitle', Math.random().toString(36).substring(7));
    await page.fill('#exampleInputAmount', "120");
    await page.click("#invoiceCategory")
    await page.fill("#invoiceCategory > input", "Car")
    await page.keyboard.press("Enter");
    await page.click('button[id="submitInvoiceButton"]');
  });
});

Read more in the playwright docs here: https://playwright.dev/docs/next/test-parallel#parallelize-tests-in-a-single-file

2 of 7
18

The solution is very simple. It is executing as an independent test since you are passing {page} in each test, so if you want to use the same context for both the test you need to modify the test like below.

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

test.describe('Add a simple invoice test', () => {
  let page: Page;
  test.beforeAll(async ({ browser }) => {
    page = await browser.newPage();
  });

    test('01.Login & add an invoice', async () => { // do not pass page
        await page.goto("https://someUrl.com");
        await page.fill('input[id="email"]', "someEmailAddress");
        await page.fill('input[ng-model="ctrl.user.password"]', "somePassword");
        await page.click('button[id="login-btn"]');
    });

    test('02.Add an invoice', async () => { //do not pass page 
        await page.click('[name="invoice"]');
        await page.click('button[id="addInvoiceButton"]');
        await page.click('a[ng-click="ctrl.goToAddInvoice()"]');
        await page.fill('#invoiceTitle', Math.random().toString(36).substring(7));
        await page.fill('#exampleInputAmount', "120");
        await page.click("#invoiceCategory")
        await page.fill("#invoiceCategory > input", "Car")
        await page.keyboard.press("Enter");
        await page.click('button[id="submitInvoiceButton"]');
    });
});

This should work as you expected You can also refer to the Dzone Article regarding the same.

Note: Playwright doesn't recommend this approach but this answer should fulfill your need.

🌐
GitHub
github.com › microsoft › playwright
GitHub - microsoft/playwright: Playwright is a framework for Web Testing and Automation. It allows testing Chromium, Firefox and WebKit with a single API. · GitHub
3 weeks ago - Click "Record new" to open a browser — navigate and interact with your app while Playwright writes the test code for you. Pick locators. Hover over any element in the browser to see the best available locator, then click to copy it to your clipboard. Trace Viewer integration. Enable "Show Trace Viewer" in the sidebar to get a full execution trace after each test run — DOM snapshots, network requests, console logs, and screenshots at every step.
Starred by 87.6K users
Forked by 5.6K users
Languages   TypeScript 90.9% | HTML 4.0% | CSS 2.5% | C++ 0.8% | Objective-C 0.5% | JavaScript 0.5%
🌐
Playwright
playwright.dev
Fast and reliable end-to-end testing for modern web apps | Playwright
Playwright waits for elements to be actionable before performing actions. Assertions automatically retry until conditions are met. No artificial timeouts, no flaky tests. Each test gets a fresh browser context — equivalent to a brand new browser ...
🌐
Playwright
playwright.dev › ui mode
UI Mode | Playwright
All test files are displayed in the testing sidebar, allowing you to expand each file and describe block to individually run, view, watch, and debug each test. Filter tests by name, projects (set in your playwright.config file), @tag, or by the execution status of passed, failed, and skipped.
🌐
CircleCI
circleci.com › blog › understanding-playwright-test-hooks-and-ci
Understanding Playwright test hooks in the CI context (JavaScript) – A complete tutorial - CircleCI
June 23, 2025 - With Playwright hooks, we can segregate the various users using a beforeEach to log in the specific users for the test, a before all to clear any king of existing state for any user, and an after each to log out users and also delete any kind of data not required for the next user.
🌐
Playwright
playwright.dev › writing tests
Writing tests | Playwright .NET
There is no need to wait for anything ... each action. There is also no need to deal with the race conditions when performing the checks - Playwright assertions are designed in a way that they describe the expectations that need to be eventually met. That's it! These design choices allow Playwright users to forget about flaky timeouts and racy checks in their tests ...
🌐
GitHub
github.com › microsoft › playwright › issues › 27138
[Feature] Allow to set test.use options per single test · Issue #27138 · microsoft/playwright
September 17, 2023 - Currently we have 2 ways of using ... only for the whole file or per whole test.describe block and not for a single test. Taking simple example from the official documentation https://playwright.dev/docs/test-fixtures#fixtures-options - having fixture option defined like ...
Author   AdamOakman
🌐
Better Stack
betterstack.com › community › guides › testing › playwright-intro
Playwright Testing Essentials: A Beginner's Guide | Better Stack Community
This change will cause a failure, as the Playwright homepage title doesn't include Wikipedia. Run the test: ... If you click on the failing test, you'll see more details such as the execution times for each action and the action that lead to the failure.