Automation Testing With Playwright Using JavaScript

Sep 5, 2024

Automation Testing With Playwright Using JavaScript

Learn how to set up automation testing in a Next.js application using Playwright. This comprehensive guide covers everything from installation to writing tests, API testing, and integrating with CI pipelines, ensuring robust software quality and performance.

Subject Matter Expert

Parth Nitin Sharma
Parth Nitin SharmaSoftware Engineer in Testing II

Automation testing is a critical part of software development that helps ensure the quality and reliability of your application. Playwright is a robust end-to-end testing framework that enables you to automate browser actions and interactions. This comprehensive guide will walk you through performing automation testing using Playwright in a Next.js application.

Playwright is a JavaScript library that provides a high-level API to automate browsers like Chromium, Firefox, and WebKit. It allows you to write browser automation scripts that simulate user interactions, navigation, form submissions, and more. Playwright's unique architecture provides a fast and reliable way to test web applications.


Setting Up a Javascript Project

Before we start writing tests, let's set up a Next.js project:

Create a new Javascript project:

mkdir my-js-project
cd my-js-project
npm init -y

This will create a new directory called my-js-project, navigate into it, and initialize a new Node.js project with default settings.

The -y flag in the npm init -y command is a shorthand for "yes". It automatically answers "yes" to all the prompts that npm init would normally ask you, thereby accepting all the default values for the project setup.

Install the Playwright dependencies:

npm install playwright


Writing Automation Tests with Playwright

Let's create a simple automation test for a javascript application. We want to test the navigation from the home page to a blog post page.

const { chromium } = require('playwright');

describe('Next.js Blog Automation Test', () => {
  let browser;
  let page;

  beforeAll(async () => {
    browser = await chromium.launch();
  });

  beforeEach(async () => {
    page = await browser.newPage();
  });

  afterAll(async () => {
    await browser.close();
  });

  it('should navigate to a blog post', async () => {
    await page.goto('http://localhost:3000');
    await page.click('a[href="/blog"]');
    await page.waitForSelector('h1');
    const pageTitle = await page.textContent('h1');
    expect(pageTitle).toContain('Blog Post Title');
  });
});

Let us see the components of the above code in a bit more detail.

When using testing frameworks like Jest, Mocha, Jasmine, or Playwright, the describe block is a fundamental organizational structure that helps group related test cases together. It provides a way to define a logical container for a set of tests with a common context or functionality. The describe block is primarily used to improve your test suite's organization, readability, and maintainability.

The beforeEach function is a part of testing frameworks like Jest and is used to set up a consistent and controlled environment before running each individual test case within a describe block. This is particularly helpful in maintaining a clean state and avoiding interference between test cases.

In testing, it's important to create a controlled environment for each test case to ensure that they run independently and reliably. Sometimes, tests require the same setup steps before execution. Instead of repeating the setup code in every test case, you can use beforeEach to define those common setup actions.

In automated testing, especially when using testing frameworks like Jest, you often encounter lifecycle functions that help you set up and tear down certain conditions before and after tests are run. One important lifecycle function is beforeAll.

The beforeAll function executes a block of code before all the tests within a test suite (also known as a test file or describe block) are run. This block of code typically sets up any common resources or configurations required for all the tests in that suite.

The afterAll function is a part of testing frameworks like Jest, and it is used to perform cleanup tasks or teardown operations after all the test cases within a describe block have been executed. It's the counterpart of the beforeAll function, which is used for setup operations before the test cases.

In testing, there might be scenarios where you need to perform cleanup tasks or reset the environment after all the test cases within a particular test suite have run. afterAll allows you to define these cleanup actions.


Enhancing API Testing with Playwright

Did you know that Playwright, commonly recognized for its powerful browser automation capabilities, can also be utilized for API testing? This versatile tool excels in web application testing and offers robust features to automate and validate API interactions. By leveraging Playwright for API testing, you can streamline your testing processes and ensure your APIs function as expected.


Writing Your First API Test

Let's start with a basic example. We'll test a simple REST API that returns user information.

1. Start with creating a new file called apiTest.spec.js.

2. Import Playwright:

const { test, expect } = require('@playwright/test');

3. Write a Basic Test:
Here's a test that fetches user data from a hypothetical API endpoint.

test('GET user data', async ({ request }) => {
  const response = await request.get('https://jsonplaceholder.typicode.com/users/1');
  expect(response.status()).toBe(200);
  const userData = await response.json();
  expect(userData.id).toBe(1);
  expect(userData.name).toBe('Leanne Graham');
});


Breaking Down the Test

  • test: Defines a test case.
  • request.get: Sends a GET request to the specified URL.
  • expect: assertions to check if the response meets our expectations. 
  • response.status(): Checks the HTTP status code of the response.
  • response.json(): Parses the response body as JSON.


Handling Different HTTP Methods

Playwright supports various HTTP methods. Here are examples of POST, PUT, and DELETE requests.

POST Request:


test('POST create new user', async ({ request }) => {
  const response = await request.post('https://jsonplaceholder.typicode.com/users', {
    data: {
      name: 'John Doe',
      username: 'johndoe',
      email: 'johndoe@example.com'
    }
  });
  expect(response.status()).toBe(201);
  const newUser = await response.json();
  expect(newUser.name).toBe('John Doe');
});

PUT Request:

test('PUT update user data', async ({ request }) => {
  const response = await request.put('https://jsonplaceholder.typicode.com/users/1', {
    data: {
      name: 'Jane Doe'
    }
  });
  expect(response.status()).toBe(200);
  const updatedUser = await response.json();
  expect(updatedUser.name).toBe('Jane Doe');
});

DELETE Request:

test('DELETE user', async ({ request }) => {
  const response = await request.delete('https://jsonplaceholder.typicode.com/users/1');
  expect(response.status()).toBe(200);
});


Handling Authentication

Many APIs require authentication. Here’s how to handle it in Playwright:

test('GET user data with authentication', async ({ request }) => {
  const response = await request.get('https://api.example.com/protected-resource', {
    headers: {
      'Authorization': `Bearer YOUR_ACCESS_TOKEN`
    }
  });
  expect(response.status()).toBe(200);
});


Using Fixtures for Setup and Teardown

Fixtures help set up and tear down resources required for your tests. Here's an example:

test.describe('API tests with setup and teardown', () => {
  let userId;

  test.beforeAll(async ({ request }) => {
    const response = await request.post('https://jsonplaceholder.typicode.com/users', {
      data: { name: 'Test User' }
    });
    const user = await response.json();
    userId = user.id;
  });

  test.afterAll(async ({ request }) => {
    await request.delete(`https://jsonplaceholder.typicode.com/users/${userId}`);
  });

  test('GET created user data', async ({ request }) => {
    const response = await request.get(`https://jsonplaceholder.typicode.com/users/${userId}`);
    expect(response.status()).toBe(200);
  });
});


Running Your Tests

You can run your Playwright tests using the Playwright Test Runner. Add a script to your package.json:

"scripts": {
  "test": "playwright test"
}

Then, run your tests with:

npm test


Generating Test Depictions

Playwright Test generates reports by default. You can view the test results in the console, but you might want a more detailed report. Playwright provides a built-in HTML report. To enable it, add a playwright.config.js file in your project root:

module.exports = {
  reporter: [
    ['list'],
    ['html', { outputFolder: 'playwright-report', open: 'never' }]
  ],
};

Run your tests again.

After completing the tests, you'll find the HTML report in the playwright-report directory. To open it, run:

npm playwright show-report

This command opens the HTML report in your default browser, providing a detailed view of your test results.

Best Practices for Automation Testing

Here are some best practices to follow when writing automation tests using Playwright in a Next.js application:

  • Isolation: Each test should be independent and not depend on the state of other tests.
  • Use Data Attributes: Use data attributes in your components to make it easier to select elements in your tests.
  • Mock API Calls: Use mocking or stubbing to avoid making actual API requests during tests.
  • Assertions: Use meaningful assertions to verify the expected behavior of your application.
  • Page Objects: Create page objects or helper functions to encapsulate the interactions with specific parts of your application.


Continuous Integration with Playwright

Integrate your Playwright tests into your continuous integration (CI) pipeline. This ensures that your tests are automatically executed whenever you push code changes.

  1. Choose a CI platform like GitHub Actions, Jenkins, or Travis CI.
  2. Configure the CI pipeline to run the Playwright tests using Jest.


Playwright vs. Selenium: A Comparison

Feature

Playwright

Selenium

Language Support

Javascript/TypeScript, Python, C#, Java

Java, C#, Python, Ruby, JavaScript, Kotlin

Browser Support

Chromium (Chrome, Edge), WebKit (Safari), Firefox

Chrome, Firefox, Safari, Edge, IE, Opera

Installation and Setup

Single package installation (includes browser binaries)

Separate installations for WebDriver binaries

Speed and Performance

Generally faster (uses DevTools Protocol)

Slower (uses WebDriver protocol)

Parallel Testing

Built-in support for parallel testing

Requires additional configuration/tools (e.g., Selenium Grid)

APIs and Features

Modern API, auto-wait, network interception, mobile emulation

Mature API, similar features but more configuration

Community and Support

Growing community, backed by Microsoft

Established community, extensive documentation

Cross-Browser Testing

Consistent results with the same API for all browsers

It is possible but may have inconsistencies

Test Reporting

Integrated reporting tools

Requires additional tools/libraries (e.g., TestNG, JUnit)

Script Debugging

Built-in tools for debugging (e.g., Playwright Inspector)

Debugging through language-specific IDE/tools


Conclusion

Automation testing using Playwright in a Next.js application can help you catch bugs and ensure the quality of your software. Following the steps in this guide, you've learned how to set up a Next.js project, write automation tests, and integrate them into your CI pipeline. Remember to continuously update and expand your test suite as your application evolves.

Subscribe to Our Newsletter

More from the engineering frontline.

Dive deep into our research and insights on design, development, and the impact of various trends to businesses.
Insight
What a PHP-to-NestJS Banking Migration Taught Us About Architecture, Security, and Trust
Sep 8, 2026

What a PHP-to-NestJS Banking Migration Taught Us About Architecture, Security, and Trust

This blog explores the architecture, security, performance, and documentation lessons from migrating a legacy PHP/Laravel banking platform to NestJS.

Insight
Building Production-Grade Video Thumbnail Scrubbing in the Browser: HLS, Frame Extraction, Caching, and Performance Trade-offs
Sep 7, 2026

Building Production-Grade Video Thumbnail Scrubbing in the Browser: HLS, Frame Extraction, Caching, and Performance Trade-offs

This blog explains how to build responsive video thumbnail scrubbing in the browser for local files and HLS streams, covering frame extraction, caching, and performance trade-offs.

Insight
The Agent Can See Your App. How Often Can It Look?
Sep 4, 2026

The Agent Can See Your App. How Often Can It Look?

AI coding agents can now interact with mobile apps, but their effectiveness depends on iteration speed. This blog explores how React Native architecture influences feedback loops and AI-driven developer productivity.

Insight
Building Interactive Cards from Design JSON Without Killing Your Feed: Overlays, Video, Mute/Unmute, and Lag-Free Lists
Sep 1, 2026

Building Interactive Cards from Design JSON Without Killing Your Feed: Overlays, Video, Mute/Unmute, and Lag-Free Lists

Learn how to turn design JSON into interactive, video-enabled cards using overlays, smart media controls, caching, and virtualization without slowing down high-cardinality feeds.

Insight
The Bug That Doesn't Show Up in Code Review: Why Your Flutter Web App Reloads on Safari
Aug 19, 2026

The Bug That Doesn't Show Up in Code Review: Why Your Flutter Web App Reloads on Safari

A real-world look at how oversized images can trigger Safari reloads and iOS crashes in Flutter apps and how smarter image decoding prevents them.

Insight
From Prompting to Process: What Changed When Flutter Shipped Agent Skills
Aug 19, 2026

From Prompting to Process: What Changed When Flutter Shipped Agent Skills

This blog explores how Flutter Agent Skills improve AI-assisted development by combining official framework workflows with project-specific guidance for more consistent development.

Insight
Why Everything Your AI Builds Looks the Same
Aug 19, 2026

Why Everything Your AI Builds Looks the Same

This blog explores why AI-generated interfaces often look alike and explains how design systems, product context, and reusable engineering practices help teams build distinctive, scalable

The Right Conversation Can

Save You Six Months.

Book a call