diff --git a/docs/src/api/class-mockingproxy.md b/docs/src/api/class-mockingproxy.md deleted file mode 100644 index f16883d23e..0000000000 --- a/docs/src/api/class-mockingproxy.md +++ /dev/null @@ -1,453 +0,0 @@ -# class: MockingProxy -* since: v1.51 - -`MockingProxy` allows you to intercept network traffic from your application server. - -```js -const { webkit, mockingProxy } = require('playwright'); // Or 'chromium' or 'firefox'. - -(async () => { - const browser = await webkit.launch(); - const context = await browser.newContext(); - const server = await mockingProxy.newProxy(8888); // point your application server to MockingProxy all requests through this port - - await server.route("https://headless-cms.example.com/posts", (route, request) => { - await route.fulfill({ - json: [ - { id: 1, title: 'Hello, World!' }, - { id: 2, title: 'Second post' }, - { id: 2, title: 'Third post' } - ] - }); - }) - - const page = await context.newPage(); - await page.goto('https://localhost:3000/posts'); - - console.log(await page.getByRole('list').ariaSnapshot()) - // - list: - // - listitem: Hello, World! - // - listitem: Second post - // - listitem: Third post -})(); -``` - -## async method: MockingProxy.route -* since: v1.51 - - -Routing provides the capability to modify network requests that are made through the MockingProxy. - -Once routing is enabled, every request matching the url pattern will stall unless it's continued, fulfilled or aborted. - -**Usage** - -An example of a naive handler that aborts all requests to a specific domain: - -```js -const page = await browser.newPage(); -const server = await page.context().newMockingProxy(8888) -await server.route('https://api.example.com', route => route.abort()); // simulates this API being unreachable -await page.goto('http://localhost:3000'); -``` - -It is possible to examine the request to decide the route action. For example, mocking all requests that contain some post data, and leaving all other requests as is: - -```js -await serer.route('https://api.example.com/*', async route => { - if (route.request().postData().includes('my-string')) - await route.fulfill({ body: 'mocked-data' }); - else - await route.continue(); -}) -``` - -To remove a route with its handler you can use [`method: MockingProxy.unroute`]. - -### param: MockingProxy.route.url -* since: v1.51 -- `url` <[string]|[RegExp]|[function]\([URL]\):[boolean]> - -A glob pattern, regex pattern or predicate receiving [URL] to match while routing. -When a [`option: Browser.newContext.baseURL`] via the context options was provided and the passed URL is a path, -it gets merged via the [`new URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) constructor. - -### param: MockingProxy.route.handler -* since: v1.51 -* langs: js, python -- `handler` <[function]\([Route], [Request]\): [Promise|any]> - -handler function to route the request. - -### param: MockingProxy.route.handler -* since: v1.51 -* langs: csharp, java -- `handler` <[function]\([Route]\)> - -handler function to route the request. - -### option: MockingProxy.route.times -* since: v1.51 -- `times` <[int]> - -How often a route should be used. By default it will be used every time. - -## async method: MockingProxy.unrouteAll -* since: v1.51 - -Removes all routes created with [`method: MockingProxy.route`]. - -### option: MockingProxy.unrouteAll.behavior = %%-unroute-all-options-behavior-%% -* since: v1.51 - -## async method: MockingProxy.unroute -* since: v1.51 - -Removes a route created with [`method: MockingProxy.route`]. When [`param: handler`] is not specified, removes all -routes for the [`param: url`]. - -### param: MockingProxy.unroute.url -* since: v1.51 -- `url` <[string]|[RegExp]|[function]\([URL]\):[boolean]> - -A glob pattern, regex pattern or predicate receiving [URL] used to register a routing with -[`method: MockingProxy.route`]. - -### param: MockingProxy.unroute.handler -* since: v1.51 -* langs: js, python -- `handler` ?<[function]\([Route], [Request]\): [Promise|any]> - -Optional handler function used to register a routing with [`method: MockingProxy.route`]. - -### param: MockingProxy.unroute.handler -* since: v1.51 -* langs: csharp, java -- `handler` ?<[function]\([Route]\)> - -Optional handler function used to register a routing with [`method: MockingProxy.route`]. - -## event: MockingProxy.request -* since: v1.51 -- argument: <[Request]> - -Emitted when a request passes through the MockingProxy. The [request] object is read-only. In order to intercept and mutate requests, see -[`method: MockingProxy.route`]. - -## event: MockingProxy.requestfailed -* since: v1.51 -- argument: <[Request]> - -Emitted when a request fails, for example by timing out. - -## event: MockingProxy.requestfinished -* since: v1.51 -- argument: <[Request]> - -Emitted when a request finishes successfully after downloading the response body. For a successful response, the -sequence of events is `request`, `response` and `requestfinished`. - -## event: MockingProxy.response -* since: v1.51 -- argument: <[Response]> - -Emitted when [response] status and headers are received for a request. For a successful response, the sequence of events -is `request`, `response` and `requestfinished`. - -## async method: MockingProxy.waitForEvent -* since: v1.51 -* langs: js, python - - alias-python: expect_event -- returns: <[any]> - -Waits for event to fire and passes its value into the predicate function. Returns when the predicate returns truthy -value. Will throw an error if the page is closed before the event is fired. Returns the event data value. - -**Usage** - -```js -const requestPromise = MockingProxy.waitForEvent('request'); -await page.getByText('Download file').click(); -const download = await requestPromise; -``` - -```python async -async with MockingProxy.expect_event("request") as event_info: - await page.get_by_role("button") -frame = await event_info.value -``` - -```python sync -with MockingProxy.expect_event("request") as event_info: - page.get_by_role("button") -frame = event_info.value -``` - -### param: MockingProxy.waitForEvent.event = %%-wait-for-event-event-%% -* since: v1.51 - -### param: MockingProxy.waitForEvent.optionsOrPredicate -* since: v1.51 -* langs: js -- `optionsOrPredicate` ?<[function]|[Object]> - - `predicate` <[function]> Receives the event data and resolves to truthy value when the waiting should resolve. - - `timeout` ?<[float]> Maximum time to wait for in milliseconds. Defaults to `0` - no timeout. - -Either a predicate that receives an event or an options object. Optional. - -### option: MockingProxy.waitForEvent.predicate = %%-wait-for-event-predicate-%% -* since: v1.51 - -### option: MockingProxy.waitForEvent.timeout = %%-wait-for-event-timeout-%% -* since: v1.51 - -## async method: MockingProxy.waitForRequest -* since: v1.51 -* langs: - * alias-python: expect_request - * alias-csharp: RunAndWaitForRequest -- returns: <[Request]> - -Waits for the matching request and returns it. See [waiting for event](../events.md#waiting-for-event) for more details about events. - -**Usage** - -```js -// Start waiting for request before clicking. Note no await. -const requestPromise = MockingProxy.waitForRequest('https://example.com/resource'); -await page.getByText('trigger request').click(); -const request = await requestPromise; - -// Alternative way with a predicate. Note no await. -const requestPromise = MockingProxy.waitForRequest(request => - request.url() === 'https://example.com' && request.method() === 'GET', -); -await page.getByText('trigger request').click(); -const request = await requestPromise; -``` - -```java -// Waits for the next request with the specified url -Request request = MockingProxy.waitForRequest("https://example.com/resource", () -> { - // Triggers the request - page.getByText("trigger request").click(); -}); - -// Waits for the next request matching some conditions -Request request = MockingProxy.waitForRequest(request -> "https://example.com".equals(request.url()) && "GET".equals(request.method()), () -> { - // Triggers the request - page.getByText("trigger request").click(); -}); -``` - -```python async -async with MockingProxy.expect_request("http://example.com/resource") as first: - await page.get_by_text("trigger request").click() -first_request = await first.value - -# or with a lambda -async with MockingProxy.expect_request(lambda request: request.url == "http://example.com" and request.method == "get") as second: - await page.get_by_text("trigger request").click() -second_request = await second.value -``` - -```python sync -with MockingProxy.expect_request("http://example.com/resource") as first: - page.get_by_text("trigger request").click() -first_request = first.value - -# or with a lambda -with MockingProxy.expect_request(lambda request: request.url == "http://example.com" and request.method == "get") as second: - page.get_by_text("trigger request").click() -second_request = second.value -``` - -```csharp -// Waits for the next request with the specified url. -await MockingProxy.RunAndWaitForRequestAsync(async () => -{ - await page.GetByText("trigger request").ClickAsync(); -}, "http://example.com/resource"); - -// Alternative way with a predicate. -await MockingProxy.RunAndWaitForRequestAsync(async () => -{ - await page.GetByText("trigger request").ClickAsync(); -}, request => request.Url == "https://example.com" && request.Method == "GET"); -``` - -## async method: MockingProxy.waitForRequest -* since: v1.51 -* langs: python -- returns: <[EventContextManager]<[Request]>> - -### param: MockingProxy.waitForRequest.action = %%-csharp-wait-for-event-action-%% -* since: v1.51 - -### param: MockingProxy.waitForRequest.urlOrPredicate -* since: v1.51 -- `urlOrPredicate` <[string]|[RegExp]|[function]\([Request]\):[boolean]> - -Request URL string, regex or predicate receiving [Request] object. - -### param: MockingProxy.waitForRequest.urlOrPredicate -* since: v1.51 -* langs: js -- `urlOrPredicate` <[string]|[RegExp]|[function]\([Request]\):[boolean]|[Promise]<[boolean]>> - -Request URL string, regex or predicate receiving [Request] object. - -### option: MockingProxy.waitForRequest.timeout -* since: v1.51 -- `timeout` <[float]> - -Maximum wait time in milliseconds, defaults to 30 seconds, pass `0` to disable the timeout. The default value can be -changed by using the [`method: Page.setDefaultTimeout`] method. - -### param: MockingProxy.waitForRequest.callback = %%-java-wait-for-event-callback-%% -* since: v1.51 - -## async method: MockingProxy.waitForRequestFinished -* since: v1.51 -* langs: java, python, csharp - - alias-python: expect_request_finished - - alias-csharp: RunAndWaitForRequestFinished -- returns: <[Request]> - -Performs action and waits for a [Request] to finish loading. If predicate is provided, it passes -[Request] value into the `predicate` function and waits for `predicate(request)` to return a truthy value. - -## async method: MockingProxy.waitForRequestFinished -* since: v1.51 -* langs: python -- returns: <[EventContextManager]<[Request]>> - -### param: MockingProxy.waitForRequestFinished.action = %%-csharp-wait-for-event-action-%% -* since: v1.51 - -### option: MockingProxy.waitForRequestFinished.predicate -* since: v1.51 -- `predicate` <[function]\([Request]\):[boolean]> - -Receives the [Request] object and resolves to truthy value when the waiting should resolve. - -### option: MockingProxy.waitForRequestFinished.timeout = %%-wait-for-event-timeout-%% -* since: v1.51 - -### param: MockingProxy.waitForRequestFinished.callback = %%-java-wait-for-event-callback-%% -* since: v1.51 - -## async method: MockingProxy.waitForResponse -* since: v1.51 -* langs: - * alias-python: expect_response - * alias-csharp: RunAndWaitForResponse -- returns: <[Response]> - -Returns the matched response. See [waiting for event](../events.md#waiting-for-event) for more details about events. - -**Usage** - -```js -// Start waiting for response before clicking. Note no await. -const responsePromise = MockingProxy.waitForResponse('https://example.com/resource'); -await page.getByText('trigger response').click(); -const response = await responsePromise; - -// Alternative way with a predicate. Note no await. -const responsePromise = MockingProxy.waitForResponse(response => - response.url() === 'https://example.com' && response.status() === 200 - && response.request().method() === 'GET' -); -await page.getByText('trigger response').click(); -const response = await responsePromise; -``` - -```java -// Waits for the next response with the specified url -Response response = MockingProxy.waitForResponse("https://example.com/resource", () -> { - // Triggers the response - page.getByText("trigger response").click(); -}); - -// Waits for the next response matching some conditions -Response response = MockingProxy.waitForResponse(response -> "https://example.com".equals(response.url()) && response.status() == 200 && "GET".equals(response.request().method()), () -> { - // Triggers the response - page.getByText("trigger response").click(); -}); -``` - -```python async -async with MockingProxy.expect_response("https://example.com/resource") as response_info: - await page.get_by_text("trigger response").click() -response = await response_info.value -return response.ok - -# or with a lambda -async with MockingProxy.expect_response(lambda response: response.url == "https://example.com" and response.status == 200 and response.request.method == "get") as response_info: - await page.get_by_text("trigger response").click() -response = await response_info.value -return response.ok -``` - -```python sync -with MockingProxy.expect_response("https://example.com/resource") as response_info: - page.get_by_text("trigger response").click() -response = response_info.value -return response.ok - -# or with a lambda -with MockingProxy.expect_response(lambda response: response.url == "https://example.com" and response.status == 200 and response.request.method == "get") as response_info: - page.get_by_text("trigger response").click() -response = response_info.value -return response.ok -``` - -```csharp -// Waits for the next response with the specified url. -await MockingProxy.RunAndWaitForResponseAsync(async () => -{ - await page.GetByText("trigger response").ClickAsync(); -}, "http://example.com/resource"); - -// Alternative way with a predicate. -await MockingProxy.RunAndWaitForResponseAsync(async () => -{ - await page.GetByText("trigger response").ClickAsync(); -}, response => response.Url == "https://example.com" && response.Status == 200 && response.Request.Method == "GET"); -``` - -## async method: MockingProxy.waitForResponse -* since: v1.51 -* langs: python -- returns: <[EventContextManager]<[Response]>> - -### param: MockingProxy.waitForResponse.action = %%-csharp-wait-for-event-action-%% -* since: v1.51 - -### param: MockingProxy.waitForResponse.urlOrPredicate -* since: v1.51 -- `urlOrPredicate` <[string]|[RegExp]|[function]\([Response]\):[boolean]> - -Request URL string, regex or predicate receiving [Response] object. - -### param: MockingProxy.waitForResponse.urlOrPredicate -* since: v1.51 -* langs: js -- `urlOrPredicate` <[string]|[RegExp]|[function]\([Response]\):[boolean]|[Promise]<[boolean]>> - -Request URL string, regex or predicate receiving [Response] object. - -### option: MockingProxy.waitForResponse.timeout -* since: v1.51 -- `timeout` <[float]> - -Maximum wait time in milliseconds, defaults to 30 seconds, pass `0` to disable the timeout. - -### param: MockingProxy.waitForResponse.callback = %%-java-wait-for-event-callback-%% -* since: v1.51 - -## method: MockingProxy.port -* since: v1.51 -- returns: <[int]> - diff --git a/docs/src/api/class-mockingproxyfactory.md b/docs/src/api/class-mockingproxyfactory.md deleted file mode 100644 index 16fbe16951..0000000000 --- a/docs/src/api/class-mockingproxyfactory.md +++ /dev/null @@ -1,18 +0,0 @@ -# class: MockingProxyFactory -* since: v1.51 - -This class is used for creating [MockingProxy] instances which in turn can be used to intercept network traffic from your application server. An instance -of this class can be obtained via [`property: Playwright.mockingProxy`]. For more information -see [MockingProxy]. - -## async method: MockingProxyFactory.newProxy -* since: v1.51 -- returns: <[MockingProxy]> - -Creates a new instance of [MockingProxy]. - -### param: MockingProxyFactory.newProxy.port -* since: v1.51 -- `port` ?<[int]> - -Port to listen on. diff --git a/packages/playwright-core/src/client/browserContext.ts b/packages/playwright-core/src/client/browserContext.ts index d28b2cc713..59dcf046be 100644 --- a/packages/playwright-core/src/client/browserContext.ts +++ b/packages/playwright-core/src/client/browserContext.ts @@ -44,6 +44,7 @@ import { Dialog } from './dialog'; import { WebError } from './webError'; import { TargetClosedError, parseError } from './errors'; import { Clock } from './clock'; +import type { MockingProxy } from './mockingProxy'; export class BrowserContext extends ChannelOwner implements api.BrowserContext { _pages = new Set(); @@ -69,6 +70,8 @@ export class BrowserContext extends ChannelOwner private _closeReason: string | undefined; private _harRouters: HarRouter[] = []; + _mockingProxies = new Set(); + static from(context: channels.BrowserContextChannel): BrowserContext { return (context as any)._object; } @@ -400,6 +403,8 @@ export class BrowserContext extends ChannelOwner private async _updateInterceptionPatterns() { const patterns = network.RouteHandler.prepareInterceptionPatterns(this._routes); await this._channel.setNetworkInterceptionPatterns({ patterns }); + for (const proxy of this._mockingProxies) + await proxy.setInterceptionPatterns({ patterns }); } private async _updateWebSocketInterceptionPatterns() { diff --git a/packages/playwright-core/src/client/mockingProxy.ts b/packages/playwright-core/src/client/mockingProxy.ts index 83012bd33d..3a5869e3a4 100644 --- a/packages/playwright-core/src/client/mockingProxy.ts +++ b/packages/playwright-core/src/client/mockingProxy.ts @@ -13,34 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import type * as api from '../../types/types'; import * as network from './network'; -import { urlMatches, urlMatchesEqual, type URLMatch } from '../utils/isomorphic/urlMatch'; -import type { LocalUtils } from './localUtils'; import type * as channels from '@protocol/channels'; -import type { WaitForEventOptions } from './types'; -import { Waiter } from './waiter'; -import { Events } from './events'; -import { isString } from '../utils/isomorphic/stringUtils'; -import { isRegExp } from '../utils'; -import { trimUrl } from './page'; -import { TimeoutSettings } from '../common/timeoutSettings'; import { ChannelOwner } from './channelOwner'; import { APIRequestContext } from './fetch'; +import type { BrowserContext } from './browserContext'; -export class MockingProxyFactory implements api.MockingProxyFactory { - constructor(private _localUtils: LocalUtils) {} - async newProxy(port?: number): Promise { - const { mockingProxy } = await this._localUtils._channel.newMockingProxy({ port }); - return (mockingProxy as any)._object; - } -} - -export class MockingProxy extends ChannelOwner implements api.MockingProxy { - private _routes: network.RouteHandler[] = []; +export class MockingProxy extends ChannelOwner { private _port: number; - private _timeoutSettings = new TimeoutSettings(); private _requestContext: APIRequestContext; + private _contexts = new Set(); constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.MockingProxyInitializer) { super(parent, type, guid, initializer); @@ -48,130 +30,24 @@ export class MockingProxy extends ChannelOwner imp this._port = initializer.port; this._requestContext = APIRequestContext.from(initializer.requestContext); - this._channel.on('route', ({ route }: channels.MockingProxyRouteEvent) => { - this._onRoute(network.Route.from(route)); - }); - this._channel.on('request', ({ request }: channels.MockingProxyRequestEvent) => { - this.emit(Events.MockingProxy.Request, network.Request.from(request)); - }); - this._channel.on('requestFailed', (params: channels.MockingProxyRequestFailedEvent) => { - const request = network.Request.from(params.request); - if (params.failureText) - request._failureText = params.failureText; - request._setResponseEndTiming(params.responseEndTiming); - this.emit(Events.MockingProxy.RequestFailed, request); - }); - this._channel.on('requestFinished', (params: channels.MockingProxyRequestFinishedEvent) => { - const { responseEndTiming } = params; - const request = network.Request.from(params.request); - request._setResponseEndTiming(responseEndTiming); - - const response = network.Response.fromNullable(params.response); - response?._finishedPromise.resolve(null); - - this.emit(Events.MockingProxy.RequestFinished, request); - }); - this._channel.on('response', ({ response }: channels.MockingProxyResponseEvent) => { - this.emit(Events.MockingProxy.Response, network.Response.from(response)); + this._channel.on('route', (params: channels.MockingProxyRouteEvent) => { + const route = network.Route.from(params.route); + route._context = this._requestContext; + for (const context of this._contexts) { + context._onRoute(route); + for (const page of context.pages()) + page._onRoute(route); + } }); } - async route(url: URLMatch, handler: network.RouteHandlerCallback, options: { times?: number } = {}): Promise { - this._routes.unshift(new network.RouteHandler(undefined, url, handler, options.times)); - await this._updateInterceptionPatterns(); + installOn(context: BrowserContext): void { + context._mockingProxies.add(this); + this._contexts.add(context); } - async unrouteAll(options?: { behavior?: 'wait' | 'ignoreErrors' | 'default' }): Promise { - await this._unrouteInternal(this._routes, [], options?.behavior); - } - - async unroute(url: URLMatch, handler?: network.RouteHandlerCallback): Promise { - const removed = []; - const remaining = []; - for (const route of this._routes) { - if (urlMatchesEqual(route.url, url) && (!handler || route.handler === handler)) - removed.push(route); - else - remaining.push(route); - } - await this._unrouteInternal(removed, remaining, 'default'); - } - - private async _unrouteInternal(removed: network.RouteHandler[], remaining: network.RouteHandler[], behavior?: 'wait' | 'ignoreErrors' | 'default'): Promise { - this._routes = remaining; - await this._updateInterceptionPatterns(); - if (!behavior || behavior === 'default') - return; - const promises = removed.map(routeHandler => routeHandler.stop(behavior)); - await Promise.all(promises); - } - - async _onRoute(route: network.Route) { - route._context = this._requestContext; - const routeHandlers = this._routes.slice(); - for (const routeHandler of routeHandlers) { - if (!routeHandler.matches(route.request().url())) - continue; - const index = this._routes.indexOf(routeHandler); - if (index === -1) - continue; - if (routeHandler.willExpire()) - this._routes.splice(index, 1); - const handled = await routeHandler.handle(route); - if (!this._routes.length) - this._updateInterceptionPatterns(); - if (handled) - return; - } - // If the page is closed or unrouteAll() was called without waiting and interception disabled, - // the method will throw an error - silence it. - await route._innerContinue(true /* isFallback */).catch(() => { }); - } - - private async _updateInterceptionPatterns() { - await this._channel.setInterceptionPatterns({ patterns: network.RouteHandler.prepareInterceptionPatterns(this._routes) }); - } - - async waitForRequest(urlOrPredicate: string | RegExp | ((r: network.Request) => boolean | Promise), options: { timeout?: number } = {}): Promise { - const predicate = async (request: network.Request) => { - if (isString(urlOrPredicate) || isRegExp(urlOrPredicate)) - return urlMatches(undefined, request.url(), urlOrPredicate); - return await urlOrPredicate(request); - }; - const trimmedUrl = trimUrl(urlOrPredicate); - const logLine = trimmedUrl ? `waiting for request ${trimmedUrl}` : undefined; - return await this._waitForEvent(Events.Page.Request, { predicate, timeout: options.timeout }, logLine); - } - - async waitForResponse(urlOrPredicate: string | RegExp | ((r: network.Response) => boolean | Promise), options: { timeout?: number } = {}): Promise { - const predicate = async (response: network.Response) => { - if (isString(urlOrPredicate) || isRegExp(urlOrPredicate)) - return urlMatches(undefined, response.url(), urlOrPredicate); - return await urlOrPredicate(response); - }; - const trimmedUrl = trimUrl(urlOrPredicate); - const logLine = trimmedUrl ? `waiting for response ${trimmedUrl}` : undefined; - return await this._waitForEvent(Events.Page.Response, { predicate, timeout: options.timeout }, logLine); - } - - async waitForEvent(event: string, optionsOrPredicate: WaitForEventOptions = {}): Promise { - const result = await this._waitForEvent(event, optionsOrPredicate, `waiting for event "${event}"`); - await this._updateInterceptionPatterns(); - return result; - } - - private async _waitForEvent(event: string, optionsOrPredicate: WaitForEventOptions, logLine?: string): Promise { - return await this._wrapApiCall(async () => { - const timeout = this._timeoutSettings.timeout(typeof optionsOrPredicate === 'function' ? {} : optionsOrPredicate); - const predicate = typeof optionsOrPredicate === 'function' ? optionsOrPredicate : optionsOrPredicate.predicate; - const waiter = Waiter.createForEvent(this, event); - if (logLine) - waiter.log(logLine); - waiter.rejectOnTimeout(timeout, `Timeout ${timeout}ms exceeded while waiting for event "${event}"`); - const result = await waiter.waitForEvent(this, event, predicate as any); - waiter.dispose(); - return result; - }); + async setInterceptionPatterns(params: channels.MockingProxySetInterceptionPatternsParams) { + await this._channel.setInterceptionPatterns(params); } port(): number { diff --git a/packages/playwright-core/src/client/page.ts b/packages/playwright-core/src/client/page.ts index d29f64efc9..960ea72bb5 100644 --- a/packages/playwright-core/src/client/page.ts +++ b/packages/playwright-core/src/client/page.ts @@ -179,7 +179,7 @@ export class Page extends ChannelOwner implements api.Page this.emit(Events.Page.FrameDetached, frame); } - private async _onRoute(route: Route) { + async _onRoute(route: Route) { route._context = this.context().request; const routeHandlers = this._routes.slice(); for (const routeHandler of routeHandlers) { @@ -565,6 +565,8 @@ export class Page extends ChannelOwner implements api.Page private async _updateInterceptionPatterns() { const patterns = RouteHandler.prepareInterceptionPatterns(this._routes); await this._channel.setNetworkInterceptionPatterns({ patterns }); + for (const proxy of this._browserContext._mockingProxies) + await proxy.setInterceptionPatterns({ patterns }); } private async _updateWebSocketInterceptionPatterns() { diff --git a/packages/playwright/src/index.ts b/packages/playwright/src/index.ts index 01b6b549aa..0d765cc649 100644 --- a/packages/playwright/src/index.ts +++ b/packages/playwright/src/index.ts @@ -16,7 +16,7 @@ import * as fs from 'fs'; import * as path from 'path'; -import type { APIRequestContext, BrowserContext, Browser, BrowserContextOptions, LaunchOptions, Page, Tracing, Video, MockingProxy } from 'playwright-core'; +import type { APIRequestContext, BrowserContext, Browser, BrowserContextOptions, LaunchOptions, Page, Tracing, Video } from 'playwright-core'; import * as playwrightLibrary from 'playwright-core'; import { createGuid, debugMode, addInternalStackPrefix, isString, asLocator, jsonStringifyForceASCII, zones } from 'playwright-core/lib/utils'; import type { Fixtures, PlaywrightTestArgs, PlaywrightTestOptions, PlaywrightWorkerArgs, PlaywrightWorkerOptions, ScreenshotMode, TestInfo, TestType, VideoMode } from '../types/test'; @@ -24,8 +24,9 @@ import type { TestInfoImpl, TestStepInternal } from './worker/testInfo'; import { rootTestType } from './common/testType'; import type { ContextReuseMode } from './common/config'; import type { ApiCallData, ClientInstrumentation, ClientInstrumentationListener } from '../../playwright-core/src/client/clientInstrumentation'; +import type { MockingProxy } from '../../playwright-core/src/client/mockingProxy'; import { currentTestInfo } from './common/globals'; -import { getFreePort } from './util'; +import type { LocalUtils } from 'playwright-core/lib/client/localUtils'; export { expect } from './matchers/expect'; export const _baseTest: TestType<{}, {}> = rootTestType.test; @@ -130,10 +131,10 @@ const playwrightFixtures: Fixtures = ({ if (typeof mockingProxyOption.port === 'number' && testInfoImpl.config.workers > 1) throw new Error(`Cannot share mocking proxy between multiple workers. Either disable parallel mode or set mockingProxy.port to 'inject'`); - const mockingProxy = await playwright.mockingProxy.newProxy( - mockingProxyOption.port === 'inject' ? undefined : mockingProxyOption.port - ); - await use(mockingProxy); + const port = mockingProxyOption.port === 'inject' ? undefined : mockingProxyOption.port; + const localUtils: LocalUtils = (playwright as any)._connection.localUtils(); + const { mockingProxy } = await localUtils._channel.newMockingProxy({ port }); + await use((mockingProxy as any)._object); }, { scope: 'worker' }], acceptDownloads: [({ contextOptions }, use) => use(contextOptions.acceptDownloads ?? true), { option: true }], @@ -347,7 +348,7 @@ const playwrightFixtures: Fixtures = ({ }, { auto: 'all-hooks-included', title: 'trace recording', box: true, timeout: 0 } as any], - _contextFactory: [async ({ browser, video, _reuseContext, _combinedContextOptions /** mitigate dep-via-auto lack of traceability */ }, use, testInfo) => { + _contextFactory: [async ({ browser, video, _reuseContext, _mockingProxy, _combinedContextOptions /** mitigate dep-via-auto lack of traceability */ }, use, testInfo) => { const testInfoImpl = testInfo as TestInfoImpl; const videoMode = normalizeVideoMode(video); const captureVideo = shouldCaptureVideo(videoMode, testInfo) && !_reuseContext; @@ -369,6 +370,7 @@ const playwrightFixtures: Fixtures = ({ } } : {}; const context = await browser.newContext({ ...videoOptions, ...options }); + _mockingProxy?.installOn(context as any); const contextData: { pagesWithVideo: Page[] } = { pagesWithVideo: [] }; contexts.set(context, contextData); if (captureVideo) @@ -468,13 +470,6 @@ const playwrightFixtures: Fixtures = ({ await request.dispose(); } }, - - server: async ({ _mockingProxy }, use) => { - if (!_mockingProxy) - throw new Error(`The 'server' fixture is only available when 'mockingProxy' is enabled.`); - await use(_mockingProxy); - await _mockingProxy.unrouteAll(); - } }); type ScreenshotOption = PlaywrightWorkerOptions['screenshot'] | undefined;