Some tests need to run with multiple sets of data. Login with 5 different user types. Search with 10 different keywords. Checkout with valid and invalid addresses. Writing separate tests for each is tedious and violates DRY. Data-driven testing lets you write the test logic once and run it with different data.
import { test, expect } from '../../fixtures/test-fixtures';
const loginScenarios = [
{
name: 'valid credentials',
email: 'user@example.com',
password: 'ValidPass123',
shouldSucceed: true,
errorMessage: '',
},
{
name: 'wrong password',
email: 'user@example.com',
password: 'WrongPass',
shouldSucceed: false,
errorMessage: 'Invalid credentials',
},
{
name: 'non-existent user',
email: 'nobody@example.com',
password: 'SomePass123',
shouldSucceed: false,
errorMessage: 'Invalid credentials',
},
{
name: 'empty email',
email: '',
password: 'SomePass123',
shouldSucceed: false,
errorMessage: 'Email is required',
},
{
name: 'empty password',
email: 'user@example.com',
password: '',
shouldSucceed: false,
errorMessage: 'Password is required',
},
];
for (const scenario of loginScenarios) {
test(`login: ${scenario.name}`, async ({ loginPage }) => {
await loginPage.goto();
await loginPage.login(scenario.email, scenario.password);
if (scenario.shouldSucceed) {
await loginPage.expectLoggedIn();
} else {
await loginPage.expectError(scenario.errorMessage);
}
});
}export const searchTerms = [
{ query: 'laptop', minResults: 1, description: 'common product' },
{ query: 'phone', minResults: 1, description: 'common product' },
{ query: 'xyznonexistent', minResults: 0, description: 'no results expected' },
{ query: 'LAPTOP', minResults: 1, description: 'case insensitive search' },
{ query: ' laptop ', minResults: 1, description: 'trimmed whitespace' },
] as const;import { test, expect } from '../../fixtures/test-fixtures';
import { searchTerms } from '../../test-data/search-terms';
test.describe('Product Search (data-driven)', () => {
for (const { query, minResults, description } of searchTerms) {
test(`search for "${query}" (${description})`, async ({
authenticatedPage,
productListPage,
}) => {
await productListPage.goto();
await productListPage.search(query);
const count = await productListPage.getProductCount();
if (minResults === 0) {
await productListPage.expectNoResults();
} else {
expect(count).toBeGreaterThanOrEqual(minResults);
}
});
}
});Data-driven tests are great for validation scenarios (5 types of invalid input), search variations (different keywords), and multi-user tests (admin, regular user, read-only user). Avoid data-driving complex flow tests -- they become hard to debug when one dataset fails.
Q: How do you implement data-driven testing in Playwright?
A: Playwright does not have a built-in @DataProvider like TestNG, but JavaScript loops work perfectly. Define test data as an array of objects, then loop with for...of to generate individual tests. Each iteration creates a separate test with a descriptive name. For complex data, import from external TypeScript files. The key: each dataset should be independent and test one variation. Keep the data arrays small (5-10 items) and meaningful. Avoid data-driving complex flow tests -- they are hard to debug when one specific dataset fails.
Key Point: Use for...of loops over data arrays to generate parameterized tests. Centralize test data in external files. Best for validation scenarios, search variations, and multi-user tests.
Key Point: Data-driven tests use for...of loops over test data arrays -- great for validation, search, and multi-user scenarios