From 631f76df75da7a336d4b571d1697aaf46d3e221b Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Fri, 10 Jul 2020 15:39:11 -0700 Subject: [PATCH] chore(rpc): remove union types from page and handles (#2912) --- src/rpc/channels.ts | 27 ++++++++++++++---- src/rpc/client/elementHandle.ts | 32 ++++++++++++++++------ src/rpc/client/frame.ts | 8 ++---- src/rpc/client/page.ts | 16 +++++++++-- src/rpc/server/elementHandlerDispatcher.ts | 24 +++++++++------- src/rpc/server/frameDispatcher.ts | 8 +++--- src/rpc/server/pageDispatcher.ts | 4 +-- test/page.spec.js | 2 +- 8 files changed, 84 insertions(+), 37 deletions(-) diff --git a/src/rpc/channels.ts b/src/rpc/channels.ts index d9e94d8947..7ba3511a0c 100644 --- a/src/rpc/channels.ts +++ b/src/rpc/channels.ts @@ -157,7 +157,7 @@ export interface PageChannel extends Channel { mouseClick(params: { x: number, y: number, delay?: number, button?: types.MouseButton, clickCount?: number }): Promise; accessibilitySnapshot(params: { interestingOnly?: boolean, root?: ElementHandleChannel }): Promise; - pdf: (params: types.PDFOptions) => Promise; + pdf: (params: PDFOptions) => Promise; crStartJSCoverage(params: types.JSCoverageOptions): Promise; crStopJSCoverage(): Promise; @@ -196,9 +196,9 @@ export interface FrameChannel extends Channel { press(params: { selector: string, key: string, delay?: number, noWaitAfter?: boolean } & types.TimeoutOptions & PageAttribution): Promise; querySelector(params: { selector: string} & PageAttribution): Promise; querySelectorAll(params: { selector: string} & PageAttribution): Promise; - selectOption(params: { selector: string, values: string | ElementHandleChannel | types.SelectOption | string[] | ElementHandleChannel[] | types.SelectOption[] | null } & types.NavigatingActionWaitOptions & PageAttribution): Promise; + selectOption(params: { selector: string, elements?: ElementHandleChannel[], options?: types.SelectOption[] } & types.NavigatingActionWaitOptions & PageAttribution): Promise; setContent(params: { html: string } & types.NavigateOptions & PageAttribution): Promise; - setInputFiles(params: { selector: string, files: { name: string, mimeType: string, buffer: string }[] } & types.NavigatingActionWaitOptions & PageAttribution): Promise; + setInputFiles(params: { selector: string, files: { name: string, mimeType: string, buffer: Binary }[] } & types.NavigatingActionWaitOptions & PageAttribution): Promise; textContent(params: { selector: string } & types.TimeoutOptions & PageAttribution): Promise; title(): Promise; type(params: { selector: string, text: string, delay?: number, noWaitAfter?: boolean } & types.TimeoutOptions & PageAttribution): Promise; @@ -260,9 +260,9 @@ export interface ElementHandleChannel extends JSHandleChannel { querySelectorAll(params: { selector: string }): Promise; screenshot(params: types.ElementScreenshotOptions): Promise; scrollIntoViewIfNeeded(params: types.TimeoutOptions): Promise; - selectOption(params: { values: string | ElementHandleChannel | types.SelectOption | string[] | ElementHandleChannel[] | types.SelectOption[] | null } & types.NavigatingActionWaitOptions): string[] | Promise; + selectOption(params: { elements?: ElementHandleChannel[], options?: types.SelectOption[] } & types.NavigatingActionWaitOptions): string[] | Promise; selectText(params: types.TimeoutOptions): Promise; - setInputFiles(params: { files: string | string[] | types.FilePayload | types.FilePayload[] } & types.NavigatingActionWaitOptions): Promise; + setInputFiles(params: { files: { name: string, mimeType: string, buffer: Binary }[] } & types.NavigatingActionWaitOptions): Promise; textContent(): Promise; type(params: { text: string, delay?: number, noWaitAfter?: boolean } & types.TimeoutOptions): Promise; uncheck(params: { force?: boolean, noWaitAfter?: boolean } & types.TimeoutOptions): Promise; @@ -364,3 +364,20 @@ export interface CDPSessionChannel extends Channel { detach(): Promise; } export type CDPSessionInitializer = {}; + + +export type PDFOptions = { + scale?: number, + displayHeaderFooter?: boolean, + headerTemplate?: string, + footerTemplate?: string, + printBackground?: boolean, + landscape?: boolean, + pageRanges?: string, + format?: string, + width?: string, + height?: string, + preferCSSPageSize?: boolean, + margin?: {top?: string, bottom?: string, left?: string, right?: string}, + path?: string, +} diff --git a/src/rpc/client/elementHandle.ts b/src/rpc/client/elementHandle.ts index 7b864c04c5..4dabff91ac 100644 --- a/src/rpc/client/elementHandle.ts +++ b/src/rpc/client/elementHandle.ts @@ -19,6 +19,8 @@ import { ElementHandleChannel, JSHandleInitializer } from '../channels'; import { Frame } from './frame'; import { FuncOn, JSHandle, serializeArgument, parseResult } from './jsHandle'; import { ChannelOwner } from './channelOwner'; +import { helper, assert } from '../../helper'; +import { normalizeFilePayloads } from '../serializers'; export class ElementHandle extends JSHandle { readonly _elementChannel: ElementHandleChannel; @@ -85,7 +87,7 @@ export class ElementHandle extends JSHandle { } async selectOption(values: string | ElementHandle | types.SelectOption | string[] | ElementHandle[] | types.SelectOption[] | null, options: types.NavigatingActionWaitOptions = {}): Promise { - return await this._elementChannel.selectOption({ values: convertSelectOptionValues(values), ...options }); + return await this._elementChannel.selectOption({ ...convertSelectOptionValues(values), ...options }); } async fill(value: string, options: types.NavigatingActionWaitOptions = {}): Promise { @@ -97,7 +99,7 @@ export class ElementHandle extends JSHandle { } async setInputFiles(files: string | types.FilePayload | string[] | types.FilePayload[], options: types.NavigatingActionWaitOptions = {}) { - await this._elementChannel.setInputFiles({ files, ...options }); + await this._elementChannel.setInputFiles({ files: await convertInputFiles(files), ...options }); } async focus(): Promise { @@ -149,10 +151,24 @@ export class ElementHandle extends JSHandle { } } -export function convertSelectOptionValues(values: string | ElementHandle | types.SelectOption | string[] | ElementHandle[] | types.SelectOption[] | null): string | ElementHandleChannel | types.SelectOption | string[] | ElementHandleChannel[] | types.SelectOption[] | null { - if (values instanceof ElementHandle) - return values._elementChannel; - if (Array.isArray(values) && values.length && values[0] instanceof ElementHandle) - return (values as ElementHandle[]).map((v: ElementHandle) => v._elementChannel); - return values as any; +export function convertSelectOptionValues(values: string | ElementHandle | types.SelectOption | string[] | ElementHandle[] | types.SelectOption[] | null): { elements?: ElementHandleChannel[], options?: types.SelectOption[] } { + if (!values) + return {}; + if (!Array.isArray(values)) + values = [ values as any ]; + if (!values.length) + return {}; + if ((values as any[]).includes(null)) + assert(false, 'Value items must not be null'); + + if (values[0] instanceof ElementHandle) + return { elements: (values as ElementHandle[]).map((v: ElementHandle) => v._elementChannel) }; + if (helper.isString(values[0])) + return { options: (values as string[]).map(value => ({ value })) }; + return { options: values as types.SelectOption[] }; +} + +export async function convertInputFiles(files: string | types.FilePayload | string[] | types.FilePayload[]): Promise<{ name: string, mimeType: string, buffer: string }[]> { + const filePayloads = await normalizeFilePayloads(files); + return filePayloads.map(f => ({ name: f.name, mimeType: f.mimeType, buffer: f.buffer.toString('base64') })); } diff --git a/src/rpc/client/frame.ts b/src/rpc/client/frame.ts index e78cb0b097..189ce1e269 100644 --- a/src/rpc/client/frame.ts +++ b/src/rpc/client/frame.ts @@ -20,12 +20,11 @@ import * as types from '../../types'; import { FrameChannel, FrameInitializer } from '../channels'; import { BrowserContext } from './browserContext'; import { ChannelOwner } from './channelOwner'; -import { ElementHandle, convertSelectOptionValues } from './elementHandle'; +import { ElementHandle, convertSelectOptionValues, convertInputFiles } from './elementHandle'; import { JSHandle, Func1, FuncOn, SmartHandle, serializeArgument, parseResult } from './jsHandle'; import * as network from './network'; import { Response } from './network'; import { Page } from './page'; -import { normalizeFilePayloads } from '../serializers'; export type GotoOptions = types.NavigateOptions & { referer?: string, @@ -192,12 +191,11 @@ export class Frame extends ChannelOwner { } async selectOption(selector: string, values: string | ElementHandle | types.SelectOption | string[] | ElementHandle[] | types.SelectOption[] | null, options: types.NavigatingActionWaitOptions = {}): Promise { - return await this._channel.selectOption({ selector, values: convertSelectOptionValues(values), ...options, isPage: this._page!._isPageCall }); + return await this._channel.selectOption({ selector, ...convertSelectOptionValues(values), ...options, isPage: this._page!._isPageCall }); } async setInputFiles(selector: string, files: string | types.FilePayload | string[] | types.FilePayload[], options: types.NavigatingActionWaitOptions = {}): Promise { - const filePayloads = await normalizeFilePayloads(files); - await this._channel.setInputFiles({ selector, files: filePayloads.map(f => ({ name: f.name, mimeType: f.mimeType, buffer: f.buffer.toString('base64') })), ...options, isPage: this._page!._isPageCall }); + await this._channel.setInputFiles({ selector, files: await convertInputFiles(files), ...options, isPage: this._page!._isPageCall }); } async type(selector: string, text: string, options: { delay?: number } & types.NavigatingActionWaitOptions = {}) { diff --git a/src/rpc/client/page.ts b/src/rpc/client/page.ts index 81dcc08588..010cbea275 100644 --- a/src/rpc/client/page.ts +++ b/src/rpc/client/page.ts @@ -21,7 +21,7 @@ import { Events } from '../../events'; import { assert, assertMaxArguments, helper, Listener } from '../../helper'; import { TimeoutSettings } from '../../timeoutSettings'; import * as types from '../../types'; -import { BindingCallChannel, BindingCallInitializer, PageChannel, PageInitializer } from '../channels'; +import { BindingCallChannel, BindingCallInitializer, PageChannel, PageInitializer, PDFOptions } from '../channels'; import { parseError, serializeError } from '../serializers'; import { Accessibility } from './accessibility'; import { BrowserContext } from './browserContext'; @@ -500,7 +500,19 @@ export class Page extends ChannelOwner { } async pdf(options: types.PDFOptions = {}): Promise { - const binary = await this._channel.pdf(options); + const transportOptions: PDFOptions = { ...options } as PDFOptions; + if (transportOptions.margin) + transportOptions.margin = { ...transportOptions.margin }; + if (typeof options.width === 'number') + transportOptions.width = options.width + 'px'; + if (typeof options.height === 'number') + transportOptions.height = options.height + 'px'; + for (const margin of ['top', 'right', 'bottom', 'left']) { + const index = margin as 'top' | 'right' | 'bottom' | 'left'; + if (options.margin && typeof options.margin[index] === 'number') + transportOptions.margin![index] = transportOptions.margin![index] + 'px'; + } + const binary = await this._channel.pdf(transportOptions); return Buffer.from(binary, 'base64'); } } diff --git a/src/rpc/server/elementHandlerDispatcher.ts b/src/rpc/server/elementHandlerDispatcher.ts index 0a09ec59e0..16e82f781e 100644 --- a/src/rpc/server/elementHandlerDispatcher.ts +++ b/src/rpc/server/elementHandlerDispatcher.ts @@ -85,8 +85,8 @@ export class ElementHandleDispatcher extends JSHandleDispatcher implements Eleme await this._elementHandle.dblclick(params); } - async selectOption(params: { values: string | ElementHandleChannel | types.SelectOption | string[] | ElementHandleChannel[] | types.SelectOption[] | null } & types.NavigatingActionWaitOptions): Promise { - return this._elementHandle.selectOption(convertSelectOptionValues(params.values), params); + async selectOption(params: { elements?: ElementHandleChannel[], options?: types.SelectOption[] } & types.NavigatingActionWaitOptions): Promise { + return this._elementHandle.selectOption(convertSelectOptionValues(params.elements, params.options), params); } async fill(params: { value: string } & types.NavigatingActionWaitOptions) { @@ -97,8 +97,8 @@ export class ElementHandleDispatcher extends JSHandleDispatcher implements Eleme await this._elementHandle.selectText(params); } - async setInputFiles(params: { files: string | types.FilePayload | string[] | types.FilePayload[] } & types.NavigatingActionWaitOptions) { - await this._elementHandle.setInputFiles(params.files, params); + async setInputFiles(params: { files: { name: string, mimeType: string, buffer: string }[] } & types.NavigatingActionWaitOptions) { + await this._elementHandle.setInputFiles(convertInputFiles(params.files), params); } async focus() { @@ -148,10 +148,14 @@ export class ElementHandleDispatcher extends JSHandleDispatcher implements Eleme } } -export function convertSelectOptionValues(values: string | ElementHandleChannel | types.SelectOption | string[] | ElementHandleChannel[] | types.SelectOption[] | null): string | ElementHandle | types.SelectOption | string[] | ElementHandle[] | types.SelectOption[] | null { - if (values instanceof ElementHandleDispatcher) - return values._elementHandle; - if (Array.isArray(values) && values.length && values[0] instanceof ElementHandle) - return (values as ElementHandleDispatcher[]).map((v: ElementHandleDispatcher) => v._elementHandle); - return values as any; +export function convertSelectOptionValues(elements?: ElementHandleChannel[], options?: types.SelectOption[]): string | ElementHandle | types.SelectOption | string[] | ElementHandle[] | types.SelectOption[] | null { + if (elements) + return elements.map(v => (v as ElementHandleDispatcher)._elementHandle); + if (options) + return options; + return null; +} + +export function convertInputFiles(files: { name: string, mimeType: string, buffer: string }[]): types.FilePayload[] { + return files.map(f => ({ name: f.name, mimeType: f.mimeType, buffer: Buffer.from(f.buffer, 'base64') })); } diff --git a/src/rpc/server/frameDispatcher.ts b/src/rpc/server/frameDispatcher.ts index 4244d9c896..d8b7b697b8 100644 --- a/src/rpc/server/frameDispatcher.ts +++ b/src/rpc/server/frameDispatcher.ts @@ -18,7 +18,7 @@ import { Frame } from '../../frames'; import * as types from '../../types'; import { ElementHandleChannel, FrameChannel, FrameInitializer, JSHandleChannel, ResponseChannel, PageAttribution } from '../channels'; import { Dispatcher, DispatcherScope, lookupNullableDispatcher, existingDispatcher } from './dispatcher'; -import { convertSelectOptionValues, ElementHandleDispatcher, createHandle } from './elementHandlerDispatcher'; +import { convertSelectOptionValues, ElementHandleDispatcher, createHandle, convertInputFiles } from './elementHandlerDispatcher'; import { parseArgument, serializeResult } from './jsHandleDispatcher'; import { ResponseDispatcher } from './networkDispatchers'; @@ -163,14 +163,14 @@ export class FrameDispatcher extends Dispatcher impleme await target.hover(params.selector, params); } - async selectOption(params: { selector: string, values: string | ElementHandleChannel | types.SelectOption | string[] | ElementHandleChannel[] | types.SelectOption[] | null } & types.NavigatingActionWaitOptions & PageAttribution): Promise { + async selectOption(params: { selector: string, elements?: ElementHandleChannel[], options?: types.SelectOption[] } & types.NavigatingActionWaitOptions & PageAttribution): Promise { const target = params.isPage ? this._frame._page : this._frame; - return target.selectOption(params.selector, convertSelectOptionValues(params.values), params); + return target.selectOption(params.selector, convertSelectOptionValues(params.elements, params.options), params); } async setInputFiles(params: { selector: string, files: { name: string, mimeType: string, buffer: string }[] } & types.NavigatingActionWaitOptions & PageAttribution): Promise { const target = params.isPage ? this._frame._page : this._frame; - await target.setInputFiles(params.selector, params.files.map(f => ({ name: f.name, mimeType: f.mimeType, buffer: Buffer.from(f.buffer, 'base64') })), params); + await target.setInputFiles(params.selector, convertInputFiles(params.files), params); } async type(params: { selector: string, text: string } & { delay?: number | undefined } & types.TimeoutOptions & { noWaitAfter?: boolean } & PageAttribution): Promise { diff --git a/src/rpc/server/pageDispatcher.ts b/src/rpc/server/pageDispatcher.ts index 61fde182b4..a769631980 100644 --- a/src/rpc/server/pageDispatcher.ts +++ b/src/rpc/server/pageDispatcher.ts @@ -20,7 +20,7 @@ import { Frame } from '../../frames'; import { Request } from '../../network'; import { Page, Worker } from '../../page'; import * as types from '../../types'; -import { BindingCallChannel, BindingCallInitializer, ElementHandleChannel, PageChannel, PageInitializer, ResponseChannel, WorkerInitializer, WorkerChannel, JSHandleChannel, Binary } from '../channels'; +import { BindingCallChannel, BindingCallInitializer, ElementHandleChannel, PageChannel, PageInitializer, ResponseChannel, WorkerInitializer, WorkerChannel, JSHandleChannel, Binary, PDFOptions } from '../channels'; import { Dispatcher, DispatcherScope, lookupDispatcher, lookupNullableDispatcher } from './dispatcher'; import { parseError, serializeError } from '../serializers'; import { ConsoleMessageDispatcher } from './consoleMessageDispatcher'; @@ -185,7 +185,7 @@ export class PageDispatcher extends Dispatcher implements }); } - async pdf(params: types.PDFOptions): Promise { + async pdf(params: PDFOptions): Promise { if (!this._page.pdf) throw new Error('PDF generation is only supported for Headless Chromium'); const binary = await this._page.pdf(params); diff --git a/test/page.spec.js b/test/page.spec.js index 6657811a66..2b90687df1 100644 --- a/test/page.spec.js +++ b/test/page.spec.js @@ -946,7 +946,7 @@ describe('Page.selectOption', function() { it('should select multiple options with attributes', async({page, server}) => { await page.goto(server.PREFIX + '/input/select.html'); await page.evaluate(() => makeMultiple()); - await page.selectOption('select', ['blue', { label: 'Green' }, { index: 4 }]); + await page.selectOption('select', [{ value: 'blue' }, { label: 'Green' }, { index: 4 }]); expect(await page.evaluate(() => result.onInput)).toEqual(['blue', 'gray', 'green']); expect(await page.evaluate(() => result.onChange)).toEqual(['blue', 'gray', 'green']); });