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. Returns the list of detected Android devices.
### option: Android.devices.port
- `port` <[int]>
Optional port to establish ADB server connection.
## method: Android.setDefaultTimeout ## method: Android.setDefaultTimeout
This setting will change the default maximum time for all the methods accepting [`param: timeout`] option. 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 }); this._channel.setDefaultTimeoutNoReply({ timeout });
} }
async devices(): Promise<AndroidDevice[]> { async devices(options: { port?: number }): Promise<AndroidDevice[]> {
const { devices } = await this._channel.devices(); const { devices } = await this._channel.devices(options);
return devices.map(d => AndroidDevice.from(d)); return devices.map(d => AndroidDevice.from(d));
} }
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -11020,8 +11020,14 @@ export {};
export interface Android { export interface Android {
/** /**
* Returns the list of detected Android devices. * 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. * 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 * [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 { export interface AndroidDevice {
/** /**

View file

@ -14,6 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
import net from 'net';
import { androidTest as test, expect } from './androidTest'; import { androidTest as test, expect } from './androidTest';
test('androidDevice.model', async function({ androidDevice }) { 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); 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 }) => { test('should be able to pass context options', async ({ androidDevice, httpsServer }) => {
const context = await androidDevice.launchBrowser({ const context = await androidDevice.launchBrowser({
colorScheme: 'dark', colorScheme: 'dark',