Core Framework & Test Design – Data-Driven Testing
So far, we’ve built a robust architecture with the Page Object Model, bridged the communication gap using BDD, and eliminated flaky tests by ditching static waits. Today, we are focusing on keeping our codebase pristine, scalable, and incredibly DRY (Don’t Repeat Yourself).
Over my 15 years in Quality Engineering, one of the most common beginner mistakes I see is copy-paste programming. You need to test a login form with an admin user, a standard user, and a guest user. So, what do you do? You write the login test, copy it, paste it twice, and just change the username strings.
Suddenly, your test suite is bloated, and when the login flow changes, you have to update the exact same logic in three different places.
There is a much better way: Data-Driven Testing (DDT). Let’s look at how beautifully simple this is to implement in Playwright.
What is Data-Driven Testing?
Data-Driven Testing is a design pattern where you separate your test logic from your test data. Instead of writing multiple identical tests for different inputs, you write one test template and feed it an array or a file full of datasets.
Playwright handles this elegantly. By wrapping your test block in a standard JavaScript loop, Playwright’s runner will dynamically generate a completely independent test execution for every piece of data.
Seeing it in Action
In our Test Automation Best Practices repository, we have a feature that changes the background color. We need to test this for Turquoise, Red, and Yellow.
Here is how we do it in data-driven.spec.ts:
import { test, expect } from '../baseFixtures'
// 1. Define the dataset for the data-driven tests
const testData = [
{ name: 'Turquoise', expectedHex: '#1abc9c', expectedRgb: 'rgb(26, 188, 156)' },
{ name: 'Red', expectedHex: '#e74c3c', expectedRgb: 'rgb(231, 76, 60)' },
{ name: 'Yellow', expectedHex: '#f1c40f', expectedRgb: 'rgb(241, 196, 15)' }
]
test.describe('Data-Driven Testing', () => {
test.beforeEach(async ({ homePage }) => {
await homePage.goto()
})
// 2. Loop over the dataset to dynamically generate tests
for (const data of testData) {
// 3. Dynamically inject the data into the test name
test(`changing color to ${data.name} should reflect in UI and DOM`, async ({ homePage }) => {
// Act
await homePage.clickColorButton(data.name)
// Assert Text updates correctly
await expect(homePage.currentColorText).toContainText(data.expectedHex)
// Assert DOM styling updates correctly
await expect(homePage.header).toHaveCSS('background-color', data.expectedRgb)
})
}
})
💡 Expert Best Practices Applied Here:
- Dynamic Test Naming: Notice the backticks in the test title:
test(`changing color to ${data.name}...`). This is critical! When you run this file, Playwright won’t just report “one test passed.” It will report three distinctly named tests. If the “Red” dataset fails, it fails in isolation, and your Allure report will tell you exactly which permutation broke. - Infinite Scalability: Imagine tomorrow your product manager adds 15 new colors to the app. How many lines of code do you have to write to test them? Zero. You just add 15 JSON objects to the
testDataarray, and your coverage scales instantly. - Externalizing Data: While we defined the array directly in the file for this simple example, enterprise frameworks often move this data out into external
.jsonor.csvfiles. This means you can update test data without even touching the TypeScript files!
Summary
Data-Driven Testing is one of the easiest “quick wins” for junior and mid-level SDETs to adopt, immediately making their codebase look more senior. It eliminates boilerplate, isolates failures, and proves that you understand how to write maintainable software, not just scripts.
