From f3e0c20f75d04e709eadc1e4ae8575076e39d4f2 Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Mon, 27 Jan 2025 14:11:44 +0100 Subject: [PATCH] inject page info --- .../src/client/browserContext.ts | 11 +++++++++-- .../src/client/mockingProxy.ts | 19 +++++++++++++++++-- .../playwright-core/src/protocol/validator.ts | 1 + .../dispatchers/mockingProxyDispatcher.ts | 4 ++-- .../src/server/mockingProxy.ts | 8 +++++++- packages/playwright/src/index.ts | 4 +--- packages/protocol/src/channels.d.ts | 1 + packages/protocol/src/protocol.yml | 1 + .../playwright.mockingproxy.spec.ts | 4 +--- 9 files changed, 40 insertions(+), 13 deletions(-) diff --git a/packages/playwright-core/src/client/browserContext.ts b/packages/playwright-core/src/client/browserContext.ts index 7b810518dd..e4512747c7 100644 --- a/packages/playwright-core/src/client/browserContext.ts +++ b/packages/playwright-core/src/client/browserContext.ts @@ -228,7 +228,13 @@ export class BrowserContext extends ChannelOwner 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) { const routeHandler = this._webSocketRoutes.find(route => route.matches(webSocketRoute.url())); @@ -245,11 +251,12 @@ export class BrowserContext extends ChannelOwner await bindingCall.call(func); } - _subscribeToMockingProxy(mockingProxy: MockingProxy) { + async _subscribeToMockingProxy(mockingProxy: MockingProxy) { if (this._mockingProxy) throw new Error('Multiple mocking proxies are not supported'); this._mockingProxy = mockingProxy; this._mockingProxy.on(Events.MockingProxy.Route, this._onRouteListener); + await this.route('**', (route: network.Route) => this._mockingProxy!.instrumentBrowserRequest(route)); } setDefaultNavigationTimeout(timeout: number | undefined) { diff --git a/packages/playwright-core/src/client/mockingProxy.ts b/packages/playwright-core/src/client/mockingProxy.ts index 5d95125491..92fa87ce7a 100644 --- a/packages/playwright-core/src/client/mockingProxy.ts +++ b/packages/playwright-core/src/client/mockingProxy.ts @@ -21,6 +21,7 @@ import { Events } from './events'; export class MockingProxy extends ChannelOwner { private _port: number; + private _browserRequests = new Map(); constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.MockingProxyInitializer) { super(parent, type, guid, initializer); @@ -28,10 +29,13 @@ export class MockingProxy extends ChannelOwner { this._port = initializer.port; 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); 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 { 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 { return this._port; } diff --git a/packages/playwright-core/src/protocol/validator.ts b/packages/playwright-core/src/protocol/validator.ts index 5cce382272..3588e848e5 100644 --- a/packages/playwright-core/src/protocol/validator.ts +++ b/packages/playwright-core/src/protocol/validator.ts @@ -348,6 +348,7 @@ scheme.MockingProxyInitializer = tObject({ }); scheme.MockingProxyRouteEvent = tObject({ route: tChannel(['Route']), + browserRequestRoute: tOptional(tString), }); scheme.MockingProxySetInterceptionPatternsParams = tObject({ patterns: tArray(tObject({ diff --git a/packages/playwright-core/src/server/dispatchers/mockingProxyDispatcher.ts b/packages/playwright-core/src/server/dispatchers/mockingProxyDispatcher.ts index 5a77a37de4..6ad0237c03 100644 --- a/packages/playwright-core/src/server/dispatchers/mockingProxyDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/mockingProxyDispatcher.ts @@ -36,9 +36,9 @@ export class MockingProxyDispatcher extends Dispatcher { + this.addObjectListener(MockingProxy.Events.Route, ({ route, browserRequestRoute }: {route: Route, browserRequestRoute?: string }) => { 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 }); }); } diff --git a/packages/playwright-core/src/server/mockingProxy.ts b/packages/playwright-core/src/server/mockingProxy.ts index c693895e99..8ffa046dc6 100644 --- a/packages/playwright-core/src/server/mockingProxy.ts +++ b/packages/playwright-core/src/server/mockingProxy.ts @@ -70,6 +70,12 @@ export class MockingProxy extends SdkObject implements RequestContext { if (req.url?.startsWith('/')) 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. if (req.url?.startsWith('http:/') && !req.url?.startsWith('http://')) req.url = req.url.replace('http:/', 'http://'); @@ -203,7 +209,7 @@ export class MockingProxy extends SdkObject implements RequestContext { }); if (this._matches?.(req.url!)) - this.emit(MockingProxy.Events.Route, route); + this.emit(MockingProxy.Events.Route, { route, browserRequestRoute }); else await route.continue({ isFallback: false }); } diff --git a/packages/playwright/src/index.ts b/packages/playwright/src/index.ts index b1f13492ab..eec7ccfb93 100644 --- a/packages/playwright/src/index.ts +++ b/packages/playwright/src/index.ts @@ -238,8 +238,6 @@ const playwrightFixtures: Fixtures = ({ options.baseURL = baseURL; if (serviceWorkers !== undefined) options.serviceWorkers = serviceWorkers; - if (_mockingProxy) - options.extraHTTPHeaders = { ...options.extraHTTPHeaders, 'x-playwright-proxy': encodeURIComponent(`http://localhost:${_mockingProxy.port()}/`) }; await use({ ...contextOptions, ...options, @@ -372,7 +370,7 @@ const playwrightFixtures: Fixtures = ({ } : {}; const context = await browser.newContext({ ...videoOptions, ...options }) as BrowserContextImpl; if (_mockingProxy) - context._subscribeToMockingProxy(_mockingProxy); + await context._subscribeToMockingProxy(_mockingProxy); const contextData: { pagesWithVideo: Page[] } = { pagesWithVideo: [] }; contexts.set(context, contextData); if (captureVideo) diff --git a/packages/protocol/src/channels.d.ts b/packages/protocol/src/channels.d.ts index 9e7d597158..aafe0f343e 100644 --- a/packages/protocol/src/channels.d.ts +++ b/packages/protocol/src/channels.d.ts @@ -593,6 +593,7 @@ export interface MockingProxyChannel extends MockingProxyEventTarget, EventTarge } export type MockingProxyRouteEvent = { route: RouteChannel, + browserRequestRoute?: string, }; export type MockingProxySetInterceptionPatternsParams = { patterns: { diff --git a/packages/protocol/src/protocol.yml b/packages/protocol/src/protocol.yml index 7d24e97df3..6178c6e04d 100644 --- a/packages/protocol/src/protocol.yml +++ b/packages/protocol/src/protocol.yml @@ -700,6 +700,7 @@ MockingProxy: route: parameters: route: Route + browserRequestRoute: string? Root: type: interface diff --git a/tests/playwright-test/playwright.mockingproxy.spec.ts b/tests/playwright-test/playwright.mockingproxy.spec.ts index 8237c0cb26..76eb63caf9 100644 --- a/tests/playwright-test/playwright.mockingproxy.spec.ts +++ b/tests/playwright-test/playwright.mockingproxy.spec.ts @@ -31,11 +31,9 @@ test('inject mode', async ({ runInlineTest, server }) => { `, 'a.test.ts': ` import { test, expect } from '@playwright/test'; - test('foo', async ({ page, request }) => { + test('foo', async ({ page }) => { await page.goto('${server.PREFIX}/page'); 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 });