feat(ct): expose msw fixture that accepts msw-style request handlers
Implemented on top of `context.route()`.
This commit is contained in:
parent
384eed65ea
commit
d05dae8db6
|
|
@ -721,6 +721,45 @@ test('update', async ({ mount }) => {
|
||||||
|
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
|
### Handling network requests
|
||||||
|
|
||||||
|
Playwright provides [`method: Page.route`] method to route your network requests. See the [network mocking guide](./mock.md) for more details.
|
||||||
|
|
||||||
|
### MSW support
|
||||||
|
|
||||||
|
If you are using the [MSW library](https://mswjs.io/) to handle network requests during development or testing, try the **experimental fixture** `msw` and give us feedback. With this fixture, you can reuse request handlers between development and tests.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { handlers } from '@src/mocks/handlers';
|
||||||
|
|
||||||
|
test.beforeEach(async ({ msw }) => {
|
||||||
|
// install common handlers before each test
|
||||||
|
await msw.use(...handlers);
|
||||||
|
})
|
||||||
|
|
||||||
|
test('example test', async ({ mount }) => {
|
||||||
|
// test as usual, your handlers are active
|
||||||
|
// ...
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also introduce test-specific handlers.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { http, HttpResponse } from 'msw';
|
||||||
|
|
||||||
|
test('example test', async ({ mount, msw }) => {
|
||||||
|
await msw.use(
|
||||||
|
http.get('/data', async ({ request }) => {
|
||||||
|
return HttpResponse.json({ value: 'mocked' });
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// test as usual, your handler is active
|
||||||
|
// ...
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
## Frequently asked questions
|
## Frequently asked questions
|
||||||
|
|
||||||
### What's the difference between `@playwright/test` and `@playwright/experimental-ct-{react,svelte,vue,solid}`?
|
### What's the difference between `@playwright/test` and `@playwright/experimental-ct-{react,svelte,vue,solid}`?
|
||||||
|
|
|
||||||
11
packages/playwright-ct-core/index.d.ts
vendored
11
packages/playwright-ct-core/index.d.ts
vendored
|
|
@ -24,6 +24,9 @@ import type {
|
||||||
} from 'playwright/test';
|
} from 'playwright/test';
|
||||||
import type { InlineConfig } from 'vite';
|
import type { InlineConfig } from 'vite';
|
||||||
|
|
||||||
|
//@ts-ignore This will be "any" if msw is not installed.
|
||||||
|
import type { RequestHandler } from 'msw';
|
||||||
|
|
||||||
export type PlaywrightTestConfig<T = {}, W = {}> = Omit<BasePlaywrightTestConfig<T, W>, 'use'> & {
|
export type PlaywrightTestConfig<T = {}, W = {}> = Omit<BasePlaywrightTestConfig<T, W>, 'use'> & {
|
||||||
use?: BasePlaywrightTestConfig<T, W>['use'] & {
|
use?: BasePlaywrightTestConfig<T, W>['use'] & {
|
||||||
ctPort?: number;
|
ctPort?: number;
|
||||||
|
|
@ -33,8 +36,14 @@ export type PlaywrightTestConfig<T = {}, W = {}> = Omit<BasePlaywrightTestConfig
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type CommonComponentFixtures = {
|
||||||
|
msw: {
|
||||||
|
use(...handlers: RequestHandler[]): Promise<void>;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export type TestType<ComponentFixtures> = BaseTestType<
|
export type TestType<ComponentFixtures> = BaseTestType<
|
||||||
PlaywrightTestArgs & PlaywrightTestOptions & ComponentFixtures,
|
PlaywrightTestArgs & PlaywrightTestOptions & ComponentFixtures & CommonComponentFixtures,
|
||||||
PlaywrightWorkerArgs & PlaywrightWorkerOptions
|
PlaywrightWorkerArgs & PlaywrightWorkerOptions
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import type { Component, JsxComponent, MountOptions, ObjectComponentOptions } fr
|
||||||
import type { ContextReuseMode, FullConfigInternal } from '../../playwright/src/common/config';
|
import type { ContextReuseMode, FullConfigInternal } from '../../playwright/src/common/config';
|
||||||
import type { ImportRef } from './injected/importRegistry';
|
import type { ImportRef } from './injected/importRegistry';
|
||||||
import { wrapObject } from './injected/serializers';
|
import { wrapObject } from './injected/serializers';
|
||||||
|
import { type MSW, MSWImpl } from './msw';
|
||||||
|
|
||||||
let boundCallbacksForMount: Function[] = [];
|
let boundCallbacksForMount: Function[] = [];
|
||||||
|
|
||||||
|
|
@ -29,8 +30,9 @@ interface MountResult extends Locator {
|
||||||
|
|
||||||
type TestFixtures = PlaywrightTestArgs & PlaywrightTestOptions & {
|
type TestFixtures = PlaywrightTestArgs & PlaywrightTestOptions & {
|
||||||
mount: (component: any, options: any) => Promise<MountResult>;
|
mount: (component: any, options: any) => Promise<MountResult>;
|
||||||
|
msw: MSW;
|
||||||
};
|
};
|
||||||
type WorkerFixtures = PlaywrightWorkerArgs & PlaywrightWorkerOptions & { _ctWorker: { context: BrowserContext | undefined, hash: string } };
|
type WorkerFixtures = PlaywrightWorkerArgs & PlaywrightWorkerOptions;
|
||||||
type BaseTestFixtures = {
|
type BaseTestFixtures = {
|
||||||
_contextFactory: (options?: BrowserContextOptions) => Promise<BrowserContext>,
|
_contextFactory: (options?: BrowserContextOptions) => Promise<BrowserContext>,
|
||||||
_optionContextReuseMode: ContextReuseMode
|
_optionContextReuseMode: ContextReuseMode
|
||||||
|
|
@ -42,8 +44,6 @@ export const fixtures: Fixtures<TestFixtures, WorkerFixtures, BaseTestFixtures>
|
||||||
|
|
||||||
serviceWorkers: 'block',
|
serviceWorkers: 'block',
|
||||||
|
|
||||||
_ctWorker: [{ context: undefined, hash: '' }, { scope: 'worker' }],
|
|
||||||
|
|
||||||
page: async ({ page }, use, info) => {
|
page: async ({ page }, use, info) => {
|
||||||
if (!((info as any)._configInternal as FullConfigInternal).defineConfigWasUsed)
|
if (!((info as any)._configInternal as FullConfigInternal).defineConfigWasUsed)
|
||||||
throw new Error('Component testing requires the use of the defineConfig() in your playwright-ct.config.{ts,js}: https://aka.ms/playwright/ct-define-config');
|
throw new Error('Component testing requires the use of the defineConfig() in your playwright-ct.config.{ts,js}: https://aka.ms/playwright/ct-define-config');
|
||||||
|
|
@ -78,6 +78,12 @@ export const fixtures: Fixtures<TestFixtures, WorkerFixtures, BaseTestFixtures>
|
||||||
});
|
});
|
||||||
boundCallbacksForMount = [];
|
boundCallbacksForMount = [];
|
||||||
},
|
},
|
||||||
|
|
||||||
|
msw: async ({ context, baseURL }, use) => {
|
||||||
|
const msw = new MSWImpl(context, baseURL);
|
||||||
|
await use(msw);
|
||||||
|
await msw.dispose();
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function isJsxComponent(component: any): component is JsxComponent {
|
function isJsxComponent(component: any): component is JsxComponent {
|
||||||
|
|
|
||||||
164
packages/playwright-ct-core/src/msw.ts
Normal file
164
packages/playwright-ct-core/src/msw.ts
Normal file
|
|
@ -0,0 +1,164 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) Microsoft Corporation.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type * as playwright from 'playwright/test';
|
||||||
|
|
||||||
|
interface RequestHandler {
|
||||||
|
run(args: { request: Request, resolutionContext: { baseUrl?: string }, requestId: string }): Promise<{ response: Response } | undefined>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MSW {
|
||||||
|
use(...handlers: RequestHandler[]): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
let lastRequestId = 0;
|
||||||
|
let fetchOverrideCounter = 0;
|
||||||
|
const currentlyInterceptingInContexts = new Map<playwright.BrowserContext, number>();
|
||||||
|
const originalFetch = globalThis.fetch;
|
||||||
|
|
||||||
|
async function executeRequestHandlers(request: Request, handlers: RequestHandler[], baseUrl: string | undefined): Promise<Response | undefined> {
|
||||||
|
const requestId = String(++lastRequestId);
|
||||||
|
const resolutionContext = { baseUrl };
|
||||||
|
for (const handler of handlers) {
|
||||||
|
const result = await handler.run({ request, requestId, resolutionContext });
|
||||||
|
if (result?.response)
|
||||||
|
return result.response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function globalFetch(...args: Parameters<typeof globalThis.fetch>) {
|
||||||
|
if (args[0] && args[0] instanceof Request) {
|
||||||
|
const request = args[0];
|
||||||
|
if (request.headers.get('x-msw-intention') === 'bypass') {
|
||||||
|
const cookieHeaders = await Promise.all([...currentlyInterceptingInContexts.keys()].map(async context => {
|
||||||
|
const cookies = await context.cookies(request.url);
|
||||||
|
if (!cookies.length)
|
||||||
|
return undefined;
|
||||||
|
return cookies.map(c => `${c.name}=${c.value}`).join('; ');
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!cookieHeaders.length)
|
||||||
|
throw new Error(`Cannot call fetch(bypass()) outside of a request handler`);
|
||||||
|
|
||||||
|
if (cookieHeaders.some(h => h !== cookieHeaders[0]))
|
||||||
|
throw new Error(`Cannot call fetch(bypass()) while concurrently handling multiple requests from different browser contexts`);
|
||||||
|
|
||||||
|
const headers = new Headers(request.headers);
|
||||||
|
headers.set('cookie', cookieHeaders[0]!);
|
||||||
|
headers.delete('x-msw-intention');
|
||||||
|
args[0] = new Request(request.clone(), { headers });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return originalFetch(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MSWImpl implements MSW {
|
||||||
|
private _context: playwright.BrowserContext;
|
||||||
|
private _handlers: RequestHandler[] = [];
|
||||||
|
private _routeHandler: (route: playwright.Route) => Promise<void>;
|
||||||
|
private _attached = false;
|
||||||
|
|
||||||
|
constructor(context: playwright.BrowserContext, baseURL: string | undefined) {
|
||||||
|
this._context = context;
|
||||||
|
|
||||||
|
this._routeHandler = async route => {
|
||||||
|
if (route.request().isNavigationRequest()) {
|
||||||
|
await route.fallback();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const request = route.request();
|
||||||
|
const headersArray = await request.headersArray();
|
||||||
|
const headers = new Headers();
|
||||||
|
for (const { name, value } of headersArray)
|
||||||
|
headers.append(name, value);
|
||||||
|
|
||||||
|
const buffer = request.postDataBuffer();
|
||||||
|
const body = buffer?.byteLength ? new Int8Array(buffer.buffer, buffer.byteOffset, buffer.length) : undefined;
|
||||||
|
|
||||||
|
const newRequest = new Request(request.url(), {
|
||||||
|
body: body,
|
||||||
|
headers: headers,
|
||||||
|
method: request.method(),
|
||||||
|
referrer: headersArray.find(h => h.name.toLowerCase() === 'referer')?.value,
|
||||||
|
});
|
||||||
|
|
||||||
|
currentlyInterceptingInContexts.set(context, 1 + (currentlyInterceptingInContexts.get(context) || 0));
|
||||||
|
const response = await executeRequestHandlers(newRequest, this._handlers, baseURL).finally(() => {
|
||||||
|
const value = currentlyInterceptingInContexts.get(context)! - 1;
|
||||||
|
if (value)
|
||||||
|
currentlyInterceptingInContexts.set(context, value);
|
||||||
|
else
|
||||||
|
currentlyInterceptingInContexts.delete(context);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response) {
|
||||||
|
await route.fallback();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.status === 302 && response.headers.get('x-msw-intention') === 'passthrough') {
|
||||||
|
await route.continue();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.type === 'error') {
|
||||||
|
await route.abort();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const responseHeaders: Record<string, string> = {};
|
||||||
|
for (const [name, value] of response.headers.entries()) {
|
||||||
|
if (responseHeaders[name])
|
||||||
|
responseHeaders[name] = responseHeaders[name] + (name.toLowerCase() === 'set-cookie' ? '\n' : ', ') + value;
|
||||||
|
else
|
||||||
|
responseHeaders[name] = value;
|
||||||
|
}
|
||||||
|
await route.fulfill({
|
||||||
|
status: response.status,
|
||||||
|
body: Buffer.from(await response.arrayBuffer()),
|
||||||
|
headers: responseHeaders,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async use(...handlers: RequestHandler[]) {
|
||||||
|
this._handlers = handlers.concat(this._handlers);
|
||||||
|
await this._update();
|
||||||
|
}
|
||||||
|
|
||||||
|
async dispose() {
|
||||||
|
this._handlers = [];
|
||||||
|
await this._update();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _update() {
|
||||||
|
if (this._handlers.length && !this._attached) {
|
||||||
|
await this._context.route('**/*', this._routeHandler);
|
||||||
|
if (!fetchOverrideCounter)
|
||||||
|
globalThis.fetch = globalFetch;
|
||||||
|
++fetchOverrideCounter;
|
||||||
|
this._attached = true;
|
||||||
|
}
|
||||||
|
if (!this._handlers.length && this._attached) {
|
||||||
|
await this._context.unroute('**/*', this._routeHandler);
|
||||||
|
this._attached = false;
|
||||||
|
--fetchOverrideCounter;
|
||||||
|
if (!fetchOverrideCounter)
|
||||||
|
globalThis.fetch = originalFetch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
"@types/react": "^18.0.26",
|
"@types/react": "^18.0.26",
|
||||||
"@types/react-dom": "^18.0.10",
|
"@types/react-dom": "^18.0.10",
|
||||||
"@vitejs/plugin-react": "^4.2.1",
|
"@vitejs/plugin-react": "^4.2.1",
|
||||||
|
"msw": "^2.3.0",
|
||||||
"typescript": "^5.2.2",
|
"typescript": "^5.2.2",
|
||||||
"vite": "^5.2.8"
|
"vite": "^5.2.8"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
32
tests/components/ct-react-vite/src/components/Fetcher.tsx
Normal file
32
tests/components/ct-react-vite/src/components/Fetcher.tsx
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
import { useEffect, useState } from "react"
|
||||||
|
|
||||||
|
export default function Fetcher() {
|
||||||
|
const [data, setData] = useState<{ name: string }>({ name: '<none>' });
|
||||||
|
const [fetched, setFetched] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const doFetch = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/data.json');
|
||||||
|
setData(await response.json());
|
||||||
|
} catch {
|
||||||
|
setData({ name: '<error>' });
|
||||||
|
}
|
||||||
|
setFetched(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fetched)
|
||||||
|
doFetch();
|
||||||
|
}, [fetched, setFetched, setData]);
|
||||||
|
|
||||||
|
return <div>
|
||||||
|
<div data-testId='name'>{data.name}</div>
|
||||||
|
<button onClick={() => {
|
||||||
|
setFetched(false);
|
||||||
|
setData({ name: '<none>' });
|
||||||
|
}}>Reset</button>
|
||||||
|
<button onClick={() => {
|
||||||
|
fetch('/post', { method: 'POST', body: 'hello from the page' });
|
||||||
|
}}>Post it</button>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
160
tests/components/ct-react-vite/tests/msw.spec.tsx
Normal file
160
tests/components/ct-react-vite/tests/msw.spec.tsx
Normal file
|
|
@ -0,0 +1,160 @@
|
||||||
|
import { test, expect } from '@playwright/experimental-ct-react';
|
||||||
|
import Fetcher from '@/components/Fetcher';
|
||||||
|
import { http, HttpResponse, passthrough, bypass } from 'msw';
|
||||||
|
import httpServer from 'http';
|
||||||
|
import type net from 'net';
|
||||||
|
|
||||||
|
test('should handle requests', async ({ page, mount, msw }) => {
|
||||||
|
let respond: (() => void) = () => {};
|
||||||
|
const promise = new Promise<void>(f => respond = f);
|
||||||
|
|
||||||
|
let postReceived: ((body: string) => void) = () => {};
|
||||||
|
const postBody = new Promise<string>(f => postReceived = f);
|
||||||
|
|
||||||
|
await msw.use(
|
||||||
|
http.get('/data.json', async () => {
|
||||||
|
await promise;
|
||||||
|
return HttpResponse.json({ name: 'John Doe' });
|
||||||
|
}),
|
||||||
|
http.post('/post', async ({ request }) => {
|
||||||
|
postReceived(await request.text());
|
||||||
|
return HttpResponse.text('ok');
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const component = await mount(<Fetcher />);
|
||||||
|
await expect(component.getByTestId('name')).toHaveText('<none>');
|
||||||
|
|
||||||
|
respond();
|
||||||
|
await expect(component.getByTestId('name')).toHaveText('John Doe');
|
||||||
|
|
||||||
|
await component.getByRole('button', { name: 'Post it' }).click();
|
||||||
|
expect(await postBody).toBe('hello from the page');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should add dynamically', async ({ page, mount, msw }) => {
|
||||||
|
await page.context().route('**/data.json', async route => {
|
||||||
|
await route.fulfill({ body: JSON.stringify({ name: '<original>' }) });
|
||||||
|
});
|
||||||
|
|
||||||
|
const component = await mount(<Fetcher />);
|
||||||
|
await expect(component.getByTestId('name')).toHaveText('<original>');
|
||||||
|
|
||||||
|
await msw.use(
|
||||||
|
http.get('/data.json', async () => {
|
||||||
|
return HttpResponse.json({ name: 'John Doe' });
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await component.getByRole('button', { name: 'Reset' }).click();
|
||||||
|
await expect(component.getByTestId('name')).toHaveText('John Doe');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should passthrough', async ({ page, mount, msw }) => {
|
||||||
|
await page.context().route('**/data.json', async route => {
|
||||||
|
await route.fulfill({ body: JSON.stringify({ name: '<original>' }) });
|
||||||
|
});
|
||||||
|
|
||||||
|
await msw.use(
|
||||||
|
http.get('/data.json', async () => {
|
||||||
|
return passthrough();
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const component = await mount(<Fetcher />);
|
||||||
|
await expect(component.getByTestId('name')).toHaveText('<error>');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should fallback when nothing is returned', async ({ page, mount, msw }) => {
|
||||||
|
await page.context().route('**/data.json', async route => {
|
||||||
|
await route.fulfill({ body: JSON.stringify({ name: '<original>' }) });
|
||||||
|
});
|
||||||
|
|
||||||
|
let called = false;
|
||||||
|
await msw.use(
|
||||||
|
http.get('/data.json', async () => {
|
||||||
|
called = true;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const component = await mount(<Fetcher />);
|
||||||
|
await expect(component.getByTestId('name')).toHaveText('<original>');
|
||||||
|
expect(called).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should bypass(request)', async ({ page, mount, msw }) => {
|
||||||
|
await page.context().route('**/data.json', async route => {
|
||||||
|
await route.fulfill({ body: JSON.stringify({ name: `<original>` }) });
|
||||||
|
});
|
||||||
|
|
||||||
|
await msw.use(
|
||||||
|
http.get('/data.json', async ({ request }) => {
|
||||||
|
return await fetch(bypass(request));
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const component = await mount(<Fetcher />);
|
||||||
|
await expect(component.getByTestId('name')).toHaveText('<error>');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should bypass(url) and get cookies', async ({ page, mount, msw, browserName }) => {
|
||||||
|
let cookie = '';
|
||||||
|
const server = new httpServer.Server();
|
||||||
|
server.on('request', (req, res) => {
|
||||||
|
cookie = req.headers['cookie']!;
|
||||||
|
res.writeHead(200, { 'Content-Type': 'application/json' }).end(JSON.stringify({ name: '<server>' }));
|
||||||
|
});
|
||||||
|
await new Promise<void>(f => server.listen(0, f));
|
||||||
|
const port = (server.address() as net.AddressInfo).port;
|
||||||
|
|
||||||
|
await page.context().route('**/data.json', async route => {
|
||||||
|
await route.fulfill({ body: JSON.stringify({ name: `<original>` }) });
|
||||||
|
});
|
||||||
|
|
||||||
|
const component = await mount(<Fetcher />);
|
||||||
|
await expect(component.getByTestId('name')).toHaveText('<original>');
|
||||||
|
|
||||||
|
await page.evaluate(() => document.cookie = 'foo=bar');
|
||||||
|
await msw.use(
|
||||||
|
http.get('/data.json', async ({ request }) => {
|
||||||
|
if (browserName !== 'webkit') {
|
||||||
|
// WebKit does not have cookies while intercepting.
|
||||||
|
expect(request.headers.get('cookie')).toBe('foo=bar');
|
||||||
|
}
|
||||||
|
return await fetch(bypass(`http://localhost:${port}`));
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
await component.getByRole('button', { name: 'Reset' }).click();
|
||||||
|
await expect(component.getByTestId('name')).toHaveText('<server>');
|
||||||
|
|
||||||
|
expect(cookie).toBe('foo=bar');
|
||||||
|
await new Promise(f => server.close(f));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should ignore navigation requests', async ({ page, mount, msw }) => {
|
||||||
|
await page.context().route('**/newpage', async route => {
|
||||||
|
await route.fulfill({ body: `<div>original</div>`, contentType: 'text/html' });
|
||||||
|
});
|
||||||
|
|
||||||
|
await msw.use(
|
||||||
|
http.get('/newpage', async ({ request }) => {
|
||||||
|
return new Response(`<div>intercepted</div>`, {
|
||||||
|
headers: new Headers({ 'Content-Type': 'text/html' }),
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await mount(<div />);
|
||||||
|
await page.goto('/newpage');
|
||||||
|
await expect(page.locator('div')).toHaveText('original');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should throw when calling fetch(bypass) outside of a handler', async ({ page, msw, baseURL }) => {
|
||||||
|
await msw.use(
|
||||||
|
http.get('/data.json', async () => {
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const error = await fetch(bypass(baseURL + '/hello')).catch(e => e);
|
||||||
|
expect(error.message).toContain(`Cannot call fetch(bypass()) outside of a request handler`);
|
||||||
|
});
|
||||||
Loading…
Reference in a new issue