Skip to content

Commit

Permalink
feat(types): make our client classes implement public types (#4817)
Browse files Browse the repository at this point in the history
This patch:
- introduces non-exported but used in api/impl struct types (e.g. Point);
- makes all client classes implement respective public api interface.

Pros:
- Typescript is now responsible for type checking.
  We can remove our doclint checker (not removed yet).
- Electron and Android types can be defined in the same way
  (this is not implemented yet).
- We can move most of the type structs like Point to the public api
  and make some of them available.

Cons:
- Any cons?
  • Loading branch information
dgozman committed Dec 27, 2020
1 parent dc25173 commit 34c1b33
Show file tree
Hide file tree
Showing 28 changed files with 196 additions and 210 deletions.
3 changes: 2 additions & 1 deletion src/client/accessibility.ts
Expand Up @@ -17,6 +17,7 @@

import * as channels from '../protocol/channels';
import { ElementHandle } from './elementHandle';
import * as api from '../../types/types';

type SerializedAXNode = Omit<channels.AXNode, 'valueString' | 'valueNumber' | 'children' | 'checked' | 'pressed'> & {
value?: string|number,
Expand All @@ -38,7 +39,7 @@ function axNodeFromProtocol(axNode: channels.AXNode): SerializedAXNode {
return result;
}

export class Accessibility {
export class Accessibility implements api.Accessibility {
private _channel: channels.PageChannel;

constructor(channel: channels.PageChannel) {
Expand Down
3 changes: 2 additions & 1 deletion src/client/browser.ts
Expand Up @@ -21,8 +21,9 @@ import { ChannelOwner } from './channelOwner';
import { Events } from './events';
import { BrowserContextOptions } from './types';
import { isSafeCloseError } from '../utils/errors';
import * as api from '../../types/types';

export class Browser extends ChannelOwner<channels.BrowserChannel, channels.BrowserInitializer> {
export class Browser extends ChannelOwner<channels.BrowserChannel, channels.BrowserInitializer> implements api.Browser {
readonly _contexts = new Set<BrowserContext>();
private _isConnected = true;
private _closedPromise: Promise<void>;
Expand Down
12 changes: 7 additions & 5 deletions src/client/browserContext.ts
Expand Up @@ -15,7 +15,7 @@
* limitations under the License.
*/

import { Page, BindingCall, FunctionWithSource } from './page';
import { Page, BindingCall } from './page';
import * as network from './network';
import * as channels from '../protocol/channels';
import * as util from 'util';
Expand All @@ -30,16 +30,18 @@ import { URLMatch, Headers, WaitForEventOptions, BrowserContextOptions, StorageS
import { isUnderTest, headersObjectToArray, mkdirIfNeeded } from '../utils/utils';
import { isSafeCloseError } from '../utils/errors';
import { serializeArgument } from './jsHandle';
import * as api from '../../types/types';
import * as structs from '../../types/structs';

const fsWriteFileAsync = util.promisify(fs.writeFile.bind(fs));
const fsReadFileAsync = util.promisify(fs.readFile.bind(fs));

export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel, channels.BrowserContextInitializer> {
export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel, channels.BrowserContextInitializer> implements api.BrowserContext {
_pages = new Set<Page>();
private _routes: { url: URLMatch, handler: network.RouteHandler }[] = [];
readonly _browser: Browser | null = null;
readonly _browserName: string;
readonly _bindings = new Map<string, FunctionWithSource>();
readonly _bindings = new Map<string, (source: structs.BindingSource, ...args: any[]) => any>();
_timeoutSettings = new TimeoutSettings();
_ownerPage: Page | undefined;
private _closedPromise: Promise<void>;
Expand Down Expand Up @@ -182,7 +184,7 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel,
});
}

async exposeBinding(name: string, playwrightBinding: FunctionWithSource, options: { handle?: boolean } = {}): Promise<void> {
async exposeBinding(name: string, playwrightBinding: (source: structs.BindingSource, ...args: any[]) => any, options: { handle?: boolean } = {}): Promise<void> {
return this._wrapApiCall('browserContext.exposeBinding', async () => {
await this._channel.exposeBinding({ name, needsHandle: options.handle });
this._bindings.set(name, playwrightBinding);
Expand All @@ -192,7 +194,7 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel,
async exposeFunction(name: string, playwrightFunction: Function): Promise<void> {
return this._wrapApiCall('browserContext.exposeFunction', async () => {
await this._channel.exposeBinding({ name });
const binding: FunctionWithSource = (source, ...args) => playwrightFunction(...args);
const binding = (source: structs.BindingSource, ...args: any[]) => playwrightFunction(...args);
this._bindings.set(name, binding);
});
}
Expand Down
10 changes: 6 additions & 4 deletions src/client/browserType.ts
Expand Up @@ -32,19 +32,21 @@ import { assert, makeWaitForNextTask, mkdirIfNeeded } from '../utils/utils';
import { SelectorsOwner, sharedSelectors } from './selectors';
import { kBrowserClosedError } from '../utils/errors';
import { Stream } from './stream';
import * as api from '../../types/types';

export interface BrowserServerLauncher {
launchServer(options?: LaunchServerOptions): Promise<BrowserServer>;
launchServer(options?: LaunchServerOptions): Promise<api.BrowserServer>;
}

export interface BrowserServer {
// This is here just for api generation and checking.
export interface BrowserServer extends api.BrowserServer {
process(): ChildProcess;
wsEndpoint(): string;
close(): Promise<void>;
kill(): Promise<void>;
}

export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel, channels.BrowserTypeInitializer> {
export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel, channels.BrowserTypeInitializer> implements api.BrowserType<api.Browser> {
private _timeoutSettings = new TimeoutSettings();
_serverLauncher?: BrowserServerLauncher;

Expand Down Expand Up @@ -83,7 +85,7 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel, chann
}, logger);
}

async launchServer(options: LaunchServerOptions = {}): Promise<BrowserServer> {
async launchServer(options: LaunchServerOptions = {}): Promise<api.BrowserServer> {
if (!this._serverLauncher)
throw new Error('Launching server is not supported');
return this._serverLauncher.launchServer(options);
Expand Down
3 changes: 2 additions & 1 deletion src/client/cdpSession.ts
Expand Up @@ -17,8 +17,9 @@
import * as channels from '../protocol/channels';
import { ChannelOwner } from './channelOwner';
import { Protocol } from '../server/chromium/protocol';
import * as api from '../../types/types';

export class CDPSession extends ChannelOwner<channels.CDPSessionChannel, channels.CDPSessionInitializer> {
export class CDPSession extends ChannelOwner<channels.CDPSessionChannel, channels.CDPSessionInitializer> implements api.CDPSession {
static from(cdpSession: channels.CDPSessionChannel): CDPSession {
return (cdpSession as any)._object;
}
Expand Down
13 changes: 12 additions & 1 deletion src/client/chromiumBrowser.ts
Expand Up @@ -17,8 +17,19 @@
import { Page } from './page';
import { CDPSession } from './cdpSession';
import { Browser } from './browser';
import * as api from '../../types/types';
import { ChromiumBrowserContext } from './chromiumBrowserContext';
import { BrowserContextOptions } from './types';

export class ChromiumBrowser extends Browser implements api.ChromiumBrowser {
contexts(): ChromiumBrowserContext[] {
return super.contexts() as ChromiumBrowserContext[];
}

newContext(options?: BrowserContextOptions): Promise<ChromiumBrowserContext> {
return super.newContext(options) as Promise<ChromiumBrowserContext>;
}

export class ChromiumBrowser extends Browser {
async newBrowserCDPSession(): Promise<CDPSession> {
return this._wrapApiCall('chromiumBrowser.newBrowserCDPSession', async () => {
return CDPSession.from((await this._channel.crNewBrowserCDPSession()).session);
Expand Down
3 changes: 2 additions & 1 deletion src/client/chromiumBrowserContext.ts
Expand Up @@ -22,8 +22,9 @@ import { CDPSession } from './cdpSession';
import { Events } from './events';
import { Worker } from './worker';
import { BrowserContext } from './browserContext';
import * as api from '../../types/types';

export class ChromiumBrowserContext extends BrowserContext {
export class ChromiumBrowserContext extends BrowserContext implements api.ChromiumBrowserContext {
_backgroundPages = new Set<Page>();
_serviceWorkers = new Set<Worker>();

Expand Down
3 changes: 2 additions & 1 deletion src/client/chromiumCoverage.ts
Expand Up @@ -15,8 +15,9 @@
*/

import * as channels from '../protocol/channels';
import * as api from '../../types/types';

export class ChromiumCoverage {
export class ChromiumCoverage implements api.ChromiumCoverage {
private _channel: channels.PageChannel;

constructor(channel: channels.PageChannel) {
Expand Down
3 changes: 2 additions & 1 deletion src/client/consoleMessage.ts
Expand Up @@ -18,10 +18,11 @@ import * as util from 'util';
import { JSHandle } from './jsHandle';
import * as channels from '../protocol/channels';
import { ChannelOwner } from './channelOwner';
import * as api from '../../types/types';

type ConsoleMessageLocation = channels.ConsoleMessageInitializer['location'];

export class ConsoleMessage extends ChannelOwner<channels.ConsoleMessageChannel, channels.ConsoleMessageInitializer> {
export class ConsoleMessage extends ChannelOwner<channels.ConsoleMessageChannel, channels.ConsoleMessageInitializer> implements api.ConsoleMessage {
static from(message: channels.ConsoleMessageChannel): ConsoleMessage {
return (message as any)._object;
}
Expand Down
3 changes: 2 additions & 1 deletion src/client/dialog.ts
Expand Up @@ -16,8 +16,9 @@

import * as channels from '../protocol/channels';
import { ChannelOwner } from './channelOwner';
import * as api from '../../types/types';

export class Dialog extends ChannelOwner<channels.DialogChannel, channels.DialogInitializer> {
export class Dialog extends ChannelOwner<channels.DialogChannel, channels.DialogInitializer> implements api.Dialog {
static from(dialog: channels.DialogChannel): Dialog {
return (dialog as any)._object;
}
Expand Down
3 changes: 2 additions & 1 deletion src/client/download.ts
Expand Up @@ -22,8 +22,9 @@ import { Browser } from './browser';
import { BrowserContext } from './browserContext';
import * as fs from 'fs';
import { mkdirIfNeeded } from '../utils/utils';
import * as api from '../../types/types';

export class Download extends ChannelOwner<channels.DownloadChannel, channels.DownloadInitializer> {
export class Download extends ChannelOwner<channels.DownloadChannel, channels.DownloadInitializer> implements api.Download {
private _browser: Browser | null;

static from(download: channels.DownloadChannel): Download {
Expand Down
13 changes: 5 additions & 8 deletions src/client/electron.ts
Expand Up @@ -18,12 +18,13 @@ import * as channels from '../protocol/channels';
import { BrowserContext } from './browserContext';
import { ChannelOwner } from './channelOwner';
import { Page } from './page';
import { serializeArgument, FuncOn, parseResult, SmartHandle, JSHandle } from './jsHandle';
import { serializeArgument, parseResult, JSHandle } from './jsHandle';
import { TimeoutSettings } from '../utils/timeoutSettings';
import { Waiter } from './waiter';
import { Events } from './events';
import { WaitForEventOptions, Env, Logger } from './types';
import { envObjectToArray } from './clientHelper';
import * as structs from '../../types/structs';

type ElectronOptions = Omit<channels.ElectronLaunchOptions, 'env'> & {
env?: Env,
Expand Down Expand Up @@ -110,17 +111,13 @@ export class ElectronApplication extends ChannelOwner<channels.ElectronApplicati
return result;
}

async evaluate<R, Arg>(pageFunction: FuncOn<any, Arg, R>, arg: Arg): Promise<R>;
async evaluate<R>(pageFunction: FuncOn<any, void, R>, arg?: any): Promise<R>;
async evaluate<R, Arg>(pageFunction: FuncOn<any, Arg, R>, arg: Arg): Promise<R> {
async evaluate<R, Arg>(pageFunction: structs.PageFunctionOn<any, Arg, R>, arg: Arg): Promise<R> {
const result = await this._channel.evaluateExpression({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) });
return parseResult(result.value);
}

async evaluateHandle<R, Arg>(pageFunction: FuncOn<any, Arg, R>, arg: Arg): Promise<SmartHandle<R>>;
async evaluateHandle<R>(pageFunction: FuncOn<any, void, R>, arg?: any): Promise<SmartHandle<R>>;
async evaluateHandle<R, Arg>(pageFunction: FuncOn<any, Arg, R>, arg: Arg): Promise<SmartHandle<R>> {
async evaluateHandle<R, Arg>(pageFunction: structs.PageFunctionOn<any, Arg, R>, arg: Arg): Promise<structs.SmartHandle<R>> {
const result = await this._channel.evaluateExpressionHandle({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) });
return JSHandle.from(result.handle) as SmartHandle<R>;
return JSHandle.from(result.handle) as any as structs.SmartHandle<R>;
}
}
36 changes: 18 additions & 18 deletions src/client/elementHandle.ts
Expand Up @@ -16,18 +16,20 @@

import * as channels from '../protocol/channels';
import { Frame } from './frame';
import { FuncOn, JSHandle, serializeArgument, parseResult } from './jsHandle';
import { JSHandle, serializeArgument, parseResult } from './jsHandle';
import { ChannelOwner } from './channelOwner';
import { SelectOption, FilePayload, Rect, SelectOptionOptions } from './types';
import * as fs from 'fs';
import * as mime from 'mime';
import * as path from 'path';
import * as util from 'util';
import { assert, isString, mkdirIfNeeded } from '../utils/utils';
import * as api from '../../types/types';
import * as structs from '../../types/structs';

const fsWriteFileAsync = util.promisify(fs.writeFile.bind(fs));

export class ElementHandle<T extends Node = Node> extends JSHandle<T> {
export class ElementHandle<T extends Node = Node> extends JSHandle<T> implements api.ElementHandle {
readonly _elementChannel: channels.ElementHandleChannel;

static from(handle: channels.ElementHandleChannel): ElementHandle {
Expand All @@ -43,8 +45,8 @@ export class ElementHandle<T extends Node = Node> extends JSHandle<T> {
this._elementChannel = this._channel as channels.ElementHandleChannel;
}

asElement(): ElementHandle<T> | null {
return this;
asElement(): T extends Node ? ElementHandle<T> : null {
return this as any;
}

async ownerFrame(): Promise<Frame | null> {
Expand Down Expand Up @@ -121,7 +123,7 @@ export class ElementHandle<T extends Node = Node> extends JSHandle<T> {
});
}

async selectOption(values: string | ElementHandle | SelectOption | string[] | ElementHandle[] | SelectOption[] | null, options: SelectOptionOptions = {}): Promise<string[]> {
async selectOption(values: string | api.ElementHandle | SelectOption | string[] | api.ElementHandle[] | SelectOption[] | null, options: SelectOptionOptions = {}): Promise<string[]> {
return this._wrapApiCall('elementHandle.selectOption', async () => {
const result = await this._elementChannel.selectOption({ ...convertSelectOptionValues(values), ...options });
return result.values;
Expand Down Expand Up @@ -198,31 +200,27 @@ export class ElementHandle<T extends Node = Node> extends JSHandle<T> {
});
}

async $(selector: string): Promise<ElementHandle<Element> | null> {
async $(selector: string): Promise<ElementHandle<SVGElement | HTMLElement> | null> {
return this._wrapApiCall('elementHandle.$', async () => {
return ElementHandle.fromNullable((await this._elementChannel.querySelector({ selector })).element) as ElementHandle<Element> | null;
return ElementHandle.fromNullable((await this._elementChannel.querySelector({ selector })).element) as ElementHandle<SVGElement | HTMLElement> | null;
});
}

async $$(selector: string): Promise<ElementHandle<Element>[]> {
async $$(selector: string): Promise<ElementHandle<SVGElement | HTMLElement>[]> {
return this._wrapApiCall('elementHandle.$$', async () => {
const result = await this._elementChannel.querySelectorAll({ selector });
return result.elements.map(h => ElementHandle.from(h) as ElementHandle<Element>);
return result.elements.map(h => ElementHandle.from(h) as ElementHandle<SVGElement | HTMLElement>);
});
}

async $eval<R, Arg>(selector: string, pageFunction: FuncOn<Element, Arg, R>, arg: Arg): Promise<R>;
async $eval<R>(selector: string, pageFunction: FuncOn<Element, void, R>, arg?: any): Promise<R>;
async $eval<R, Arg>(selector: string, pageFunction: FuncOn<Element, Arg, R>, arg: Arg): Promise<R> {
async $eval<R, Arg>(selector: string, pageFunction: structs.PageFunctionOn<Element, Arg, R>, arg?: Arg): Promise<R> {
return this._wrapApiCall('elementHandle.$eval', async () => {
const result = await this._elementChannel.evalOnSelector({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) });
return parseResult(result.value);
});
}

async $$eval<R, Arg>(selector: string, pageFunction: FuncOn<Element[], Arg, R>, arg: Arg): Promise<R>;
async $$eval<R>(selector: string, pageFunction: FuncOn<Element[], void, R>, arg?: any): Promise<R>;
async $$eval<R, Arg>(selector: string, pageFunction: FuncOn<Element[], Arg, R>, arg: Arg): Promise<R> {
async $$eval<R, Arg>(selector: string, pageFunction: structs.PageFunctionOn<Element[], Arg, R>, arg?: Arg): Promise<R> {
return this._wrapApiCall('elementHandle.$$eval', async () => {
const result = await this._elementChannel.evalOnSelectorAll({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) });
return parseResult(result.value);
Expand All @@ -235,15 +233,17 @@ export class ElementHandle<T extends Node = Node> extends JSHandle<T> {
});
}

async waitForSelector(selector: string, options: channels.ElementHandleWaitForSelectorOptions = {}): Promise<ElementHandle<Element> | null> {
waitForSelector(selector: string, options: channels.ElementHandleWaitForSelectorOptions & { state: 'attached' | 'visible' }): Promise<ElementHandle<SVGElement | HTMLElement>>;
waitForSelector(selector: string, options?: channels.ElementHandleWaitForSelectorOptions): Promise<ElementHandle<SVGElement | HTMLElement> | null>;
async waitForSelector(selector: string, options: channels.ElementHandleWaitForSelectorOptions = {}): Promise<ElementHandle<SVGElement | HTMLElement> | null> {
return this._wrapApiCall('elementHandle.waitForSelector', async () => {
const result = await this._elementChannel.waitForSelector({ selector, ...options });
return ElementHandle.fromNullable(result.element) as ElementHandle<Element> | null;
return ElementHandle.fromNullable(result.element) as ElementHandle<SVGElement | HTMLElement> | null;
});
}
}

export function convertSelectOptionValues(values: string | ElementHandle | SelectOption | string[] | ElementHandle[] | SelectOption[] | null): { elements?: channels.ElementHandleChannel[], options?: SelectOption[] } {
export function convertSelectOptionValues(values: string | api.ElementHandle | SelectOption | string[] | api.ElementHandle[] | SelectOption[] | null): { elements?: channels.ElementHandleChannel[], options?: SelectOption[] } {
if (values === null)
return {};
if (!Array.isArray(values))
Expand Down
3 changes: 2 additions & 1 deletion src/client/fileChooser.ts
Expand Up @@ -18,8 +18,9 @@ import { ElementHandle } from './elementHandle';
import { Page } from './page';
import { FilePayload } from './types';
import * as channels from '../protocol/channels';
import * as api from '../../types/types';

export class FileChooser {
export class FileChooser implements api.FileChooser {
private _page: Page;
private _elementHandle: ElementHandle<Node>;
private _isMultiple: boolean;
Expand Down
3 changes: 2 additions & 1 deletion src/client/firefoxBrowser.ts
Expand Up @@ -15,6 +15,7 @@
*/

import { Browser } from './browser';
import * as api from '../../types/types';

export class FirefoxBrowser extends Browser {
export class FirefoxBrowser extends Browser implements api.FirefoxBrowser {
}

0 comments on commit 34c1b33

Please sign in to comment.