Skip to content

Commit d363f11

Browse files
authored
feat!: implement new defineNetworkFixture api (#36)
1 parent 6933ba9 commit d363f11

14 files changed

Lines changed: 249 additions & 120 deletions

README.md

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,24 @@ interface Fixtures {
3737
network: NetworkFixture
3838
}
3939

40-
export const test = testBase.extend<Fixtures>({
41-
// Create a fixture that will control the network in your tests.
42-
network: createNetworkFixture({
43-
initialHandlers: handlers,
44-
}),
40+
const test = testBase.extend<Fixtures>({
41+
// Initial list of the network handlers.
42+
handlers: [[], { option: true }],
43+
44+
// A fixture you use to control the network in your tests.
45+
network: [
46+
async ({ context, handlers }, use) => {
47+
const network = defineNetworkFixture({
48+
context,
49+
handlers,
50+
})
51+
52+
await network.enable()
53+
await use(network)
54+
await network.disable()
55+
},
56+
{ auto: true },
57+
],
4558
})
4659
```
4760

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,15 @@
4242
"node": ">=20.0.0"
4343
},
4444
"peerDependencies": {
45-
"msw": "^2.12.9"
45+
"msw": "^2.12.10"
4646
},
4747
"devDependencies": {
4848
"@epic-web/test-server": "^0.1.6",
4949
"@ossjs/release": "^0.10.1",
5050
"@playwright/test": "^1.58.1",
5151
"@types/node": "^22.15.29",
5252
"@types/sinon": "^21.0.0",
53-
"msw": "^2.12.9",
53+
"msw": "^2.12.10",
5454
"sinon": "^21.0.1",
5555
"tsdown": "^0.12.7",
5656
"typescript": "^5.9.3",
@@ -60,4 +60,4 @@
6060
"@mswjs/interceptors": "^0.41.2",
6161
"outvariant": "^1.4.3"
6262
}
63-
}
63+
}

pnpm-lock.yaml

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/fixture.ts

Lines changed: 43 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,18 @@ import { invariant } from 'outvariant'
22
import type {
33
BrowserContext,
44
Page,
5-
PlaywrightTestArgs,
6-
PlaywrightWorkerArgs,
75
Request as PlaywrightRequest,
86
Route,
9-
TestFixture,
107
WebSocketRoute,
118
} from '@playwright/test'
9+
import { WebSocketHandler } from 'msw'
1210
import {
13-
type LifeCycleEventsMap,
14-
type UnhandledRequestStrategy,
1511
SetupApi,
16-
RequestHandler,
17-
WebSocketHandler,
1812
handleRequest,
1913
isCommonAssetRequest,
14+
type AnyHandler,
15+
type LifeCycleEventsMap,
16+
type UnhandledRequestStrategy,
2017
} from 'msw'
2118
import {
2219
type WebSocketClientEventMap,
@@ -27,9 +24,11 @@ import {
2724
WebSocketClientConnectionProtocol,
2825
WebSocketServerConnectionProtocol,
2926
} from '@mswjs/interceptors/WebSocket'
27+
import { RequestHandler } from 'msw'
3028

31-
export interface CreateNetworkFixtureArgs {
32-
initialHandlers?: Array<RequestHandler | WebSocketHandler>
29+
export interface NetworkFixtureOptions {
30+
context: BrowserContext
31+
handlers?: Array<AnyHandler>
3332
onUnhandledRequest?: UnhandledRequestStrategy
3433
/**
3534
* Skip common asset requests (e.g. `*.html`, `*.css`, `*.js`, etc).
@@ -41,46 +40,27 @@ export interface CreateNetworkFixtureArgs {
4140
skipAssetRequests?: boolean
4241
}
4342

44-
/**
45-
* Creates a fixture that controls the network in your tests.
46-
*
47-
* @note The returned fixture already has the `auto` option set to `true`.
48-
*
49-
* **Usage**
50-
* ```ts
51-
* import { test as testBase } from '@playwright/test'
52-
* import { createNetworkFixture, type WorkerFixture } from '@msw/playwright'
53-
*
54-
* interface Fixtures {
55-
* network: WorkerFixture
56-
* }
57-
*
58-
* export const test = testBase.extend<Fixtures>({
59-
* network: createNetworkFixture()
60-
* })
61-
* ```
62-
*/
63-
export function createNetworkFixture(
64-
args?: CreateNetworkFixtureArgs,
65-
): [
66-
TestFixture<NetworkFixture, PlaywrightTestArgs & PlaywrightWorkerArgs>,
67-
{ auto: boolean },
68-
] {
69-
return [
70-
async ({ context }, use) => {
71-
const worker = new NetworkFixture({
72-
context,
73-
skipAssetRequests: args?.skipAssetRequests ?? true,
74-
initialHandlers: args?.initialHandlers || [],
75-
onUnhandledRequest: args?.onUnhandledRequest,
76-
})
43+
export type NetworkFixture = Omit<SetupApi<LifeCycleEventsMap>, 'dispose'> & {
44+
enable: () => Promise<void>
45+
disable: () => Promise<void>
46+
}
47+
48+
export function defineNetworkFixture(
49+
options: NetworkFixtureOptions,
50+
): NetworkFixture {
51+
return new SetupPlaywrightApi({
52+
context: options.context,
53+
initialHandlers: options.handlers || [],
54+
onUnhandledRequest: options.onUnhandledRequest,
55+
skipAssetRequests: options.skipAssetRequests ?? true,
56+
})
57+
}
7758

78-
await worker.start()
79-
await use(worker)
80-
await worker.stop()
81-
},
82-
{ auto: true },
83-
]
59+
interface SetupPlaywrightOptions {
60+
context: BrowserContext
61+
initialHandlers: Array<AnyHandler>
62+
onUnhandledRequest?: UnhandledRequestStrategy
63+
skipAssetRequests?: boolean
8464
}
8565

8666
/**
@@ -91,21 +71,14 @@ export function createNetworkFixture(
9171
*/
9272
export const INTERNAL_MATCH_ALL_REG_EXP = /.+(__MSW_PLAYWRIGHT_PREDICATE__)?/
9373

94-
export class NetworkFixture extends SetupApi<LifeCycleEventsMap> {
95-
constructor(
96-
protected args: {
97-
context: BrowserContext
98-
skipAssetRequests: boolean
99-
initialHandlers: Array<RequestHandler | WebSocketHandler>
100-
onUnhandledRequest?: UnhandledRequestStrategy
101-
},
102-
) {
103-
super(...args.initialHandlers)
74+
class SetupPlaywrightApi extends SetupApi<LifeCycleEventsMap> {
75+
constructor(private readonly options: SetupPlaywrightOptions) {
76+
super(...options.initialHandlers)
10477
}
10578

106-
public async start(): Promise<void> {
79+
public async enable(): Promise<void> {
10780
// Handle HTTP requests.
108-
await this.args.context.route(
81+
await this.options.context.route(
10982
INTERNAL_MATCH_ALL_REG_EXP,
11083
async (route: Route, request: PlaywrightRequest) => {
11184
const fetchRequest = new Request(request.url(), {
@@ -120,7 +93,10 @@ export class NetworkFixture extends SetupApi<LifeCycleEventsMap> {
12093
* requests through the matching logic below.
12194
* @see https://github.com/mswjs/playwright/issues/13
12295
*/
123-
if (this.args.skipAssetRequests && isCommonAssetRequest(fetchRequest)) {
96+
if (
97+
this.options.skipAssetRequests &&
98+
isCommonAssetRequest(fetchRequest)
99+
) {
124100
return route.continue()
125101
}
126102

@@ -143,7 +119,7 @@ export class NetworkFixture extends SetupApi<LifeCycleEventsMap> {
143119
crypto.randomUUID(),
144120
handlers,
145121
{
146-
onUnhandledRequest: this.args.onUnhandledRequest || 'bypass',
122+
onUnhandledRequest: this.options.onUnhandledRequest || 'bypass',
147123
},
148124
this.emitter,
149125
{
@@ -173,7 +149,7 @@ export class NetworkFixture extends SetupApi<LifeCycleEventsMap> {
173149
)
174150

175151
// Handle WebSocket connections.
176-
await this.args.context.routeWebSocket(
152+
await this.options.context.routeWebSocket(
177153
INTERNAL_MATCH_ALL_REG_EXP,
178154
async (route) => {
179155
const allWebSocketHandlers = this.handlersController
@@ -190,7 +166,7 @@ export class NetworkFixture extends SetupApi<LifeCycleEventsMap> {
190166
const client = new PlaywrightWebSocketClientConnection(route)
191167
const server = new PlaywrightWebSocketServerConnection(route)
192168

193-
const pages = this.args.context.pages()
169+
const pages = this.options.context.pages()
194170
const lastPage = pages[pages.length - 1]
195171
const baseUrl = lastPage ? this.getPageUrl(lastPage) : undefined
196172

@@ -210,10 +186,10 @@ export class NetworkFixture extends SetupApi<LifeCycleEventsMap> {
210186
)
211187
}
212188

213-
public async stop(): Promise<void> {
189+
public async disable(): Promise<void> {
214190
super.dispose()
215-
await this.args.context.unroute(INTERNAL_MATCH_ALL_REG_EXP)
216-
await unrouteWebSocket(this.args.context, INTERNAL_MATCH_ALL_REG_EXP)
191+
await this.options.context.unroute(INTERNAL_MATCH_ALL_REG_EXP)
192+
await unrouteWebSocket(this.options.context, INTERNAL_MATCH_ALL_REG_EXP)
217193
}
218194

219195
private getPageUrl(page: Page): string | undefined {

src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
export {
2-
type CreateNetworkFixtureArgs,
2+
defineNetworkFixture,
33
type NetworkFixture,
4-
createNetworkFixture,
4+
type NetworkFixtureOptions,
55
} from './fixture.js'

tests/internal/routes.test.ts

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,30 @@
44
* given Playwright doesn't expose proper means to list route handlers.
55
*/
66
import { test as testBase, expect } from '@playwright/test'
7+
import type { AnyHandler } from 'msw'
78
import { INTERNAL_MATCH_ALL_REG_EXP } from '../../src/fixture.js'
8-
import { createNetworkFixture, type NetworkFixture } from '../../src/index.js'
9+
import { defineNetworkFixture, type NetworkFixture } from '../../src/index.js'
910

1011
interface Fixtures {
12+
handlers: Array<AnyHandler>
1113
network: NetworkFixture
1214
}
1315

1416
const test = testBase.extend<Fixtures>({
15-
network: createNetworkFixture(),
17+
handlers: [[], { option: true }],
18+
network: [
19+
async ({ context, handlers }, use) => {
20+
const network = defineNetworkFixture({
21+
context,
22+
handlers,
23+
})
24+
25+
await network.enable()
26+
await use(network)
27+
await network.disable()
28+
},
29+
{ auto: true },
30+
],
1631
})
1732

1833
test('registers a single HTTP route', async ({ context }) => {
@@ -25,7 +40,7 @@ test('unroutes the HTTP route when the fixture is stopped', async ({
2540
context,
2641
network,
2742
}) => {
28-
await network.stop()
43+
await network.disable()
2944
expect(Reflect.get(context, '_routes')).toEqual([])
3045
})
3146

@@ -38,7 +53,7 @@ test('preserves user-defined HTTP routes', async ({ context, network }) => {
3853
expect.objectContaining({ url: INTERNAL_MATCH_ALL_REG_EXP }),
3954
])
4055

41-
await network.stop()
56+
await network.disable()
4257
expect(Reflect.get(context, '_routes')).toEqual([
4358
expect.objectContaining({ url: '/user-defined', handler: routeHandler }),
4459
])
@@ -54,7 +69,7 @@ test('unroutes the WebSocket handler when the fixture is stopped', async ({
5469
context,
5570
network,
5671
}) => {
57-
await network.stop()
72+
await network.disable()
5873
expect(Reflect.get(context, '_webSocketRoutes')).toEqual([])
5974
})
6075

@@ -70,7 +85,7 @@ test('preserves user-defined WebSocket routes', async ({
7085
expect.objectContaining({ url: INTERNAL_MATCH_ALL_REG_EXP }),
7186
])
7287

73-
await network.stop()
88+
await network.disable()
7489
expect(Reflect.get(context, '_webSocketRoutes')).toEqual([
7590
expect.objectContaining({ url: '/user-defined', handler: routeHandler }),
7691
])

tests/multiple-pages.test.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,27 @@
11
import { test as testBase, expect } from '@playwright/test'
2-
import { http, HttpResponse, ws } from 'msw'
3-
import { createNetworkFixture, type NetworkFixture } from '../src/index.js'
2+
import { http, HttpResponse, ws, type AnyHandler } from 'msw'
3+
import { defineNetworkFixture, type NetworkFixture } from '../src/index.js'
44

55
interface Fixtures {
6+
handlers: Array<AnyHandler>
67
network: NetworkFixture
78
}
89

910
const test = testBase.extend<Fixtures>({
10-
network: createNetworkFixture(),
11+
handlers: [[], { option: true }],
12+
network: [
13+
async ({ context, handlers }, use) => {
14+
const network = defineNetworkFixture({
15+
context,
16+
handlers,
17+
})
18+
19+
await network.enable()
20+
await use(network)
21+
await network.disable()
22+
},
23+
{ auto: true },
24+
],
1125
})
1226

1327
test('intercepts an HTTP request on a programmatically created page', async ({

0 commit comments

Comments
 (0)