@msw/playwright 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025–present Artem Zakharchenko
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,67 @@
1
+ # `@msw/playwright`
2
+
3
+ [Mock Service Worker](https://mswjs.io) binding for [Playwright](https://playwright.dev/).
4
+
5
+ ## Motivation
6
+
7
+ While you can use MSW in Playwright following the default [Browser integration](https://mswjs.io/docs/integrations/browser), the cross-process messaging in Playwright makes it clunky to work with the `worker` instance in one process (your tests; Node.js) to affect another (your app; browser).
8
+
9
+ ```ts
10
+ await page.evaluate(() => {
11
+ // In order to reference the worker instance in your tests,
12
+ // you have to set it on `window` alongside any other
13
+ // functions from the `msw` package you want to use since
14
+ // you cannot reference them in `page.evaluate` directly.
15
+ const { worker, http, graphql } = window.msw
16
+ worker.use(...)
17
+ })
18
+ ```
19
+
20
+ This package aims to provide a better developer experience when mocking APIs in Playwright.
21
+
22
+ > Until we ship [cross-process request interception](https://github.com/mswjs/msw/pull/1617), `@msw/playwright` will rely on the `page.route()` API to provision the request interception in your tests. That means you _don't have to initialize the worker script_ to use this package. That also means that any `page.route()` limitations now affect this library. Treat this as an implementation detail that is likely to change in the future.
23
+
24
+ ## Usage
25
+
26
+ ```sh
27
+ npm i @msw/playwright
28
+ ```
29
+
30
+ ```ts
31
+ // playwright.setup.ts
32
+ import { test as testBase } from '@playwright/test'
33
+ import { createWorkerFixture, type WorkerFixture } from '@msw/playwright'
34
+ import { handlers } from '../mocks/handlers.js'
35
+
36
+ interface Fixtures {
37
+ worker: WorkerFixture
38
+ }
39
+
40
+ export const test = testBase.extend<Fixtures>({
41
+ // Create your worker fixture to access in tests.
42
+ worker: createWorkerFixture({
43
+ initialHandlers: handlers,
44
+ }),
45
+ })
46
+ ```
47
+
48
+ ```ts
49
+ import { http, HttpResponse } from 'msw'
50
+ import { test } from './playwright.setup.js'
51
+
52
+ test('displays the user dashboard', async ({ worker, page }) => {
53
+ // Access and use the worker as you normally would!
54
+ // No more disrupted context between processes.
55
+ worker.use(
56
+ http.get('/user', () => {
57
+ return HttpResponse.json({
58
+ id: 'abc-123',
59
+ firstName: 'John',
60
+ lastName: 'Maverick',
61
+ })
62
+ }),
63
+ )
64
+
65
+ await page.goto('/dashboard')
66
+ })
67
+ ```
@@ -0,0 +1,16 @@
1
+ import { LifeCycleEventsMap, RequestHandler, SetupApi } from "msw";
2
+ import { Page, TestFixture } from "@playwright/test";
3
+
4
+ //#region src/index.d.ts
5
+ declare function createWorkerFixture(initialHandlers?: Array<RequestHandler>): TestFixture<WorkerFixture, any>;
6
+ declare class WorkerFixture extends SetupApi<LifeCycleEventsMap> {
7
+ #private;
8
+ constructor(args: {
9
+ page: Page;
10
+ initialHandlers: Array<RequestHandler>;
11
+ });
12
+ start(): Promise<void>;
13
+ stop(): Promise<void>;
14
+ }
15
+ //#endregion
16
+ export { WorkerFixture, createWorkerFixture };
package/build/index.js ADDED
@@ -0,0 +1,49 @@
1
+ import { RequestHandler, SetupApi, getResponse } from "msw";
2
+
3
+ //#region src/index.ts
4
+ function createWorkerFixture(initialHandlers = []) {
5
+ return async ({ page }, use) => {
6
+ const worker = new WorkerFixture({
7
+ page,
8
+ initialHandlers
9
+ });
10
+ await worker.start();
11
+ await use(worker);
12
+ await worker.stop();
13
+ };
14
+ }
15
+ var WorkerFixture = class extends SetupApi {
16
+ #page;
17
+ constructor(args) {
18
+ super(...args.initialHandlers);
19
+ this.#page = args.page;
20
+ }
21
+ async start() {
22
+ await this.#page.route(/.+/, async (route, request) => {
23
+ const fetchRequest = new Request(request.url(), {
24
+ method: request.method(),
25
+ headers: new Headers(await request.allHeaders()),
26
+ body: request.postDataBuffer()
27
+ });
28
+ const response = await getResponse(this.handlersController.currentHandlers().filter((handler) => {
29
+ return handler instanceof RequestHandler;
30
+ }), fetchRequest);
31
+ if (response) {
32
+ route.fulfill({
33
+ status: response.status,
34
+ headers: Object.fromEntries(response.headers),
35
+ body: response.body ? Buffer.from(await response.arrayBuffer()) : void 0
36
+ });
37
+ return;
38
+ }
39
+ route.continue();
40
+ });
41
+ }
42
+ async stop() {
43
+ super.dispose();
44
+ await this.#page.unroute(/.+/);
45
+ }
46
+ };
47
+
48
+ //#endregion
49
+ export { WorkerFixture, createWorkerFixture };
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "type": "module",
3
+ "name": "@msw/playwright",
4
+ "version": "0.1.0",
5
+ "description": "Mock Service Worker binding for Playwright",
6
+ "main": "./build/index.js",
7
+ "types": "./build/index.d.ts",
8
+ "files": [
9
+ "./src",
10
+ "./build"
11
+ ],
12
+ "keywords": [
13
+ "playwright",
14
+ "api",
15
+ "mock",
16
+ "msw",
17
+ "request"
18
+ ],
19
+ "license": "MIT",
20
+ "funding": "https://github.com/sponsors/mswjs",
21
+ "homepage": "https://mswjs.io",
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "git+https://github.com/mswjs/playwright.git"
25
+ },
26
+ "author": {
27
+ "name": "Artem Zakharchenko",
28
+ "url": "https://github.com/kettanaito"
29
+ },
30
+ "publishConfig": {
31
+ "access": "public"
32
+ },
33
+ "engines": {
34
+ "node": ">=20.0.0"
35
+ },
36
+ "peerDependencies": {
37
+ "msw": "^2.9.0"
38
+ },
39
+ "devDependencies": {
40
+ "@ossjs/release": "^0.8.1",
41
+ "@playwright/test": "^1.52.0",
42
+ "@types/node": "^22.15.29",
43
+ "msw": "^2.9.0",
44
+ "tsdown": "^0.12.7",
45
+ "typescript": "^5.8.3"
46
+ },
47
+ "scripts": {
48
+ "dev": "tsdown --watch",
49
+ "test": "playwright test",
50
+ "build": "tsdown",
51
+ "release": "release publish"
52
+ }
53
+ }
package/src/index.ts ADDED
@@ -0,0 +1,67 @@
1
+ import type { Page, TestFixture } from '@playwright/test'
2
+ import {
3
+ type LifeCycleEventsMap,
4
+ SetupApi,
5
+ RequestHandler,
6
+ getResponse,
7
+ } from 'msw'
8
+
9
+ export function createWorkerFixture(
10
+ initialHandlers: Array<RequestHandler> = [],
11
+ /** @todo `onUnhandledRequest`? */
12
+ ): TestFixture<WorkerFixture, any> {
13
+ return async ({ page }, use) => {
14
+ const worker = new WorkerFixture({
15
+ page,
16
+ initialHandlers,
17
+ })
18
+
19
+ await worker.start()
20
+ await use(worker)
21
+ await worker.stop()
22
+ }
23
+ }
24
+
25
+ export class WorkerFixture extends SetupApi<LifeCycleEventsMap> {
26
+ #page: Page
27
+
28
+ constructor(args: { page: Page; initialHandlers: Array<RequestHandler> }) {
29
+ super(...args.initialHandlers)
30
+ this.#page = args.page
31
+ }
32
+
33
+ public async start() {
34
+ await this.#page.route(/.+/, async (route, request) => {
35
+ const fetchRequest = new Request(request.url(), {
36
+ method: request.method(),
37
+ headers: new Headers(await request.allHeaders()),
38
+ body: request.postDataBuffer(),
39
+ })
40
+
41
+ const response = await getResponse(
42
+ this.handlersController.currentHandlers().filter((handler) => {
43
+ return handler instanceof RequestHandler
44
+ }),
45
+ fetchRequest,
46
+ )
47
+
48
+ if (response) {
49
+ route.fulfill({
50
+ status: response.status,
51
+ headers: Object.fromEntries(response.headers),
52
+ body: response.body
53
+ ? Buffer.from(await response.arrayBuffer())
54
+ : undefined,
55
+ })
56
+ return
57
+ }
58
+
59
+ route.continue()
60
+ })
61
+ }
62
+
63
+ public async stop() {
64
+ super.dispose()
65
+ await this.#page.unroute(/.+/)
66
+ }
67
+ }