Skip to content

Commit 88ad471

Browse files
authored
Concept: test mode for Playwright and similar integration tools (vercel#52520)
An experimental test mode that enables integration tests to mock server-side fetch requests in Playwright tests. For explanation on how this works, see the [`next-playwright/README.md`](https://github.com/vercel/next.js/pull/52520/files#diff-3b8da7782c16f015df5afafe0ac11247f5b8e5a1c0dbede341ca2b5124dfd924).
1 parent 3958fc0 commit 88ad471

28 files changed

+1071
-82
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from '../../dist/experimental/testmode/playwright'
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = require('../../dist/experimental/testmode/playwright')
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from '../../../dist/experimental/testmode/playwright/msw'
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = require('../../../dist/experimental/testmode/playwright/msw')
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from '../../dist/experimental/testmode/proxy'
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = require('../../dist/experimental/testmode/proxy')

packages/next/package.json

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,13 @@
6161
"headers.d.ts",
6262
"navigation-types",
6363
"web-vitals.js",
64-
"web-vitals.d.ts"
64+
"web-vitals.d.ts",
65+
"experimental/testmode/playwright.js",
66+
"experimental/testmode/playwright.d.ts",
67+
"experimental/testmode/playwright/msw.js",
68+
"experimental/testmode/playwright/msw.d.ts",
69+
"experimental/testmode/proxy.js",
70+
"experimental/testmode/proxy.d.ts"
6571
],
6672
"bin": {
6773
"next": "./dist/bin/next"
@@ -143,6 +149,7 @@
143149
"@next/react-refresh-utils": "13.4.15",
144150
"@next/swc": "13.4.15",
145151
"@opentelemetry/api": "1.4.1",
152+
"@playwright/test": "^1.35.1",
146153
"@segment/ajv-human-errors": "2.1.2",
147154
"@taskr/clear": "1.1.0",
148155
"@taskr/esnext": "1.1.0",
@@ -245,6 +252,7 @@
245252
"lru-cache": "5.1.1",
246253
"micromatch": "4.0.4",
247254
"mini-css-extract-plugin": "2.4.3",
255+
"msw": "^1.2.2",
248256
"nanoid": "3.1.32",
249257
"native-url": "0.3.4",
250258
"neo-async": "2.6.1",
@@ -283,6 +291,7 @@
283291
"stacktrace-parser": "0.1.10",
284292
"stream-browserify": "3.0.0",
285293
"stream-http": "3.1.1",
294+
"strict-event-emitter": "0.5.0",
286295
"string-hash": "1.1.3",
287296
"string_decoder": "1.3.0",
288297
"strip-ansi": "6.0.0",

packages/next/src/cli/next-dev.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ const nextDev: CliCommand = async (argv) => {
189189
'--hostname': String,
190190
'--turbo': Boolean,
191191
'--experimental-turbo': Boolean,
192+
'--experimental-test-proxy': Boolean,
192193

193194
// To align current messages with native binary.
194195
// Will need to adjust subcommand later.
@@ -274,13 +275,15 @@ const nextDev: CliCommand = async (argv) => {
274275
// some set-ups that rely on listening on other interfaces
275276
const host = args['--hostname']
276277
config = await loadConfig(PHASE_DEVELOPMENT_SERVER, dir)
278+
const isExperimentalTestProxy = args['--experimental-test-proxy']
277279

278280
const devServerOptions: StartServerOptions = {
279281
dir,
280282
port,
281283
allowRetry,
282284
isDev: true,
283285
hostname: host,
286+
isExperimentalTestProxy,
284287
}
285288

286289
if (args['--turbo']) {

packages/next/src/cli/next-start.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const nextStart: CliCommand = async (argv) => {
1414
'--port': Number,
1515
'--hostname': String,
1616
'--keepAliveTimeout': Number,
17+
'--experimental-test-proxy': Boolean,
1718

1819
// Aliases
1920
'-h': '--help',
@@ -46,6 +47,8 @@ const nextStart: CliCommand = async (argv) => {
4647
const host = args['--hostname']
4748
const port = getPort(args)
4849

50+
const isExperimentalTestProxy = args['--experimental-test-proxy']
51+
4952
const keepAliveTimeoutArg: number | undefined = args['--keepAliveTimeout']
5053
if (
5154
typeof keepAliveTimeoutArg !== 'undefined' &&
@@ -66,6 +69,7 @@ const nextStart: CliCommand = async (argv) => {
6669
await startServer({
6770
dir,
6871
isDev: false,
72+
isExperimentalTestProxy,
6973
hostname: host,
7074
port,
7175
keepAliveTimeout,
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# Experimental test mode for Playwright
2+
3+
### Prerequisites
4+
5+
You have a Next.js project.
6+
7+
### Install `@playwright/test` in your project
8+
9+
```sh
10+
npm install -D @playwright/test
11+
```
12+
13+
### Optionally install MSW in your project
14+
15+
[MSW](https://mswjs.io/) can be helpful for fetch mocking.
16+
17+
```sh
18+
npm install -D msw
19+
```
20+
21+
### Update `playwright.config.ts`
22+
23+
```javascript
24+
import { defineConfig } from 'next/experimental/testmode/playwright'
25+
26+
export default defineConfig({
27+
webServer: {
28+
command: 'npm dev -- --experimental-test-proxy',
29+
url: 'http://localhost:3000',
30+
},
31+
})
32+
```
33+
34+
### Use the `next/experimental/testmode/playwright` to create tests
35+
36+
```javascript
37+
import { test, expect } from 'next/experimental/testmode/playwright'
38+
39+
test('/product/shoe', async ({ page, next }) => {
40+
next.onFetch((request) => {
41+
if (request.url === 'http://my-db/product/shoe') {
42+
return new Response(
43+
JSON.stringify({
44+
title: 'A shoe',
45+
}),
46+
{
47+
headers: {
48+
'Content-Type': 'application/json',
49+
},
50+
}
51+
)
52+
}
53+
return 'abort'
54+
})
55+
56+
await page.goto('/product/shoe')
57+
58+
await expect(page.locator('body')).toHaveText(/Shoe/)
59+
})
60+
```
61+
62+
### Or use the `next/experimental/testmode/playwright/msw`
63+
64+
```javascript
65+
import { test, expect, rest } from 'next/experimental/testmode/playwright/msw'
66+
67+
test.use({
68+
mswHandlers: [
69+
rest.get('http://my-db/product/shoe', (req, res, ctx) => {
70+
return res(
71+
ctx.status(200),
72+
ctx.json({
73+
title: 'A shoe',
74+
})
75+
)
76+
}),
77+
],
78+
})
79+
80+
test('/product/shoe', async ({ page, msw }) => {
81+
msw.use(
82+
rest.get('http://my-db/product/boot', (req, res, ctx) => {
83+
return res.once(
84+
ctx.status(200),
85+
ctx.json({
86+
title: 'A boot',
87+
})
88+
)
89+
})
90+
)
91+
92+
await page.goto('/product/boot')
93+
94+
await expect(page.locator('body')).toHaveText(/Boot/)
95+
})
96+
```

0 commit comments

Comments
 (0)