-
Notifications
You must be signed in to change notification settings - Fork 5k
feat(downloads): support downloads on cr and wk #1632
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
5a81f42 to
dec9bb6
Compare
| Deletes the downloaded file. | ||
|
|
||
| #### download.error() | ||
| - returns: <[Promise]<null|[string]>> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What kind of errors are returned here? When should I use this string?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WebKit will report network error here. If you did not pass acceptDownloads, you get canceled error as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this match the response.failure() api? It is equivalent right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is not equivalent, but it is similar, so i can rename.
| const page = await context.newPage(); | ||
| page._ownedContext = context; | ||
| return page; | ||
| export abstract class BrowserBase extends EventEmitter implements Browser { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Browser already extends EventEmitter
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My understanding is that Browser is an interface, so it can't extend class
|
|
||
| async _initialize() { | ||
| const promises: Promise<any>[] = [ | ||
| this._browser._session.send('Browser.setDownloadBehavior', { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't contextId be passed too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes :)
| import { Readable } from 'stream'; | ||
|
|
||
| export class Download { | ||
| private _downloadsPath: string; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: readonly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't do it systematically :/
src/server/processLauncher.ts
Outdated
| import { helper } from '../helper'; | ||
| import * as fs from 'fs'; | ||
| import * as os from 'os'; | ||
| import * as path from 'path'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: run 'Organize imports'?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure
|
|
||
| export class InterceptingTransport implements ConnectionTransport { | ||
| private readonly _delegate: ConnectionTransport; | ||
| private _interceptor: (message: ProtocolRequest) => ProtocolRequest; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: readonly too?
src/webkit/wkPage.ts
Outdated
| this._page._frameManager.frameLifecycleEvent(frameId, event); | ||
| } | ||
|
|
||
| private _onDownloadCreated() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
revert?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
test/download.spec.js
Outdated
| expect(fs.existsSync(path1)).toBeFalsy(); | ||
| expect(fs.existsSync(path2)).toBeFalsy(); | ||
| }); | ||
| it.fail(true)('should delete downloads on browser gone', async ({ server, defaultBrowserOptions }) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unskip now that Joel has a fix?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As soon as it lands
| } | ||
|
|
||
| _didCloseInternal() { | ||
| async _didCloseInternal(omitDeleteDownloads = false) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was hard for me to follow. Can we leave it as _didCloseInternal(), and add async deleteDownloads(). Is it important that the close event fires before the downloads have been deleted?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My worry is that it is easy to forget to call deleteDownloads. As of now, they know they need to call _didCloseInternal. Or what do you mean?
| [/object/g, 'Object'], | ||
| [/Promise\<T\>/, 'Promise<Object>'] | ||
| [/Promise\<T\>/, 'Promise<Object>'], | ||
| [/Readable/, 'stream.Readable'] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we leave this as Readable, it shouldn't need any special casing or overrides.
src/download.ts
Outdated
| return fileName; | ||
| } | ||
|
|
||
| async error(): Promise<string | null> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
May be waitForFinished() ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ugly?
test/playwright.spec.js
Outdated
| state._stderr.on('line', dumperr); | ||
| if (dumpProtocolOnFailure) | ||
| state.browser._setDebugFunction(data => test.output.push(`\x1b[32m[pw:protocol]\x1b[0m ${data}`)); | ||
| debug('pw:protocol').output = data => test.output.push(`\x1b[32m[pw:protocol]\x1b[0m ${data}`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in my console: debug('str') !== debug('str')
How does this work?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good question.
dec9bb6 to
cc1666f
Compare
| #### event: 'download' | ||
| - <[Download]> | ||
|
|
||
| Emitted when attachment is downloaded. User can access basic file operations on downloaded content via the passed [Download] instance. Browser context must be created with the `acceptDownloads` set to `true` when user needs access to the downloaded content. If `acceptDownloads` is not set or set to `false`, download events are emitted, but the actual download is not performed and user has no access to the downloaded files. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
super nit: we don't usually mention "user" in the docs.
| async path(): Promise<string | null> { | ||
| if (!this._acceptDownloads) | ||
| throw new Error('Pass { acceptDownloads: true } when you are creating your browser context.'); | ||
| const fileName = path.join(this._downloadsPath, this._uuid); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a way to retrieve suggested file name, extension, mime type or url? I imagine that would be very handy, e.g. to assert that my "Download" button tries to download the "screenshot-{current-time}.png" file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that'd be super handy. Follow up?
| async delete(): Promise<void> { | ||
| if (!this._acceptDownloads || this._deleted) | ||
| return; | ||
| const fileName = await this.path(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should check _deleted after this await to avoid a race.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done!
src/download.ts
Outdated
| return; | ||
| const fileName = await this.path(); | ||
| this._deleted = true; | ||
| if (fileName && await util.promisify(fs.exists)(fileName)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Asynchronous exists check is racy. Better to always call unlink and catch ENOENT error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
test/download.spec.js
Outdated
| await browser.close(); | ||
| expect(fs.existsSync(path1)).toBeFalsy(); | ||
| expect(fs.existsSync(path2)).toBeFalsy(); | ||
| console.log(path.join(path1, '..')); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
stray log
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
test/download.spec.js
Outdated
| expect(fs.existsSync(path.join(path1, '..'))).toBeFalsy(); | ||
| }); | ||
| it('make coverage happy', async({page}) => { | ||
| // Otherwise FF gets mad. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a list of not covered methods in playwright.spec.js.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
test/playwright.spec.js
Outdated
| state._stderr.off('line', dumperr); | ||
| if (dumpProtocolOnFailure) | ||
| state.browser._setDebugFunction(() => void 0); | ||
| debug('pw:protocol').output = data => console.log; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure what this meant to be, but looks suspicious :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
| } | ||
| } | ||
|
|
||
| export class InterceptingTransport implements ConnectionTransport { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where is this one used?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nowhere just yet, but looks generally useful?
cc1666f to
7735a80
Compare
7735a80 to
f7f4de8
Compare
No description provided.