Skip to content

Commit

Permalink
fix: fileUploading, enables to transfer local file to remote Chrome c…
Browse files Browse the repository at this point in the history
…onnected via puppeteer.connect

Issues: puppeteer#4405
  • Loading branch information
YusukeIwaki committed Sep 16, 2021
1 parent caa2b73 commit d398337
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 20 deletions.
45 changes: 25 additions & 20 deletions src/common/JSHandle.ts
Expand Up @@ -635,9 +635,10 @@ export class ElementHandle<
avoid paying the cost unnecessarily.
*/
const path = await import('path');
const mime = await import('mime');
const fs = await helper.importFSModule();
// Locate all files and confirm that they exist.
const files = await Promise.all(
const resolvedFilePaths = await Promise.all(
filePaths.map(async (filePath) => {
const resolvedPath: string = path.resolve(filePath);
try {
Expand All @@ -650,29 +651,33 @@ export class ElementHandle<
return resolvedPath;
})
);
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 as ElementHandle<HTMLInputElement>).evaluate((element) => {
element.files = new DataTransfer().files;
const filePayloads = await Promise.all(
resolvedFilePaths.map(async (filePath) => {
const buffer = await fs.promises.readFile(filePath);
const name = path.basename(filePath);
const mimeType = mime.getType(name) || 'application/octet-stream';

// Dispatch events for this case because it should behave akin to a user action.
return { name, mimeType, content: buffer.toString('base64') };
})
);

await (this as ElementHandle<HTMLInputElement>).evaluate(
(element, files) => {
const dt = new DataTransfer();
for (const item of files) {
const bytes = Uint8Array.from(atob(item.content), (c) =>
c.charCodeAt(0)
);
const file = new File([bytes], item.name);
dt.items.add(file);
}
element.files = dt.files;
element.dispatchEvent(new Event('input', { bubbles: true }));
element.dispatchEvent(new Event('change', { bubbles: true }));
});
} else {
await this._client.send('DOM.setFileInputFiles', {
objectId,
files,
backendNodeId,
});
}
},
filePayloads
);
}

/**
Expand Down
29 changes: 29 additions & 0 deletions test/input.spec.ts
Expand Up @@ -14,6 +14,7 @@
* limitations under the License.
*/

import fs from 'fs';
import path from 'path';
import expect from 'expect';
import {
Expand Down Expand Up @@ -65,6 +66,34 @@ describe('input tests', function () {
}, input)
).toBe('contents of the file');
});

it('uploadFile should work with CSP', async () => {
const { page, server } = getTestState();

server.setCSP('/empty.html', 'default-src "none"');
await page.goto(server.EMPTY_PAGE);
await page.setContent(`<input type=file>`);
const input = await page.$('input');
await input.uploadFile(
path.join(__dirname, '/assets/file-to-upload.txt')
);

const numFiles = await input.evaluate(
(e: HTMLInputElement) => e.files.length
);
expect(numFiles).toBe(1);
const filename = await input.evaluate(
(e: HTMLInputElement) => e.files[0].name
);
expect(filename).toBe('file-to-upload.txt');
const content = await input.evaluate((e: HTMLInputElement) =>
e.files[0].text()
);
const expectedContent = await fs.promises.readFile(
path.join(__dirname, '/assets/file-to-upload.txt')
);
expect(content).toBe(expectedContent.toString());
});
});

describeFailsFirefox('Page.waitForFileChooser', function () {
Expand Down

0 comments on commit d398337

Please sign in to comment.