Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 155 additions & 15 deletions docs/src/api/class-route.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ async def handle(route, request):
"bar": None # remove "bar" header
}
await route.continue_(headers=headers)
}

await page.route("**/*", handle)
```

Expand All @@ -87,7 +87,7 @@ def handle(route, request):
"bar": None # remove "bar" header
}
route.continue_(headers=headers)
}

page.route("**/*", handle)
```

Expand All @@ -110,21 +110,21 @@ If set changes the request URL. New URL must have same protocol as original one.
* since: v1.8
- `method` <[string]>

If set changes the request method (e.g. GET or POST)
If set changes the request method (e.g. GET or POST).

### option: Route.continue.postData
* since: v1.8
* langs: js, python, java
- `postData` <[string]|[Buffer]>

If set changes the post data of request
If set changes the post data of request.

### option: Route.continue.postData
* since: v1.8
* langs: csharp
- `postData` <[Buffer]>

If set changes the post data of request
If set changes the post data of request.

### option: Route.continue.headers
* since: v1.8
Expand Down Expand Up @@ -349,7 +349,7 @@ async def handle(route, request):
"bar": None # remove "bar" header
}
await route.fallback(headers=headers)
}

await page.route("**/*", handle)
```

Expand All @@ -362,7 +362,7 @@ def handle(route, request):
"bar": None # remove "bar" header
}
route.fallback(headers=headers)
}

page.route("**/*", handle)
```

Expand All @@ -386,28 +386,115 @@ affect the route matching, all the routes are matched using the original request
* since: v1.23
- `method` <[string]>

If set changes the request method (e.g. GET or POST)
If set changes the request method (e.g. GET or POST).

### option: Route.fallback.postData
* since: v1.23
* langs: js, python, java
- `postData` <[string]|[Buffer]>

If set changes the post data of request
If set changes the post data of request.

### option: Route.fallback.postData
* since: v1.23
* langs: csharp
- `postData` <[Buffer]>

If set changes the post data of request
If set changes the post data of request.

### option: Route.fallback.headers
* since: v1.23
- `headers` <[Object]<[string], [string]>>

If set changes the request HTTP headers. Header values will be converted to a string.

## async method: Route.fetch
* since: v1.29
- returns: <[APIResponse]>

Performs the request and fetches result without fulfilling it, so that the response
could be modified and then fulfilled.

**Usage**

```js
await page.route('https://dog.ceo/api/breeds/list/all', async route => {
const response = await route.fetch();
const json = await response.json();
json.message['big_red_dog'] = [];
await route.fulfill({ response, json });
});
```

```java
page.route("https://dog.ceo/api/breeds/list/all", route -> {
APIResponse response = route.fetch();
JsonObject json = new Gson().fromJson(response.text(), JsonObject.class);
json.set("big_red_dog", new JsonArray());
route.fulfill(new Route.FulfillOptions()
.setResponse(response)
.setBody(json.toString()));
});
```

```python async
async def handle(route):
response = await route.fulfill()
json = await response.json()
json["big_red_dog"] = []
await route.fulfill(response=response, json=json)

await page.route("https://dog.ceo/api/breeds/list/all", handle)
```

```python sync
def handle(route):
response = route.fulfill()
json = response.json()
json["big_red_dog"] = []
route.fulfill(response=response, json=json)

page.route("https://dog.ceo/api/breeds/list/all", handle)
```

```csharp
await page.RouteAsync("https://dog.ceo/api/breeds/list/all", async route =>
{
var response = await route.FetchAsync();
dynamic json = await response.JsonAsync();
json.big_red_dog = new string[] {};
await route.FulfillAsync(new() { Response = response, Json = json });
});
```

### option: Route.fetch.url
* since: v1.29
- `url` <[string]>

If set changes the request URL. New URL must have same protocol as original one.

### option: Route.fetch.method
* since: v1.29
- `method` <[string]>

If set changes the request method (e.g. GET or POST).

### option: Route.fetch.postData = %%-js-python-csharp-fetch-option-data-%%
* since: v1.29

### option: Route.fetch.data
* since: v1.29
* langs: csharp
- `postData` <[Buffer]>

If set changes the post data of request.

### option: Route.fetch.headers
* since: v1.29
- `headers` <[Object]<[string], [string]>>

If set changes the request HTTP headers. Header values will be converted to a string.

## async method: Route.fulfill
* since: v1.8

Expand Down Expand Up @@ -451,10 +538,12 @@ page.route("**/*", lambda route: route.fulfill(
```

```csharp
await page.RouteAsync("**/*", route => route.FulfillAsync(
status: 404,
contentType: "text/plain",
body: "Not Found!"));
await page.RouteAsync("**/*", route => route.FulfillAsync(new ()
{
Status = 404,
ContentType = "text/plain",
Body = "Not Found!")
});
```

An example of serving static file:
Expand All @@ -477,7 +566,7 @@ page.route("**/xhr_endpoint", lambda route: route.fulfill(path="mock_data.json")
```

```csharp
await page.RouteAsync("**/xhr_endpoint", route => route.FulfillAsync(new RouteFulfillOptions { Path = "mock_data.json" }));
await page.RouteAsync("**/xhr_endpoint", route => route.FulfillAsync(new() { Path = "mock_data.json" }));
```

### option: Route.fulfill.status
Expand Down Expand Up @@ -519,6 +608,57 @@ Optional response body as text.

Optional response body as raw bytes.

### option: Route.fulfill.json
* since: v1.29
* langs: js, python
- `json` <[Serializable]>

JSON response. This method will set the content type to `application/json` if not set.

**Usage**

```js
await page.route('https://dog.ceo/api/breeds/list/all', async route => {
const json = {
message: { 'test_breed': [] }
};
await route.fulfill({ json });
});
```

```python async
async def handle(route):
json = { "test_breed": [] }
await route.fulfill(json=json)

await page.route("https://dog.ceo/api/breeds/list/all", handle)
```

```python sync
async def handle(route):
json = { "test_breed": [] }
route.fulfill(json=json)

page.route("https://dog.ceo/api/breeds/list/all", handle)
```

### option: Route.fulfill.json
* since: v1.29
* langs: csharp
- `json` <[JsonElement]>

JSON response. This method will set the content type to `application/json` if not set.

**Usage**

```csharp
await page.RouteAsync("https://dog.ceo/api/breeds/list/all", async route =>
{
var json = /* JsonElement with test payload */;
await route.FulfillAsync(new () { Json: json });
});
```

### option: Route.fulfill.path
* since: v1.8
- `path` <[path]>
Expand Down
3 changes: 2 additions & 1 deletion docs/src/network.md
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,7 @@ var waitForResponseTask = page.WaitForResponseAsync(r => r.Url.Contains(token));
await page.GetByText("Update").ClickAsync();
var response = await waitForResponseTask;
```

## Handle requests

```js
Expand Down Expand Up @@ -416,7 +417,7 @@ page.goto("https://example.com")

```csharp
await page.RouteAsync("**/api/fetch_data", async route => {
await route.FulfillAsync(status: 200, body: testData);
await route.FulfillAsync(new() { Status = 200, Body = testData });
});
await page.GotoAsync("https://example.com");
```
Expand Down
11 changes: 6 additions & 5 deletions docs/src/service-workers-experimental-network-events-js.md
Original file line number Diff line number Diff line change
Expand Up @@ -377,11 +377,12 @@ context.route('**', handle)
await context.RouteAsync("**", async route => {
if (route.request().serviceWorker() != null) {
// NB: calling route.request.frame here would THROW
await route.FulfillAsync(
contentType: "text/plain",
status: 200,
body: "from sw"
);
await route.FulfillAsync(new ()
{
ContentType = "text/plain",
Status = 200,
Body = "from sw"
});
} else {
await route.Continue()Async();
}
Expand Down
18 changes: 11 additions & 7 deletions packages/playwright-core/src/client/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import { kBrowserOrContextClosedError } from '../common/errors';
import { assert, headersObjectToArray, isFilePayload, isString, objectToArray } from '../utils';
import { mkdirIfNeeded } from '../utils/fileUtils';
import { ChannelOwner } from './channelOwner';
import * as network from './network';
import { RawHeaders } from './network';
import type { FilePayload, Headers, StorageState } from './types';
import type { Playwright } from './playwright';
Expand Down Expand Up @@ -142,17 +141,22 @@ export class APIRequestContext extends ChannelOwner<channels.APIRequestContextCh
}

async fetch(urlOrRequest: string | api.Request, options: FetchOptions = {}): Promise<APIResponse> {
const url = isString(urlOrRequest) ? urlOrRequest : undefined;
const request = isString(urlOrRequest) ? undefined : urlOrRequest;
return this._innerFetch({ url, request, ...options });
}

async _innerFetch(options: FetchOptions & { url?: string, request?: api.Request } = {}): Promise<APIResponse> {
return this._wrapApiCall(async () => {
const request: network.Request | undefined = (urlOrRequest instanceof network.Request) ? urlOrRequest as network.Request : undefined;
assert(request || typeof urlOrRequest === 'string', 'First argument must be either URL string or Request');
assert(options.request || typeof options.url === 'string', 'First argument must be either URL string or Request');
assert((options.data === undefined ? 0 : 1) + (options.form === undefined ? 0 : 1) + (options.multipart === undefined ? 0 : 1) <= 1, `Only one of 'data', 'form' or 'multipart' can be specified`);
assert(options.maxRedirects === undefined || options.maxRedirects >= 0, `'maxRedirects' should be greater than or equal to '0'`);
const url = request ? request.url() : urlOrRequest as string;
const url = options.url !== undefined ? options.url : options.request!.url();
const params = objectToArray(options.params);
const method = options.method || request?.method();
const method = options.method || options.request?.method();
const maxRedirects = options.maxRedirects;
// Cannot call allHeaders() here as the request may be paused inside route handler.
const headersObj = options.headers || request?.headers() ;
const headersObj = options.headers || options.request?.headers() ;
const headers = headersObj ? headersObjectToArray(headersObj) : undefined;
let jsonData: any;
let formData: channels.NameValue[] | undefined;
Expand Down Expand Up @@ -190,7 +194,7 @@ export class APIRequestContext extends ChannelOwner<channels.APIRequestContextCh
}
}
if (postDataBuffer === undefined && jsonData === undefined && formData === undefined && multipartData === undefined)
postDataBuffer = request?.postDataBuffer() || undefined;
postDataBuffer = options.request?.postDataBuffer() || undefined;
const result = await this._channel.fetch({
url,
params,
Expand Down
18 changes: 16 additions & 2 deletions packages/playwright-core/src/client/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -307,18 +307,30 @@ export class Route extends ChannelOwner<channels.RouteChannel> implements api.Ro
this._reportHandled(true);
}

async fulfill(options: { response?: api.APIResponse, status?: number, headers?: Headers, contentType?: string, body?: string | Buffer, path?: string } = {}) {
async fetch(options: FallbackOverrides = {}) {
return await this._wrapApiCall(async () => {
const context = this.request()._context();
return context.request._innerFetch({ request: this.request(), data: options.postData, ...options });
});
}

async fulfill(options: { response?: api.APIResponse, status?: number, headers?: Headers, contentType?: string, body?: string | Buffer, json?: any, path?: string } = {}) {
this._checkNotHandled();
await this._wrapApiCall(async () => {
await this._innerFulfill(options);
this._reportHandled(true);
});
}

private async _innerFulfill(options: { response?: api.APIResponse, status?: number, headers?: Headers, contentType?: string, body?: string | Buffer, path?: string } = {}): Promise<void> {
private async _innerFulfill(options: { response?: api.APIResponse, status?: number, headers?: Headers, contentType?: string, body?: string | Buffer, json?: any, path?: string } = {}): Promise<void> {
let fetchResponseUid;
let { status: statusOption, headers: headersOption, body } = options;

if (options.json !== undefined) {
assert(options.body === undefined, 'Can specify either body or json parameters');
Comment thread
pavelfeldman marked this conversation as resolved.
body = JSON.stringify(options.json);
}

if (options.response instanceof APIResponse) {
statusOption ??= options.response.status();
headersOption ??= options.response.headers();
Expand Down Expand Up @@ -351,6 +363,8 @@ export class Route extends ChannelOwner<channels.RouteChannel> implements api.Ro
headers[header.toLowerCase()] = String(headersOption![header]);
if (options.contentType)
headers['content-type'] = String(options.contentType);
else if (options.json)
headers['content-type'] = 'application/json';
else if (options.path)
headers['content-type'] = mime.getType(options.path) || 'application/octet-stream';
if (length && !('content-length' in headers))
Expand Down
Loading