move stuff over to page

This commit is contained in:
Simon Knott 2025-01-24 13:11:05 +01:00
parent e0e8f2e499
commit e580a4cca2
No known key found for this signature in database
GPG key ID: 8CEDC00028084AEC
6 changed files with 33 additions and 626 deletions

View file

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

View file

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

View file

@ -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<channels.BrowserContextChannel> implements api.BrowserContext {
_pages = new Set<Page>();
@ -69,6 +70,8 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
private _closeReason: string | undefined;
private _harRouters: HarRouter[] = [];
_mockingProxies = new Set<MockingProxy>();
static from(context: channels.BrowserContextChannel): BrowserContext {
return (context as any)._object;
}
@ -400,6 +403,8 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
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() {

View file

@ -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<api.MockingProxy> {
const { mockingProxy } = await this._localUtils._channel.newMockingProxy({ port });
return (mockingProxy as any)._object;
}
}
export class MockingProxy extends ChannelOwner<channels.MockingProxyChannel> implements api.MockingProxy {
private _routes: network.RouteHandler[] = [];
export class MockingProxy extends ChannelOwner<channels.MockingProxyChannel> {
private _port: number;
private _timeoutSettings = new TimeoutSettings();
private _requestContext: APIRequestContext;
private _contexts = new Set<BrowserContext>();
constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.MockingProxyInitializer) {
super(parent, type, guid, initializer);
@ -48,130 +30,24 @@ export class MockingProxy extends ChannelOwner<channels.MockingProxyChannel> 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<void> {
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<void> {
await this._unrouteInternal(this._routes, [], options?.behavior);
}
async unroute(url: URLMatch, handler?: network.RouteHandlerCallback): Promise<void> {
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<void> {
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<boolean>), options: { timeout?: number } = {}): Promise<network.Request> {
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<boolean>), options: { timeout?: number } = {}): Promise<network.Response> {
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<any> {
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<any> {
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 {

View file

@ -179,7 +179,7 @@ export class Page extends ChannelOwner<channels.PageChannel> 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<channels.PageChannel> 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() {

View file

@ -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<TestFixtures, WorkerFixtures> = ({
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<TestFixtures, WorkerFixtures> = ({
}, { 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<TestFixtures, WorkerFixtures> = ({
}
} : {};
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<TestFixtures, WorkerFixtures> = ({
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;