@msw/playwright 0.4.3 → 0.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/index.d.ts CHANGED
@@ -1,9 +1,10 @@
1
- import { LifeCycleEventsMap, RequestHandler, SetupApi, WebSocketHandler } from "msw";
1
+ import { LifeCycleEventsMap, RequestHandler, SetupApi, UnhandledRequestStrategy, WebSocketHandler } from "msw";
2
2
  import { Page, PlaywrightTestArgs, PlaywrightWorkerArgs, TestFixture } from "@playwright/test";
3
3
 
4
- //#region src/index.d.ts
4
+ //#region src/fixture.d.ts
5
5
  interface CreateNetworkFixtureArgs {
6
6
  initialHandlers: Array<RequestHandler | WebSocketHandler>;
7
+ onUnhandledRequest?: UnhandledRequestStrategy;
7
8
  }
8
9
  /**
9
10
  * Creates a fixture that controls the network in your tests.
@@ -27,11 +28,23 @@ interface CreateNetworkFixtureArgs {
27
28
  declare function createNetworkFixture(args?: CreateNetworkFixtureArgs): [TestFixture<NetworkFixture, PlaywrightTestArgs & PlaywrightWorkerArgs>, {
28
29
  auto: boolean;
29
30
  }];
31
+ /**
32
+ * @note Use a match-all RegExp with an optional group as the predicate
33
+ * for the `page.route()`/`page.unroute()` calls. Playwright treats given RegExp
34
+ * as the handler ID, which allows us to remove only those handlers introduces by us
35
+ * without carrying the reference to the handler function around.
36
+ */
37
+
30
38
  declare class NetworkFixture extends SetupApi<LifeCycleEventsMap> {
31
- #private;
39
+ protected args: {
40
+ page: Page;
41
+ initialHandlers: Array<RequestHandler | WebSocketHandler>;
42
+ onUnhandledRequest?: UnhandledRequestStrategy;
43
+ };
32
44
  constructor(args: {
33
45
  page: Page;
34
46
  initialHandlers: Array<RequestHandler | WebSocketHandler>;
47
+ onUnhandledRequest?: UnhandledRequestStrategy;
35
48
  });
36
49
  start(): Promise<void>;
37
50
  stop(): Promise<void>;
package/build/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { invariant } from "outvariant";
2
- import { RequestHandler, SetupApi, WebSocketHandler, getResponse } from "msw";
2
+ import { RequestHandler, SetupApi, WebSocketHandler, handleRequest } from "msw";
3
3
  import { CancelableCloseEvent, CancelableMessageEvent } from "@mswjs/interceptors/WebSocket";
4
4
 
5
- //#region src/index.ts
5
+ //#region src/fixture.ts
6
6
  /**
7
7
  * Creates a fixture that controls the network in your tests.
8
8
  *
@@ -26,29 +26,40 @@ function createNetworkFixture(args) {
26
26
  return [async ({ page }, use) => {
27
27
  const worker = new NetworkFixture({
28
28
  page,
29
- initialHandlers: args?.initialHandlers || []
29
+ initialHandlers: args?.initialHandlers || [],
30
+ onUnhandledRequest: args?.onUnhandledRequest
30
31
  });
31
32
  await worker.start();
32
33
  await use(worker);
33
34
  await worker.stop();
34
35
  }, { auto: true }];
35
36
  }
37
+ /**
38
+ * @note Use a match-all RegExp with an optional group as the predicate
39
+ * for the `page.route()`/`page.unroute()` calls. Playwright treats given RegExp
40
+ * as the handler ID, which allows us to remove only those handlers introduces by us
41
+ * without carrying the reference to the handler function around.
42
+ */
43
+ const INTERNAL_MATCH_ALL_REG_EXP = /.+(__MSW_PLAYWRIGHT_PREDICATE__)?/;
36
44
  var NetworkFixture = class extends SetupApi {
37
- #page;
38
45
  constructor(args) {
39
46
  super(...args.initialHandlers);
40
- this.#page = args.page;
47
+ this.args = args;
41
48
  }
42
49
  async start() {
43
- const httpRequestListener = async (route, request) => {
50
+ await this.args.page.route(INTERNAL_MATCH_ALL_REG_EXP, async (route, request) => {
44
51
  const fetchRequest = new Request(request.url(), {
45
52
  method: request.method(),
46
53
  headers: new Headers(await request.allHeaders()),
47
54
  body: request.postDataBuffer()
48
55
  });
49
- const response = await getResponse(this.handlersController.currentHandlers().filter((handler) => {
56
+ const handlers = this.handlersController.currentHandlers().filter((handler) => {
50
57
  return handler instanceof RequestHandler;
51
- }), fetchRequest, { baseUrl: this.getPageUrl() });
58
+ });
59
+ const response = await handleRequest(fetchRequest, crypto.randomUUID(), handlers, { onUnhandledRequest: this.args.onUnhandledRequest || "warn" }, this.emitter, { resolutionContext: {
60
+ quiet: true,
61
+ baseUrl: this.getPageUrl()
62
+ } });
52
63
  if (response) {
53
64
  if (response.status === 0) {
54
65
  route.abort();
@@ -62,9 +73,8 @@ var NetworkFixture = class extends SetupApi {
62
73
  return;
63
74
  }
64
75
  route.continue();
65
- };
66
- await this.#page.route(/.+/, httpRequestListener);
67
- await this.#page.routeWebSocket(/.+/, async (ws) => {
76
+ });
77
+ await this.args.page.routeWebSocket(INTERNAL_MATCH_ALL_REG_EXP, async (ws) => {
68
78
  const allWebSocketHandlers = this.handlersController.currentHandlers().filter((handler) => {
69
79
  return handler instanceof WebSocketHandler;
70
80
  });
@@ -83,7 +93,8 @@ var NetworkFixture = class extends SetupApi {
83
93
  }
84
94
  async stop() {
85
95
  super.dispose();
86
- await this.#page.unroute(/.+/);
96
+ await this.#page.unroute(INTERNAL_MATCH_ALL_REG_EXP);
97
+ await unrouteWebSocket(this.#page, INTERNAL_MATCH_ALL_REG_EXP);
87
98
  }
88
99
  getPageUrl() {
89
100
  const url = this.#page.url();
@@ -229,6 +240,17 @@ var PlaywrightWebSocketServerConnection = class {
229
240
  console.warn("@msw/playwright: WebSocketRoute does not support removing event listeners");
230
241
  }
231
242
  };
243
+ /**
244
+ * Custom implementation of the missing `page.unrouteWebSocket()` to remove
245
+ * WebSocket route handlers from the page. Loosely inspired by `page.unroute()`.
246
+ */
247
+ async function unrouteWebSocket(page, url, handler) {
248
+ if (!("_webSocketRoutes" in page && Array.isArray(page._webSocketRoutes))) return;
249
+ for (let i = page._webSocketRoutes.length - 1; i >= 0; i--) {
250
+ const route = page._webSocketRoutes[i];
251
+ if (route.url === url && (handler != null ? route.handler === handler : true)) page._webSocketRoutes.splice(i, 1);
252
+ }
253
+ }
232
254
 
233
255
  //#endregion
234
- export { NetworkFixture, createNetworkFixture };
256
+ export { createNetworkFixture };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@msw/playwright",
4
- "version": "0.4.3",
4
+ "version": "0.4.4",
5
5
  "description": "Mock Service Worker binding for Playwright",
6
6
  "main": "./build/index.js",
7
7
  "types": "./build/index.d.ts",
@@ -40,13 +40,15 @@
40
40
  "@ossjs/release": "^0.10.1",
41
41
  "@playwright/test": "^1.57.0",
42
42
  "@types/node": "^22.15.29",
43
- "msw": "^2.10.3",
43
+ "@types/sinon": "^21.0.0",
44
+ "msw": "^2.12.7",
45
+ "sinon": "^21.0.1",
44
46
  "tsdown": "^0.12.7",
45
- "typescript": "^5.8.3",
47
+ "typescript": "^5.9.3",
46
48
  "vite": "^7.3.1"
47
49
  },
48
50
  "dependencies": {
49
- "@mswjs/interceptors": "^0.39.2",
51
+ "@mswjs/interceptors": "^0.40.0",
50
52
  "outvariant": "^1.4.3"
51
53
  },
52
54
  "scripts": {
package/src/fixture.ts CHANGED
@@ -10,10 +10,11 @@ import type {
10
10
  } from '@playwright/test'
11
11
  import {
12
12
  type LifeCycleEventsMap,
13
+ type UnhandledRequestStrategy,
13
14
  SetupApi,
14
15
  RequestHandler,
15
16
  WebSocketHandler,
16
- getResponse,
17
+ handleRequest,
17
18
  } from 'msw'
18
19
  import {
19
20
  type WebSocketClientEventMap,
@@ -26,7 +27,8 @@ import {
26
27
  } from '@mswjs/interceptors/WebSocket'
27
28
 
28
29
  export interface CreateNetworkFixtureArgs {
29
- initialHandlers: Array<RequestHandler | WebSocketHandler>
30
+ initialHandlers?: Array<RequestHandler | WebSocketHandler>
31
+ onUnhandledRequest?: UnhandledRequestStrategy
30
32
  }
31
33
 
32
34
  /**
@@ -50,7 +52,6 @@ export interface CreateNetworkFixtureArgs {
50
52
  */
51
53
  export function createNetworkFixture(
52
54
  args?: CreateNetworkFixtureArgs,
53
- /** @todo `onUnhandledRequest`? */
54
55
  ): [
55
56
  TestFixture<NetworkFixture, PlaywrightTestArgs & PlaywrightWorkerArgs>,
56
57
  { auto: boolean },
@@ -60,6 +61,7 @@ export function createNetworkFixture(
60
61
  const worker = new NetworkFixture({
61
62
  page,
62
63
  initialHandlers: args?.initialHandlers || [],
64
+ onUnhandledRequest: args?.onUnhandledRequest,
63
65
  })
64
66
 
65
67
  await worker.start()
@@ -79,19 +81,19 @@ export function createNetworkFixture(
79
81
  export const INTERNAL_MATCH_ALL_REG_EXP = /.+(__MSW_PLAYWRIGHT_PREDICATE__)?/
80
82
 
81
83
  export class NetworkFixture extends SetupApi<LifeCycleEventsMap> {
82
- #page: Page
83
-
84
- constructor(args: {
85
- page: Page
86
- initialHandlers: Array<RequestHandler | WebSocketHandler>
87
- }) {
84
+ constructor(
85
+ protected args: {
86
+ page: Page
87
+ initialHandlers: Array<RequestHandler | WebSocketHandler>
88
+ onUnhandledRequest?: UnhandledRequestStrategy
89
+ },
90
+ ) {
88
91
  super(...args.initialHandlers)
89
- this.#page = args.page
90
92
  }
91
93
 
92
94
  public async start(): Promise<void> {
93
95
  // Handle HTTP requests.
94
- await this.#page.route(
96
+ await this.args.page.route(
95
97
  INTERNAL_MATCH_ALL_REG_EXP,
96
98
  async (route: Route, request: Request) => {
97
99
  const fetchRequest = new Request(request.url(), {
@@ -100,13 +102,29 @@ export class NetworkFixture extends SetupApi<LifeCycleEventsMap> {
100
102
  body: request.postDataBuffer(),
101
103
  })
102
104
 
103
- const response = await getResponse(
104
- this.handlersController.currentHandlers().filter((handler) => {
105
+ const handlers = this.handlersController
106
+ .currentHandlers()
107
+ .filter((handler) => {
105
108
  return handler instanceof RequestHandler
106
- }),
109
+ })
110
+
111
+ /**
112
+ * @note Use `handleRequest` instead of `getResponse` so we can pass
113
+ * the `onUnhandledRequest` option as-is and benefit from MSW's default behaviors.
114
+ */
115
+ const response = await handleRequest(
107
116
  fetchRequest,
117
+ crypto.randomUUID(),
118
+ handlers,
108
119
  {
109
- baseUrl: this.getPageUrl(),
120
+ onUnhandledRequest: this.args.onUnhandledRequest || 'bypass',
121
+ },
122
+ this.emitter,
123
+ {
124
+ resolutionContext: {
125
+ quiet: true,
126
+ baseUrl: this.getPageUrl(),
127
+ },
110
128
  },
111
129
  )
112
130
 
@@ -131,44 +149,47 @@ export class NetworkFixture extends SetupApi<LifeCycleEventsMap> {
131
149
  )
132
150
 
133
151
  // Handle WebSocket connections.
134
- await this.#page.routeWebSocket(INTERNAL_MATCH_ALL_REG_EXP, async (ws) => {
135
- const allWebSocketHandlers = this.handlersController
136
- .currentHandlers()
137
- .filter((handler) => {
138
- return handler instanceof WebSocketHandler
139
- })
140
-
141
- if (allWebSocketHandlers.length === 0) {
142
- ws.connectToServer()
143
- return
144
- }
152
+ await this.args.page.routeWebSocket(
153
+ INTERNAL_MATCH_ALL_REG_EXP,
154
+ async (ws) => {
155
+ const allWebSocketHandlers = this.handlersController
156
+ .currentHandlers()
157
+ .filter((handler) => {
158
+ return handler instanceof WebSocketHandler
159
+ })
145
160
 
146
- const client = new PlaywrightWebSocketClientConnection(ws)
147
- const server = new PlaywrightWebSocketServerConnection(ws)
161
+ if (allWebSocketHandlers.length === 0) {
162
+ ws.connectToServer()
163
+ return
164
+ }
148
165
 
149
- for (const handler of allWebSocketHandlers) {
150
- await handler.run(
151
- {
152
- client,
153
- server,
154
- info: { protocols: [] },
155
- },
156
- {
157
- baseUrl: this.getPageUrl(),
158
- },
159
- )
160
- }
161
- })
166
+ const client = new PlaywrightWebSocketClientConnection(ws)
167
+ const server = new PlaywrightWebSocketServerConnection(ws)
168
+
169
+ for (const handler of allWebSocketHandlers) {
170
+ await handler.run(
171
+ {
172
+ client,
173
+ server,
174
+ info: { protocols: [] },
175
+ },
176
+ {
177
+ baseUrl: this.getPageUrl(),
178
+ },
179
+ )
180
+ }
181
+ },
182
+ )
162
183
  }
163
184
 
164
185
  public async stop(): Promise<void> {
165
186
  super.dispose()
166
- await this.#page.unroute(INTERNAL_MATCH_ALL_REG_EXP)
167
- await unrouteWebSocket(this.#page, INTERNAL_MATCH_ALL_REG_EXP)
187
+ await this.args.page.unroute(INTERNAL_MATCH_ALL_REG_EXP)
188
+ await unrouteWebSocket(this.args.page, INTERNAL_MATCH_ALL_REG_EXP)
168
189
  }
169
190
 
170
191
  private getPageUrl(): string | undefined {
171
- const url = this.#page.url()
192
+ const url = this.args.page.url()
172
193
  return url !== 'about:blank' ? url : undefined
173
194
  }
174
195
  }