From df89f303bc41573eda8434f34607e91142776197 Mon Sep 17 00:00:00 2001 From: Guillermo Orellana Date: Thu, 19 Apr 2018 00:11:52 +0100 Subject: [PATCH 1/4] query pm and regex through results --- detox/src/devices/AndroidDriver.js | 13 +++++++-- detox/src/devices/AndroidDriver.test.js | 37 +++++++++++++++++++++++++ detox/src/devices/android/ADB.js | 6 +++- detox/src/devices/android/ADB.test.js | 5 ++++ 4 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 detox/src/devices/AndroidDriver.test.js diff --git a/detox/src/devices/AndroidDriver.js b/detox/src/devices/AndroidDriver.js index f4609b8d4a..76dfd99ef8 100644 --- a/detox/src/devices/AndroidDriver.js +++ b/detox/src/devices/AndroidDriver.js @@ -60,6 +60,13 @@ class AndroidDriver extends DeviceDriverBase { } } + async getInstrumentationRunner(deviceId, bundleId) { + const instrumentationRunners = await this.adb.listInstrumentation(deviceId); + const expr = new RegExp(`^instrumentation:(.*) \\(target=${bundleId.replace(new RegExp('\\.', 'g'), "\\.")}\\)$`, 'gm'); + const regexRes = expr.exec(instrumentationRunners); + return regexRes[1]; + } + async launch(deviceId, bundleId, launchArgs) { const args = []; _.forEach(launchArgs, (value, key) => { @@ -72,6 +79,8 @@ 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`]); log.verbose(this.instrumentationProcess.spawnargs.join(" ")); @@ -96,7 +105,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. } @@ -171,7 +180,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); } diff --git a/detox/src/devices/AndroidDriver.test.js b/detox/src/devices/AndroidDriver.test.js new file mode 100644 index 0000000000..38247e3b9b --- /dev/null +++ b/detox/src/devices/AndroidDriver.test.js @@ -0,0 +1,37 @@ +describe("AndroidDriver", () => { + let mockClient; + let driver; + let ADB; + + beforeEach(() => { + jest.mock("npmlog"); + + jest.mock("./android/ADB"); + ADB = require("./android/ADB"); + + const AndroidDriver = require("./AndroidDriver"); + + driver = new AndroidDriver(jest.fn()); + }); + + it("getInstrumentationRunner", async () => { + const adbShellPmListInstrumentationOutput = + "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:com.example.android.apis/.app.LocalSampleInstrumentation (target=com.example.android.apis)\n" + + "instrumentation:org.chromium.webview_shell/.WebViewLayoutTestRunner (target=org.chromium.webview_shell)\n"; + + const adbMockInstance = ADB.mock.instances[0]; + adbMockInstance.listInstrumentation.mockReturnValue( + Promise.resolve(adbShellPmListInstrumentationOutput) + ); + + const result = await driver.getInstrumentationRunner( + "deviceId", + "com.example.android.apis" + ); + expect(result).toEqual( + "com.example.android.apis/.app.LocalSampleInstrumentation" + ); + }); +}); diff --git a/detox/src/devices/android/ADB.js b/detox/src/devices/android/ADB.js index 1f5bc06709..ac7498e29c 100644 --- a/detox/src/devices/android/ADB.js +++ b/detox/src/devices/android/ADB.js @@ -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) { @@ -102,6 +102,10 @@ 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'); + } } module.exports = ADB; diff --git a/detox/src/devices/android/ADB.test.js b/detox/src/devices/android/ADB.test.js index bf122db456..3bdc777f3e 100644 --- a/detox/src/devices/android/ADB.test.js +++ b/detox/src/devices/android/ADB.test.js @@ -55,5 +55,10 @@ xdescribe('ADB', () => { await adb.unlockScreen('deviceId'); expect(exec).toHaveBeenCalledTimes(1); }); + + it('listInstrumentation', async () => { + await adb.listInstrumentation('deviceId'); + expect(exec).toHaveBeenCalledTimes(1); + }); }); From 86bfd0283e1275818761c34b2676dc134456c2c1 Mon Sep 17 00:00:00 2001 From: Guillermo Orellana Date: Thu, 19 Apr 2018 13:12:37 +0100 Subject: [PATCH 2/4] PR comments --- detox/src/devices/AndroidDriver.js | 11 +--- detox/src/devices/AndroidDriver.test.js | 37 ------------- detox/src/devices/android/ADB.js | 7 +++ detox/src/devices/android/ADB.test.js | 69 +++++++++++++++++-------- 4 files changed, 56 insertions(+), 68 deletions(-) delete mode 100644 detox/src/devices/AndroidDriver.test.js diff --git a/detox/src/devices/AndroidDriver.js b/detox/src/devices/AndroidDriver.js index 76dfd99ef8..19f1136c70 100644 --- a/detox/src/devices/AndroidDriver.js +++ b/detox/src/devices/AndroidDriver.js @@ -60,13 +60,6 @@ class AndroidDriver extends DeviceDriverBase { } } - async getInstrumentationRunner(deviceId, bundleId) { - const instrumentationRunners = await this.adb.listInstrumentation(deviceId); - const expr = new RegExp(`^instrumentation:(.*) \\(target=${bundleId.replace(new RegExp('\\.', 'g'), "\\.")}\\)$`, 'gm'); - const regexRes = expr.exec(instrumentationRunners); - return regexRes[1]; - } - async launch(deviceId, bundleId, launchArgs) { const args = []; _.forEach(launchArgs, (value, key) => { @@ -79,10 +72,10 @@ class AndroidDriver extends DeviceDriverBase { return this.instrumentationProcess.pid; } - const testRunner = await this.adb.getInstrumentationRunner(deviceId, bundleId) + 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) { diff --git a/detox/src/devices/AndroidDriver.test.js b/detox/src/devices/AndroidDriver.test.js deleted file mode 100644 index 38247e3b9b..0000000000 --- a/detox/src/devices/AndroidDriver.test.js +++ /dev/null @@ -1,37 +0,0 @@ -describe("AndroidDriver", () => { - let mockClient; - let driver; - let ADB; - - beforeEach(() => { - jest.mock("npmlog"); - - jest.mock("./android/ADB"); - ADB = require("./android/ADB"); - - const AndroidDriver = require("./AndroidDriver"); - - driver = new AndroidDriver(jest.fn()); - }); - - it("getInstrumentationRunner", async () => { - const adbShellPmListInstrumentationOutput = - "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:com.example.android.apis/.app.LocalSampleInstrumentation (target=com.example.android.apis)\n" + - "instrumentation:org.chromium.webview_shell/.WebViewLayoutTestRunner (target=org.chromium.webview_shell)\n"; - - const adbMockInstance = ADB.mock.instances[0]; - adbMockInstance.listInstrumentation.mockReturnValue( - Promise.resolve(adbShellPmListInstrumentationOutput) - ); - - const result = await driver.getInstrumentationRunner( - "deviceId", - "com.example.android.apis" - ); - expect(result).toEqual( - "com.example.android.apis/.app.LocalSampleInstrumentation" - ); - }); -}); diff --git a/detox/src/devices/android/ADB.js b/detox/src/devices/android/ADB.js index ac7498e29c..64c2f3223b 100644 --- a/detox/src/devices/android/ADB.js +++ b/detox/src/devices/android/ADB.js @@ -106,6 +106,13 @@ class ADB { async listInstrumentation(deviceId) { return await this.shell(deviceId, 'pm list instrumentation'); } + + async getInstrumentationRunner(deviceId, bundleId) { + const instrumentationRunners = await this.listInstrumentation(deviceId); + const expr = new RegExp(`^instrumentation:(.*) \\(target=${bundleId.replace(new RegExp('\\.', 'g'), "\\.")}\\)$`, 'gm'); + const regexRes = expr.exec(instrumentationRunners); + return regexRes[1]; + } } module.exports = ADB; diff --git a/detox/src/devices/android/ADB.test.js b/detox/src/devices/android/ADB.test.js index 3bdc777f3e..4de1745371 100644 --- a/detox/src/devices/android/ADB.test.js +++ b/detox/src/devices/android/ADB.test.js @@ -1,5 +1,4 @@ -//Disabled until we can create a build environment for Android in CI -xdescribe('ADB', () => { +describe('ADB', () => { let ADB; let adb; let EmulatorTelnet; @@ -12,33 +11,24 @@ xdescribe('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 () => { @@ -56,9 +46,44 @@ xdescribe('ADB', () => { expect(exec).toHaveBeenCalledTimes(1); }); - it('listInstrumentation', async () => { + it(`listInstrumentation`, async () => { await adb.listInstrumentation('deviceId'); expect(exec).toHaveBeenCalledTimes(1); }); + + 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`, async () => { + const adbShellPmListInstrumentationOutput = + "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:com.example.android.apis/.app.LocalSampleInstrumentation (target=com.example.android.apis)\n" + + "instrumentation:org.chromium.webview_shell/.WebViewLayoutTestRunner (target=org.chromium.webview_shell)\n"; + + const spyListInstrumentation = jest.spyOn(adb, 'listInstrumentation'); + spyListInstrumentation.mockReturnValue(Promise.resolve(adbShellPmListInstrumentationOutput)); + + const result = await adb.getInstrumentationRunner("deviceId", "com.example.android.apis"); + expect(result).toEqual("com.example.android.apis/.app.LocalSampleInstrumentation"); + }); }); From a49e7e3bed8affd9ad30e4775e8e2145281e15c9 Mon Sep 17 00:00:00 2001 From: Guillermo Orellana Date: Thu, 19 Apr 2018 15:06:00 +0100 Subject: [PATCH 3/4] mock android SDK path --- detox/src/devices/android/ADB.test.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/detox/src/devices/android/ADB.test.js b/detox/src/devices/android/ADB.test.js index 4de1745371..bcf94a0536 100644 --- a/detox/src/devices/android/ADB.test.js +++ b/detox/src/devices/android/ADB.test.js @@ -6,14 +6,19 @@ describe('ADB', () => { beforeEach(() => { jest.mock('npmlog'); + jest.mock('../../utils/environment', () => ({ + getAndroidSDKPath: () => '/dev/null', + })); + ADB = require('./ADB'); jest.mock('./EmulatorTelnet'); EmulatorTelnet = require('./EmulatorTelnet'); + jest.mock('../../utils/exec', () => { const exec = jest.fn(); - exec.mockReturnValue({ stdout: '' }) + exec.mockReturnValue({ stdout: '' }); return { execWithRetriesAndLogs: exec }; }); exec = require('../../utils/exec').execWithRetriesAndLogs; From b28ea66660f9240f71969fe546706920e9c62895 Mon Sep 17 00:00:00 2001 From: Guillermo Orellana Date: Mon, 23 Apr 2018 23:29:45 +0100 Subject: [PATCH 4/4] More modularity and better test readability --- detox/src/devices/android/ADB.js | 13 +++++++--- detox/src/devices/android/ADB.test.js | 35 +++++++++++++++++++-------- 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/detox/src/devices/android/ADB.js b/detox/src/devices/android/ADB.js index 64c2f3223b..5a4238ab82 100644 --- a/detox/src/devices/android/ADB.js +++ b/detox/src/devices/android/ADB.js @@ -107,11 +107,16 @@ class ADB { 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 expr = new RegExp(`^instrumentation:(.*) \\(target=${bundleId.replace(new RegExp('\\.', 'g'), "\\.")}\\)$`, 'gm'); - const regexRes = expr.exec(instrumentationRunners); - return regexRes[1]; + 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; } } diff --git a/detox/src/devices/android/ADB.test.js b/detox/src/devices/android/ADB.test.js index bcf94a0536..5f5dd9e83d 100644 --- a/detox/src/devices/android/ADB.test.js +++ b/detox/src/devices/android/ADB.test.js @@ -51,9 +51,13 @@ describe('ADB', () => { expect(exec).toHaveBeenCalledTimes(1); }); - it(`listInstrumentation`, async () => { - await adb.listInstrumentation('deviceId'); - expect(exec).toHaveBeenCalledTimes(1); + 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 () => { @@ -77,18 +81,29 @@ describe('ADB', () => { expect(actual).toEqual(parsedDevices); }); - it(`getInstrumentationRunner`, async () => { - const adbShellPmListInstrumentationOutput = + 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:com.example.android.apis/.app.LocalSampleInstrumentation (target=com.example.android.apis)\n" + + `instrumentation:${expectedRunner} (target=${expectedPackage})\n` + "instrumentation:org.chromium.webview_shell/.WebViewLayoutTestRunner (target=org.chromium.webview_shell)\n"; - const spyListInstrumentation = jest.spyOn(adb, 'listInstrumentation'); - spyListInstrumentation.mockReturnValue(Promise.resolve(adbShellPmListInstrumentationOutput)); + const result = await adb.instrumentationRunnerForBundleId(instrumentationRunnersShellOutput, expectedPackage); - const result = await adb.getInstrumentationRunner("deviceId", "com.example.android.apis"); - expect(result).toEqual("com.example.android.apis/.app.LocalSampleInstrumentation"); + expect(result).toEqual(expectedRunner); }); });