feedback 5
This commit is contained in:
parent
f8728919b3
commit
ea9269b426
|
|
@ -250,7 +250,7 @@ export function convertSelectOptionValues(values: string | api.ElementHandle | S
|
|||
return { options: values as SelectOption[] };
|
||||
}
|
||||
|
||||
type SetInputFilesFiles = Pick<channels.ElementHandleSetInputFilesParams, 'payloads' | 'localPaths'| 'localDirectory' | 'streams'>;
|
||||
type SetInputFilesFiles = Pick<channels.ElementHandleSetInputFilesParams, 'payloads' | 'localPaths' | 'localDirectory' | 'streams' | 'directoryStream'>;
|
||||
|
||||
function filePayloadExceedsSizeLimit(payloads: FilePayload[]) {
|
||||
return payloads.reduce((size, item) => size + (item.buffer ? item.buffer.byteLength : 0), 0) >= fileUploadSizeLimit;
|
||||
|
|
@ -262,36 +262,46 @@ export async function convertInputFiles(files: string | FilePayload | string[] |
|
|||
if (items.some(item => typeof item === 'string')) {
|
||||
if (!items.every(item => typeof item === 'string'))
|
||||
throw new Error('File paths cannot be mixed with buffers');
|
||||
const itemFileTypes = (await Promise.all((items as string[]).map(async item => (await fs.promises.stat(item)).isDirectory() ? 'directory' : 'file')));
|
||||
if (new Set(itemFileTypes).size > 1 || itemFileTypes.filter(type => type === 'directory').length > 1)
|
||||
let localPaths: string[] | undefined;
|
||||
let localDirectory: string | undefined;
|
||||
for (const item of items) {
|
||||
const stat = await fs.promises.stat(item as string);
|
||||
if (stat.isDirectory()) {
|
||||
if (localDirectory)
|
||||
throw new Error('Multiple directories are not supported');
|
||||
localDirectory = path.resolve(item as string);
|
||||
} else {
|
||||
localPaths ??= [];
|
||||
localPaths.push(path.resolve(item as string));
|
||||
}
|
||||
}
|
||||
if (localPaths?.length && localDirectory)
|
||||
throw new Error('File paths must be all files or a single directory');
|
||||
|
||||
if (context._connection.isRemote()) {
|
||||
const streams = (await Promise.all((items).map(async item => {
|
||||
const isDirectory = (await fs.promises.stat(item as string)).isDirectory();
|
||||
const files = isDirectory ? (await fs.promises.readdir(item as string, { withFileTypes: true, recursive: true })).filter(f => f.isFile()).map(f => path.join(f.path, f.name)) : [item];
|
||||
const { writableStreams, rootDir } = await context._wrapApiCall(async () => context._channel.createTempFiles({
|
||||
rootDirName: isDirectory ? path.basename(item as string) : undefined,
|
||||
items: await Promise.all((files as string[]).map(async file => {
|
||||
const lastModifiedMs = (await fs.promises.stat(item as string)).mtimeMs;
|
||||
return {
|
||||
name: isDirectory ? path.relative(item as string, file) : path.basename(file),
|
||||
lastModifiedMs
|
||||
};
|
||||
})),
|
||||
}), true);
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const writable = WritableStream.from(writableStreams[i]);
|
||||
await pipelineAsync(fs.createReadStream((files as string[])[i]), writable.stream());
|
||||
}
|
||||
return rootDir ?? writableStreams;
|
||||
}))).flat();
|
||||
return { streams };
|
||||
const files = localDirectory ? (await fs.promises.readdir(localDirectory, { withFileTypes: true, recursive: true })).filter(f => f.isFile()).map(f => path.join(f.path, f.name)) : localPaths!;
|
||||
const { writableStreams, rootDir } = await context._wrapApiCall(async () => context._channel.createTempFiles({
|
||||
rootDirName: localDirectory ? path.basename(localDirectory as string) : undefined,
|
||||
items: await Promise.all(files.map(async file => {
|
||||
const lastModifiedMs = (await fs.promises.stat(file)).mtimeMs;
|
||||
return {
|
||||
name: localDirectory ? path.relative(localDirectory as string, file) : path.basename(file),
|
||||
lastModifiedMs
|
||||
};
|
||||
})),
|
||||
}), true);
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const writable = WritableStream.from(writableStreams[i]);
|
||||
await pipelineAsync(fs.createReadStream(files[i]), writable.stream());
|
||||
}
|
||||
return {
|
||||
directoryStream: rootDir,
|
||||
streams: localDirectory ? undefined : writableStreams,
|
||||
};
|
||||
}
|
||||
const resolvedItems = (items as string[]).map(f => path.resolve(f));
|
||||
return {
|
||||
localPaths: resolvedItems.filter((_, i) => itemFileTypes[i] === 'file'),
|
||||
localDirectory: resolvedItems.find((_, i) => itemFileTypes[i] === 'directory'),
|
||||
localPaths,
|
||||
localDirectory,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1631,6 +1631,7 @@ scheme.FrameSetInputFilesParams = tObject({
|
|||
buffer: tBinary,
|
||||
}))),
|
||||
localDirectory: tOptional(tString),
|
||||
directoryStream: tOptional(tChannel(['WritableStream'])),
|
||||
localPaths: tOptional(tArray(tString)),
|
||||
streams: tOptional(tArray(tChannel(['WritableStream']))),
|
||||
timeout: tOptional(tNumber),
|
||||
|
|
@ -1999,6 +2000,7 @@ scheme.ElementHandleSetInputFilesParams = tObject({
|
|||
buffer: tBinary,
|
||||
}))),
|
||||
localDirectory: tOptional(tString),
|
||||
directoryStream: tOptional(tChannel(['WritableStream'])),
|
||||
localPaths: tOptional(tArray(tString)),
|
||||
streams: tOptional(tArray(tChannel(['WritableStream']))),
|
||||
timeout: tOptional(tNumber),
|
||||
|
|
|
|||
|
|
@ -57,8 +57,4 @@ export class WritableStreamDispatcher extends Dispatcher<{ guid: string, streamO
|
|||
return this._object.streamOrDirectory;
|
||||
return this._object.streamOrDirectory.path as string;
|
||||
}
|
||||
|
||||
isDirectory(): boolean {
|
||||
return typeof this._object.streamOrDirectory === 'string';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -652,6 +652,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
|||
await Promise.all((localPathsOrDirectory).map(localPath => (
|
||||
fs.promises.access(localPath, fs.constants.F_OK)
|
||||
)));
|
||||
// Browsers traverse the given directory asynchronously and we want to ensure all files are uploaded.
|
||||
const waitForInputEvent = localDirectory ? this.evaluate(node => new Promise<any>(fulfill => {
|
||||
node.addEventListener('input', fulfill, { once: true });
|
||||
})).catch(() => {}) : Promise.resolve();
|
||||
|
|
|
|||
|
|
@ -30,16 +30,17 @@ async function filesExceedUploadLimit(files: string[]) {
|
|||
}
|
||||
|
||||
export async function prepareFilesForUpload(frame: Frame, params: channels.ElementHandleSetInputFilesParams): Promise<InputFilesItems> {
|
||||
const { payloads, streams } = params;
|
||||
const { payloads, streams, directoryStream } = params;
|
||||
let { localPaths, localDirectory } = params;
|
||||
|
||||
if ([payloads, localPaths, streams].filter(Boolean).length !== 1)
|
||||
if ([payloads, localPaths, localDirectory, streams, directoryStream].filter(Boolean).length !== 1)
|
||||
throw new Error('Exactly one of payloads, localPaths and streams must be provided');
|
||||
|
||||
if (streams) {
|
||||
localPaths = streams.map(c => (c as WritableStreamDispatcher)).filter(s => !s.isDirectory()).map(s => s.path());
|
||||
localDirectory = streams.map(c => (c as WritableStreamDispatcher)).filter(s => s.isDirectory()).map(s => s.path())[0];
|
||||
}
|
||||
if (streams)
|
||||
localPaths = streams.map(c => (c as WritableStreamDispatcher)).map(s => s.path());
|
||||
if (directoryStream)
|
||||
localDirectory = (directoryStream as WritableStreamDispatcher | undefined)?.path();
|
||||
|
||||
if (localPaths) {
|
||||
for (const p of localPaths)
|
||||
assert(path.isAbsolute(p) && path.resolve(p) === p, 'Paths provided to localPaths must be absolute and fully resolved.');
|
||||
|
|
|
|||
|
|
@ -2918,6 +2918,7 @@ export type FrameSetInputFilesParams = {
|
|||
buffer: Binary,
|
||||
}[],
|
||||
localDirectory?: string,
|
||||
directoryStream?: WritableStreamChannel,
|
||||
localPaths?: string[],
|
||||
streams?: WritableStreamChannel[],
|
||||
timeout?: number,
|
||||
|
|
@ -2931,6 +2932,7 @@ export type FrameSetInputFilesOptions = {
|
|||
buffer: Binary,
|
||||
}[],
|
||||
localDirectory?: string,
|
||||
directoryStream?: WritableStreamChannel,
|
||||
localPaths?: string[],
|
||||
streams?: WritableStreamChannel[],
|
||||
timeout?: number,
|
||||
|
|
@ -3544,6 +3546,7 @@ export type ElementHandleSetInputFilesParams = {
|
|||
buffer: Binary,
|
||||
}[],
|
||||
localDirectory?: string,
|
||||
directoryStream?: WritableStreamChannel,
|
||||
localPaths?: string[],
|
||||
streams?: WritableStreamChannel[],
|
||||
timeout?: number,
|
||||
|
|
@ -3556,6 +3559,7 @@ export type ElementHandleSetInputFilesOptions = {
|
|||
buffer: Binary,
|
||||
}[],
|
||||
localDirectory?: string,
|
||||
directoryStream?: WritableStreamChannel,
|
||||
localPaths?: string[],
|
||||
streams?: WritableStreamChannel[],
|
||||
timeout?: number,
|
||||
|
|
|
|||
|
|
@ -2197,6 +2197,7 @@ Frame:
|
|||
mimeType: string?
|
||||
buffer: binary
|
||||
localDirectory: string?
|
||||
directoryStream: WritableStream?
|
||||
localPaths:
|
||||
type: array?
|
||||
items: string
|
||||
|
|
@ -2758,6 +2759,7 @@ ElementHandle:
|
|||
mimeType: string?
|
||||
buffer: binary
|
||||
localDirectory: string?
|
||||
directoryStream: WritableStream?
|
||||
localPaths:
|
||||
type: array?
|
||||
items: string
|
||||
|
|
|
|||
|
|
@ -80,6 +80,20 @@ it('should upload a folder and throw for multiple directories', async ({ page, s
|
|||
await expect(input.setInputFiles([
|
||||
path.join(dir, 'folder1'),
|
||||
path.join(dir, 'folder2'),
|
||||
])).rejects.toThrow('Multiple directories are not supported');
|
||||
});
|
||||
|
||||
it('should throw if a directory and files are passed', async ({ page, server, browserName, headless, browserMajorVersion }) => {
|
||||
await page.goto(server.PREFIX + '/input/folderupload.html');
|
||||
const input = await page.$('input');
|
||||
const dir = path.join(it.info().outputDir, 'file-upload-test');
|
||||
{
|
||||
await fs.promises.mkdir(path.join(dir, 'folder1'), { recursive: true });
|
||||
await fs.promises.writeFile(path.join(dir, 'folder1', 'file1.txt'), 'file1 content');
|
||||
}
|
||||
await expect(input.setInputFiles([
|
||||
path.join(dir, 'folder1'),
|
||||
path.join(dir, 'folder1', 'file1.txt'),
|
||||
])).rejects.toThrow('File paths must be all files or a single directory');
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue