inject page info

This commit is contained in:
Simon Knott 2025-01-27 14:11:44 +01:00
parent f2cba29b85
commit f3e0c20f75
No known key found for this signature in database
GPG key ID: 8CEDC00028084AEC
9 changed files with 40 additions and 13 deletions

View file

@ -228,7 +228,13 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
await route._innerContinue(true /* isFallback */).catch(() => {}); await route._innerContinue(true /* isFallback */).catch(() => {});
} }
private _onRouteListener = (route: network.Route) => this._onRoute(route); private _onRouteListener = ({ route, browserRequest }: { route: network.Route, browserRequest: network.Route }) => {
const page = browserRequest.request()._safePage();
if (page)
page._onRoute(route);
else
this._onRoute(route);
};
async _onWebSocketRoute(webSocketRoute: network.WebSocketRoute) { async _onWebSocketRoute(webSocketRoute: network.WebSocketRoute) {
const routeHandler = this._webSocketRoutes.find(route => route.matches(webSocketRoute.url())); const routeHandler = this._webSocketRoutes.find(route => route.matches(webSocketRoute.url()));
@ -245,11 +251,12 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
await bindingCall.call(func); await bindingCall.call(func);
} }
_subscribeToMockingProxy(mockingProxy: MockingProxy) { async _subscribeToMockingProxy(mockingProxy: MockingProxy) {
if (this._mockingProxy) if (this._mockingProxy)
throw new Error('Multiple mocking proxies are not supported'); throw new Error('Multiple mocking proxies are not supported');
this._mockingProxy = mockingProxy; this._mockingProxy = mockingProxy;
this._mockingProxy.on(Events.MockingProxy.Route, this._onRouteListener); this._mockingProxy.on(Events.MockingProxy.Route, this._onRouteListener);
await this.route('**', (route: network.Route) => this._mockingProxy!.instrumentBrowserRequest(route));
} }
setDefaultNavigationTimeout(timeout: number | undefined) { setDefaultNavigationTimeout(timeout: number | undefined) {

View file

@ -21,6 +21,7 @@ import { Events } from './events';
export class MockingProxy extends ChannelOwner<channels.MockingProxyChannel> { export class MockingProxy extends ChannelOwner<channels.MockingProxyChannel> {
private _port: number; private _port: number;
private _browserRequests = new Map<string, network.Route>();
constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.MockingProxyInitializer) { constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.MockingProxyInitializer) {
super(parent, type, guid, initializer); super(parent, type, guid, initializer);
@ -28,10 +29,13 @@ export class MockingProxy extends ChannelOwner<channels.MockingProxyChannel> {
this._port = initializer.port; this._port = initializer.port;
const requestContext = APIRequestContext.from(initializer.requestContext); const requestContext = APIRequestContext.from(initializer.requestContext);
this._channel.on('route', (params: channels.MockingProxyRouteEvent) => { this._channel.on('route', async (params: channels.MockingProxyRouteEvent) => {
const browserRequest = params.browserRequestRoute ? this._browserRequests.get(params.browserRequestRoute) : undefined;
if (params.browserRequestRoute)
this._browserRequests.delete(params.browserRequestRoute);
const route = network.Route.from(params.route); const route = network.Route.from(params.route);
route._context = requestContext; route._context = requestContext;
this.emit(Events.MockingProxy.Route, route); this.emit(Events.MockingProxy.Route, { route, browserRequest });
}); });
} }
@ -39,6 +43,17 @@ export class MockingProxy extends ChannelOwner<channels.MockingProxyChannel> {
await this._channel.setInterceptionPatterns(params); await this._channel.setInterceptionPatterns(params);
} }
async instrumentBrowserRequest(route: network.Route) {
const isSimpleCORS = false; // TODO: implement simple CORS
if (isSimpleCORS)
return await route.continue();
this._browserRequests.set(route._guid, route);
const proxyUrl = `http://localhost:${this.port()}/pw_meta:${route._guid}/`;
await route.continue({ headers: { 'x-playwright-proxy': encodeURIComponent(proxyUrl) } });
}
port(): number { port(): number {
return this._port; return this._port;
} }

View file

@ -348,6 +348,7 @@ scheme.MockingProxyInitializer = tObject({
}); });
scheme.MockingProxyRouteEvent = tObject({ scheme.MockingProxyRouteEvent = tObject({
route: tChannel(['Route']), route: tChannel(['Route']),
browserRequestRoute: tOptional(tString),
}); });
scheme.MockingProxySetInterceptionPatternsParams = tObject({ scheme.MockingProxySetInterceptionPatternsParams = tObject({
patterns: tArray(tObject({ patterns: tArray(tObject({

View file

@ -36,9 +36,9 @@ export class MockingProxyDispatcher extends Dispatcher<MockingProxy, channels.Mo
requestContext: APIRequestContextDispatcher.from(scope, mockingProxy.fetchRequest), requestContext: APIRequestContextDispatcher.from(scope, mockingProxy.fetchRequest),
}); });
this.addObjectListener(MockingProxy.Events.Route, (route: Route) => { this.addObjectListener(MockingProxy.Events.Route, ({ route, browserRequestRoute }: {route: Route, browserRequestRoute?: string }) => {
const requestDispatcher = RequestDispatcher.from(this as any, route.request()); const requestDispatcher = RequestDispatcher.from(this as any, route.request());
this._dispatchEvent('route', { route: RouteDispatcher.from(requestDispatcher, route) }); this._dispatchEvent('route', { route: RouteDispatcher.from(requestDispatcher, route), browserRequestRoute });
}); });
} }

View file

@ -70,6 +70,12 @@ export class MockingProxy extends SdkObject implements RequestContext {
if (req.url?.startsWith('/')) if (req.url?.startsWith('/'))
req.url = req.url.substring(1); req.url = req.url.substring(1);
let browserRequestRoute: string | undefined;
if (req.url?.startsWith('pw_meta:')) {
browserRequestRoute = req.url.substring('pw_meta:'.length, req.url.indexOf('/'));
req.url = req.url.substring(req.url.indexOf('/') + 1);
}
// Java URL likes removing double slashes from the pathname. // Java URL likes removing double slashes from the pathname.
if (req.url?.startsWith('http:/') && !req.url?.startsWith('http://')) if (req.url?.startsWith('http:/') && !req.url?.startsWith('http://'))
req.url = req.url.replace('http:/', 'http://'); req.url = req.url.replace('http:/', 'http://');
@ -203,7 +209,7 @@ export class MockingProxy extends SdkObject implements RequestContext {
}); });
if (this._matches?.(req.url!)) if (this._matches?.(req.url!))
this.emit(MockingProxy.Events.Route, route); this.emit(MockingProxy.Events.Route, { route, browserRequestRoute });
else else
await route.continue({ isFallback: false }); await route.continue({ isFallback: false });
} }

View file

@ -238,8 +238,6 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
options.baseURL = baseURL; options.baseURL = baseURL;
if (serviceWorkers !== undefined) if (serviceWorkers !== undefined)
options.serviceWorkers = serviceWorkers; options.serviceWorkers = serviceWorkers;
if (_mockingProxy)
options.extraHTTPHeaders = { ...options.extraHTTPHeaders, 'x-playwright-proxy': encodeURIComponent(`http://localhost:${_mockingProxy.port()}/`) };
await use({ await use({
...contextOptions, ...contextOptions,
...options, ...options,
@ -372,7 +370,7 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
} : {}; } : {};
const context = await browser.newContext({ ...videoOptions, ...options }) as BrowserContextImpl; const context = await browser.newContext({ ...videoOptions, ...options }) as BrowserContextImpl;
if (_mockingProxy) if (_mockingProxy)
context._subscribeToMockingProxy(_mockingProxy); await context._subscribeToMockingProxy(_mockingProxy);
const contextData: { pagesWithVideo: Page[] } = { pagesWithVideo: [] }; const contextData: { pagesWithVideo: Page[] } = { pagesWithVideo: [] };
contexts.set(context, contextData); contexts.set(context, contextData);
if (captureVideo) if (captureVideo)

View file

@ -593,6 +593,7 @@ export interface MockingProxyChannel extends MockingProxyEventTarget, EventTarge
} }
export type MockingProxyRouteEvent = { export type MockingProxyRouteEvent = {
route: RouteChannel, route: RouteChannel,
browserRequestRoute?: string,
}; };
export type MockingProxySetInterceptionPatternsParams = { export type MockingProxySetInterceptionPatternsParams = {
patterns: { patterns: {

View file

@ -700,6 +700,7 @@ MockingProxy:
route: route:
parameters: parameters:
route: Route route: Route
browserRequestRoute: string?
Root: Root:
type: interface type: interface

View file

@ -31,11 +31,9 @@ test('inject mode', async ({ runInlineTest, server }) => {
`, `,
'a.test.ts': ` 'a.test.ts': `
import { test, expect } from '@playwright/test'; import { test, expect } from '@playwright/test';
test('foo', async ({ page, request }) => { test('foo', async ({ page }) => {
await page.goto('${server.PREFIX}/page'); await page.goto('${server.PREFIX}/page');
expect(await page.textContent('body')).toEqual('proxy url injected'); expect(await page.textContent('body')).toEqual('proxy url injected');
const response = await request.get('${server.PREFIX}/page');
expect(await response.text()).toEqual('proxy url injected');
}); });
` `
}, { workers: 1 }); }, { workers: 1 });