Max Schmitt

March 15 2024

Next.js: How to Mock Server-Side Requests Using Playwright

When you're writing end-to-end tests for your Next.js application, there might come a time when you want to mock HTTP requests that your app is making.

Maybe your app is interacting with a third-party API and its output isn't predictable. Or maybe you want to test how your app behaves when the server returns an error.

Historically, this has been a bit tricky for Next.js apps where requests can be made from the browser or the server.

My buddy Max (yes, another Max Schmitt – don't get confused!) recently published an experimental package for Playwright that makes it super-easy to mock server-side requests.

Here's an example of how it works:

An Example Page

Let's say we have a simple Next.js project (using the app router) to display my last 3 repositories on GitHub:

app/page.tsx

export default async function Home() {
const repos = await fetch('https://api.github.com/users/maximilianschmitt/repos?sort=updated', {
// It's important to use cache: 'no-cache', otherwise we can't
// mock the request. See:
// https://github.com/playwright-community/ssr/issues/7
cache: 'no-cache',
})
.then((res) => res.json())
.then(repos => repos.slice(0, 3))
return (
<main>
<h1>Latest repositories</h1>
<ul>
{repos.map((repo: any) => {
return <li key={repo.id}>{repo.full_name}</li>
})}
</ul>
</main>
)
}

In the browser, the page would look something like this:

A website showing 3 GitHub repositories

An Example Playwright Test

If we wanted to test this page, we could write the following test with Playwright:

app/page.test.ts

import { test, expect } from 'playwright'
test('displays latest repos from GitHub', async ({ page }) => {
// Visit the homepage
await page.goto('/')
// Check that the correct title is displayed
await expect(page.locator('h1')).toContainText('Latest repositories')
// Check that we have exactly 3 repos
await expect(page.locator('li')).toHaveCount(3)
await expect(page.locator('li').nth(0)).toContainText('maximilianschmitt/nextjs-playwright-ssr')
await expect(page.locator('li').nth(1)).toContainText('maximilianschmitt/nextjs-contact-form-app')
await expect(page.locator('li').nth(2)).toContainText('maximilianschmitt/nextjs-contact-form-pages')
})

Now, what happens if I create new repositories? I would need to update the tests!

To avoid this, let's mock the server-side request and return a fixed list of repositories.

Mocking Next.js' Server-Side Requests

We'll use the experimental playwright-ssr package to mock the server-side requests.

Let's update our test file:

app/page.test.ts

// Import 'test' and 'expect' from 'playwright-ssr'
// instead of 'playwright' 👇
import { test, expect } from 'playwright-ssr'
// Get a reference to the webServer fixture 👇
test('displays latest repos from GitHub', async ({ page, webServer }) => {
// Intercept the request to https://api.github.com/users/maximilianschmitt/repos?sort=updated
// and reply with our own list of repos
await webServer.route('https://api.github.com/users/*/repos*', async (route) => {
await route.fulfill({
status: 200,
json: [
{ id: 1, full_name: 'maximilianschmitt/hello-world' },
{ id: 2, full_name: 'maximilianschmitt/maxschmitt.me' },
{ id: 3, full_name: 'maximilianschmitt/cakedesk' },
],
})
})
// Visit the homepage
await page.goto('/')
// ...
})

Isn't this super easy? I really love how easy it is to describe which request we want to intercept and what response we want to send back.

How to Setup playright-ssr

How do we set this up? It's pretty easy!

First, install playwright-ssr:

npm install playwright-ssr

Then, adjust the playwright.config.ts file like so:

playwright.config.ts

import { defineConfig, devices } from '@playwright/test'
import { WorkerConfigOptions } from 'playwright-ssr'
export default defineConfig<WorkerConfigOptions>({
// ...
projects: [
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
webServer: {
command: 'npm',
args: ['run', 'dev'],
url: 'http://localhost:3000',
cwd: __dirname,
},
},
},
],
})

And that's it!

Check out the playwright-ssr project on GitHub. You can find the full example from this post on GitHub as well.