Overview
Data-driven testing is a powerful technique that allows you to run the same test code with different sets of data, making your test suite more comprehensive and efficient.
In this article, let’s see how to perform data-driven testing using Playwright and Typescript. Please note that I will not focus on setting up Playwright (Typescript) from scratch. If you do not know how, please refer to its doc first at here.
Real Examples
Consider an example in real world where we have to run the validation test case for email field. We have to verify if user enter a valid email.
You can redirect to this URL to check possible test case for email field validation.
=> Corresponding to requirements, we can prepare some invalid email for validation. Note that, in real project you might need to prepare more test data to cover all user cases.
+ Email: hello
+ Email: hello@
+ Email: hello@gmail
Use Array Of Objects
The most simple way is define an array of objects in a separate file. Create a “test_data.ts” file with below content:
export const credentials = [ { "email": "hello", "error_msg": "An email address must contain a single @.", }, { "email": "hello@", "error_msg": "The domain portion of the email address is invalid (the portion after the @: )", }, { "email": "hello@gmail", "error_msg": "The domain portion of the email address is invalid (the portion after the @: gmail)", } ]
And this is our test.spec.ts (file that contains test script in Playwright – name it as you wish):
import { test, expect } from '@playwright/test'; import { credentials } from '../test_data'; test.beforeEach(async ({ page }) => { await page.setViewportSize({ width: 1920, height: 1080 }); await page.goto('https://login.mailchimp.com/signup/'); }); for (const credential of credentials) { test(`Validate email field with email = ${credential.email}`, async ({ page }) => { // Clear input before entering and input value await page.locator('input#email').clear(); await page.fill('input#email', credential.email); // Click to Sign Up button await page.click('button#create-account-enabled'); // assert validation error message for email field await expect(page.locator('input#email + span.invalid-error')).toHaveText(credential.error_msg); }); }
Use CSV File
Ideally, you should isolate your test data from test script for easy management. You can consider using csv files because they have quite good performance.
To read csv content, firstly install csv-parser using npm (or yarn).
npm install csv-parser --save
Create a “test_data.csv” file as below:
EMAIL,ERROR_MSG hello,An email address must contain a single @. hello@,The domain portion of the email address is invalid (the portion after the @: ) hello@gmail,The domain portion of the email address is invalid (the portion after the @: gmail)
Create a function to read csv content:
async function readCSVContent(filePath: string) { fs.createReadStream(filePath) .pipe(csv()) .on('data', (data: any) => results.push(data)) .on('end', () => { console.log('Stream CSV content successfully') console.log(results); }); }
After that you can get those data for your test script. Full test.spec.file as below:
import { test, expect } from '@playwright/test'; const csv = require('csv-parser'); const fs = require('fs'); const results: any[] = []; test.beforeEach(async ({ page }) => { await readCSVContent('./test_data.csv'); await page.setViewportSize({ width: 1920, height: 1080 }); await page.goto('https://login.mailchimp.com/signup/'); }); test(`Validate email field`, async ({ page }) => { for (const result of results) { // Clear input before entering and input value await page.locator('input#email').clear(); await page.fill('input#email', result.EMAIL); // Click to Sign Up button await page.click('button#create-account-enabled'); // assert validation error message for email field await expect(page.locator('input#email + span.invalid-error')).toHaveText(result.ERROR_MSG); } }); async function readCSVContent(filePath: string) { fs.createReadStream(filePath) .pipe(csv()) .on('data', (data: any) => results.push(data)) .on('end', () => { console.log('Stream CSV content successfully') console.log(results); }); }
=> The drawback of this method is you have to use for loop inside test method. This made test case run repeatedly up to three times instead of three separate test case of first method (Refer to below image)
Use Excel File
Create an Excel file named “test_data.xlsx” as below:
Next, you should install “xlsx” lib using npm or yarn.
To read xlsx file and parse its content to array of objects, you firstly need to define a type. If you do not, Typescript compiler will throw an error about you are using an “unknown” type.
type RowData = { EMAIL: string, ERROR_MSG: string }
=> Note that it has two property EMAIL and ERROR_MSG (relevant to name of column in xlsx file)
Use below code to read .xlsx file and parse it to array of objects:
const workbook = XLSX.readFile('./test_data.xlsx'); const worksheet = workbook.Sheets[workbook.SheetNames[0]]; const rows: RowData[] = XLSX.utils.sheet_to_json(worksheet);
=> When reading an .xlsx file using the “xlsx” library, the data returned by the sheet_to_json() method is typically an array of objects, where each object represents a row in the worksheet.
Finally, you can use it as below:
import { test, expect } from '@playwright/test'; import * as XLSX from 'xlsx'; type RowData = { EMAIL: string, ERROR_MSG: string } const workbook = XLSX.readFile('./test_data.xlsx'); const worksheet = workbook.Sheets[workbook.SheetNames[0]]; const rows: RowData[] = XLSX.utils.sheet_to_json(worksheet); test.beforeEach(async ({ page }) => { await page.setViewportSize({ width: 1920, height: 1080 }); await page.goto('https://login.mailchimp.com/signup/'); }); for (const row of rows) { test(`Validate email field with email = ${row.EMAIL}`, async ({ page }) => { // Clear input before entering and input value await page.locator('input#email').clear(); await page.fill('input#email', row.EMAIL); // Click to Sign Up button await page.click('button#create-account-enabled'); // assert validation error message for email field await expect(page.locator('input#email + span.invalid-error')).toHaveText(row.ERROR_MSG); }); }
Conclusion
In Selenium Java, we have @DataProvider annotation (for TestNG) or @TestCaseSource (for Nunit) which supports data driven testing. Although Playwright recently does not have nature features for this, we can still manage to have some nice work-around methods. Thanks for reading!