Skip to content

Commit

Permalink
chore: remove from client check if browser is co-located with server (#…
Browse files Browse the repository at this point in the history
…28071)

Reference #27792
  • Loading branch information
yury-s authored Nov 10, 2023
1 parent 1aee48f commit fae5dd8
Show file tree
Hide file tree
Showing 13 changed files with 145 additions and 219 deletions.
4 changes: 0 additions & 4 deletions packages/playwright-core/src/client/browserContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
77 changes: 17 additions & 60 deletions packages/playwright-core/src/client/elementHandle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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> {
Expand Down Expand Up @@ -257,72 +250,36 @@ 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;
}

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;
return payloads.reduce((size, item) => size + (item.buffer ? item.buffer.byteLength : 0), 0) >= fileUploadSizeLimit;
}

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')) {
if (!items.every(item => typeof item === '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 {
Expand Down
9 changes: 1 addition & 8 deletions packages/playwright-core/src/client/frame.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 = {}) {
Expand Down
6 changes: 0 additions & 6 deletions packages/playwright-core/src/protocol/debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ export const slowMoActions = new Set([
'Frame.press',
'Frame.selectOption',
'Frame.setInputFiles',
'Frame.setInputFilePaths',
'Frame.tap',
'Frame.type',
'Frame.uncheck',
Expand All @@ -60,7 +59,6 @@ export const slowMoActions = new Set([
'ElementHandle.selectOption',
'ElementHandle.selectText',
'ElementHandle.setInputFiles',
'ElementHandle.setInputFilePaths',
'ElementHandle.tap',
'ElementHandle.type',
'ElementHandle.uncheck'
Expand Down Expand Up @@ -121,7 +119,6 @@ export const commandsWithTracingSnapshots = new Set([
'Frame.selectOption',
'Frame.setContent',
'Frame.setInputFiles',
'Frame.setInputFilePaths',
'Frame.tap',
'Frame.textContent',
'Frame.type',
Expand Down Expand Up @@ -158,7 +155,6 @@ export const commandsWithTracingSnapshots = new Set([
'ElementHandle.selectOption',
'ElementHandle.selectText',
'ElementHandle.setInputFiles',
'ElementHandle.setInputFilePaths',
'ElementHandle.tap',
'ElementHandle.textContent',
'ElementHandle.type',
Expand All @@ -177,7 +173,6 @@ export const pausesBeforeInputActions = new Set([
'Frame.press',
'Frame.selectOption',
'Frame.setInputFiles',
'Frame.setInputFilePaths',
'Frame.tap',
'Frame.type',
'Frame.uncheck',
Expand All @@ -189,7 +184,6 @@ export const pausesBeforeInputActions = new Set([
'ElementHandle.press',
'ElementHandle.selectOption',
'ElementHandle.setInputFiles',
'ElementHandle.setInputFilePaths',
'ElementHandle.tap',
'ElementHandle.type',
'ElementHandle.uncheck'
Expand Down
27 changes: 6 additions & 21 deletions packages/playwright-core/src/protocol/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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']),
});
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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> {
Expand Down
34 changes: 13 additions & 21 deletions packages/playwright-core/src/server/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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 {
Expand Down Expand Up @@ -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)
Expand Down
Loading

0 comments on commit fae5dd8

Please sign in to comment.