feat(android): Adding custom port parameter to connect to different adb server port (#12220)

This commit is contained in:
Karan Shah 2022-03-05 00:57:25 +05:30 committed by GitHub
parent 50cc1641d1
commit fd1a1a2b1c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 75 additions and 29 deletions

View file

@ -82,6 +82,11 @@ PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 npm i -D playwright
Returns the list of detected Android devices.
### option: Android.devices.port
- `port` <[int]>
Optional port to establish ADB server connection.
## method: Android.setDefaultTimeout
This setting will change the default maximum time for all the methods accepting [`param: timeout`] option.

View file

@ -47,8 +47,8 @@ export class Android extends ChannelOwner<channels.AndroidChannel> implements ap
this._channel.setDefaultTimeoutNoReply({ timeout });
}
async devices(): Promise<AndroidDevice[]> {
const { devices } = await this._channel.devices();
async devices(options: { port?: number }): Promise<AndroidDevice[]> {
const { devices } = await this._channel.devices(options);
return devices.map(d => AndroidDevice.from(d));
}
}
@ -190,7 +190,7 @@ export class AndroidDevice extends ChannelOwner<channels.AndroidDeviceChannel> i
await this._channel.push({ file: await loadFile(file), path, mode: options ? options.mode : undefined });
}
async launchBrowser(options: types.BrowserContextOptions & { pkg?: string } = {}): Promise<BrowserContext> {
async launchBrowser(options: types.BrowserContextOptions & { pkg?: string } = {}): Promise<BrowserContext> {
const contextOptions = await prepareBrowserContextParams(options);
const { context } = await this._channel.launchBrowser(contextOptions);
return BrowserContext.from(context) as BrowserContext;

View file

@ -27,7 +27,7 @@ export class AndroidDispatcher extends Dispatcher<Android, channels.AndroidChann
}
async devices(params: channels.AndroidDevicesParams): Promise<channels.AndroidDevicesResult> {
const devices = await this._object.devices();
const devices = await this._object.devices(params);
return {
devices: devices.map(d => AndroidDeviceDispatcher.from(this._scope, d))
};

View file

@ -3592,11 +3592,15 @@ export interface AndroidEventTarget {
}
export interface AndroidChannel extends AndroidEventTarget, Channel {
_type_Android: boolean;
devices(params?: AndroidDevicesParams, metadata?: Metadata): Promise<AndroidDevicesResult>;
devices(params: AndroidDevicesParams, metadata?: Metadata): Promise<AndroidDevicesResult>;
setDefaultTimeoutNoReply(params: AndroidSetDefaultTimeoutNoReplyParams, metadata?: Metadata): Promise<AndroidSetDefaultTimeoutNoReplyResult>;
}
export type AndroidDevicesParams = {};
export type AndroidDevicesOptions = {};
export type AndroidDevicesParams = {
port?: number,
};
export type AndroidDevicesOptions = {
port?: number,
};
export type AndroidDevicesResult = {
devices: AndroidDeviceChannel[],
};

View file

@ -2793,6 +2793,8 @@ Android:
commands:
devices:
parameters:
port: number?
returns:
devices:
type: array

View file

@ -1271,7 +1271,9 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
arg: tType('SerializedArgument'),
});
scheme.ElectronApplicationCloseParams = tOptional(tObject({}));
scheme.AndroidDevicesParams = tOptional(tObject({}));
scheme.AndroidDevicesParams = tObject({
port: tOptional(tNumber),
});
scheme.AndroidSetDefaultTimeoutNoReplyParams = tObject({
timeout: tNumber,
});

View file

@ -38,7 +38,7 @@ import { SdkObject, internalCallMetadata } from '../instrumentation';
const ARTIFACTS_FOLDER = path.join(os.tmpdir(), 'playwright-artifacts-');
export interface Backend {
devices(): Promise<DeviceBackend[]>;
devices(options: types.AndroidDeviceOptions): Promise<DeviceBackend[]>;
}
export interface DeviceBackend {
@ -73,8 +73,8 @@ export class Android extends SdkObject {
this._timeoutSettings.setDefaultTimeout(timeout);
}
async devices(): Promise<AndroidDevice[]> {
const devices = (await this._backend.devices()).filter(d => d.status === 'device');
async devices(options: types.AndroidDeviceOptions): Promise<AndroidDevice[]> {
const devices = (await this._backend.devices(options)).filter(d => d.status === 'device');
const newSerials = new Set<string>();
for (const d of devices) {
newSerials.add(d.serial);

View file

@ -16,30 +16,26 @@
import assert from 'assert';
import debug from 'debug';
import * as types from '../types';
import * as net from 'net';
import { EventEmitter } from 'events';
import { Backend, DeviceBackend, SocketBackend } from './android';
import { createGuid } from '../../utils/utils';
export class AdbBackend implements Backend {
async devices(): Promise<DeviceBackend[]> {
const result = await runCommand('host:devices');
async devices(options: types.AndroidDeviceOptions = {}): Promise<DeviceBackend[]> {
const port = options.port ? options.port : 5037;
const result = await runCommand('host:devices', port);
const lines = result.toString().trim().split('\n');
return lines.map(line => {
const [serial, status] = line.trim().split('\t');
return new AdbDevice(serial, status);
return new AdbDevice(serial, status, port);
});
}
}
class AdbDevice implements DeviceBackend {
readonly serial: string;
readonly status: string;
constructor(serial: string, status: string) {
this.serial = serial;
this.status = status;
}
constructor(readonly serial: string, readonly status: string, readonly port: number) { }
async init() {
}
@ -48,19 +44,19 @@ class AdbDevice implements DeviceBackend {
}
runCommand(command: string): Promise<Buffer> {
return runCommand(command, this.serial);
return runCommand(command, this.port, this.serial);
}
async open(command: string): Promise<SocketBackend> {
const result = await open(command, this.serial);
const result = await open(command, this.port, this.serial);
result.becomeSocket();
return result;
}
}
async function runCommand(command: string, serial?: string): Promise<Buffer> {
async function runCommand(command: string, port: number = 5037, serial?: string): Promise<Buffer> {
debug('pw:adb:runCommand')(command, serial);
const socket = new BufferedSocketWrapper(command, net.createConnection({ port: 5037 }));
const socket = new BufferedSocketWrapper(command, net.createConnection({ port }));
if (serial) {
await socket.write(encodeMessage(`host:transport:${serial}`));
const status = await socket.read(4);
@ -80,8 +76,8 @@ async function runCommand(command: string, serial?: string): Promise<Buffer> {
return commandOutput;
}
async function open(command: string, serial?: string): Promise<BufferedSocketWrapper> {
const socket = new BufferedSocketWrapper(command, net.createConnection({ port: 5037 }));
async function open(command: string, port: number = 5037, serial?: string): Promise<BufferedSocketWrapper> {
const socket = new BufferedSocketWrapper(command, net.createConnection({ port }));
if (serial) {
await socket.write(encodeMessage(`host:transport:${serial}`));
const status = await socket.read(4);

View file

@ -366,3 +366,7 @@ export type APIResponse = {
headers: HeadersArray,
body: Buffer,
};
export type AndroidDeviceOptions = {
port?: number
};

View file

@ -11020,8 +11020,14 @@ export {};
export interface Android {
/**
* Returns the list of detected Android devices.
* @param options
*/
devices(): Promise<Array<AndroidDevice>>;
devices(options?: {
/**
* Optional port to establish ADB server connection.
*/
port?: number;
}): Promise<Array<AndroidDevice>>;
/**
* This setting will change the default maximum time for all the methods accepting `timeout` option.
@ -11032,7 +11038,7 @@ export interface Android {
/**
* [AndroidDevice] represents a connected device, either real hardware or emulated. Devices can be obtained using
* [android.devices()](https://playwright.dev/docs/api/class-android#android-devices).
* [android.devices([options])](https://playwright.dev/docs/api/class-android#android-devices).
*/
export interface AndroidDevice {
/**

View file

@ -14,6 +14,7 @@
* limitations under the License.
*/
import net from 'net';
import { androidTest as test, expect } from './androidTest';
test('androidDevice.model', async function({ androidDevice }) {
@ -56,6 +57,32 @@ test('should be able to send CDP messages', async ({ androidDevice }) => {
expect(evalResponse.result.value).toBe(3);
});
test('should be able to use a custom port', async function({ playwright }) {
const proxyPort = 5038;
let countOfIncomingConnections = 0;
let countOfConnections = 0;
const server = net.createServer(socket => {
++countOfIncomingConnections;
++countOfConnections;
socket.on('close', () => countOfConnections--);
const client = net.connect(5037);
socket.pipe(client).pipe(socket);
});
await new Promise<void>(resolve => server.listen(proxyPort, resolve));
const devices = await playwright._android.devices({ port: proxyPort });
expect(countOfIncomingConnections).toBeGreaterThanOrEqual(1);
expect(devices).toHaveLength(1);
const device = devices[0];
const value = await device.shell('echo foobar');
expect(value.toString()).toBe('foobar\n');
await device.close();
await new Promise(resolve => server.close(resolve));
expect(countOfIncomingConnections).toBeGreaterThanOrEqual(1);
expect(countOfConnections).toBe(0);
});
test('should be able to pass context options', async ({ androidDevice, httpsServer }) => {
const context = await androidDevice.launchBrowser({
colorScheme: 'dark',