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'));