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.

Author

Sankalp Nihal PandeySoftware Engineer

Subject Matter Expert

Parth Nitin SharmaParth Nitin SharmaSoftware Engineer in Testing II
Automation Testing With Playwright Using JavaScript

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

RELATED ARTICLES

More from the engineering frontline.

Dive deep into our research and insights on design, development, and the impact of various trends to businesses.
The Bug That Doesn't Show Up in Code Review: Why Your Flutter Web App Reloads on Safari
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.
From Prompting to Process: What Changed When Flutter Shipped Agent Skills
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.
Why Everything Your AI Builds Looks the Same
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
How We Built the Missing Bridge from Code to Figma
Technology

Jul 10, 2026

How We Built the Missing Bridge from Code to Figma
This blog explores how AI-generated React apps get turned into fully editable, designer-ready Figma files by reading React Fiber instead of the DOM.
Building a Resilient Hybrid-Cloud Network with WireGuard HA, Route-Based Failover, and Deep Observability
Technology

Jun 27, 2026

Building a Resilient Hybrid-Cloud Network with WireGuard HA, Route-Based Failover, and Deep Observability
A practical breakdown of building resilient AWS-to-on-premises connectivity with WireGuard HA, active-standby failover, and deep packet-forwarding observability.
We Built a 114-Second AWS-to-Azure Failover. Here’s What We Learned
Technology

Jun 19, 2026

We Built a 114-Second AWS-to-Azure Failover. Here’s What We Learned
A practical guide to building a 114-second multi-cloud disaster recovery failover between AWS and Azure — what we built, what broke, and what we learned.
Cloud-Native and Cloud-Agnostic Are Not Ideologies; They Are Business-Stage Decisions
Technology

Jun 12, 2026

Cloud-Native and Cloud-Agnostic Are Not Ideologies; They Are Business-Stage Decisions
This blog explains how organizations can balance speed, scalability, and operational flexibility as they grow from startup to enterprise scale.

The Right Conversation Can Save You Six Months.

Whether you’re navigating AI adoption, modernizing legacy systems, or scaling a product - we start by listening. No pitch deck. No template. A real conversation.