@msw/playwright 0.4.5 → 0.6.0
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/README.md +22 -5
- package/build/index.d.ts +19 -43
- package/build/index.js +48 -63
- package/package.json +5 -5
- package/src/fixture.ts +99 -91
- package/src/index.ts +2 -2
package/README.md
CHANGED
|
@@ -37,11 +37,24 @@ interface Fixtures {
|
|
|
37
37
|
network: NetworkFixture
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
//
|
|
42
|
-
|
|
43
|
-
|
|
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
|
+
],
|
|
45
58
|
})
|
|
46
59
|
```
|
|
47
60
|
|
|
@@ -66,6 +79,10 @@ test('displays the user dashboard', async ({ network, page }) => {
|
|
|
66
79
|
})
|
|
67
80
|
```
|
|
68
81
|
|
|
82
|
+
## Limitations
|
|
83
|
+
|
|
84
|
+
- Since `context.routeWebSocket()` provides no means of knowing which page triggered a WebSocket connection, relative WebSocket URLs in `ws.link(url)` will be resolved against the _latest_ created page in the browser context.
|
|
85
|
+
|
|
69
86
|
## Comparison
|
|
70
87
|
|
|
71
88
|
### `playwright-msw`
|
package/build/index.d.ts
CHANGED
|
@@ -1,54 +1,30 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { AnyHandler, LifeCycleEventsMap, SetupApi, UnhandledRequestStrategy } from "msw";
|
|
2
|
+
import { BrowserContext } from "@playwright/test";
|
|
3
3
|
|
|
4
4
|
//#region src/fixture.d.ts
|
|
5
|
-
interface
|
|
6
|
-
|
|
5
|
+
interface NetworkFixtureOptions {
|
|
6
|
+
context: BrowserContext;
|
|
7
|
+
handlers?: Array<AnyHandler>;
|
|
7
8
|
onUnhandledRequest?: UnhandledRequestStrategy;
|
|
9
|
+
/**
|
|
10
|
+
* Skip common asset requests (e.g. `*.html`, `*.css`, `*.js`, etc).
|
|
11
|
+
* This improves performance for certian projects.
|
|
12
|
+
* @default true
|
|
13
|
+
*
|
|
14
|
+
* @see https://mswjs.io/docs/api/is-common-asset-request
|
|
15
|
+
*/
|
|
16
|
+
skipAssetRequests?: boolean;
|
|
8
17
|
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
* **Usage**
|
|
15
|
-
* ```ts
|
|
16
|
-
* import { test as testBase } from '@playwright/test'
|
|
17
|
-
* import { createNetworkFixture, type WorkerFixture } from '@msw/playwright'
|
|
18
|
-
*
|
|
19
|
-
* interface Fixtures {
|
|
20
|
-
* network: WorkerFixture
|
|
21
|
-
* }
|
|
22
|
-
*
|
|
23
|
-
* export const test = testBase.extend<Fixtures>({
|
|
24
|
-
* network: createNetworkFixture()
|
|
25
|
-
* })
|
|
26
|
-
* ```
|
|
27
|
-
*/
|
|
28
|
-
declare function createNetworkFixture(args?: CreateNetworkFixtureArgs): [TestFixture<NetworkFixture, PlaywrightTestArgs & PlaywrightWorkerArgs>, {
|
|
29
|
-
auto: boolean;
|
|
30
|
-
}];
|
|
18
|
+
type NetworkFixture = Omit<SetupApi<LifeCycleEventsMap>, 'dispose'> & {
|
|
19
|
+
enable: () => Promise<void>;
|
|
20
|
+
disable: () => Promise<void>;
|
|
21
|
+
};
|
|
22
|
+
declare function defineNetworkFixture(options: NetworkFixtureOptions): NetworkFixture;
|
|
31
23
|
/**
|
|
32
24
|
* @note Use a match-all RegExp with an optional group as the predicate
|
|
33
25
|
* for the `page.route()`/`page.unroute()` calls. Playwright treats given RegExp
|
|
34
26
|
* as the handler ID, which allows us to remove only those handlers introduces by us
|
|
35
27
|
* without carrying the reference to the handler function around.
|
|
36
28
|
*/
|
|
37
|
-
|
|
38
|
-
declare class NetworkFixture extends SetupApi<LifeCycleEventsMap> {
|
|
39
|
-
protected args: {
|
|
40
|
-
page: Page;
|
|
41
|
-
initialHandlers: Array<RequestHandler | WebSocketHandler>;
|
|
42
|
-
onUnhandledRequest?: UnhandledRequestStrategy;
|
|
43
|
-
};
|
|
44
|
-
constructor(args: {
|
|
45
|
-
page: Page;
|
|
46
|
-
initialHandlers: Array<RequestHandler | WebSocketHandler>;
|
|
47
|
-
onUnhandledRequest?: UnhandledRequestStrategy;
|
|
48
|
-
});
|
|
49
|
-
start(): Promise<void>;
|
|
50
|
-
stop(): Promise<void>;
|
|
51
|
-
private getPageUrl;
|
|
52
|
-
}
|
|
53
29
|
//#endregion
|
|
54
|
-
export {
|
|
30
|
+
export { NetworkFixture, NetworkFixtureOptions, defineNetworkFixture };
|
package/build/index.js
CHANGED
|
@@ -1,38 +1,15 @@
|
|
|
1
1
|
import { invariant } from "outvariant";
|
|
2
|
-
import { RequestHandler, SetupApi, WebSocketHandler, handleRequest } from "msw";
|
|
2
|
+
import { RequestHandler, SetupApi, WebSocketHandler, handleRequest, isCommonAssetRequest } from "msw";
|
|
3
3
|
import { CancelableCloseEvent, CancelableMessageEvent } from "@mswjs/interceptors/WebSocket";
|
|
4
4
|
|
|
5
5
|
//#region src/fixture.ts
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
* import { test as testBase } from '@playwright/test'
|
|
14
|
-
* import { createNetworkFixture, type WorkerFixture } from '@msw/playwright'
|
|
15
|
-
*
|
|
16
|
-
* interface Fixtures {
|
|
17
|
-
* network: WorkerFixture
|
|
18
|
-
* }
|
|
19
|
-
*
|
|
20
|
-
* export const test = testBase.extend<Fixtures>({
|
|
21
|
-
* network: createNetworkFixture()
|
|
22
|
-
* })
|
|
23
|
-
* ```
|
|
24
|
-
*/
|
|
25
|
-
function createNetworkFixture(args) {
|
|
26
|
-
return [async ({ page }, use) => {
|
|
27
|
-
const worker = new NetworkFixture({
|
|
28
|
-
page,
|
|
29
|
-
initialHandlers: args?.initialHandlers || [],
|
|
30
|
-
onUnhandledRequest: args?.onUnhandledRequest
|
|
31
|
-
});
|
|
32
|
-
await worker.start();
|
|
33
|
-
await use(worker);
|
|
34
|
-
await worker.stop();
|
|
35
|
-
}, { auto: true }];
|
|
6
|
+
function defineNetworkFixture(options) {
|
|
7
|
+
return new SetupPlaywrightApi({
|
|
8
|
+
context: options.context,
|
|
9
|
+
initialHandlers: options.handlers || [],
|
|
10
|
+
onUnhandledRequest: options.onUnhandledRequest,
|
|
11
|
+
skipAssetRequests: options.skipAssetRequests ?? true
|
|
12
|
+
});
|
|
36
13
|
}
|
|
37
14
|
/**
|
|
38
15
|
* @note Use a match-all RegExp with an optional group as the predicate
|
|
@@ -41,68 +18,76 @@ function createNetworkFixture(args) {
|
|
|
41
18
|
* without carrying the reference to the handler function around.
|
|
42
19
|
*/
|
|
43
20
|
const INTERNAL_MATCH_ALL_REG_EXP = /.+(__MSW_PLAYWRIGHT_PREDICATE__)?/;
|
|
44
|
-
var
|
|
45
|
-
constructor(
|
|
46
|
-
super(...
|
|
47
|
-
this.
|
|
21
|
+
var SetupPlaywrightApi = class extends SetupApi {
|
|
22
|
+
constructor(options) {
|
|
23
|
+
super(...options.initialHandlers);
|
|
24
|
+
this.options = options;
|
|
48
25
|
}
|
|
49
|
-
async
|
|
50
|
-
await this.
|
|
26
|
+
async enable() {
|
|
27
|
+
await this.options.context.route(INTERNAL_MATCH_ALL_REG_EXP, async (route, request) => {
|
|
51
28
|
const fetchRequest = new Request(request.url(), {
|
|
52
29
|
method: request.method(),
|
|
53
30
|
headers: new Headers(await request.allHeaders()),
|
|
54
31
|
body: request.postDataBuffer()
|
|
55
32
|
});
|
|
33
|
+
/**
|
|
34
|
+
* @note Skip common asset requests (default).
|
|
35
|
+
* Playwright seems to experience performance degradation when routing all
|
|
36
|
+
* requests through the matching logic below.
|
|
37
|
+
* @see https://github.com/mswjs/playwright/issues/13
|
|
38
|
+
*/
|
|
39
|
+
if (this.options.skipAssetRequests && isCommonAssetRequest(fetchRequest)) return route.continue();
|
|
56
40
|
const handlers = this.handlersController.currentHandlers().filter((handler) => {
|
|
57
41
|
return handler instanceof RequestHandler;
|
|
58
42
|
});
|
|
43
|
+
const baseUrl = request.headers().referer ? new URL(request.headers().referer).origin : void 0;
|
|
59
44
|
/**
|
|
60
45
|
* @note Use `handleRequest` instead of `getResponse` so we can pass
|
|
61
46
|
* the `onUnhandledRequest` option as-is and benefit from MSW's default behaviors.
|
|
62
47
|
*/
|
|
63
|
-
const response = await handleRequest(fetchRequest, crypto.randomUUID(), handlers, { onUnhandledRequest: this.
|
|
48
|
+
const response = await handleRequest(fetchRequest, crypto.randomUUID(), handlers, { onUnhandledRequest: this.options.onUnhandledRequest || "bypass" }, this.emitter, { resolutionContext: {
|
|
64
49
|
quiet: true,
|
|
65
|
-
baseUrl
|
|
50
|
+
baseUrl
|
|
66
51
|
} });
|
|
67
52
|
if (response) {
|
|
68
|
-
if (response.status === 0)
|
|
69
|
-
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
route.fulfill({
|
|
53
|
+
if (response.status === 0) return route.abort();
|
|
54
|
+
return route.fulfill({
|
|
73
55
|
status: response.status,
|
|
74
56
|
headers: Object.fromEntries(response.headers),
|
|
75
57
|
body: response.body ? Buffer.from(await response.arrayBuffer()) : void 0
|
|
76
58
|
});
|
|
77
|
-
return;
|
|
78
59
|
}
|
|
79
|
-
route.continue();
|
|
60
|
+
return route.continue();
|
|
80
61
|
});
|
|
81
|
-
await this.
|
|
62
|
+
await this.options.context.routeWebSocket(INTERNAL_MATCH_ALL_REG_EXP, async (route) => {
|
|
82
63
|
const allWebSocketHandlers = this.handlersController.currentHandlers().filter((handler) => {
|
|
83
64
|
return handler instanceof WebSocketHandler;
|
|
84
65
|
});
|
|
85
66
|
if (allWebSocketHandlers.length === 0) {
|
|
86
|
-
|
|
67
|
+
route.connectToServer();
|
|
87
68
|
return;
|
|
88
69
|
}
|
|
89
|
-
const client = new PlaywrightWebSocketClientConnection(
|
|
90
|
-
const server = new PlaywrightWebSocketServerConnection(
|
|
70
|
+
const client = new PlaywrightWebSocketClientConnection(route);
|
|
71
|
+
const server = new PlaywrightWebSocketServerConnection(route);
|
|
72
|
+
const pages = this.options.context.pages();
|
|
73
|
+
const lastPage = pages[pages.length - 1];
|
|
74
|
+
const baseUrl = lastPage ? this.getPageUrl(lastPage) : void 0;
|
|
91
75
|
for (const handler of allWebSocketHandlers) await handler.run({
|
|
92
76
|
client,
|
|
93
77
|
server,
|
|
94
78
|
info: { protocols: [] }
|
|
95
|
-
}, { baseUrl
|
|
79
|
+
}, { baseUrl });
|
|
96
80
|
});
|
|
97
81
|
}
|
|
98
|
-
async
|
|
82
|
+
async disable() {
|
|
99
83
|
super.dispose();
|
|
100
|
-
await this.
|
|
101
|
-
await unrouteWebSocket(this.
|
|
84
|
+
await this.options.context.unroute(INTERNAL_MATCH_ALL_REG_EXP);
|
|
85
|
+
await unrouteWebSocket(this.options.context, INTERNAL_MATCH_ALL_REG_EXP);
|
|
102
86
|
}
|
|
103
|
-
getPageUrl() {
|
|
104
|
-
const url =
|
|
105
|
-
|
|
87
|
+
getPageUrl(page) {
|
|
88
|
+
const url = page.url();
|
|
89
|
+
if (url === "about:blank") return;
|
|
90
|
+
return decodeURI(new URL(encodeURI(url)).origin);
|
|
106
91
|
}
|
|
107
92
|
};
|
|
108
93
|
var PlaywrightWebSocketClientConnection = class {
|
|
@@ -248,13 +233,13 @@ var PlaywrightWebSocketServerConnection = class {
|
|
|
248
233
|
* Custom implementation of the missing `page.unrouteWebSocket()` to remove
|
|
249
234
|
* WebSocket route handlers from the page. Loosely inspired by `page.unroute()`.
|
|
250
235
|
*/
|
|
251
|
-
async function unrouteWebSocket(
|
|
252
|
-
if (!("_webSocketRoutes" in
|
|
253
|
-
for (let i =
|
|
254
|
-
const route =
|
|
255
|
-
if (route.url === url && (handler != null ? route.handler === handler : true))
|
|
236
|
+
async function unrouteWebSocket(target, url, handler) {
|
|
237
|
+
if (!("_webSocketRoutes" in target && Array.isArray(target._webSocketRoutes))) return;
|
|
238
|
+
for (let i = target._webSocketRoutes.length - 1; i >= 0; i--) {
|
|
239
|
+
const route = target._webSocketRoutes[i];
|
|
240
|
+
if (route.url === url && (handler != null ? route.handler === handler : true)) target._webSocketRoutes.splice(i, 1);
|
|
256
241
|
}
|
|
257
242
|
}
|
|
258
243
|
|
|
259
244
|
//#endregion
|
|
260
|
-
export {
|
|
245
|
+
export { defineNetworkFixture };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@msw/playwright",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.6.0",
|
|
5
5
|
"description": "Mock Service Worker binding for Playwright",
|
|
6
6
|
"main": "./build/index.js",
|
|
7
7
|
"types": "./build/index.d.ts",
|
|
@@ -33,22 +33,22 @@
|
|
|
33
33
|
"node": ">=20.0.0"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
|
-
"msw": "^2.10
|
|
36
|
+
"msw": "^2.12.10"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@epic-web/test-server": "^0.1.6",
|
|
40
40
|
"@ossjs/release": "^0.10.1",
|
|
41
|
-
"@playwright/test": "^1.
|
|
41
|
+
"@playwright/test": "^1.58.1",
|
|
42
42
|
"@types/node": "^22.15.29",
|
|
43
43
|
"@types/sinon": "^21.0.0",
|
|
44
|
-
"msw": "^2.12.
|
|
44
|
+
"msw": "^2.12.10",
|
|
45
45
|
"sinon": "^21.0.1",
|
|
46
46
|
"tsdown": "^0.12.7",
|
|
47
47
|
"typescript": "^5.9.3",
|
|
48
48
|
"vite": "^7.3.1"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@mswjs/interceptors": "^0.
|
|
51
|
+
"@mswjs/interceptors": "^0.41.2",
|
|
52
52
|
"outvariant": "^1.4.3"
|
|
53
53
|
},
|
|
54
54
|
"scripts": {
|
package/src/fixture.ts
CHANGED
|
@@ -1,20 +1,19 @@
|
|
|
1
1
|
import { invariant } from 'outvariant'
|
|
2
2
|
import type {
|
|
3
|
+
BrowserContext,
|
|
3
4
|
Page,
|
|
4
|
-
|
|
5
|
-
PlaywrightWorkerArgs,
|
|
6
|
-
Request,
|
|
5
|
+
Request as PlaywrightRequest,
|
|
7
6
|
Route,
|
|
8
|
-
TestFixture,
|
|
9
7
|
WebSocketRoute,
|
|
10
8
|
} from '@playwright/test'
|
|
9
|
+
import { WebSocketHandler } from 'msw'
|
|
11
10
|
import {
|
|
12
|
-
type LifeCycleEventsMap,
|
|
13
|
-
type UnhandledRequestStrategy,
|
|
14
11
|
SetupApi,
|
|
15
|
-
RequestHandler,
|
|
16
|
-
WebSocketHandler,
|
|
17
12
|
handleRequest,
|
|
13
|
+
isCommonAssetRequest,
|
|
14
|
+
type AnyHandler,
|
|
15
|
+
type LifeCycleEventsMap,
|
|
16
|
+
type UnhandledRequestStrategy,
|
|
18
17
|
} from 'msw'
|
|
19
18
|
import {
|
|
20
19
|
type WebSocketClientEventMap,
|
|
@@ -25,51 +24,43 @@ import {
|
|
|
25
24
|
WebSocketClientConnectionProtocol,
|
|
26
25
|
WebSocketServerConnectionProtocol,
|
|
27
26
|
} from '@mswjs/interceptors/WebSocket'
|
|
27
|
+
import { RequestHandler } from 'msw'
|
|
28
28
|
|
|
29
|
-
export interface
|
|
30
|
-
|
|
29
|
+
export interface NetworkFixtureOptions {
|
|
30
|
+
context: BrowserContext
|
|
31
|
+
handlers?: Array<AnyHandler>
|
|
31
32
|
onUnhandledRequest?: UnhandledRequestStrategy
|
|
33
|
+
/**
|
|
34
|
+
* Skip common asset requests (e.g. `*.html`, `*.css`, `*.js`, etc).
|
|
35
|
+
* This improves performance for certian projects.
|
|
36
|
+
* @default true
|
|
37
|
+
*
|
|
38
|
+
* @see https://mswjs.io/docs/api/is-common-asset-request
|
|
39
|
+
*/
|
|
40
|
+
skipAssetRequests?: boolean
|
|
32
41
|
}
|
|
33
42
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
* network: createNetworkFixture()
|
|
50
|
-
* })
|
|
51
|
-
* ```
|
|
52
|
-
*/
|
|
53
|
-
export function createNetworkFixture(
|
|
54
|
-
args?: CreateNetworkFixtureArgs,
|
|
55
|
-
): [
|
|
56
|
-
TestFixture<NetworkFixture, PlaywrightTestArgs & PlaywrightWorkerArgs>,
|
|
57
|
-
{ auto: boolean },
|
|
58
|
-
] {
|
|
59
|
-
return [
|
|
60
|
-
async ({ page }, use) => {
|
|
61
|
-
const worker = new NetworkFixture({
|
|
62
|
-
page,
|
|
63
|
-
initialHandlers: args?.initialHandlers || [],
|
|
64
|
-
onUnhandledRequest: args?.onUnhandledRequest,
|
|
65
|
-
})
|
|
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
|
+
}
|
|
66
58
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
]
|
|
59
|
+
interface SetupPlaywrightOptions {
|
|
60
|
+
context: BrowserContext
|
|
61
|
+
initialHandlers: Array<AnyHandler>
|
|
62
|
+
onUnhandledRequest?: UnhandledRequestStrategy
|
|
63
|
+
skipAssetRequests?: boolean
|
|
73
64
|
}
|
|
74
65
|
|
|
75
66
|
/**
|
|
@@ -80,34 +71,45 @@ export function createNetworkFixture(
|
|
|
80
71
|
*/
|
|
81
72
|
export const INTERNAL_MATCH_ALL_REG_EXP = /.+(__MSW_PLAYWRIGHT_PREDICATE__)?/
|
|
82
73
|
|
|
83
|
-
|
|
84
|
-
constructor(
|
|
85
|
-
|
|
86
|
-
page: Page
|
|
87
|
-
initialHandlers: Array<RequestHandler | WebSocketHandler>
|
|
88
|
-
onUnhandledRequest?: UnhandledRequestStrategy
|
|
89
|
-
},
|
|
90
|
-
) {
|
|
91
|
-
super(...args.initialHandlers)
|
|
74
|
+
class SetupPlaywrightApi extends SetupApi<LifeCycleEventsMap> {
|
|
75
|
+
constructor(private readonly options: SetupPlaywrightOptions) {
|
|
76
|
+
super(...options.initialHandlers)
|
|
92
77
|
}
|
|
93
78
|
|
|
94
|
-
public async
|
|
79
|
+
public async enable(): Promise<void> {
|
|
95
80
|
// Handle HTTP requests.
|
|
96
|
-
await this.
|
|
81
|
+
await this.options.context.route(
|
|
97
82
|
INTERNAL_MATCH_ALL_REG_EXP,
|
|
98
|
-
async (route: Route, request:
|
|
83
|
+
async (route: Route, request: PlaywrightRequest) => {
|
|
99
84
|
const fetchRequest = new Request(request.url(), {
|
|
100
85
|
method: request.method(),
|
|
101
86
|
headers: new Headers(await request.allHeaders()),
|
|
102
|
-
body: request.postDataBuffer(),
|
|
87
|
+
body: request.postDataBuffer() as ArrayBuffer | null,
|
|
103
88
|
})
|
|
104
89
|
|
|
90
|
+
/**
|
|
91
|
+
* @note Skip common asset requests (default).
|
|
92
|
+
* Playwright seems to experience performance degradation when routing all
|
|
93
|
+
* requests through the matching logic below.
|
|
94
|
+
* @see https://github.com/mswjs/playwright/issues/13
|
|
95
|
+
*/
|
|
96
|
+
if (
|
|
97
|
+
this.options.skipAssetRequests &&
|
|
98
|
+
isCommonAssetRequest(fetchRequest)
|
|
99
|
+
) {
|
|
100
|
+
return route.continue()
|
|
101
|
+
}
|
|
102
|
+
|
|
105
103
|
const handlers = this.handlersController
|
|
106
104
|
.currentHandlers()
|
|
107
105
|
.filter((handler) => {
|
|
108
106
|
return handler instanceof RequestHandler
|
|
109
107
|
})
|
|
110
108
|
|
|
109
|
+
const baseUrl = request.headers().referer
|
|
110
|
+
? new URL(request.headers().referer).origin
|
|
111
|
+
: undefined
|
|
112
|
+
|
|
111
113
|
/**
|
|
112
114
|
* @note Use `handleRequest` instead of `getResponse` so we can pass
|
|
113
115
|
* the `onUnhandledRequest` option as-is and benefit from MSW's default behaviors.
|
|
@@ -117,41 +119,39 @@ export class NetworkFixture extends SetupApi<LifeCycleEventsMap> {
|
|
|
117
119
|
crypto.randomUUID(),
|
|
118
120
|
handlers,
|
|
119
121
|
{
|
|
120
|
-
onUnhandledRequest: this.
|
|
122
|
+
onUnhandledRequest: this.options.onUnhandledRequest || 'bypass',
|
|
121
123
|
},
|
|
122
124
|
this.emitter,
|
|
123
125
|
{
|
|
124
126
|
resolutionContext: {
|
|
125
127
|
quiet: true,
|
|
126
|
-
baseUrl
|
|
128
|
+
baseUrl,
|
|
127
129
|
},
|
|
128
130
|
},
|
|
129
131
|
)
|
|
130
132
|
|
|
131
133
|
if (response) {
|
|
132
134
|
if (response.status === 0) {
|
|
133
|
-
route.abort()
|
|
134
|
-
return
|
|
135
|
+
return route.abort()
|
|
135
136
|
}
|
|
136
137
|
|
|
137
|
-
route.fulfill({
|
|
138
|
+
return route.fulfill({
|
|
138
139
|
status: response.status,
|
|
139
140
|
headers: Object.fromEntries(response.headers),
|
|
140
141
|
body: response.body
|
|
141
142
|
? Buffer.from(await response.arrayBuffer())
|
|
142
143
|
: undefined,
|
|
143
144
|
})
|
|
144
|
-
return
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
-
route.continue()
|
|
147
|
+
return route.continue()
|
|
148
148
|
},
|
|
149
149
|
)
|
|
150
150
|
|
|
151
151
|
// Handle WebSocket connections.
|
|
152
|
-
await this.
|
|
152
|
+
await this.options.context.routeWebSocket(
|
|
153
153
|
INTERNAL_MATCH_ALL_REG_EXP,
|
|
154
|
-
async (
|
|
154
|
+
async (route) => {
|
|
155
155
|
const allWebSocketHandlers = this.handlersController
|
|
156
156
|
.currentHandlers()
|
|
157
157
|
.filter((handler) => {
|
|
@@ -159,12 +159,16 @@ export class NetworkFixture extends SetupApi<LifeCycleEventsMap> {
|
|
|
159
159
|
})
|
|
160
160
|
|
|
161
161
|
if (allWebSocketHandlers.length === 0) {
|
|
162
|
-
|
|
162
|
+
route.connectToServer()
|
|
163
163
|
return
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
-
const client = new PlaywrightWebSocketClientConnection(
|
|
167
|
-
const server = new PlaywrightWebSocketServerConnection(
|
|
166
|
+
const client = new PlaywrightWebSocketClientConnection(route)
|
|
167
|
+
const server = new PlaywrightWebSocketServerConnection(route)
|
|
168
|
+
|
|
169
|
+
const pages = this.options.context.pages()
|
|
170
|
+
const lastPage = pages[pages.length - 1]
|
|
171
|
+
const baseUrl = lastPage ? this.getPageUrl(lastPage) : undefined
|
|
168
172
|
|
|
169
173
|
for (const handler of allWebSocketHandlers) {
|
|
170
174
|
await handler.run(
|
|
@@ -174,7 +178,7 @@ export class NetworkFixture extends SetupApi<LifeCycleEventsMap> {
|
|
|
174
178
|
info: { protocols: [] },
|
|
175
179
|
},
|
|
176
180
|
{
|
|
177
|
-
baseUrl
|
|
181
|
+
baseUrl,
|
|
178
182
|
},
|
|
179
183
|
)
|
|
180
184
|
}
|
|
@@ -182,21 +186,25 @@ export class NetworkFixture extends SetupApi<LifeCycleEventsMap> {
|
|
|
182
186
|
)
|
|
183
187
|
}
|
|
184
188
|
|
|
185
|
-
public async
|
|
189
|
+
public async disable(): Promise<void> {
|
|
186
190
|
super.dispose()
|
|
187
|
-
await this.
|
|
188
|
-
await unrouteWebSocket(this.
|
|
191
|
+
await this.options.context.unroute(INTERNAL_MATCH_ALL_REG_EXP)
|
|
192
|
+
await unrouteWebSocket(this.options.context, INTERNAL_MATCH_ALL_REG_EXP)
|
|
189
193
|
}
|
|
190
194
|
|
|
191
|
-
private getPageUrl(): string | undefined {
|
|
192
|
-
const url =
|
|
193
|
-
|
|
195
|
+
private getPageUrl(page: Page): string | undefined {
|
|
196
|
+
const url = page.url()
|
|
197
|
+
|
|
198
|
+
if (url === 'about:blank') {
|
|
199
|
+
return
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Encode/decode to preserve escape characters.
|
|
203
|
+
return decodeURI(new URL(encodeURI(url)).origin)
|
|
194
204
|
}
|
|
195
205
|
}
|
|
196
206
|
|
|
197
|
-
class PlaywrightWebSocketClientConnection
|
|
198
|
-
implements WebSocketClientConnectionProtocol
|
|
199
|
-
{
|
|
207
|
+
class PlaywrightWebSocketClientConnection implements WebSocketClientConnectionProtocol {
|
|
200
208
|
public id: string
|
|
201
209
|
public url: URL
|
|
202
210
|
|
|
@@ -298,9 +306,7 @@ class PlaywrightWebSocketClientConnection
|
|
|
298
306
|
}
|
|
299
307
|
}
|
|
300
308
|
|
|
301
|
-
class PlaywrightWebSocketServerConnection
|
|
302
|
-
implements WebSocketServerConnectionProtocol
|
|
303
|
-
{
|
|
309
|
+
class PlaywrightWebSocketServerConnection implements WebSocketServerConnectionProtocol {
|
|
304
310
|
#server?: WebSocketRoute
|
|
305
311
|
#bufferedEvents: Array<
|
|
306
312
|
Parameters<WebSocketServerConnectionProtocol['addEventListener']>
|
|
@@ -411,22 +417,24 @@ interface InternalWebSocketRoute {
|
|
|
411
417
|
* WebSocket route handlers from the page. Loosely inspired by `page.unroute()`.
|
|
412
418
|
*/
|
|
413
419
|
async function unrouteWebSocket(
|
|
414
|
-
|
|
420
|
+
target: BrowserContext,
|
|
415
421
|
url: InternalWebSocketRoute['url'],
|
|
416
422
|
handler?: InternalWebSocketRoute['handler'],
|
|
417
423
|
): Promise<void> {
|
|
418
|
-
if (
|
|
424
|
+
if (
|
|
425
|
+
!('_webSocketRoutes' in target && Array.isArray(target._webSocketRoutes))
|
|
426
|
+
) {
|
|
419
427
|
return
|
|
420
428
|
}
|
|
421
429
|
|
|
422
|
-
for (let i =
|
|
423
|
-
const route =
|
|
430
|
+
for (let i = target._webSocketRoutes.length - 1; i >= 0; i--) {
|
|
431
|
+
const route = target._webSocketRoutes[i] as InternalWebSocketRoute
|
|
424
432
|
|
|
425
433
|
if (
|
|
426
434
|
route.url === url &&
|
|
427
435
|
(handler != null ? route.handler === handler : true)
|
|
428
436
|
) {
|
|
429
|
-
|
|
437
|
+
target._webSocketRoutes.splice(i, 1)
|
|
430
438
|
}
|
|
431
439
|
}
|
|
432
440
|
}
|
package/src/index.ts
CHANGED