From 23a668e3beb6edd9bad242ee4f2b2987fecefffe Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Thu, 23 Jan 2020 10:38:28 -0800 Subject: [PATCH] feat(firefox): support request interception (#571) --- package.json | 2 +- src/firefox/ffNetworkManager.ts | 42 ++++++++++++++++--- src/firefox/ffPage.ts | 4 +- test/golden-firefox/mock-binary-response.png | Bin 0 -> 6051 bytes test/interception.spec.js | 6 +-- 5 files changed, 42 insertions(+), 12 deletions(-) create mode 100644 test/golden-firefox/mock-binary-response.png diff --git a/package.json b/package.json index 4c712aba76..79570ee1bb 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "main": "index.js", "playwright": { "chromium_revision": "733125", - "firefox_revision": "1016", + "firefox_revision": "1017", "webkit_revision": "1106" }, "scripts": { diff --git a/src/firefox/ffNetworkManager.ts b/src/firefox/ffNetworkManager.ts index 68969eecd8..d16eb456de 100644 --- a/src/firefox/ffNetworkManager.ts +++ b/src/firefox/ffNetworkManager.ts @@ -155,7 +155,7 @@ class InterceptableRequest implements network.RequestDelegate { for (const {name, value} of payload.headers) headers[name.toLowerCase()] = value; - this.request = new network.Request(payload.suspended ? this : null, frame, redirectChain, payload.navigationId, + this.request = new network.Request(payload.isIntercepted ? this : null, frame, redirectChain, payload.navigationId, payload.url, causeToResourceType[payload.cause] || 'other', payload.method, payload.postData, headers); } @@ -163,23 +163,53 @@ class InterceptableRequest implements network.RequestDelegate { const { headers, } = overrides; - await this._session.send('Network.resumeSuspendedRequest', { + await this._session.send('Network.resumeInterceptedRequest', { requestId: this._id, - headers: headers ? Object.entries(headers).filter(([, value]) => !Object.is(value, undefined)).map(([name, value]) => ({name, value})) : undefined, + headers: headers ? headersArray(headers) : undefined, }).catch(error => { debugError(error); }); } async fulfill(response: { status: number; headers: network.Headers; contentType: string; body: (string | platform.BufferType); }) { - throw new Error('Fulfill is not supported in Firefox'); + const responseBody = response.body && helper.isString(response.body) ? platform.Buffer.from(response.body) : (response.body || null); + + const responseHeaders: { [s: string]: string; } = {}; + if (response.headers) { + for (const header of Object.keys(response.headers)) + responseHeaders[header.toLowerCase()] = response.headers[header]; + } + if (response.contentType) + responseHeaders['content-type'] = response.contentType; + if (responseBody && !('content-length' in responseHeaders)) + responseHeaders['content-length'] = String(platform.Buffer.byteLength(responseBody)); + + await this._session.send('Network.fulfillInterceptedRequest', { + requestId: this._id, + status: response.status || 200, + statusText: network.STATUS_TEXTS[String(response.status || 200)] || '', + headers: headersArray(responseHeaders), + base64body: responseBody ? responseBody.toString('base64') : undefined, + }).catch(error => { + debugError(error); + }); } - async abort() { - await this._session.send('Network.abortSuspendedRequest', { + async abort(errorCode: string) { + await this._session.send('Network.abortInterceptedRequest', { requestId: this._id, + errorCode, }).catch(error => { debugError(error); }); } } + +function headersArray(headers: network.Headers): Protocol.Network.HTTPHeader[] { + const result: Protocol.Network.HTTPHeader[] = []; + for (const name in headers) { + if (!Object.is(headers[name], undefined)) + result.push({name, value: headers[name] + ''}); + } + return result; +} diff --git a/src/firefox/ffPage.ts b/src/firefox/ffPage.ts index 32c836bf79..8e9650d03c 100644 --- a/src/firefox/ffPage.ts +++ b/src/firefox/ffPage.ts @@ -294,8 +294,8 @@ export class FFPage implements PageDelegate { throw new Error('Offline mode not implemented in Firefox'); } - async authenticate(credentials: types.Credentials): Promise { - throw new Error('Offline mode not implemented in Firefox'); + async authenticate(credentials: types.Credentials | null): Promise { + await this._session.send('Network.setAuthCredentials', credentials || { username: null, password: null }); } async reload(): Promise { diff --git a/test/golden-firefox/mock-binary-response.png b/test/golden-firefox/mock-binary-response.png new file mode 100644 index 0000000000000000000000000000000000000000..be9f4a44684f9120e7906196017816579dce2fa5 GIT binary patch literal 6051 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 literal 0 HcmV?d00001 diff --git a/test/interception.spec.js b/test/interception.spec.js index a347113151..1cb70b3455 100644 --- a/test/interception.spec.js +++ b/test/interception.spec.js @@ -178,7 +178,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p if (WEBKIT) expect(failedRequest.failure().errorText).toBe('Request intercepted'); else if (FFOX) - expect(failedRequest.failure().errorText).toBe('NS_ERROR_FAILURE'); + expect(failedRequest.failure().errorText).toBe('NS_ERROR_OFFLINE'); else expect(failedRequest.failure().errorText).toBe('net::ERR_INTERNET_DISCONNECTED'); }); @@ -438,7 +438,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p }); }); - describe.skip(FFOX)('interception.fulfill', function() { + describe('interception.fulfill', function() { it('should work', async({page, server}) => { await page.setRequestInterception(true); page.on('request', request => { @@ -506,7 +506,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p }); }); - describe.skip(FFOX)('Interception.authenticate', function() { + describe('Page.authenticate', function() { it('should work', async({page, server}) => { server.setAuth('/empty.html', 'user', 'pass'); let response = await page.goto(server.EMPTY_PAGE);