Data Driven Testing with Playwright (Typescript)

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.

playwright_driven_testing_tomatoqa

=> 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)

playwright_driven_testing_tomatoqa

Use Excel File

Create an Excel file named “test_data.xlsx” as below:

playwright_driven_testing_tomatoqa

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!

Ask me anything