mirror of
https://github.com/microsoft/playwright.git
synced 2024-12-13 17:14:02 +03:00
chore: remove from client check if browser is co-located with server (#28071)
Reference https://github.com/microsoft/playwright/issues/27792
This commit is contained in:
parent
1aee48f2d0
commit
fae5dd898a
@ -151,10 +151,6 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
|
||||
this.tracing._tracesDir = browserOptions.tracesDir;
|
||||
}
|
||||
|
||||
_isLocalBrowserOnServer(): boolean {
|
||||
return this._initializer.isLocalBrowserOnServer;
|
||||
}
|
||||
|
||||
private _onPage(page: Page): void {
|
||||
this._pages.add(page);
|
||||
this.emit(Events.BrowserContext.Page, page);
|
||||
|
@ -24,14 +24,13 @@ import fs from 'fs';
|
||||
import { mime } from '../utilsBundle';
|
||||
import path from 'path';
|
||||
import { assert, isString } from '../utils';
|
||||
import { mkdirIfNeeded } from '../utils/fileUtils';
|
||||
import { fileUploadSizeLimit, mkdirIfNeeded } from '../utils/fileUtils';
|
||||
import type * as api from '../../types/types';
|
||||
import type * as structs from '../../types/structs';
|
||||
import type { BrowserContext } from './browserContext';
|
||||
import { WritableStream } from './writableStream';
|
||||
import { pipeline } from 'stream';
|
||||
import { promisify } from 'util';
|
||||
import { debugLogger } from '../common/debugLogger';
|
||||
|
||||
const pipelineAsync = promisify(pipeline);
|
||||
|
||||
@ -151,13 +150,7 @@ export class ElementHandle<T extends Node = Node> extends JSHandle<T> implements
|
||||
if (!frame)
|
||||
throw new Error('Cannot set input files to detached element');
|
||||
const converted = await convertInputFiles(files, frame.page().context());
|
||||
if (converted.files) {
|
||||
debugLogger.log('api', 'setting input buffers');
|
||||
await this._elementChannel.setInputFiles({ files: converted.files, ...options });
|
||||
} else {
|
||||
debugLogger.log('api', 'setting input file paths');
|
||||
await this._elementChannel.setInputFilePaths({ ...converted, ...options });
|
||||
}
|
||||
await this._elementChannel.setInputFiles({ ...converted, ...options });
|
||||
}
|
||||
|
||||
async focus(): Promise<void> {
|
||||
@ -257,36 +250,13 @@ export function convertSelectOptionValues(values: string | api.ElementHandle | S
|
||||
return { options: values as SelectOption[] };
|
||||
}
|
||||
|
||||
type SetInputFilesFiles = channels.ElementHandleSetInputFilesParams['files'];
|
||||
type InputFilesList = {
|
||||
files?: SetInputFilesFiles;
|
||||
localPaths?: string[];
|
||||
streams?: channels.WritableStreamChannel[];
|
||||
};
|
||||
|
||||
const filePayloadSizeLimit = 50 * 1024 * 1024;
|
||||
type SetInputFilesFiles = Pick<channels.ElementHandleSetInputFilesParams, 'payloads' | 'localPaths' | 'streams'>;
|
||||
|
||||
function filePayloadExceedsSizeLimit(payloads: FilePayload[]) {
|
||||
return payloads.reduce((size, item) => size + (item.buffer ? item.buffer.byteLength : 0), 0) >= filePayloadSizeLimit;
|
||||
return payloads.reduce((size, item) => size + (item.buffer ? item.buffer.byteLength : 0), 0) >= fileUploadSizeLimit;
|
||||
}
|
||||
|
||||
async function filesExceedSizeLimit(files: string[]) {
|
||||
const sizes = await Promise.all(files.map(async file => (await fs.promises.stat(file)).size));
|
||||
return sizes.reduce((total, size) => total + size, 0) >= filePayloadSizeLimit;
|
||||
}
|
||||
|
||||
async function readFilesIntoBuffers(items: string[]): Promise<SetInputFilesFiles> {
|
||||
const filePayloads: SetInputFilesFiles = await Promise.all((items as string[]).map(async item => {
|
||||
return {
|
||||
name: path.basename(item),
|
||||
buffer: await fs.promises.readFile(item),
|
||||
lastModifiedMs: (await fs.promises.stat(item)).mtimeMs,
|
||||
};
|
||||
}));
|
||||
return filePayloads;
|
||||
}
|
||||
|
||||
export async function convertInputFiles(files: string | FilePayload | string[] | FilePayload[], context: BrowserContext): Promise<InputFilesList> {
|
||||
export async function convertInputFiles(files: string | FilePayload | string[] | FilePayload[], context: BrowserContext): Promise<SetInputFilesFiles> {
|
||||
const items: (string | FilePayload)[] = Array.isArray(files) ? files.slice() : [files];
|
||||
|
||||
if (items.some(item => typeof item === 'string')) {
|
||||
@ -294,35 +264,22 @@ export async function convertInputFiles(files: string | FilePayload | string[] |
|
||||
throw new Error('File paths cannot be mixed with buffers');
|
||||
|
||||
if (context._connection.isRemote()) {
|
||||
if (context._isLocalBrowserOnServer()) {
|
||||
const streams: channels.WritableStreamChannel[] = await Promise.all((items as string[]).map(async item => {
|
||||
const lastModifiedMs = (await fs.promises.stat(item)).mtimeMs;
|
||||
const { writableStream: stream } = await context._channel.createTempFile({ name: path.basename(item), lastModifiedMs });
|
||||
const writable = WritableStream.from(stream);
|
||||
await pipelineAsync(fs.createReadStream(item), writable.stream());
|
||||
return stream;
|
||||
}));
|
||||
return { streams };
|
||||
}
|
||||
if (await filesExceedSizeLimit(items as string[]))
|
||||
throw new Error('Cannot transfer files larger than 50Mb to a browser not co-located with the server');
|
||||
return { files: await readFilesIntoBuffers(items as string[]) };
|
||||
const streams: channels.WritableStreamChannel[] = await Promise.all((items as string[]).map(async item => {
|
||||
const lastModifiedMs = (await fs.promises.stat(item)).mtimeMs;
|
||||
const { writableStream: stream } = await context._channel.createTempFile({ name: path.basename(item), lastModifiedMs });
|
||||
const writable = WritableStream.from(stream);
|
||||
await pipelineAsync(fs.createReadStream(item), writable.stream());
|
||||
return stream;
|
||||
}));
|
||||
return { streams };
|
||||
}
|
||||
if (context._isLocalBrowserOnServer())
|
||||
return { localPaths: items.map(f => path.resolve(f as string)) as string[] };
|
||||
if (await filesExceedSizeLimit(items as string[]))
|
||||
throw new Error('Cannot transfer files larger than 50Mb to a browser not co-located with the server');
|
||||
return { files: await readFilesIntoBuffers(items as string[]) };
|
||||
return { localPaths: items.map(f => path.resolve(f as string)) as string[] };
|
||||
}
|
||||
|
||||
const payloads = items as FilePayload[];
|
||||
if (filePayloadExceedsSizeLimit(payloads)) {
|
||||
let error = 'Cannot set buffer larger than 50Mb';
|
||||
if (context._isLocalBrowserOnServer())
|
||||
error += ', please write it to a file and pass its path instead.';
|
||||
throw new Error(error);
|
||||
}
|
||||
return { files: payloads };
|
||||
if (filePayloadExceedsSizeLimit(payloads))
|
||||
throw new Error('Cannot set buffer larger than 50Mb, please write it to a file and pass its path instead.');
|
||||
return { payloads };
|
||||
}
|
||||
|
||||
export function determineScreenshotType(options: { path?: string, type?: 'png' | 'jpeg' }): 'png' | 'jpeg' | undefined {
|
||||
|
@ -35,7 +35,6 @@ import { kLifecycleEvents } from './types';
|
||||
import { urlMatches } from '../utils/network';
|
||||
import type * as api from '../../types/types';
|
||||
import type * as structs from '../../types/structs';
|
||||
import { debugLogger } from '../common/debugLogger';
|
||||
|
||||
export type WaitForNavigationOptions = {
|
||||
timeout?: number,
|
||||
@ -401,13 +400,7 @@ export class Frame extends ChannelOwner<channels.FrameChannel> implements api.Fr
|
||||
|
||||
async setInputFiles(selector: string, files: string | FilePayload | string[] | FilePayload[], options: channels.FrameSetInputFilesOptions = {}): Promise<void> {
|
||||
const converted = await convertInputFiles(files, this.page().context());
|
||||
if (converted.files) {
|
||||
debugLogger.log('api', 'setting input buffers');
|
||||
await this._channel.setInputFiles({ selector, files: converted.files, ...options });
|
||||
} else {
|
||||
debugLogger.log('api', 'setting input file paths');
|
||||
await this._channel.setInputFilePaths({ selector, ...converted, ...options });
|
||||
}
|
||||
await this._channel.setInputFiles({ selector, ...converted, ...options });
|
||||
}
|
||||
|
||||
async type(selector: string, text: string, options: channels.FrameTypeOptions = {}) {
|
||||
|
@ -44,7 +44,6 @@ export const slowMoActions = new Set([
|
||||
'Frame.press',
|
||||
'Frame.selectOption',
|
||||
'Frame.setInputFiles',
|
||||
'Frame.setInputFilePaths',
|
||||
'Frame.tap',
|
||||
'Frame.type',
|
||||
'Frame.uncheck',
|
||||
@ -60,7 +59,6 @@ export const slowMoActions = new Set([
|
||||
'ElementHandle.selectOption',
|
||||
'ElementHandle.selectText',
|
||||
'ElementHandle.setInputFiles',
|
||||
'ElementHandle.setInputFilePaths',
|
||||
'ElementHandle.tap',
|
||||
'ElementHandle.type',
|
||||
'ElementHandle.uncheck'
|
||||
@ -121,7 +119,6 @@ export const commandsWithTracingSnapshots = new Set([
|
||||
'Frame.selectOption',
|
||||
'Frame.setContent',
|
||||
'Frame.setInputFiles',
|
||||
'Frame.setInputFilePaths',
|
||||
'Frame.tap',
|
||||
'Frame.textContent',
|
||||
'Frame.type',
|
||||
@ -158,7 +155,6 @@ export const commandsWithTracingSnapshots = new Set([
|
||||
'ElementHandle.selectOption',
|
||||
'ElementHandle.selectText',
|
||||
'ElementHandle.setInputFiles',
|
||||
'ElementHandle.setInputFilePaths',
|
||||
'ElementHandle.tap',
|
||||
'ElementHandle.textContent',
|
||||
'ElementHandle.type',
|
||||
@ -177,7 +173,6 @@ export const pausesBeforeInputActions = new Set([
|
||||
'Frame.press',
|
||||
'Frame.selectOption',
|
||||
'Frame.setInputFiles',
|
||||
'Frame.setInputFilePaths',
|
||||
'Frame.tap',
|
||||
'Frame.type',
|
||||
'Frame.uncheck',
|
||||
@ -189,7 +184,6 @@ export const pausesBeforeInputActions = new Set([
|
||||
'ElementHandle.press',
|
||||
'ElementHandle.selectOption',
|
||||
'ElementHandle.setInputFiles',
|
||||
'ElementHandle.setInputFilePaths',
|
||||
'ElementHandle.tap',
|
||||
'ElementHandle.type',
|
||||
'ElementHandle.uncheck'
|
||||
|
@ -762,7 +762,6 @@ scheme.ElectronApplicationWaitForEventInfoResult = tType('EventTargetWaitForEven
|
||||
scheme.AndroidDeviceWaitForEventInfoResult = tType('EventTargetWaitForEventInfoResult');
|
||||
scheme.BrowserContextInitializer = tObject({
|
||||
isChromium: tBoolean,
|
||||
isLocalBrowserOnServer: tBoolean,
|
||||
requestContext: tChannel(['APIRequestContext']),
|
||||
tracing: tChannel(['Tracing']),
|
||||
});
|
||||
@ -1557,25 +1556,17 @@ scheme.FrameSetContentResult = tOptional(tObject({}));
|
||||
scheme.FrameSetInputFilesParams = tObject({
|
||||
selector: tString,
|
||||
strict: tOptional(tBoolean),
|
||||
files: tArray(tObject({
|
||||
payloads: tOptional(tArray(tObject({
|
||||
name: tString,
|
||||
mimeType: tOptional(tString),
|
||||
buffer: tBinary,
|
||||
lastModifiedMs: tOptional(tNumber),
|
||||
})),
|
||||
timeout: tOptional(tNumber),
|
||||
noWaitAfter: tOptional(tBoolean),
|
||||
});
|
||||
scheme.FrameSetInputFilesResult = tOptional(tObject({}));
|
||||
scheme.FrameSetInputFilePathsParams = tObject({
|
||||
selector: tString,
|
||||
strict: tOptional(tBoolean),
|
||||
}))),
|
||||
localPaths: tOptional(tArray(tString)),
|
||||
streams: tOptional(tArray(tChannel(['WritableStream']))),
|
||||
timeout: tOptional(tNumber),
|
||||
noWaitAfter: tOptional(tBoolean),
|
||||
});
|
||||
scheme.FrameSetInputFilePathsResult = tOptional(tObject({}));
|
||||
scheme.FrameSetInputFilesResult = tOptional(tObject({}));
|
||||
scheme.FrameTapParams = tObject({
|
||||
selector: tString,
|
||||
strict: tOptional(tBoolean),
|
||||
@ -1931,23 +1922,17 @@ scheme.ElementHandleSelectTextParams = tObject({
|
||||
});
|
||||
scheme.ElementHandleSelectTextResult = tOptional(tObject({}));
|
||||
scheme.ElementHandleSetInputFilesParams = tObject({
|
||||
files: tArray(tObject({
|
||||
payloads: tOptional(tArray(tObject({
|
||||
name: tString,
|
||||
mimeType: tOptional(tString),
|
||||
buffer: tBinary,
|
||||
lastModifiedMs: tOptional(tNumber),
|
||||
})),
|
||||
timeout: tOptional(tNumber),
|
||||
noWaitAfter: tOptional(tBoolean),
|
||||
});
|
||||
scheme.ElementHandleSetInputFilesResult = tOptional(tObject({}));
|
||||
scheme.ElementHandleSetInputFilePathsParams = tObject({
|
||||
}))),
|
||||
localPaths: tOptional(tArray(tString)),
|
||||
streams: tOptional(tArray(tChannel(['WritableStream']))),
|
||||
timeout: tOptional(tNumber),
|
||||
noWaitAfter: tOptional(tBoolean),
|
||||
});
|
||||
scheme.ElementHandleSetInputFilePathsResult = tOptional(tObject({}));
|
||||
scheme.ElementHandleSetInputFilesResult = tOptional(tObject({}));
|
||||
scheme.ElementHandleTapParams = tObject({
|
||||
force: tOptional(tBoolean),
|
||||
noWaitAfter: tOptional(tBoolean),
|
||||
|
@ -23,9 +23,6 @@ import { JSHandleDispatcher, serializeResult, parseArgument } from './jsHandleDi
|
||||
import type { JSHandleDispatcherParentScope } from './jsHandleDispatcher';
|
||||
import { FrameDispatcher } from './frameDispatcher';
|
||||
import type { CallMetadata } from '../instrumentation';
|
||||
import type { WritableStreamDispatcher } from './writableStreamDispatcher';
|
||||
import { assert } from '../../utils';
|
||||
import path from 'path';
|
||||
import { BrowserContextDispatcher } from './browserContextDispatcher';
|
||||
import { PageDispatcher, WorkerDispatcher } from './pageDispatcher';
|
||||
|
||||
@ -151,19 +148,7 @@ export class ElementHandleDispatcher extends JSHandleDispatcher implements chann
|
||||
}
|
||||
|
||||
async setInputFiles(params: channels.ElementHandleSetInputFilesParams, metadata: CallMetadata): Promise<void> {
|
||||
return await this._elementHandle.setInputFiles(metadata, { files: params.files }, params);
|
||||
}
|
||||
|
||||
async setInputFilePaths(params: channels.ElementHandleSetInputFilePathsParams, metadata: CallMetadata): Promise<void> {
|
||||
let { localPaths } = params;
|
||||
if (!localPaths) {
|
||||
if (!params.streams)
|
||||
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);
|
||||
return await this._elementHandle.setInputFiles(metadata, params);
|
||||
}
|
||||
|
||||
async focus(params: channels.ElementHandleFocusParams, metadata: CallMetadata): Promise<void> {
|
||||
|
@ -23,9 +23,6 @@ import { parseArgument, serializeResult } from './jsHandleDispatcher';
|
||||
import { 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';
|
||||
import type { BrowserContextDispatcher } from './browserContextDispatcher';
|
||||
import type { PageDispatcher } from './pageDispatcher';
|
||||
|
||||
@ -218,19 +215,7 @@ export class FrameDispatcher extends Dispatcher<Frame, channels.FrameChannel, Br
|
||||
}
|
||||
|
||||
async setInputFiles(params: channels.FrameSetInputFilesParams, metadata: CallMetadata): Promise<channels.FrameSetInputFilesResult> {
|
||||
return await this._frame.setInputFiles(metadata, params.selector, { files: params.files }, params);
|
||||
}
|
||||
|
||||
async setInputFilePaths(params: channels.FrameSetInputFilePathsParams, metadata: CallMetadata): Promise<void> {
|
||||
let { localPaths } = params;
|
||||
if (!localPaths) {
|
||||
if (!params.streams)
|
||||
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);
|
||||
return await this._frame.setInputFiles(metadata, params.selector, params);
|
||||
}
|
||||
|
||||
async type(params: channels.FrameTypeParams, metadata: CallMetadata): Promise<void> {
|
||||
|
@ -14,9 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { mime } from '../utilsBundle';
|
||||
import * as injectedScriptSource from '../generated/injectedScriptSource';
|
||||
import type * as channels from '@protocol/channels';
|
||||
import * as injectedScriptSource from '../generated/injectedScriptSource';
|
||||
import { isSessionClosedError } from './protocolError';
|
||||
import type { ScreenshotOptions } from './screenshotter';
|
||||
import type * as frames from './frames';
|
||||
@ -29,9 +28,13 @@ import { ProgressController } from './progress';
|
||||
import type * as types from './types';
|
||||
import type { TimeoutOptions } from '../common/types';
|
||||
import { isUnderTest } from '../utils';
|
||||
import { prepareFilesForUpload } from './fileUploadUtils';
|
||||
|
||||
export type InputFilesItems = {
|
||||
filePayloads?: types.FilePayload[],
|
||||
localPaths?: string[]
|
||||
};
|
||||
|
||||
type SetInputFilesFiles = channels.ElementHandleSetInputFilesParams['files'];
|
||||
export type InputFilesItems = { files?: SetInputFilesFiles, localPaths?: string[] };
|
||||
type ActionName = 'click' | 'hover' | 'dblclick' | 'tap' | 'move and up' | 'move and down';
|
||||
|
||||
export class NonRecoverableDOMError extends Error {
|
||||
@ -579,29 +582,18 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
||||
}, this._page._timeoutSettings.timeout(options));
|
||||
}
|
||||
|
||||
async setInputFiles(metadata: CallMetadata, items: InputFilesItems, options: types.NavigatingActionWaitOptions) {
|
||||
async setInputFiles(metadata: CallMetadata, params: channels.ElementHandleSetInputFilesParams) {
|
||||
const inputFileItems = await prepareFilesForUpload(this._frame, params);
|
||||
const controller = new ProgressController(metadata, this);
|
||||
return controller.run(async progress => {
|
||||
const result = await this._setInputFiles(progress, items, options);
|
||||
const result = await this._setInputFiles(progress, inputFileItems, params);
|
||||
return assertDone(throwRetargetableDOMError(result));
|
||||
}, this._page._timeoutSettings.timeout(options));
|
||||
}, this._page._timeoutSettings.timeout(params));
|
||||
}
|
||||
|
||||
async _setInputFiles(progress: Progress, items: InputFilesItems, options: types.NavigatingActionWaitOptions): Promise<'error:notconnected' | 'done'> {
|
||||
const { files, localPaths } = items;
|
||||
let filePayloads: types.FilePayload[] | undefined;
|
||||
if (files) {
|
||||
filePayloads = [];
|
||||
for (const payload of files) {
|
||||
filePayloads.push({
|
||||
name: payload.name,
|
||||
mimeType: payload.mimeType || mime.getType(payload.name) || 'application/octet-stream',
|
||||
buffer: payload.buffer.toString('base64'),
|
||||
lastModifiedMs: payload.lastModifiedMs
|
||||
});
|
||||
}
|
||||
}
|
||||
const multiple = files && files.length > 1 || localPaths && localPaths.length > 1;
|
||||
const { filePayloads, localPaths } = items;
|
||||
const multiple = filePayloads && filePayloads.length > 1 || localPaths && localPaths.length > 1;
|
||||
const result = await this.evaluateHandleInUtility(([injected, node, multiple]): Element | undefined => {
|
||||
const element = injected.retarget(node, 'follow-label');
|
||||
if (!element)
|
||||
|
77
packages/playwright-core/src/server/fileUploadUtils.ts
Normal file
77
packages/playwright-core/src/server/fileUploadUtils.ts
Normal file
@ -0,0 +1,77 @@
|
||||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type * as channels from '@protocol/channels';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { assert, fileUploadSizeLimit } from '../utils';
|
||||
import { mime } from '../utilsBundle';
|
||||
import type { WritableStreamDispatcher } from './dispatchers/writableStreamDispatcher';
|
||||
import type { InputFilesItems } from './dom';
|
||||
import type { Frame } from './frames';
|
||||
import type * as types from './types';
|
||||
|
||||
async function filesExceedUploadLimit(files: string[]) {
|
||||
const sizes = await Promise.all(files.map(async file => (await fs.promises.stat(file)).size));
|
||||
return sizes.reduce((total, size) => total + size, 0) >= fileUploadSizeLimit;
|
||||
}
|
||||
|
||||
export async function prepareFilesForUpload(frame: Frame, params: channels.ElementHandleSetInputFilesParams): Promise<InputFilesItems> {
|
||||
const { payloads, streams } = params;
|
||||
let { localPaths } = params;
|
||||
|
||||
if ([payloads, localPaths, streams].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).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.');
|
||||
}
|
||||
|
||||
let fileBuffers: {
|
||||
name: string,
|
||||
mimeType?: string,
|
||||
buffer: Buffer,
|
||||
lastModifiedMs?: number,
|
||||
}[] | undefined = payloads;
|
||||
|
||||
if (!frame._page._browserContext._browser._isCollocatedWithServer) {
|
||||
// If the browser is on a different machine read files into buffers.
|
||||
if (localPaths) {
|
||||
if (await filesExceedUploadLimit(localPaths))
|
||||
throw new Error('Cannot transfer files larger than 50Mb to a browser not co-located with the server');
|
||||
fileBuffers = await Promise.all(localPaths.map(async item => {
|
||||
return {
|
||||
name: path.basename(item),
|
||||
buffer: await fs.promises.readFile(item),
|
||||
lastModifiedMs: (await fs.promises.stat(item)).mtimeMs,
|
||||
};
|
||||
}));
|
||||
localPaths = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const filePayloads: types.FilePayload[] | undefined = fileBuffers?.map(payload => ({
|
||||
name: payload.name,
|
||||
mimeType: payload.mimeType || mime.getType(payload.name) || 'application/octet-stream',
|
||||
buffer: payload.buffer.toString('base64'),
|
||||
lastModifiedMs: payload.lastModifiedMs
|
||||
}));
|
||||
|
||||
return { localPaths, filePayloads };
|
||||
}
|
@ -38,10 +38,10 @@ import type { InjectedScript, ElementStateWithoutStable, FrameExpectParams, Inje
|
||||
import { isSessionClosedError } from './protocolError';
|
||||
import { type ParsedSelector, isInvalidSelectorError } from '../utils/isomorphic/selectorParser';
|
||||
import type { ScreenshotOptions } from './screenshotter';
|
||||
import type { InputFilesItems } from './dom';
|
||||
import { asLocator } from '../utils/isomorphic/locatorGenerators';
|
||||
import { FrameSelectors } from './frameSelectors';
|
||||
import { TimeoutError } from './errors';
|
||||
import { prepareFilesForUpload } from './fileUploadUtils';
|
||||
|
||||
type ContextData = {
|
||||
contextPromise: ManualPromise<dom.FrameExecutionContext | { destroyedReason: string }>;
|
||||
@ -1319,11 +1319,12 @@ export class Frame extends SdkObject {
|
||||
}, this._page._timeoutSettings.timeout(options));
|
||||
}
|
||||
|
||||
async setInputFiles(metadata: CallMetadata, selector: string, items: InputFilesItems, options: types.NavigatingActionWaitOptions = {}): Promise<channels.FrameSetInputFilesResult> {
|
||||
async setInputFiles(metadata: CallMetadata, selector: string, params: channels.FrameSetInputFilesParams): Promise<channels.FrameSetInputFilesResult> {
|
||||
const inputFileItems = await prepareFilesForUpload(this, params);
|
||||
const controller = new ProgressController(metadata, this);
|
||||
return controller.run(async progress => {
|
||||
return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options.strict, handle => handle._setInputFiles(progress, items, options)));
|
||||
}, this._page._timeoutSettings.timeout(options));
|
||||
return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, params.strict, handle => handle._setInputFiles(progress, inputFileItems, params)));
|
||||
}, this._page._timeoutSettings.timeout(params));
|
||||
}
|
||||
|
||||
async type(metadata: CallMetadata, selector: string, text: string, options: { delay?: number } & types.NavigatingActionWaitOptions = {}) {
|
||||
|
@ -17,6 +17,8 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
export const fileUploadSizeLimit = 50 * 1024 * 1024;
|
||||
|
||||
export const existsAsync = (path: string): Promise<boolean> => new Promise(resolve => fs.stat(path, err => resolve(!err)));
|
||||
|
||||
export async function mkdirIfNeeded(filePath: string) {
|
||||
|
@ -1407,7 +1407,6 @@ export interface EventTargetEvents {
|
||||
// ----------- BrowserContext -----------
|
||||
export type BrowserContextInitializer = {
|
||||
isChromium: boolean,
|
||||
isLocalBrowserOnServer: boolean,
|
||||
requestContext: APIRequestContextChannel,
|
||||
tracing: TracingChannel,
|
||||
};
|
||||
@ -2305,7 +2304,6 @@ export interface FrameChannel extends FrameEventTarget, Channel {
|
||||
selectOption(params: FrameSelectOptionParams, metadata?: CallMetadata): Promise<FrameSelectOptionResult>;
|
||||
setContent(params: FrameSetContentParams, metadata?: CallMetadata): Promise<FrameSetContentResult>;
|
||||
setInputFiles(params: FrameSetInputFilesParams, metadata?: CallMetadata): Promise<FrameSetInputFilesResult>;
|
||||
setInputFilePaths(params: FrameSetInputFilePathsParams, metadata?: CallMetadata): Promise<FrameSetInputFilePathsResult>;
|
||||
tap(params: FrameTapParams, metadata?: CallMetadata): Promise<FrameTapResult>;
|
||||
textContent(params: FrameTextContentParams, metadata?: CallMetadata): Promise<FrameTextContentResult>;
|
||||
title(params?: FrameTitleParams, metadata?: CallMetadata): Promise<FrameTitleResult>;
|
||||
@ -2792,37 +2790,29 @@ export type FrameSetContentResult = void;
|
||||
export type FrameSetInputFilesParams = {
|
||||
selector: string,
|
||||
strict?: boolean,
|
||||
files: {
|
||||
payloads?: {
|
||||
name: string,
|
||||
mimeType?: string,
|
||||
buffer: Binary,
|
||||
lastModifiedMs?: number,
|
||||
}[],
|
||||
localPaths?: string[],
|
||||
streams?: WritableStreamChannel[],
|
||||
timeout?: number,
|
||||
noWaitAfter?: boolean,
|
||||
};
|
||||
export type FrameSetInputFilesOptions = {
|
||||
strict?: boolean,
|
||||
payloads?: {
|
||||
name: string,
|
||||
mimeType?: string,
|
||||
buffer: Binary,
|
||||
}[],
|
||||
localPaths?: string[],
|
||||
streams?: WritableStreamChannel[],
|
||||
timeout?: number,
|
||||
noWaitAfter?: boolean,
|
||||
};
|
||||
export type FrameSetInputFilesResult = void;
|
||||
export type FrameSetInputFilePathsParams = {
|
||||
selector: string,
|
||||
strict?: boolean,
|
||||
localPaths?: string[],
|
||||
streams?: WritableStreamChannel[],
|
||||
timeout?: number,
|
||||
noWaitAfter?: boolean,
|
||||
};
|
||||
export type FrameSetInputFilePathsOptions = {
|
||||
strict?: boolean,
|
||||
localPaths?: string[],
|
||||
streams?: WritableStreamChannel[],
|
||||
timeout?: number,
|
||||
noWaitAfter?: boolean,
|
||||
};
|
||||
export type FrameSetInputFilePathsResult = void;
|
||||
export type FrameTapParams = {
|
||||
selector: string,
|
||||
strict?: boolean,
|
||||
@ -3115,7 +3105,6 @@ export interface ElementHandleChannel extends ElementHandleEventTarget, JSHandle
|
||||
selectOption(params: ElementHandleSelectOptionParams, metadata?: CallMetadata): Promise<ElementHandleSelectOptionResult>;
|
||||
selectText(params: ElementHandleSelectTextParams, metadata?: CallMetadata): Promise<ElementHandleSelectTextResult>;
|
||||
setInputFiles(params: ElementHandleSetInputFilesParams, metadata?: CallMetadata): Promise<ElementHandleSetInputFilesResult>;
|
||||
setInputFilePaths(params: ElementHandleSetInputFilePathsParams, metadata?: CallMetadata): Promise<ElementHandleSetInputFilePathsResult>;
|
||||
tap(params: ElementHandleTapParams, metadata?: CallMetadata): Promise<ElementHandleTapResult>;
|
||||
textContent(params?: ElementHandleTextContentParams, metadata?: CallMetadata): Promise<ElementHandleTextContentResult>;
|
||||
type(params: ElementHandleTypeParams, metadata?: CallMetadata): Promise<ElementHandleTypeResult>;
|
||||
@ -3423,33 +3412,28 @@ export type ElementHandleSelectTextOptions = {
|
||||
};
|
||||
export type ElementHandleSelectTextResult = void;
|
||||
export type ElementHandleSetInputFilesParams = {
|
||||
files: {
|
||||
payloads?: {
|
||||
name: string,
|
||||
mimeType?: string,
|
||||
buffer: Binary,
|
||||
lastModifiedMs?: number,
|
||||
}[],
|
||||
localPaths?: string[],
|
||||
streams?: WritableStreamChannel[],
|
||||
timeout?: number,
|
||||
noWaitAfter?: boolean,
|
||||
};
|
||||
export type ElementHandleSetInputFilesOptions = {
|
||||
payloads?: {
|
||||
name: string,
|
||||
mimeType?: string,
|
||||
buffer: Binary,
|
||||
}[],
|
||||
localPaths?: string[],
|
||||
streams?: WritableStreamChannel[],
|
||||
timeout?: number,
|
||||
noWaitAfter?: boolean,
|
||||
};
|
||||
export type ElementHandleSetInputFilesResult = void;
|
||||
export type ElementHandleSetInputFilePathsParams = {
|
||||
localPaths?: string[],
|
||||
streams?: WritableStreamChannel[],
|
||||
timeout?: number,
|
||||
noWaitAfter?: boolean,
|
||||
};
|
||||
export type ElementHandleSetInputFilePathsOptions = {
|
||||
localPaths?: string[],
|
||||
streams?: WritableStreamChannel[],
|
||||
timeout?: number,
|
||||
noWaitAfter?: boolean,
|
||||
};
|
||||
export type ElementHandleSetInputFilePathsResult = void;
|
||||
export type ElementHandleTapParams = {
|
||||
force?: boolean,
|
||||
noWaitAfter?: boolean,
|
||||
|
@ -1012,7 +1012,6 @@ BrowserContext:
|
||||
|
||||
initializer:
|
||||
isChromium: boolean
|
||||
isLocalBrowserOnServer: boolean
|
||||
requestContext: APIRequestContext
|
||||
tracing: Tracing
|
||||
|
||||
@ -2111,28 +2110,15 @@ Frame:
|
||||
parameters:
|
||||
selector: string
|
||||
strict: boolean?
|
||||
files:
|
||||
type: array
|
||||
# Only one of payloads, localPaths and streams should be present.
|
||||
payloads:
|
||||
type: array?
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
name: string
|
||||
mimeType: string?
|
||||
buffer: binary
|
||||
lastModifiedMs: number?
|
||||
timeout: number?
|
||||
noWaitAfter: boolean?
|
||||
flags:
|
||||
slowMo: true
|
||||
snapshot: true
|
||||
pausesBeforeInput: true
|
||||
|
||||
# This method should be used if one of the files is large (>50Mb).
|
||||
setInputFilePaths:
|
||||
parameters:
|
||||
selector: string
|
||||
strict: boolean?
|
||||
# Only one of localPaths and streams should be present.
|
||||
localPaths:
|
||||
type: array?
|
||||
items: string
|
||||
@ -2680,26 +2666,15 @@ ElementHandle:
|
||||
|
||||
setInputFiles:
|
||||
parameters:
|
||||
files:
|
||||
type: array
|
||||
# Only one of payloads, localPaths and streams should be present.
|
||||
payloads:
|
||||
type: array?
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
name: string
|
||||
mimeType: string?
|
||||
buffer: binary
|
||||
lastModifiedMs: number?
|
||||
timeout: number?
|
||||
noWaitAfter: boolean?
|
||||
flags:
|
||||
slowMo: true
|
||||
snapshot: true
|
||||
pausesBeforeInput: true
|
||||
|
||||
# This method should be used if one of the files is large (>50Mb).
|
||||
setInputFilePaths:
|
||||
parameters:
|
||||
# Only one of localPaths and streams should be present.
|
||||
localPaths:
|
||||
type: array?
|
||||
items: string
|
||||
|
Loading…
Reference in New Issue
Block a user