feat(android): expose installAPK(path) and ADB socket (#4689)
This commit is contained in:
parent
6cc695d92a
commit
1b7fb7d56a
8
android-types-internal.d.ts
vendored
8
android-types-internal.d.ts
vendored
|
|
@ -28,6 +28,8 @@ export interface AndroidDevice<BrowserContextOptions, BrowserContext, Page> exte
|
||||||
webViews(): AndroidWebView<Page>[];
|
webViews(): AndroidWebView<Page>[];
|
||||||
webView(selector: { pkg: string }, options?: { timeout?: number }): Promise<AndroidWebView<Page>>;
|
webView(selector: { pkg: string }, options?: { timeout?: number }): Promise<AndroidWebView<Page>>;
|
||||||
shell(command: string): Promise<string>;
|
shell(command: string): Promise<string>;
|
||||||
|
open(command: string): Promise<AndroidSocket>;
|
||||||
|
installApk(file: string | Buffer, options?: { args?: string[] }): Promise<void>;
|
||||||
launchBrowser(options?: BrowserContextOptions & { packageName?: string }): Promise<BrowserContext>;
|
launchBrowser(options?: BrowserContextOptions & { packageName?: string }): Promise<BrowserContext>;
|
||||||
close(): Promise<void>;
|
close(): Promise<void>;
|
||||||
|
|
||||||
|
|
@ -46,6 +48,12 @@ export interface AndroidDevice<BrowserContextOptions, BrowserContext, Page> exte
|
||||||
info(selector: AndroidSelector): Promise<AndroidElementInfo>;
|
info(selector: AndroidSelector): Promise<AndroidElementInfo>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AndroidSocket extends EventEmitter {
|
||||||
|
on(event: 'data', handler: (data: Buffer) => void): this;
|
||||||
|
write(data: Buffer): Promise<void>
|
||||||
|
close(): Promise<void>
|
||||||
|
}
|
||||||
|
|
||||||
export interface AndroidInput {
|
export interface AndroidInput {
|
||||||
type(text: string): Promise<void>;
|
type(text: string): Promise<void>;
|
||||||
press(key: AndroidKey): Promise<void>;
|
press(key: AndroidKey): Promise<void>;
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,9 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as util from 'util';
|
||||||
|
import { isString } from '../utils/utils';
|
||||||
import * as channels from '../protocol/channels';
|
import * as channels from '../protocol/channels';
|
||||||
import { Events } from './events';
|
import { Events } from './events';
|
||||||
import { BrowserContext, validateBrowserContextOptions } from './browserContext';
|
import { BrowserContext, validateBrowserContextOptions } from './browserContext';
|
||||||
|
|
@ -196,6 +199,18 @@ export class AndroidDevice extends ChannelOwner<channels.AndroidDeviceChannel, c
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async open(command: string): Promise<AndroidSocket> {
|
||||||
|
return this._wrapApiCall('androidDevice.open', async () => {
|
||||||
|
return AndroidSocket.from((await this._channel.open({ command })).socket);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async installApk(file: string | Buffer, options?: { args: string[] }): Promise<void> {
|
||||||
|
return this._wrapApiCall('androidDevice.installApk', async () => {
|
||||||
|
await this._channel.installApk({ file: await readApkFile(file), args: options && options.args });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async launchBrowser(options: types.BrowserContextOptions & { packageName?: string } = {}): Promise<BrowserContext> {
|
async launchBrowser(options: types.BrowserContextOptions & { packageName?: string } = {}): Promise<BrowserContext> {
|
||||||
return this._wrapApiCall('androidDevice.launchBrowser', async () => {
|
return this._wrapApiCall('androidDevice.launchBrowser', async () => {
|
||||||
const contextOptions = validateBrowserContextOptions(options);
|
const contextOptions = validateBrowserContextOptions(options);
|
||||||
|
|
@ -217,6 +232,35 @@ export class AndroidDevice extends ChannelOwner<channels.AndroidDeviceChannel, c
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class AndroidSocket extends ChannelOwner<channels.AndroidSocketChannel, channels.AndroidSocketInitializer> {
|
||||||
|
static from(androidDevice: channels.AndroidSocketChannel): AndroidSocket {
|
||||||
|
return (androidDevice as any)._object;
|
||||||
|
}
|
||||||
|
|
||||||
|
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')));
|
||||||
|
}
|
||||||
|
|
||||||
|
async write(data: Buffer): Promise<void> {
|
||||||
|
return this._wrapApiCall('androidDevice.write', async () => {
|
||||||
|
await this._channel.write({ data: data.toString('base64') });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async close(): Promise<void> {
|
||||||
|
return this._wrapApiCall('androidDevice.close', async () => {
|
||||||
|
await this._channel.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function readApkFile(file: string | Buffer): Promise<string> {
|
||||||
|
if (isString(file))
|
||||||
|
return (await util.promisify(fs.readFile)(file)).toString('base64');
|
||||||
|
return file.toString('base64');
|
||||||
|
}
|
||||||
|
|
||||||
class Input implements apiInternal.AndroidInput {
|
class Input implements apiInternal.AndroidInput {
|
||||||
private _device: AndroidDevice;
|
private _device: AndroidDevice;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ import { FirefoxBrowser } from './firefoxBrowser';
|
||||||
import { debugLogger } from '../utils/debugLogger';
|
import { debugLogger } from '../utils/debugLogger';
|
||||||
import { SelectorsOwner } from './selectors';
|
import { SelectorsOwner } from './selectors';
|
||||||
import { isUnderTest } from '../utils/utils';
|
import { isUnderTest } from '../utils/utils';
|
||||||
import { Android, AndroidDevice } from './android';
|
import { Android, AndroidSocket, AndroidDevice } from './android';
|
||||||
|
|
||||||
class Root extends ChannelOwner<channels.Channel, {}> {
|
class Root extends ChannelOwner<channels.Channel, {}> {
|
||||||
constructor(connection: Connection) {
|
constructor(connection: Connection) {
|
||||||
|
|
@ -151,6 +151,9 @@ export class Connection {
|
||||||
case 'Android':
|
case 'Android':
|
||||||
result = new Android(parent, type, guid, initializer);
|
result = new Android(parent, type, guid, initializer);
|
||||||
break;
|
break;
|
||||||
|
case 'AndroidSocket':
|
||||||
|
result = new AndroidSocket(parent, type, guid, initializer);
|
||||||
|
break;
|
||||||
case 'AndroidDevice':
|
case 'AndroidDevice':
|
||||||
result = new AndroidDevice(parent, type, guid, initializer);
|
result = new AndroidDevice(parent, type, guid, initializer);
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,10 @@ export const Events = {
|
||||||
Close: 'close'
|
Close: 'close'
|
||||||
},
|
},
|
||||||
|
|
||||||
|
AndroidSocket: {
|
||||||
|
Data: 'data'
|
||||||
|
},
|
||||||
|
|
||||||
AndroidWebView: {
|
AndroidWebView: {
|
||||||
Close: 'close'
|
Close: 'close'
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Dispatcher, DispatcherScope, existingDispatcher } from './dispatcher';
|
import { Dispatcher, DispatcherScope, existingDispatcher } from './dispatcher';
|
||||||
import { Android, AndroidDevice } from '../server/android/android';
|
import { Android, AndroidDevice, SocketBackend } from '../server/android/android';
|
||||||
import * as channels from '../protocol/channels';
|
import * as channels from '../protocol/channels';
|
||||||
import { BrowserContextDispatcher } from './browserContextDispatcher';
|
import { BrowserContextDispatcher } from './browserContextDispatcher';
|
||||||
|
import { Events } from '../client/events';
|
||||||
|
|
||||||
export class AndroidDispatcher extends Dispatcher<Android, channels.AndroidInitializer> implements channels.AndroidChannel {
|
export class AndroidDispatcher extends Dispatcher<Android, channels.AndroidInitializer> implements channels.AndroidChannel {
|
||||||
constructor(scope: DispatcherScope, android: Android) {
|
constructor(scope: DispatcherScope, android: Android) {
|
||||||
|
|
@ -131,10 +132,19 @@ export class AndroidDeviceDispatcher extends Dispatcher<AndroidDevice, channels.
|
||||||
await this._object.send('inputDrag', params);
|
await this._object.send('inputDrag', params);
|
||||||
}
|
}
|
||||||
|
|
||||||
async shell(params: channels.AndroidDeviceShellParams) {
|
async shell(params: channels.AndroidDeviceShellParams): Promise<channels.AndroidDeviceShellResult> {
|
||||||
return { result: await this._object.shell(params.command) };
|
return { result: await this._object.shell(params.command) };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 });
|
||||||
|
}
|
||||||
|
|
||||||
async launchBrowser(params: channels.AndroidDeviceLaunchBrowserParams): Promise<channels.AndroidDeviceLaunchBrowserResult> {
|
async launchBrowser(params: channels.AndroidDeviceLaunchBrowserParams): Promise<channels.AndroidDeviceLaunchBrowserResult> {
|
||||||
const context = await this._object.launchBrowser(params.packageName, params);
|
const context = await this._object.launchBrowser(params.packageName, params);
|
||||||
return { context: new BrowserContextDispatcher(this._scope, context) };
|
return { context: new BrowserContextDispatcher(this._scope, context) };
|
||||||
|
|
@ -153,6 +163,21 @@ export class AndroidDeviceDispatcher extends Dispatcher<AndroidDevice, channels.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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>([
|
const keyMap = new Map<string, number>([
|
||||||
['Unknown', 0],
|
['Unknown', 0],
|
||||||
['SoftLeft', 1],
|
['SoftLeft', 1],
|
||||||
|
|
|
||||||
|
|
@ -2421,6 +2421,27 @@ export type AndroidSetDefaultTimeoutNoReplyOptions = {
|
||||||
};
|
};
|
||||||
export type AndroidSetDefaultTimeoutNoReplyResult = void;
|
export type AndroidSetDefaultTimeoutNoReplyResult = void;
|
||||||
|
|
||||||
|
// ----------- AndroidSocket -----------
|
||||||
|
export type AndroidSocketInitializer = {};
|
||||||
|
export interface AndroidSocketChannel extends Channel {
|
||||||
|
on(event: 'data', callback: (params: AndroidSocketDataEvent) => void): this;
|
||||||
|
write(params: AndroidSocketWriteParams, metadata?: Metadata): Promise<AndroidSocketWriteResult>;
|
||||||
|
close(params?: AndroidSocketCloseParams, metadata?: Metadata): Promise<AndroidSocketCloseResult>;
|
||||||
|
}
|
||||||
|
export type AndroidSocketDataEvent = {
|
||||||
|
data: Binary,
|
||||||
|
};
|
||||||
|
export type AndroidSocketWriteParams = {
|
||||||
|
data: Binary,
|
||||||
|
};
|
||||||
|
export type AndroidSocketWriteOptions = {
|
||||||
|
|
||||||
|
};
|
||||||
|
export type AndroidSocketWriteResult = void;
|
||||||
|
export type AndroidSocketCloseParams = {};
|
||||||
|
export type AndroidSocketCloseOptions = {};
|
||||||
|
export type AndroidSocketCloseResult = void;
|
||||||
|
|
||||||
// ----------- AndroidDevice -----------
|
// ----------- AndroidDevice -----------
|
||||||
export type AndroidDeviceInitializer = {
|
export type AndroidDeviceInitializer = {
|
||||||
model: string,
|
model: string,
|
||||||
|
|
@ -2446,7 +2467,9 @@ export interface AndroidDeviceChannel extends Channel {
|
||||||
inputSwipe(params: AndroidDeviceInputSwipeParams, metadata?: Metadata): Promise<AndroidDeviceInputSwipeResult>;
|
inputSwipe(params: AndroidDeviceInputSwipeParams, metadata?: Metadata): Promise<AndroidDeviceInputSwipeResult>;
|
||||||
inputDrag(params: AndroidDeviceInputDragParams, metadata?: Metadata): Promise<AndroidDeviceInputDragResult>;
|
inputDrag(params: AndroidDeviceInputDragParams, metadata?: Metadata): Promise<AndroidDeviceInputDragResult>;
|
||||||
launchBrowser(params: AndroidDeviceLaunchBrowserParams, metadata?: Metadata): Promise<AndroidDeviceLaunchBrowserResult>;
|
launchBrowser(params: AndroidDeviceLaunchBrowserParams, metadata?: Metadata): Promise<AndroidDeviceLaunchBrowserResult>;
|
||||||
|
open(params: AndroidDeviceOpenParams, metadata?: Metadata): Promise<AndroidDeviceOpenResult>;
|
||||||
shell(params: AndroidDeviceShellParams, metadata?: Metadata): Promise<AndroidDeviceShellResult>;
|
shell(params: AndroidDeviceShellParams, metadata?: Metadata): Promise<AndroidDeviceShellResult>;
|
||||||
|
installApk(params: AndroidDeviceInstallApkParams, metadata?: Metadata): Promise<AndroidDeviceInstallApkResult>;
|
||||||
setDefaultTimeoutNoReply(params: AndroidDeviceSetDefaultTimeoutNoReplyParams, metadata?: Metadata): Promise<AndroidDeviceSetDefaultTimeoutNoReplyResult>;
|
setDefaultTimeoutNoReply(params: AndroidDeviceSetDefaultTimeoutNoReplyParams, metadata?: Metadata): Promise<AndroidDeviceSetDefaultTimeoutNoReplyResult>;
|
||||||
connectToWebView(params: AndroidDeviceConnectToWebViewParams, metadata?: Metadata): Promise<AndroidDeviceConnectToWebViewResult>;
|
connectToWebView(params: AndroidDeviceConnectToWebViewParams, metadata?: Metadata): Promise<AndroidDeviceConnectToWebViewResult>;
|
||||||
close(params?: AndroidDeviceCloseParams, metadata?: Metadata): Promise<AndroidDeviceCloseResult>;
|
close(params?: AndroidDeviceCloseParams, metadata?: Metadata): Promise<AndroidDeviceCloseResult>;
|
||||||
|
|
@ -2702,6 +2725,15 @@ export type AndroidDeviceLaunchBrowserOptions = {
|
||||||
export type AndroidDeviceLaunchBrowserResult = {
|
export type AndroidDeviceLaunchBrowserResult = {
|
||||||
context: BrowserContextChannel,
|
context: BrowserContextChannel,
|
||||||
};
|
};
|
||||||
|
export type AndroidDeviceOpenParams = {
|
||||||
|
command: string,
|
||||||
|
};
|
||||||
|
export type AndroidDeviceOpenOptions = {
|
||||||
|
|
||||||
|
};
|
||||||
|
export type AndroidDeviceOpenResult = {
|
||||||
|
socket: AndroidSocketChannel,
|
||||||
|
};
|
||||||
export type AndroidDeviceShellParams = {
|
export type AndroidDeviceShellParams = {
|
||||||
command: string,
|
command: string,
|
||||||
};
|
};
|
||||||
|
|
@ -2711,6 +2743,14 @@ export type AndroidDeviceShellOptions = {
|
||||||
export type AndroidDeviceShellResult = {
|
export type AndroidDeviceShellResult = {
|
||||||
result: string,
|
result: string,
|
||||||
};
|
};
|
||||||
|
export type AndroidDeviceInstallApkParams = {
|
||||||
|
file: Binary,
|
||||||
|
args?: string[],
|
||||||
|
};
|
||||||
|
export type AndroidDeviceInstallApkOptions = {
|
||||||
|
args?: string[],
|
||||||
|
};
|
||||||
|
export type AndroidDeviceInstallApkResult = void;
|
||||||
export type AndroidDeviceSetDefaultTimeoutNoReplyParams = {
|
export type AndroidDeviceSetDefaultTimeoutNoReplyParams = {
|
||||||
timeout: number,
|
timeout: number,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -2083,6 +2083,20 @@ Android:
|
||||||
parameters:
|
parameters:
|
||||||
timeout: number
|
timeout: number
|
||||||
|
|
||||||
|
AndroidSocket:
|
||||||
|
type: interface
|
||||||
|
|
||||||
|
commands:
|
||||||
|
write:
|
||||||
|
parameters:
|
||||||
|
data: binary
|
||||||
|
|
||||||
|
close:
|
||||||
|
|
||||||
|
events:
|
||||||
|
data:
|
||||||
|
parameters:
|
||||||
|
data: binary
|
||||||
|
|
||||||
AndroidDevice:
|
AndroidDevice:
|
||||||
type: interface
|
type: interface
|
||||||
|
|
@ -2275,12 +2289,25 @@ AndroidDevice:
|
||||||
returns:
|
returns:
|
||||||
context: BrowserContext
|
context: BrowserContext
|
||||||
|
|
||||||
|
open:
|
||||||
|
parameters:
|
||||||
|
command: string
|
||||||
|
returns:
|
||||||
|
socket: AndroidSocket
|
||||||
|
|
||||||
shell:
|
shell:
|
||||||
parameters:
|
parameters:
|
||||||
command: string
|
command: string
|
||||||
returns:
|
returns:
|
||||||
result: string
|
result: string
|
||||||
|
|
||||||
|
installApk:
|
||||||
|
parameters:
|
||||||
|
file: binary
|
||||||
|
args:
|
||||||
|
type: array?
|
||||||
|
items: string
|
||||||
|
|
||||||
setDefaultTimeoutNoReply:
|
setDefaultTimeoutNoReply:
|
||||||
parameters:
|
parameters:
|
||||||
timeout: number
|
timeout: number
|
||||||
|
|
|
||||||
|
|
@ -901,6 +901,10 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
||||||
scheme.AndroidSetDefaultTimeoutNoReplyParams = tObject({
|
scheme.AndroidSetDefaultTimeoutNoReplyParams = tObject({
|
||||||
timeout: tNumber,
|
timeout: tNumber,
|
||||||
});
|
});
|
||||||
|
scheme.AndroidSocketWriteParams = tObject({
|
||||||
|
data: tBinary,
|
||||||
|
});
|
||||||
|
scheme.AndroidSocketCloseParams = tOptional(tObject({}));
|
||||||
scheme.AndroidDeviceWaitParams = tObject({
|
scheme.AndroidDeviceWaitParams = tObject({
|
||||||
selector: tType('AndroidSelector'),
|
selector: tType('AndroidSelector'),
|
||||||
state: tOptional(tEnum(['gone'])),
|
state: tOptional(tEnum(['gone'])),
|
||||||
|
|
@ -1024,9 +1028,16 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
||||||
password: tOptional(tString),
|
password: tOptional(tString),
|
||||||
})),
|
})),
|
||||||
});
|
});
|
||||||
|
scheme.AndroidDeviceOpenParams = tObject({
|
||||||
|
command: tString,
|
||||||
|
});
|
||||||
scheme.AndroidDeviceShellParams = tObject({
|
scheme.AndroidDeviceShellParams = tObject({
|
||||||
command: tString,
|
command: tString,
|
||||||
});
|
});
|
||||||
|
scheme.AndroidDeviceInstallApkParams = tObject({
|
||||||
|
file: tBinary,
|
||||||
|
args: tOptional(tArray(tString)),
|
||||||
|
});
|
||||||
scheme.AndroidDeviceSetDefaultTimeoutNoReplyParams = tObject({
|
scheme.AndroidDeviceSetDefaultTimeoutNoReplyParams = tObject({
|
||||||
timeout: tNumber,
|
timeout: tNumber,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -144,6 +144,10 @@ export class AndroidDevice extends EventEmitter {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async open(command: string): Promise<SocketBackend> {
|
||||||
|
return await this._backend.open(`shell:${command}`);
|
||||||
|
}
|
||||||
|
|
||||||
private async _driver(): Promise<Transport> {
|
private async _driver(): Promise<Transport> {
|
||||||
if (this._driverPromise)
|
if (this._driverPromise)
|
||||||
return this._driverPromise;
|
return this._driverPromise;
|
||||||
|
|
@ -158,16 +162,8 @@ export class AndroidDevice extends EventEmitter {
|
||||||
await this.shell(`cmd package uninstall com.microsoft.playwright.androiddriver.test`);
|
await this.shell(`cmd package uninstall com.microsoft.playwright.androiddriver.test`);
|
||||||
|
|
||||||
debug('pw:android')('Installing the new driver');
|
debug('pw:android')('Installing the new driver');
|
||||||
for (const file of ['android-driver.apk', 'android-driver-target.apk']) {
|
for (const file of ['android-driver.apk', 'android-driver-target.apk'])
|
||||||
debug('pw:android')('Reading ' + require.resolve(`../../../bin/${file}`));
|
await this.installApk(await readFileAsync(require.resolve(`../../../bin/${file}`)));
|
||||||
const driverFile = await readFileAsync(require.resolve(`../../../bin/${file}`));
|
|
||||||
debug('pw:android')('Opening install socket');
|
|
||||||
const installSocket = await this._backend.open(`shell:cmd package install -r -t -S ${driverFile.length}`);
|
|
||||||
debug('pw:android')('Writing driver bytes: ' + driverFile.length);
|
|
||||||
await installSocket.write(driverFile);
|
|
||||||
const success = await new Promise(f => installSocket.on('data', f));
|
|
||||||
debug('pw:android')('Written driver bytes: ' + success);
|
|
||||||
}
|
|
||||||
|
|
||||||
debug('pw:android')('Starting the new driver');
|
debug('pw:android')('Starting the new driver');
|
||||||
this.shell(`am instrument -w com.microsoft.playwright.androiddriver.test/androidx.test.runner.AndroidJUnitRunner`);
|
this.shell(`am instrument -w com.microsoft.playwright.androiddriver.test/androidx.test.runner.AndroidJUnitRunner`);
|
||||||
|
|
@ -177,7 +173,7 @@ export class AndroidDevice extends EventEmitter {
|
||||||
while (!socket) {
|
while (!socket) {
|
||||||
try {
|
try {
|
||||||
socket = await this._backend.open(`localabstract:playwright_android_driver_socket`);
|
socket = await this._backend.open(`localabstract:playwright_android_driver_socket`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await new Promise(f => setTimeout(f, 100));
|
await new Promise(f => setTimeout(f, 100));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -281,6 +277,16 @@ export class AndroidDevice extends EventEmitter {
|
||||||
return [...this._webViews.values()];
|
return [...this._webViews.values()];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async installApk(content: Buffer, options?: { args?: string[] }): Promise<void> {
|
||||||
|
const args = options && options.args ? options.args : ['-r', '-t', '-S'];
|
||||||
|
debug('pw:android')('Opening install socket');
|
||||||
|
const installSocket = await this._backend.open(`shell:cmd package install ${args.join(' ')} ${content.length}`);
|
||||||
|
debug('pw:android')('Writing driver bytes: ' + content.length);
|
||||||
|
await installSocket.write(content);
|
||||||
|
const success = await new Promise(f => installSocket.on('data', f));
|
||||||
|
debug('pw:android')('Written driver bytes: ' + success);
|
||||||
|
}
|
||||||
|
|
||||||
private async _refreshWebViews() {
|
private async _refreshWebViews() {
|
||||||
const sockets = (await this._backend.runCommand(`shell:cat /proc/net/unix | grep webview_devtools_remote`)).split('\n');
|
const sockets = (await this._backend.runCommand(`shell:cat /proc/net/unix | grep webview_devtools_remote`)).split('\n');
|
||||||
if (this._isClosed)
|
if (this._isClosed)
|
||||||
|
|
|
||||||
32
test/android/device.spec.ts
Normal file
32
test/android/device.spec.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
/**
|
||||||
|
* Copyright 2020 Microsoft Corporation. All rights reserved.
|
||||||
|
*
|
||||||
|
* 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 { folio } from './android.fixtures';
|
||||||
|
const { it, expect } = folio;
|
||||||
|
|
||||||
|
if (process.env.PW_ANDROID_TESTS) {
|
||||||
|
it('should run ADB shell commands', async function({ device }) {
|
||||||
|
const output = await device.shell('echo 123');
|
||||||
|
expect(output).toBe('123\n');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should open a ADB socket', async function({ device }) {
|
||||||
|
const socket = await device.open('/bin/cat');
|
||||||
|
await socket.write(Buffer.from('321\n'));
|
||||||
|
const output = await new Promise(resolve => socket.on('data', resolve));
|
||||||
|
expect(output.toString()).toBe('321\n');
|
||||||
|
});
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue