feat(downloads): expose suggested filename (#2062)

This commit is contained in:
Pavel Feldman 2020-05-12 19:23:08 -07:00 committed by GitHub
parent 84f966c301
commit f63ea3ffd2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 48 additions and 9 deletions

View File

@ -3050,6 +3050,7 @@ const path = await download.path();
- [download.delete()](#downloaddelete) - [download.delete()](#downloaddelete)
- [download.failure()](#downloadfailure) - [download.failure()](#downloadfailure)
- [download.path()](#downloadpath) - [download.path()](#downloadpath)
- [download.suggestedFilename()](#downloadsuggestedfilename)
- [download.url()](#downloadurl) - [download.url()](#downloadurl)
<!-- GEN:stop --> <!-- GEN:stop -->
@ -3073,6 +3074,11 @@ Returns download error if any.
Returns path to the downloaded file in case of successful download. Returns path to the downloaded file in case of successful download.
#### download.suggestedFilename()
- returns: <[string]>
Returns suggested filename for this download. It is typically computed by the browser from the [`Content-Disposition`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition) response header or the `download` attribute. See the spec on [whatwg](https://html.spec.whatwg.org/#downloading-resources). Different browsers can use different logic for computing it.
#### download.url() #### download.url()
- returns: <[string]> - returns: <[string]>

View File

@ -53,11 +53,18 @@ export abstract class BrowserBase extends EventEmitter implements Browser, Inner
return page; return page;
} }
_downloadCreated(page: Page, uuid: string, url: string) { _downloadCreated(page: Page, uuid: string, url: string, suggestedFilename?: string) {
const download = new Download(page, this._downloadsPath, uuid, url); const download = new Download(page, this._downloadsPath, uuid, url, suggestedFilename);
this._downloads.set(uuid, download); this._downloads.set(uuid, download);
} }
_downloadFilenameSuggested(uuid: string, suggestedFilename: string) {
const download = this._downloads.get(uuid);
if (!download)
return;
download._filenameSuggested(suggestedFilename);
}
_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)

View File

@ -670,7 +670,7 @@ class FrameSession {
} }
if (!originPage) if (!originPage)
return; return;
this._crPage._browserContext._browser._downloadCreated(originPage, payload.guid, payload.url); this._crPage._browserContext._browser._downloadCreated(originPage, payload.guid, payload.url, payload.suggestedFilename);
} }
_onDownloadProgress(payload: Protocol.Page.downloadProgressPayload) { _onDownloadProgress(payload: Protocol.Page.downloadProgressPayload) {

View File

@ -20,6 +20,7 @@ import * as util from 'util';
import { Page } from './page'; import { Page } from './page';
import { Events } from './events'; import { Events } from './events';
import { Readable } from 'stream'; import { Readable } from 'stream';
import { assert } from './helper';
export class Download { export class Download {
private _downloadsPath: string; private _downloadsPath: string;
@ -31,16 +32,25 @@ export class Download {
private _failure: string | null = null; private _failure: string | null = null;
private _deleted = false; private _deleted = false;
private _url: string; private _url: string;
private _suggestedFilename: string | undefined;
constructor(page: Page, downloadsPath: string, uuid: string, url: string) { constructor(page: Page, downloadsPath: string, uuid: string, url: string, suggestedFilename?: string) {
this._page = page; this._page = page;
this._downloadsPath = downloadsPath; this._downloadsPath = downloadsPath;
this._uuid = uuid; this._uuid = uuid;
this._url = url; this._url = url;
this._suggestedFilename = suggestedFilename;
this._finishedCallback = () => {}; this._finishedCallback = () => {};
this._finishedPromise = new Promise(f => this._finishedCallback = f); this._finishedPromise = new Promise(f => this._finishedCallback = f);
page._browserContext._downloads.add(this); page._browserContext._downloads.add(this);
this._acceptDownloads = !!this._page._browserContext._options.acceptDownloads; this._acceptDownloads = !!this._page._browserContext._options.acceptDownloads;
if (suggestedFilename !== undefined)
this._page.emit(Events.Page.Download, this);
}
_filenameSuggested(suggestedFilename: string) {
assert(this._suggestedFilename === undefined);
this._suggestedFilename = suggestedFilename;
this._page.emit(Events.Page.Download, this); this._page.emit(Events.Page.Download, this);
} }
@ -48,6 +58,10 @@ export class Download {
return this._url; return this._url;
} }
suggestedFilename(): string {
return this._suggestedFilename!;
}
async path(): Promise<string | null> { async path(): Promise<string | null> {
if (!this._acceptDownloads) if (!this._acceptDownloads)
throw new Error('Pass { acceptDownloads: true } when you are creating your browser context.'); throw new Error('Pass { acceptDownloads: true } when you are creating your browser context.');

View File

@ -130,7 +130,7 @@ export class FFBrowser extends BrowserBase {
} }
if (!originPage) if (!originPage)
return; return;
this._downloadCreated(originPage, payload.uuid, payload.url); this._downloadCreated(originPage, payload.uuid, payload.url, payload.suggestedFileName);
} }
_onDownloadFinished(payload: Protocol.Browser.downloadFinishedPayload) { _onDownloadFinished(payload: Protocol.Browser.downloadFinishedPayload) {

View File

@ -56,6 +56,7 @@ export class WKBrowser extends BrowserBase {
helper.addEventListener(this._browserSession, 'Playwright.pageProxyDestroyed', this._onPageProxyDestroyed.bind(this)), helper.addEventListener(this._browserSession, 'Playwright.pageProxyDestroyed', this._onPageProxyDestroyed.bind(this)),
helper.addEventListener(this._browserSession, 'Playwright.provisionalLoadFailed', event => this._onProvisionalLoadFailed(event)), helper.addEventListener(this._browserSession, 'Playwright.provisionalLoadFailed', event => this._onProvisionalLoadFailed(event)),
helper.addEventListener(this._browserSession, 'Playwright.downloadCreated', this._onDownloadCreated.bind(this)), helper.addEventListener(this._browserSession, 'Playwright.downloadCreated', this._onDownloadCreated.bind(this)),
helper.addEventListener(this._browserSession, 'Playwright.downloadFilenameSuggested', this._onDownloadFilenameSuggested.bind(this)),
helper.addEventListener(this._browserSession, 'Playwright.downloadFinished', this._onDownloadFinished.bind(this)), helper.addEventListener(this._browserSession, 'Playwright.downloadFinished', this._onDownloadFinished.bind(this)),
helper.addEventListener(this._browserSession, kPageProxyMessageReceived, this._onPageProxyMessageReceived.bind(this)), helper.addEventListener(this._browserSession, kPageProxyMessageReceived, this._onPageProxyMessageReceived.bind(this)),
]; ];
@ -111,6 +112,10 @@ export class WKBrowser extends BrowserBase {
this._downloadCreated(originPage, payload.uuid, payload.url); this._downloadCreated(originPage, payload.uuid, payload.url);
} }
_onDownloadFilenameSuggested(payload: Protocol.Playwright.downloadFilenameSuggestedPayload) {
this._downloadFilenameSuggested(payload.uuid, payload.suggestedFilename);
}
_onDownloadFinished(payload: Protocol.Playwright.downloadFinishedPayload) { _onDownloadFinished(payload: Protocol.Playwright.downloadFinishedPayload) {
this._downloadFinished(payload.uuid, payload.error); this._downloadFinished(payload.uuid, payload.error);
} }

View File

@ -25,16 +25,22 @@ describe('Download', function() {
res.setHeader('Content-Disposition', 'attachment'); res.setHeader('Content-Disposition', 'attachment');
res.end(`Hello world`); res.end(`Hello world`);
}); });
state.server.setRoute('/downloadWithFilename', (req, res) => {
res.setHeader('Content-Type', 'application/octet-stream');
res.setHeader('Content-Disposition', 'attachment; filename=file.txt');
res.end(`Hello world`);
});
}); });
it('should report downloads with acceptDownloads: false', async({page, server}) => { it('should report downloads with acceptDownloads: false', async({page, server}) => {
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`); await page.setContent(`<a href="${server.PREFIX}/downloadWithFilename">download</a>`);
const [ download ] = await Promise.all([ const [ download ] = await Promise.all([
page.waitForEvent('download'), page.waitForEvent('download'),
page.click('a') page.click('a')
]); ]);
let error; let error;
expect(download.url()).toBe(`${server.PREFIX}/download`); expect(download.url()).toBe(`${server.PREFIX}/downloadWithFilename`);
expect(download.suggestedFilename()).toBe(`file.txt`);
await download.path().catch(e => error = e); await download.path().catch(e => error = e);
expect(await download.failure()).toContain('acceptDownloads'); expect(await download.failure()).toContain('acceptDownloads');
expect(error.message).toContain('acceptDownloads: true'); expect(error.message).toContain('acceptDownloads: true');
@ -51,8 +57,8 @@ describe('Download', function() {
expect(fs.readFileSync(path).toString()).toBe('Hello world'); expect(fs.readFileSync(path).toString()).toBe('Hello world');
await page.close(); await page.close();
}); });
it.fail(WEBKIT)('should report non-navigation downloads', async({browser, server}) => { it.fail(WEBKIT && MAC)('should report non-navigation downloads', async({browser, server}) => {
// Our WebKit embedder does not download in this case, although Safari does. // Mac WebKit embedder does not download in this case, although Safari does.
server.setRoute('/download', (req, res) => { server.setRoute('/download', (req, res) => {
res.setHeader('Content-Type', 'application/octet-stream'); res.setHeader('Content-Type', 'application/octet-stream');
res.end(`Hello world`); res.end(`Hello world`);
@ -65,6 +71,7 @@ describe('Download', function() {
page.waitForEvent('download'), page.waitForEvent('download'),
page.click('a') page.click('a')
]); ]);
expect(download.suggestedFilename()).toBe(`file.txt`);
const path = await download.path(); const path = await download.path();
expect(fs.existsSync(path)).toBeTruthy(); expect(fs.existsSync(path)).toBeTruthy();
expect(fs.readFileSync(path).toString()).toBe('Hello world'); expect(fs.readFileSync(path).toString()).toBe('Hello world');