feat(firefox): support downloads (#1689)
This commit is contained in:
parent
949dc7b514
commit
a7ae205254
|
|
@ -9,7 +9,7 @@
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"playwright": {
|
"playwright": {
|
||||||
"chromium_revision": "754895",
|
"chromium_revision": "754895",
|
||||||
"firefox_revision": "1072",
|
"firefox_revision": "1074",
|
||||||
"webkit_revision": "1188"
|
"webkit_revision": "1188"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ export abstract class BrowserBase extends EventEmitter implements Browser {
|
||||||
this._downloads.set(uuid, download);
|
this._downloads.set(uuid, download);
|
||||||
}
|
}
|
||||||
|
|
||||||
_downloadFinished(uuid: string, error: string) {
|
_downloadFinished(uuid: string, error?: string) {
|
||||||
const download = this._downloads.get(uuid);
|
const download = this._downloads.get(uuid);
|
||||||
if (!download)
|
if (!download)
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ export class Download {
|
||||||
await util.promisify(fs.unlink)(fileName).catch(e => {});
|
await util.promisify(fs.unlink)(fileName).catch(e => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
_reportFinished(error: string) {
|
_reportFinished(error?: string) {
|
||||||
this._failure = error || null;
|
this._failure = error || null;
|
||||||
this._finishedCallback();
|
this._finishedCallback();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,8 @@ export class FFBrowser extends BrowserBase {
|
||||||
this._eventListeners = [
|
this._eventListeners = [
|
||||||
helper.addEventListener(this._connection, 'Browser.attachedToTarget', this._onAttachedToTarget.bind(this)),
|
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.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);
|
this._firstPagePromise = new Promise(f => this._firstPageCallback = f);
|
||||||
}
|
}
|
||||||
|
|
@ -96,7 +98,11 @@ export class FFBrowser extends BrowserBase {
|
||||||
viewport,
|
viewport,
|
||||||
locale: options.locale,
|
locale: options.locale,
|
||||||
timezoneId: options.timezoneId,
|
timezoneId: options.timezoneId,
|
||||||
removeOnDetach: true
|
removeOnDetach: true,
|
||||||
|
downloadOptions: {
|
||||||
|
behavior: options.acceptDownloads ? 'saveToDisk' : 'cancel',
|
||||||
|
downloadsDir: this._downloadsPath,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
const context = new FFBrowserContext(this, browserContextId, options);
|
const context = new FFBrowserContext(this, browserContextId, options);
|
||||||
await context._initialize();
|
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() {
|
_disconnect() {
|
||||||
helper.removeEventListeners(this._eventListeners);
|
helper.removeEventListeners(this._eventListeners);
|
||||||
this._connection.close();
|
this._connection.close();
|
||||||
|
|
|
||||||
|
|
@ -49,16 +49,17 @@ export class Firefox implements BrowserType<FFBrowser> {
|
||||||
|
|
||||||
async launch(options: LaunchOptions = {}): Promise<FFBrowser> {
|
async launch(options: LaunchOptions = {}): Promise<FFBrowser> {
|
||||||
assert(!(options as any).userDataDir, 'userDataDir option is not supported in `browserType.launch`. Use `browserType.launchPersistentContext` instead');
|
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 => {
|
const browser = await WebSocketTransport.connect(browserServer.wsEndpoint()!, transport => {
|
||||||
return FFBrowser.connect(transport, false, options.slowMo);
|
return FFBrowser.connect(transport, false, options.slowMo);
|
||||||
});
|
});
|
||||||
browser._ownedServer = browserServer;
|
browser._ownedServer = browserServer;
|
||||||
|
browser._downloadsPath = downloadsPath;
|
||||||
return browser;
|
return browser;
|
||||||
}
|
}
|
||||||
|
|
||||||
async launchServer(options: LaunchServerOptions = {}): Promise<BrowserServer> {
|
async launchServer(options: LaunchServerOptions = {}): Promise<BrowserServer> {
|
||||||
return await this._launchServer(options, 'server');
|
return (await this._launchServer(options, 'server')).browserServer;
|
||||||
}
|
}
|
||||||
|
|
||||||
async launchPersistentContext(userDataDir: string, options: LaunchOptions = {}): Promise<BrowserContext> {
|
async launchPersistentContext(userDataDir: string, options: LaunchOptions = {}): Promise<BrowserContext> {
|
||||||
|
|
@ -66,17 +67,18 @@ export class Firefox implements BrowserType<FFBrowser> {
|
||||||
timeout = 30000,
|
timeout = 30000,
|
||||||
slowMo = 0,
|
slowMo = 0,
|
||||||
} = options;
|
} = 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 => {
|
const browser = await WebSocketTransport.connect(browserServer.wsEndpoint()!, transport => {
|
||||||
return FFBrowser.connect(transport, true, slowMo);
|
return FFBrowser.connect(transport, true, slowMo);
|
||||||
});
|
});
|
||||||
browser._ownedServer = browserServer;
|
browser._ownedServer = browserServer;
|
||||||
|
browser._downloadsPath = downloadsPath;
|
||||||
await helper.waitWithTimeout(browser._firstPagePromise, 'first page', timeout);
|
await helper.waitWithTimeout(browser._firstPagePromise, 'first page', timeout);
|
||||||
const browserContext = browser._defaultContext;
|
const browserContext = browser._defaultContext;
|
||||||
return browserContext;
|
return browserContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _launchServer(options: LaunchServerOptions, launchType: LaunchType, userDataDir?: string): Promise<BrowserServer> {
|
private async _launchServer(options: LaunchServerOptions, launchType: LaunchType, userDataDir?: string): Promise<{ browserServer: BrowserServer, downloadsPath: string }> {
|
||||||
const {
|
const {
|
||||||
ignoreDefaultArgs = false,
|
ignoreDefaultArgs = false,
|
||||||
args = [],
|
args = [],
|
||||||
|
|
@ -110,7 +112,7 @@ export class Firefox implements BrowserType<FFBrowser> {
|
||||||
if (!firefoxExecutable)
|
if (!firefoxExecutable)
|
||||||
throw new Error(`No executable path is specified. Pass "executablePath" option directly.`);
|
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,
|
executablePath: firefoxExecutable,
|
||||||
args: firefoxArguments,
|
args: firefoxArguments,
|
||||||
env: os.platform() === 'linux' ? {
|
env: os.platform() === 'linux' ? {
|
||||||
|
|
@ -146,7 +148,7 @@ export class Firefox implements BrowserType<FFBrowser> {
|
||||||
const webSocketWrapper = launchType === 'server' ? (await WebSocketTransport.connect(innerEndpoint, t => wrapTransportWithWebSocket(t, port))) : new WebSocketWrapper(innerEndpoint, []);
|
const webSocketWrapper = launchType === 'server' ? (await WebSocketTransport.connect(innerEndpoint, t => wrapTransportWithWebSocket(t, port))) : new WebSocketWrapper(innerEndpoint, []);
|
||||||
browserWSEndpoint = webSocketWrapper.wsEndpoint;
|
browserWSEndpoint = webSocketWrapper.wsEndpoint;
|
||||||
browserServer = new BrowserServer(launchedProcess, gracefullyClose, webSocketWrapper);
|
browserServer = new BrowserServer(launchedProcess, gracefullyClose, webSocketWrapper);
|
||||||
return browserServer;
|
return {browserServer, downloadsPath};
|
||||||
}
|
}
|
||||||
|
|
||||||
async connect(options: ConnectOptions): Promise<FFBrowser> {
|
async connect(options: ConnectOptions): Promise<FFBrowser> {
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,9 @@
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
module.exports.describe = function({browserType, CHROMIUM, WEBKIT, FFOX, WIN, MAC}) {
|
module.exports.describe = function({browserType, defaultBrowserOptions, CHROMIUM, WEBKIT, FFOX, WIN, MAC}) {
|
||||||
|
|
||||||
describe.fail(FFOX)('Download', function() {
|
describe('Download', function() {
|
||||||
beforeEach(async(state) => {
|
beforeEach(async(state) => {
|
||||||
state.server.setRoute('/download', (req, res) => {
|
state.server.setRoute('/download', (req, res) => {
|
||||||
res.setHeader('Content-Type', 'application/octet-stream');
|
res.setHeader('Content-Type', 'application/octet-stream');
|
||||||
|
|
@ -97,7 +97,7 @@ module.exports.describe = function({browserType, CHROMIUM, WEBKIT, FFOX, WIN, MA
|
||||||
expect(fs.existsSync(path1)).toBeFalsy();
|
expect(fs.existsSync(path1)).toBeFalsy();
|
||||||
expect(fs.existsSync(path2)).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 browser = await browserType.launch(defaultBrowserOptions);
|
||||||
const page = await browser.newPage({ acceptDownloads: true });
|
const page = await browser.newPage({ acceptDownloads: true });
|
||||||
await page.setContent(`<a download=true href="${server.PREFIX}/download">download</a>`);
|
await page.setContent(`<a download=true href="${server.PREFIX}/download">download</a>`);
|
||||||
|
|
|
||||||
|
|
@ -30,8 +30,7 @@ const BROWSER_CONFIGS = [
|
||||||
...require('../lib/events').Events,
|
...require('../lib/events').Events,
|
||||||
...require('../lib/chromium/events').Events,
|
...require('../lib/chromium/events').Events,
|
||||||
},
|
},
|
||||||
missingCoverage: ['browserContext.setGeolocation', 'browserContext.setOffline', 'cDPSession.send', 'cDPSession.detach', 'page.emit("download")',
|
missingCoverage: ['browserContext.setGeolocation', 'browserContext.setOffline', 'cDPSession.send', 'cDPSession.detach'],
|
||||||
'download.url', 'download.path', 'download.failure', 'download.createReadStream', 'download.delete'],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'WebKit',
|
name: 'WebKit',
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue