Skip to content

Commit

Permalink
fix(JSHandle): Fixes file upload (#5655)
Browse files Browse the repository at this point in the history
This PR returns to using `DOM.setFileInputFiles`, but with some additional fixes and checks for events and multiple files.
  • Loading branch information
paullewis committed Apr 16, 2020
1 parent 3e4c8c9 commit 532ae57
Show file tree
Hide file tree
Showing 2 changed files with 22 additions and 28 deletions.
49 changes: 21 additions & 28 deletions src/JSHandle.js
Expand Up @@ -313,36 +313,29 @@ class ElementHandle extends JSHandle {
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.

// This import is only needed for `uploadFile`, so keep it scoped here to avoid paying
// the cost unnecessarily.
const path = require('path');
const mime = require('mime-types');
const fs = require('fs');
const readFileAsync = helper.promisify(fs.readFile);

const promises = filePaths.map(filePath => readFileAsync(filePath));
const files = [];
for (let i = 0; i < filePaths.length; i++) {
const buffer = await promises[i];
const filePath = path.basename(filePaths[i]);
const file = {
name: filePath,
content: buffer.toString('base64'),
mimeType: mime.lookup(filePath),
};
files.push(file);
const files = filePaths.map(filePath => path.resolve(filePath));
const { objectId } = this._remoteObject;
const { node } = await this._client.send('DOM.describeNode', { objectId });
const { backendNodeId } = node;

// The zero-length array is a special case, it seems that DOM.setFileInputFiles does
// not actually update the files in that case, so the solution is to eval the element
// value to a new FileList directly.
if (files.length === 0) {
await this.evaluate(element => {
element.files = new DataTransfer().files;

// Dispatch events for this case because it should behave akin to a user action.
element.dispatchEvent(new Event('input', { bubbles: true }));
element.dispatchEvent(new Event('change', { bubbles: true }));
});
} else {
await this._client.send('DOM.setFileInputFiles', { objectId, files, backendNodeId });
}
await this.evaluateHandle(async(element, files) => {
const dt = new DataTransfer();
for (const item of files) {
const response = await fetch(`data:${item.mimeType};base64,${item.content}`);
const file = new File([await response.blob()], item.name);
dt.items.add(file);
}
element.files = dt.files;
element.dispatchEvent(new Event('input', { bubbles: true }));
element.dispatchEvent(new Event('change', { bubbles: true }));
}, files);
}

async tap() {
Expand Down
1 change: 1 addition & 0 deletions test/input.spec.js
Expand Up @@ -38,6 +38,7 @@ describe('input tests', function() {
}, input);
await input.uploadFile(filePath);
expect(await page.evaluate(e => e.files[0].name, input)).toBe('file-to-upload.txt');
expect(await page.evaluate(e => e.files[0].type, input)).toBe('text/plain');
expect(await page.evaluate(() => window._inputEvents)).toEqual(['input', 'change']);
expect(await page.evaluate(e => {
const reader = new FileReader();
Expand Down

0 comments on commit 532ae57

Please sign in to comment.