playwright/src/dispatchers/androidDispatcher.ts

294 lines
8.9 KiB
TypeScript
Raw Normal View History

/**
* 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.
*/
2020-12-12 10:18:32 +01:00
import { Dispatcher, DispatcherScope, existingDispatcher } from './dispatcher';
import { Android, AndroidDevice, SocketBackend } from '../server/android/android';
import * as channels from '../protocol/channels';
import { BrowserContextDispatcher } from './browserContextDispatcher';
import { Events } from '../client/events';
export class AndroidDispatcher extends Dispatcher<Android, channels.AndroidInitializer> implements channels.AndroidChannel {
2020-12-10 02:15:24 +01:00
constructor(scope: DispatcherScope, android: Android) {
super(scope, android, 'Android', {}, true);
}
async devices(params: channels.AndroidDevicesParams): Promise<channels.AndroidDevicesResult> {
const devices = await this._object.devices();
return {
2020-12-12 10:18:32 +01:00
devices: devices.map(d => AndroidDeviceDispatcher.from(this._scope, d))
};
}
2020-12-10 02:15:24 +01:00
async setDefaultTimeoutNoReply(params: channels.AndroidSetDefaultTimeoutNoReplyParams) {
this._object.setDefaultTimeout(params.timeout);
}
}
export class AndroidDeviceDispatcher extends Dispatcher<AndroidDevice, channels.AndroidDeviceInitializer> implements channels.AndroidDeviceChannel {
2020-12-12 10:18:32 +01:00
static from(scope: DispatcherScope, device: AndroidDevice): AndroidDeviceDispatcher {
const result = existingDispatcher<AndroidDeviceDispatcher>(device);
return result || new AndroidDeviceDispatcher(scope, device);
}
constructor(scope: DispatcherScope, device: AndroidDevice) {
super(scope, device, 'AndroidDevice', {
model: device.model,
2020-12-10 02:15:24 +01:00
serial: device.serial,
}, true);
2020-12-10 02:15:24 +01:00
for (const webView of device.webViews())
this._dispatchEvent('webViewAdded', { webView });
device.on(AndroidDevice.Events.WebViewAdded, webView => this._dispatchEvent('webViewAdded', { webView }));
device.on(AndroidDevice.Events.WebViewRemoved, pid => this._dispatchEvent('webViewRemoved', { pid }));
}
async wait(params: channels.AndroidDeviceWaitParams) {
await this._object.send('wait', params);
}
async fill(params: channels.AndroidDeviceFillParams) {
await Promise.all([
this._object.send('click', { selector: params.selector }),
this._object.send('fill', params)
]);
}
async tap(params: channels.AndroidDeviceTapParams) {
await this._object.send('click', params);
}
async drag(params: channels.AndroidDeviceDragParams) {
await this._object.send('drag', params);
}
async fling(params: channels.AndroidDeviceFlingParams) {
await this._object.send('fling', params);
}
async longTap(params: channels.AndroidDeviceLongTapParams) {
await this._object.send('longClick', params);
}
async pinchClose(params: channels.AndroidDevicePinchCloseParams) {
await this._object.send('pinchClose', params);
}
async pinchOpen(params: channels.AndroidDevicePinchOpenParams) {
await this._object.send('pinchOpen', params);
}
async scroll(params: channels.AndroidDeviceScrollParams) {
await this._object.send('scroll', params);
}
async swipe(params: channels.AndroidDeviceSwipeParams) {
await this._object.send('swipe', params);
}
async info(params: channels.AndroidDeviceTapParams): Promise<channels.AndroidDeviceInfoResult> {
return { info: await this._object.send('info', params) };
}
2020-12-13 17:14:32 +01:00
async tree(params: channels.AndroidDeviceTreeParams): Promise<channels.AndroidDeviceTreeResult> {
return { tree: await this._object.send('tree', params) };
}
async inputType(params: channels.AndroidDeviceInputTypeParams) {
const text = params.text;
const keyCodes: number[] = [];
for (let i = 0; i < text.length; ++i) {
const code = keyMap.get(text[i].toUpperCase());
if (code === undefined)
throw new Error('No mapping for ' + text[i] + ' found');
keyCodes.push(code);
}
await Promise.all(keyCodes.map(keyCode => this._object.send('inputPress', { keyCode })));
}
async inputPress(params: channels.AndroidDeviceInputPressParams) {
if (!keyMap.has(params.key))
throw new Error('Unknown key: ' + params.key);
await this._object.send('inputPress', { keyCode: keyMap.get(params.key) });
}
async inputTap(params: channels.AndroidDeviceInputTapParams) {
await this._object.send('inputClick', params);
}
async inputSwipe(params: channels.AndroidDeviceInputSwipeParams) {
await this._object.send('inputSwipe', params);
}
async inputDrag(params: channels.AndroidDeviceInputDragParams) {
await this._object.send('inputDrag', params);
}
2020-12-14 08:20:13 +01:00
async screenshot(params: channels.AndroidDeviceScreenshotParams): Promise<channels.AndroidDeviceScreenshotResult> {
return { binary: (await this._object.screenshot()).toString('base64') };
}
async shell(params: channels.AndroidDeviceShellParams): Promise<channels.AndroidDeviceShellResult> {
return { result: (await this._object.shell(params.command)).toString('base64') };
}
async open(params: channels.AndroidDeviceOpenParams, metadata?: channels.Metadata): Promise<channels.AndroidDeviceOpenResult> {
const socket = await this._object.open(params.command);
return { socket: new AndroidSocketDispatcher(this._scope, socket) };
}
async installApk(params: channels.AndroidDeviceInstallApkParams) {
await this._object.installApk(Buffer.from(params.file, 'base64'), { args: params.args });
}
2020-12-14 07:00:37 +01:00
async push(params: channels.AndroidDevicePushParams) {
await this._object.push(Buffer.from(params.file, 'base64'), params.path, params.mode);
}
async launchBrowser(params: channels.AndroidDeviceLaunchBrowserParams): Promise<channels.AndroidDeviceLaunchBrowserResult> {
const context = await this._object.launchBrowser(params.packageName, params);
return { context: new BrowserContextDispatcher(this._scope, context) };
}
async close(params: channels.AndroidDeviceCloseParams) {
await this._object.close();
}
2020-12-10 02:15:24 +01:00
async setDefaultTimeoutNoReply(params: channels.AndroidDeviceSetDefaultTimeoutNoReplyParams) {
this._object.setDefaultTimeout(params.timeout);
}
async connectToWebView(params: channels.AndroidDeviceConnectToWebViewParams): Promise<channels.AndroidDeviceConnectToWebViewResult> {
return { context: new BrowserContextDispatcher(this._scope, await this._object.connectToWebView(params.pid)) };
}
}
export class AndroidSocketDispatcher extends Dispatcher<SocketBackend, channels.AndroidSocketInitializer> implements channels.AndroidSocketChannel {
constructor(scope: DispatcherScope, socket: SocketBackend) {
super(scope, socket, 'AndroidSocket', {}, true);
socket.on(Events.AndroidSocket.Data, (data: Buffer) => this._dispatchEvent('data', { data: data.toString('base64') }));
}
async write(params: channels.AndroidSocketWriteParams, metadata?: channels.Metadata): Promise<void> {
await this._object.write(Buffer.from(params.data, 'base64'));
}
async close(params: channels.AndroidSocketCloseParams, metadata?: channels.Metadata): Promise<void> {
await this._object.close();
}
}
const keyMap = new Map<string, number>([
['Unknown', 0],
['SoftLeft', 1],
['SoftRight', 2],
['Home', 3],
['Back', 4],
['Call', 5],
['EndCall', 6],
['0', 7],
['1', 8],
['2', 9],
['3', 10],
['4', 11],
['5', 12],
['6', 13],
['7', 14],
['8', 15],
['9', 16],
['Star', 17],
['*', 17],
['Pound', 18],
['#', 18],
['DialUp', 19],
['DialDown', 20],
['DialLeft', 21],
['DialRight', 22],
['DialCenter', 23],
['VolumeUp', 24],
['VolumeDown', 25],
['Power', 26],
['Camera', 27],
['Clear', 28],
['A', 29],
['B', 30],
['C', 31],
['D', 32],
['E', 33],
['F', 34],
['G', 35],
['H', 36],
['I', 37],
['J', 38],
['K', 39],
['L', 40],
['M', 41],
['N', 42],
['O', 43],
['P', 44],
['Q', 45],
['R', 46],
['S', 47],
['T', 48],
['U', 49],
['V', 50],
['W', 51],
['X', 52],
['Y', 53],
['Z', 54],
['Comma', 55],
[',', 55],
['Period', 56],
['.', 56],
['AltLeft', 57],
['AltRight', 58],
['ShiftLeft', 59],
['ShiftRight', 60],
['Tab', 61],
['\t', 61],
['Space', 62],
[' ', 62],
['Sym', 63],
['Explorer', 64],
['Envelop', 65],
['Enter', 66],
['Del', 67],
['Grave', 68],
['Minus', 69],
['-', 69],
['Equals', 70],
['=', 70],
['LeftBracket', 71],
['(', 71],
['RightBracket', 72],
[')', 72],
['Backslash', 73],
['\\', 73],
['Semicolon', 74],
[';', 74],
['Apostrophe', 75],
['`', 75],
['Slash', 76],
['/', 76],
['At', 77],
['Num', 78],
['HeadsetHook', 79],
['Focus', 80],
['Plus', 81],
['Menu', 82],
['Notification', 83],
['Search', 84],
]);