From 4ee22d2f097ccfeaf0670b30b45caf4311df9ace Mon Sep 17 00:00:00 2001 From: Daniel Zlotin Date: Sun, 17 Sep 2017 12:07:32 +0300 Subject: [PATCH 01/20] remove old DeviceLoader --- detox/src/devices/DeviceLoader.js | 45 ------------------------------- 1 file changed, 45 deletions(-) delete mode 100644 detox/src/devices/DeviceLoader.js diff --git a/detox/src/devices/DeviceLoader.js b/detox/src/devices/DeviceLoader.js deleted file mode 100644 index 82a1f96666..0000000000 --- a/detox/src/devices/DeviceLoader.js +++ /dev/null @@ -1,45 +0,0 @@ -const log = require('npmlog'); -const _ = require('lodash'); -const exec = require('child-process-promise').exec; -const path = require('path'); -const fs = require('fs'); -const os = require('os'); -const FBsimctl = require('./Fbsimctl'); -log.level = 'verbose'; - -let fbsimctl = new FBsimctl(); - -const deviceList = ['iPhone 5s', 'iPhone 6s', 'iPhone 7 Plus']; - -async function start() { - - const appPath = "ios/build/Build/Products/Release-iphonesimulator/example.app"; - const bundleId = await getBundleIdFromApp(appPath); - _.forEach(deviceList, async(device) => { - let simulatorUdid = await fbsimctl.list(device); - await fbsimctl.boot(simulatorUdid); - await fbsimctl.install(simulatorUdid, _getAppAbsolutePath(appPath)); - await fbsimctl.launch(simulatorUdid, bundleId, []) - }); -} - -async function getBundleIdFromApp(appPath) { - const absPath = _getAppAbsolutePath(appPath); - try { - const result = await exec(`/usr/libexec/PlistBuddy -c "Print CFBundleIdentifier" ${path.join(absPath, 'Info.plist')}`); - return _.trim(result.stdout); - } catch (ex) { - throw new Error(`field CFBundleIdentifier not found inside Info.plist of app binary at ${absPath}`); - } -} - -function _getAppAbsolutePath(appPath) { - const absPath = path.join(process.cwd(), appPath); - if (fs.existsSync(absPath)) { - return absPath; - } else { - throw new Error(`app binary not found at ${absPath}, did you build it?`); - } -} - -start(); \ No newline at end of file From f930f5d9cc64493bf2e591bd5464f405a78089e3 Mon Sep 17 00:00:00 2001 From: Daniel Zlotin Date: Tue, 19 Sep 2017 17:56:23 +0300 Subject: [PATCH 02/20] applesimutils list --- detox/.vscode/settings.json | 3 +++ detox/package.json | 2 +- detox/src/devices/AppleSimUtils.js | 15 +++++++++++++-- detox/src/devices/AppleSimUtils.test.js | 20 +++++++++++++++++++- 4 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 detox/.vscode/settings.json diff --git a/detox/.vscode/settings.json b/detox/.vscode/settings.json new file mode 100644 index 0000000000..a92f73ffdf --- /dev/null +++ b/detox/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "eslint.enable": false +} \ No newline at end of file diff --git a/detox/package.json b/detox/package.json index 64f7d0212c..e2442a269d 100644 --- a/detox/package.json +++ b/detox/package.json @@ -25,7 +25,7 @@ "lint": "eslint src", "unit": "jest --coverage --verbose", "test": "npm run unit", - "unit:watch": "jest --watch --coverage --verbose", + "unit:watch": "jest --watch", "postinstall": "scripts/postinstall.sh", "prepublish": "npm run build" }, diff --git a/detox/src/devices/AppleSimUtils.js b/detox/src/devices/AppleSimUtils.js index bbe1e29cf8..8df773a38f 100644 --- a/detox/src/devices/AppleSimUtils.js +++ b/detox/src/devices/AppleSimUtils.js @@ -9,12 +9,23 @@ class AppleSimUtils { successful: 'Permissions are set' }; let permissions = []; - _.forEach(permissionsObj, function(shouldAllow, permission) { + _.forEach(permissionsObj, function (shouldAllow, permission) { permissions.push(permission + '=' + shouldAllow); }); - const options = {args: `--simulator ${udid} --bundle ${bundleId} --setPermissions ${_.join(permissions, ',')}`}; + const options = { args: `--simulator ${udid} --bundle ${bundleId} --setPermissions ${_.join(permissions, ',')}` }; await this._execAppleSimUtilsCommand(options, statusLogs, 1); + } + async list(query) { + const statusLogs = { + trying: `Listing devices...` + }; + let correctQuery = query; + if (_.includes(query, ',')) { + const parts = _.split(query, ','); + correctQuery = `${parts[0].trim()}, OS=${parts[1].trim()}`; + } + await this._execAppleSimUtilsCommand({ args: `--list "${correctQuery}" --maxResults=1` }, statusLogs, 1); } async _execAppleSimUtilsCommand(options, statusLogs, retries, interval) { diff --git a/detox/src/devices/AppleSimUtils.test.js b/detox/src/devices/AppleSimUtils.test.js index 7f3850a641..1108d71235 100644 --- a/detox/src/devices/AppleSimUtils.test.js +++ b/detox/src/devices/AppleSimUtils.test.js @@ -16,8 +16,26 @@ describe('AppleSimUtils', () => { }); it(`appleSimUtils setPermissions`, async () => { - appSimUtils.setPermissions(bundleId, simUdid, {permissions: {calendar: "YES"}}); + appSimUtils.setPermissions(bundleId, simUdid, { permissions: { calendar: "YES" } }); expect(exec.execWithRetriesAndLogs).toHaveBeenCalledTimes(1); }); + + it('list devices', async () => { + expect(exec.execWithRetriesAndLogs).not.toHaveBeenCalled(); + appSimUtils.list('iPhone 6'); + expect(exec.execWithRetriesAndLogs).toHaveBeenCalledTimes(1); + expect(exec.execWithRetriesAndLogs).toHaveBeenCalledWith('applesimutils', { + args: `--list "iPhone 6" --maxResults=1` + }, expect.anything(), 1, undefined); + }); + + it('list devices with os version adapted to new api', async () => { + expect(exec.execWithRetriesAndLogs).not.toHaveBeenCalled(); + appSimUtils.list('iPhone 6 , iOS 10.3'); + expect(exec.execWithRetriesAndLogs).toHaveBeenCalledTimes(1); + expect(exec.execWithRetriesAndLogs).toHaveBeenCalledWith('applesimutils', { + args: `--list "iPhone 6, OS=iOS 10.3" --maxResults=1` + }, expect.anything(), 1, undefined); + }); }); From f20fcbb241f949008b92b40d8fec118ee44c49c9 Mon Sep 17 00:00:00 2001 From: Daniel Zlotin Date: Wed, 20 Sep 2017 01:32:16 +0300 Subject: [PATCH 03/20] migrated findDeviceUUID --- detox/src/devices/AppleSimUtils.js | 39 +++++++++--- detox/src/devices/AppleSimUtils.test.js | 79 ++++++++++++++++++++----- detox/src/devices/Fbsimctl.js | 34 +---------- detox/src/devices/Fbsimctl.test.js | 15 ++--- 4 files changed, 101 insertions(+), 66 deletions(-) diff --git a/detox/src/devices/AppleSimUtils.js b/detox/src/devices/AppleSimUtils.js index 8df773a38f..c84557ca30 100644 --- a/detox/src/devices/AppleSimUtils.js +++ b/detox/src/devices/AppleSimUtils.js @@ -12,20 +12,24 @@ class AppleSimUtils { _.forEach(permissionsObj, function (shouldAllow, permission) { permissions.push(permission + '=' + shouldAllow); }); - const options = { args: `--simulator ${udid} --bundle ${bundleId} --setPermissions ${_.join(permissions, ',')}` }; - await this._execAppleSimUtilsCommand(options, statusLogs, 1); + await this._execAppleSimUtilsCommand({ + args: `--simulator ${udid} --bundle ${bundleId} --setPermissions ${_.join(permissions, ',')}` + }, statusLogs, 1); } - async list(query) { + async findDeviceUUID(query) { const statusLogs = { - trying: `Listing devices...` + trying: `Searching for device matching ${query}...` }; - let correctQuery = query; - if (_.includes(query, ',')) { - const parts = _.split(query, ','); - correctQuery = `${parts[0].trim()}, OS=${parts[1].trim()}`; + let correctQuery = correctQueryWithOS(query); + const response = await this._execAppleSimUtilsCommand({ args: `--list "${correctQuery}" --maxResults=1` }, statusLogs, 1); + const parsed = parseStdout(response); + const udid = _.get(parsed, [0, 'udid']); + if (!udid) { + throw new Error(`Can't find a simulator to match with "${query}", run 'xcrun simctl list' to list your supported devices. + It is advised to only state a device type, and not to state iOS version, e.g. "iPhone 7"`); } - await this._execAppleSimUtilsCommand({ args: `--list "${correctQuery}" --maxResults=1` }, statusLogs, 1); + return udid; } async _execAppleSimUtilsCommand(options, statusLogs, retries, interval) { @@ -34,4 +38,21 @@ class AppleSimUtils { } } +function correctQueryWithOS(query) { + let correctQuery = query; + if (_.includes(query, ',')) { + const parts = _.split(query, ','); + correctQuery = `${parts[0].trim()}, OS=${parts[1].trim()}`; + } + return correctQuery; +} + +function parseStdout(response) { + const stdout = _.get(response, 'stdout'); + if (_.isEmpty(stdout)) { + return []; + } + return JSON.parse(stdout); +} + module.exports = AppleSimUtils; diff --git a/detox/src/devices/AppleSimUtils.test.js b/detox/src/devices/AppleSimUtils.test.js index 1108d71235..a34ecf71f6 100644 --- a/detox/src/devices/AppleSimUtils.test.js +++ b/detox/src/devices/AppleSimUtils.test.js @@ -20,22 +20,71 @@ describe('AppleSimUtils', () => { expect(exec.execWithRetriesAndLogs).toHaveBeenCalledTimes(1); }); - it('list devices', async () => { - expect(exec.execWithRetriesAndLogs).not.toHaveBeenCalled(); - appSimUtils.list('iPhone 6'); - expect(exec.execWithRetriesAndLogs).toHaveBeenCalledTimes(1); - expect(exec.execWithRetriesAndLogs).toHaveBeenCalledWith('applesimutils', { - args: `--list "iPhone 6" --maxResults=1` - }, expect.anything(), 1, undefined); - }); + describe('findDeviceUUID', () => { + it('correct params', async () => { + expect(exec.execWithRetriesAndLogs).not.toHaveBeenCalled(); + try { + await appSimUtils.findDeviceUUID('iPhone 6'); + } catch (e) { } + expect(exec.execWithRetriesAndLogs).toHaveBeenCalledTimes(1); + expect(exec.execWithRetriesAndLogs).toHaveBeenCalledWith('applesimutils', { + args: `--list "iPhone 6" --maxResults=1` + }, expect.anything(), 1, undefined); + }); + + it('adapted to new api with optional OS', async () => { + try { + await appSimUtils.findDeviceUUID('iPhone 6 , iOS 10.3'); + } catch (e) { } + expect(exec.execWithRetriesAndLogs).toHaveBeenCalledWith('applesimutils', { + args: `--list "iPhone 6, OS=iOS 10.3" --maxResults=1` + }, expect.anything(), 1, undefined); + }); + + it('returns udid from found device', async () => { + exec.execWithRetriesAndLogs.mockReturnValueOnce(Promise.resolve({ + stdout: JSON.stringify([ + { + "state": "Shutdown", + "availability": "(available)", + "name": "iPhone 6", + "udid": "the uuid", + "os": { + "version": "10.3.1", + "availability": "(available)", + "name": "iOS 10.3", + "identifier": "com.apple.CoreSimulator.SimRuntime.iOS-10-3", + "buildversion": "14E8301" + } + } + ]) + })); + const result = await appSimUtils.findDeviceUUID('iPhone 7'); + expect(result).toEqual('the uuid'); + }); + + describe('throws on bad response', () => { + const args = [ + null, + {}, + { stdout: '' }, + { stdout: '[]' }, + { stdout: '[{}]' }, + { stdout: '[{ "udid": "" }]' } + ]; + args.forEach((arg) => { + it(`invalid input ${JSON.stringify(arg)}`, async () => { + exec.execWithRetriesAndLogs.mockReturnValueOnce(Promise.resolve(arg)); + try { + await appSimUtils.findDeviceUUID('iPhone 6, iOS 10'); + fail('should throw'); + } catch (e) { + expect(e.message).toMatch(`Can't find a simulator to match with "iPhone 6, iOS 10"`); + } + }); + }) + }); - it('list devices with os version adapted to new api', async () => { - expect(exec.execWithRetriesAndLogs).not.toHaveBeenCalled(); - appSimUtils.list('iPhone 6 , iOS 10.3'); - expect(exec.execWithRetriesAndLogs).toHaveBeenCalledTimes(1); - expect(exec.execWithRetriesAndLogs).toHaveBeenCalledWith('applesimutils', { - args: `--list "iPhone 6, OS=iOS 10.3" --maxResults=1` - }, expect.anything(), 1, undefined); }); }); diff --git a/detox/src/devices/Fbsimctl.js b/detox/src/devices/Fbsimctl.js index 62ca57a521..b11832e0d7 100644 --- a/detox/src/devices/Fbsimctl.js +++ b/detox/src/devices/Fbsimctl.js @@ -28,27 +28,8 @@ class Fbsimctl { } async list(device) { - const statusLogs = { - trying: `Listing devices...` - }; - const query = this._getQueryFromDevice(device); - const options = {args: `${query} --first 1 --simulators list`}; - let result = {}; - let simId; - try { - result = await this._execFbsimctlCommand(options, statusLogs, 1); - const parsedJson = JSON.parse(result.stdout); - simId = _.get(parsedJson, 'subject.udid'); - } catch (ex) { - log.error(ex); - } - - if (!simId) { - throw new Error('Can\'t find a simulator to match with \'' + device + '\', run \'fbsimctl list\' to list your supported devices.\n' - + 'It is advised to only state a device type, and not to state iOS version, e.g. \'iPhone 7\''); - } - - return simId; + const AppleSimUtils = require('./AppleSimUtils'); + return new AppleSimUtils().findDeviceUUID(device); } async boot(udid) { @@ -71,7 +52,7 @@ class Fbsimctl { log.info(`Device ${udid} is already booted`); return; } - + if(initialState === 'Booting') { log.info(`Device ${udid} is already booting`); } else { @@ -193,15 +174,6 @@ class Fbsimctl { } return frameworkPath; } - - _getQueryFromDevice(device) { - let res = ''; - const deviceParts = device.split(','); - for (let i = 0; i < deviceParts.length; i++) { - res += `"${deviceParts[i].trim()}" `; - } - return res.trim(); - } } module.exports = Fbsimctl; diff --git a/detox/src/devices/Fbsimctl.test.js b/detox/src/devices/Fbsimctl.test.js index ab77b21581..17e574d46a 100644 --- a/detox/src/devices/Fbsimctl.test.js +++ b/detox/src/devices/Fbsimctl.test.js @@ -23,13 +23,6 @@ describe('Fbsimctl', () => { fbsimctl = new Fbsimctl(); }); - it(`list() - specify a valid simulator should return that simulator's UDID`, async() => { - const result = returnSuccessfulWithValue(listAsimUdidAtState(simUdid, "Shutdown")); - exec.mockReturnValue(Promise.resolve(result)); - - expect(await fbsimctl.list('iPhone 7')).toEqual(simUdid); - }); - it(`list() - specify an invalid simulator should throw an Error`, async() => { const returnValue = {}; const result = returnSuccessfulWithValue(returnValue); @@ -58,7 +51,7 @@ describe('Fbsimctl', () => { it(`boot() - when shutting down, should wait for the device`, async() => { fbsimctl._execFbsimctlCommand = jest.fn(() => ({stdout: `{"subject": {"state": "Shutting Down"}}`})); - + try { await fbsimctl.boot(simUdid); fail('should throw'); @@ -69,7 +62,7 @@ describe('Fbsimctl', () => { it(`boot() - when state is undefined, should wait for the device`, async() => { fbsimctl._execFbsimctlCommand = jest.fn(() => ({})); - + try { await fbsimctl.boot(simUdid); fail('should throw'); @@ -190,7 +183,7 @@ describe('Fbsimctl', () => { const errorResult = returnErrorWithValue(''); exec.mockReturnValue(Promise.reject(errorResult)); const options = {args: `an argument`}; - + try { await fbsimctl._execFbsimctlCommand(options, '', 10, 1); } catch (object) { @@ -203,7 +196,7 @@ describe('Fbsimctl', () => { const resolvedPromise = Promise.resolve(successfulResult); exec.mockReturnValueOnce(resolvedPromise); - + const options = {args: `an argument`}; expect(await fbsimctl._execFbsimctlCommand(options, '', 10, 1)).toEqual(successfulResult); }); From f460b5c142e0e1efd4b6a26bf44e0217ba231625 Mon Sep 17 00:00:00 2001 From: Daniel Zlotin Date: Wed, 20 Sep 2017 01:46:03 +0300 Subject: [PATCH 04/20] migrated list --- detox/src/devices/AppleSimUtils.js | 3 +- detox/src/devices/AppleSimUtils.test.js | 10 ++--- detox/src/devices/Fbsimctl.js | 49 ++++++++++--------------- detox/src/devices/Fbsimctl.test.js | 26 ------------- detox/src/devices/SimulatorDriver.js | 2 +- 5 files changed, 28 insertions(+), 62 deletions(-) diff --git a/detox/src/devices/AppleSimUtils.js b/detox/src/devices/AppleSimUtils.js index c84557ca30..b53825c914 100644 --- a/detox/src/devices/AppleSimUtils.js +++ b/detox/src/devices/AppleSimUtils.js @@ -1,5 +1,6 @@ const _ = require('lodash'); const exec = require('../utils/exec'); +const log = require('npmlog'); class AppleSimUtils { @@ -17,7 +18,7 @@ class AppleSimUtils { }, statusLogs, 1); } - async findDeviceUUID(query) { + async findDeviceUDID(query) { const statusLogs = { trying: `Searching for device matching ${query}...` }; diff --git a/detox/src/devices/AppleSimUtils.test.js b/detox/src/devices/AppleSimUtils.test.js index a34ecf71f6..118c6b656b 100644 --- a/detox/src/devices/AppleSimUtils.test.js +++ b/detox/src/devices/AppleSimUtils.test.js @@ -20,11 +20,11 @@ describe('AppleSimUtils', () => { expect(exec.execWithRetriesAndLogs).toHaveBeenCalledTimes(1); }); - describe('findDeviceUUID', () => { + describe('findDeviceUDID', () => { it('correct params', async () => { expect(exec.execWithRetriesAndLogs).not.toHaveBeenCalled(); try { - await appSimUtils.findDeviceUUID('iPhone 6'); + await appSimUtils.findDeviceUDID('iPhone 6'); } catch (e) { } expect(exec.execWithRetriesAndLogs).toHaveBeenCalledTimes(1); expect(exec.execWithRetriesAndLogs).toHaveBeenCalledWith('applesimutils', { @@ -34,7 +34,7 @@ describe('AppleSimUtils', () => { it('adapted to new api with optional OS', async () => { try { - await appSimUtils.findDeviceUUID('iPhone 6 , iOS 10.3'); + await appSimUtils.findDeviceUDID('iPhone 6 , iOS 10.3'); } catch (e) { } expect(exec.execWithRetriesAndLogs).toHaveBeenCalledWith('applesimutils', { args: `--list "iPhone 6, OS=iOS 10.3" --maxResults=1` @@ -59,7 +59,7 @@ describe('AppleSimUtils', () => { } ]) })); - const result = await appSimUtils.findDeviceUUID('iPhone 7'); + const result = await appSimUtils.findDeviceUDID('iPhone 7'); expect(result).toEqual('the uuid'); }); @@ -76,7 +76,7 @@ describe('AppleSimUtils', () => { it(`invalid input ${JSON.stringify(arg)}`, async () => { exec.execWithRetriesAndLogs.mockReturnValueOnce(Promise.resolve(arg)); try { - await appSimUtils.findDeviceUUID('iPhone 6, iOS 10'); + await appSimUtils.findDeviceUDID('iPhone 6, iOS 10'); fail('should throw'); } catch (e) { expect(e.message).toMatch(`Can't find a simulator to match with "iPhone 6, iOS 10"`); diff --git a/detox/src/devices/Fbsimctl.js b/detox/src/devices/Fbsimctl.js index b11832e0d7..d8d186f841 100644 --- a/detox/src/devices/Fbsimctl.js +++ b/detox/src/devices/Fbsimctl.js @@ -23,49 +23,40 @@ class LogsInfo { class Fbsimctl { - constructor() { - this._operationCounter = 0; - } - - async list(device) { - const AppleSimUtils = require('./AppleSimUtils'); - return new AppleSimUtils().findDeviceUUID(device); - } - async boot(udid) { let initialState; - await retry({retries: 10, interval: 1000}, async() => { - const initialStateCmdResult = await this._execFbsimctlCommand({args: `${udid} list`}, undefined, 1); + await retry({ retries: 10, interval: 1000 }, async () => { + const initialStateCmdResult = await this._execFbsimctlCommand({ args: `${udid} list` }, undefined, 1); initialState = _.get(initialStateCmdResult, 'stdout', '') === '' ? undefined : - _.get(JSON.parse(_.get(initialStateCmdResult, 'stdout')), 'subject.state'); - if(initialState === undefined) { + _.get(JSON.parse(_.get(initialStateCmdResult, 'stdout')), 'subject.state'); + if (initialState === undefined) { log.info(`Couldn't get the state of ${udid}`); throw `Couldn't get the state of the device`; } - if(initialState === 'Shutting Down') { + if (initialState === 'Shutting Down') { log.info(`Waiting for device ${udid} to shut down`); throw `The device is in 'Shutting Down' state`; } }); - if(initialState === 'Booted') { + if (initialState === 'Booted') { log.info(`Device ${udid} is already booted`); return; } - if(initialState === 'Booting') { + if (initialState === 'Booting') { log.info(`Device ${udid} is already booting`); } else { const launchBin = "/bin/bash -c '`xcode-select -p`/Applications/Simulator.app/Contents/MacOS/Simulator " + - `--args -CurrentDeviceUDID ${udid} -ConnectHardwareKeyboard 0 ` + - "-DeviceSetPath $HOME/Library/Developer/CoreSimulator/Devices > /dev/null 2>&1 < /dev/null &'"; + `--args -CurrentDeviceUDID ${udid} -ConnectHardwareKeyboard 0 ` + + "-DeviceSetPath $HOME/Library/Developer/CoreSimulator/Devices > /dev/null 2>&1 < /dev/null &'"; await exec.execWithRetriesAndLogs(launchBin, undefined, { trying: `Launching device ${udid}...`, successful: '' }, 1); } - return await this._execFbsimctlCommand({args: `--state booted ${udid} list`}, { + return await this._execFbsimctlCommand({ args: `--state booted ${udid} list` }, { trying: `Waiting for device ${udid} to boot...`, successful: `Device ${udid} booted` }); @@ -76,7 +67,7 @@ class Fbsimctl { trying: `Installing ${absPath}...`, successful: `${absPath} installed` }; - const options = {args: `${udid} install ${absPath}`}; + const options = { args: `${udid} install ${absPath}` }; return await this._execFbsimctlCommand(options, statusLogs); } @@ -85,7 +76,7 @@ class Fbsimctl { trying: `Uninstalling ${bundleId}...`, successful: `${bundleId} uninstalled` }; - const options = {args: `${udid} uninstall ${bundleId}`}; + const options = { args: `${udid} uninstall ${bundleId}` }; try { await this._execFbsimctlCommand(options, statusLogs, 1); } catch (ex) { @@ -101,13 +92,13 @@ class Fbsimctl { const logsInfo = new LogsInfo(udid); const launchBin = `/bin/cat /dev/null >${logsInfo.absStdout} 2>${logsInfo.absStderr} && ` + - `SIMCTL_CHILD_DYLD_INSERT_LIBRARIES="${this._getFrameworkPath()}" ` + - `/usr/bin/xcrun simctl launch --stdout=${logsInfo.simStdout} --stderr=${logsInfo.simStderr} ` + - `${udid} ${bundleId} --args ${args.join(' ')}`; + `SIMCTL_CHILD_DYLD_INSERT_LIBRARIES="${this._getFrameworkPath()}" ` + + `/usr/bin/xcrun simctl launch --stdout=${logsInfo.simStdout} --stderr=${logsInfo.simStderr} ` + + `${udid} ${bundleId} --args ${args.join(' ')}`; const result = await exec.execWithRetriesAndLogs(launchBin, undefined, { trying: `Launching ${bundleId}...`, successful: `${bundleId} launched. The stdout and stderr logs were recreated, you can watch them with:\n` + - ` tail -F ${logsInfo.absJoined}` + ` tail -F ${logsInfo.absJoined}` }, 1); return parseInt(result.stdout.trim().split(':')[1]); } @@ -134,23 +125,23 @@ class Fbsimctl { } async shutdown(udid) { - const options = {args: `${udid} shutdown`}; + const options = { args: `${udid} shutdown` }; await this._execFbsimctlCommand(options); } async open(udid, url) { - const options = {args: `${udid} open ${url}`}; + const options = { args: `${udid} open ${url}` }; await this._execFbsimctlCommand(options); } async isDeviceBooted(udid) { - const options = {args: `${udid} list`}; + const options = { args: `${udid} list` }; const result = await this._execFbsimctlCommand(options); return JSON.parse(result.stdout).subject.state !== 'Booted'; } async setLocation(udid, lat, lon) { - const options = {args: `${udid} set_location ${lat} ${lon}`}; + const options = { args: `${udid} set_location ${lat} ${lon}` }; await this._execFbsimctlCommand(options); } diff --git a/detox/src/devices/Fbsimctl.test.js b/detox/src/devices/Fbsimctl.test.js index 17e574d46a..59d44cf917 100644 --- a/detox/src/devices/Fbsimctl.test.js +++ b/detox/src/devices/Fbsimctl.test.js @@ -23,32 +23,6 @@ describe('Fbsimctl', () => { fbsimctl = new Fbsimctl(); }); - it(`list() - specify an invalid simulator should throw an Error`, async() => { - const returnValue = {}; - const result = returnSuccessfulWithValue(returnValue); - exec.mockReturnValue(Promise.resolve(result)); - - try { - await fbsimctl.list('iPhone 7'); - fail('expected list() to throw'); - } catch (object) { - expect(object).toBeDefined(); - } - }); - - it(`list() - when something goes wrong in the list retrival process, log the given error error`, async() => { - const returnValue = {}; - const result = returnErrorWithValue(returnValue); - exec.mockReturnValue(Promise.reject(result)); - - try { - await fbsimctl.list('iPhone 7'); - fail('expected list() to throw'); - } catch (object) { - expect(object).toBeDefined(); - } - }); - it(`boot() - when shutting down, should wait for the device`, async() => { fbsimctl._execFbsimctlCommand = jest.fn(() => ({stdout: `{"subject": {"state": "Shutting Down"}}`})); diff --git a/detox/src/devices/SimulatorDriver.js b/detox/src/devices/SimulatorDriver.js index 71c6da3258..e895983444 100644 --- a/detox/src/devices/SimulatorDriver.js +++ b/detox/src/devices/SimulatorDriver.js @@ -17,7 +17,7 @@ class SimulatorDriver extends IosDriver { } async acquireFreeDevice(name) { - return await this._fbsimctl.list(name); + return await this._applesimutils.findDeviceUDID(name); } async getBundleIdFromBinary(appPath) { From cad999bb50b641a618a2bb988e31472a2b9f695c Mon Sep 17 00:00:00 2001 From: Daniel Zlotin Date: Wed, 20 Sep 2017 01:54:03 +0300 Subject: [PATCH 05/20] removed old code --- detox/src/devices/AppleSimUtils.js | 30 ++++++++++++------------- detox/src/devices/AppleSimUtils.test.js | 15 ++++++------- detox/src/devices/Fbsimctl.js | 6 ----- detox/src/devices/Fbsimctl.test.js | 16 ------------- 4 files changed, 22 insertions(+), 45 deletions(-) diff --git a/detox/src/devices/AppleSimUtils.js b/detox/src/devices/AppleSimUtils.js index b53825c914..3d033d2356 100644 --- a/detox/src/devices/AppleSimUtils.js +++ b/detox/src/devices/AppleSimUtils.js @@ -22,9 +22,9 @@ class AppleSimUtils { const statusLogs = { trying: `Searching for device matching ${query}...` }; - let correctQuery = correctQueryWithOS(query); + let correctQuery = this._correctQueryWithOS(query); const response = await this._execAppleSimUtilsCommand({ args: `--list "${correctQuery}" --maxResults=1` }, statusLogs, 1); - const parsed = parseStdout(response); + const parsed = this._parseStdout(response); const udid = _.get(parsed, [0, 'udid']); if (!udid) { throw new Error(`Can't find a simulator to match with "${query}", run 'xcrun simctl list' to list your supported devices. @@ -37,23 +37,23 @@ class AppleSimUtils { const bin = `applesimutils`; return await exec.execWithRetriesAndLogs(bin, options, statusLogs, retries, interval); } -} -function correctQueryWithOS(query) { - let correctQuery = query; - if (_.includes(query, ',')) { - const parts = _.split(query, ','); - correctQuery = `${parts[0].trim()}, OS=${parts[1].trim()}`; + _correctQueryWithOS(query) { + let correctQuery = query; + if (_.includes(query, ',')) { + const parts = _.split(query, ','); + correctQuery = `${parts[0].trim()}, OS=${parts[1].trim()}`; + } + return correctQuery; } - return correctQuery; -} -function parseStdout(response) { - const stdout = _.get(response, 'stdout'); - if (_.isEmpty(stdout)) { - return []; + _parseStdout(response) { + const stdout = _.get(response, 'stdout'); + if (_.isEmpty(stdout)) { + return []; + } + return JSON.parse(stdout); } - return JSON.parse(stdout); } module.exports = AppleSimUtils; diff --git a/detox/src/devices/AppleSimUtils.test.js b/detox/src/devices/AppleSimUtils.test.js index 118c6b656b..1474da1dd5 100644 --- a/detox/src/devices/AppleSimUtils.test.js +++ b/detox/src/devices/AppleSimUtils.test.js @@ -1,6 +1,6 @@ describe('AppleSimUtils', () => { let AppleSimUtils; - let appSimUtils; + let uut; let exec; const simUdid = `9C9ABE4D-70C7-49DC-A396-3CB1D0E82846`; @@ -12,11 +12,11 @@ describe('AppleSimUtils', () => { exec = require('../utils/exec'); AppleSimUtils = require('./AppleSimUtils'); - appSimUtils = new AppleSimUtils(); + uut = new AppleSimUtils(); }); it(`appleSimUtils setPermissions`, async () => { - appSimUtils.setPermissions(bundleId, simUdid, { permissions: { calendar: "YES" } }); + uut.setPermissions(bundleId, simUdid, { permissions: { calendar: "YES" } }); expect(exec.execWithRetriesAndLogs).toHaveBeenCalledTimes(1); }); @@ -24,7 +24,7 @@ describe('AppleSimUtils', () => { it('correct params', async () => { expect(exec.execWithRetriesAndLogs).not.toHaveBeenCalled(); try { - await appSimUtils.findDeviceUDID('iPhone 6'); + await uut.findDeviceUDID('iPhone 6'); } catch (e) { } expect(exec.execWithRetriesAndLogs).toHaveBeenCalledTimes(1); expect(exec.execWithRetriesAndLogs).toHaveBeenCalledWith('applesimutils', { @@ -34,7 +34,7 @@ describe('AppleSimUtils', () => { it('adapted to new api with optional OS', async () => { try { - await appSimUtils.findDeviceUDID('iPhone 6 , iOS 10.3'); + await uut.findDeviceUDID('iPhone 6 , iOS 10.3'); } catch (e) { } expect(exec.execWithRetriesAndLogs).toHaveBeenCalledWith('applesimutils', { args: `--list "iPhone 6, OS=iOS 10.3" --maxResults=1` @@ -59,7 +59,7 @@ describe('AppleSimUtils', () => { } ]) })); - const result = await appSimUtils.findDeviceUDID('iPhone 7'); + const result = await uut.findDeviceUDID('iPhone 7'); expect(result).toEqual('the uuid'); }); @@ -76,7 +76,7 @@ describe('AppleSimUtils', () => { it(`invalid input ${JSON.stringify(arg)}`, async () => { exec.execWithRetriesAndLogs.mockReturnValueOnce(Promise.resolve(arg)); try { - await appSimUtils.findDeviceUDID('iPhone 6, iOS 10'); + await uut.findDeviceUDID('iPhone 6, iOS 10'); fail('should throw'); } catch (e) { expect(e.message).toMatch(`Can't find a simulator to match with "iPhone 6, iOS 10"`); @@ -84,7 +84,6 @@ describe('AppleSimUtils', () => { }); }) }); - }); }); diff --git a/detox/src/devices/Fbsimctl.js b/detox/src/devices/Fbsimctl.js index d8d186f841..3a9a3157f0 100644 --- a/detox/src/devices/Fbsimctl.js +++ b/detox/src/devices/Fbsimctl.js @@ -134,12 +134,6 @@ class Fbsimctl { await this._execFbsimctlCommand(options); } - async isDeviceBooted(udid) { - const options = { args: `${udid} list` }; - const result = await this._execFbsimctlCommand(options); - return JSON.parse(result.stdout).subject.state !== 'Booted'; - } - async setLocation(udid, lat, lon) { const options = { args: `${udid} set_location ${lat} ${lon}` }; await this._execFbsimctlCommand(options); diff --git a/detox/src/devices/Fbsimctl.test.js b/detox/src/devices/Fbsimctl.test.js index 59d44cf917..b6d46b9716 100644 --- a/detox/src/devices/Fbsimctl.test.js +++ b/detox/src/devices/Fbsimctl.test.js @@ -115,22 +115,6 @@ describe('Fbsimctl', () => { await validateFbsimctlisCalledOn(fbsimctl, async () => fbsimctl.open(simUdid, bundleId)); }); - it(`isDeviceBooted() - specify a shutdown simulator`, async() => { - fbsimctl._execFbsimctlCommand = jest.fn(() => { - return returnSuccessfulWithValue(listAsimUdidAtState(simUdid, `Shutdown`)); - }); - const isDeviceBooted = await fbsimctl.isDeviceBooted(simUdid); - expect(isDeviceBooted).toBe(true); - }); - - it(`isDeviceBooted() - specify a booted simulator`, async() => { - fbsimctl._execFbsimctlCommand = jest.fn(() => { - return returnSuccessfulWithValue(listAsimUdidAtState(simUdid, `Booted`)); - }); - const isDeviceBooted = await fbsimctl.isDeviceBooted(simUdid); - expect(isDeviceBooted).toBe(false); - }); - it(`setLocation() - is triggering fbsimctl set_location`, async() => { await validateFbsimctlisCalledOn(fbsimctl, async () => fbsimctl.setLocation(simUdid)); }); From 0cc0657ab1b46dcb7610c081dfd09b666c70aeba Mon Sep 17 00:00:00 2001 From: Daniel Zlotin Date: Sun, 24 Sep 2017 16:39:32 +0300 Subject: [PATCH 06/20] migrated boot --- detox/src/devices/AppleSimUtils.js | 48 +++++++++++++++-- detox/src/devices/AppleSimUtils.test.js | 72 +++++++++++++++++++++++++ detox/src/devices/Fbsimctl.js | 39 -------------- detox/src/devices/Fbsimctl.test.js | 40 -------------- 4 files changed, 115 insertions(+), 84 deletions(-) diff --git a/detox/src/devices/AppleSimUtils.js b/detox/src/devices/AppleSimUtils.js index 3d033d2356..e1a292b74f 100644 --- a/detox/src/devices/AppleSimUtils.js +++ b/detox/src/devices/AppleSimUtils.js @@ -1,6 +1,6 @@ const _ = require('lodash'); const exec = require('../utils/exec'); -const log = require('npmlog'); +const retry = require('../utils/retry'); class AppleSimUtils { @@ -13,7 +13,7 @@ class AppleSimUtils { _.forEach(permissionsObj, function (shouldAllow, permission) { permissions.push(permission + '=' + shouldAllow); }); - await this._execAppleSimUtilsCommand({ + await this._execAppleSimUtils({ args: `--simulator ${udid} --bundle ${bundleId} --setPermissions ${_.join(permissions, ',')}` }, statusLogs, 1); } @@ -23,7 +23,7 @@ class AppleSimUtils { trying: `Searching for device matching ${query}...` }; let correctQuery = this._correctQueryWithOS(query); - const response = await this._execAppleSimUtilsCommand({ args: `--list "${correctQuery}" --maxResults=1` }, statusLogs, 1); + const response = await this._execAppleSimUtils({ args: `--list "${correctQuery}" --maxResults=1` }, statusLogs, 1); const parsed = this._parseStdout(response); const udid = _.get(parsed, [0, 'udid']); if (!udid) { @@ -33,7 +33,38 @@ class AppleSimUtils { return udid; } - async _execAppleSimUtilsCommand(options, statusLogs, retries, interval) { + async findDeviceByUDID(udid) { + const response = await this._execAppleSimUtils({ args: `--list` }, undefined, 1); + const parsed = this._parseStdout(response); + const device = _.find(parsed, (device) => _.isEqual(device.udid, udid)); + if (!device) { + throw new Error(`Can't find device ${udid}`); + } + return device; + } + + async waitForDeviceState(udid, state) { + let device; + await retry({ retries: 10, interval: 1000 }, async () => { + device = await this.findDeviceByUDID(udid); + if (!_.isEqual(device.state, state)) { + throw new Error(`device is in state '${device.state}'`); + } + }); + return device; + } + + async boot(udid) { + const device = await this.findDeviceByUDID(udid); + if (_.isEqual(device.state, 'Booted') || _.isEqual(device.state, 'Booting')) { + return false; + } + await this.waitForDeviceState(udid, 'Shutdown'); + await this._bootDeviceMagically(udid); + await this.waitForDeviceState(udid, 'Booted'); + } + + async _execAppleSimUtils(options, statusLogs, retries, interval) { const bin = `applesimutils`; return await exec.execWithRetriesAndLogs(bin, options, statusLogs, retries, interval); } @@ -50,10 +81,17 @@ class AppleSimUtils { _parseStdout(response) { const stdout = _.get(response, 'stdout'); if (_.isEmpty(stdout)) { - return []; + return undefined; } return JSON.parse(stdout); } + + async _bootDeviceMagically(udid) { + const cmd = "/bin/bash -c '`xcode-select -p`/Applications/Simulator.app/Contents/MacOS/Simulator " + + `--args -CurrentDeviceUDID ${udid} -ConnectHardwareKeyboard 0 ` + + "-DeviceSetPath $HOME/Library/Developer/CoreSimulator/Devices > /dev/null 2>&1 < /dev/null &'"; + await exec.execWithRetriesAndLogs(cmd, undefined, { trying: `Launching device ${udid}...` }, 1); + } } module.exports = AppleSimUtils; diff --git a/detox/src/devices/AppleSimUtils.test.js b/detox/src/devices/AppleSimUtils.test.js index 1474da1dd5..ddb220a847 100644 --- a/detox/src/devices/AppleSimUtils.test.js +++ b/detox/src/devices/AppleSimUtils.test.js @@ -2,6 +2,7 @@ describe('AppleSimUtils', () => { let AppleSimUtils; let uut; let exec; + let retry; const simUdid = `9C9ABE4D-70C7-49DC-A396-3CB1D0E82846`; const bundleId = 'bundle.id'; @@ -10,6 +11,8 @@ describe('AppleSimUtils', () => { jest.mock('npmlog'); jest.mock('../utils/exec'); exec = require('../utils/exec'); + jest.mock('../utils/retry'); + retry = require('../utils/retry'); AppleSimUtils = require('./AppleSimUtils'); uut = new AppleSimUtils(); @@ -85,5 +88,74 @@ describe('AppleSimUtils', () => { }) }); }); + + describe('findDeviceByUDID', () => { + it('throws when cant find device by udid', async () => { + try { + await uut.findDeviceByUDID('someUdid'); + fail('should throw'); + } catch (e) { + expect(e).toEqual(new Error(`Can't find device someUdid`)); + } + }); + + it('lists devices, finds by udid', async () => { + const device = { udid: 'someUdid' }; + exec.execWithRetriesAndLogs.mockReturnValueOnce(Promise.resolve({ stdout: JSON.stringify([device, { udid: 'other' }]) })); + const result = await uut.findDeviceByUDID('someUdid'); + expect(result).toEqual(device); + }); + }); + + describe('waitForDeviceState', () => { + it('findsDeviceByUdid', async () => { + uut.findDeviceByUDID = jest.fn(() => Promise.resolve({ udid: 'the udid', state: 'the state' })); + retry.mockImplementation((opts, fn) => Promise.resolve(fn())); + const result = await uut.waitForDeviceState(`the udid`, `the state`); + expect(uut.findDeviceByUDID).toHaveBeenCalledTimes(1); + expect(result).toEqual({ udid: 'the udid', state: 'the state' }); + }); + + it('waits for state to be equal', async () => { + uut.findDeviceByUDID = jest.fn(() => Promise.resolve({ udid: 'the udid', state: 'different state' })); + retry.mockImplementation((opts, fn) => Promise.resolve(fn())); + try { + await uut.waitForDeviceState(`the udid`, `the state`); + fail(`should throw`); + } catch (e) { + expect(e).toEqual(new Error(`device is in state 'different state'`)); + } + expect(uut.findDeviceByUDID).toHaveBeenCalledTimes(1); + expect(retry).toHaveBeenCalledTimes(1); + expect(retry).toHaveBeenCalledWith({ retries: 10, interval: 1000 }, expect.any(Function)); + }); + }); + + describe('boot', () => { + it('waits for device by udid to be Shutdown, boots magically, then waits for state to be Booted', async () => { + uut.findDeviceByUDID = jest.fn(() => Promise.resolve({ state: 'unknown' })); + uut.waitForDeviceState = jest.fn(() => Promise.resolve(true)); + expect(exec.execWithRetriesAndLogs).not.toHaveBeenCalled(); + await uut.boot('some udid'); + expect(exec.execWithRetriesAndLogs).toHaveBeenCalledWith(expect.stringMatching('xcode-select -p'), undefined, expect.anything(), 1); + expect(uut.waitForDeviceState).toHaveBeenCalledTimes(2); + expect(uut.waitForDeviceState.mock.calls[0]).toEqual([`some udid`, `Shutdown`]); + expect(uut.waitForDeviceState.mock.calls[1]).toEqual([`some udid`, `Booted`]); + }); + + it('skips if device state was already Booted', async () => { + uut.findDeviceByUDID = jest.fn(() => Promise.resolve({ state: 'Booted' })); + await uut.boot('udid'); + expect(uut.findDeviceByUDID).toHaveBeenCalledTimes(1); + expect(exec.execWithRetriesAndLogs).not.toHaveBeenCalled(); + }); + + it('skips if device state was already Booting', async () => { + uut.findDeviceByUDID = jest.fn(() => Promise.resolve({ state: 'Booting' })); + await uut.boot('udid'); + expect(uut.findDeviceByUDID).toHaveBeenCalledTimes(1); + expect(exec.execWithRetriesAndLogs).not.toHaveBeenCalled(); + }); + }); }); diff --git a/detox/src/devices/Fbsimctl.js b/detox/src/devices/Fbsimctl.js index 3a9a3157f0..d85a6e092c 100644 --- a/detox/src/devices/Fbsimctl.js +++ b/detox/src/devices/Fbsimctl.js @@ -23,45 +23,6 @@ class LogsInfo { class Fbsimctl { - async boot(udid) { - let initialState; - await retry({ retries: 10, interval: 1000 }, async () => { - const initialStateCmdResult = await this._execFbsimctlCommand({ args: `${udid} list` }, undefined, 1); - initialState = _.get(initialStateCmdResult, 'stdout', '') === '' ? undefined : - _.get(JSON.parse(_.get(initialStateCmdResult, 'stdout')), 'subject.state'); - if (initialState === undefined) { - log.info(`Couldn't get the state of ${udid}`); - throw `Couldn't get the state of the device`; - } - if (initialState === 'Shutting Down') { - log.info(`Waiting for device ${udid} to shut down`); - throw `The device is in 'Shutting Down' state`; - } - }); - - if (initialState === 'Booted') { - log.info(`Device ${udid} is already booted`); - return; - } - - if (initialState === 'Booting') { - log.info(`Device ${udid} is already booting`); - } else { - const launchBin = "/bin/bash -c '`xcode-select -p`/Applications/Simulator.app/Contents/MacOS/Simulator " + - `--args -CurrentDeviceUDID ${udid} -ConnectHardwareKeyboard 0 ` + - "-DeviceSetPath $HOME/Library/Developer/CoreSimulator/Devices > /dev/null 2>&1 < /dev/null &'"; - await exec.execWithRetriesAndLogs(launchBin, undefined, { - trying: `Launching device ${udid}...`, - successful: '' - }, 1); - } - - return await this._execFbsimctlCommand({ args: `--state booted ${udid} list` }, { - trying: `Waiting for device ${udid} to boot...`, - successful: `Device ${udid} booted` - }); - } - async install(udid, absPath) { const statusLogs = { trying: `Installing ${absPath}...`, diff --git a/detox/src/devices/Fbsimctl.test.js b/detox/src/devices/Fbsimctl.test.js index b6d46b9716..64532fb2a0 100644 --- a/detox/src/devices/Fbsimctl.test.js +++ b/detox/src/devices/Fbsimctl.test.js @@ -23,46 +23,6 @@ describe('Fbsimctl', () => { fbsimctl = new Fbsimctl(); }); - it(`boot() - when shutting down, should wait for the device`, async() => { - fbsimctl._execFbsimctlCommand = jest.fn(() => ({stdout: `{"subject": {"state": "Shutting Down"}}`})); - - try { - await fbsimctl.boot(simUdid); - fail('should throw'); - } catch (ex) { - expect(ex).toBe("The device is in 'Shutting Down' state"); - } - }); - - it(`boot() - when state is undefined, should wait for the device`, async() => { - fbsimctl._execFbsimctlCommand = jest.fn(() => ({})); - - try { - await fbsimctl.boot(simUdid); - fail('should throw'); - } catch (ex) { - expect(ex).toBe("Couldn't get the state of the device"); - } - }); - - it(`boot() - when booted, should not wait for the device to boot`, async() => { - fbsimctl._execFbsimctlCommand = jest.fn(() => ({stdout: `{"subject": {"state": "Booted"}}`})); - await fbsimctl.boot(simUdid); - expect(exec).toHaveBeenCalledTimes(0); - }); - - it(`boot() - when booting, should not call exec`, async() => { - fbsimctl._execFbsimctlCommand = jest.fn(() => ({stdout: `{"subject": {"state": "Booting"}}`})); - await fbsimctl.boot(simUdid); - expect(exec).toHaveBeenCalledTimes(0); - }); - - it(`boot() - when shutdown, should call exec`, async() => { - fbsimctl._execFbsimctlCommand = jest.fn(() => ({stdout: `{"subject": {"state": "Shutdown"}}`})); - await fbsimctl.boot(simUdid); - expect(exec).toHaveBeenCalledTimes(1); - }); - it(`install() - is triggering fbsimctl install`, async() => { await validateFbsimctlisCalledOn(fbsimctl, async () => fbsimctl.install(simUdid, bundleId, {})); }); From bec41631c2bccfe6985a14ddbdf7f34edc608a4a Mon Sep 17 00:00:00 2001 From: Daniel Zlotin Date: Sun, 24 Sep 2017 16:43:31 +0300 Subject: [PATCH 07/20] fix coverage --- detox/src/devices/Fbsimctl.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/detox/src/devices/Fbsimctl.js b/detox/src/devices/Fbsimctl.js index 0d1fc8d80a..29bfcf87d8 100644 --- a/detox/src/devices/Fbsimctl.js +++ b/detox/src/devices/Fbsimctl.js @@ -111,14 +111,6 @@ class Fbsimctl { const bin = `fbsimctl --json`; return await exec.execWithRetriesAndLogs(bin, options, statusLogs, retries, interval); } - - _getFrameworkPath() { - const frameworkPath = path.join(__dirname, `/../../Detox.framework/Detox`); - if (!fs.existsSync(frameworkPath)) { - throw new Error(`Detox.framework not found at ${frameworkPath}`); - } - return frameworkPath; - } } module.exports = Fbsimctl; From 63c4b9b67791f320a3379456e89b580864653913 Mon Sep 17 00:00:00 2001 From: Daniel Zlotin Date: Sun, 24 Sep 2017 19:37:46 +0300 Subject: [PATCH 08/20] migrated boot install and uninstall --- detox/src/devices/AppleSimUtils.js | 30 +++++++++++++++++++++++++ detox/src/devices/AppleSimUtils.test.js | 29 ++++++++++++++++++++++++ detox/src/devices/Device.test.js | 1 - detox/src/devices/Fbsimctl.js | 20 ----------------- detox/src/devices/Fbsimctl.test.js | 2 +- detox/src/devices/SimulatorDriver.js | 22 +++++++++--------- 6 files changed, 70 insertions(+), 34 deletions(-) diff --git a/detox/src/devices/AppleSimUtils.js b/detox/src/devices/AppleSimUtils.js index e1a292b74f..7a002e378b 100644 --- a/detox/src/devices/AppleSimUtils.js +++ b/detox/src/devices/AppleSimUtils.js @@ -64,6 +64,36 @@ class AppleSimUtils { await this.waitForDeviceState(udid, 'Booted'); } + async install(udid, absPath) { + const statusLogs = { + trying: `Installing ${absPath}...`, + successful: `${absPath} installed` + }; + const cmd = `/usr/bin/xcrun simctl install ${udid} ${absPath}`; + await exec.execWithRetriesAndLogs(cmd, undefined, statusLogs, 1); + } + + async uninstall(udid, bundleId) { + const statusLogs = { + trying: `Uninstalling ${bundleId}...`, + successful: `${bundleId} uninstalled` + }; + const cmd = `/usr/bin/xcrun simctl uninstall ${udid} ${bundleId}`; + try { + await exec.execWithRetriesAndLogs(cmd, undefined, statusLogs, 1); + } catch (e) { + // that's fine + } + } + + async terminate() { + fail(); + } + + async launch() { + fail(); + } + async _execAppleSimUtils(options, statusLogs, retries, interval) { const bin = `applesimutils`; return await exec.execWithRetriesAndLogs(bin, options, statusLogs, retries, interval); diff --git a/detox/src/devices/AppleSimUtils.test.js b/detox/src/devices/AppleSimUtils.test.js index ddb220a847..26de5111a1 100644 --- a/detox/src/devices/AppleSimUtils.test.js +++ b/detox/src/devices/AppleSimUtils.test.js @@ -157,5 +157,34 @@ describe('AppleSimUtils', () => { expect(exec.execWithRetriesAndLogs).not.toHaveBeenCalled(); }); }); + + describe('install', () => { + it('calls xcrun', async () => { + await uut.install('udid', 'somePath'); + expect(exec.execWithRetriesAndLogs).toHaveBeenCalledTimes(1); + expect(exec.execWithRetriesAndLogs).toHaveBeenCalledWith( + `/usr/bin/xcrun simctl install udid somePath`, + undefined, + expect.anything(), + 1); + }); + }); + + describe('uninstall', () => { + it('calls xcrun', async () => { + await uut.uninstall('udid', 'theBundleId'); + expect(exec.execWithRetriesAndLogs).toHaveBeenCalledTimes(1); + expect(exec.execWithRetriesAndLogs).toHaveBeenCalledWith( + `/usr/bin/xcrun simctl uninstall udid theBundleId`, + undefined, + expect.anything(), + 1); + }); + + it('does not throw', async () => { + exec.execWithRetriesAndLogs.mockImplementation(() => Promise.reject('some reason')); + await uut.uninstall('udid', 'theBundleId'); + }); + }); }); diff --git a/detox/src/devices/Device.test.js b/detox/src/devices/Device.test.js index 5b0e609207..c5fc7b013d 100644 --- a/detox/src/devices/Device.test.js +++ b/detox/src/devices/Device.test.js @@ -36,7 +36,6 @@ describe('Device', () => { jest.mock('../utils/environment'); - jest.mock('./Fbsimctl'); jest.mock('./AppleSimUtils'); jest.mock('../client/Client'); diff --git a/detox/src/devices/Fbsimctl.js b/detox/src/devices/Fbsimctl.js index 29bfcf87d8..2527e5d6a6 100644 --- a/detox/src/devices/Fbsimctl.js +++ b/detox/src/devices/Fbsimctl.js @@ -22,27 +22,7 @@ class LogsInfo { class Fbsimctl { - async install(udid, absPath) { - const statusLogs = { - trying: `Installing ${absPath}...`, - successful: `${absPath} installed` - }; - const options = { args: `${udid} install ${absPath}` }; - return await this._execFbsimctlCommand(options, statusLogs); - } - async uninstall(udid, bundleId) { - const statusLogs = { - trying: `Uninstalling ${bundleId}...`, - successful: `${bundleId} uninstalled` - }; - const options = { args: `${udid} uninstall ${bundleId}` }; - try { - await this._execFbsimctlCommand(options, statusLogs, 1); - } catch (ex) { - //that's ok - } - } async launch(udid, bundleId, launchArgs) { const args = []; diff --git a/detox/src/devices/Fbsimctl.test.js b/detox/src/devices/Fbsimctl.test.js index 64532fb2a0..010dbf382d 100644 --- a/detox/src/devices/Fbsimctl.test.js +++ b/detox/src/devices/Fbsimctl.test.js @@ -1,7 +1,7 @@ const _ = require('lodash'); -describe('Fbsimctl', () => { +xdescribe('Fbsimctl', () => { let Fbsimctl; let fbsimctl; let exec; diff --git a/detox/src/devices/SimulatorDriver.js b/detox/src/devices/SimulatorDriver.js index e895983444..c6c45c2263 100644 --- a/detox/src/devices/SimulatorDriver.js +++ b/detox/src/devices/SimulatorDriver.js @@ -4,7 +4,6 @@ const fs = require('fs'); const os = require('os'); const _ = require('lodash'); const IosDriver = require('./IosDriver'); -const FBsimctl = require('./Fbsimctl'); const AppleSimUtils = require('./AppleSimUtils'); const configuration = require('../configuration'); @@ -12,7 +11,6 @@ class SimulatorDriver extends IosDriver { constructor(client) { super(client); - this._fbsimctl = new FBsimctl(); this._applesimutils = new AppleSimUtils(); } @@ -30,35 +28,35 @@ class SimulatorDriver extends IosDriver { } async boot(deviceId) { - await this._fbsimctl.boot(deviceId); + await this._applesimutils.boot(deviceId); } async installApp(deviceId, binaryPath) { - await this._fbsimctl.install(deviceId, binaryPath); + await this._applesimutils.install(deviceId, binaryPath); } async uninstallApp(deviceId, bundleId) { - await this._fbsimctl.uninstall(deviceId, bundleId); + await this._applesimutils.uninstall(deviceId, bundleId); } async launch(deviceId, bundleId, launchArgs) { - return await this._fbsimctl.launch(deviceId, bundleId, launchArgs); + return await this._applesimutils.launch(deviceId, bundleId, launchArgs); } async terminate(deviceId, bundleId) { - await this._fbsimctl.terminate(deviceId, bundleId); + await this._applesimutils.terminate(deviceId, bundleId); } async sendToHome(deviceId) { - return await this._fbsimctl.sendToHome(deviceId); + return await this._applesimutils.sendToHome(deviceId); } async shutdown(deviceId) { - await this._fbsimctl.shutdown(deviceId); + await this._applesimutils.shutdown(deviceId); } async setLocation(deviceId, lat, lon) { - await this._fbsimctl.setLocation(deviceId, lat, lon); + await this._applesimutils.setLocation(deviceId, lat, lon); } async setPermissions(deviceId, bundleId, permissions) { @@ -66,7 +64,7 @@ class SimulatorDriver extends IosDriver { } async resetContentAndSettings(deviceId) { - return await this._fbsimctl.resetContentAndSettings(deviceId); + return await this._applesimutils.resetContentAndSettings(deviceId); } validateDeviceConfig(deviceConfig) { @@ -80,7 +78,7 @@ class SimulatorDriver extends IosDriver { } getLogsPaths(deviceId) { - return this._fbsimctl.getLogsPaths(deviceId); + return this._applesimutils.getLogsPaths(deviceId); } } From a69ddccb7aecff5e8f479debac411f8deed178cd Mon Sep 17 00:00:00 2001 From: Daniel Zlotin Date: Sun, 24 Sep 2017 20:04:11 +0300 Subject: [PATCH 09/20] migrated launch --- detox/src/devices/AppleSimUtils.js | 36 ++++++- detox/src/devices/AppleSimUtils.test.js | 47 +++++++++ detox/src/devices/Fbsimctl.js | 32 +----- detox/src/devices/Fbsimctl.test.js | 135 +++++++++--------------- 4 files changed, 131 insertions(+), 119 deletions(-) diff --git a/detox/src/devices/AppleSimUtils.js b/detox/src/devices/AppleSimUtils.js index 7a002e378b..9a259ccac0 100644 --- a/detox/src/devices/AppleSimUtils.js +++ b/detox/src/devices/AppleSimUtils.js @@ -1,6 +1,7 @@ const _ = require('lodash'); const exec = require('../utils/exec'); const retry = require('../utils/retry'); +const environment = require('../utils/environment'); class AppleSimUtils { @@ -86,11 +87,28 @@ class AppleSimUtils { } } - async terminate() { - fail(); + async launch(udid, bundleId, launchArgs) { + const frameworkPath = await environment.getFrameworkPath(); + const logsInfo = new LogsInfo(udid); + const args = _.map(launchArgs, (v, k) => `${k} ${v}`).join(' ').trim(); + + const statusLogs = { + trying: `Launching ${bundleId}...`, + successful: `${bundleId} launched. The stdout and stderr logs were recreated, you can watch them with:\n` + + ` tail -F ${logsInfo.absJoined}` + }; + + const launchBin = `/bin/cat /dev/null >${logsInfo.absStdout} 2>${logsInfo.absStderr} && ` + + `SIMCTL_CHILD_DYLD_INSERT_LIBRARIES="${frameworkPath}/Detox" ` + + `/usr/bin/xcrun simctl launch --stdout=${logsInfo.simStdout} --stderr=${logsInfo.simStderr} ` + + `${udid} ${bundleId} --args ${args}`; + const result = await exec.execWithRetriesAndLogs(launchBin, undefined, statusLogs, 1); + // return parseInt(result.stdout.trim().split(':')[1]); + // exec.mockReturnValue({stdout: "appId: 22 \n"}); + } - async launch() { + async terminate() { fail(); } @@ -124,4 +142,16 @@ class AppleSimUtils { } } +class LogsInfo { + constructor(udid) { + const logPrefix = '/tmp/detox.last_launch_app_log.'; + this.simStdout = logPrefix + 'out'; + this.simStderr = logPrefix + 'err'; + const simDataRoot = `$HOME/Library/Developer/CoreSimulator/Devices/${udid}/data`; + this.absStdout = simDataRoot + this.simStdout; + this.absStderr = simDataRoot + this.simStderr; + this.absJoined = `${simDataRoot}${logPrefix}{out,err}` + } +} + module.exports = AppleSimUtils; diff --git a/detox/src/devices/AppleSimUtils.test.js b/detox/src/devices/AppleSimUtils.test.js index 26de5111a1..4706e5932b 100644 --- a/detox/src/devices/AppleSimUtils.test.js +++ b/detox/src/devices/AppleSimUtils.test.js @@ -3,6 +3,7 @@ describe('AppleSimUtils', () => { let uut; let exec; let retry; + let environment; const simUdid = `9C9ABE4D-70C7-49DC-A396-3CB1D0E82846`; const bundleId = 'bundle.id'; @@ -13,6 +14,8 @@ describe('AppleSimUtils', () => { exec = require('../utils/exec'); jest.mock('../utils/retry'); retry = require('../utils/retry'); + jest.mock('../utils/environment'); + environment = require('../utils/environment'); AppleSimUtils = require('./AppleSimUtils'); uut = new AppleSimUtils(); @@ -186,5 +189,49 @@ describe('AppleSimUtils', () => { await uut.uninstall('udid', 'theBundleId'); }); }); + + describe('launch', () => { + it('launches magically', async () => { + await uut.launch('udid', 'theBundleId'); + expect(exec.execWithRetriesAndLogs).toHaveBeenCalledTimes(1); + expect(exec.execWithRetriesAndLogs).toHaveBeenCalledWith( + expect.stringMatching(/^.*xcrun simctl launch.*$/), + undefined, + expect.anything(), + 1); + }); + + it('concats args', async () => { + await uut.launch('udid', 'theBundleId', { 'foo': 'bar', 'bob': 'yourUncle' }); + expect(exec.execWithRetriesAndLogs).toHaveBeenCalledTimes(1); + expect(exec.execWithRetriesAndLogs).toHaveBeenCalledWith( + expect.stringMatching(/^.*xcrun simctl launch.* --args foo bar bob yourUncle$/), + undefined, + expect.anything(), + 1); + }); + + it('asks environment for frameworkPath', async () => { + environment.getFrameworkPath.mockReturnValueOnce(Promise.resolve('thePathToFrameworks')); + await uut.launch('udid', 'theBundleId'); + expect(environment.getFrameworkPath).toHaveBeenCalledTimes(1); + expect(exec.execWithRetriesAndLogs).toHaveBeenCalledTimes(1); + expect(exec.execWithRetriesAndLogs).toHaveBeenCalledWith( + expect.stringMatching(/^.*thePathToFrameworks\/Detox.*xcrun simctl launch.*$/), + undefined, + expect.anything(), + 1); + }); + + it('should fail when cant locate framework path', async () => { + environment.getFrameworkPath.mockReturnValueOnce(Promise.reject('cant find anything')); + try { + await uut.launch('udid', 'theBundleId'); + fail(`should throw`); + } catch (e) { + expect(e).toBeDefined(); + } + }); + }); }); diff --git a/detox/src/devices/Fbsimctl.js b/detox/src/devices/Fbsimctl.js index 2527e5d6a6..66c8e63b75 100644 --- a/detox/src/devices/Fbsimctl.js +++ b/detox/src/devices/Fbsimctl.js @@ -2,46 +2,16 @@ const _ = require('lodash'); const log = require('npmlog'); const exec = require('../utils/exec'); const retry = require('../utils/retry'); -const environment = require('../utils/environment'); // FBSimulatorControl command line docs // https://github.com/facebook/FBSimulatorControl/issues/250 // https://github.com/facebook/FBSimulatorControl/blob/master/fbsimctl/FBSimulatorControlKitTests/Tests/Unit/CommandParsersTests.swift -class LogsInfo { - constructor(udid) { - const logPrefix = '/tmp/detox.last_launch_app_log.'; - this.simStdout = logPrefix + 'out'; - this.simStderr = logPrefix + 'err'; - const simDataRoot = `$HOME/Library/Developer/CoreSimulator/Devices/${udid}/data`; - this.absStdout = simDataRoot + this.simStdout; - this.absStderr = simDataRoot + this.simStderr; - this.absJoined = `${simDataRoot}${logPrefix}{out,err}` - } -} - -class Fbsimctl { +class Fbsimctl { - async launch(udid, bundleId, launchArgs) { - const args = []; - _.forEach(launchArgs, (value, key) => { - args.push(`${key} ${value}`); - }); - const logsInfo = new LogsInfo(udid); - const launchBin = `/bin/cat /dev/null >${logsInfo.absStdout} 2>${logsInfo.absStderr} && ` + - `SIMCTL_CHILD_DYLD_INSERT_LIBRARIES="${await environment.getFrameworkPath()}/Detox" ` + - `/usr/bin/xcrun simctl launch --stdout=${logsInfo.simStdout} --stderr=${logsInfo.simStderr} ` + - `${udid} ${bundleId} --args ${args.join(' ')}`; - const result = await exec.execWithRetriesAndLogs(launchBin, undefined, { - trying: `Launching ${bundleId}...`, - successful: `${bundleId} launched. The stdout and stderr logs were recreated, you can watch them with:\n` + - ` tail -F ${logsInfo.absJoined}` - }, 1); - return parseInt(result.stdout.trim().split(':')[1]); - } async sendToHome(udid) { const result = await exec.execWithRetriesAndLogs(`/usr/bin/xcrun simctl launch ${udid} com.apple.springboard`); diff --git a/detox/src/devices/Fbsimctl.test.js b/detox/src/devices/Fbsimctl.test.js index 010dbf382d..b9f1d90d6a 100644 --- a/detox/src/devices/Fbsimctl.test.js +++ b/detox/src/devices/Fbsimctl.test.js @@ -2,7 +2,6 @@ const _ = require('lodash'); xdescribe('Fbsimctl', () => { - let Fbsimctl; let fbsimctl; let exec; let fs; @@ -11,48 +10,14 @@ xdescribe('Fbsimctl', () => { const bundleId = 'bundle.id'; beforeEach(() => { - jest.mock('npmlog'); - jest.mock('fs'); - fs = require('fs'); - jest.mock('../utils/exec'); - exec = require('../utils/exec').execWithRetriesAndLogs; - jest.setMock('../utils/retry', async (options, func) => { - return await func(1); - }); - Fbsimctl = require('./Fbsimctl'); - fbsimctl = new Fbsimctl(); - }); - - it(`install() - is triggering fbsimctl install`, async() => { - await validateFbsimctlisCalledOn(fbsimctl, async () => fbsimctl.install(simUdid, bundleId, {})); - }); - - it(`uninstall() - is triggering fbsimctl uninstall`, async() => { - await validateFbsimctlisCalledOn(fbsimctl, async () => fbsimctl.uninstall(simUdid, bundleId)); - }); - - it(`launch() - is triggering exec`, async() => { - fs.existsSync.mockReturnValue(true); - exec.mockReturnValue({stdout: "appId: 22 \n"}); - await fbsimctl.launch(simUdid, bundleId, []); - expect(exec).toHaveBeenCalledTimes(1); - }); - - it(`launch() - is triggering exec with custom launch args`, async() => { - fs.existsSync.mockReturnValue(true); - exec.mockReturnValue({stdout: "appId: 22 \n"}); - await fbsimctl.launch(simUdid, bundleId, [{param: "param1"}]); - expect(exec).toHaveBeenCalledTimes(1); - }); - - it(`launch() - should throw when no Detox.framework exists`, async() => { - fs.existsSync.mockReturnValue(false); - try { - await fbsimctl.launch(simUdid, bundleId, []); - fail(`should fail when Detox.framework doesn't exist`); - } catch (ex) { - expect(ex).toBeDefined(); - } + // jest.mock('npmlog'); + // jest.mock('fs'); + // fs = require('fs'); + // jest.mock('../utils/exec'); + // exec = require('../utils/exec').execWithRetriesAndLogs; + // jest.setMock('../utils/retry', async (options, func) => { + // return await func(1); + // }); }); it(`sendToHome() - is triggering exec`, async() => { @@ -127,45 +92,45 @@ xdescribe('Fbsimctl', () => { }); }); -async function validateFbsimctlisCalledOn(fbsimctl, func) { - fbsimctl._execFbsimctlCommand = jest.fn(); - func(); - expect(fbsimctl._execFbsimctlCommand).toHaveBeenCalledTimes(1); -} - -function listAsimUdidAtState(udid, state) { - return { - "event_type": "discrete", - "timestamp": 1485328213, - "subject": { - "state": state, - "os": "iOS 10.1", - "name": "iPhone 7", - "udid": udid, - "device-name": "iPhone 7" - }, - "event_name": "list" - }; -} - -function returnSuccessfulWithValue(value) { - const result = { - stdout: JSON.stringify(value), - stderr: "", - childProcess: { - exitCode: 0 - } - }; - return result; -} - -function returnErrorWithValue(value) { - const result = { - stdout: "", - stderr: value, - childProcess: { - exitCode: 1 - } - }; - return result; -} +// async function validateFbsimctlisCalledOn(fbsimctl, func) { +// fbsimctl._execFbsimctlCommand = jest.fn(); +// func(); +// expect(fbsimctl._execFbsimctlCommand).toHaveBeenCalledTimes(1); +// } + +// function listAsimUdidAtState(udid, state) { +// return { +// "event_type": "discrete", +// "timestamp": 1485328213, +// "subject": { +// "state": state, +// "os": "iOS 10.1", +// "name": "iPhone 7", +// "udid": udid, +// "device-name": "iPhone 7" +// }, +// "event_name": "list" +// }; +// } + +// function returnSuccessfulWithValue(value) { +// const result = { +// stdout: JSON.stringify(value), +// stderr: "", +// childProcess: { +// exitCode: 0 +// } +// }; +// return result; +// } + +// function returnErrorWithValue(value) { +// const result = { +// stdout: "", +// stderr: value, +// childProcess: { +// exitCode: 1 +// } +// }; +// return result; +// } From 6b1e4e2fe9b1e08853816a66adc2b2de381bf6bd Mon Sep 17 00:00:00 2001 From: Daniel Zlotin Date: Sun, 24 Sep 2017 20:09:10 +0300 Subject: [PATCH 10/20] finished launch --- detox/src/devices/AppleSimUtils.js | 4 +--- detox/src/devices/AppleSimUtils.test.js | 6 ++++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/detox/src/devices/AppleSimUtils.js b/detox/src/devices/AppleSimUtils.js index 9a259ccac0..6638e9bd7f 100644 --- a/detox/src/devices/AppleSimUtils.js +++ b/detox/src/devices/AppleSimUtils.js @@ -103,9 +103,7 @@ class AppleSimUtils { `/usr/bin/xcrun simctl launch --stdout=${logsInfo.simStdout} --stderr=${logsInfo.simStderr} ` + `${udid} ${bundleId} --args ${args}`; const result = await exec.execWithRetriesAndLogs(launchBin, undefined, statusLogs, 1); - // return parseInt(result.stdout.trim().split(':')[1]); - // exec.mockReturnValue({stdout: "appId: 22 \n"}); - + return parseInt(_.get(result, 'stdout', ':').trim().split(':')[1]); } async terminate() { diff --git a/detox/src/devices/AppleSimUtils.test.js b/detox/src/devices/AppleSimUtils.test.js index 4706e5932b..28833c3d1d 100644 --- a/detox/src/devices/AppleSimUtils.test.js +++ b/detox/src/devices/AppleSimUtils.test.js @@ -232,6 +232,12 @@ describe('AppleSimUtils', () => { expect(e).toBeDefined(); } }); + + it('returns the parsed id', async () => { + exec.execWithRetriesAndLogs.mockReturnValueOnce(Promise.resolve({stdout: 'appId: 12345 \n'})); + const result = await uut.launch('udid', 'theBundleId'); + expect(result).toEqual(12345); + }); }); }); From dbed5add2179b15a7d7ab6b7ab5cd373ec9007a0 Mon Sep 17 00:00:00 2001 From: Daniel Zlotin Date: Sun, 24 Sep 2017 20:17:47 +0300 Subject: [PATCH 11/20] migrated sendToHome --- detox/src/devices/AppleSimUtils.js | 41 +++++++++++++++++-------- detox/src/devices/AppleSimUtils.test.js | 11 ++++++- detox/src/devices/Fbsimctl.js | 7 ----- 3 files changed, 39 insertions(+), 20 deletions(-) diff --git a/detox/src/devices/AppleSimUtils.js b/detox/src/devices/AppleSimUtils.js index 6638e9bd7f..e2068229ed 100644 --- a/detox/src/devices/AppleSimUtils.js +++ b/detox/src/devices/AppleSimUtils.js @@ -90,20 +90,14 @@ class AppleSimUtils { async launch(udid, bundleId, launchArgs) { const frameworkPath = await environment.getFrameworkPath(); const logsInfo = new LogsInfo(udid); - const args = _.map(launchArgs, (v, k) => `${k} ${v}`).join(' ').trim(); + const args = this._joinLaunchArgs(launchArgs); - const statusLogs = { - trying: `Launching ${bundleId}...`, - successful: `${bundleId} launched. The stdout and stderr logs were recreated, you can watch them with:\n` + - ` tail -F ${logsInfo.absJoined}` - }; + const result = await this._launchMagically(frameworkPath, logsInfo, udid, bundleId, args); + return this._parseLaunchId(result); + } - const launchBin = `/bin/cat /dev/null >${logsInfo.absStdout} 2>${logsInfo.absStderr} && ` + - `SIMCTL_CHILD_DYLD_INSERT_LIBRARIES="${frameworkPath}/Detox" ` + - `/usr/bin/xcrun simctl launch --stdout=${logsInfo.simStdout} --stderr=${logsInfo.simStderr} ` + - `${udid} ${bundleId} --args ${args}`; - const result = await exec.execWithRetriesAndLogs(launchBin, undefined, statusLogs, 1); - return parseInt(_.get(result, 'stdout', ':').trim().split(':')[1]); + async sendToHome(udid) { + await exec.execWithRetriesAndLogs(`/usr/bin/xcrun simctl launch ${udid} com.apple.springboard`); } async terminate() { @@ -138,6 +132,29 @@ class AppleSimUtils { "-DeviceSetPath $HOME/Library/Developer/CoreSimulator/Devices > /dev/null 2>&1 < /dev/null &'"; await exec.execWithRetriesAndLogs(cmd, undefined, { trying: `Launching device ${udid}...` }, 1); } + + _joinLaunchArgs(launchArgs) { + return _.map(launchArgs, (v, k) => `${k} ${v}`).join(' ').trim(); + } + + async _launchMagically(frameworkPath, logsInfo, udid, bundleId, args) { + const statusLogs = { + trying: `Launching ${bundleId}...`, + successful: `${bundleId} launched. The stdout and stderr logs were recreated, you can watch them with:\n` + + ` tail -F ${logsInfo.absJoined}` + }; + + const launchBin = `/bin/cat /dev/null >${logsInfo.absStdout} 2>${logsInfo.absStderr} && ` + + `SIMCTL_CHILD_DYLD_INSERT_LIBRARIES="${frameworkPath}/Detox" ` + + `/usr/bin/xcrun simctl launch --stdout=${logsInfo.simStdout} --stderr=${logsInfo.simStderr} ` + + `${udid} ${bundleId} --args ${args}`; + + return await exec.execWithRetriesAndLogs(launchBin, undefined, statusLogs, 1); + } + + _parseLaunchId(result) { + return parseInt(_.get(result, 'stdout', ':').trim().split(':')[1]); + } } class LogsInfo { diff --git a/detox/src/devices/AppleSimUtils.test.js b/detox/src/devices/AppleSimUtils.test.js index 28833c3d1d..eb0bceac94 100644 --- a/detox/src/devices/AppleSimUtils.test.js +++ b/detox/src/devices/AppleSimUtils.test.js @@ -234,10 +234,19 @@ describe('AppleSimUtils', () => { }); it('returns the parsed id', async () => { - exec.execWithRetriesAndLogs.mockReturnValueOnce(Promise.resolve({stdout: 'appId: 12345 \n'})); + exec.execWithRetriesAndLogs.mockReturnValueOnce(Promise.resolve({ stdout: 'appId: 12345 \n' })); const result = await uut.launch('udid', 'theBundleId'); expect(result).toEqual(12345); }); }); + + describe('sendToHome', () => { + it('calls xcrun', async () => { + await uut.sendToHome('theUdid'); + expect(exec.execWithRetriesAndLogs).toHaveBeenCalledTimes(1); + expect(exec.execWithRetriesAndLogs).toHaveBeenCalledWith(expect.stringMatching(/.*xcrun simctl launch theUdid.*/)); + }); + }); + }); diff --git a/detox/src/devices/Fbsimctl.js b/detox/src/devices/Fbsimctl.js index 66c8e63b75..51afde653d 100644 --- a/detox/src/devices/Fbsimctl.js +++ b/detox/src/devices/Fbsimctl.js @@ -11,13 +11,6 @@ const retry = require('../utils/retry'); class Fbsimctl { - - - async sendToHome(udid) { - const result = await exec.execWithRetriesAndLogs(`/usr/bin/xcrun simctl launch ${udid} com.apple.springboard`); - return parseInt(result.stdout.trim().split(':')[1]); - } - getLogsPaths(udid) { const logsInfo = new LogsInfo(udid); return { From edab6bc057c83eacebc1f52f635fac515af33b0d Mon Sep 17 00:00:00 2001 From: Daniel Zlotin Date: Sun, 24 Sep 2017 20:21:03 +0300 Subject: [PATCH 12/20] migrated logsPath --- detox/src/devices/AppleSimUtils.js | 8 ++++++++ detox/src/devices/AppleSimUtils.test.js | 8 ++++++++ detox/src/devices/Fbsimctl.js | 8 +------- detox/src/devices/Fbsimctl.test.js | 14 -------------- 4 files changed, 17 insertions(+), 21 deletions(-) diff --git a/detox/src/devices/AppleSimUtils.js b/detox/src/devices/AppleSimUtils.js index e2068229ed..7f432f58f3 100644 --- a/detox/src/devices/AppleSimUtils.js +++ b/detox/src/devices/AppleSimUtils.js @@ -100,6 +100,14 @@ class AppleSimUtils { await exec.execWithRetriesAndLogs(`/usr/bin/xcrun simctl launch ${udid} com.apple.springboard`); } + getLogsPaths(udid) { + const logsInfo = new LogsInfo(udid); + return { + stdout: logsInfo.absStdout, + stderr: logsInfo.absStderr + } + } + async terminate() { fail(); } diff --git a/detox/src/devices/AppleSimUtils.test.js b/detox/src/devices/AppleSimUtils.test.js index eb0bceac94..50023579f9 100644 --- a/detox/src/devices/AppleSimUtils.test.js +++ b/detox/src/devices/AppleSimUtils.test.js @@ -248,5 +248,13 @@ describe('AppleSimUtils', () => { }); }); + describe('getLogsPaths', () => { + it('returns correct paths', () => { + expect(uut.getLogsPaths('123')).toEqual({ + stdout: '$HOME/Library/Developer/CoreSimulator/Devices/123/data/tmp/detox.last_launch_app_log.out', + stderr: '$HOME/Library/Developer/CoreSimulator/Devices/123/data/tmp/detox.last_launch_app_log.err' + }) + }); + }); }); diff --git a/detox/src/devices/Fbsimctl.js b/detox/src/devices/Fbsimctl.js index 51afde653d..c67f0e99dc 100644 --- a/detox/src/devices/Fbsimctl.js +++ b/detox/src/devices/Fbsimctl.js @@ -11,13 +11,7 @@ const retry = require('../utils/retry'); class Fbsimctl { - getLogsPaths(udid) { - const logsInfo = new LogsInfo(udid); - return { - stdout: logsInfo.absStdout, - stderr: logsInfo.absStderr - } - } + async terminate(udid, bundleId) { const launchBin = `/usr/bin/xcrun simctl terminate ${udid} ${bundleId}`; diff --git a/detox/src/devices/Fbsimctl.test.js b/detox/src/devices/Fbsimctl.test.js index b9f1d90d6a..9c52046086 100644 --- a/detox/src/devices/Fbsimctl.test.js +++ b/detox/src/devices/Fbsimctl.test.js @@ -20,13 +20,6 @@ xdescribe('Fbsimctl', () => { // }); }); - it(`sendToHome() - is triggering exec`, async() => { - fs.existsSync.mockReturnValue(true); - exec.mockReturnValue({stdout: "appId: 22 \n"}); - await fbsimctl.sendToHome(simUdid, bundleId, []); - expect(exec).toHaveBeenCalledTimes(1); - }); - it(`terminate() - is triggering exec`, async() => { await fbsimctl.terminate(simUdid, bundleId); expect(exec).toHaveBeenCalledTimes(1); @@ -83,13 +76,6 @@ xdescribe('Fbsimctl', () => { const options = {args: `an argument`}; expect(await fbsimctl._execFbsimctlCommand(options, '', 10, 1)).toEqual(successfulResult); }); - - it(`getLogsPath() should return proper paths`, () => { - expect(fbsimctl.getLogsPaths('123')).toEqual({ - stdout: '$HOME/Library/Developer/CoreSimulator/Devices/123/data/tmp/detox.last_launch_app_log.out', - stderr: '$HOME/Library/Developer/CoreSimulator/Devices/123/data/tmp/detox.last_launch_app_log.err' - }) - }); }); // async function validateFbsimctlisCalledOn(fbsimctl, func) { From dfcae59c82d5c231cf887865b59ce9331f869023 Mon Sep 17 00:00:00 2001 From: Daniel Zlotin Date: Sun, 24 Sep 2017 20:24:47 +0300 Subject: [PATCH 13/20] migrated termiate --- detox/src/devices/AppleSimUtils.js | 9 +++++++-- detox/src/devices/AppleSimUtils.test.js | 12 ++++++++++++ detox/src/devices/Fbsimctl.js | 8 +------- detox/src/devices/Fbsimctl.test.js | 17 ----------------- 4 files changed, 20 insertions(+), 26 deletions(-) diff --git a/detox/src/devices/AppleSimUtils.js b/detox/src/devices/AppleSimUtils.js index 7f432f58f3..7448db8b0c 100644 --- a/detox/src/devices/AppleSimUtils.js +++ b/detox/src/devices/AppleSimUtils.js @@ -108,8 +108,13 @@ class AppleSimUtils { } } - async terminate() { - fail(); + async terminate(udid, bundleId) { + const statusLogs = { + trying: `Terminating ${bundleId}...`, + successful: `${bundleId} terminated` + }; + const launchBin = `/usr/bin/xcrun simctl terminate ${udid} ${bundleId}`; + await exec.execWithRetriesAndLogs(launchBin, undefined, statusLogs, 1); } async _execAppleSimUtils(options, statusLogs, retries, interval) { diff --git a/detox/src/devices/AppleSimUtils.test.js b/detox/src/devices/AppleSimUtils.test.js index 50023579f9..8dd07606d8 100644 --- a/detox/src/devices/AppleSimUtils.test.js +++ b/detox/src/devices/AppleSimUtils.test.js @@ -256,5 +256,17 @@ describe('AppleSimUtils', () => { }) }); }); + + describe('terminate', () => { + it('calls xcrun simctl', async () => { + await uut.terminate('theUdid', 'thebundleId'); + expect(exec.execWithRetriesAndLogs).toHaveBeenCalledTimes(1); + expect(exec.execWithRetriesAndLogs).toHaveBeenCalledWith( + expect.stringMatching(/.*xcrun simctl terminate theUdid thebundleId.*/), + undefined, + expect.anything(), + 1); + }); + }); }); diff --git a/detox/src/devices/Fbsimctl.js b/detox/src/devices/Fbsimctl.js index c67f0e99dc..2d3757d15f 100644 --- a/detox/src/devices/Fbsimctl.js +++ b/detox/src/devices/Fbsimctl.js @@ -13,13 +13,7 @@ class Fbsimctl { - async terminate(udid, bundleId) { - const launchBin = `/usr/bin/xcrun simctl terminate ${udid} ${bundleId}`; - await exec.execWithRetriesAndLogs(launchBin, undefined, { - trying: `Terminating ${bundleId}...`, - successful: `${bundleId} terminated` - }, 1); - } + async shutdown(udid) { const options = { args: `${udid} shutdown` }; diff --git a/detox/src/devices/Fbsimctl.test.js b/detox/src/devices/Fbsimctl.test.js index 9c52046086..3fd6d93d07 100644 --- a/detox/src/devices/Fbsimctl.test.js +++ b/detox/src/devices/Fbsimctl.test.js @@ -20,23 +20,6 @@ xdescribe('Fbsimctl', () => { // }); }); - it(`terminate() - is triggering exec`, async() => { - await fbsimctl.terminate(simUdid, bundleId); - expect(exec).toHaveBeenCalledTimes(1); - }); - - it(`shutdown() - is triggering fbsimctl shutdown`, async() => { - await validateFbsimctlisCalledOn(fbsimctl, async () => fbsimctl.shutdown(simUdid)); - }); - - it(`open() - is triggering fbsimctl open`, async() => { - await validateFbsimctlisCalledOn(fbsimctl, async () => fbsimctl.open(simUdid, bundleId)); - }); - - it(`setLocation() - is triggering fbsimctl set_location`, async() => { - await validateFbsimctlisCalledOn(fbsimctl, async () => fbsimctl.setLocation(simUdid)); - }); - it(`resetContentAndSettings() - is triggering shutdown, exec and boot`, async() => { fs.existsSync.mockReturnValue(true); exec.mockReturnValue({stdout: "appId: 22 \n"}); From 1fcf61a6c6e72e2b9a9bba8347d3ef0ad2c1e85a Mon Sep 17 00:00:00 2001 From: Daniel Zlotin Date: Mon, 25 Sep 2017 12:31:59 +0300 Subject: [PATCH 14/20] finished migration --- detox/package.json | 3 +- detox/src/devices/AppleSimUtils.js | 44 ++++++++-- detox/src/devices/AppleSimUtils.test.js | 76 ++++++++++++++++- detox/src/devices/Fbsimctl.js | 47 ----------- detox/src/devices/Fbsimctl.test.js | 105 ------------------------ detox/src/utils/environment.js | 1 - 6 files changed, 111 insertions(+), 165 deletions(-) delete mode 100644 detox/src/devices/Fbsimctl.js delete mode 100644 detox/src/devices/Fbsimctl.test.js diff --git a/detox/package.json b/detox/package.json index fc59c26e88..c485790e62 100644 --- a/detox/package.json +++ b/detox/package.json @@ -78,7 +78,8 @@ ".*Driver.js", "DeviceDriverBase.js", "GREYConfiguration.js", - "src/ios/earlgreyapi" + "src/ios/earlgreyapi", + "src/utils/environment.js" ], "resetMocks": true, "resetModules": true, diff --git a/detox/src/devices/AppleSimUtils.js b/detox/src/devices/AppleSimUtils.js index 7448db8b0c..d28932eef0 100644 --- a/detox/src/devices/AppleSimUtils.js +++ b/detox/src/devices/AppleSimUtils.js @@ -70,8 +70,7 @@ class AppleSimUtils { trying: `Installing ${absPath}...`, successful: `${absPath} installed` }; - const cmd = `/usr/bin/xcrun simctl install ${udid} ${absPath}`; - await exec.execWithRetriesAndLogs(cmd, undefined, statusLogs, 1); + await this._execSimctl({ cmd: `install ${udid} ${absPath}`, statusLogs }); } async uninstall(udid, bundleId) { @@ -79,9 +78,8 @@ class AppleSimUtils { trying: `Uninstalling ${bundleId}...`, successful: `${bundleId} uninstalled` }; - const cmd = `/usr/bin/xcrun simctl uninstall ${udid} ${bundleId}`; try { - await exec.execWithRetriesAndLogs(cmd, undefined, statusLogs, 1); + await this._execSimctl({ cmd: `uninstall ${udid} ${bundleId}`, statusLogs }); } catch (e) { // that's fine } @@ -97,7 +95,7 @@ class AppleSimUtils { } async sendToHome(udid) { - await exec.execWithRetriesAndLogs(`/usr/bin/xcrun simctl launch ${udid} com.apple.springboard`); + await this._execSimctl({ cmd: `launch ${udid} com.apple.springboard`, retries: 10 }); } getLogsPaths(udid) { @@ -113,8 +111,36 @@ class AppleSimUtils { trying: `Terminating ${bundleId}...`, successful: `${bundleId} terminated` }; - const launchBin = `/usr/bin/xcrun simctl terminate ${udid} ${bundleId}`; - await exec.execWithRetriesAndLogs(launchBin, undefined, statusLogs, 1); + await this._execSimctl({ cmd: `terminate ${udid} ${bundleId}`, statusLogs }); + } + + async shutdown(udid) { + const statusLogs = { + trying: `Shutting down ${udid}...`, + successful: `${udid} shut down` + }; + await this._execSimctl({ cmd: `shutdown ${udid}`, statusLogs }); + } + + async openUrl(udid, url) { + await this._execSimctl({ cmd: `openurl ${udid} ${url}` }); + } + + async setLocation(udid, lat, lon) { + const result = await exec.execWithRetriesAndLogs(`which fbsimctl`, {}, {}, 1); + if (_.get(result, 'stdout')) { + await exec.execWithRetriesAndLogs(`fbsimctl ${udid} set_location ${lat} ${lon}`, {}, {}, 1); + } else { + throw new Error(`setLocation currently supported only through fbsimctl. + Install fbsimctl using: + "brew tap facebook/fb && export CODE_SIGNING_REQUIRED=NO && brew install fbsimctl"`); + } + } + + async resetContentAndSettings(udid) { + await this.shutdown(udid); + await this._execSimctl({ cmd: `erase ${udid}` }); + await this.boot(udid); } async _execAppleSimUtils(options, statusLogs, retries, interval) { @@ -122,6 +148,10 @@ class AppleSimUtils { return await exec.execWithRetriesAndLogs(bin, options, statusLogs, retries, interval); } + async _execSimctl({ cmd, statusLogs = {}, retries = 1 }) { + return await exec.execWithRetriesAndLogs(`/usr/bin/xcrun simctl ${cmd}`, {}, statusLogs, retries); + } + _correctQueryWithOS(query) { let correctQuery = query; if (_.includes(query, ',')) { diff --git a/detox/src/devices/AppleSimUtils.test.js b/detox/src/devices/AppleSimUtils.test.js index 8dd07606d8..50d0003f93 100644 --- a/detox/src/devices/AppleSimUtils.test.js +++ b/detox/src/devices/AppleSimUtils.test.js @@ -167,7 +167,7 @@ describe('AppleSimUtils', () => { expect(exec.execWithRetriesAndLogs).toHaveBeenCalledTimes(1); expect(exec.execWithRetriesAndLogs).toHaveBeenCalledWith( `/usr/bin/xcrun simctl install udid somePath`, - undefined, + expect.anything(), expect.anything(), 1); }); @@ -179,7 +179,7 @@ describe('AppleSimUtils', () => { expect(exec.execWithRetriesAndLogs).toHaveBeenCalledTimes(1); expect(exec.execWithRetriesAndLogs).toHaveBeenCalledWith( `/usr/bin/xcrun simctl uninstall udid theBundleId`, - undefined, + expect.anything(), expect.anything(), 1); }); @@ -244,7 +244,12 @@ describe('AppleSimUtils', () => { it('calls xcrun', async () => { await uut.sendToHome('theUdid'); expect(exec.execWithRetriesAndLogs).toHaveBeenCalledTimes(1); - expect(exec.execWithRetriesAndLogs).toHaveBeenCalledWith(expect.stringMatching(/.*xcrun simctl launch theUdid.*/)); + expect(exec.execWithRetriesAndLogs).toHaveBeenCalledWith( + expect.stringMatching(/.*xcrun simctl launch theUdid.*/), + expect.anything(), + expect.anything(), + 10 + ); }); }); @@ -263,10 +268,73 @@ describe('AppleSimUtils', () => { expect(exec.execWithRetriesAndLogs).toHaveBeenCalledTimes(1); expect(exec.execWithRetriesAndLogs).toHaveBeenCalledWith( expect.stringMatching(/.*xcrun simctl terminate theUdid thebundleId.*/), - undefined, + expect.anything(), expect.anything(), 1); }); }); + + describe('shutdown', () => { + it('calls xcrun simctl', async () => { + await uut.shutdown('theUdid'); + expect(exec.execWithRetriesAndLogs).toHaveBeenCalledTimes(1); + expect(exec.execWithRetriesAndLogs).toHaveBeenCalledWith( + expect.stringMatching(/.*xcrun simctl shutdown theUdid.*/), + expect.anything(), + expect.anything(), + 1); + }); + }); + + describe('openUrl', () => { + it('calls xcrun simctl', async () => { + await uut.openUrl('theUdid', 'someUrl'); + expect(exec.execWithRetriesAndLogs).toHaveBeenCalledTimes(1); + expect(exec.execWithRetriesAndLogs).toHaveBeenCalledWith( + expect.stringMatching(/.*xcrun simctl openurl theUdid someUrl.*/), + expect.anything(), + expect.anything(), + 1); + }); + }); + + describe('setLocation', () => { + it('throws when no fbsimctl installed', async () => { + try { + await uut.setLocation('theUdid', 123.456, 789.123); + fail(`should throw`); + } catch (e) { + expect(e.message).toMatch(/.*Install fbsimctl using.*/); + expect(exec.execWithRetriesAndLogs).toHaveBeenCalledTimes(1); + } + }); + + it('calls fbsimctl set_location', async () => { + exec.execWithRetriesAndLogs.mockReturnValueOnce(Promise.resolve({ stdout: `true` })); + await uut.setLocation('theUdid', 123.456, 789.123); + expect(exec.execWithRetriesAndLogs).toHaveBeenCalledTimes(2); + expect(exec.execWithRetriesAndLogs.mock.calls[1][0]) + .toMatch(/.*fbsimctl theUdid set_location 123.456 789.123.*/); + }); + }); + + describe('resetContentAndSettings', () => { + it('shutdown, simctl erase, then boot', async () => { + uut.shutdown = jest.fn(); + uut.boot = jest.fn(); + expect(uut.shutdown).not.toHaveBeenCalled(); + expect(uut.boot).not.toHaveBeenCalled(); + await uut.resetContentAndSettings('theUdid'); + expect(uut.shutdown).toHaveBeenCalledTimes(1); + expect(exec.execWithRetriesAndLogs).toHaveBeenCalledTimes(1); + expect(exec.execWithRetriesAndLogs).toHaveBeenCalledWith( + expect.stringMatching(/.*xcrun simctl erase theUdid.*/), + expect.anything(), + expect.anything(), + 1); + expect(uut.boot).toHaveBeenCalledTimes(1); + }); + }); + }); diff --git a/detox/src/devices/Fbsimctl.js b/detox/src/devices/Fbsimctl.js deleted file mode 100644 index 2d3757d15f..0000000000 --- a/detox/src/devices/Fbsimctl.js +++ /dev/null @@ -1,47 +0,0 @@ -const _ = require('lodash'); -const log = require('npmlog'); -const exec = require('../utils/exec'); -const retry = require('../utils/retry'); - -// FBSimulatorControl command line docs -// https://github.com/facebook/FBSimulatorControl/issues/250 -// https://github.com/facebook/FBSimulatorControl/blob/master/fbsimctl/FBSimulatorControlKitTests/Tests/Unit/CommandParsersTests.swift - - - -class Fbsimctl { - - - - - - async shutdown(udid) { - const options = { args: `${udid} shutdown` }; - await this._execFbsimctlCommand(options); - } - - async open(udid, url) { - const options = { args: `${udid} open ${url}` }; - await this._execFbsimctlCommand(options); - } - - async setLocation(udid, lat, lon) { - const options = { args: `${udid} set_location ${lat} ${lon}` }; - await this._execFbsimctlCommand(options); - } - - async resetContentAndSettings(udid) { - await this.shutdown(udid); - const result = await exec.execWithRetriesAndLogs(`/usr/bin/xcrun simctl erase ${udid}`); - const resultCode = parseInt(result.stdout.trim().split(':')[1]); - await this.boot(udid); - return resultCode; - } - - async _execFbsimctlCommand(options, statusLogs, retries, interval) { - const bin = `fbsimctl --json`; - return await exec.execWithRetriesAndLogs(bin, options, statusLogs, retries, interval); - } -} - -module.exports = Fbsimctl; diff --git a/detox/src/devices/Fbsimctl.test.js b/detox/src/devices/Fbsimctl.test.js deleted file mode 100644 index 3fd6d93d07..0000000000 --- a/detox/src/devices/Fbsimctl.test.js +++ /dev/null @@ -1,105 +0,0 @@ - -const _ = require('lodash'); - -xdescribe('Fbsimctl', () => { - let fbsimctl; - let exec; - let fs; - - const simUdid = `9C9ABE4D-70C7-49DC-A396-3CB1D0E82846`; - const bundleId = 'bundle.id'; - - beforeEach(() => { - // jest.mock('npmlog'); - // jest.mock('fs'); - // fs = require('fs'); - // jest.mock('../utils/exec'); - // exec = require('../utils/exec').execWithRetriesAndLogs; - // jest.setMock('../utils/retry', async (options, func) => { - // return await func(1); - // }); - }); - - it(`resetContentAndSettings() - is triggering shutdown, exec and boot`, async() => { - fs.existsSync.mockReturnValue(true); - exec.mockReturnValue({stdout: "appId: 22 \n"}); - fbsimctl.shutdown = jest.fn(); - fbsimctl.boot = jest.fn(); - await fbsimctl.resetContentAndSettings(simUdid); - expect(fbsimctl.shutdown).toHaveBeenCalledTimes(1); - expect(exec).toHaveBeenCalledTimes(1); - expect(fbsimctl.boot).toHaveBeenCalledTimes(1); - }); - - it(`exec simulator command successfully`, async() => { - const result = returnSuccessfulWithValue(""); - exec.mockReturnValue(Promise.resolve(result)); - const options = {args: `an argument`}; - expect(await fbsimctl._execFbsimctlCommand(options)).toEqual(result); - }); - - it(`exec simulator command with error`, async() => { - const errorResult = returnErrorWithValue(''); - exec.mockReturnValue(Promise.reject(errorResult)); - const options = {args: `an argument`}; - - try { - await fbsimctl._execFbsimctlCommand(options, '', 10, 1); - } catch (object) { - expect(object).toEqual(errorResult); - } - }); - - it(`exec simulator command with multiple errors and then a success`, async() => { - const successfulResult = returnSuccessfulWithValue('successful result'); - const resolvedPromise = Promise.resolve(successfulResult); - - exec.mockReturnValueOnce(resolvedPromise); - - const options = {args: `an argument`}; - expect(await fbsimctl._execFbsimctlCommand(options, '', 10, 1)).toEqual(successfulResult); - }); -}); - -// async function validateFbsimctlisCalledOn(fbsimctl, func) { -// fbsimctl._execFbsimctlCommand = jest.fn(); -// func(); -// expect(fbsimctl._execFbsimctlCommand).toHaveBeenCalledTimes(1); -// } - -// function listAsimUdidAtState(udid, state) { -// return { -// "event_type": "discrete", -// "timestamp": 1485328213, -// "subject": { -// "state": state, -// "os": "iOS 10.1", -// "name": "iPhone 7", -// "udid": udid, -// "device-name": "iPhone 7" -// }, -// "event_name": "list" -// }; -// } - -// function returnSuccessfulWithValue(value) { -// const result = { -// stdout: JSON.stringify(value), -// stderr: "", -// childProcess: { -// exitCode: 0 -// } -// }; -// return result; -// } - -// function returnErrorWithValue(value) { -// const result = { -// stdout: "", -// stderr: value, -// childProcess: { -// exitCode: 1 -// } -// }; -// return result; -// } diff --git a/detox/src/utils/environment.js b/detox/src/utils/environment.js index 1ea657a128..0bf22b242b 100644 --- a/detox/src/utils/environment.js +++ b/detox/src/utils/environment.js @@ -2,7 +2,6 @@ const os = require('os'); const path = require('path'); const exec = require('child-process-promise').exec; - function getDetoxVersion() { return require(path.join(__dirname, '../../package.json')).version; } From 296d18a5d181f083f25803cf1ef6abcc2a2f1e23 Mon Sep 17 00:00:00 2001 From: Daniel Zlotin Date: Mon, 25 Sep 2017 13:21:30 +0300 Subject: [PATCH 15/20] fixing e2e --- detox/src/devices/AppleSimUtils.js | 17 ++++++----- detox/src/devices/AppleSimUtils.test.js | 39 ++++++++++++++++++++----- detox/src/devices/SimulatorDriver.js | 6 +++- detox/src/utils/exec.js | 2 +- 4 files changed, 47 insertions(+), 17 deletions(-) diff --git a/detox/src/devices/AppleSimUtils.js b/detox/src/devices/AppleSimUtils.js index d28932eef0..02cf17c5b6 100644 --- a/detox/src/devices/AppleSimUtils.js +++ b/detox/src/devices/AppleSimUtils.js @@ -25,7 +25,7 @@ class AppleSimUtils { }; let correctQuery = this._correctQueryWithOS(query); const response = await this._execAppleSimUtils({ args: `--list "${correctQuery}" --maxResults=1` }, statusLogs, 1); - const parsed = this._parseStdout(response); + const parsed = this._parseResponseFromAppleSimUtils(response); const udid = _.get(parsed, [0, 'udid']); if (!udid) { throw new Error(`Can't find a simulator to match with "${query}", run 'xcrun simctl list' to list your supported devices. @@ -36,7 +36,7 @@ class AppleSimUtils { async findDeviceByUDID(udid) { const response = await this._execAppleSimUtils({ args: `--list` }, undefined, 1); - const parsed = this._parseStdout(response); + const parsed = this._parseResponseFromAppleSimUtils(response); const device = _.find(parsed, (device) => _.isEqual(device.udid, udid)); if (!device) { throw new Error(`Can't find device ${udid}`); @@ -149,7 +149,7 @@ class AppleSimUtils { } async _execSimctl({ cmd, statusLogs = {}, retries = 1 }) { - return await exec.execWithRetriesAndLogs(`/usr/bin/xcrun simctl ${cmd}`, {}, statusLogs, retries); + return await exec.execWithRetriesAndLogs(`/usr/bin/xcrun simctl ${cmd}`, undefined, statusLogs, retries); } _correctQueryWithOS(query) { @@ -161,12 +161,15 @@ class AppleSimUtils { return correctQuery; } - _parseStdout(response) { - const stdout = _.get(response, 'stdout'); - if (_.isEmpty(stdout)) { + _parseResponseFromAppleSimUtils(response) { + let out = _.get(response, 'stdout'); + if (_.isEmpty(out)) { + out = _.get(response, 'stderr'); + } + if (_.isEmpty(out)) { return undefined; } - return JSON.parse(stdout); + return JSON.parse(out); } async _bootDeviceMagically(udid) { diff --git a/detox/src/devices/AppleSimUtils.test.js b/detox/src/devices/AppleSimUtils.test.js index 50d0003f93..c2becc0c08 100644 --- a/detox/src/devices/AppleSimUtils.test.js +++ b/detox/src/devices/AppleSimUtils.test.js @@ -22,7 +22,8 @@ describe('AppleSimUtils', () => { }); it(`appleSimUtils setPermissions`, async () => { - uut.setPermissions(bundleId, simUdid, { permissions: { calendar: "YES" } }); + uut.setPermissions(bundleId, simUdid, { permissions: + { calendar: "YES" } }); expect(exec.execWithRetriesAndLogs).toHaveBeenCalledTimes(1); }); @@ -69,6 +70,28 @@ describe('AppleSimUtils', () => { expect(result).toEqual('the uuid'); }); + it('handles stderr as if stdout', async () => { + exec.execWithRetriesAndLogs.mockReturnValueOnce(Promise.resolve({ + stderr: JSON.stringify([ + { + "state": "Shutdown", + "availability": "(available)", + "name": "iPhone 6", + "udid": "the uuid", + "os": { + "version": "10.3.1", + "availability": "(available)", + "name": "iOS 10.3", + "identifier": "com.apple.CoreSimulator.SimRuntime.iOS-10-3", + "buildversion": "14E8301" + } + } + ]) + })); + const result = await uut.findDeviceUDID('iPhone 7'); + expect(result).toEqual('the uuid'); + }); + describe('throws on bad response', () => { const args = [ null, @@ -167,7 +190,7 @@ describe('AppleSimUtils', () => { expect(exec.execWithRetriesAndLogs).toHaveBeenCalledTimes(1); expect(exec.execWithRetriesAndLogs).toHaveBeenCalledWith( `/usr/bin/xcrun simctl install udid somePath`, - expect.anything(), + undefined, expect.anything(), 1); }); @@ -179,7 +202,7 @@ describe('AppleSimUtils', () => { expect(exec.execWithRetriesAndLogs).toHaveBeenCalledTimes(1); expect(exec.execWithRetriesAndLogs).toHaveBeenCalledWith( `/usr/bin/xcrun simctl uninstall udid theBundleId`, - expect.anything(), + undefined, expect.anything(), 1); }); @@ -246,7 +269,7 @@ describe('AppleSimUtils', () => { expect(exec.execWithRetriesAndLogs).toHaveBeenCalledTimes(1); expect(exec.execWithRetriesAndLogs).toHaveBeenCalledWith( expect.stringMatching(/.*xcrun simctl launch theUdid.*/), - expect.anything(), + undefined, expect.anything(), 10 ); @@ -268,7 +291,7 @@ describe('AppleSimUtils', () => { expect(exec.execWithRetriesAndLogs).toHaveBeenCalledTimes(1); expect(exec.execWithRetriesAndLogs).toHaveBeenCalledWith( expect.stringMatching(/.*xcrun simctl terminate theUdid thebundleId.*/), - expect.anything(), + undefined, expect.anything(), 1); }); @@ -280,7 +303,7 @@ describe('AppleSimUtils', () => { expect(exec.execWithRetriesAndLogs).toHaveBeenCalledTimes(1); expect(exec.execWithRetriesAndLogs).toHaveBeenCalledWith( expect.stringMatching(/.*xcrun simctl shutdown theUdid.*/), - expect.anything(), + undefined, expect.anything(), 1); }); @@ -292,7 +315,7 @@ describe('AppleSimUtils', () => { expect(exec.execWithRetriesAndLogs).toHaveBeenCalledTimes(1); expect(exec.execWithRetriesAndLogs).toHaveBeenCalledWith( expect.stringMatching(/.*xcrun simctl openurl theUdid someUrl.*/), - expect.anything(), + undefined, expect.anything(), 1); }); @@ -329,7 +352,7 @@ describe('AppleSimUtils', () => { expect(exec.execWithRetriesAndLogs).toHaveBeenCalledTimes(1); expect(exec.execWithRetriesAndLogs).toHaveBeenCalledWith( expect.stringMatching(/.*xcrun simctl erase theUdid.*/), - expect.anything(), + undefined, expect.anything(), 1); expect(uut.boot).toHaveBeenCalledTimes(1); diff --git a/detox/src/devices/SimulatorDriver.js b/detox/src/devices/SimulatorDriver.js index c6c45c2263..3aa0ef2055 100644 --- a/detox/src/devices/SimulatorDriver.js +++ b/detox/src/devices/SimulatorDriver.js @@ -21,7 +21,11 @@ class SimulatorDriver extends IosDriver { async getBundleIdFromBinary(appPath) { try { const result = await exec(`/usr/libexec/PlistBuddy -c "Print CFBundleIdentifier" ${path.join(appPath, 'Info.plist')}`); - return _.trim(result.stdout); + const bundleId = _.trim(result.stdout); + if (_.isEmpty(bundleId)) { + throw new Error(); + } + return bundleId; } catch (ex) { throw new Error(`field CFBundleIdentifier not found inside Info.plist of app binary at ${appPath}`); } diff --git a/detox/src/utils/exec.js b/detox/src/utils/exec.js index 990fb8c5af..b2004c08d7 100644 --- a/detox/src/utils/exec.js +++ b/detox/src/utils/exec.js @@ -17,7 +17,7 @@ async function execWithRetriesAndLogs(bin, options, statusLogs, retries = 10, in log.verbose(`${_operationCounter}: ${cmd}`); let result; - await retry({retries, interval}, async () => { + await retry({ retries, interval }, async () => { if (statusLogs && statusLogs.trying) { log.info(`${_operationCounter}: ${statusLogs.trying}`); } From 804c85bcc489f6c6c4f2ddc02634457cdb011f43 Mon Sep 17 00:00:00 2001 From: Daniel Zlotin Date: Mon, 25 Sep 2017 13:33:28 +0300 Subject: [PATCH 16/20] only run location tests if fbsimctl installed --- detox/test/e2e/o-location.js | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/detox/test/e2e/o-location.js b/detox/test/e2e/o-location.js index b80ff6c6fe..7c8516023f 100644 --- a/detox/test/e2e/o-location.js +++ b/detox/test/e2e/o-location.js @@ -1,13 +1,34 @@ +const exec = require('child-process-promise').exec; + +async function isFbsimctlInstalled() { + try { + await exec(`which fbsimctl`); + return true; + } catch (e) { + return false; + } +} + describe('location', () => { it('Location should be unavabilable', async () => { - await device.relaunchApp({permissions: {location: 'never'}}); + const fbsimclInstalled = await isFbsimctlInstalled(); + if (!fbsimclInstalled) { + console.log(`setLocation only works through fbsimcl currently`); + return; + } + await device.relaunchApp({ permissions: { location: 'never' } }); await element(by.label('Location')).tap(); await element(by.id('getLocationButton')).tap(); await expect(element(by.id('error'))).toBeVisible(); }); it('Should receive location (20,20)', async () => { - await device.relaunchApp({permissions: {location: 'always'}}); + const fbsimclInstalled = await isFbsimctlInstalled(); + if (!fbsimclInstalled) { + console.log(`setLocation only works through fbsimcl currently`); + return; + } + await device.relaunchApp({ permissions: { location: 'always' } }); await device.setLocation(20, 20); await element(by.label('Location')).tap(); await element(by.id('getLocationButton')).tap(); @@ -17,3 +38,4 @@ describe('location', () => { await expect(element(by.text('Longitude: 20'))).toBeVisible(); }); }); + From 1825e5539a492bcd6b4b7db18648ef876cf4ddba Mon Sep 17 00:00:00 2001 From: Daniel Zlotin Date: Tue, 26 Sep 2017 12:19:09 +0300 Subject: [PATCH 17/20] fixed e2e --- detox/src/devices/AppleSimUtils.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/detox/src/devices/AppleSimUtils.js b/detox/src/devices/AppleSimUtils.js index 02cf17c5b6..9b28d10086 100644 --- a/detox/src/devices/AppleSimUtils.js +++ b/detox/src/devices/AppleSimUtils.js @@ -127,9 +127,9 @@ class AppleSimUtils { } async setLocation(udid, lat, lon) { - const result = await exec.execWithRetriesAndLogs(`which fbsimctl`, {}, {}, 1); + const result = await exec.execWithRetriesAndLogs(`which fbsimctl`, undefined, undefined, 1); if (_.get(result, 'stdout')) { - await exec.execWithRetriesAndLogs(`fbsimctl ${udid} set_location ${lat} ${lon}`, {}, {}, 1); + await exec.execWithRetriesAndLogs(`fbsimctl ${udid} set_location ${lat} ${lon}`, undefined, undefined, 1); } else { throw new Error(`setLocation currently supported only through fbsimctl. Install fbsimctl using: From 862564f2636e06d2d8965c0b1e68c4e99d2fe697 Mon Sep 17 00:00:00 2001 From: Daniel Zlotin Date: Tue, 26 Sep 2017 12:40:38 +0300 Subject: [PATCH 18/20] update docs --- docs/APIRef.Configuration.md | 2 +- docs/Guide.Contributing.md | 7 ------- docs/Guide.RunningOnCI.md | 8 -------- docs/Introduction.GettingStarted.md | 17 +++-------------- docs/More.DesignPrinciples.md | 2 +- docs/Troubleshooting.Flakiness.md | 4 ++-- docs/Troubleshooting.Installation.md | 15 ++------------- 7 files changed, 9 insertions(+), 46 deletions(-) diff --git a/docs/APIRef.Configuration.md b/docs/APIRef.Configuration.md index 1023f8caf0..b3ccb63b23 100644 --- a/docs/APIRef.Configuration.md +++ b/docs/APIRef.Configuration.md @@ -9,7 +9,7 @@ |---|---| |`binaryPath`|relative path to the ipa/app due to be tested (make sure you build the app in a project relative path)| |`type`|device type, currently only `ios.simulator` is supported| -|`name`|device name, aligns to the device list avaliable through `fbsimctl list` for example, this is one line of the output of `fbsimctl list`: `A3C93900-6D17-4830-8FBE-E102E4BBCBB9 iPhone 7 Shutdown iPhone 7 iOS 10.2`, ir order to choose the first `iPhone 7` regardless of OS version, use `iPhone 7`.
To be OS specific use `iPhone 7, iOS 10.2`| +|`name`|device name, aligns to the device list avaliable through `xcrun simctl list` for example, this is one line of the output of `xcrun simctl list`: `A3C93900-6D17-4830-8FBE-E102E4BBCBB9 iPhone 7 Shutdown iPhone 7 iOS 10.2`, ir order to choose the first `iPhone 7` regardless of OS version, use `iPhone 7`.
To be OS specific use `iPhone 7, iOS 10.2`| |`build`| **[optional]** build command (either `xcodebuild`, `react-native run-ios`, etc...), will be later available through detox CLI tool.| **Example:** diff --git a/docs/Guide.Contributing.md b/docs/Guide.Contributing.md index 131657dc0c..dabecb2c22 100644 --- a/docs/Guide.Contributing.md +++ b/docs/Guide.Contributing.md @@ -25,13 +25,6 @@ npm install -g react-native-cli For all the internal projects (detox, detox-server, detox-cli, demos, test) `lerna` will create symbolic links in `node_modules` instead of `npm` copying the content of the projects. This way, any change you do on any code is there immediately. There is no need to update node modules or copy files between projects. -### Install `fbsimctl` - -```sh -brew tap facebook/fb -export CODE_SIGNING_REQUIRED=NO && brew install fbsimctl --HEAD -``` - ### Install `xcpretty` ```sh diff --git a/docs/Guide.RunningOnCI.md b/docs/Guide.RunningOnCI.md index ab15e7b9be..6160347ccf 100644 --- a/docs/Guide.RunningOnCI.md +++ b/docs/Guide.RunningOnCI.md @@ -65,10 +65,6 @@ env: - NODE_VERSION=stable install: -- brew tap facebook/fb -- export CODE_SIGNING_REQUIRED=NO -- brew install fbsimctl --HEAD - - curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.2/install.sh | bash - export NVM_DIR="$HOME/.nvm" && [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" - nvm install $NODE_VERSION @@ -134,10 +130,6 @@ workflows: - content: |- #!/bin/bash - brew tap facebook/fb - export CODE_SIGNING_REQUIRED=NO - brew install fbsimctl - brew tap wix/brew brew install applesimutils --HEAD title: Install Detox Utils diff --git a/docs/Introduction.GettingStarted.md b/docs/Introduction.GettingStarted.md index 0da63a256a..4766be121e 100644 --- a/docs/Introduction.GettingStarted.md +++ b/docs/Introduction.GettingStarted.md @@ -36,19 +36,8 @@ Node is the JavaScript runtime Detox will run on. **Install Node 7.6.0 or above ``` > TIP: Verify it works by typing in terminal `node -v` to output current node version, should be higher than 7.6.0 - -#### 3. Install [fbsimctl](https://github.com/facebook/FBSimulatorControl/tree/master/fbsimctl) - -This tool by Facebook helps Detox manage and automate iOS Simulators. - - ```sh - brew tap facebook/fb - export CODE_SIGNING_REQUIRED=NO && brew install fbsimctl - ``` -Make sure you install a stable version, **do not** use `brew install fbsimctl --HEAD` as instructed in the original repo. -> TIP: Verify it works by typing in terminal `fbsimctl list` to output the list of available simulators -#### 4. Install [appleSimUtils](https://github.com/wix/AppleSimulatorUtils) +#### 3. Install [appleSimUtils](https://github.com/wix/AppleSimulatorUtils) A collection of utils for Apple simulators, Detox uses it to set (grant or deny) runtime permissions per application. @@ -59,7 +48,7 @@ brew install --HEAD applesimutils > TIP: Verify it works by typing in terminal `applesimutils` to output the tool help screen -#### 5. Install Detox command line tools (detox-cli) +#### 4. Install Detox command line tools (detox-cli) This package makes it easier to operate Detox from the command line. `detox-cli` should be installed globally, enabling usage of the command line tools outside of your npm scripts. @@ -107,7 +96,7 @@ The basic configuration for Detox should be in your `package.json` file under th In the above configuration example, change `example` to your actual project name. Under the key `"binaryPath"`, `example.app` should be `.app`. Under the key `"build"`, `example.xcodeproj` should be `.xcodeproj` and `-scheme example` should be `-scheme `. -Also make sure the simulator model specified under the key `"name"` (`iPhone 7` above) is actually available on your machine (it was installed by Xcode). Check this by typing `fbsimctl list` in terminal to display all available simulators. +Also make sure the simulator model specified under the key `"name"` (`iPhone 7` above) is actually available on your machine (it was installed by Xcode). Check this by typing `xcrun simctl list` in terminal to display all available simulators. > TIP: To test a release version, replace 'Debug' with 'Release' in the binaryPath and build properties. For full configuration options see Configuration under the API Reference. diff --git a/docs/More.DesignPrinciples.md b/docs/More.DesignPrinciples.md index 9767cbd6d8..d950a8f2a7 100644 --- a/docs/More.DesignPrinciples.md +++ b/docs/More.DesignPrinciples.md @@ -12,7 +12,7 @@ Traditionally, end-to-end tests on mobile are riddled with inherent issues, maki * **Friendly Protractor-like API for tests**

Tests in Detox are implemented in human-readable JavaScript and can even be shared between platforms. This easy to use API completely abstracts the complex native driver invocations taking place under the hood. -* **Detox controls devices through low-level APIs**

Let's take iOS simulators for example, which are difficult to control efficiently since multiple concurrent instances aren't supported. Detox uses fbsimctl by Facebook to work around these issues and support test sharding. +* **Detox controls devices through low-level APIs**

Let's take iOS simulators for example, which are difficult to control efficiently since multiple concurrent instances aren't supported. Detox uses [AppleSimulatorUtils](https://github.com/wix/AppleSimulatorUtils) (another opensource library by Wix) to work around these issues and support test sharding. * **Built from the ground up for mobile and React Native**

Detox is inspired by web testing methodologies but is not a direct translation of a solution designed for a different platform. Detox is built from the ground up for native mobile and has deep first-class support for React Native apps. diff --git a/docs/Troubleshooting.Flakiness.md b/docs/Troubleshooting.Flakiness.md index cb86075a77..fa9f46da31 100644 --- a/docs/Troubleshooting.Flakiness.md +++ b/docs/Troubleshooting.Flakiness.md @@ -14,7 +14,7 @@ Assume you have a suite of 100 tests and each test is flaky in 0.5% of execution It's important to identify the various sources of flakiness in Detox tests. -* Control of the device / simulator - in order to run your tests, Detox must communicate with a simulator and instruct it to install the app, restart it, etc. Simulators don't always behave and controlling them might occasionally fail.
Detox's underlying simulator control is [`fbsimctl`](https://github.com/facebook/FBSimulatorControl/tree/master/fbsimctl), it is a tool that supports both basic and advanced simulator and device interaction options, it uses some core simulator features which are not always stable and may need time to "warm up" (booting, shutting down, etc.). Detox is set to have a few retries on any of these actions before failing. It will also print all the `fbsimctl` commands when using verbose log level. +* Control of the device / simulator - in order to run your tests, Detox must communicate with a simulator and instruct it to install the app, restart it, etc. Simulators don't always behave and controlling them might occasionally fail.
Detox's underlying simulator control is [`AppleSimulatorUtils`](https://github.com/wix/AppleSimulatorUtils), it is a tool that supports both basic and advanced simulator and device interaction options, it uses some core simulator features which are not always stable and may need time to "warm up" (booting, shutting down, etc.). Detox is set to have a few retries on any of these actions before failing. It will also print all the `exec` commands when using verbose log level. * Asynchronous operations inside your app - every time an E2E test runs, operations might take place in a different order inside your app. This makes E2E tests nondeterministic. Consider an HTTP request made to a server, this request may take a variable time to complete due to external concerns like network congestion and server load.
Detox takes this into account by monitoring all asynchronous operations that take place in your app from the inside. Detox knows which network requests are currently in-flight. Detox knows how busy the React Native bridge is. Tests are automatically synchronized to the app and only move forward when the app is idle. @@ -23,7 +23,7 @@ It's important to identify the various sources of flakiness in Detox tests. In order to identify the source of flakiness you're suffering from you need more data. If you catch a failing test that should be passing, you need to record as much information as possible in order to investigate. * Enable verbose mode in Detox. This will output a lot of information about what happening during the test.
- 1. `fbsimctl` commands + 1. `exec` commands 2. All communication going over the websocket, both from tester and testee To enable verbose mode run your tests in verbose log mode: diff --git a/docs/Troubleshooting.Installation.md b/docs/Troubleshooting.Installation.md index eef3f4ff97..273e84ac06 100644 --- a/docs/Troubleshooting.Installation.md +++ b/docs/Troubleshooting.Installation.md @@ -1,24 +1,13 @@ # Troubleshooting Detox Installation -* [Can't install fbsimctl](#cant-install-fbsimctl) * [No simulators found](#no-simulators-found)
-### Can't install fbsimctl - -[fbsimctl](https://github.com/facebook/FBSimulatorControl) is an open source tool by Facebook for controlling iOS simulators from the command line with greater flexibility than Apple's own command line solution. This is a 3rd party tool that is normally installed with `brew`. Installation with `brew` actually takes the current master from GitHub and builds it on your machine using Xcode command line tools. This process is somewhat fragile and might fail. - -The best way to troubleshoot fbsimctl installation is to search for similar issues. This tool has been available for quite some time and has a loyal following. Start by searching the [GitHub issues](https://github.com/facebook/FBSimulatorControl/issues?utf8=%E2%9C%93&q=is%3Aissue) of the project. If you're certain the problem is with fbsimctl itself, please open a new issue in its [GitHub page](https://github.com/facebook/FBSimulatorControl). - -Sometimes the problem is with `brew` itself. There are a number of common troubleshooting steps to fix common `brew` issues, you can find them [here](https://github.com/Homebrew/brew/blob/master/docs/Troubleshooting.md). Another official list of common issues is available [here](http://docs.brew.sh/Common-Issues.html). Once your `brew` is back in order, remove fbsimctl with `brew remove fbsimctl` and try installing it again. - -
- ### No simulators found -In order to run tests on a simulator, you need to have simulator images installed on your machine. This process is performed by Xcode itself. You can list all available simulators using fbsimctl by typing `fbsimctl list` in terminal. +In order to run tests on a simulator, you need to have simulator images installed on your machine. This process is performed by Xcode itself. You can list all available simulators using simctl by typing `xcrun simctl list` in terminal. If you're missing a simulator, make sure Xcode is installed and use it to download the simulator. Take a look at the Preferences screen, some screenshots can be seen [here](http://stackoverflow.com/questions/33738113/how-to-install-ios-9-1-simulator-in-xcode-version-7-1-1-7b1005). -Once the desired simulator is installed and returned by `fbsimctl list`, double check its name in the list and make sure this name is found in the `detox` configuraton entry in `package.json`. The reference for the configuration options is available [here](APIRef.Configuration.md). +Once the desired simulator is installed and returned by `xcrun simctl list`, double check its name in the list and make sure this name is found in the `detox` configuraton entry in `package.json`. The reference for the configuration options is available [here](APIRef.Configuration.md). From edccc0bef79cf1dc3e6d8b682724427362c8e35a Mon Sep 17 00:00:00 2001 From: Daniel Zlotin Date: Tue, 26 Sep 2017 12:55:30 +0300 Subject: [PATCH 19/20] getXcodeVersion --- detox/src/devices/AppleSimUtils.js | 7 +++++++ detox/src/devices/AppleSimUtils.test.js | 16 ++++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/detox/src/devices/AppleSimUtils.js b/detox/src/devices/AppleSimUtils.js index 9b28d10086..3dcbe62c15 100644 --- a/detox/src/devices/AppleSimUtils.js +++ b/detox/src/devices/AppleSimUtils.js @@ -143,6 +143,13 @@ class AppleSimUtils { await this.boot(udid); } + async getXcodeVersion() { + const raw = await exec.execWithRetriesAndLogs(`xcodebuild -version`, undefined, undefined, 1); + const stdout = _.get(raw, 'stdout', ''); + const version = /^Xcode (\S+)\s+/.exec(stdout)[1]; + return version; + } + async _execAppleSimUtils(options, statusLogs, retries, interval) { const bin = `applesimutils`; return await exec.execWithRetriesAndLogs(bin, options, statusLogs, retries, interval); diff --git a/detox/src/devices/AppleSimUtils.test.js b/detox/src/devices/AppleSimUtils.test.js index c2becc0c08..675b315e13 100644 --- a/detox/src/devices/AppleSimUtils.test.js +++ b/detox/src/devices/AppleSimUtils.test.js @@ -22,8 +22,10 @@ describe('AppleSimUtils', () => { }); it(`appleSimUtils setPermissions`, async () => { - uut.setPermissions(bundleId, simUdid, { permissions: - { calendar: "YES" } }); + uut.setPermissions(bundleId, simUdid, { + permissions: + { calendar: "YES" } + }); expect(exec.execWithRetriesAndLogs).toHaveBeenCalledTimes(1); }); @@ -157,6 +159,16 @@ describe('AppleSimUtils', () => { }); }); + describe('getXcodeVersion', () => { + it('returns xcode version', async () => { + exec.execWithRetriesAndLogs.mockReturnValueOnce(Promise.resolve({ stdout: 'Xcode 123.456\nBuild version 123abc123\n' })); + const result = await uut.getXcodeVersion(); + expect(result).toEqual('123.456'); + expect(exec.execWithRetriesAndLogs).toHaveBeenCalledTimes(1); + expect(exec.execWithRetriesAndLogs).toBeCalledWith(`xcodebuild -version`, undefined, undefined, 1); + }); + }); + describe('boot', () => { it('waits for device by udid to be Shutdown, boots magically, then waits for state to be Booted', async () => { uut.findDeviceByUDID = jest.fn(() => Promise.resolve({ state: 'unknown' })); From f2ecf8bd2d1228d9c0bfcb5342d0f383d23073b5 Mon Sep 17 00:00:00 2001 From: Daniel Zlotin Date: Tue, 26 Sep 2017 13:41:10 +0300 Subject: [PATCH 20/20] boot HEADLESS MODE. you did good Apple. you did good. --- detox/src/devices/AppleSimUtils.js | 22 ++++++++++--- detox/src/devices/AppleSimUtils.test.js | 42 +++++++++++++++++++++++-- 2 files changed, 58 insertions(+), 6 deletions(-) diff --git a/detox/src/devices/AppleSimUtils.js b/detox/src/devices/AppleSimUtils.js index 3dcbe62c15..c927a83a45 100644 --- a/detox/src/devices/AppleSimUtils.js +++ b/detox/src/devices/AppleSimUtils.js @@ -61,7 +61,7 @@ class AppleSimUtils { return false; } await this.waitForDeviceState(udid, 'Shutdown'); - await this._bootDeviceMagically(udid); + await this._bootDeviceByXcodeVersion(udid); await this.waitForDeviceState(udid, 'Booted'); } @@ -145,9 +145,13 @@ class AppleSimUtils { async getXcodeVersion() { const raw = await exec.execWithRetriesAndLogs(`xcodebuild -version`, undefined, undefined, 1); - const stdout = _.get(raw, 'stdout', ''); - const version = /^Xcode (\S+)\s+/.exec(stdout)[1]; - return version; + const stdout = _.get(raw, 'stdout', 'undefined'); + const match = /^Xcode (\S+)\.*\S*\s*/.exec(stdout); + const majorVersion = parseInt(_.get(match, '[1]')); + if (!_.isInteger(majorVersion) || majorVersion < 1) { + throw new Error(`Can't read Xcode version, got: ${stdout}`); + } + return majorVersion; } async _execAppleSimUtils(options, statusLogs, retries, interval) { @@ -179,6 +183,16 @@ class AppleSimUtils { return JSON.parse(out); } + async _bootDeviceByXcodeVersion(udid) { + const xcodeVersion = await this.getXcodeVersion(); + if (xcodeVersion >= 9) { + const statusLogs = { trying: `Booting device ${udid}` }; + await this._execSimctl({ cmd: `boot ${udid}`, statusLogs, retries: 10 }); + } else { + await this._bootDeviceMagically(udid); + } + } + async _bootDeviceMagically(udid) { const cmd = "/bin/bash -c '`xcode-select -p`/Applications/Simulator.app/Contents/MacOS/Simulator " + `--args -CurrentDeviceUDID ${udid} -ConnectHardwareKeyboard 0 ` + diff --git a/detox/src/devices/AppleSimUtils.test.js b/detox/src/devices/AppleSimUtils.test.js index 675b315e13..8ec1bdfcf8 100644 --- a/detox/src/devices/AppleSimUtils.test.js +++ b/detox/src/devices/AppleSimUtils.test.js @@ -160,19 +160,45 @@ describe('AppleSimUtils', () => { }); describe('getXcodeVersion', () => { - it('returns xcode version', async () => { + it('returns xcode major version', async () => { exec.execWithRetriesAndLogs.mockReturnValueOnce(Promise.resolve({ stdout: 'Xcode 123.456\nBuild version 123abc123\n' })); const result = await uut.getXcodeVersion(); - expect(result).toEqual('123.456'); + expect(result).toEqual(123); expect(exec.execWithRetriesAndLogs).toHaveBeenCalledTimes(1); expect(exec.execWithRetriesAndLogs).toBeCalledWith(`xcodebuild -version`, undefined, undefined, 1); }); + + it('handles all sorts of results', async () => { + exec.execWithRetriesAndLogs.mockReturnValueOnce(Promise.resolve({ stdout: 'Xcode 999' })); + const result = await uut.getXcodeVersion(); + expect(result).toEqual(999); + }); + + it('throws when cant read version', async () => { + try { + await uut.getXcodeVersion(); + fail(`should throw`); + } catch (e) { + expect(e).toEqual(new Error(`Can't read Xcode version, got: undefined`)); + } + }); + + it('throws when invalid version', async () => { + exec.execWithRetriesAndLogs.mockReturnValueOnce(Promise.resolve({ stdout: 'Xcode bla' })); + try { + await uut.getXcodeVersion(); + fail(`should throw`); + } catch (e) { + expect(e).toEqual(new Error(`Can't read Xcode version, got: Xcode bla`)); + } + }); }); describe('boot', () => { it('waits for device by udid to be Shutdown, boots magically, then waits for state to be Booted', async () => { uut.findDeviceByUDID = jest.fn(() => Promise.resolve({ state: 'unknown' })); uut.waitForDeviceState = jest.fn(() => Promise.resolve(true)); + uut.getXcodeVersion = jest.fn(() => Promise.resolve(1)); expect(exec.execWithRetriesAndLogs).not.toHaveBeenCalled(); await uut.boot('some udid'); expect(exec.execWithRetriesAndLogs).toHaveBeenCalledWith(expect.stringMatching('xcode-select -p'), undefined, expect.anything(), 1); @@ -183,6 +209,7 @@ describe('AppleSimUtils', () => { it('skips if device state was already Booted', async () => { uut.findDeviceByUDID = jest.fn(() => Promise.resolve({ state: 'Booted' })); + uut.getXcodeVersion = jest.fn(() => Promise.resolve(1)); await uut.boot('udid'); expect(uut.findDeviceByUDID).toHaveBeenCalledTimes(1); expect(exec.execWithRetriesAndLogs).not.toHaveBeenCalled(); @@ -190,10 +217,21 @@ describe('AppleSimUtils', () => { it('skips if device state was already Booting', async () => { uut.findDeviceByUDID = jest.fn(() => Promise.resolve({ state: 'Booting' })); + uut.getXcodeVersion = jest.fn(() => Promise.resolve(1)); await uut.boot('udid'); expect(uut.findDeviceByUDID).toHaveBeenCalledTimes(1); expect(exec.execWithRetriesAndLogs).not.toHaveBeenCalled(); }); + + it('boots with xcrun simctl boot when xcode version >= 9', async () => { + uut.findDeviceByUDID = jest.fn(() => Promise.resolve({ state: 'unknown' })); + uut.getXcodeVersion = jest.fn(() => Promise.resolve(9)); + await uut.boot('udid'); + expect(uut.getXcodeVersion).toHaveBeenCalledTimes(1); + expect(exec.execWithRetriesAndLogs).toHaveBeenCalledTimes(1); + expect(exec.execWithRetriesAndLogs).toHaveBeenCalledWith(expect.stringMatching('xcrun simctl boot udid'), undefined, expect.anything(), 10); + + }); }); describe('install', () => {