diff --git a/docs/api.md b/docs/api.md index 426bc41630..39c3a2e702 100644 --- a/docs/api.md +++ b/docs/api.md @@ -71,6 +71,7 @@ * [event: 'console'](#event-console) * [event: 'dialog'](#event-dialog) * [event: 'domcontentloaded'](#event-domcontentloaded) + * [event: 'filechooser'](#event-filechooser) * [event: 'frameattached'](#event-frameattached) * [event: 'framedetached'](#event-framedetached) * [event: 'framenavigated'](#event-framenavigated) @@ -176,10 +177,6 @@ * [chromium.startTracing(page, [options])](#chromiumstarttracingpage-options) * [chromium.stopTracing()](#chromiumstoptracing) * [chromium.wsEndpoint()](#chromiumwsendpoint) -- [class: FileChooser](#class-filechooser) - * [fileChooser.accept(filePaths)](#filechooseracceptfilepaths) - * [fileChooser.cancel()](#filechoosercancel) - * [fileChooser.isMultiple()](#filechooserismultiple) - [class: Dialog](#class-dialog) * [dialog.accept([promptText])](#dialogacceptprompttext) * [dialog.defaultValue()](#dialogdefaultvalue) @@ -972,6 +969,19 @@ Emitted when a JavaScript dialog appears, such as `alert`, `prompt`, `confirm` o Emitted when the JavaScript [`DOMContentLoaded`](https://developer.mozilla.org/en-US/docs/Web/Events/DOMContentLoaded) event is dispatched. +#### event: 'filechooser' +- <[Object]> + - `element` <[ElementHandle]> handle to the input element that was clicked + - `multiple` <[boolean]> Whether file chooser allow for [multiple](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#attr-multiple) file selection. + +Emitted when a file chooser is supposed to appear, such as after clicking the ``. Playwright can respond to it via setting the input files using [`elementHandle.setInputFiles`](#elementhandlesetinputfilesfiles). + +```js +page.on('filechooser', async ({element, multiple}) => { + await element.setInputFiles('/tmp/myfile.pdf'); +}); +``` + #### event: 'frameattached' - <[Frame]> @@ -1795,7 +1805,9 @@ Shortcut for [page.mainFrame().waitFor(selectorOrFunctionOrTimeout[, options[, . #### page.waitForFileChooser([options]) - `options` <[Object]> Optional waiting parameters - `timeout` <[number]> Maximum wait time in milliseconds, defaults to 30 seconds, pass `0` to disable the timeout. The default value can be changed by using the [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) method. -- returns: <[Promise]<[FileChooser]>> A promise that resolves after a page requests a file picker. +- returns: <[Promise]<[Object]>> + - `element` <[ElementHandle]> handle to the input element that was clicked + - `multiple` <[boolean]> Whether file chooser allow for [multiple](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#attr-multiple) file selection. > **NOTE** In non-headless Chromium, this method results in the native file picker dialog **not showing up** for the user. @@ -1804,16 +1816,15 @@ The following example clicks a button that issues a file chooser, and then responds with `/tmp/myfile.pdf` as if a user has selected this file. ```js -const [fileChooser] = await Promise.all([ +const [{element, multiple}] = await Promise.all([ page.waitForFileChooser(), page.click('#upload-file-button'), // some button that triggers file selection ]); -await fileChooser.accept(['/tmp/myfile.pdf']); +await element.setInputFiles('/tmp/myfile.pdf'); ``` > **NOTE** This must be called *before* the file chooser is launched. It will not return a currently active file chooser. - #### page.waitForFunction(pageFunction[, options[, ...args]]) - `pageFunction` <[function]|[string]> Function to be evaluated in browser context - `options` <[Object]> Optional waiting parameters @@ -2396,37 +2407,6 @@ Browser websocket endpoint which can be used as an argument to You can find the `webSocketDebuggerUrl` from `http://${host}:${port}/json/version`. Learn more about the [devtools protocol](https://chromedevtools.github.io/devtools-protocol) and the [browser endpoint](https://chromedevtools.github.io/devtools-protocol/#how-do-i-access-the-browser-target). -### class: FileChooser - -[FileChooser] objects are returned via the ['page.waitForFileChooser'](#pagewaitforfilechooseroptions) method. - -File choosers let you react to the page requesting for a file. - -An example of using [FileChooser]: - -```js -const [fileChooser] = await Promise.all([ - page.waitForFileChooser(), - page.click('#upload-file-button'), // some button that triggers file selection -]); -await fileChooser.accept(['/tmp/myfile.pdf']); -``` - -> **NOTE** In browsers, only one file chooser can be opened at a time. -> All file choosers must be accepted or canceled. Not doing so will prevent subsequent file choosers from appearing. - -#### fileChooser.accept(filePaths) -- `filePaths` <[Array]<[string]>> Accept the file chooser request with given paths. If some of the `filePaths` are relative paths, then they are resolved relative to the [current working directory](https://nodejs.org/api/process.html#process_process_cwd). -- returns: <[Promise]> - -#### fileChooser.cancel() -- returns: <[Promise]> - -Closes the file chooser without selecting any files. - -#### fileChooser.isMultiple() -- returns: <[boolean]> Whether file chooser allow for [multiple](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#attr-multiple) file selection. - ### class: Dialog [Dialog] objects are dispatched by page via the ['dialog'](#event-dialog) event. diff --git a/src/chromium/Page.ts b/src/chromium/Page.ts index e47dd2f5d9..e498eb7cb5 100644 --- a/src/chromium/Page.ts +++ b/src/chromium/Page.ts @@ -161,9 +161,10 @@ export class Page extends EventEmitter { const interceptors = Array.from(this._fileChooserInterceptors); this._fileChooserInterceptors.clear(); const multiple = await handle.evaluate((element: HTMLInputElement) => !!element.multiple); - const fileChooser = new FileChooser(handle, multiple); + const fileChooser = { element: handle, multiple }; for (const interceptor of interceptors) interceptor.call(null, fileChooser); + this.emit(Events.Page.FileChooser, fileChooser); } async waitForFileChooser(options: { timeout?: number; } = {}): Promise { @@ -729,28 +730,7 @@ export class ConsoleMessage { } } -export class FileChooser { - private _element: ElementHandle; - private _multiple: boolean; - private _handled = false; - - constructor(element: ElementHandle, multiple: boolean) { - this._element = element; - this._multiple = multiple; - } - - isMultiple(): boolean { - return this._multiple; - } - - async accept(filePaths: string[]): Promise { - assert(!this._handled, 'Cannot accept FileChooser which is already handled!'); - this._handled = true; - await this._element.setInputFiles(...filePaths); - } - - async cancel(): Promise { - assert(!this._handled, 'Cannot cancel FileChooser which is already handled!'); - this._handled = true; - } -} +type FileChooser = { + element: ElementHandle, + multiple: boolean +}; diff --git a/src/chromium/api.ts b/src/chromium/api.ts index d15ccb9334..bcc99cb689 100644 --- a/src/chromium/api.ts +++ b/src/chromium/api.ts @@ -20,6 +20,6 @@ export { Frame } from '../frames'; export { Keyboard, Mouse } from '../input'; export { ElementHandle } from './JSHandle'; export { Request, Response } from '../network'; -export { ConsoleMessage, FileChooser, Page } from './Page'; +export { ConsoleMessage, Page } from './Page'; export { Playwright } from './Playwright'; export { Target } from './Target'; diff --git a/src/chromium/events.ts b/src/chromium/events.ts index a23f209b0e..fd3a0e7c85 100644 --- a/src/chromium/events.ts +++ b/src/chromium/events.ts @@ -20,6 +20,7 @@ export const Events = { Close: 'close', Console: 'console', Dialog: 'dialog', + FileChooser: 'filechooser', DOMContentLoaded: 'domcontentloaded', // Can't use just 'error' due to node.js special treatment of error events. // @see https://nodejs.org/api/events.html#events_error_events diff --git a/src/firefox/Page.ts b/src/firefox/Page.ts index 15ec3936c9..5c127c2a0a 100644 --- a/src/firefox/Page.ts +++ b/src/firefox/Page.ts @@ -552,9 +552,10 @@ export class Page extends EventEmitter { const interceptors = Array.from(this._fileChooserInterceptors); this._fileChooserInterceptors.clear(); const multiple = await handle.evaluate((element: HTMLInputElement) => !!element.multiple); - const fileChooser = new FileChooser(handle, multiple); + const fileChooser = { element: handle, multiple }; for (const interceptor of interceptors) interceptor.call(null, fileChooser); + this.emit(Events.Page.FileChooser, fileChooser); } } @@ -619,28 +620,7 @@ export type Viewport = { hasTouch?: boolean; } -export class FileChooser { - private _element: ElementHandle; - private _multiple: boolean; - private _handled = false; - - constructor(element: ElementHandle, multiple: boolean) { - this._element = element; - this._multiple = multiple; - } - - isMultiple(): boolean { - return this._multiple; - } - - async accept(filePaths: string[]): Promise { - assert(!this._handled, 'Cannot accept FileChooser which is already handled!'); - this._handled = true; - await this._element.setInputFiles(...filePaths); - } - - async cancel(): Promise { - assert(!this._handled, 'Cannot cancel FileChooser which is already handled!'); - this._handled = true; - } -} +type FileChooser = { + element: ElementHandle, + multiple: boolean +}; diff --git a/src/firefox/api.ts b/src/firefox/api.ts index f17df9e964..0a08f152d2 100644 --- a/src/firefox/api.ts +++ b/src/firefox/api.ts @@ -13,6 +13,6 @@ export { Permissions } from './features/permissions'; export { Frame } from './FrameManager'; export { ElementHandle } from './JSHandle'; export { Request, Response } from '../network'; -export { ConsoleMessage, FileChooser, Page } from './Page'; +export { ConsoleMessage, Page } from './Page'; export { Playwright } from './Playwright'; diff --git a/src/firefox/events.ts b/src/firefox/events.ts index 276be50f7f..b9ea22b794 100644 --- a/src/firefox/events.ts +++ b/src/firefox/events.ts @@ -20,6 +20,7 @@ export const Events = { Close: 'close', Console: 'console', Dialog: 'dialog', + FileChooser: 'filechooser', DOMContentLoaded: 'domcontentloaded', // Can't use just 'error' due to node.js special treatment of error events. // @see https://nodejs.org/api/events.html#events_error_events diff --git a/src/webkit/Page.ts b/src/webkit/Page.ts index 25fc1710a1..bdd2fd6296 100644 --- a/src/webkit/Page.ts +++ b/src/webkit/Page.ts @@ -462,9 +462,10 @@ export class Page extends EventEmitter { const interceptors = Array.from(this._fileChooserInterceptors); this._fileChooserInterceptors.clear(); const multiple = await handle.evaluate((element: HTMLInputElement) => !!element.multiple); - const fileChooser = new FileChooser(handle, multiple); + const fileChooser = { element: handle, multiple }; for (const interceptor of interceptors) interceptor.call(null, fileChooser); + this.emit(Events.Page.FileChooser, fileChooser); } get mouse(): input.Mouse { @@ -586,28 +587,7 @@ export class ConsoleMessage { } } -export class FileChooser { - private _element: ElementHandle; - private _multiple: boolean; - private _handled = false; - - constructor(element: ElementHandle, multiple: boolean) { - this._element = element; - this._multiple = multiple; - } - - isMultiple(): boolean { - return this._multiple; - } - - async accept(filePaths: string[]): Promise { - assert(!this._handled, 'Cannot accept FileChooser which is already handled!'); - this._handled = true; - await this._element.setInputFiles(...filePaths); - } - - async cancel(): Promise { - assert(!this._handled, 'Cannot cancel FileChooser which is already handled!'); - this._handled = true; - } -} +type FileChooser = { + element: ElementHandle, + multiple: boolean +}; diff --git a/src/webkit/events.ts b/src/webkit/events.ts index 54d33500e5..de060389b0 100644 --- a/src/webkit/events.ts +++ b/src/webkit/events.ts @@ -20,6 +20,7 @@ export const Events = { Close: 'close', Console: 'console', Dialog: 'dialog', + FileChooser: 'filechooser', DOMContentLoaded: 'domcontentloaded', Request: 'request', Response: 'response',