Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Android] Enable usage of custom instrumentation test runners #675

Merged
merged 4 commits into from
Apr 24, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions detox/src/devices/AndroidDriver.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,10 @@ class AndroidDriver extends DeviceDriverBase {
return this.instrumentationProcess.pid;
}

const testRunner = await this.adb.getInstrumentationRunner(deviceId, bundleId);

this.instrumentationProcess = spawn(this.adb.adbBin, [`-s`, `${deviceId}`, `shell`, `am`, `instrument`, `-w`, `-r`, `${args.join(' ')}`, `-e`, `debug`,
`false`, `${bundleId}.test/android.support.test.runner.AndroidJUnitRunner`]);
`false`, testRunner]);
log.verbose(this.instrumentationProcess.spawnargs.join(" "));
log.verbose('Instrumentation spawned, childProcess.pid: ', this.instrumentationProcess.pid);
this.instrumentationProcess.stdout.on('data', function(data) {
Expand All @@ -96,7 +98,7 @@ class AndroidDriver extends DeviceDriverBase {
const call = invoke.call(invoke.Android.Class("com.wix.detox.Detox"), 'startActivityFromUrl', invoke.Android.String(params.url));
await this.invocationManager.execute(call);
}

//The other types are not yet supported.
}

Expand Down Expand Up @@ -171,7 +173,7 @@ class AndroidDriver extends DeviceDriverBase {
landscape: 1, // top at left side landscape
portrait: 0 // non-reversed portrait.
};

const call = invoke.call(invoke.Android.Class(EspressoDetox), 'changeOrientation', invoke.Android.Integer(orientationMapping[orientation]));
await this.invocationManager.execute(call);
}
Expand Down
18 changes: 17 additions & 1 deletion detox/src/devices/android/ADB.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ class ADB {
await this.adbCmd(deviceId, `install -r -g ${apkPath}`);
} else {
await this.adbCmd(deviceId, `install -rg ${apkPath}`);
}
}
}

async uninstall(deviceId, appId) {
Expand Down Expand Up @@ -102,6 +102,22 @@ class ADB {
async sleep(ms = 0) {
return new Promise((resolve, reject) => setTimeout(resolve, ms));
}

async listInstrumentation(deviceId) {
return await this.shell(deviceId, 'pm list instrumentation');
}

instrumentationRunnerForBundleId(instrumentationRunners, bundleId) {
const runnerForBundleRegEx = new RegExp(`^instrumentation:(.*) \\(target=${bundleId.replace(new RegExp('\\.', 'g'), "\\.")}\\)$`, 'gm');
return _.get(runnerForBundleRegEx.exec(instrumentationRunners), [1], 'undefined');
}

async getInstrumentationRunner(deviceId, bundleId) {
const instrumentationRunners = await this.listInstrumentation(deviceId);
const instrumentationRunner = this.instrumentationRunnerForBundleId(instrumentationRunners, bundleId);
if (instrumentationRunner === 'undefined') throw new Error(`No instrumentation runner found on device ${deviceId} for package ${bundleId}`);
return instrumentationRunner;
}
}

module.exports = ADB;
92 changes: 71 additions & 21 deletions detox/src/devices/android/ADB.test.js
Original file line number Diff line number Diff line change
@@ -1,44 +1,39 @@
//Disabled until we can create a build environment for Android in CI
xdescribe('ADB', () => {
describe('ADB', () => {
let ADB;
let adb;
let EmulatorTelnet;
let exec;

beforeEach(() => {
jest.mock('npmlog');
jest.mock('../../utils/environment', () => ({
getAndroidSDKPath: () => '/dev/null',
}));

ADB = require('./ADB');

jest.mock('./EmulatorTelnet');
EmulatorTelnet = require('./EmulatorTelnet');

jest.mock('../../utils/exec');

jest.mock('../../utils/exec', () => {
const exec = jest.fn();
exec.mockReturnValue({ stdout: '' });
return { execWithRetriesAndLogs: exec };
});
exec = require('../../utils/exec').execWithRetriesAndLogs;

adb = new ADB();
});

it(`Parse 'adb device' output`, async () => {
const adbDevicesConsoleOutput = "List of devices attached\n"
+ "192.168.60.101:5555\tdevice\n"
+ "emulator-5556\tdevice\n"
+ "emulator-5554\tdevice\n"
+ "sx432wsds\tdevice\n"
+ "\n";
exec.mockReturnValue(Promise.resolve({stdout: adbDevicesConsoleOutput}));

const parsedDevices = [
{"adbName": "192.168.60.101:5555", "name": "192.168.60.101:5555", "type": "genymotion"},
{"adbName": "emulator-5556", "name": undefined, "port": "5556", "type": "emulator"},
{"adbName": "emulator-5554", "name": undefined, "port": "5554", "type": "emulator"},
{"adbName": "sx432wsds", "name": "sx432wsds", "type": "device"}];

const devices = await adb.devices();
expect(devices).toEqual(parsedDevices);
it(`devices`, async () => {
await adb.devices();
expect(exec).toHaveBeenCalledTimes(1);
});

it(`install`, async () => {
await adb.install('path/to/app');
expect(exec).toHaveBeenCalledTimes(1);
expect(exec).toHaveBeenCalledTimes(2);
});

it(`uninstall`, async () => {
Expand All @@ -55,5 +50,60 @@ xdescribe('ADB', () => {
await adb.unlockScreen('deviceId');
expect(exec).toHaveBeenCalledTimes(1);
});

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we should consider dropping unit tests on ADB as well, but for now, don't delete this test

it(`listInstrumentation passes the right deviceId`, async () => {
const deviceId = 'aDeviceId';
const spyShell = jest.spyOn(adb, 'shell');

await adb.listInstrumentation(deviceId);

expect(spyShell).toBeCalledWith(deviceId, expect.any(String));
});

it(`Parse 'adb device' output`, async () => {
const adbDevicesConsoleOutput = "List of devices attached\n"
+ "192.168.60.101:5555\tdevice\n"
+ "emulator-5556\tdevice\n"
+ "emulator-5554\tdevice\n"
+ "sx432wsds\tdevice\n"
+ "\n";

const spyDevices = jest.spyOn(adb, 'devices');
spyDevices.mockReturnValue(Promise.resolve(adbDevicesConsoleOutput));

const parsedDevices = [
{ "adbName": "192.168.60.101:5555", "name": "192.168.60.101:5555", "type": "genymotion" },
{ "adbName": "emulator-5556", "name": undefined, "port": "5556", "type": "emulator" },
{ "adbName": "emulator-5554", "name": undefined, "port": "5554", "type": "emulator" },
{ "adbName": "sx432wsds", "name": "sx432wsds", "type": "device" }];

const actual = await adb.parseAdbDevicesConsoleOutput(adbDevicesConsoleOutput);
expect(actual).toEqual(parsedDevices);
});

it(`getInstrumentationRunner passes the right deviceId`, async () => {
const deviceId = 'aDeviceId';
const spyRunnerForBundle = jest.spyOn(adb, 'instrumentationRunnerForBundleId');
spyRunnerForBundle.mockReturnValue('');
const spyShell = jest.spyOn(adb, 'shell');

await adb.getInstrumentationRunner(deviceId, 'com.whatever.package');

expect(spyShell).toBeCalledWith(deviceId, expect.any(String));
});

it(`instrumentationRunnerForBundleId parses the correct runner for the package`, async () => {
const expectedRunner = "com.example.android.apis/.app.LocalSampleInstrumentation";
const expectedPackage = "com.example.android.apis";
const instrumentationRunnersShellOutput =
"instrumentation:com.android.emulator.smoketests/android.support.test.runner.AndroidJUnitRunner (target=com.android.emulator.smoketests)\n" +
"instrumentation:com.android.smoketest.tests/com.android.smoketest.SmokeTestRunner (target=com.android.smoketest)\n" +
`instrumentation:${expectedRunner} (target=${expectedPackage})\n` +
"instrumentation:org.chromium.webview_shell/.WebViewLayoutTestRunner (target=org.chromium.webview_shell)\n";

const result = await adb.instrumentationRunnerForBundleId(instrumentationRunnersShellOutput, expectedPackage);

expect(result).toEqual(expectedRunner);
});
});