diff --git a/packages/playwright-core/src/client/elementHandle.ts b/packages/playwright-core/src/client/elementHandle.ts index 891410c58a..ae230e074b 100644 --- a/packages/playwright-core/src/client/elementHandle.ts +++ b/packages/playwright-core/src/client/elementHandle.ts @@ -266,13 +266,13 @@ export async function convertInputFiles(files: string | FilePayload | string[] | const items: (string | FilePayload)[] = Array.isArray(files) ? files.slice() : [files]; const sizeLimit = 50 * 1024 * 1024; - const hasLargeBuffer = items.find(item => typeof item === 'object' && item.buffer && item.buffer.byteLength > sizeLimit); - if (hasLargeBuffer) + const totalBufferSizeExceedsLimit = items.reduce((size, item) => size + ((typeof item === 'object' && item.buffer) ? item.buffer.byteLength : 0), 0) > sizeLimit; + if (totalBufferSizeExceedsLimit) throw new Error('Cannot set buffer larger than 50Mb, please write it to a file and pass its path instead.'); const stats = await Promise.all(items.filter(isString).map(item => fs.promises.stat(item as string))); - const hasLargeFile = !!stats.find(s => s.size > sizeLimit); - if (hasLargeFile) { + const totalFileSizeExceedsLimit = stats.reduce((acc, stat) => acc + stat.size, 0) > sizeLimit; + if (totalFileSizeExceedsLimit) { if (context._connection.isRemote()) { const streams: channels.WritableStreamChannel[] = await Promise.all(items.map(async item => { assert(isString(item)); diff --git a/tests/assets/input/fileupload-multi.html b/tests/assets/input/fileupload-multi.html new file mode 100644 index 0000000000..05dd5a2237 --- /dev/null +++ b/tests/assets/input/fileupload-multi.html @@ -0,0 +1,12 @@ + + + + File upload test + + +
+ + +
+ + diff --git a/tests/page/page-set-input-files.spec.ts b/tests/page/page-set-input-files.spec.ts index de404868fe..8b5a59d7df 100644 --- a/tests/page/page-set-input-files.spec.ts +++ b/tests/page/page-set-input-files.spec.ts @@ -85,6 +85,44 @@ it('should upload large file', async ({ page, server, browserName, isMac, isAndr await Promise.all([uploadFile, file1.filepath].map(fs.promises.unlink)); }); +it('should upload multiple large files', async ({ page, server, browserName, isMac, isAndroid }, testInfo) => { + it.skip(browserName === 'webkit' && isMac && parseInt(os.release(), 10) < 20, 'WebKit for macOS 10.15 is frozen and does not have corresponding protocol features.'); + it.skip(isAndroid); + it.slow(); + const filesCount = 10; + await page.goto(server.PREFIX + '/input/fileupload-multi.html'); + const uploadFile = testInfo.outputPath('50MB_1.zip'); + const str = 'A'.repeat(1024); + const stream = fs.createWriteStream(uploadFile); + // 49 is close to the actual limit + for (let i = 0; i < 49 * 1024; i++) { + await new Promise((fulfill, reject) => { + stream.write(str, err => { + if (err) + reject(err); + else + fulfill(); + }); + }); + } + await new Promise(f => stream.end(f)); + const input = page.locator('input[type="file"]'); + const uploadFiles = [uploadFile]; + for (let i = 2; i <= filesCount; i++) { + const dstFile = testInfo.outputPath(`50MB_${i}.zip`); + fs.copyFileSync(uploadFile, dstFile); + uploadFiles.push(dstFile); + } + const fileChooserPromise = page.waitForEvent('filechooser'); + await input.click(); + const fileChooser = await fileChooserPromise; + await fileChooser.setFiles(uploadFiles); + const filesLen = await page.evaluate('document.getElementsByTagName("input")[0].files.length'); + expect(fileChooser.isMultiple()).toBe(true); + expect(filesLen).toEqual(filesCount); + await Promise.all(uploadFiles.map(path => fs.promises.unlink(path))); +}); + it('should upload large file with relative path', async ({ page, server, browserName, isMac, isAndroid }, testInfo) => { it.skip(browserName === 'webkit' && isMac && parseInt(os.release(), 10) < 20, 'WebKit for macOS 10.15 is frozen and does not have corresponding protocol features.'); it.skip(isAndroid);