diff --git a/packages/playwright-core/src/client/browserContext.ts b/packages/playwright-core/src/client/browserContext.ts index 20d91bf305..150e9581d6 100644 --- a/packages/playwright-core/src/client/browserContext.ts +++ b/packages/playwright-core/src/client/browserContext.ts @@ -144,7 +144,7 @@ export class BrowserContext extends ChannelOwner }); this._channel.on('request', ({ request, page }) => this._onRequest(network.Request.from(request), Page.fromNullable(page))); this._channel.on('requestFailed', ({ request, failureText, responseEndTiming, page }) => this._onRequestFailed(network.Request.from(request), responseEndTiming, failureText, Page.fromNullable(page))); - this._channel.on('requestFinished', params => this._onRequestFinished(params)); + this._channel.on('requestFinished', ({ request, response, page, responseEndTiming }) => this._onRequestFinished(network.Request.from(request), network.Response.fromNullable(response), Page.fromNullable(page), responseEndTiming)); this._channel.on('response', ({ response, page }) => this._onResponse(network.Response.from(response), Page.fromNullable(page))); this._closedPromise = new Promise(f => this.once(Events.BrowserContext.Close, f)); @@ -165,27 +165,27 @@ export class BrowserContext extends ChannelOwner this.tracing._tracesDir = browserOptions.tracesDir; } - private async _onPage(page: Page): Promise{ + private _onPage(page: Page): void { this._pages.add(page); this.emit(Events.BrowserContext.Page, page); - await this._mockingProxy?.instrumentPage(page); if (page._opener && !page._opener.isClosed()) page._opener.emit(Events.Page.Popup, page); + this._mockingProxy?.instrumentPage(page); } - private _onRequest(request: network.Request, page: Page | null) { + _onRequest(request: network.Request, page: Page | null) { this.emit(Events.BrowserContext.Request, request); if (page) page.emit(Events.Page.Request, request); } - private _onResponse(response: network.Response, page: Page | null) { + _onResponse(response: network.Response, page: Page | null) { this.emit(Events.BrowserContext.Response, response); if (page) page.emit(Events.Page.Response, response); } - private _onRequestFailed(request: network.Request, responseEndTiming: number, failureText: string | undefined, page: Page | null) { + _onRequestFailed(request: network.Request, responseEndTiming: number, failureText: string | undefined, page: Page | null) { request._failureText = failureText || null; request._setResponseEndTiming(responseEndTiming); this.emit(Events.BrowserContext.RequestFailed, request); @@ -193,11 +193,7 @@ export class BrowserContext extends ChannelOwner page.emit(Events.Page.RequestFailed, request); } - private _onRequestFinished(params: channels.BrowserContextRequestFinishedEvent) { - const { responseEndTiming } = params; - const request = network.Request.from(params.request); - const response = network.Response.fromNullable(params.response); - const page = Page.fromNullable(params.page); + _onRequestFinished(request: network.Request, response: network.Response | null, page: Page | null, responseEndTiming: number) { request._setResponseEndTiming(responseEndTiming); this.emit(Events.BrowserContext.RequestFinished, request); if (page) @@ -250,13 +246,6 @@ export class BrowserContext extends ChannelOwner if (this._mockingProxy) throw new Error('Multiple mocking proxies are not supported'); this._mockingProxy = mockingProxy; - this._registeredListeners.push( - eventsHelper.addEventListener(this._mockingProxy, Events.MockingProxy.Route, (route: network.Route) => { - const page = route.request()._safePage()!; - page._onRoute(route); - }), - // TODO: should we also emit `request`, `response`, `requestFinished`, `requestFailed` events? - ); } setDefaultNavigationTimeout(timeout: number | undefined) { @@ -421,15 +410,6 @@ export class BrowserContext extends ChannelOwner private async _updateInterceptionPatterns() { const patterns = network.RouteHandler.prepareInterceptionPatterns(this._routes); await this._channel.setNetworkInterceptionPatterns({ patterns }); - await this._updateMockingProxyInterceptionPatterns(); - } - - async _updateMockingProxyInterceptionPatterns() { - if (!this._mockingProxy) - return; - const pageRoutes = this.pages().flatMap(page => page._routes); - const patterns = network.RouteHandler.prepareInterceptionPatterns(this._routes.concat(pageRoutes)); - await this._mockingProxy.setInterceptionPatterns({ patterns }); } private async _updateWebSocketInterceptionPatterns() { diff --git a/packages/playwright-core/src/client/events.ts b/packages/playwright-core/src/client/events.ts index 3bf4040ad3..a074b26f3d 100644 --- a/packages/playwright-core/src/client/events.ts +++ b/packages/playwright-core/src/client/events.ts @@ -94,8 +94,4 @@ export const Events = { Console: 'console', Window: 'window', }, - - MockingProxy: { - Route: 'route', - }, }; diff --git a/packages/playwright-core/src/client/mockingProxy.ts b/packages/playwright-core/src/client/mockingProxy.ts index 91a7105f35..5bbc92d812 100644 --- a/packages/playwright-core/src/client/mockingProxy.ts +++ b/packages/playwright-core/src/client/mockingProxy.ts @@ -17,7 +17,6 @@ import * as network from './network'; import type * as channels from '@protocol/channels'; import { ChannelOwner } from './channelOwner'; import { APIRequestContext } from './fetch'; -import { Events } from './events'; import { assert } from '../utils'; import type { Page } from './page'; @@ -31,39 +30,39 @@ export class MockingProxy extends ChannelOwner { this._channel.on('route', async (params: channels.MockingProxyRouteEvent) => { const route = network.Route.from(params.route); route._context = requestContext; - this.emit(Events.MockingProxy.Route, route); + const page = route.request()._safePage()!; + await page._onRoute(route); }); this._channel.on('request', async (params: channels.MockingProxyRequestEvent) => { const page = this._pages.get(params.correlation); assert(page); const request = network.Request.from(params.request); - request._page = page; + request._pageForMockingProxy = page; + page.context()._onRequest(request, page); }); this._channel.on('requestFailed', async (params: channels.MockingProxyRequestFailedEvent) => { const request = network.Request.from(params.request); - request._failureText = params.failureText ?? null; - request._setResponseEndTiming(params.responseEndTiming); + const page = request._safePage()!; + page.context()._onRequestFailed(request, params.responseEndTiming, params.failureText, page); }); this._channel.on('requestFinished', async (params: channels.MockingProxyRequestFinishedEvent) => { const { responseEndTiming } = params; const request = network.Request.from(params.request); const response = network.Response.fromNullable(params.response); - request._setResponseEndTiming(responseEndTiming); - response?._finishedPromise.resolve(null); + const page = request._safePage()!; + page.context()._onRequestFinished(request, response, page, responseEndTiming); }); this._channel.on('response', async (params: channels.MockingProxyResponseEvent) => { - // no-op + const response = network.Response.from(params.response); + const page = response.request()._safePage()!; + page.context()._onResponse(response, page); }); } - async setInterceptionPatterns(params: channels.MockingProxySetInterceptionPatternsParams) { - await this._channel.setInterceptionPatterns(params); - } - async instrumentPage(page: Page) { const correlation = page._guid.split('@')[1]; this._pages.set(correlation, page); diff --git a/packages/playwright-core/src/client/network.ts b/packages/playwright-core/src/client/network.ts index 302edb8a91..beff573146 100644 --- a/packages/playwright-core/src/client/network.ts +++ b/packages/playwright-core/src/client/network.ts @@ -86,7 +86,7 @@ export class Request extends ChannelOwner implements ap private _actualHeadersPromise: Promise | undefined; _timing: ResourceTiming; private _fallbackOverrides: SerializedFallbackOverrides = {}; - _page: Page | null = null; + _pageForMockingProxy: Page | null = null; static from(request: channels.RequestChannel): Request { return (request as any)._object; @@ -217,7 +217,7 @@ export class Request extends ChannelOwner implements ap } _safePage(): Page | null { - return this._page ?? Frame.fromNullable(this._initializer.frame)?._page ?? null; + return this._pageForMockingProxy ?? Frame.fromNullable(this._initializer.frame)?._page ?? null; } serviceWorker(): Worker | null { diff --git a/packages/playwright-core/src/client/page.ts b/packages/playwright-core/src/client/page.ts index 2d7ecb61fd..4f00e80664 100644 --- a/packages/playwright-core/src/client/page.ts +++ b/packages/playwright-core/src/client/page.ts @@ -568,7 +568,6 @@ export class Page extends ChannelOwner implements api.Page private async _updateInterceptionPatterns() { const patterns = RouteHandler.prepareInterceptionPatterns(this._routes); await this._channel.setNetworkInterceptionPatterns({ patterns }); - await this._browserContext._updateMockingProxyInterceptionPatterns(); } private async _updateWebSocketInterceptionPatterns() { diff --git a/packages/playwright-core/src/client/playwright.ts b/packages/playwright-core/src/client/playwright.ts index 9933ce15de..0687ee4cff 100644 --- a/packages/playwright-core/src/client/playwright.ts +++ b/packages/playwright-core/src/client/playwright.ts @@ -73,4 +73,9 @@ export class Playwright extends ChannelOwner { static from(channel: channels.PlaywrightChannel): Playwright { return (channel as any)._object; } + + async _startMockingProxy() { + const { mockingProxy } = await this._connection.localUtils()._channel.newMockingProxy({}); + return (mockingProxy as any)._object; + } } diff --git a/packages/playwright-core/src/protocol/validator.ts b/packages/playwright-core/src/protocol/validator.ts index 337288a7e4..36542dfea1 100644 --- a/packages/playwright-core/src/protocol/validator.ts +++ b/packages/playwright-core/src/protocol/validator.ts @@ -363,16 +363,7 @@ scheme.MockingProxyRequestFinishedEvent = tObject({ }); scheme.MockingProxyResponseEvent = tObject({ response: tChannel(['Response']), - page: tOptional(tChannel(['Page'])), }); -scheme.MockingProxySetInterceptionPatternsParams = tObject({ - patterns: tArray(tObject({ - glob: tOptional(tString), - regexSource: tOptional(tString), - regexFlags: tOptional(tString), - })), -}); -scheme.MockingProxySetInterceptionPatternsResult = tOptional(tObject({})); scheme.RootInitializer = tOptional(tObject({})); scheme.RootInitializeParams = tObject({ sdkLanguage: tEnum(['javascript', 'python', 'java', 'csharp']), diff --git a/packages/playwright-core/src/server/dispatchers/localUtilsDispatcher.ts b/packages/playwright-core/src/server/dispatchers/localUtilsDispatcher.ts index ff26a57a2e..62323e9a64 100644 --- a/packages/playwright-core/src/server/dispatchers/localUtilsDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/localUtilsDispatcher.ts @@ -312,8 +312,7 @@ class HarBackend { redirectURL?: string, status?: number, headers?: HeadersArray, - body?: Buffer - }> { + body?: Buffer }> { let entry; try { entry = await this._harFindResponse(url, method, headers, postData); diff --git a/packages/playwright-core/src/server/dispatchers/mockingProxyDispatcher.ts b/packages/playwright-core/src/server/dispatchers/mockingProxyDispatcher.ts index 315e8f83ef..f40d06c689 100644 --- a/packages/playwright-core/src/server/dispatchers/mockingProxyDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/mockingProxyDispatcher.ts @@ -13,14 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import type { CallMetadata } from '@protocol/callMetadata'; import { MockingProxy } from '../mockingProxy'; import type { RootDispatcher } from './dispatcher'; import { Dispatcher, existingDispatcher } from './dispatcher'; import type * as channels from '@protocol/channels'; import { APIRequestContextDispatcher, RequestDispatcher, ResponseDispatcher, RouteDispatcher } from './networkDispatchers'; import type { Request, Route } from '../network'; -import { urlMatches } from '../../utils/isomorphic/urlMatch'; export class MockingProxyDispatcher extends Dispatcher implements channels.MockingProxyChannel { _type_MockingProxy = true; @@ -58,12 +56,4 @@ export class MockingProxyDispatcher extends Dispatcher { - if (params.patterns.length === 0) - return this._object.setInterceptionPatterns(undefined); - - const urlMatchers = params.patterns.map(pattern => pattern.regexSource ? new RegExp(pattern.regexSource, pattern.regexFlags!) : pattern.glob!); - this._object.setInterceptionPatterns(url => urlMatchers.some(urlMatch => urlMatches(undefined, url, urlMatch))); - } } diff --git a/packages/playwright-core/src/server/mockingProxy.ts b/packages/playwright-core/src/server/mockingProxy.ts index 8c250572f1..9dfe62e403 100644 --- a/packages/playwright-core/src/server/mockingProxy.ts +++ b/packages/playwright-core/src/server/mockingProxy.ts @@ -38,7 +38,6 @@ export class MockingProxy extends SdkObject implements RequestContext { }; fetchRequest: APIRequestContext; - private _matches?: (url: string) => boolean; private _httpServer = new WorkerHttpServer(); constructor(parent: SdkObject, requestContext: APIRequestContext) { @@ -63,10 +62,6 @@ export class MockingProxy extends SdkObject implements RequestContext { return this._httpServer.port(); } - setInterceptionPatterns(matches?: (url: string) => boolean) { - this._matches = matches; - } - private async _proxy(req: http.IncomingMessage, res: http.ServerResponse) { if (req.url?.startsWith('/')) req.url = req.url.substring(1); @@ -212,14 +207,7 @@ export class MockingProxy extends SdkObject implements RequestContext { }, }); - if (!correlation) - return await route.continue({ isFallback: false }); - - - if (this._matches?.(req.url!)) - this.emit(MockingProxy.Events.Route, route); - else - await route.continue({ isFallback: false }); + this.emit(MockingProxy.Events.Route, route); } addRouteInFlight(route: Route): void { @@ -258,7 +246,7 @@ async function collectBody(req: http.IncomingMessage) { } export class WorkerHttpServer extends HttpServer { - override _handleCORS(request: http.IncomingMessage, response: http.ServerResponse): boolean { + override handleCORS(request: http.IncomingMessage, response: http.ServerResponse): boolean { return false; } } diff --git a/packages/playwright-core/src/utils/httpServer.ts b/packages/playwright-core/src/utils/httpServer.ts index f31222ce10..256bc24a57 100644 --- a/packages/playwright-core/src/utils/httpServer.ts +++ b/packages/playwright-core/src/utils/httpServer.ts @@ -213,7 +213,7 @@ export class HttpServer { readable.pipe(response); } - _handleCORS(request: http.IncomingMessage, response: http.ServerResponse): boolean { + handleCORS(request: http.IncomingMessage, response: http.ServerResponse): boolean { if (request.method === 'OPTIONS') { response.writeHead(200); response.end(); @@ -224,7 +224,7 @@ export class HttpServer { } private _onRequest(request: http.IncomingMessage, response: http.ServerResponse) { - if (this._handleCORS(request, response)) + if (this.handleCORS(request, response)) return; request.on('error', () => response.end()); diff --git a/packages/playwright/src/index.ts b/packages/playwright/src/index.ts index 3b75a3649e..5665eb8fea 100644 --- a/packages/playwright/src/index.ts +++ b/packages/playwright/src/index.ts @@ -27,7 +27,7 @@ import type { ApiCallData, ClientInstrumentation, ClientInstrumentationListener import type { MockingProxy } from '../../playwright-core/src/client/mockingProxy'; import type { BrowserContext as BrowserContextImpl } from '../../playwright-core/src/client/browserContext'; import { currentTestInfo } from './common/globals'; -import type { LocalUtils } from 'playwright-core/lib/client/localUtils'; +import type { Playwright as PlaywrightImpl } from 'playwright-core/lib/client/playwright'; export { expect } from './matchers/expect'; export const _baseTest: TestType<{}, {}> = rootTestType.test; @@ -127,9 +127,8 @@ const playwrightFixtures: Fixtures = ({ _mockingProxy: [async ({ mockingProxy: mockingProxyOption, playwright }, use) => { if (!mockingProxyOption) return await use(undefined); - const localUtils: LocalUtils = (playwright as any)._connection.localUtils(); - const { mockingProxy } = await localUtils._channel.newMockingProxy({}); - await use((mockingProxy as any)._object); + const mockingProxy = await (playwright as PlaywrightImpl)._startMockingProxy(); + await use(mockingProxy); }, { scope: 'worker', box: true }], acceptDownloads: [({ contextOptions }, use) => use(contextOptions.acceptDownloads ?? true), { option: true }], diff --git a/packages/protocol/src/channels.d.ts b/packages/protocol/src/channels.d.ts index 3acc370d66..580a803ba8 100644 --- a/packages/protocol/src/channels.d.ts +++ b/packages/protocol/src/channels.d.ts @@ -589,7 +589,6 @@ export interface MockingProxyEventTarget { } export interface MockingProxyChannel extends MockingProxyEventTarget, EventTargetChannel { _type_MockingProxy: boolean; - setInterceptionPatterns(params: MockingProxySetInterceptionPatternsParams, metadata?: CallMetadata): Promise; } export type MockingProxyRouteEvent = { route: RouteChannel, @@ -610,19 +609,7 @@ export type MockingProxyRequestFinishedEvent = { }; export type MockingProxyResponseEvent = { response: ResponseChannel, - page?: PageChannel, }; -export type MockingProxySetInterceptionPatternsParams = { - patterns: { - glob?: string, - regexSource?: string, - regexFlags?: string, - }[], -}; -export type MockingProxySetInterceptionPatternsOptions = { - -}; -export type MockingProxySetInterceptionPatternsResult = void; export interface MockingProxyEvents { 'route': MockingProxyRouteEvent; diff --git a/packages/protocol/src/protocol.yml b/packages/protocol/src/protocol.yml index 9e4d18e5af..2aa0a34881 100644 --- a/packages/protocol/src/protocol.yml +++ b/packages/protocol/src/protocol.yml @@ -682,18 +682,6 @@ MockingProxy: port: number requestContext: APIRequestContext - commands: - setInterceptionPatterns: - parameters: - patterns: - type: array - items: - type: object - properties: - glob: string? - regexSource: string? - regexFlags: string? - events: route: parameters: @@ -719,7 +707,6 @@ MockingProxy: response: parameters: response: Response - page: Page? Root: type: interface