Rayrun

Testing Next.js Apps Using Playwright

Supercharge your Next.js app testing with Playwright – a tool for automating Chromium, Firefox, and WebKit browsers. In this guide, I will walk you through setting up and running your first Playwright E2E test for a Next.js application.
  1. Quickstart
    1. Manual Setup
      1. Crafting Your First Playwright E2E Test
        1. Running Playwright Tests
          1. Use webServer to Start the Development Server
            1. Playwright on Continuous Integration (CI)
              1. Using the Experimental test mode for Playwright
                1. Mocking API responses
                  1. App Router
                    1. Error: No test info
                      1. Conclusion

                        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

                        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

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

                        {
                          "scripts": {
                            "dev": "next dev",
                            "build": "next build",
                            "start": "next start",
                            "test:e2e": "playwright test"
                          }
                        }

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

                        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');
                        })

                        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

                        Then in another terminal, run your Playwright tests:

                        npm run test:e2e

                        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;

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

                        npm run test:e2e

                        Playwright on Continuous Integration (CI)

                        Playwright official documentation has a section dedicated to CI.

                        If you are using GitHub Actions, the basic setup looks as follows:

                        name: Playwright Tests
                        on:
                          push:
                            branches: [ main, master ]
                          pull_request:
                            branches: [ main, master ]
                        jobs:
                          test:
                            timeout-minutes: 60
                            runs-on: ubuntu-latest
                            steps:
                            - uses: actions/checkout@v3
                            - uses: actions/setup-node@v3
                              with:
                                node-version: 18
                            - name: Install dependencies
                              run: npm ci
                            - name: Install Playwright Browsers
                              run: npx playwright install --with-deps
                            - name: Run Playwright tests
                              run: npx playwright test
                            - uses: actions/upload-artifact@v3
                              if: always()
                              with:
                                name: playwright-report
                                path: playwright-report/
                                retention-days: 30

                        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' });
                        }

                        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,
                            },
                          }
                        }

                        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');
                        });

                        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

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

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

                        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');
                        });

                        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' })
                        }

                        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>
                        }

                        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');
                        });

                        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

                        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!

                        Thank you!
                        Was this helpful?

                        Now check your email!

                        To complete the signup, click the confirmation link in your inbox.

                        Subscribe to newsletter

                        You will receive a weekly email with the latest posts and QA jobs.

                        TwitterGitHubLinkedIn
                        AboutQuestionsDiscord ForumBrowser ExtensionTagsQA Jobs

                        Rayrun is a community for QA engineers. I am constantly looking for new ways to add value to people learning Playwright and other browser automation frameworks. If you have feedback, email luc@ray.run.