Skip to content

Commit

Permalink
Restore upload file functionality WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
mathiasbynens committed Jan 27, 2020
1 parent 6c6141c commit 7b3735e
Show file tree
Hide file tree
Showing 5 changed files with 34 additions and 50 deletions.
18 changes: 13 additions & 5 deletions lib/ExecutionContext.js
Expand Up @@ -182,6 +182,18 @@ class ExecutionContext {
return createJSHandle(this, response.objects);
}

/**
* @param {Protocol.DOM.BackendNodeId} backendNodeId
* @return {Promise<Puppeteer.ElementHandle>}
*/
async _adoptBackendNodeId(backendNodeId) {
const {object} = await this._client.send('DOM.resolveNode', {
backendNodeId: backendNodeId,
executionContextId: this._contextId,
});
return /** @type {Puppeteer.ElementHandle}*/(createJSHandle(this, object));
}

/**
* @param {Puppeteer.ElementHandle} elementHandle
* @return {Promise<Puppeteer.ElementHandle>}
Expand All @@ -192,11 +204,7 @@ class ExecutionContext {
const nodeInfo = await this._client.send('DOM.describeNode', {
objectId: elementHandle._remoteObject.objectId,
});
const {object} = await this._client.send('DOM.resolveNode', {
backendNodeId: nodeInfo.node.backendNodeId,
executionContextId: this._contextId,
});
return /** @type {Puppeteer.ElementHandle}*/(createJSHandle(this, object));
return this._adoptBackendNodeId(nodeInfo.node.backendNodeId);
}
}

Expand Down
2 changes: 2 additions & 0 deletions lib/JSHandle.js
Expand Up @@ -311,6 +311,8 @@ class ElementHandle extends JSHandle {
* @param {!Array<string>} filePaths
*/
async uploadFile(...filePaths) {
const isMultiple = await this.evaluate(element => element.multiple);
assert(filePaths.length <= 1 || isMultiple, 'Multiple file uploads only work with <input type=file multiple>');
// These imports are only needed for `uploadFile`, so keep them
// scoped here to avoid paying the cost unnecessarily.
const path = require('path');
Expand Down
32 changes: 11 additions & 21 deletions lib/Page.js
Expand Up @@ -15,7 +15,6 @@
*/

const fs = require('fs');
const path = require('path');
const EventEmitter = require('events');
const mime = require('mime');
const {Events} = require('./Events');
Expand Down Expand Up @@ -112,7 +111,6 @@ 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));
Expand All @@ -137,23 +135,22 @@ class Page extends EventEmitter {
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;
}),
this._client.send('Page.setInterceptFileChooserDialog', {enabled: true}),
]);
}

/**
* @param {!Protocol.Page.fileChooserOpenedPayload} event
*/
_onFileChooser(event) {
if (!this._fileChooserInterceptors.size) {
this._client.send('Page.handleFileChooser', { action: 'fallback' }).catch(debugError);
async _onFileChooser(event) {
if (!this._fileChooserInterceptors.size)
return;
}
const frame = this._frameManager.frame(event.frameId);
const context = await frame.executionContext();
const element = await context._adoptBackendNodeId(event.backendNodeId);
const interceptors = Array.from(this._fileChooserInterceptors);
this._fileChooserInterceptors.clear();
const fileChooser = new FileChooser(this._client, event);
const fileChooser = new FileChooser(this._client, element, event);
for (const interceptor of interceptors)
interceptor.call(null, fileChooser);
}
Expand All @@ -163,8 +160,6 @@ class Page extends EventEmitter {
* @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;
Expand Down Expand Up @@ -1351,10 +1346,12 @@ class ConsoleMessage {
class FileChooser {
/**
* @param {Puppeteer.CDPSession} client
* @param {Puppeteer.ElementHandle} element
* @param {!Protocol.Page.fileChooserOpenedPayload} event
*/
constructor(client, event) {
constructor(client, element, event) {
this._client = client;
this._element = element;
this._multiple = event.mode !== 'selectSingle';
this._handled = false;
}
Expand All @@ -1373,11 +1370,7 @@ class FileChooser {
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,
});
await this._element.uploadFile(...filePaths);
}

/**
Expand All @@ -1386,9 +1379,6 @@ class FileChooser {
async cancel() {
assert(!this._handled, 'Cannot cancel FileChooser which is already handled!');
this._handled = true;
await this._client.send('Page.handleFileChooser', {
action: 'cancel',
});
}
}

Expand Down
16 changes: 0 additions & 16 deletions test/chromiumonly.spec.js
Expand Up @@ -94,22 +94,6 @@ module.exports.addLauncherTests = function({testRunner, expect, defaultBrowserOp
});
});

describe('Page.waitForFileChooser', () => {
xit('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();
});

});
});
};

Expand Down
16 changes: 8 additions & 8 deletions test/input.spec.js
Expand Up @@ -23,7 +23,7 @@ module.exports.addTests = function({testRunner, expect, puppeteer}) {
const {it, fit, xit, it_fails_ffox} = testRunner;
const {beforeAll, beforeEach, afterAll, afterEach} = testRunner;
describe('input', function() {
xit('should upload the file', async({page, server}) => {
it('should upload the file', async({page, server}) => {
await page.goto(server.PREFIX + '/input/fileupload.html');
const filePath = path.relative(process.cwd(), FILE_TO_UPLOAD);
const input = await page.$('input');
Expand Down Expand Up @@ -98,7 +98,7 @@ module.exports.addTests = function({testRunner, expect, puppeteer}) {
});

describe_fails_ffox('FileChooser.accept', function() {
xit('should accept single file', async({page, server}) => {
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(),
Expand All @@ -111,7 +111,7 @@ module.exports.addTests = function({testRunner, expect, puppeteer}) {
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');
});
xit('should be able to read selected file', async({page, server}) => {
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 => {
Expand All @@ -123,7 +123,7 @@ module.exports.addTests = function({testRunner, expect, puppeteer}) {
return promise.then(() => reader.result);
})).toBe('contents of the file');
});
xit('should be able to reset selected files with empty file list', async({page, server}) => {
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 => {
Expand All @@ -138,7 +138,7 @@ module.exports.addTests = function({testRunner, expect, puppeteer}) {
return picker.files.length;
})).toBe(0);
});
xit('should not accept multiple files for single-file input', async({page, server}) => {
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(),
Expand All @@ -151,7 +151,7 @@ module.exports.addTests = function({testRunner, expect, puppeteer}) {
]).catch(e => error = e);
expect(error).not.toBe(null);
});
xit('should fail when accepting file chooser twice', async({page, server}) => {
it('should fail when accepting file chooser twice', async({page, server}) => {
await page.setContent(`<input type=file>`);
const [fileChooser] = await Promise.all([
page.waitForFileChooser(),
Expand All @@ -165,7 +165,7 @@ module.exports.addTests = function({testRunner, expect, puppeteer}) {
});

describe_fails_ffox('FileChooser.cancel', function() {
xit('should cancel dialog', async({page, server}) => {
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.
Expand All @@ -181,7 +181,7 @@ module.exports.addTests = function({testRunner, expect, puppeteer}) {
page.$eval('input', input => input.click()),
]);
});
xit('should fail when canceling file chooser twice', async({page, server}) => {
it('should fail when canceling file chooser twice', async({page, server}) => {
await page.setContent(`<input type=file>`);
const [fileChooser] = await Promise.all([
page.waitForFileChooser(),
Expand Down

0 comments on commit 7b3735e

Please sign in to comment.