From 11179982fca60537022e503c1fb7e869e4a8bbd1 Mon Sep 17 00:00:00 2001 From: Ross Wollman Date: Thu, 14 Apr 2022 11:19:36 -0700 Subject: [PATCH] chore: force localPaths to be resolved (#13544) --- .../src/client/elementHandle.ts | 2 +- .../dispatchers/elementHandlerDispatcher.ts | 5 +- .../src/server/dispatchers/frameDispatcher.ts | 4 ++ tests/page/page-set-input-files.spec.ts | 49 +++++++++++++++++++ 4 files changed, 58 insertions(+), 2 deletions(-) diff --git a/packages/playwright-core/src/client/elementHandle.ts b/packages/playwright-core/src/client/elementHandle.ts index e3be4dc5e6..357192c2bf 100644 --- a/packages/playwright-core/src/client/elementHandle.ts +++ b/packages/playwright-core/src/client/elementHandle.ts @@ -284,7 +284,7 @@ export async function convertInputFiles(files: string | FilePayload | string[] | })); return { streams }; } - return { localPaths: items as string[] }; + return { localPaths: items.map(f => path.resolve(f as string)) as string[] }; } const filePayloads: SetInputFilesFiles = await Promise.all(items.map(async item => { diff --git a/packages/playwright-core/src/server/dispatchers/elementHandlerDispatcher.ts b/packages/playwright-core/src/server/dispatchers/elementHandlerDispatcher.ts index 2b87644ba8..82eab5341d 100644 --- a/packages/playwright-core/src/server/dispatchers/elementHandlerDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/elementHandlerDispatcher.ts @@ -24,7 +24,8 @@ import { JSHandleDispatcher, serializeResult, parseArgument } from './jsHandleDi import type { FrameDispatcher } from './frameDispatcher'; import type { CallMetadata } from '../instrumentation'; import type { WritableStreamDispatcher } from './writableStreamDispatcher'; - +import { assert } from '../../utils'; +import path from 'path'; export class ElementHandleDispatcher extends JSHandleDispatcher implements channels.ElementHandleChannel { _type_ElementHandle = true; @@ -155,6 +156,8 @@ export class ElementHandleDispatcher extends JSHandleDispatcher implements chann throw new Error('Neither localPaths nor streams is specified'); localPaths = params.streams.map(c => (c as WritableStreamDispatcher).path()); } + for (const p of localPaths) + assert(path.isAbsolute(p) && path.resolve(p) === p, 'Paths provided to localPaths must be absolute and fully resolved.'); return await this._elementHandle.setInputFiles(metadata, { localPaths }, params); } diff --git a/packages/playwright-core/src/server/dispatchers/frameDispatcher.ts b/packages/playwright-core/src/server/dispatchers/frameDispatcher.ts index 1fef3a3c73..771903dfca 100644 --- a/packages/playwright-core/src/server/dispatchers/frameDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/frameDispatcher.ts @@ -25,6 +25,8 @@ import type { ResponseDispatcher } from './networkDispatchers'; import { RequestDispatcher } from './networkDispatchers'; import type { CallMetadata } from '../instrumentation'; import type { WritableStreamDispatcher } from './writableStreamDispatcher'; +import { assert } from '../../utils'; +import path from 'path'; export class FrameDispatcher extends Dispatcher implements channels.FrameChannel { _type_Frame = true; @@ -215,6 +217,8 @@ export class FrameDispatcher extends Dispatcher im throw new Error('Neither localPaths nor streams is specified'); localPaths = params.streams.map(c => (c as WritableStreamDispatcher).path()); } + for (const p of localPaths) + assert(path.isAbsolute(p) && path.resolve(p) === p, 'Paths provided to localPaths must be absolute and fully resolved.'); return await this._frame.setInputFiles(metadata, params.selector, { localPaths }, params); } diff --git a/tests/page/page-set-input-files.spec.ts b/tests/page/page-set-input-files.spec.ts index 2541db9f62..6530bd0a85 100644 --- a/tests/page/page-set-input-files.spec.ts +++ b/tests/page/page-set-input-files.spec.ts @@ -84,6 +84,55 @@ it('should upload large file', async ({ page, server, browserName, isMac }, test await Promise.all([uploadFile, file1.filepath].map(fs.promises.unlink)); }); +it('should upload large file with relative path', async ({ page, server, browserName, isMac }, 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.slow(); + await page.goto(server.PREFIX + '/input/fileupload.html'); + const uploadFile = testInfo.outputPath('200MB.zip'); + const str = 'A'.repeat(4 * 1024); + const stream = fs.createWriteStream(uploadFile); + for (let i = 0; i < 50 * 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 events = await input.evaluateHandle(e => { + const events = []; + e.addEventListener('input', () => events.push('input')); + e.addEventListener('change', () => events.push('change')); + return events; + }); + const relativeUploadPath = path.relative(process.cwd(), uploadFile); + expect(path.isAbsolute(relativeUploadPath)).toBeFalsy(); + await input.setInputFiles(relativeUploadPath); + expect(await input.evaluate(e => (e as HTMLInputElement).files[0].name)).toBe('200MB.zip'); + expect(await events.evaluate(e => e)).toEqual(['input', 'change']); + const serverFilePromise = new Promise(fulfill => { + server.setRoute('/upload', async (req, res) => { + const form = new formidable.IncomingForm({ uploadDir: testInfo.outputPath() }); + form.parse(req, function(err, fields, f) { + res.end(); + const files = f as Record; + fulfill(files.file1); + }); + }); + }); + const [file1] = await Promise.all([ + serverFilePromise, + page.click('input[type=submit]') + ]); + expect(file1.originalFilename).toBe('200MB.zip'); + expect(file1.size).toBe(200 * 1024 * 1024); + await Promise.all([uploadFile, file1.filepath].map(fs.promises.unlink)); +}); + it('should work @smoke', async ({ page, asset }) => { await page.setContent(``); await page.setInputFiles('input', asset('file-to-upload.txt'));