chore: rename route fixture in ct (#31817)

Addresses review feedback.
This commit is contained in:
Dmitry Gozman 2024-07-23 07:43:28 -07:00 committed by GitHub
parent 526e4118fa
commit e86c8af599
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 48 additions and 96 deletions

View File

@ -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' });
}));

View File

@ -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
>;

View File

@ -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();
},
};

View File

@ -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() {

View File

@ -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`);