@msw/playwright 0.4.1 → 0.4.3

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
@@ -35,6 +35,7 @@ declare class NetworkFixture extends SetupApi<LifeCycleEventsMap> {
35
35
  });
36
36
  start(): Promise<void>;
37
37
  stop(): Promise<void>;
38
+ private getPageUrl;
38
39
  }
39
40
  //#endregion
40
41
  export { CreateNetworkFixtureArgs, NetworkFixture, createNetworkFixture };
package/build/index.js CHANGED
@@ -40,7 +40,7 @@ var NetworkFixture = class extends SetupApi {
40
40
  this.#page = args.page;
41
41
  }
42
42
  async start() {
43
- await this.#page.route(/.+/, async (route, request) => {
43
+ const httpRequestListener = async (route, request) => {
44
44
  const fetchRequest = new Request(request.url(), {
45
45
  method: request.method(),
46
46
  headers: new Headers(await request.allHeaders()),
@@ -48,7 +48,7 @@ var NetworkFixture = class extends SetupApi {
48
48
  });
49
49
  const response = await getResponse(this.handlersController.currentHandlers().filter((handler) => {
50
50
  return handler instanceof RequestHandler;
51
- }), fetchRequest);
51
+ }), fetchRequest, { baseUrl: this.getPageUrl() });
52
52
  if (response) {
53
53
  if (response.status === 0) {
54
54
  route.abort();
@@ -62,7 +62,8 @@ var NetworkFixture = class extends SetupApi {
62
62
  return;
63
63
  }
64
64
  route.continue();
65
- });
65
+ };
66
+ await this.#page.route(/.+/, httpRequestListener);
66
67
  await this.#page.routeWebSocket(/.+/, async (ws) => {
67
68
  const allWebSocketHandlers = this.handlersController.currentHandlers().filter((handler) => {
68
69
  return handler instanceof WebSocketHandler;
@@ -77,13 +78,17 @@ var NetworkFixture = class extends SetupApi {
77
78
  client,
78
79
  server,
79
80
  info: { protocols: [] }
80
- });
81
+ }, { baseUrl: this.getPageUrl() });
81
82
  });
82
83
  }
83
84
  async stop() {
84
85
  super.dispose();
85
86
  await this.#page.unroute(/.+/);
86
87
  }
88
+ getPageUrl() {
89
+ const url = this.#page.url();
90
+ return url !== "about:blank" ? url : void 0;
91
+ }
87
92
  };
88
93
  var PlaywrightWebSocketClientConnection = class {
89
94
  id;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@msw/playwright",
4
- "version": "0.4.1",
4
+ "version": "0.4.3",
5
5
  "description": "Mock Service Worker binding for Playwright",
6
6
  "main": "./build/index.js",
7
7
  "types": "./build/index.d.ts",
@@ -20,8 +20,7 @@
20
20
  "funding": "https://github.com/sponsors/mswjs",
21
21
  "homepage": "https://mswjs.io",
22
22
  "repository": {
23
- "type": "git",
24
- "url": "git+https://github.com/mswjs/playwright.git"
23
+ "url": "https://github.com/mswjs/playwright"
25
24
  },
26
25
  "author": {
27
26
  "name": "Artem Zakharchenko",
@@ -34,16 +33,17 @@
34
33
  "node": ">=20.0.0"
35
34
  },
36
35
  "peerDependencies": {
37
- "msw": "^2.10.1"
36
+ "msw": "^2.10.3"
38
37
  },
39
38
  "devDependencies": {
40
39
  "@epic-web/test-server": "^0.1.6",
41
- "@ossjs/release": "^0.8.1",
42
- "@playwright/test": "^1.52.0",
40
+ "@ossjs/release": "^0.10.1",
41
+ "@playwright/test": "^1.57.0",
43
42
  "@types/node": "^22.15.29",
44
- "msw": "^2.10.1",
43
+ "msw": "^2.10.3",
45
44
  "tsdown": "^0.12.7",
46
- "typescript": "^5.8.3"
45
+ "typescript": "^5.8.3",
46
+ "vite": "^7.3.1"
47
47
  },
48
48
  "dependencies": {
49
49
  "@mswjs/interceptors": "^0.39.2",
@@ -52,6 +52,9 @@
52
52
  "scripts": {
53
53
  "dev": "tsdown --watch",
54
54
  "test": "playwright test",
55
+ "app:dev": "vite dev",
56
+ "app:build": "vite build",
57
+ "app:start": "vite",
55
58
  "build": "tsdown",
56
59
  "release": "release publish"
57
60
  }
package/src/fixture.ts ADDED
@@ -0,0 +1,411 @@
1
+ import { invariant } from 'outvariant'
2
+ import type {
3
+ Page,
4
+ PlaywrightTestArgs,
5
+ PlaywrightWorkerArgs,
6
+ Request,
7
+ Route,
8
+ TestFixture,
9
+ WebSocketRoute,
10
+ } from '@playwright/test'
11
+ import {
12
+ type LifeCycleEventsMap,
13
+ SetupApi,
14
+ RequestHandler,
15
+ WebSocketHandler,
16
+ getResponse,
17
+ } from 'msw'
18
+ import {
19
+ type WebSocketClientEventMap,
20
+ type WebSocketData,
21
+ type WebSocketServerEventMap,
22
+ CancelableMessageEvent,
23
+ CancelableCloseEvent,
24
+ WebSocketClientConnectionProtocol,
25
+ WebSocketServerConnectionProtocol,
26
+ } from '@mswjs/interceptors/WebSocket'
27
+
28
+ export interface CreateNetworkFixtureArgs {
29
+ initialHandlers: Array<RequestHandler | WebSocketHandler>
30
+ }
31
+
32
+ /**
33
+ * Creates a fixture that controls the network in your tests.
34
+ *
35
+ * @note The returned fixture already has the `auto` option set to `true`.
36
+ *
37
+ * **Usage**
38
+ * ```ts
39
+ * import { test as testBase } from '@playwright/test'
40
+ * import { createNetworkFixture, type WorkerFixture } from '@msw/playwright'
41
+ *
42
+ * interface Fixtures {
43
+ * network: WorkerFixture
44
+ * }
45
+ *
46
+ * export const test = testBase.extend<Fixtures>({
47
+ * network: createNetworkFixture()
48
+ * })
49
+ * ```
50
+ */
51
+ export function createNetworkFixture(
52
+ args?: CreateNetworkFixtureArgs,
53
+ /** @todo `onUnhandledRequest`? */
54
+ ): [
55
+ TestFixture<NetworkFixture, PlaywrightTestArgs & PlaywrightWorkerArgs>,
56
+ { auto: boolean },
57
+ ] {
58
+ return [
59
+ async ({ page }, use) => {
60
+ const worker = new NetworkFixture({
61
+ page,
62
+ initialHandlers: args?.initialHandlers || [],
63
+ })
64
+
65
+ await worker.start()
66
+ await use(worker)
67
+ await worker.stop()
68
+ },
69
+ { auto: true },
70
+ ]
71
+ }
72
+
73
+ /**
74
+ * @note Use a match-all RegExp with an optional group as the predicate
75
+ * for the `page.route()`/`page.unroute()` calls. Playwright treats given RegExp
76
+ * as the handler ID, which allows us to remove only those handlers introduces by us
77
+ * without carrying the reference to the handler function around.
78
+ */
79
+ export const INTERNAL_MATCH_ALL_REG_EXP = /.+(__MSW_PLAYWRIGHT_PREDICATE__)?/
80
+
81
+ export class NetworkFixture extends SetupApi<LifeCycleEventsMap> {
82
+ #page: Page
83
+
84
+ constructor(args: {
85
+ page: Page
86
+ initialHandlers: Array<RequestHandler | WebSocketHandler>
87
+ }) {
88
+ super(...args.initialHandlers)
89
+ this.#page = args.page
90
+ }
91
+
92
+ public async start(): Promise<void> {
93
+ // Handle HTTP requests.
94
+ await this.#page.route(
95
+ INTERNAL_MATCH_ALL_REG_EXP,
96
+ async (route: Route, request: Request) => {
97
+ const fetchRequest = new Request(request.url(), {
98
+ method: request.method(),
99
+ headers: new Headers(await request.allHeaders()),
100
+ body: request.postDataBuffer(),
101
+ })
102
+
103
+ const response = await getResponse(
104
+ this.handlersController.currentHandlers().filter((handler) => {
105
+ return handler instanceof RequestHandler
106
+ }),
107
+ fetchRequest,
108
+ {
109
+ baseUrl: this.getPageUrl(),
110
+ },
111
+ )
112
+
113
+ if (response) {
114
+ if (response.status === 0) {
115
+ route.abort()
116
+ return
117
+ }
118
+
119
+ route.fulfill({
120
+ status: response.status,
121
+ headers: Object.fromEntries(response.headers),
122
+ body: response.body
123
+ ? Buffer.from(await response.arrayBuffer())
124
+ : undefined,
125
+ })
126
+ return
127
+ }
128
+
129
+ route.continue()
130
+ },
131
+ )
132
+
133
+ // 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
+ }
145
+
146
+ const client = new PlaywrightWebSocketClientConnection(ws)
147
+ const server = new PlaywrightWebSocketServerConnection(ws)
148
+
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
+ })
162
+ }
163
+
164
+ public async stop(): Promise<void> {
165
+ super.dispose()
166
+ await this.#page.unroute(INTERNAL_MATCH_ALL_REG_EXP)
167
+ await unrouteWebSocket(this.#page, INTERNAL_MATCH_ALL_REG_EXP)
168
+ }
169
+
170
+ private getPageUrl(): string | undefined {
171
+ const url = this.#page.url()
172
+ return url !== 'about:blank' ? url : undefined
173
+ }
174
+ }
175
+
176
+ class PlaywrightWebSocketClientConnection
177
+ implements WebSocketClientConnectionProtocol
178
+ {
179
+ public id: string
180
+ public url: URL
181
+
182
+ constructor(protected readonly ws: WebSocketRoute) {
183
+ this.id = crypto.randomUUID()
184
+ this.url = new URL(ws.url())
185
+ }
186
+
187
+ public send(data: WebSocketData): void {
188
+ if (data instanceof Blob) {
189
+ /**
190
+ * @note Playwright does not support sending Blob data.
191
+ * Read the blob as buffer, then send the buffer instead.
192
+ */
193
+ data.bytes().then((bytes) => {
194
+ this.ws.send(Buffer.from(bytes))
195
+ })
196
+ return
197
+ }
198
+
199
+ if (typeof data === 'string') {
200
+ this.ws.send(data)
201
+ return
202
+ }
203
+
204
+ this.ws.send(
205
+ /**
206
+ * @note Forcefully cast all data to Buffer because Playwright
207
+ * has trouble digesting ArrayBuffer and Blob directly.
208
+ */
209
+ Buffer.from(
210
+ /**
211
+ * @note Playwright type definitions are tailored to Node.js
212
+ * while MSW describes all data types that can be sent over
213
+ * the WebSocket protocol, like ArrayBuffer and Blob.
214
+ */
215
+ data as any,
216
+ ),
217
+ )
218
+ }
219
+
220
+ public close(code?: number, reason?: string): void {
221
+ const resolvedCode = code ?? 1000
222
+ this.ws.close({ code: resolvedCode, reason })
223
+ }
224
+
225
+ public addEventListener<EventType extends keyof WebSocketClientEventMap>(
226
+ type: EventType,
227
+ listener: (
228
+ this: WebSocket,
229
+ event: WebSocketClientEventMap[EventType],
230
+ ) => void,
231
+ options?: AddEventListenerOptions | boolean,
232
+ ): void {
233
+ /**
234
+ * @note Playwright does not expose the actual WebSocket reference.
235
+ */
236
+ const target = {} as WebSocket
237
+
238
+ switch (type) {
239
+ case 'message': {
240
+ this.ws.onMessage((data) => {
241
+ listener.call(
242
+ target,
243
+ new CancelableMessageEvent('message', {
244
+ data,
245
+ }) as any,
246
+ )
247
+ })
248
+ break
249
+ }
250
+
251
+ case 'close': {
252
+ this.ws.onClose((code, reason) => {
253
+ listener.call(
254
+ target,
255
+ new CancelableCloseEvent('close', {
256
+ code,
257
+ reason,
258
+ }) as any,
259
+ )
260
+ })
261
+ break
262
+ }
263
+ }
264
+ }
265
+
266
+ public removeEventListener<EventType extends keyof WebSocketClientEventMap>(
267
+ event: EventType,
268
+ listener: (
269
+ this: WebSocket,
270
+ event: WebSocketClientEventMap[EventType],
271
+ ) => void,
272
+ options?: EventListenerOptions | boolean,
273
+ ): void {
274
+ console.warn(
275
+ '@msw/playwright: WebSocketRoute does not support removing event listeners',
276
+ )
277
+ }
278
+ }
279
+
280
+ class PlaywrightWebSocketServerConnection
281
+ implements WebSocketServerConnectionProtocol
282
+ {
283
+ #server?: WebSocketRoute
284
+ #bufferedEvents: Array<
285
+ Parameters<WebSocketServerConnectionProtocol['addEventListener']>
286
+ >
287
+ #bufferedData: Array<WebSocketData>
288
+
289
+ constructor(protected readonly ws: WebSocketRoute) {
290
+ this.#bufferedEvents = []
291
+ this.#bufferedData = []
292
+ }
293
+
294
+ public connect(): void {
295
+ this.#server = this.ws.connectToServer()
296
+
297
+ /**
298
+ * @note Playwright does not support event buffering.
299
+ * Manually add event listeners that might have been registered
300
+ * before `connect()` was called.
301
+ */
302
+ for (const [type, listener, options] of this.#bufferedEvents) {
303
+ this.addEventListener(type, listener, options)
304
+ }
305
+ this.#bufferedEvents.length = 0
306
+
307
+ // Same for the buffered data.
308
+ for (const data of this.#bufferedData) {
309
+ this.send(data)
310
+ }
311
+ this.#bufferedData.length = 0
312
+ }
313
+
314
+ public send(data: WebSocketData): void {
315
+ if (this.#server == null) {
316
+ this.#bufferedData.push(data)
317
+ return
318
+ }
319
+
320
+ this.#server.send(data as any)
321
+ }
322
+
323
+ public close(code?: number, reason?: string): void {
324
+ invariant(
325
+ this.#server,
326
+ 'Failed to close connection to the actual WebSocket server: connection not established. Did you forget to call `connect()`?',
327
+ )
328
+
329
+ this.#server.close({ code, reason })
330
+ }
331
+
332
+ public addEventListener<EventType extends keyof WebSocketServerEventMap>(
333
+ type: EventType,
334
+ listener: (
335
+ this: WebSocket,
336
+ event: WebSocketServerEventMap[EventType],
337
+ ) => void,
338
+ options?: AddEventListenerOptions | boolean,
339
+ ): void {
340
+ if (this.#server == null) {
341
+ this.#bufferedEvents.push([type, listener as any, options])
342
+ return
343
+ }
344
+
345
+ const target = {} as WebSocket
346
+ switch (type) {
347
+ case 'message': {
348
+ this.#server.onMessage((data) => {
349
+ listener.call(
350
+ target,
351
+ new CancelableMessageEvent('message', { data }) as any,
352
+ )
353
+ })
354
+ break
355
+ }
356
+
357
+ case 'close': {
358
+ this.#server.onClose((code, reason) => {
359
+ listener.call(
360
+ target,
361
+ new CancelableCloseEvent('close', { code, reason }) as any,
362
+ )
363
+ })
364
+ break
365
+ }
366
+ }
367
+ }
368
+
369
+ public removeEventListener<EventType extends keyof WebSocketServerEventMap>(
370
+ type: EventType,
371
+ listener: (
372
+ this: WebSocket,
373
+ event: WebSocketServerEventMap[EventType],
374
+ ) => void,
375
+ options?: EventListenerOptions | boolean,
376
+ ): void {
377
+ console.warn(
378
+ '@msw/playwright: WebSocketRoute does not support removing event listeners',
379
+ )
380
+ }
381
+ }
382
+
383
+ interface InternalWebSocketRoute {
384
+ url: Parameters<Page['routeWebSocket']>[0]
385
+ handler: Parameters<Page['routeWebSocket']>[1]
386
+ }
387
+
388
+ /**
389
+ * Custom implementation of the missing `page.unrouteWebSocket()` to remove
390
+ * WebSocket route handlers from the page. Loosely inspired by `page.unroute()`.
391
+ */
392
+ async function unrouteWebSocket(
393
+ page: Page,
394
+ url: InternalWebSocketRoute['url'],
395
+ handler?: InternalWebSocketRoute['handler'],
396
+ ): Promise<void> {
397
+ if (!('_webSocketRoutes' in page && Array.isArray(page._webSocketRoutes))) {
398
+ return
399
+ }
400
+
401
+ for (let i = page._webSocketRoutes.length - 1; i >= 0; i--) {
402
+ const route = page._webSocketRoutes[i] as InternalWebSocketRoute
403
+
404
+ if (
405
+ route.url === url &&
406
+ (handler != null ? route.handler === handler : true)
407
+ ) {
408
+ page._webSocketRoutes.splice(i, 1)
409
+ }
410
+ }
411
+ }
package/src/index.ts CHANGED
@@ -1,354 +1,5 @@
1
- import { invariant } from 'outvariant'
2
- import type {
3
- Page,
4
- PlaywrightTestArgs,
5
- PlaywrightWorkerArgs,
6
- TestFixture,
7
- WebSocketRoute,
8
- } from '@playwright/test'
9
- import {
10
- type LifeCycleEventsMap,
11
- SetupApi,
12
- RequestHandler,
13
- WebSocketHandler,
14
- getResponse,
15
- } from 'msw'
16
- import {
17
- type WebSocketClientEventMap,
18
- type WebSocketData,
19
- type WebSocketServerEventMap,
20
- CancelableMessageEvent,
21
- CancelableCloseEvent,
22
- WebSocketClientConnectionProtocol,
23
- WebSocketServerConnectionProtocol,
24
- } from '@mswjs/interceptors/WebSocket'
25
-
26
- export interface CreateNetworkFixtureArgs {
27
- initialHandlers: Array<RequestHandler | WebSocketHandler>
28
- }
29
-
30
- /**
31
- * Creates a fixture that controls the network in your tests.
32
- *
33
- * @note The returned fixture already has the `auto` option set to `true`.
34
- *
35
- * **Usage**
36
- * ```ts
37
- * import { test as testBase } from '@playwright/test'
38
- * import { createNetworkFixture, type WorkerFixture } from '@msw/playwright'
39
- *
40
- * interface Fixtures {
41
- * network: WorkerFixture
42
- * }
43
- *
44
- * export const test = testBase.extend<Fixtures>({
45
- * network: createNetworkFixture()
46
- * })
47
- * ```
48
- */
49
- export function createNetworkFixture(
50
- args?: CreateNetworkFixtureArgs,
51
- /** @todo `onUnhandledRequest`? */
52
- ): [
53
- TestFixture<NetworkFixture, PlaywrightTestArgs & PlaywrightWorkerArgs>,
54
- { auto: boolean },
55
- ] {
56
- return [
57
- async ({ page }, use) => {
58
- const worker = new NetworkFixture({
59
- page,
60
- initialHandlers: args?.initialHandlers || [],
61
- })
62
-
63
- await worker.start()
64
- await use(worker)
65
- await worker.stop()
66
- },
67
- { auto: true },
68
- ]
69
- }
70
-
71
- export class NetworkFixture extends SetupApi<LifeCycleEventsMap> {
72
- #page: Page
73
-
74
- constructor(args: {
75
- page: Page
76
- initialHandlers: Array<RequestHandler | WebSocketHandler>
77
- }) {
78
- super(...args.initialHandlers)
79
- this.#page = args.page
80
- }
81
-
82
- public async start() {
83
- // Handle HTTP requests.
84
- await this.#page.route(/.+/, async (route, request) => {
85
- const fetchRequest = new Request(request.url(), {
86
- method: request.method(),
87
- headers: new Headers(await request.allHeaders()),
88
- body: request.postDataBuffer(),
89
- })
90
-
91
- const response = await getResponse(
92
- this.handlersController.currentHandlers().filter((handler) => {
93
- return handler instanceof RequestHandler
94
- }),
95
- fetchRequest,
96
- )
97
-
98
- if (response) {
99
- if (response.status === 0) {
100
- route.abort()
101
- return
102
- }
103
-
104
- route.fulfill({
105
- status: response.status,
106
- headers: Object.fromEntries(response.headers),
107
- body: response.body
108
- ? Buffer.from(await response.arrayBuffer())
109
- : undefined,
110
- })
111
- return
112
- }
113
-
114
- route.continue()
115
- })
116
-
117
- // Handle WebSocket connections.
118
- await this.#page.routeWebSocket(/.+/, async (ws) => {
119
- const allWebSocketHandlers = this.handlersController
120
- .currentHandlers()
121
- .filter((handler) => {
122
- return handler instanceof WebSocketHandler
123
- })
124
-
125
- if (allWebSocketHandlers.length === 0) {
126
- ws.connectToServer()
127
- return
128
- }
129
-
130
- const client = new PlaywrightWebSocketClientConnection(ws)
131
- const server = new PlaywrightWebSocketServerConnection(ws)
132
-
133
- for (const handler of allWebSocketHandlers) {
134
- await handler.run({
135
- client,
136
- server,
137
- info: { protocols: [] },
138
- })
139
- }
140
- })
141
- }
142
-
143
- public async stop() {
144
- super.dispose()
145
- await this.#page.unroute(/.+/)
146
- }
147
- }
148
-
149
- class PlaywrightWebSocketClientConnection
150
- implements WebSocketClientConnectionProtocol
151
- {
152
- public id: string
153
- public url: URL
154
-
155
- constructor(protected readonly ws: WebSocketRoute) {
156
- this.id = crypto.randomUUID()
157
- this.url = new URL(ws.url())
158
- }
159
-
160
- public send(data: WebSocketData): void {
161
- if (data instanceof Blob) {
162
- /**
163
- * @note Playwright does not support sending Blob data.
164
- * Read the blob as buffer, then send the buffer instead.
165
- */
166
- data.bytes().then((bytes) => {
167
- this.ws.send(Buffer.from(bytes))
168
- })
169
- return
170
- }
171
-
172
- if (typeof data === 'string') {
173
- this.ws.send(data)
174
- return
175
- }
176
-
177
- this.ws.send(
178
- /**
179
- * @note Forcefully cast all data to Buffer because Playwright
180
- * has trouble digesting ArrayBuffer and Blob directly.
181
- */
182
- Buffer.from(
183
- /**
184
- * @note Playwright type definitions are tailored to Node.js
185
- * while MSW describes all data types that can be sent over
186
- * the WebSocket protocol, like ArrayBuffer and Blob.
187
- */
188
- data as any,
189
- ),
190
- )
191
- }
192
-
193
- public close(code?: number, reason?: string): void {
194
- const resolvedCode = code ?? 1000
195
- this.ws.close({ code: resolvedCode, reason })
196
- }
197
-
198
- public addEventListener<EventType extends keyof WebSocketClientEventMap>(
199
- type: EventType,
200
- listener: (
201
- this: WebSocket,
202
- event: WebSocketClientEventMap[EventType],
203
- ) => void,
204
- options?: AddEventListenerOptions | boolean,
205
- ): void {
206
- /**
207
- * @note Playwright does not expose the actual WebSocket reference.
208
- */
209
- const target = {} as WebSocket
210
-
211
- switch (type) {
212
- case 'message': {
213
- this.ws.onMessage((data) => {
214
- listener.call(
215
- target,
216
- new CancelableMessageEvent('message', {
217
- data,
218
- }) as any,
219
- )
220
- })
221
- break
222
- }
223
-
224
- case 'close': {
225
- this.ws.onClose((code, reason) => {
226
- listener.call(
227
- target,
228
- new CancelableCloseEvent('close', {
229
- code,
230
- reason,
231
- }) as any,
232
- )
233
- })
234
- break
235
- }
236
- }
237
- }
238
-
239
- public removeEventListener<EventType extends keyof WebSocketClientEventMap>(
240
- event: EventType,
241
- listener: (
242
- this: WebSocket,
243
- event: WebSocketClientEventMap[EventType],
244
- ) => void,
245
- options?: EventListenerOptions | boolean,
246
- ): void {
247
- console.warn(
248
- '@msw/playwright: WebSocketRoute does not support removing event listeners',
249
- )
250
- }
251
- }
252
-
253
- class PlaywrightWebSocketServerConnection
254
- implements WebSocketServerConnectionProtocol
255
- {
256
- #server?: WebSocketRoute
257
- #bufferedEvents: Array<
258
- Parameters<WebSocketServerConnectionProtocol['addEventListener']>
259
- >
260
- #bufferedData: Array<WebSocketData>
261
-
262
- constructor(protected readonly ws: WebSocketRoute) {
263
- this.#bufferedEvents = []
264
- this.#bufferedData = []
265
- }
266
-
267
- public connect(): void {
268
- this.#server = this.ws.connectToServer()
269
-
270
- /**
271
- * @note Playwright does not support event buffering.
272
- * Manually add event listeners that might have been registered
273
- * before `connect()` was called.
274
- */
275
- for (const [type, listener, options] of this.#bufferedEvents) {
276
- this.addEventListener(type, listener, options)
277
- }
278
- this.#bufferedEvents.length = 0
279
-
280
- // Same for the buffered data.
281
- for (const data of this.#bufferedData) {
282
- this.send(data)
283
- }
284
- this.#bufferedData.length = 0
285
- }
286
-
287
- public send(data: WebSocketData): void {
288
- if (this.#server == null) {
289
- this.#bufferedData.push(data)
290
- return
291
- }
292
-
293
- this.#server.send(data as any)
294
- }
295
-
296
- public close(code?: number, reason?: string): void {
297
- invariant(
298
- this.#server,
299
- 'Failed to close connection to the actual WebSocket server: connection not established. Did you forget to call `connect()`?',
300
- )
301
-
302
- this.#server.close({ code, reason })
303
- }
304
-
305
- public addEventListener<EventType extends keyof WebSocketServerEventMap>(
306
- type: EventType,
307
- listener: (
308
- this: WebSocket,
309
- event: WebSocketServerEventMap[EventType],
310
- ) => void,
311
- options?: AddEventListenerOptions | boolean,
312
- ): void {
313
- if (this.#server == null) {
314
- this.#bufferedEvents.push([type, listener as any, options])
315
- return
316
- }
317
-
318
- const target = {} as WebSocket
319
- switch (type) {
320
- case 'message': {
321
- this.#server.onMessage((data) => {
322
- listener.call(
323
- target,
324
- new CancelableMessageEvent('message', { data }) as any,
325
- )
326
- })
327
- break
328
- }
329
-
330
- case 'close': {
331
- this.#server.onClose((code, reason) => {
332
- listener.call(
333
- target,
334
- new CancelableCloseEvent('close', { code, reason }) as any,
335
- )
336
- })
337
- break
338
- }
339
- }
340
- }
341
-
342
- public removeEventListener<EventType extends keyof WebSocketServerEventMap>(
343
- type: EventType,
344
- listener: (
345
- this: WebSocket,
346
- event: WebSocketServerEventMap[EventType],
347
- ) => void,
348
- options?: EventListenerOptions | boolean,
349
- ): void {
350
- console.warn(
351
- '@msw/playwright: WebSocketRoute does not support removing event listeners',
352
- )
353
- }
354
- }
1
+ export {
2
+ type CreateNetworkFixtureArgs,
3
+ type NetworkFixture,
4
+ createNetworkFixture,
5
+ } from './fixture.js'