From c34acf2557bc77baa73d6ea6fa7c03e0d51e32ec Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Fri, 3 Apr 2020 09:48:49 -0700 Subject: [PATCH] feat(firefox): support downloads --- package.json | 2 +- src/firefox/ffBrowser.ts | 21 ++++++++++++++++++++- src/server/firefox.ts | 14 ++++++++------ test/download.spec.js | 6 +++--- 4 files changed, 32 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 645099c6978df4..d1749a4ad551e7 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "main": "index.js", "playwright": { "chromium_revision": "754895", - "firefox_revision": "1072", + "firefox_revision": "1074", "webkit_revision": "1188" }, "scripts": { diff --git a/src/firefox/ffBrowser.ts b/src/firefox/ffBrowser.ts index f14e69c3b7e36c..33eeebd56d7cea 100644 --- a/src/firefox/ffBrowser.ts +++ b/src/firefox/ffBrowser.ts @@ -59,6 +59,8 @@ export class FFBrowser extends BrowserBase { this._eventListeners = [ helper.addEventListener(this._connection, 'Browser.attachedToTarget', this._onAttachedToTarget.bind(this)), helper.addEventListener(this._connection, 'Browser.detachedFromTarget', this._onDetachedFromTarget.bind(this)), + helper.addEventListener(this._connection, 'Browser.downloadCreated', this._onDownloadCreated.bind(this)), + helper.addEventListener(this._connection, 'Browser.downloadFinished', this._onDownloadFinished.bind(this)), ]; this._firstPagePromise = new Promise(f => this._firstPageCallback = f); } @@ -96,7 +98,11 @@ export class FFBrowser extends BrowserBase { viewport, locale: options.locale, timezoneId: options.timezoneId, - removeOnDetach: true + removeOnDetach: true, + downloadOptions: { + behavior: options.acceptDownloads ? 'saveToDisk' : 'cancel', + downloadsDir: this._downloadsPath, + }, }); const context = new FFBrowserContext(this, browserContextId, options); await context._initialize(); @@ -135,6 +141,19 @@ export class FFBrowser extends BrowserBase { }); } + _onDownloadCreated(payload: Protocol.Browser.downloadCreatedPayload) { + const ffPage = this._ffPages.get(payload.pageTargetId)!; + assert(ffPage); + if (!ffPage) + return; + this._downloadCreated(ffPage._page, payload.uuid, payload.url); + } + + _onDownloadFinished(payload: Protocol.Browser.downloadFinishedPayload) { + const error = payload.canceled ? 'canceled' : payload.error; + this._downloadFinished(payload.uuid, error); + } + _disconnect() { helper.removeEventListeners(this._eventListeners); this._connection.close(); diff --git a/src/server/firefox.ts b/src/server/firefox.ts index d1181ce0dbe6a7..f5ff763bf64658 100644 --- a/src/server/firefox.ts +++ b/src/server/firefox.ts @@ -49,16 +49,17 @@ export class Firefox implements BrowserType { async launch(options: LaunchOptions = {}): Promise { assert(!(options as any).userDataDir, 'userDataDir option is not supported in `browserType.launch`. Use `browserType.launchPersistentContext` instead'); - const browserServer = await this._launchServer(options, 'local'); + const {browserServer, downloadsPath} = await this._launchServer(options, 'local'); const browser = await WebSocketTransport.connect(browserServer.wsEndpoint()!, transport => { return FFBrowser.connect(transport, false, options.slowMo); }); browser._ownedServer = browserServer; + browser._downloadsPath = downloadsPath; return browser; } async launchServer(options: LaunchServerOptions = {}): Promise { - return await this._launchServer(options, 'server'); + return (await this._launchServer(options, 'server')).browserServer; } async launchPersistentContext(userDataDir: string, options: LaunchOptions = {}): Promise { @@ -66,17 +67,18 @@ export class Firefox implements BrowserType { timeout = 30000, slowMo = 0, } = options; - const browserServer = await this._launchServer(options, 'persistent', userDataDir); + const {browserServer, downloadsPath} = await this._launchServer(options, 'persistent', userDataDir); const browser = await WebSocketTransport.connect(browserServer.wsEndpoint()!, transport => { return FFBrowser.connect(transport, true, slowMo); }); browser._ownedServer = browserServer; + browser._downloadsPath = downloadsPath; await helper.waitWithTimeout(browser._firstPagePromise, 'first page', timeout); const browserContext = browser._defaultContext; return browserContext; } - private async _launchServer(options: LaunchServerOptions, launchType: LaunchType, userDataDir?: string): Promise { + private async _launchServer(options: LaunchServerOptions, launchType: LaunchType, userDataDir?: string): Promise<{ browserServer: BrowserServer, downloadsPath: string }> { const { ignoreDefaultArgs = false, args = [], @@ -110,7 +112,7 @@ export class Firefox implements BrowserType { if (!firefoxExecutable) throw new Error(`No executable path is specified. Pass "executablePath" option directly.`); - const { launchedProcess, gracefullyClose } = await launchProcess({ + const { launchedProcess, gracefullyClose, downloadsPath } = await launchProcess({ executablePath: firefoxExecutable, args: firefoxArguments, env: os.platform() === 'linux' ? { @@ -146,7 +148,7 @@ export class Firefox implements BrowserType { const webSocketWrapper = launchType === 'server' ? (await WebSocketTransport.connect(innerEndpoint, t => wrapTransportWithWebSocket(t, port))) : new WebSocketWrapper(innerEndpoint, []); browserWSEndpoint = webSocketWrapper.wsEndpoint; browserServer = new BrowserServer(launchedProcess, gracefullyClose, webSocketWrapper); - return browserServer; + return {browserServer, downloadsPath}; } async connect(options: ConnectOptions): Promise { diff --git a/test/download.spec.js b/test/download.spec.js index 1d4337de27076f..e57b6dec4ed555 100644 --- a/test/download.spec.js +++ b/test/download.spec.js @@ -17,12 +17,12 @@ const fs = require('fs'); const path = require('path'); -module.exports.describe = function({testRunner, expect, browserType, CHROMIUM, WEBKIT, FFOX, WIN, MAC}) { +module.exports.describe = function({testRunner, expect, browserType, defaultBrowserOptions, CHROMIUM, WEBKIT, FFOX, WIN, MAC}) { const {describe, xdescribe, fdescribe} = testRunner; const {it, fit, xit, dit} = testRunner; const {beforeAll, beforeEach, afterAll, afterEach} = testRunner; - describe.fail(FFOX)('Download', function() { + describe('Download', function() { beforeEach(async(state) => { state.server.setRoute('/download', (req, res) => { res.setHeader('Content-Type', 'application/octet-stream'); @@ -100,7 +100,7 @@ module.exports.describe = function({testRunner, expect, browserType, CHROMIUM, W expect(fs.existsSync(path1)).toBeFalsy(); expect(fs.existsSync(path2)).toBeFalsy(); }); - it('should delete downloads on browser gone', async ({ server, defaultBrowserOptions }) => { + it('should delete downloads on browser gone', async ({ server }) => { const browser = await browserType.launch(defaultBrowserOptions); const page = await browser.newPage({ acceptDownloads: true }); await page.setContent(`download`);