feat(types): simplify android and electron types (#4829)

These now follow the scheme for regular types.
This commit is contained in:
Dmitry Gozman 2020-12-26 20:25:18 -08:00 committed by GitHub
parent 34c1b338be
commit 905f28c339
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 85 additions and 87 deletions

View File

@ -8,7 +8,6 @@ src/server/firefox/protocol.ts
src/server/webkit/protocol.ts
/types/*
/index.d.ts
/electron-types.d.ts
utils/generate_types/overrides.d.ts
utils/generate_types/test/test.ts
node_modules/

28
android-types.d.ts vendored
View File

@ -1,28 +0,0 @@
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Page, BrowserContext, BrowserContextOptions } from './types/types';
import * as apiInternal from './android-types-internal';
import { EventEmitter } from 'events';
export { AndroidElementInfo, AndroidSelector } from './android-types-internal';
export type AndroidDevice = apiInternal.AndroidDevice<BrowserContextOptions, BrowserContext, Page>;
export type AndroidWebView = apiInternal.AndroidWebView<Page>;
export interface Android extends EventEmitter {
setDefaultTimeout(timeout: number): void;
devices(): Promise<AndroidDevice[]>;
}

View File

@ -62,13 +62,13 @@ const PACKAGES = {
version: '0.4.0', // Manually manage playwright-electron version.
description: 'A high-level API to automate Electron',
browsers: [],
files: [...PLAYWRIGHT_CORE_FILES, ...FFMPEG_FILES, 'electron-types.d.ts'],
files: [...PLAYWRIGHT_CORE_FILES, ...FFMPEG_FILES],
},
'playwright-android': {
version: '0.0.8', // Manually manage playwright-android version.
description: 'A high-level API to automate Chrome for Android',
browsers: [],
files: [...PLAYWRIGHT_CORE_FILES, ...FFMPEG_FILES, 'android-types.d.ts', 'android-types-internal.d.ts', 'bin/android-driver.apk', 'bin/android-driver-target.apk'],
files: [...PLAYWRIGHT_CORE_FILES, ...FFMPEG_FILES, 'bin/android-driver.apk', 'bin/android-driver-target.apk'],
},
};

View File

@ -18,11 +18,6 @@ lib/server/injected/
# Include generated types and entrypoint.
!types/*
!index.d.ts
# Include separate android types.
!android-types.d.ts
!android-types-internal.d.ts
# Include separate electron types.
!electron-types.d.ts
# Include main entrypoint.
!index.js
# Include main entrypoint for ES Modules.

View File

@ -22,6 +22,7 @@ PLAYWRIGHT_CHROMIUM_TGZ="$(node ${PACKAGE_BUILDER} playwright-chromium ./playwri
PLAYWRIGHT_WEBKIT_TGZ="$(node ${PACKAGE_BUILDER} playwright-webkit ./playwright-webkit.tgz)"
PLAYWRIGHT_FIREFOX_TGZ="$(node ${PACKAGE_BUILDER} playwright-firefox ./playwright-firefox.tgz)"
PLAYWRIGHT_ELECTRON_TGZ="$(node ${PACKAGE_BUILDER} playwright-electron ./playwright-electron.tgz)"
PLAYWRIGHT_ANDROID_TGZ="$(node ${PACKAGE_BUILDER} playwright-android ./playwright-android.tgz)"
SCRIPTS_PATH="$(pwd -P)/.."
TEST_ROOT="$(pwd -P)"
@ -52,6 +53,7 @@ function run_tests {
test_playwright_global_installation_cross_package
test_playwright_electron_should_work
test_electron_types
test_android_types
test_playwright_cli_should_work
test_playwright_cli_install_should_work
}
@ -316,6 +318,20 @@ function test_electron_types {
echo "${FUNCNAME[0]} success"
}
function test_android_types {
initialize_test "${FUNCNAME[0]}"
npm install ${PLAYWRIGHT_ANDROID_TGZ}
npm install -D typescript@3.8
npm install -D @types/node@10.17
echo "import { AndroidDevice, android, AndroidWebView, Page } from 'playwright-android';" > "test.ts"
echo "Running tsc"
npx tsc "test.ts"
echo "${FUNCNAME[0]} success"
}
function test_playwright_cli_should_work {
initialize_test "${FUNCNAME[0]}"

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
import { Android } from './android-types';
import { Android } from './types/android';
export * from './types/types';
export * from './android-types';
export * from './types/android';
export const android: Android;

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
import { ElectronLauncher } from './electron-types';
import { ElectronLauncher } from './types/electron';
export * from './types/types';
export * from './electron-types';
export * from './types/electron';
export const electron: ElectronLauncher;

View File

@ -21,7 +21,7 @@ import * as channels from '../protocol/channels';
import { Events } from './events';
import { BrowserContext, prepareBrowserContextOptions } from './browserContext';
import { ChannelOwner } from './channelOwner';
import * as apiInternal from '../../android-types-internal';
import * as androidApi from '../../types/android';
import * as types from './types';
import { Page } from './page';
import { TimeoutSettings } from '../utils/timeoutSettings';
@ -31,7 +31,7 @@ import { EventEmitter } from 'events';
type Direction = 'down' | 'up' | 'left' | 'right';
type SpeedOptions = { speed?: number };
export class Android extends ChannelOwner<channels.AndroidChannel, channels.AndroidInitializer> {
export class Android extends ChannelOwner<channels.AndroidChannel, channels.AndroidInitializer> implements androidApi.Android {
readonly _timeoutSettings: TimeoutSettings;
static from(android: channels.AndroidChannel): Android {
@ -56,7 +56,7 @@ export class Android extends ChannelOwner<channels.AndroidChannel, channels.Andr
}
}
export class AndroidDevice extends ChannelOwner<channels.AndroidDeviceChannel, channels.AndroidDeviceInitializer> implements apiInternal.AndroidDevice<types.BrowserContextOptions, BrowserContext, Page> {
export class AndroidDevice extends ChannelOwner<channels.AndroidDeviceChannel, channels.AndroidDeviceInitializer> implements androidApi.AndroidDevice {
readonly _timeoutSettings: TimeoutSettings;
private _webViews = new Map<number, AndroidWebView>();
@ -114,78 +114,78 @@ export class AndroidDevice extends ChannelOwner<channels.AndroidDeviceChannel, c
});
}
async wait(selector: apiInternal.AndroidSelector, options?: { state?: 'gone' } & types.TimeoutOptions) {
async wait(selector: androidApi.AndroidSelector, options?: { state?: 'gone' } & types.TimeoutOptions) {
await this._wrapApiCall('androidDevice.wait', async () => {
await this._channel.wait({ selector: toSelectorChannel(selector), ...options });
});
}
async fill(selector: apiInternal.AndroidSelector, text: string, options?: types.TimeoutOptions) {
async fill(selector: androidApi.AndroidSelector, text: string, options?: types.TimeoutOptions) {
await this._wrapApiCall('androidDevice.fill', async () => {
await this._channel.fill({ selector: toSelectorChannel(selector), text, ...options });
});
}
async press(selector: apiInternal.AndroidSelector, key: apiInternal.AndroidKey, options?: types.TimeoutOptions) {
async press(selector: androidApi.AndroidSelector, key: androidApi.AndroidKey, options?: types.TimeoutOptions) {
await this.tap(selector, options);
await this.input.press(key);
}
async tap(selector: apiInternal.AndroidSelector, options?: { duration?: number } & types.TimeoutOptions) {
async tap(selector: androidApi.AndroidSelector, options?: { duration?: number } & types.TimeoutOptions) {
await this._wrapApiCall('androidDevice.tap', async () => {
await this._channel.tap({ selector: toSelectorChannel(selector), ...options });
});
}
async drag(selector: apiInternal.AndroidSelector, dest: types.Point, options?: SpeedOptions & types.TimeoutOptions) {
async drag(selector: androidApi.AndroidSelector, dest: types.Point, options?: SpeedOptions & types.TimeoutOptions) {
await this._wrapApiCall('androidDevice.drag', async () => {
await this._channel.drag({ selector: toSelectorChannel(selector), dest, ...options });
});
}
async fling(selector: apiInternal.AndroidSelector, direction: Direction, options?: SpeedOptions & types.TimeoutOptions) {
async fling(selector: androidApi.AndroidSelector, direction: Direction, options?: SpeedOptions & types.TimeoutOptions) {
await this._wrapApiCall('androidDevice.fling', async () => {
await this._channel.fling({ selector: toSelectorChannel(selector), direction, ...options });
});
}
async longTap(selector: apiInternal.AndroidSelector, options?: types.TimeoutOptions) {
async longTap(selector: androidApi.AndroidSelector, options?: types.TimeoutOptions) {
await this._wrapApiCall('androidDevice.longTap', async () => {
await this._channel.longTap({ selector: toSelectorChannel(selector), ...options });
});
}
async pinchClose(selector: apiInternal.AndroidSelector, percent: number, options?: SpeedOptions & types.TimeoutOptions) {
async pinchClose(selector: androidApi.AndroidSelector, percent: number, options?: SpeedOptions & types.TimeoutOptions) {
await this._wrapApiCall('androidDevice.pinchClose', async () => {
await this._channel.pinchClose({ selector: toSelectorChannel(selector), percent, ...options });
});
}
async pinchOpen(selector: apiInternal.AndroidSelector, percent: number, options?: SpeedOptions & types.TimeoutOptions) {
async pinchOpen(selector: androidApi.AndroidSelector, percent: number, options?: SpeedOptions & types.TimeoutOptions) {
await this._wrapApiCall('androidDevice.pinchOpen', async () => {
await this._channel.pinchOpen({ selector: toSelectorChannel(selector), percent, ...options });
});
}
async scroll(selector: apiInternal.AndroidSelector, direction: Direction, percent: number, options?: SpeedOptions & types.TimeoutOptions) {
async scroll(selector: androidApi.AndroidSelector, direction: Direction, percent: number, options?: SpeedOptions & types.TimeoutOptions) {
await this._wrapApiCall('androidDevice.scroll', async () => {
await this._channel.scroll({ selector: toSelectorChannel(selector), direction, percent, ...options });
});
}
async swipe(selector: apiInternal.AndroidSelector, direction: Direction, percent: number, options?: SpeedOptions & types.TimeoutOptions) {
async swipe(selector: androidApi.AndroidSelector, direction: Direction, percent: number, options?: SpeedOptions & types.TimeoutOptions) {
await this._wrapApiCall('androidDevice.swipe', async () => {
await this._channel.swipe({ selector: toSelectorChannel(selector), direction, percent, ...options });
});
}
async info(selector: apiInternal.AndroidSelector): Promise<apiInternal.AndroidElementInfo> {
async info(selector: androidApi.AndroidSelector): Promise<androidApi.AndroidElementInfo> {
return await this._wrapApiCall('androidDevice.info', async () => {
return (await this._channel.info({ selector: toSelectorChannel(selector) })).info;
});
}
async tree(): Promise<apiInternal.AndroidElementInfo> {
async tree(): Promise<androidApi.AndroidElementInfo> {
return await this._wrapApiCall('androidDevice.tree', async () => {
return (await this._channel.tree()).tree;
});
@ -254,7 +254,7 @@ export class AndroidDevice extends ChannelOwner<channels.AndroidDeviceChannel, c
}
}
export class AndroidSocket extends ChannelOwner<channels.AndroidSocketChannel, channels.AndroidSocketInitializer> {
export class AndroidSocket extends ChannelOwner<channels.AndroidSocketChannel, channels.AndroidSocketInitializer> implements androidApi.AndroidSocket {
static from(androidDevice: channels.AndroidSocketChannel): AndroidSocket {
return (androidDevice as any)._object;
}
@ -284,7 +284,7 @@ async function loadFile(file: string | Buffer): Promise<string> {
return file.toString('base64');
}
class Input implements apiInternal.AndroidInput {
class Input implements androidApi.AndroidInput {
private _device: AndroidDevice;
constructor(device: AndroidDevice) {
@ -297,7 +297,7 @@ class Input implements apiInternal.AndroidInput {
});
}
async press(key: apiInternal.AndroidKey) {
async press(key: androidApi.AndroidKey) {
return this._device._wrapApiCall('androidDevice.inputPress', async () => {
await this._device._channel.inputPress({ key });
});
@ -322,7 +322,7 @@ class Input implements apiInternal.AndroidInput {
}
}
function toSelectorChannel(selector: apiInternal.AndroidSelector): channels.AndroidSelector {
function toSelectorChannel(selector: androidApi.AndroidSelector): channels.AndroidSelector {
const {
checkable,
checked,
@ -372,7 +372,7 @@ function toSelectorChannel(selector: apiInternal.AndroidSelector): channels.Andr
};
}
export class AndroidWebView extends EventEmitter {
export class AndroidWebView extends EventEmitter implements androidApi.AndroidWebView {
private _device: AndroidDevice;
private _data: channels.AndroidWebView;
private _pagePromise: Promise<Page> | undefined;

View File

@ -24,14 +24,18 @@ import { Waiter } from './waiter';
import { Events } from './events';
import { WaitForEventOptions, Env, Logger } from './types';
import { envObjectToArray } from './clientHelper';
import * as electronApi from '../../types/electron';
import * as structs from '../../types/structs';
import type { ChromiumBrowserContext } from './chromiumBrowserContext';
type ElectronOptions = Omit<channels.ElectronLaunchOptions, 'env'> & {
env?: Env,
logger?: Logger,
};
export class Electron extends ChannelOwner<channels.ElectronChannel, channels.ElectronInitializer> {
type ElectronAppType = typeof import('electron');
export class Electron extends ChannelOwner<channels.ElectronChannel, channels.ElectronInitializer> implements electronApi.ElectronLauncher {
static from(electron: channels.ElectronChannel): Electron {
return (electron as any)._object;
}
@ -54,7 +58,7 @@ export class Electron extends ChannelOwner<channels.ElectronChannel, channels.El
}
}
export class ElectronApplication extends ChannelOwner<channels.ElectronApplicationChannel, channels.ElectronApplicationInitializer> {
export class ElectronApplication extends ChannelOwner<channels.ElectronApplicationChannel, channels.ElectronApplicationInitializer> implements electronApi.ElectronApplication {
private _context?: BrowserContext;
private _windows = new Set<Page>();
private _timeoutSettings = new TimeoutSettings();
@ -76,23 +80,24 @@ export class ElectronApplication extends ChannelOwner<channels.ElectronApplicati
this._channel.on('close', () => this.emit(Events.ElectronApplication.Close));
}
windows(): Page[] {
return [...this._windows];
windows(): electronApi.ElectronPage[] {
// TODO: add ElectronPage class inherting from Page.
return [...this._windows] as any as electronApi.ElectronPage[];
}
async firstWindow(): Promise<Page> {
async firstWindow(): Promise<electronApi.ElectronPage> {
if (this._windows.size)
return this._windows.values().next().value;
return this.waitForEvent('window');
}
async newBrowserWindow(options: any): Promise<Page> {
async newBrowserWindow(options: any): Promise<electronApi.ElectronPage> {
const result = await this._channel.newBrowserWindow({ arg: serializeArgument(options) });
return Page.from(result.page);
return Page.from(result.page) as any as electronApi.ElectronPage;
}
context(): BrowserContext {
return this._context!;
context(): ChromiumBrowserContext {
return this._context! as ChromiumBrowserContext;
}
async close() {
@ -111,12 +116,12 @@ export class ElectronApplication extends ChannelOwner<channels.ElectronApplicati
return result;
}
async evaluate<R, Arg>(pageFunction: structs.PageFunctionOn<any, Arg, R>, arg: Arg): Promise<R> {
async evaluate<R, Arg>(pageFunction: structs.PageFunctionOn<ElectronAppType, 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: structs.PageFunctionOn<any, Arg, R>, arg: Arg): Promise<structs.SmartHandle<R>> {
async evaluateHandle<R, Arg>(pageFunction: structs.PageFunctionOn<ElectronAppType, 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 any as structs.SmartHandle<R>;
}

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
import type { Android, AndroidDevice } from '../../android-types';
import type { Android, AndroidDevice } from '../../types/android';
import { folio as baseFolio } from '../fixtures';
const fixtures = baseFolio.extend<{

View File

@ -15,7 +15,7 @@
*/
import { folio as base } from '../fixtures';
import type { ElectronApplication, ElectronPage } from '../../electron-types';
import type { ElectronApplication, ElectronPage } from '../../types/electron';
import path from 'path';
const electronName = process.platform === 'win32' ? 'electron.cmd' : 'electron';

View File

@ -27,8 +27,8 @@ import { installCoverageHooks } from './coverage';
import { folio as httpFolio } from './http.fixtures';
import { folio as playwrightFolio } from './playwright.fixtures';
import { PlaywrightClient } from '../lib/remote/playwrightClient';
import type { Android } from '../android-types';
import type { ElectronLauncher } from '../electron-types';
import type { Android } from '../types/android';
import type { ElectronLauncher } from '../types/electron';
export { expect, config } from 'folio';
const removeFolderAsync = util.promisify(require('rimraf'));

View File

@ -15,18 +15,24 @@
*/
import { EventEmitter } from 'events';
import { BrowserContextOptions, BrowserContext, Page } from './types';
export interface AndroidDevice<BrowserContextOptions, BrowserContext, Page> extends EventEmitter {
export interface Android extends EventEmitter {
setDefaultTimeout(timeout: number): void;
devices(): Promise<AndroidDevice[]>;
}
export interface AndroidDevice extends EventEmitter {
input: AndroidInput;
setDefaultTimeout(timeout: number): void;
on(event: 'webview', handler: (webView: AndroidWebView<Page>) => void): this;
on(event: 'webview', handler: (webView: AndroidWebView) => void): this;
waitForEvent(event: string, optionsOrPredicate?: (data: any) => boolean | { timeout?: number, predicate?: (data: any) => boolean }): Promise<any>;
serial(): string;
model(): string;
webViews(): AndroidWebView<Page>[];
webView(selector: { pkg: string }, options?: { timeout?: number }): Promise<AndroidWebView<Page>>;
webViews(): AndroidWebView[];
webView(selector: { pkg: string }, options?: { timeout?: number }): Promise<AndroidWebView>;
shell(command: string): Promise<Buffer>;
open(command: string): Promise<AndroidSocket>;
installApk(file: string | Buffer, options?: { args?: string[] }): Promise<void>;
@ -53,8 +59,8 @@ export interface AndroidDevice<BrowserContextOptions, BrowserContext, Page> exte
export interface AndroidSocket extends EventEmitter {
on(event: 'data', handler: (data: Buffer) => void): this;
on(event: 'close', handler: () => void): this;
write(data: Buffer): Promise<void>
close(): Promise<void>
write(data: Buffer): Promise<void>;
close(): Promise<void>;
}
export interface AndroidInput {
@ -65,7 +71,7 @@ export interface AndroidInput {
drag(from: { x: number, y: number }, to: { x: number, y: number }, steps: number): Promise<void>;
}
export interface AndroidWebView<Page> extends EventEmitter {
export interface AndroidWebView extends EventEmitter {
on(event: 'close', handler: () => void): this;
pid(): number;
pkg(): string;

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
import { Logger, Page, JSHandle, ChromiumBrowserContext } from './types/types';
import { Logger, Page, JSHandle, ChromiumBrowserContext } from './types';
import { BrowserWindow, BrowserWindowConstructorOptions } from 'electron';
export type ElectronLaunchOptions = {
@ -27,9 +27,11 @@ export type ElectronLaunchOptions = {
timeout?: number,
logger?: Logger,
};
export interface ElectronLauncher {
launch(executablePath: string, options?: ElectronLaunchOptions): Promise<ElectronApplication>;
}
export interface ElectronApplication {
on(event: 'window', listener: (page : ElectronPage) => void): this;
addListener(event: 'window', listener: (page : ElectronPage) => void): this;
@ -44,9 +46,12 @@ export interface ElectronApplication {
firstWindow(): Promise<ElectronPage>;
newBrowserWindow(options?: BrowserWindowConstructorOptions): Promise<ElectronPage>;
close(): Promise<void>;
evaluate: JSHandle<typeof import('electron')>['evaluate'];
evaluateHandle: JSHandle<typeof import('electron')>['evaluateHandle'];
evaluate: HandleToElectron['evaluate'];
evaluateHandle: HandleToElectron['evaluateHandle'];
}
export interface ElectronPage extends Page {
browserWindow: JSHandle<BrowserWindow>;
}
type HandleToElectron = JSHandle<typeof import('electron')>;

View File

@ -99,7 +99,7 @@ function checkSources(sources) {
parent = parent.parent;
className = path.basename(parent.fileName, '.js');
}
if (className && !excludeClasses.has(className) && !fileName.endsWith('/protocol.ts') && !fileName.endsWith('/protocol.d.ts') && !fileName.endsWith('/types.d.ts')) {
if (className && !excludeClasses.has(className) && !fileName.endsWith('/protocol.ts') && !fileName.endsWith('/protocol.d.ts') && !fileName.endsWith('/types.d.ts') && !fileName.endsWith('node_modules/electron/electron.d.ts')) {
excludeClasses.add(className);
classes.push(serializeClass(className, symbol, node));
inheritance.set(className, parentClasses(node));