move stuff over to page
This commit is contained in:
parent
e0e8f2e499
commit
e580a4cca2
|
|
@ -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]>
|
||||
|
||||
|
|
@ -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.
|
||||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue