@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
ormiddleware.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
.
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.
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
withnpm
/yarn
if needed.
How it works under the hood 🔬
- 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
).
- Per‑test setup
_environment
injectsx-playwright-edge-port
so your middleware knows where to send requests.clientWorker
converts any MSW handler topage.route
.- Extra helpers (
edgeConfig
,flags
,waitForGraphQLResponse
,baseURL
) are registered.
- 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.