diff --git a/docs/api.md b/docs/api.md index c2e488d118..93f15158cc 100644 --- a/docs/api.md +++ b/docs/api.md @@ -1011,7 +1011,7 @@ Browser-specific Coverage implementation, only available for Chromium atm. See [ - `'load'` - consider navigation to be finished when the `load` event is fired. - `'domcontentloaded'` - consider navigation to be finished when the `DOMContentLoaded` event is fired. - `'networkidle0'` - consider navigation to be finished when there are no more than 0 network connections for at least `500` ms. - - `'networkidle2'` - consider navigation to be finished when there are no more than 2 network connections for at least `500` ms. + - `'networkidle2'` - consider navigation to be finished when there are no more than 2 network connections for at least `500` ms. - `'nowait'` - do not wait. - `timeout` <[number]> Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the [browserContext.setDefaultTimeout(timeout)](#browsercontextsetdefaulttimeouttimeout) or [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) methods. - returns: <[Promise]> Promise which resolves when the element matching `selector` is successfully double clicked. The Promise will be rejected if there is no element matching `selector`. @@ -3377,8 +3377,9 @@ page.on('requestfailed', request => { - `response` <[Object]> Response that will fulfill this request - `status` <[number]> Response status code, defaults to `200`. - `headers` <[Object]> Optional response headers. Header values will be converted to a string. - - `contentType` <[string]> If set, equals to setting `Content-Type` response header - - `body` <[string]|[Buffer]> Optional response body + - `contentType` <[string]> If set, equals to setting `Content-Type` response header. + - `body` <[string]|[Buffer]> Optional response body. + - `path` <[string]> Optional file path to respond with. The content type will be inferred from file extension. If `path` is a relative path, then it is resolved relative to [current working directory](https://nodejs.org/api/process.html#process_process_cwd). - returns: <[Promise]> Fulfills request with given response. To use this, request interception should @@ -3397,8 +3398,11 @@ await page.route('**/*', request => { }); ``` -> **NOTE** Mocking responses for dataURL requests is not supported. -> Calling `request.fulfill` for a dataURL request is a noop. +An example of serving static file: + +```js +await page.route('**/xhr_endpoint', request => request.fulfill({ path: 'mock_data.json' })); +``` #### request.headers() - returns: <[Object]> An object with HTTP headers associated with the request. All header names are lower-case. diff --git a/src/chromium/crNetworkManager.ts b/src/chromium/crNetworkManager.ts index dbb56bf372..33f23bb337 100644 --- a/src/chromium/crNetworkManager.ts +++ b/src/chromium/crNetworkManager.ts @@ -267,7 +267,7 @@ class InterceptableRequest implements network.RequestDelegate { }); } - async fulfill(response: { status: number; headers: network.Headers; contentType: string; body: (string | platform.BufferType); }) { + async fulfill(response: network.FulfillResponse) { const responseBody = response.body && helper.isString(response.body) ? platform.Buffer.from(response.body) : (response.body || null); const responseHeaders: { [s: string]: string; } = {}; diff --git a/src/firefox/ffNetworkManager.ts b/src/firefox/ffNetworkManager.ts index b117492cad..b3608c5612 100644 --- a/src/firefox/ffNetworkManager.ts +++ b/src/firefox/ffNetworkManager.ts @@ -174,7 +174,7 @@ class InterceptableRequest implements network.RequestDelegate { }); } - async fulfill(response: { status: number; headers: network.Headers; contentType: string; body: (string | platform.BufferType); }) { + async fulfill(response: network.FulfillResponse) { const responseBody = response.body && helper.isString(response.body) ? platform.Buffer.from(response.body) : (response.body || null); const responseHeaders: { [s: string]: string; } = {}; diff --git a/src/network.ts b/src/network.ts index 39b37a4bff..e06f09c120 100644 --- a/src/network.ts +++ b/src/network.ts @@ -202,10 +202,18 @@ export class Request { await this._delegate.abort(errorCode); } - async fulfill(response: { status: number; headers: Headers; contentType: string; body: (string | platform.BufferType); }) { // Mocking responses for dataURL requests is not currently supported. + async fulfill(response: FulfillResponse & { path?: string }) { assert(this._delegate, 'Request Interception is not enabled!'); assert(!this._interceptionHandled, 'Request is already handled!'); this._interceptionHandled = true; + if (response.path) { + response = { + status: response.status, + headers: response.headers, + contentType: platform.getMimeType(response.path), + body: await platform.readFileBuffer(response.path) + }; + } await this._delegate.fulfill(response); } @@ -304,9 +312,16 @@ export class Response { } } +export type FulfillResponse = { + status?: number, + headers?: Headers, + contentType?: string, + body?: string | platform.BufferType, +}; + export interface RequestDelegate { abort(errorCode: string): Promise; - fulfill(response: { status: number; headers: Headers; contentType: string; body: (string | platform.BufferType); }): Promise; + fulfill(response: FulfillResponse): Promise; continue(overrides: { method?: string; headers?: Headers; postData?: string; }): Promise; } diff --git a/src/platform.ts b/src/platform.ts index 36ccb12273..f88946367b 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -187,6 +187,11 @@ export async function readFileAsync(file: string, encoding: string): Promise { + assertFileAccess(); + return await promisify(nodeFS.readFile)(file); +} + export async function writeFileAsync(file: string, data: any) { assertFileAccess(); return await promisify(nodeFS.writeFile)(file, data); diff --git a/src/webkit/wkInterceptableRequest.ts b/src/webkit/wkInterceptableRequest.ts index 476306e99b..c039523ff8 100644 --- a/src/webkit/wkInterceptableRequest.ts +++ b/src/webkit/wkInterceptableRequest.ts @@ -65,7 +65,7 @@ export class WKInterceptableRequest implements network.RequestDelegate { }); } - async fulfill(response: { status: number; headers: network.Headers; contentType: string; body: (string | platform.BufferType); }) { + async fulfill(response: network.FulfillResponse) { await this._interceptedPromise; const base64Encoded = !!response.body && !helper.isString(response.body); diff --git a/test/interception.spec.js b/test/interception.spec.js index 4f434540b7..59d4f11cf2 100644 --- a/test/interception.spec.js +++ b/test/interception.spec.js @@ -468,6 +468,17 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p const img = await page.$('img'); expect(await img.screenshot()).toBeGolden('mock-binary-response.png'); }); + it('should work with file path', async({page, server}) => { + await page.route('**/*', request => request.fulfill({ contentType: 'shouldBeIgnored', path: path.join(__dirname, 'assets', 'pptr.png') })); + await page.evaluate(PREFIX => { + const img = document.createElement('img'); + img.src = PREFIX + '/does-not-exist.png'; + document.body.appendChild(img); + return new Promise(fulfill => img.onload = fulfill); + }, server.PREFIX); + const img = await page.$('img'); + expect(await img.screenshot()).toBeGolden('mock-binary-response.png'); + }); it('should stringify intercepted request response headers', async({page, server}) => { await page.route('**/*', request => { request.fulfill({