mirror of
https://github.com/puppeteer/puppeteer
synced 2024-06-14 14:02:48 +00:00
feat(page): introduce file chooser interception (#4653)
This patch introduces a page.waitForFileChooser() method that adds a watchdog to wait for file chooser dialogs. This lets Puppeteer users to capture file chooser requests and fulfill/cancel them if necessary. Fixes #2946
This commit is contained in:
parent
2abaac10aa
commit
ea28cccfe0
138
docs/api.md
138
docs/api.md
@ -152,6 +152,7 @@
|
||||
* [page.url()](#pageurl)
|
||||
* [page.viewport()](#pageviewport)
|
||||
* [page.waitFor(selectorOrFunctionOrTimeout[, options[, ...args]])](#pagewaitforselectororfunctionortimeout-options-args)
|
||||
* [page.waitForFileChooser([options])](#pagewaitforfilechooseroptions)
|
||||
* [page.waitForFunction(pageFunction[, options[, ...args]])](#pagewaitforfunctionpagefunction-options-args)
|
||||
* [page.waitForNavigation([options])](#pagewaitfornavigationoptions)
|
||||
* [page.waitForRequest(urlOrPredicate[, options])](#pagewaitforrequesturlorpredicate-options)
|
||||
@ -182,6 +183,10 @@
|
||||
- [class: Tracing](#class-tracing)
|
||||
* [tracing.start([options])](#tracingstartoptions)
|
||||
* [tracing.stop()](#tracingstop)
|
||||
- [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)
|
||||
@ -1727,6 +1732,7 @@ This setting will change the default maximum time for the following methods and
|
||||
- [page.reload([options])](#pagereloadoptions)
|
||||
- [page.setContent(html[, options])](#pagesetcontenthtml-options)
|
||||
- [page.waitFor(selectorOrFunctionOrTimeout[, options[, ...args]])](#pagewaitforselectororfunctionortimeout-options-args)
|
||||
- [page.waitForFileChooser([options])](#pagewaitforfilechooseroptions)
|
||||
- [page.waitForFunction(pageFunction[, options[, ...args]])](#pagewaitforfunctionpagefunction-options-args)
|
||||
- [page.waitForNavigation([options])](#pagewaitfornavigationoptions)
|
||||
- [page.waitForRequest(urlOrPredicate[, options])](#pagewaitforrequesturlorpredicate-options)
|
||||
@ -1914,6 +1920,28 @@ await page.waitFor(selector => !!document.querySelector(selector), {}, selector)
|
||||
|
||||
Shortcut for [page.mainFrame().waitFor(selectorOrFunctionOrTimeout[, options[, ...args]])](#framewaitforselectororfunctionortimeout-options-args).
|
||||
|
||||
#### 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.
|
||||
|
||||
> **NOTE** In non-headless Chromium, this method results in the native file picker dialog **not showing up** for the user.
|
||||
|
||||
This method is typically coupled with an action that triggers file choosing.
|
||||
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([
|
||||
page.waitForFileChooser(),
|
||||
page.click('#upload-file-button'), // some button that triggers file selection
|
||||
]);
|
||||
await fileChooser.accept(['/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
|
||||
@ -2351,6 +2379,37 @@ Only one trace can be active at a time per browser.
|
||||
#### tracing.stop()
|
||||
- returns: <[Promise]<[Buffer]>> Promise which resolves to buffer with trace data.
|
||||
|
||||
### 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.
|
||||
@ -3626,50 +3685,51 @@ TimeoutError is emitted whenever certain operations are terminated due to timeou
|
||||
|
||||
|
||||
|
||||
[AXNode]: #accessibilitysnapshotoptions "AXNode"
|
||||
[Accessibility]: #class-accessibility "Accessibility"
|
||||
[Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array "Array"
|
||||
[boolean]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Boolean_type "Boolean"
|
||||
[Body]: #class-body "Body"
|
||||
[BrowserContext]: #class-browsercontext "BrowserContext"
|
||||
[BrowserFetcher]: #class-browserfetcher "BrowserFetcher"
|
||||
[Browser]: #class-browser "Browser"
|
||||
[Buffer]: https://nodejs.org/api/buffer.html#buffer_class_buffer "Buffer"
|
||||
[function]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function "Function"
|
||||
[number]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Number_type "Number"
|
||||
[CDPSession]: #class-cdpsession "CDPSession"
|
||||
[ChildProcess]: https://nodejs.org/api/child_process.html "ChildProcess"
|
||||
[ConnectionTransport]: ../lib/WebSocketTransport.js "ConnectionTransport"
|
||||
[ConsoleMessage]: #class-consolemessage "ConsoleMessage"
|
||||
[Coverage]: #class-coverage "Coverage"
|
||||
[Dialog]: #class-dialog "Dialog"
|
||||
[ElementHandle]: #class-elementhandle "ElementHandle"
|
||||
[Element]: https://developer.mozilla.org/en-US/docs/Web/API/element "Element"
|
||||
[Error]: https://nodejs.org/api/errors.html#errors_class_error "Error"
|
||||
[ExecutionContext]: #class-executioncontext "ExecutionContext"
|
||||
[FileChooser]: #class-filechooser "FileChooser"
|
||||
[Frame]: #class-frame "Frame"
|
||||
[JSHandle]: #class-jshandle "JSHandle"
|
||||
[Keyboard]: #class-keyboard "Keyboard"
|
||||
[Map]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map "Map"
|
||||
[Mouse]: #class-mouse "Mouse"
|
||||
[Object]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object "Object"
|
||||
[origin]: https://developer.mozilla.org/en-US/docs/Glossary/Origin "Origin"
|
||||
[Page]: #class-page "Page"
|
||||
[Promise]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise "Promise"
|
||||
[string]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#String_type "String"
|
||||
[stream.Readable]: https://nodejs.org/api/stream.html#stream_class_stream_readable "stream.Readable"
|
||||
[CDPSession]: #class-cdpsession "CDPSession"
|
||||
[BrowserFetcher]: #class-browserfetcher "BrowserFetcher"
|
||||
[BrowserContext]: #class-browsercontext "BrowserContext"
|
||||
[Error]: https://nodejs.org/api/errors.html#errors_class_error "Error"
|
||||
[Frame]: #class-frame "Frame"
|
||||
[ConsoleMessage]: #class-consolemessage "ConsoleMessage"
|
||||
[ChildProcess]: https://nodejs.org/api/child_process.html "ChildProcess"
|
||||
[Coverage]: #class-coverage "Coverage"
|
||||
[iterator]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols "Iterator"
|
||||
[Response]: #class-response "Response"
|
||||
[Request]: #class-request "Request"
|
||||
[Browser]: #class-browser "Browser"
|
||||
[TimeoutError]: #class-timeouterror "TimeoutError"
|
||||
[Body]: #class-body "Body"
|
||||
[Element]: https://developer.mozilla.org/en-US/docs/Web/API/element "Element"
|
||||
[Keyboard]: #class-keyboard "Keyboard"
|
||||
[Dialog]: #class-dialog "Dialog"
|
||||
[JSHandle]: #class-jshandle "JSHandle"
|
||||
[ExecutionContext]: #class-executioncontext "ExecutionContext"
|
||||
[Mouse]: #class-mouse "Mouse"
|
||||
[Map]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map "Map"
|
||||
[selector]: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors "selector"
|
||||
[Tracing]: #class-tracing "Tracing"
|
||||
[ElementHandle]: #class-elementhandle "ElementHandle"
|
||||
[UIEvent.detail]: https://developer.mozilla.org/en-US/docs/Web/API/UIEvent/detail "UIEvent.detail"
|
||||
[Serializable]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#Description "Serializable"
|
||||
[Touchscreen]: #class-touchscreen "Touchscreen"
|
||||
[Target]: #class-target "Target"
|
||||
[USKeyboardLayout]: ../lib/USKeyboardLayout.js "USKeyboardLayout"
|
||||
[xpath]: https://developer.mozilla.org/en-US/docs/Web/XPath "xpath"
|
||||
[UnixTime]: https://en.wikipedia.org/wiki/Unix_time "Unix Time"
|
||||
[Response]: #class-response "Response"
|
||||
[SecurityDetails]: #class-securitydetails "SecurityDetails"
|
||||
[Serializable]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#Description "Serializable"
|
||||
[Target]: #class-target "Target"
|
||||
[TimeoutError]: #class-timeouterror "TimeoutError"
|
||||
[Touchscreen]: #class-touchscreen "Touchscreen"
|
||||
[Tracing]: #class-tracing "Tracing"
|
||||
[UIEvent.detail]: https://developer.mozilla.org/en-US/docs/Web/API/UIEvent/detail "UIEvent.detail"
|
||||
[USKeyboardLayout]: ../lib/USKeyboardLayout.js "USKeyboardLayout"
|
||||
[UnixTime]: https://en.wikipedia.org/wiki/Unix_time "Unix Time"
|
||||
[Worker]: #class-worker "Worker"
|
||||
[Accessibility]: #class-accessibility "Accessibility"
|
||||
[AXNode]: #accessibilitysnapshotoptions "AXNode"
|
||||
[ConnectionTransport]: ../lib/WebSocketTransport.js "ConnectionTransport"
|
||||
[boolean]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Boolean_type "Boolean"
|
||||
[function]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function "Function"
|
||||
[iterator]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols "Iterator"
|
||||
[number]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Number_type "Number"
|
||||
[origin]: https://developer.mozilla.org/en-US/docs/Glossary/Origin "Origin"
|
||||
[selector]: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors "selector"
|
||||
[stream.Readable]: https://nodejs.org/api/stream.html#stream_class_stream_readable "stream.Readable"
|
||||
[string]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#String_type "String"
|
||||
[xpath]: https://developer.mozilla.org/en-US/docs/Web/XPath "xpath"
|
||||
|
102
lib/Page.js
102
lib/Page.js
@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const EventEmitter = require('events');
|
||||
const mime = require('mime');
|
||||
const {Events} = require('./Events');
|
||||
@ -43,12 +44,7 @@ class Page extends EventEmitter {
|
||||
*/
|
||||
static async create(client, target, ignoreHTTPSErrors, defaultViewport, screenshotTaskQueue) {
|
||||
const page = new Page(client, target, ignoreHTTPSErrors, screenshotTaskQueue);
|
||||
await Promise.all([
|
||||
page._frameManager.initialize(),
|
||||
client.send('Target.setAutoAttach', {autoAttach: true, waitForDebuggerOnStart: false, flatten: true}),
|
||||
client.send('Performance.enable', {}),
|
||||
client.send('Log.enable', {}),
|
||||
]);
|
||||
await page._initialize();
|
||||
if (defaultViewport)
|
||||
await page.setViewport(defaultViewport);
|
||||
return page;
|
||||
@ -115,6 +111,8 @@ class Page extends EventEmitter {
|
||||
networkManager.on(Events.NetworkManager.Response, event => this.emit(Events.Page.Response, event));
|
||||
networkManager.on(Events.NetworkManager.RequestFailed, event => this.emit(Events.Page.RequestFailed, event));
|
||||
networkManager.on(Events.NetworkManager.RequestFinished, event => this.emit(Events.Page.RequestFinished, event));
|
||||
this._fileChooserInterceptionIsDisabled = false;
|
||||
this._fileChooserInterceptors = new Set();
|
||||
|
||||
client.on('Page.domContentEventFired', event => this.emit(Events.Page.DOMContentLoaded));
|
||||
client.on('Page.loadEventFired', event => this.emit(Events.Page.Load));
|
||||
@ -125,12 +123,59 @@ class Page extends EventEmitter {
|
||||
client.on('Inspector.targetCrashed', event => this._onTargetCrashed());
|
||||
client.on('Performance.metrics', event => this._emitMetrics(event));
|
||||
client.on('Log.entryAdded', event => this._onLogEntryAdded(event));
|
||||
client.on('Page.fileChooserOpened', event => this._onFileChooser(event));
|
||||
this._target._isClosedPromise.then(() => {
|
||||
this.emit(Events.Page.Close);
|
||||
this._closed = true;
|
||||
});
|
||||
}
|
||||
|
||||
async _initialize() {
|
||||
await Promise.all([
|
||||
this._frameManager.initialize(),
|
||||
this._client.send('Target.setAutoAttach', {autoAttach: true, waitForDebuggerOnStart: false, flatten: true}),
|
||||
this._client.send('Performance.enable', {}),
|
||||
this._client.send('Log.enable', {}),
|
||||
this._client.send('Page.setInterceptFileChooserDialog', {enabled: true}).catch(e => {
|
||||
this._fileChooserInterceptionIsDisabled = true;
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!Protocol.Page.fileChooserOpenedPayload} event
|
||||
*/
|
||||
_onFileChooser(event) {
|
||||
if (!this._fileChooserInterceptors.size) {
|
||||
this._client.send('Page.handleFileChooser', { action: 'fallback' }).catch(debugError);
|
||||
return;
|
||||
}
|
||||
const interceptors = Array.from(this._fileChooserInterceptors);
|
||||
this._fileChooserInterceptors.clear();
|
||||
const fileChooser = new FileChooser(this._client, event);
|
||||
for (const interceptor of interceptors)
|
||||
interceptor.call(null, fileChooser);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!{timeout?: number}=} options
|
||||
* @return !Promise<!FileChooser>}
|
||||
*/
|
||||
async waitForFileChooser(options = {}) {
|
||||
if (this._fileChooserInterceptionIsDisabled)
|
||||
throw new Error('File chooser handling does not work with multiple connections to the same page');
|
||||
const {
|
||||
timeout = this._timeoutSettings.timeout(),
|
||||
} = options;
|
||||
let callback;
|
||||
const promise = new Promise(x => callback = x);
|
||||
this._fileChooserInterceptors.add(callback);
|
||||
return helper.waitWithTimeout(promise, 'waiting for file chooser', timeout).catch(e => {
|
||||
this._fileChooserInterceptors.delete(callback);
|
||||
throw e;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!{longitude: number, latitude: number, accuracy: (number|undefined)}} options
|
||||
*/
|
||||
@ -1258,5 +1303,48 @@ class ConsoleMessage {
|
||||
}
|
||||
}
|
||||
|
||||
class FileChooser {
|
||||
/**
|
||||
* @param {Puppeteer.CDPSession} client
|
||||
* @param {!Protocol.Page.fileChooserOpenedPayload} event
|
||||
*/
|
||||
constructor(client, event) {
|
||||
this._client = client;
|
||||
this._multiple = event.mode !== 'selectSingle';
|
||||
this._handled = false;
|
||||
}
|
||||
|
||||
module.exports = {Page, ConsoleMessage};
|
||||
/**
|
||||
* @return {boolean}
|
||||
*/
|
||||
isMultiple() {
|
||||
return this._multiple;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!Array<string>} filePaths
|
||||
* @return {!Promise}
|
||||
*/
|
||||
async accept(filePaths) {
|
||||
assert(!this._handled, 'Cannot accept FileChooser which is already handled!');
|
||||
this._handled = true;
|
||||
const files = filePaths.map(filePath => path.resolve(filePath));
|
||||
await this._client.send('Page.handleFileChooser', {
|
||||
action: 'accept',
|
||||
files,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {!Promise}
|
||||
*/
|
||||
async cancel() {
|
||||
assert(!this._handled, 'Cannot cancel FileChooser which is already handled!');
|
||||
this._handled = true;
|
||||
await this._client.send('Page.handleFileChooser', {
|
||||
action: 'cancel',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {Page, ConsoleMessage, FileChooser};
|
||||
|
@ -25,6 +25,7 @@ module.exports = {
|
||||
Dialog: require('./Dialog').Dialog,
|
||||
ElementHandle: require('./JSHandle').ElementHandle,
|
||||
ExecutionContext: require('./ExecutionContext').ExecutionContext,
|
||||
FileChooser: require('./Page').FileChooser,
|
||||
Frame: require('./FrameManager').Frame,
|
||||
JSHandle: require('./JSHandle').JSHandle,
|
||||
Keyboard: require('./Input').Keyboard,
|
||||
|
@ -214,10 +214,13 @@ class Helper {
|
||||
let reject;
|
||||
const timeoutError = new TimeoutError(`waiting for ${taskName} failed: timeout ${timeout}ms exceeded`);
|
||||
const timeoutPromise = new Promise((resolve, x) => reject = x);
|
||||
const timeoutTimer = setTimeout(() => reject(timeoutError), timeout);
|
||||
let timeoutTimer = null;
|
||||
if (timeout)
|
||||
timeoutTimer = setTimeout(() => reject(timeoutError), timeout);
|
||||
try {
|
||||
return await Promise.race([promise, timeoutPromise]);
|
||||
} finally {
|
||||
if (timeoutTimer)
|
||||
clearTimeout(timeoutTimer);
|
||||
}
|
||||
}
|
||||
|
@ -93,6 +93,23 @@ module.exports.addLauncherTests = function({testRunner, expect, defaultBrowserOp
|
||||
await disconnectedEventPromise;
|
||||
});
|
||||
});
|
||||
|
||||
describe('Page.waitForFileChooser', () => {
|
||||
it('should fail gracefully when trying to work with filechoosers within multiple connections', async() => {
|
||||
// 1. Launch a browser and connect to all pages.
|
||||
const originalBrowser = await puppeteer.launch(defaultBrowserOptions);
|
||||
await originalBrowser.pages();
|
||||
// 2. Connect a remote browser and connect to first page.
|
||||
const remoteBrowser = await puppeteer.connect({browserWSEndpoint: originalBrowser.wsEndpoint()});
|
||||
const [page] = await remoteBrowser.pages();
|
||||
// 3. Make sure |page.waitForFileChooser()| does not work with multiclient.
|
||||
let error = null;
|
||||
await page.waitForFileChooser().catch(e => error = e);
|
||||
expect(error.message).toBe('File chooser handling does not work with multiple connections to the same page');
|
||||
originalBrowser.close();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -16,14 +16,16 @@
|
||||
|
||||
const path = require('path');
|
||||
|
||||
module.exports.addTests = function({testRunner, expect}) {
|
||||
const {describe, xdescribe, fdescribe} = testRunner;
|
||||
const FILE_TO_UPLOAD = path.join(__dirname, '/assets/file-to-upload.txt');
|
||||
|
||||
module.exports.addTests = function({testRunner, expect, puppeteer}) {
|
||||
const {describe, xdescribe, fdescribe, describe_fails_ffox} = testRunner;
|
||||
const {it, fit, xit, it_fails_ffox} = testRunner;
|
||||
const {beforeAll, beforeEach, afterAll, afterEach} = testRunner;
|
||||
describe('input', function() {
|
||||
it('should upload the file', async({page, server}) => {
|
||||
await page.goto(server.PREFIX + '/input/fileupload.html');
|
||||
const filePath = path.relative(process.cwd(), __dirname + '/assets/file-to-upload.txt');
|
||||
const filePath = path.relative(process.cwd(), FILE_TO_UPLOAD);
|
||||
const input = await page.$('input');
|
||||
await input.uploadFile(filePath);
|
||||
expect(await page.evaluate(e => e.files[0].name, input)).toBe('file-to-upload.txt');
|
||||
@ -35,4 +37,187 @@ module.exports.addTests = function({testRunner, expect}) {
|
||||
}, input)).toBe('contents of the file');
|
||||
});
|
||||
});
|
||||
|
||||
describe_fails_ffox('Page.waitForFileChooser', function() {
|
||||
it('should work when file input is attached to DOM', async({page, server}) => {
|
||||
await page.setContent(`<input type=file>`);
|
||||
const [chooser] = await Promise.all([
|
||||
page.waitForFileChooser(),
|
||||
page.click('input'),
|
||||
]);
|
||||
expect(chooser).toBeTruthy();
|
||||
});
|
||||
it('should work when file input is not attached to DOM', async({page, server}) => {
|
||||
const [chooser] = await Promise.all([
|
||||
page.waitForFileChooser(),
|
||||
page.evaluate(() => {
|
||||
const el = document.createElement('input');
|
||||
el.type = 'file';
|
||||
el.click();
|
||||
}),
|
||||
]);
|
||||
expect(chooser).toBeTruthy();
|
||||
});
|
||||
it('should respect timeout', async({page, server}) => {
|
||||
let error = null;
|
||||
await page.waitForFileChooser({timeout: 1}).catch(e => error = e);
|
||||
expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
|
||||
});
|
||||
it('should respect default timeout when there is no custom timeout', async({page, server}) => {
|
||||
page.setDefaultTimeout(1);
|
||||
let error = null;
|
||||
await page.waitForFileChooser().catch(e => error = e);
|
||||
expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
|
||||
});
|
||||
it('should prioritize exact timeout over default timeout', async({page, server}) => {
|
||||
page.setDefaultTimeout(0);
|
||||
let error = null;
|
||||
await page.waitForFileChooser({timeout: 1}).catch(e => error = e);
|
||||
expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
|
||||
});
|
||||
it('should work with no timeout', async({page, server}) => {
|
||||
const [chooser] = await Promise.all([
|
||||
page.waitForFileChooser({timeout: 0}),
|
||||
page.evaluate(() => setTimeout(() => {
|
||||
const el = document.createElement('input');
|
||||
el.type = 'file';
|
||||
el.click();
|
||||
}, 50))
|
||||
]);
|
||||
expect(chooser).toBeTruthy();
|
||||
});
|
||||
it('should return the same file chooser when there are many watchdogs simultaneously', async({page, server}) => {
|
||||
await page.setContent(`<input type=file>`);
|
||||
const [fileChooser1, fileChooser2] = await Promise.all([
|
||||
page.waitForFileChooser(),
|
||||
page.waitForFileChooser(),
|
||||
page.$eval('input', input => input.click()),
|
||||
]);
|
||||
expect(fileChooser1 === fileChooser2).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe_fails_ffox('FileChooser.accept', function() {
|
||||
it('should accept single file', async({page, server}) => {
|
||||
await page.setContent(`<input type=file oninput='javascript:console.timeStamp()'>`);
|
||||
const [chooser] = await Promise.all([
|
||||
page.waitForFileChooser(),
|
||||
page.click('input'),
|
||||
]);
|
||||
await Promise.all([
|
||||
chooser.accept([FILE_TO_UPLOAD]),
|
||||
new Promise(x => page.once('metrics', x)),
|
||||
]);
|
||||
expect(await page.$eval('input', input => input.files.length)).toBe(1);
|
||||
expect(await page.$eval('input', input => input.files[0].name)).toBe('file-to-upload.txt');
|
||||
});
|
||||
it('should be able to read selected file', async({page, server}) => {
|
||||
await page.setContent(`<input type=file>`);
|
||||
page.waitForFileChooser().then(chooser => chooser.accept([FILE_TO_UPLOAD]));
|
||||
expect(await page.$eval('input', async picker => {
|
||||
picker.click();
|
||||
await new Promise(x => picker.oninput = x);
|
||||
const reader = new FileReader();
|
||||
const promise = new Promise(fulfill => reader.onload = fulfill);
|
||||
reader.readAsText(picker.files[0]);
|
||||
return promise.then(() => reader.result);
|
||||
})).toBe('contents of the file');
|
||||
});
|
||||
it('should be able to reset selected files with empty file list', async({page, server}) => {
|
||||
await page.setContent(`<input type=file>`);
|
||||
page.waitForFileChooser().then(chooser => chooser.accept([FILE_TO_UPLOAD]));
|
||||
expect(await page.$eval('input', async picker => {
|
||||
picker.click();
|
||||
await new Promise(x => picker.oninput = x);
|
||||
return picker.files.length;
|
||||
})).toBe(1);
|
||||
page.waitForFileChooser().then(chooser => chooser.accept([]));
|
||||
expect(await page.$eval('input', async picker => {
|
||||
picker.click();
|
||||
await new Promise(x => picker.oninput = x);
|
||||
return picker.files.length;
|
||||
})).toBe(0);
|
||||
});
|
||||
it('should not accept multiple files for single-file input', async({page, server}) => {
|
||||
await page.setContent(`<input type=file>`);
|
||||
const [chooser] = await Promise.all([
|
||||
page.waitForFileChooser(),
|
||||
page.click('input'),
|
||||
]);
|
||||
let error = null;
|
||||
await chooser.accept([
|
||||
path.relative(process.cwd(), __dirname + '/assets/file-to-upload.txt'),
|
||||
path.relative(process.cwd(), __dirname + '/assets/pptr.png'),
|
||||
]).catch(e => error = e);
|
||||
expect(error).not.toBe(null);
|
||||
});
|
||||
it('should fail when accepting file chooser twice', async({page, server}) => {
|
||||
await page.setContent(`<input type=file>`);
|
||||
const [fileChooser] = await Promise.all([
|
||||
page.waitForFileChooser(),
|
||||
page.$eval('input', input => input.click()),
|
||||
]);
|
||||
await fileChooser.accept([]);
|
||||
let error = null;
|
||||
await fileChooser.accept([]).catch(e => error = e);
|
||||
expect(error.message).toBe('Cannot accept FileChooser which is already handled!');
|
||||
});
|
||||
});
|
||||
|
||||
describe_fails_ffox('FileChooser.cancel', function() {
|
||||
it('should cancel dialog', async({page, server}) => {
|
||||
// Consider file chooser canceled if we can summon another one.
|
||||
// There's no reliable way in WebPlatform to see that FileChooser was
|
||||
// canceled.
|
||||
await page.setContent(`<input type=file>`);
|
||||
const [fileChooser1] = await Promise.all([
|
||||
page.waitForFileChooser(),
|
||||
page.$eval('input', input => input.click()),
|
||||
]);
|
||||
await fileChooser1.cancel();
|
||||
// If this resolves, than we successfully canceled file chooser.
|
||||
await Promise.all([
|
||||
page.waitForFileChooser(),
|
||||
page.$eval('input', input => input.click()),
|
||||
]);
|
||||
});
|
||||
it('should fail when canceling file chooser twice', async({page, server}) => {
|
||||
await page.setContent(`<input type=file>`);
|
||||
const [fileChooser] = await Promise.all([
|
||||
page.waitForFileChooser(),
|
||||
page.$eval('input', input => input.click()),
|
||||
]);
|
||||
await fileChooser.cancel();
|
||||
let error = null;
|
||||
await fileChooser.cancel().catch(e => error = e);
|
||||
expect(error.message).toBe('Cannot cancel FileChooser which is already handled!');
|
||||
});
|
||||
});
|
||||
|
||||
describe_fails_ffox('FileChooser.isMultiple', () => {
|
||||
it('should work for single file pick', async({page, server}) => {
|
||||
await page.setContent(`<input type=file>`);
|
||||
const [chooser] = await Promise.all([
|
||||
page.waitForFileChooser(),
|
||||
page.click('input'),
|
||||
]);
|
||||
expect(chooser.isMultiple()).toBe(false);
|
||||
});
|
||||
it('should work for "multiple"', async({page, server}) => {
|
||||
await page.setContent(`<input multiple type=file>`);
|
||||
const [chooser] = await Promise.all([
|
||||
page.waitForFileChooser(),
|
||||
page.click('input'),
|
||||
]);
|
||||
expect(chooser.isMultiple()).toBe(true);
|
||||
});
|
||||
it('should work for "webkitdirectory"', async({page, server}) => {
|
||||
await page.setContent(`<input multiple webkitdirectory type=file>`);
|
||||
const [chooser] = await Promise.all([
|
||||
page.waitForFileChooser(),
|
||||
page.click('input'),
|
||||
]);
|
||||
expect(chooser.isMultiple()).toBe(true);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user