Skip to main content

@krakentech/blueprint‑playwright

@krakentech/blueprint-playwright is a Playwright test suite for Next.js that lets you mock client‑side, server‑side and Edge Runtime requests in the same test, using the declarative power of MSW.

TL;DR

  • Spin up a headless Next.js server per worker.
  • Intercept network traffic with MSW on both Node and the browser.
  • Proxy middleware requests through a local Edge Proxy so they can be mocked too.
  • Get handy fixtures for Edge Config, feature flags, GraphQL helpers and more.

Why we built it 🧩

We built this tool to solve recurring pain points in our Next.js E2E testing:

  • Avoid real mutations – writing to production-like databases during tests pollutes staging environments and requires manual cleanup.
  • Bypass rate-limited APIs – internal endpoints get hit thousands of times across parallel CI workers.
  • Unmockable SSR and Edge requests – requests from getServerSideProps or middleware.ts can't be intercepted, making tests brittle and unpredictable.
  • Keep tests fast & debuggable – no extra setup, no network flakiness, and full support for the Playwright VSCode extension.

We needed a clean, consistent way to mock every layer of a Next.js app – without sacrificing confidence, speed, or developer experience. And @krakentech/blueprint‑playwright delivers exactly that by wrapping Playwright with MSW and a tiny Express proxy for Edge Runtime.


Setup in 3 steps ⚙️

1 – Playwright config

Create/extend playwright.config.ts.

playwright.config.ts
import { devices, type PlaywrightTestConfig } from '@playwright/test';

const config: PlaywrightTestConfig = {
testDir: './src/tests/playwright',
fullyParallel: true,
timeout: 60000 * 10, // 10 minutes
expect: { timeout: 20000 }, // 20 seconds
retries: process.env.CI ? 2 : 0,
workers: process.env.CI || process.env.PLAYWRIGHT_DEV_MODE ? 1 : undefined,
use: {
locale: 'en-GB',
timezoneId: 'Europe/London',
trace: 'on-first-retry',
ignoreHTTPSErrors: true,
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }
],
};

export default config;

2 – Helpers file

Create src/lib/playwright/test.ts and always import test/expect from there.

src/lib/playwright/test.ts
import { resolve } from 'node:path';
import { env } from '@/src/lib/env';
import { createPlaywrightHelpers } from '@krakentech/blueprint-playwright';

export const { test, expect } = createPlaywrightHelpers({
nextServerOptions: {
dir: resolve(__dirname, '../../..'), // Next.js root
dev: process.env.PLAYWRIGHT_DEV_MODE === 'true',
quiet: true,
experimentalHttpsServer: true,
},
graphqlEndpoint: env.NEXT_PUBLIC_KRAKEN_API_URL,
});

3 – `package.json scripts

{
"test:e2e": "NEXT_PUBLIC_PLAYWRIGHT_MODE=production playwright test",
"test:e2e:build": "NEXT_PUBLIC_PLAYWRIGHT_MODE=production NODE_ENV=production pnpm build:partial && pnpm test:e2e",
"test:e2e:build:ui": "pnpm test:e2e:build --ui",
"test:e2e:dev": "rm -rf .next && NEXT_PUBLIC_PLAYWRIGHT_MODE=development playwright test",
"test:e2e:dev:ui": "pnpm test:e2e:dev --ui",
"test:e2e:ui": "pnpm test:e2e --ui"
}

Replace pnpm with npm/yarn if needed.

How it works under the hood 🔬

  1. Worker bootstrap
  • A headless Next.js server is started (_nextServer).
  • An Edge Proxy (Express) starts on 127.0.0.1:<random>.
  • MSW spins up in Node (_serverWorker).
  1. Per‑test setup
  • _environment injects x-playwright-edge-port so your middleware knows where to send requests.
  • clientWorker converts any MSW handler to page.route.
  • Extra helpers (edgeConfig, flags, waitForGraphQLResponse, baseURL) are registered.
  1. Runtime
  • Browser → intercepted by clientWorker.
  • SSR / API routes → intercepted by serverWorker.
  • Middleware (Edge) → hits the local proxy → mocked if a handler exists, otherwise forwarded to the real API.

Examples 🍭

Client‑side render + client mock

import { test, expect } from '@/lib/playwright/test';
import { graphql, HttpResponse } from 'msw';
import { ViewerQuery } from '@/handlers/queries' // gql.tada document

test("client mock", async ({ page, clientWorker }) => {
await clientWorker.use(
graphql.query(Viewer, () =>
HttpResponse.json({ data: { viewer: { id: '42' } } }),
),
);

await page.goto('/user'); // Client side public route
await expect(page.getByText('42')).toBeVisible();
});

Server‑side render + middleware mock

import { test, expect } from '@/lib/playwright/test';
import { graphql, HttpResponse } from 'msw';
import { ObtainKrakenTokenMutation } from '@/handlers/mutations'
import { ObtainKrakenTokenMutationMock } from '@/lib/mocks'

test("SSR + middleware mocks", async ({ page, serverWorker }) => {
// Mock SSR + middleware
serverWorker.use(
graphql.mutation(ObtainKrakenTokenMutation, () =>
HttpResponse.json(ObtainKrakenTokenMutationMock),
),
graphql.query("Viewer", () =>
HttpResponse.json({ data: { viewer: { id: '42' } } }),
),
);

await page.goto('/dashboard'); // Server side protected route
await expect(page.getByText('42')).toBeVisible();
});

Next steps ➡️

Check out the API reference for the full fixture list and advanced patterns.