diff --git a/packages/playwright-core/src/client/android.ts b/packages/playwright-core/src/client/android.ts index 895f466ceb..e5512b2b05 100644 --- a/packages/playwright-core/src/client/android.ts +++ b/packages/playwright-core/src/client/android.ts @@ -166,10 +166,9 @@ export class AndroidDevice extends ChannelOwner i async screenshot(options: { path?: string } = {}): Promise { const { binary } = await this._channel.screenshot(); - const buffer = Buffer.from(binary, 'base64'); if (options.path) - await fs.promises.writeFile(options.path, buffer); - return buffer; + await fs.promises.writeFile(options.path, binary); + return binary; } async close() { @@ -179,7 +178,7 @@ export class AndroidDevice extends ChannelOwner i async shell(command: string): Promise { const { result } = await this._channel.shell({ command }); - return Buffer.from(result, 'base64'); + return result; } async open(command: string): Promise { @@ -222,12 +221,12 @@ export class AndroidSocket extends ChannelOwner i constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.AndroidSocketInitializer) { super(parent, type, guid, initializer); - this._channel.on('data', ({ data }) => this.emit(Events.AndroidSocket.Data, Buffer.from(data, 'base64'))); + this._channel.on('data', ({ data }) => this.emit(Events.AndroidSocket.Data, data)); this._channel.on('close', () => this.emit(Events.AndroidSocket.Close)); } async write(data: Buffer): Promise { - await this._channel.write({ data: data.toString('base64') }); + await this._channel.write({ data }); } async close(): Promise { @@ -235,10 +234,10 @@ export class AndroidSocket extends ChannelOwner i } } -async function loadFile(file: string | Buffer): Promise { +async function loadFile(file: string | Buffer): Promise { if (isString(file)) - return fs.promises.readFile(file, { encoding: 'base64' }).toString(); - return file.toString('base64'); + return fs.promises.readFile(file); + return file; } export class AndroidInput implements api.AndroidInput { diff --git a/packages/playwright-core/src/client/browser.ts b/packages/playwright-core/src/client/browser.ts index 077f8c1902..bacb00ba0d 100644 --- a/packages/playwright-core/src/client/browser.ts +++ b/packages/playwright-core/src/client/browser.ts @@ -99,7 +99,7 @@ export class Browser extends ChannelOwner implements ap } async stopTracing(): Promise { - return Buffer.from((await this._channel.stopTracing()).binary, 'base64'); + return (await this._channel.stopTracing()).binary; } async close(): Promise { diff --git a/packages/playwright-core/src/client/channelOwner.ts b/packages/playwright-core/src/client/channelOwner.ts index c9399a3d0e..0b5e2c0706 100644 --- a/packages/playwright-core/src/client/channelOwner.ts +++ b/packages/playwright-core/src/client/channelOwner.ts @@ -89,7 +89,7 @@ export abstract class ChannelOwner; const validator = findValidator(type, '', 'Initializer'); - initializer = validator(initializer, '', { tChannelImpl: this._tChannelImplFromWire.bind(this) }); + initializer = validator(initializer, '', { tChannelImpl: this._tChannelImplFromWire.bind(this), binary: this.isRemote() ? 'fromBase64' : 'buffer' }); switch (type) { case 'Android': result = new Android(parent, type, guid, initializer); diff --git a/packages/playwright-core/src/client/elementHandle.ts b/packages/playwright-core/src/client/elementHandle.ts index d95c54461a..2be8b7f5e6 100644 --- a/packages/playwright-core/src/client/elementHandle.ts +++ b/packages/playwright-core/src/client/elementHandle.ts @@ -202,12 +202,11 @@ export class ElementHandle extends JSHandle implements })); } const result = await this._elementChannel.screenshot(copy); - const buffer = Buffer.from(result.binary, 'base64'); if (options.path) { await mkdirIfNeeded(options.path); - await fs.promises.writeFile(options.path, buffer); + await fs.promises.writeFile(options.path, result.binary); } - return buffer; + return result.binary; } async $(selector: string): Promise | null> { @@ -291,13 +290,13 @@ export async function convertInputFiles(files: string | FilePayload | string[] | if (typeof item === 'string') { return { name: path.basename(item), - buffer: (await fs.promises.readFile(item)).toString('base64') + buffer: await fs.promises.readFile(item) }; } else { return { name: item.name, mimeType: item.mimeType, - buffer: item.buffer.toString('base64'), + buffer: item.buffer, }; } })); diff --git a/packages/playwright-core/src/client/fetch.ts b/packages/playwright-core/src/client/fetch.ts index 83965c38ec..af43f91f47 100644 --- a/packages/playwright-core/src/client/fetch.ts +++ b/packages/playwright-core/src/client/fetch.ts @@ -189,13 +189,12 @@ export class APIRequestContext extends ChannelOwner [h.name, h.value])), - body: Buffer.from(response.body!, 'base64') + body: response.body! }); return; } diff --git a/packages/playwright-core/src/client/network.ts b/packages/playwright-core/src/client/network.ts index 54f0eab730..2dee4482a9 100644 --- a/packages/playwright-core/src/client/network.ts +++ b/packages/playwright-core/src/client/network.ts @@ -87,7 +87,7 @@ export class Request extends ChannelOwner implements ap if (this._redirectedFrom) this._redirectedFrom._redirectedTo = this; this._provisionalHeaders = new RawHeaders(initializer.headers); - this._postData = initializer.postData !== undefined ? Buffer.from(initializer.postData, 'base64') : null; + this._postData = initializer.postData ?? null; this._timing = { startTime: 0, domainLookupStart: -1, @@ -385,7 +385,7 @@ export class Route extends ChannelOwner implements api.Ro url: options.url, method: options.method, headers: options.headers ? headersObjectToArray(options.headers) : undefined, - postData: postDataBuffer ? postDataBuffer.toString('base64') : undefined, + postData: postDataBuffer, })); }, !!internal); } @@ -491,7 +491,7 @@ export class Response extends ChannelOwner implements } async body(): Promise { - return Buffer.from((await this._channel.body()).binary, 'base64'); + return (await this._channel.body()).binary; } async text(): Promise { @@ -533,13 +533,13 @@ export class WebSocket extends ChannelOwner implement super(parent, type, guid, initializer); this._isClosed = false; this._page = parent as Page; - this._channel.on('frameSent', (event: { opcode: number, data: string }) => { + this._channel.on('frameSent', event => { if (event.opcode === 1) this.emit(Events.WebSocket.FrameSent, { payload: event.data }); else if (event.opcode === 2) this.emit(Events.WebSocket.FrameSent, { payload: Buffer.from(event.data, 'base64') }); }); - this._channel.on('frameReceived', (event: { opcode: number, data: string }) => { + this._channel.on('frameReceived', event => { if (event.opcode === 1) this.emit(Events.WebSocket.FrameReceived, { payload: event.data }); else if (event.opcode === 2) diff --git a/packages/playwright-core/src/client/page.ts b/packages/playwright-core/src/client/page.ts index 17c9252f01..10b05f53a4 100644 --- a/packages/playwright-core/src/client/page.ts +++ b/packages/playwright-core/src/client/page.ts @@ -15,7 +15,6 @@ * limitations under the License. */ -import { Buffer } from 'buffer'; import fs from 'fs'; import path from 'path'; import type * as structs from '../../types/structs'; @@ -503,12 +502,11 @@ export class Page extends ChannelOwner implements api.Page })); } const result = await this._channel.screenshot(copy); - const buffer = Buffer.from(result.binary, 'base64'); if (options.path) { await mkdirIfNeeded(options.path); - await fs.promises.writeFile(options.path, buffer); + await fs.promises.writeFile(options.path, result.binary); } - return buffer; + return result.binary; } async _expectScreenshot(customStackTrace: ParsedStackTrace, options: ExpectScreenshotOptions): Promise<{ actual?: Buffer, previous?: Buffer, diff?: Buffer, errorMessage?: string, log?: string[]}> { @@ -521,25 +519,15 @@ export class Page extends ChannelOwner implements api.Page frame: options.locator._frame._channel, selector: options.locator._selector, } : undefined; - const expected = options.expected ? options.expected.toString('base64') : undefined; - - const result = await this._channel.expectScreenshot({ + return await this._channel.expectScreenshot({ ...options, isNot: !!options.isNot, - expected, locator, screenshotOptions: { ...options.screenshotOptions, mask, } }); - return { - log: result.log, - actual: result.actual ? Buffer.from(result.actual, 'base64') : undefined, - previous: result.previous ? Buffer.from(result.previous, 'base64') : undefined, - diff: result.diff ? Buffer.from(result.diff, 'base64') : undefined, - errorMessage: result.errorMessage, - }; }, false /* isInternal */, customStackTrace); } @@ -742,12 +730,11 @@ export class Page extends ChannelOwner implements api.Page transportOptions.margin![index] = transportOptions.margin![index] + 'px'; } const result = await this._channel.pdf(transportOptions); - const buffer = Buffer.from(result.pdf, 'base64'); if (options.path) { await fs.promises.mkdir(path.dirname(options.path), { recursive: true }); - await fs.promises.writeFile(options.path, buffer); + await fs.promises.writeFile(options.path, result.pdf); } - return buffer; + return result.pdf; } async _resetForReuse() { diff --git a/packages/playwright-core/src/client/stream.ts b/packages/playwright-core/src/client/stream.ts index 9386c63fbc..897b33416d 100644 --- a/packages/playwright-core/src/client/stream.ts +++ b/packages/playwright-core/src/client/stream.ts @@ -42,8 +42,8 @@ class StreamImpl extends Readable { override async _read(size: number) { const result = await this._channel.read({ size }); - if (result.binary) - this.push(Buffer.from(result.binary, 'base64')); + if (result.binary.byteLength) + this.push(result.binary); else this.push(null); } diff --git a/packages/playwright-core/src/client/writableStream.ts b/packages/playwright-core/src/client/writableStream.ts index 8a70033e7f..1efad263d0 100644 --- a/packages/playwright-core/src/client/writableStream.ts +++ b/packages/playwright-core/src/client/writableStream.ts @@ -40,8 +40,8 @@ class WritableStreamImpl extends Writable { this._channel = channel; } - override async _write(chunk: any, encoding: BufferEncoding, callback: (error?: Error | null) => void) { - const error = await this._channel.write({ binary: chunk.toString('base64') }).catch(e => e); + override async _write(chunk: Buffer | string, encoding: BufferEncoding, callback: (error?: Error | null) => void) { + const error = await this._channel.write({ binary: typeof chunk === 'string' ? Buffer.from(chunk) : chunk }).catch(e => e); callback(error || null); } diff --git a/packages/playwright-core/src/inProcessFactory.ts b/packages/playwright-core/src/inProcessFactory.ts index 01ba2c2f6d..4c11844183 100644 --- a/packages/playwright-core/src/inProcessFactory.ts +++ b/packages/playwright-core/src/inProcessFactory.ts @@ -23,7 +23,7 @@ export function createInProcessPlaywright(): PlaywrightAPI { const playwright = createPlaywright('javascript'); const clientConnection = new Connection(); - const dispatcherConnection = new DispatcherConnection(); + const dispatcherConnection = new DispatcherConnection(true /* local */); // Dispatch synchronously at first. dispatcherConnection.onmessage = message => clientConnection.dispatch(message); diff --git a/packages/playwright-core/src/protocol/channels.ts b/packages/playwright-core/src/protocol/channels.ts index 1f32cff4ba..4db7055ef3 100644 --- a/packages/playwright-core/src/protocol/channels.ts +++ b/packages/playwright-core/src/protocol/channels.ts @@ -16,7 +16,7 @@ // This file is generated by generate_channels.js, do not edit manually. -export type Binary = string; +export type Binary = Buffer; export interface Channel { } @@ -408,11 +408,11 @@ export type LocalUtilsHarLookupParams = { url: string, method: string, headers: NameValue[], - postData?: string, + postData?: Binary, isNavigationRequest: boolean, }; export type LocalUtilsHarLookupOptions = { - postData?: string, + postData?: Binary, }; export type LocalUtilsHarLookupResult = { action: 'error' | 'redirect' | 'fulfill' | 'noentry', @@ -420,7 +420,7 @@ export type LocalUtilsHarLookupResult = { redirectURL?: string, status?: number, headers?: NameValue[], - body?: string, + body?: Binary, }; export type LocalUtilsHarCloseParams = { harId: string, diff --git a/packages/playwright-core/src/protocol/protocol.yml b/packages/playwright-core/src/protocol/protocol.yml index 018d4c3460..dcf8ca2c5a 100644 --- a/packages/playwright-core/src/protocol/protocol.yml +++ b/packages/playwright-core/src/protocol/protocol.yml @@ -500,7 +500,7 @@ LocalUtils: headers: type: array items: NameValue - postData: string? + postData: binary? isNavigationRequest: boolean returns: action: @@ -516,7 +516,7 @@ LocalUtils: headers: type: array? items: NameValue - body: string? + body: binary? harClose: parameters: diff --git a/packages/playwright-core/src/protocol/validator.ts b/packages/playwright-core/src/protocol/validator.ts index 520092d3e7..1eab0e1b52 100644 --- a/packages/playwright-core/src/protocol/validator.ts +++ b/packages/playwright-core/src/protocol/validator.ts @@ -224,7 +224,7 @@ scheme.LocalUtilsHarLookupParams = tObject({ url: tString, method: tString, headers: tArray(tType('NameValue')), - postData: tOptional(tString), + postData: tOptional(tBinary), isNavigationRequest: tBoolean, }); scheme.LocalUtilsHarLookupResult = tObject({ @@ -233,7 +233,7 @@ scheme.LocalUtilsHarLookupResult = tObject({ redirectURL: tOptional(tString), status: tOptional(tNumber), headers: tOptional(tArray(tType('NameValue'))), - body: tOptional(tString), + body: tOptional(tBinary), }); scheme.LocalUtilsHarCloseParams = tObject({ harId: tString, diff --git a/packages/playwright-core/src/protocol/validatorPrimitives.ts b/packages/playwright-core/src/protocol/validatorPrimitives.ts index 25b8e8db9d..9d4614512b 100644 --- a/packages/playwright-core/src/protocol/validatorPrimitives.ts +++ b/packages/playwright-core/src/protocol/validatorPrimitives.ts @@ -20,6 +20,7 @@ export class ValidationError extends Error {} export type Validator = (arg: any, path: string, context: ValidatorContext) => any; export type ValidatorContext = { tChannelImpl: (names: '*' | string[], arg: any, path: string, context: ValidatorContext) => any, + binary: 'toBase64' | 'fromBase64' | 'buffer', }; export const scheme: { [key: string]: Validator } = {}; @@ -59,11 +60,24 @@ export const tString: Validator = (arg: any, path: string, context: ValidatorCon throw new ValidationError(`${path}: expected string, got ${typeof arg}`); }; export const tBinary: Validator = (arg: any, path: string, context: ValidatorContext) => { - if (arg instanceof String) - return arg.valueOf(); - if (typeof arg === 'string') + if (context.binary === 'fromBase64') { + if (arg instanceof String) + return Buffer.from(arg.valueOf(), 'base64'); + if (typeof arg === 'string') + return Buffer.from(arg, 'base64'); + throw new ValidationError(`${path}: expected base64-encoded buffer, got ${typeof arg}`); + } + if (context.binary === 'toBase64') { + if (!(arg instanceof Buffer)) + throw new ValidationError(`${path}: expected Buffer, got ${typeof arg}`); + return (arg as Buffer).toString('base64'); + } + if (context.binary === 'buffer') { + if (!(arg instanceof Buffer)) + throw new ValidationError(`${path}: expected Buffer, got ${typeof arg}`); return arg; - throw new ValidationError(`${path}: expected base64-encoded buffer, got ${typeof arg}`); + } + throw new ValidationError(`Unsupported binary behavior "${context.binary}"`); }; export const tUndefined: Validator = (arg: any, path: string, context: ValidatorContext) => { if (Object.is(arg, undefined)) diff --git a/packages/playwright-core/src/server/dispatchers/androidDispatcher.ts b/packages/playwright-core/src/server/dispatchers/androidDispatcher.ts index af23d11eb0..3208a3be22 100644 --- a/packages/playwright-core/src/server/dispatchers/androidDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/androidDispatcher.ts @@ -136,11 +136,11 @@ export class AndroidDeviceDispatcher extends Dispatcher { - return { binary: (await this._object.screenshot()).toString('base64') }; + return { binary: await this._object.screenshot() }; } async shell(params: channels.AndroidDeviceShellParams): Promise { - return { result: (await this._object.shell(params.command)).toString('base64') }; + return { result: await this._object.shell(params.command) }; } async open(params: channels.AndroidDeviceOpenParams, metadata: CallMetadata): Promise { @@ -149,11 +149,11 @@ export class AndroidDeviceDispatcher extends Dispatcher { @@ -179,7 +179,7 @@ export class AndroidSocketDispatcher extends Dispatcher this._dispatchEvent('data', { data: data.toString('base64') })); + socket.on('data', (data: Buffer) => this._dispatchEvent('data', { data })); socket.on('close', () => { this._dispatchEvent('close'); this._dispose(); @@ -187,7 +187,7 @@ export class AndroidSocketDispatcher extends Dispatcher { - await this._object.write(Buffer.from(params.data, 'base64')); + await this._object.write(params.data); } async close(params: channels.AndroidSocketCloseParams, metadata: CallMetadata): Promise { diff --git a/packages/playwright-core/src/server/dispatchers/browserDispatcher.ts b/packages/playwright-core/src/server/dispatchers/browserDispatcher.ts index 353e233f0e..4c8b181d4a 100644 --- a/packages/playwright-core/src/server/dispatchers/browserDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/browserDispatcher.ts @@ -70,8 +70,7 @@ export class BrowserDispatcher extends Dispatcher implements channels.BrowserTypeChannel { _type_BrowserType = true; @@ -113,6 +114,8 @@ class SocksInterceptor { try { const id = --lastId; this._ids.add(id); + const validator = findValidator('SocksSupport', prop, 'Params'); + params = validator(params, '', { tChannelImpl: tChannelForSocks, binary: 'toBase64' }); transport.send({ id, guid: socksSupportObjectGuid, method: prop, params, metadata: { stack: [], apiName: '', internal: true } } as any); } catch (e) { } @@ -120,13 +123,13 @@ class SocksInterceptor { }, }) as channels.SocksSupportChannel & EventEmitter; this._handler.on(socks.SocksProxyHandler.Events.SocksConnected, (payload: socks.SocksSocketConnectedPayload) => this._channel.socksConnected(payload)); - this._handler.on(socks.SocksProxyHandler.Events.SocksData, (payload: socks.SocksSocketDataPayload) => this._channel.socksData({ uid: payload.uid, data: payload.data.toString('base64') })); + this._handler.on(socks.SocksProxyHandler.Events.SocksData, (payload: socks.SocksSocketDataPayload) => this._channel.socksData(payload)); this._handler.on(socks.SocksProxyHandler.Events.SocksError, (payload: socks.SocksSocketErrorPayload) => this._channel.socksError(payload)); this._handler.on(socks.SocksProxyHandler.Events.SocksFailed, (payload: socks.SocksSocketFailedPayload) => this._channel.socksFailed(payload)); this._handler.on(socks.SocksProxyHandler.Events.SocksEnd, (payload: socks.SocksSocketEndPayload) => this._channel.socksEnd(payload)); this._channel.on('socksRequested', payload => this._handler.socketRequested(payload)); this._channel.on('socksClosed', payload => this._handler.socketClosed(payload)); - this._channel.on('socksData', payload => this._handler.sendSocketData({ uid: payload.uid, data: Buffer.from(payload.data, 'base64') })); + this._channel.on('socksData', payload => this._handler.sendSocketData(payload)); } cleanup() { @@ -139,9 +142,15 @@ class SocksInterceptor { return true; } if (message.guid === this._socksSupportObjectGuid) { - this._channel.emit(message.method, message.params); + const validator = findValidator('SocksSupport', message.method, 'Event'); + const params = validator(message.params, '', { tChannelImpl: tChannelForSocks, binary: 'fromBase64' }); + this._channel.emit(message.method, params); return true; } return false; } } + +function tChannelForSocks(names: '*' | string[], arg: any, path: string, context: ValidatorContext) { + throw new ValidationError(`${path}: channels are not expected in SocksSupport`); +} diff --git a/packages/playwright-core/src/server/dispatchers/dispatcher.ts b/packages/playwright-core/src/server/dispatchers/dispatcher.ts index b77143eb65..6448df227b 100644 --- a/packages/playwright-core/src/server/dispatchers/dispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/dispatcher.ts @@ -144,16 +144,21 @@ export class DispatcherConnection { readonly _dispatchers = new Map>(); onmessage = (message: object) => {}; private _waitOperations = new Map(); + private _isLocal: boolean; + + constructor(isLocal?: boolean) { + this._isLocal = !!isLocal; + } sendEvent(dispatcher: Dispatcher, event: string, params: any, sdkObject?: SdkObject) { const validator = findValidator(dispatcher._type, event, 'Event'); - params = validator(params, '', { tChannelImpl: this._tChannelImplToWire.bind(this) }); + params = validator(params, '', { tChannelImpl: this._tChannelImplToWire.bind(this), binary: this._isLocal ? 'buffer' : 'toBase64' }); this._sendMessageToClient(dispatcher._guid, dispatcher._type, event, params, sdkObject); } sendCreate(parent: Dispatcher, type: string, guid: string, initializer: any, sdkObject?: SdkObject) { const validator = findValidator(type, '', 'Initializer'); - initializer = validator(initializer, '', { tChannelImpl: this._tChannelImplToWire.bind(this) }); + initializer = validator(initializer, '', { tChannelImpl: this._tChannelImplToWire.bind(this), binary: this._isLocal ? 'buffer' : 'toBase64' }); this._sendMessageToClient(parent._guid, type, '__create__', { type, initializer, guid }, sdkObject); } @@ -216,8 +221,8 @@ export class DispatcherConnection { let validMetadata: channels.Metadata; try { const validator = findValidator(dispatcher._type, method, 'Params'); - validParams = validator(params, '', { tChannelImpl: this._tChannelImplFromWire.bind(this) }); - validMetadata = metadataValidator(metadata, '', { tChannelImpl: this._tChannelImplFromWire.bind(this) }); + validParams = validator(params, '', { tChannelImpl: this._tChannelImplFromWire.bind(this), binary: this._isLocal ? 'buffer' : 'fromBase64' }); + validMetadata = metadataValidator(metadata, '', { tChannelImpl: this._tChannelImplFromWire.bind(this), binary: this._isLocal ? 'buffer' : 'fromBase64' }); if (typeof (dispatcher as any)[method] !== 'function') throw new Error(`Mismatching dispatcher: "${dispatcher._type}" does not implement "${method}"`); } catch (e) { @@ -277,7 +282,7 @@ export class DispatcherConnection { try { const result = await (dispatcher as any)[method](validParams, callMetadata); const validator = findValidator(dispatcher._type, method, 'Result'); - callMetadata.result = validator(result, '', { tChannelImpl: this._tChannelImplToWire.bind(this) }); + callMetadata.result = validator(result, '', { tChannelImpl: this._tChannelImplToWire.bind(this), binary: this._isLocal ? 'buffer' : 'toBase64' }); } catch (e) { // Dispatching error // We want original, unmodified error in metadata. diff --git a/packages/playwright-core/src/server/dispatchers/elementHandlerDispatcher.ts b/packages/playwright-core/src/server/dispatchers/elementHandlerDispatcher.ts index 82eab5341d..cc442b95c5 100644 --- a/packages/playwright-core/src/server/dispatchers/elementHandlerDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/elementHandlerDispatcher.ts @@ -191,7 +191,7 @@ export class ElementHandleDispatcher extends JSHandleDispatcher implements chann frame: (frame as FrameDispatcher)._object, selector, })); - return { binary: (await this._elementHandle.screenshot(metadata, { ...params, mask })).toString('base64') }; + return { binary: await this._elementHandle.screenshot(metadata, { ...params, mask }) }; } async querySelector(params: channels.ElementHandleQuerySelectorParams, metadata: CallMetadata): Promise { diff --git a/packages/playwright-core/src/server/dispatchers/localUtilsDispatcher.ts b/packages/playwright-core/src/server/dispatchers/localUtilsDispatcher.ts index 64d29e477f..2b0372810b 100644 --- a/packages/playwright-core/src/server/dispatchers/localUtilsDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/localUtilsDispatcher.ts @@ -114,7 +114,7 @@ export class LocalUtilsDispatcher extends Dispatcher<{ guid: string }, channels. const harBackend = this._harBakends.get(params.harId); if (!harBackend) return { action: 'error', message: `Internal error: har was not opened` }; - return await harBackend.lookup(params.url, params.method, params.headers, params.postData ? Buffer.from(params.postData, 'base64') : undefined, params.isNavigationRequest); + return await harBackend.lookup(params.url, params.method, params.headers, params.postData, params.isNavigationRequest); } async harClose(params: channels.LocalUtilsHarCloseParams, metadata?: channels.Metadata): Promise { @@ -160,8 +160,7 @@ class HarBackend { redirectURL?: string, status?: number, headers?: HeadersArray, - body?: string, - base64Encoded?: boolean }> { + body?: Buffer }> { let entry; try { entry = await this._harFindResponse(url, method, headers, postData); @@ -183,7 +182,7 @@ class HarBackend { action: 'fulfill', status: response.status, headers: response.headers, - body: buffer.toString('base64'), + body: buffer, }; } catch (e) { return { action: 'error', message: e.message }; diff --git a/packages/playwright-core/src/server/dispatchers/networkDispatchers.ts b/packages/playwright-core/src/server/dispatchers/networkDispatchers.ts index aafba3e0e9..d9ac336e57 100644 --- a/packages/playwright-core/src/server/dispatchers/networkDispatchers.ts +++ b/packages/playwright-core/src/server/dispatchers/networkDispatchers.ts @@ -45,7 +45,7 @@ export class RequestDispatcher extends Dispatcher { - return { binary: (await this._object.body()).toString('base64') }; + return { binary: await this._object.body() }; } async securityDetails(): Promise { @@ -128,7 +128,7 @@ export class RouteDispatcher extends Dispatcher im url: params.url, method: params.method, headers: params.headers, - postData: params.postData !== undefined ? Buffer.from(params.postData, 'base64') : undefined, + postData: params.postData, }); } @@ -204,8 +204,7 @@ export class APIRequestContextDispatcher extends Dispatcher { - const buffer = this._object.fetchResponses.get(params.fetchUid); - return { binary: buffer ? buffer.toString('base64') : undefined }; + return { binary: this._object.fetchResponses.get(params.fetchUid) }; } async fetchLog(params: channels.APIRequestContextFetchLogParams, metadata?: channels.Metadata): Promise { diff --git a/packages/playwright-core/src/server/dispatchers/pageDispatcher.ts b/packages/playwright-core/src/server/dispatchers/pageDispatcher.ts index c547b888f3..a7a1304032 100644 --- a/packages/playwright-core/src/server/dispatchers/pageDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/pageDispatcher.ts @@ -167,23 +167,14 @@ export class PageDispatcher extends Dispatcher imple frame: (params.locator.frame as FrameDispatcher)._object, selector: params.locator.selector, } : undefined; - const expected = params.expected ? Buffer.from(params.expected, 'base64') : undefined; - const result = await this._page.expectScreenshot(metadata, { + return await this._page.expectScreenshot(metadata, { ...params, - expected, locator, screenshotOptions: { ...params.screenshotOptions, mask, }, }); - return { - diff: result.diff?.toString('base64'), - errorMessage: result.errorMessage, - actual: result.actual?.toString('base64'), - previous: result.previous?.toString('base64'), - log: result.log, - }; } async screenshot(params: channels.PageScreenshotParams, metadata: CallMetadata): Promise { @@ -191,7 +182,7 @@ export class PageDispatcher extends Dispatcher imple frame: (frame as FrameDispatcher)._object, selector, })); - return { binary: (await this._page.screenshot(metadata, { ...params, mask })).toString('base64') }; + return { binary: await this._page.screenshot(metadata, { ...params, mask }) }; } async close(params: channels.PageCloseParams, metadata: CallMetadata): Promise { @@ -258,7 +249,7 @@ export class PageDispatcher extends Dispatcher imple if (!this._page.pdf) throw new Error('PDF generation is only supported for Headless Chromium'); const buffer = await this._page.pdf(params); - return { pdf: buffer.toString('base64') }; + return { pdf: buffer }; } async bringToFront(params: channels.PageBringToFrontParams, metadata: CallMetadata): Promise { diff --git a/packages/playwright-core/src/server/dispatchers/playwrightDispatcher.ts b/packages/playwright-core/src/server/dispatchers/playwrightDispatcher.ts index da505eadcc..84547ccb4a 100644 --- a/packages/playwright-core/src/server/dispatchers/playwrightDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/playwrightDispatcher.ts @@ -81,7 +81,7 @@ class SocksSupportDispatcher extends Dispatcher<{ guid: string }, channels.Socks this._type_SocksSupport = true; this._socksProxy = socksProxy; socksProxy.on(SocksProxy.Events.SocksRequested, (payload: SocksSocketRequestedPayload) => this._dispatchEvent('socksRequested', payload)); - socksProxy.on(SocksProxy.Events.SocksData, (payload: SocksSocketDataPayload) => this._dispatchEvent('socksData', { uid: payload.uid, data: payload.data.toString('base64') })); + socksProxy.on(SocksProxy.Events.SocksData, (payload: SocksSocketDataPayload) => this._dispatchEvent('socksData', payload)); socksProxy.on(SocksProxy.Events.SocksClosed, (payload: SocksSocketClosedPayload) => this._dispatchEvent('socksClosed', payload)); } @@ -94,7 +94,7 @@ class SocksSupportDispatcher extends Dispatcher<{ guid: string }, channels.Socks } async socksData(params: channels.SocksSupportSocksDataParams): Promise { - this._socksProxy?.sendSocketData({ uid: params.uid, data: Buffer.from(params.data, 'base64') }); + this._socksProxy?.sendSocketData(params); } async socksError(params: channels.SocksSupportSocksErrorParams): Promise { diff --git a/packages/playwright-core/src/server/dispatchers/streamDispatcher.ts b/packages/playwright-core/src/server/dispatchers/streamDispatcher.ts index 25eb9ff732..b186efd692 100644 --- a/packages/playwright-core/src/server/dispatchers/streamDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/streamDispatcher.ts @@ -33,7 +33,7 @@ export class StreamDispatcher extends Dispatcher<{ guid: string, stream: stream. async read(params: channels.StreamReadParams): Promise { const stream = this._object.stream; if (this._ended) - return { binary: '' }; + return { binary: Buffer.from('') }; if (!stream.readableLength) { await new Promise((fulfill, reject) => { stream.once('readable', fulfill); @@ -42,7 +42,7 @@ export class StreamDispatcher extends Dispatcher<{ guid: string, stream: stream. }); } const buffer = stream.read(Math.min(stream.readableLength, params.size || stream.readableLength)); - return { binary: buffer ? buffer.toString('base64') : '' }; + return { binary: buffer || Buffer.from('') }; } async close() { diff --git a/packages/playwright-core/src/server/dispatchers/writableStreamDispatcher.ts b/packages/playwright-core/src/server/dispatchers/writableStreamDispatcher.ts index 5569c13d27..d48b5fe0d8 100644 --- a/packages/playwright-core/src/server/dispatchers/writableStreamDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/writableStreamDispatcher.ts @@ -29,7 +29,7 @@ export class WritableStreamDispatcher extends Dispatcher<{ guid: string, stream: async write(params: channels.WritableStreamWriteParams): Promise { const stream = this._object.stream; await new Promise((fulfill, reject) => { - stream.write(Buffer.from(params.binary, 'base64'), error => { + stream.write(params.binary, error => { if (error) reject(error); else diff --git a/packages/playwright-core/src/server/dom.ts b/packages/playwright-core/src/server/dom.ts index b6ca9df065..1125f75e2a 100644 --- a/packages/playwright-core/src/server/dom.ts +++ b/packages/playwright-core/src/server/dom.ts @@ -615,10 +615,15 @@ export class ElementHandle extends js.JSHandle { 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) { - if (!payload.mimeType) - payload.mimeType = mime.getType(payload.name) || 'application/octet-stream'; + filePayloads.push({ + name: payload.name, + mimeType: payload.mimeType || mime.getType(payload.name) || 'application/octet-stream', + buffer: payload.buffer.toString('base64'), + }); } } const multiple = files && files.length > 1 || localPaths && localPaths.length > 1; @@ -641,7 +646,7 @@ export class ElementHandle extends js.JSHandle { if (localPaths) await this._page._delegate.setInputFilePaths(retargeted, localPaths); else - await this._page._delegate.setInputFiles(retargeted, files as types.FilePayload[]); + await this._page._delegate.setInputFiles(retargeted, filePayloads!); }); await this._page._doSlowMo(); return 'done'; diff --git a/packages/playwright-core/src/server/fetch.ts b/packages/playwright-core/src/server/fetch.ts index 66a88ea910..2974f9e802 100644 --- a/packages/playwright-core/src/server/fetch.ts +++ b/packages/playwright-core/src/server/fetch.ts @@ -643,7 +643,7 @@ function serializePostData(params: channels.APIRequestContextFetchParams, header return formData.finish(); } else if (params.postData !== undefined) { headers['content-type'] ??= 'application/octet-stream'; - return Buffer.from(params.postData, 'base64'); + return params.postData; } return undefined; } diff --git a/packages/playwright-core/src/server/formData.ts b/packages/playwright-core/src/server/formData.ts index 280e261f06..2905d920d0 100644 --- a/packages/playwright-core/src/server/formData.ts +++ b/packages/playwright-core/src/server/formData.ts @@ -41,7 +41,7 @@ export class MultipartFormData { this._chunks.push(Buffer.from(`; filename="${value.name}"`)); this._chunks.push(Buffer.from(`\r\ncontent-type: ${value.mimeType || mime.getType(value.name) || 'application/octet-stream'}`)); this._finishMultiPartHeader(); - this._chunks.push(Buffer.from(value.buffer, 'base64')); + this._chunks.push(value.buffer); this._finishMultiPartField(); } diff --git a/utils/generate_channels.js b/utils/generate_channels.js index 8124d65d0b..a691fe3a89 100755 --- a/utils/generate_channels.js +++ b/utils/generate_channels.js @@ -126,7 +126,7 @@ const channels_ts = [ // This file is generated by ${path.basename(__filename).split(path.sep).join(path.posix.sep)}, do not edit manually. -export type Binary = string; +export type Binary = Buffer; export interface Channel { }