DEV Community

Cover image for Efficient E2E Testing for Next.js: A Playwright Tutorial
Luc Gagan
Luc Gagan

Posted on • Originally published at ray.run

Efficient E2E Testing for Next.js: A Playwright Tutorial

Supercharge your Next.js app testing with Playwright – a tool for automating Chromium, Firefox, and WebKit browsers. Whether you're gearing up for End-to-End (E2E) or Integration tests, Playwright offers a seamless experience across all platforms. In this guide, I will walk you through setting up and running your first Playwright E2E test for a Next.js application.

Quickstart

If you're eager to dive right in, the quickest way to kick things off is by leveraging the create-next-app with the with-playwright example:

npx create-next-app@latest --example with-playwright with-playwright-app
Enter fullscreen mode Exit fullscreen mode

Then run npm run test:e2e to run the tests. Continue reading to learn how to setup Playwright in an existing Next.js project.

Manual Setup

For those of you with an existing NPM project and would prefer a manual setup, no worries, you're covered! Begin by installing the @playwright/test package:

npm install --save-dev @playwright/test
Enter fullscreen mode Exit fullscreen mode

Now, update your package.json scripts to incorporate Playwright:

{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "test:e2e": "playwright test"
  }
}
Enter fullscreen mode Exit fullscreen mode

Crafting Your First Playwright E2E Test

Imagine you have two Next.js pages set up as shown:

// pages/index.js
import Link from 'next/link'

export default function Home() {
  return (
    <nav>
      <Link href="/about">About</Link>
    </nav>
  )
}

// pages/about.js
export default function About() {
  return (
    <div>
      <h1>About Page</h1>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

It's time to ensure your navigation operates flawlessly. Here's how to craft a test for it:

// e2e/example.spec.ts
import { test, expect } from '@playwright/test'

test('navigates to the about page', async ({ page }) => {
  await page.goto('/')
  await page.getByRole('link', { name: /About/ }).click();
  await expect(page).toHaveURL('/about')
  await expect(page.getByRole('heading', { level: 1 })).toContainText('About Page');
})
Enter fullscreen mode Exit fullscreen mode

Note: Use page.goto('/') and have "baseURL": "http://ray.run" set in the playwright.config.ts file for concise code.

Running Playwright Tests

Remember, Playwright tests the actual Next.js app, so the server should be up and running. It is also a good idea to test against production build to mimic real-world behavior.

Start your Next.js server:

npm run build
npm run start
Enter fullscreen mode Exit fullscreen mode

Then in another terminal, run your Playwright tests:

npm run test:e2e
Enter fullscreen mode Exit fullscreen mode

Use webServer to Start the Development Server

With Playwright's webServer feature, you can let it handle starting the development server and ensuring it's ready for action.

Just add this configration to your playwright.config.ts file:

import { type PlaywrightTestConfig } from '@playwright/test';

const config: PlaywrightTestConfig = {
  webServer: {
    command: 'npm run dev',
    port: 3000,
    reuseExistingServer: !process.env.CI,
  },
};

export default config;
Enter fullscreen mode Exit fullscreen mode

Now you can run your tests without having to start the development server manually:

npm run test:e2e
Enter fullscreen mode Exit fullscreen mode

Playwright on Continuous Integration (CI)

When running on CI, Playwright defaults to headless mode. To have all dependencies in place, execute:

npx playwright install-deps
Enter fullscreen mode Exit fullscreen mode

Using the Experimental test mode for Playwright

There is not a lot of documentation on this yet, but Vercel team is working on an experimental test mode for Playwright. This mode will allow you to intercept requests and mock responses. This is useful for testing API routes and other server-side code.

Suppose you have an API route at /api/hello:

// pages/api/hello.ts
import type { NextApiRequest, NextApiResponse } from 'next'

type ResponseData = {
  name: string
}

export default function handler(
  req: NextApiRequest,
  res: NextApiResponse<ResponseData>
) {
  res.status(200).json({ name: 'Foo' });
}
Enter fullscreen mode Exit fullscreen mode

This route is called using getServerSideProps:

// pages/index.tsx
import { GetServerSideProps } from 'next'

export default function Home({ name }) {
  return <div data-testid="message">Hello {name}</div>
}

export const getServerSideProps: GetServerSideProps = async () => {
  const res = await fetch('http://localhost:3000/api/hello')
  const data = await res.json();

  return {
    props: {
      name: data.name,
    },
  }
}
Enter fullscreen mode Exit fullscreen mode

Now you want to write a test that verifies that the page renders the name returned by the API.

import { test, expect } from '@playwright/test';

test('prints name retrieved from the API', async ({ page, msw }) => {
  await page.goto('/');

  await expect(page.getByTestId('message')).toHaveText('Hello Foo');
});
Enter fullscreen mode Exit fullscreen mode

The latter asserts that the page renders the name returned by the API. However, using the experimental test proxy, you can intercept the request to /api/hello and mock the response.

Mocking API responses

First, you need to install msw to mock responses:

npm install -D msw
Enter fullscreen mode Exit fullscreen mode

Then you need to change your import to next/experimental/testmode/playwright/msw:

import { test, expect } from 'next/experimental/testmode/playwright/msw'
Enter fullscreen mode Exit fullscreen mode

Now you can mock the response:

import { test, expect, rest } from 'next/experimental/testmode/playwright/msw';

test('prints name retrieved from the API', async ({ page, msw }) => {
  msw.use(
    rest.get('http://localhost:3000/api/hello', (req, res, ctx) => {
      return res.once(
        ctx.status(200),
        ctx.json({
          name: 'Bar',
        })
      )
    })
  );

  await page.goto('/');

  await expect(page.getByTestId('message')).toHaveText('Hello Bar');
});
Enter fullscreen mode Exit fullscreen mode

App Router

You can also use the experimental test mode to test the Next.js app/ router.

Define a route:

// app/api/hello/route.ts
export async function GET() { 
  return Response.json({ name: 'Foo' })
}
Enter fullscreen mode Exit fullscreen mode

Fetch the data in your page:

// app/page.tsx
const getData = async () => {
  const res = await fetch('http://localhost:3000/api/hello')
  const data = await res.json();

  return {
    name: data.name,
  }
};

export default async () => {
  const { name } = await getData();

  return <div data-testid="message">Hello {name}</div>
}
Enter fullscreen mode Exit fullscreen mode

Now you can test the page:

import { test, expect, rest } from 'next/experimental/testmode/playwright/msw';

test('prints name retrieved from the API', async ({ page, msw }) => {
  msw.use(
    rest.get('http://localhost:3000/api/hello', (req, res, ctx) => {
      return res.once(
        ctx.status(200),
        ctx.json({
          name: 'Bar',
        })
      )
    })
  );

  await page.goto('/');

  await expect(page.getByTestId('message')).toHaveText('Hello Bar');
});
Enter fullscreen mode Exit fullscreen mode

Error: No test info

It appears that when you are running tests in the experimental test mode, you may get the following error:

Error: No test info
Enter fullscreen mode Exit fullscreen mode

This happens when you try to access the web server directly, outside of the test. As far as I can tell, there is no way to access the Next.js web server directly when running in the experimental test mode. You can only access it by running a Playwright test.

Conclusion

In closing, integrating Playwright into your Next.js workflow ensures a robust, tested, and reliable application. If this is your first time using Playwright, you should familiarize with Trace viewer next as it will help you debug your tests. Now, go ahead and craft tests that leave no stone unturned. Happy testing!

Top comments (0)