parent
526e4118fa
commit
e86c8af599
|
|
@ -726,51 +726,18 @@ test('update', async ({ mount }) => {
|
|||
|
||||
### Handling network requests
|
||||
|
||||
Playwright provides a `route` fixture to intercept and handle network requests.
|
||||
Playwright provides an **experimental** `router` fixture to intercept and handle network requests. There are two ways to use the `router` fixture:
|
||||
* Call `router.route(url, handler)` that behaves similarly to [`method: Page.route`]. See the [network mocking guide](./mock.md) for more details.
|
||||
* Call `router.use(handlers)` and pass [MSW library](https://mswjs.io/) request handlers to it.
|
||||
|
||||
```ts
|
||||
test.beforeEach(async ({ route }) => {
|
||||
// install common routes before each test
|
||||
await route('*/**/api/v1/fruits', async route => {
|
||||
const json = [{ name: 'Strawberry', id: 21 }];
|
||||
await route.fulfill({ json });
|
||||
});
|
||||
});
|
||||
|
||||
test('example test', async ({ mount }) => {
|
||||
// test as usual, your routes are active
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
You can also introduce test-specific routes.
|
||||
|
||||
```ts
|
||||
import { http, HttpResponse } from 'msw';
|
||||
|
||||
test('example test', async ({ mount, route }) => {
|
||||
await route('*/**/api/v1/fruits', async route => {
|
||||
const json = [{ name: 'fruit for this single test', id: 42 }];
|
||||
await route.fulfill({ json });
|
||||
});
|
||||
|
||||
// test as usual, your route is active
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
The `route` fixture works in the same way as [`method: Page.route`]. See the [network mocking guide](./mock.md) for more details.
|
||||
|
||||
**Re-using MSW handlers**
|
||||
|
||||
If you are using the [MSW library](https://mswjs.io/) to handle network requests during development or testing, you can pass them directly to the `route` fixture.
|
||||
Here is an example of reusing your existing MSW handlers in the test.
|
||||
|
||||
```ts
|
||||
import { handlers } from '@src/mocks/handlers';
|
||||
|
||||
test.beforeEach(async ({ route }) => {
|
||||
test.beforeEach(async ({ router }) => {
|
||||
// install common handlers before each test
|
||||
await route(handlers);
|
||||
await router.use(...handlers);
|
||||
});
|
||||
|
||||
test('example test', async ({ mount }) => {
|
||||
|
|
@ -779,13 +746,13 @@ test('example test', async ({ mount }) => {
|
|||
});
|
||||
```
|
||||
|
||||
You can also introduce test-specific handlers.
|
||||
You can also introduce a one-off handler for a specific test.
|
||||
|
||||
```ts
|
||||
import { http, HttpResponse } from 'msw';
|
||||
|
||||
test('example test', async ({ mount, route }) => {
|
||||
await route(http.get('/data', async ({ request }) => {
|
||||
test('example test', async ({ mount, router }) => {
|
||||
await router.use(http.get('/data', async ({ request }) => {
|
||||
return HttpResponse.json({ value: 'mocked' });
|
||||
}));
|
||||
|
||||
|
|
|
|||
9
packages/playwright-ct-core/index.d.ts
vendored
9
packages/playwright-ct-core/index.d.ts
vendored
|
|
@ -38,14 +38,13 @@ interface RequestHandler {
|
|||
run(args: { request: Request, requestId?: string, resolutionContext?: { baseUrl?: string } }): Promise<{ response?: Response } | null>;
|
||||
}
|
||||
|
||||
export interface RouteFixture {
|
||||
(...args: Parameters<BrowserContext['route']>): Promise<void>;
|
||||
(handlers: RequestHandler[]): Promise<void>;
|
||||
(handler: RequestHandler): Promise<void>;
|
||||
export interface RouterFixture {
|
||||
route(...args: Parameters<BrowserContext['route']>): Promise<void>;
|
||||
use(...handlers: RequestHandler[]): Promise<void>;
|
||||
}
|
||||
|
||||
export type TestType<ComponentFixtures> = BaseTestType<
|
||||
PlaywrightTestArgs & PlaywrightTestOptions & ComponentFixtures & { route: RouteFixture },
|
||||
PlaywrightTestArgs & PlaywrightTestOptions & ComponentFixtures & { router: RouterFixture },
|
||||
PlaywrightWorkerArgs & PlaywrightWorkerOptions
|
||||
>;
|
||||
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ import type { Component, JsxComponent, MountOptions, ObjectComponentOptions } fr
|
|||
import type { ContextReuseMode, FullConfigInternal } from '../../playwright/src/common/config';
|
||||
import type { ImportRef } from './injected/importRegistry';
|
||||
import { wrapObject } from './injected/serializers';
|
||||
import { Router } from './route';
|
||||
import type { RouteFixture } from '../index';
|
||||
import { Router } from './router';
|
||||
import type { RouterFixture } from '../index';
|
||||
|
||||
let boundCallbacksForMount: Function[] = [];
|
||||
|
||||
|
|
@ -31,7 +31,7 @@ interface MountResult extends Locator {
|
|||
|
||||
type TestFixtures = PlaywrightTestArgs & PlaywrightTestOptions & {
|
||||
mount: (component: any, options: any) => Promise<MountResult>;
|
||||
route: RouteFixture;
|
||||
router: RouterFixture;
|
||||
};
|
||||
type WorkerFixtures = PlaywrightWorkerArgs & PlaywrightWorkerOptions;
|
||||
type BaseTestFixtures = {
|
||||
|
|
@ -80,9 +80,9 @@ export const fixtures: Fixtures<TestFixtures, WorkerFixtures, BaseTestFixtures>
|
|||
boundCallbacksForMount = [];
|
||||
},
|
||||
|
||||
route: async ({ context, baseURL }, use) => {
|
||||
router: async ({ context, baseURL }, use) => {
|
||||
const router = new Router(context, baseURL);
|
||||
await use((...args) => router.handle(...args));
|
||||
await use(router);
|
||||
await router.dispose();
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -134,25 +134,14 @@ export class Router {
|
|||
};
|
||||
}
|
||||
|
||||
async handle(...args: any[]) {
|
||||
// Multiple RequestHandlers.
|
||||
if (Array.isArray(args[0])) {
|
||||
const handlers = args[0] as RequestHandler[];
|
||||
this._requestHandlers = handlers.concat(this._requestHandlers);
|
||||
await this._updateRequestHandlersRoute();
|
||||
return;
|
||||
}
|
||||
// Single RequestHandler.
|
||||
if (args.length === 1 && typeof args[0] === 'object') {
|
||||
const handlers = [args[0] as RequestHandler];
|
||||
this._requestHandlers = handlers.concat(this._requestHandlers);
|
||||
await this._updateRequestHandlersRoute();
|
||||
return;
|
||||
}
|
||||
// Arguments of BrowserContext.route(url, handler, options?).
|
||||
const routeArgs = args as RouteArgs;
|
||||
async route(...routeArgs: RouteArgs) {
|
||||
this._routes.push(routeArgs);
|
||||
await this._context.route(...routeArgs);
|
||||
return await this._context.route(...routeArgs);
|
||||
}
|
||||
|
||||
async use(...handlers: RequestHandler[]) {
|
||||
this._requestHandlers = handlers.concat(this._requestHandlers);
|
||||
await this._updateRequestHandlersRoute();
|
||||
}
|
||||
|
||||
async dispose() {
|
||||
|
|
@ -26,14 +26,14 @@ test('should load font with routes', async ({ mount, page }) => {
|
|||
});
|
||||
|
||||
test.describe('request handlers', () => {
|
||||
test('should handle requests', async ({ page, mount, route }) => {
|
||||
test('should handle requests', async ({ page, mount, router }) => {
|
||||
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 route([
|
||||
await router.use(
|
||||
http.get('/data.json', async () => {
|
||||
await promise;
|
||||
return HttpResponse.json({ name: 'John Doe' });
|
||||
|
|
@ -42,7 +42,7 @@ test.describe('request handlers', () => {
|
|||
postReceived(await request.text());
|
||||
return HttpResponse.text('ok');
|
||||
}),
|
||||
]);
|
||||
);
|
||||
|
||||
const component = await mount(<Fetcher />);
|
||||
await expect(component.getByTestId('name')).toHaveText('<none>');
|
||||
|
|
@ -54,15 +54,15 @@ test.describe('request handlers', () => {
|
|||
expect(await postBody).toBe('hello from the page');
|
||||
});
|
||||
|
||||
test('should add dynamically', async ({ page, mount, route }) => {
|
||||
await route('**/data.json', async route => {
|
||||
test('should add dynamically', async ({ page, mount, router }) => {
|
||||
await router.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 route(
|
||||
await router.use(
|
||||
http.get('/data.json', async () => {
|
||||
return HttpResponse.json({ name: 'John Doe' });
|
||||
}),
|
||||
|
|
@ -72,12 +72,12 @@ test.describe('request handlers', () => {
|
|||
await expect(component.getByTestId('name')).toHaveText('John Doe');
|
||||
});
|
||||
|
||||
test('should passthrough', async ({ page, mount, route }) => {
|
||||
await route('**/data.json', async route => {
|
||||
test('should passthrough', async ({ page, mount, router }) => {
|
||||
await router.route('**/data.json', async route => {
|
||||
await route.fulfill({ body: JSON.stringify({ name: '<original>' }) });
|
||||
});
|
||||
|
||||
await route(
|
||||
await router.use(
|
||||
http.get('/data.json', async () => {
|
||||
return passthrough();
|
||||
}),
|
||||
|
|
@ -87,13 +87,13 @@ test.describe('request handlers', () => {
|
|||
await expect(component.getByTestId('name')).toHaveText('<error>');
|
||||
});
|
||||
|
||||
test('should fallback when nothing is returned', async ({ page, mount, route }) => {
|
||||
await route('**/data.json', async route => {
|
||||
test('should fallback when nothing is returned', async ({ page, mount, router }) => {
|
||||
await router.route('**/data.json', async route => {
|
||||
await route.fulfill({ body: JSON.stringify({ name: '<original>' }) });
|
||||
});
|
||||
|
||||
let called = false;
|
||||
await route(
|
||||
await router.use(
|
||||
http.get('/data.json', async () => {
|
||||
called = true;
|
||||
}),
|
||||
|
|
@ -104,12 +104,12 @@ test.describe('request handlers', () => {
|
|||
expect(called).toBe(true);
|
||||
});
|
||||
|
||||
test('should bypass(request)', async ({ page, mount, route }) => {
|
||||
await route('**/data.json', async route => {
|
||||
test('should bypass(request)', async ({ page, mount, router }) => {
|
||||
await router.route('**/data.json', async route => {
|
||||
await route.fulfill({ body: JSON.stringify({ name: `<original>` }) });
|
||||
});
|
||||
|
||||
await route(
|
||||
await router.use(
|
||||
http.get('/data.json', async ({ request }) => {
|
||||
return await fetch(bypass(request));
|
||||
}),
|
||||
|
|
@ -119,7 +119,7 @@ test.describe('request handlers', () => {
|
|||
await expect(component.getByTestId('name')).toHaveText('<error>');
|
||||
});
|
||||
|
||||
test('should bypass(url) and get cookies', async ({ page, mount, route, browserName }) => {
|
||||
test('should bypass(url) and get cookies', async ({ page, mount, router, browserName }) => {
|
||||
let cookie = '';
|
||||
const server = new httpServer.Server();
|
||||
server.on('request', (req, res) => {
|
||||
|
|
@ -129,7 +129,7 @@ test.describe('request handlers', () => {
|
|||
await new Promise<void>(f => server.listen(0, f));
|
||||
const port = (server.address() as net.AddressInfo).port;
|
||||
|
||||
await route('**/data.json', async route => {
|
||||
await router.route('**/data.json', async route => {
|
||||
await route.fulfill({ body: JSON.stringify({ name: `<original>` }) });
|
||||
});
|
||||
|
||||
|
|
@ -137,7 +137,7 @@ test.describe('request handlers', () => {
|
|||
await expect(component.getByTestId('name')).toHaveText('<original>');
|
||||
|
||||
await page.evaluate(() => document.cookie = 'foo=bar');
|
||||
await route(
|
||||
await router.use(
|
||||
http.get('/data.json', async ({ request }) => {
|
||||
if (browserName !== 'webkit') {
|
||||
// WebKit does not have cookies while intercepting.
|
||||
|
|
@ -153,12 +153,12 @@ test.describe('request handlers', () => {
|
|||
await new Promise(f => server.close(f));
|
||||
});
|
||||
|
||||
test('should ignore navigation requests', async ({ page, mount, route }) => {
|
||||
await route('**/newpage', async route => {
|
||||
test('should ignore navigation requests', async ({ page, mount, router }) => {
|
||||
await router.route('**/newpage', async route => {
|
||||
await route.fulfill({ body: `<div>original</div>`, contentType: 'text/html' });
|
||||
});
|
||||
|
||||
await route(
|
||||
await router.use(
|
||||
http.get('/newpage', async ({ request }) => {
|
||||
return new Response(`<div>intercepted</div>`, {
|
||||
headers: new Headers({ 'Content-Type': 'text/html' }),
|
||||
|
|
@ -171,11 +171,8 @@ test.describe('request handlers', () => {
|
|||
await expect(page.locator('div')).toHaveText('original');
|
||||
});
|
||||
|
||||
test('should throw when calling fetch(bypass) outside of a handler', async ({ page, route, baseURL }) => {
|
||||
await route(
|
||||
http.get('/data.json', async () => {
|
||||
}),
|
||||
);
|
||||
test('should throw when calling fetch(bypass) outside of a handler', async ({ page, router, baseURL }) => {
|
||||
await router.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