Skip to content

Commit

Permalink
This is a big commit that kinda sums up 3 different open issues (docu…
Browse files Browse the repository at this point in the history
…mentation will follow in next commits):

closes #181
closes #180
closes #204
  • Loading branch information
rotemmiz committed Jul 18, 2017
1 parent 78770f5 commit cd4d8a1
Show file tree
Hide file tree
Showing 14 changed files with 246 additions and 39 deletions.
9 changes: 3 additions & 6 deletions detox/src/Detox.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class Detox {
}
}

async init() {
async init(params = {launchApp: true}) {
if (!(this.userConfig.configurations && _.size(this.userConfig.configurations) >= 1)) {
throw new Error(`No configured devices`);
}
Expand All @@ -65,13 +65,10 @@ class Detox {
if (!deviceClass) {
throw new Error(`'${deviceConfig.type}' is not supported`);
}
await this._setDevice(sessionConfig, deviceClass, deviceConfig, this.client);
}

async _setDevice(sessionConfig, deviceClass, deviceConfig, client) {
const deviceDriver = new deviceClass(client);
const deviceDriver = new deviceClass(this.client);
this.device = new Device(deviceConfig, sessionConfig, deviceDriver);
await this.device.prepare();
await this.device.prepare(params);
global.device = this.device;
}

Expand Down
63 changes: 48 additions & 15 deletions detox/src/devices/Device.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ const path = require('path');
const _ = require('lodash');
const argparse = require('../utils/argparse');
const configuration = require('../configuration');
const sh = require('../utils/sh');
const log = require('npmlog');
const ArtifactsCopier = require('../artifacts/ArtifactsCopier');

class Device {
Expand All @@ -13,19 +11,27 @@ class Device {
this._deviceConfig = deviceConfig;
this._sessionConfig = sessionConfig;
this.deviceDriver = deviceDriver;
this._processes = {};
this._artifactsCopier = new ArtifactsCopier(deviceDriver);

this.deviceDriver.validateDeviceConfig(deviceConfig);
}

async prepare() {
async prepare(params = {}) {
this._binaryPath = this._getAbsolutePath(this._deviceConfig.binaryPath);
this._deviceId = await this.deviceDriver.acquireFreeDevice(this._deviceConfig.name);
this._bundleId = await this.deviceDriver.getBundleIdFromBinary(this._binaryPath);
this._artifactsCopier.prepare(this._deviceId);

await this.deviceDriver.boot(this._deviceId);
await this.relaunchApp({delete: !argparse.getArgValue('reuse')});

if (!argparse.getArgValue('reuse')) {
await this.deviceDriver.uninstallApp(this._deviceId, this._bundleId);
await this.deviceDriver.installApp(this._deviceId, this._binaryPath);
}

if (params.launchApp) {
await this.launchApp({newInstance: true});
}
}

setArtifactsDestination(testArtifactsPath) {
Expand All @@ -36,8 +42,8 @@ class Device {
await this._artifactsCopier.finalizeArtifacts();
}

async relaunchApp(params = {}, bundleId) {
await this._artifactsCopier.handleAppRelaunch();
async launchApp(params = {newInstance: false}, bundleId) {
await this._artifactsCopier.handleAppRelaunch();

if (params.url && params.userNotification) {
throw new Error(`detox can't understand this 'relaunchApp(${JSON.stringify(params)})' request, either request to launch with url or with userNotification, not both`);
Expand All @@ -46,32 +52,59 @@ class Device {
if (params.delete) {
await this.deviceDriver.uninstallApp(this._deviceId, this._bundleId);
await this.deviceDriver.installApp(this._deviceId, this._binaryPath);
} else {
} else if (params.newInstance) {
await this.deviceDriver.terminate(this._deviceId, this._bundleId);
}

let baseLaunchArgs = {};
if(params.launchArgs) {
baseLaunchArgs = params.launchArgs;
}

let additionalLaunchArgs;
if (params.url) {
additionalLaunchArgs = {'detoxURLOverride': params.url};
baseLaunchArgs['detoxURLOverride'] = params.url;
if(params.sourceApp) {
additionalLaunchArgs['detoxSourceAppOverride'] = params.sourceApp;
baseLaunchArgs['detoxSourceAppOverride'] = params.sourceApp;
}
} else if (params.userNotification) {
additionalLaunchArgs = {'detoxUserNotificationDataURL': this.deviceDriver.createPushNotificationJson(params.userNotification)};
baseLaunchArgs = {'detoxUserNotificationDataURL': this.deviceDriver.createPushNotificationJson(params.userNotification)};
}

if (params.permissions) {
await this.deviceDriver.setPermissions(this._deviceId, this._bundleId, params.permissions);
}

this._addPrefixToDefaultLaunchArgs(additionalLaunchArgs);
this._addPrefixToDefaultLaunchArgs(baseLaunchArgs);

const _bundleId = bundleId || this._bundleId;
await this.deviceDriver.launch(this._deviceId, _bundleId, this._prepareLaunchArgs(additionalLaunchArgs));
const processId = await this.deviceDriver.launch(this._deviceId, _bundleId, this._prepareLaunchArgs(baseLaunchArgs));

if (this._processes[_bundleId] === processId) {
if (params.url) {
await this.openURL(params);
} else if (params.userNotification) {
await this.sendUserNotification(params.userNotification);
}
}

this._processes[_bundleId] = processId;

await this.deviceDriver.waitUntilReady();
}

/**deprecated */
async relaunchApp(params = {newInstance: true}, bundleId) {
await this.launchApp(params, bundleId);
}

async sendToHome() {
await this.deviceDriver.sendToHome(this._deviceId);
}

async terminateApp(bundleId) {
const _bundleId = bundleId || this._bundleId;
await this.deviceDriver.terminate(this._deviceId, _bundleId);
}

async installApp(binaryPath) {
const _binaryPath = binaryPath || this._binaryPath;
Expand All @@ -89,7 +122,7 @@ class Device {

async openURL(params) {
if(typeof params !== 'object' || !params.url) {
throw new Error(`openURL must be called with JSON params, and a value for 'url' key must be provided. example: await device.openURL({url: "url", sourceApp: "sourceAppBundleID"}`);
throw new Error(`openURL must be called with JSON params, and a value for 'url' key must be provided. example: await device.openURL({url: "url", sourceApp[optional]: "sourceAppBundleID"}`);
}

await this.deviceDriver.openURL(this._deviceId, params);
Expand Down
131 changes: 127 additions & 4 deletions detox/src/devices/Device.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ describe('Device', () => {

beforeEach(async () => {
Device = require('./Device');

jest.mock('npmlog');

jest.mock('../utils/sh');
Expand Down Expand Up @@ -103,6 +102,37 @@ describe('Device', () => {
expect(device.deviceDriver.boot).toHaveBeenCalledTimes(1);
});

it(`prepare() should boot a device`, async () => {
device = validDevice();
cpp.exec.mockReturnValue(() => Promise.resolve());
fs.existsSync.mockReturnValue(true);
await device.prepare();

expect(device.deviceDriver.boot).toHaveBeenCalledTimes(1);
});

it(`prepare() with when reuse is enabled should not uninstall and install`, async () => {
device = validDevice();
cpp.exec.mockReturnValue(() => Promise.resolve());
fs.existsSync.mockReturnValue(true);
argparse.getArgValue.mockReturnValue(true);

await device.prepare();

expect(device.deviceDriver.uninstallApp).not.toHaveBeenCalled();
expect(device.deviceDriver.installApp).not.toHaveBeenCalled();
});

it(`launchApp() should launch app with default launch args`, async () => {
device = validDevice();

await device.launchApp();

expect(device.deviceDriver.launch).toHaveBeenCalledWith(device._deviceId,
device._bundleId,
["-detoxServer", "ws://localhost:8099", "-detoxSessionId", "test"]);
});

it(`relaunchApp()`, async () => {
device = validDevice();

Expand Down Expand Up @@ -204,6 +234,30 @@ describe('Device', () => {
device._bundleId, {calendar: "YES"});
});

it(`launchApp({launchArgs: }) should pass to native as launch args`, async () => {
device = validDevice();

await device.launchApp({launchArgs: {arg1: "1", arg2: 2}});

expect(device.deviceDriver.launch).toHaveBeenCalledWith(device._deviceId,
device._bundleId,
["-detoxServer", "ws://localhost:8099", "-detoxSessionId", "test", "-arg1", "1", "-arg2", 2]);
});

it(`sendToHome() should pass to device driver`, async () => {
device = validDevice();
await device.sendToHome();

expect(device.deviceDriver.sendToHome).toHaveBeenCalledTimes(1);
});

it(`terminateApp() should pass to device driver`, async () => {
device = validDevice();
await device.terminateApp();

expect(device.deviceDriver.terminate).toHaveBeenCalledTimes(1);
});

it(`installApp() with a custom app path should use custom app path`, async () => {
device = validDevice();
fs.existsSync.mockReturnValue(true);
Expand All @@ -215,7 +269,6 @@ describe('Device', () => {

it(`installApp() with no params should use the default path given in configuration`, async () => {
device = validDevice();
//fs.existsSync.mockReturnValue(true);

await device.installApp();

Expand Down Expand Up @@ -312,7 +365,7 @@ describe('Device', () => {
expect(device.deviceDriver.disableSynchronization).toHaveBeenCalledTimes(1);
});

it(``, async () => {
it(`new Device() with invalid device config (no binary) should throw`, async () => {
try {
new Device(invalidDeviceNoBinary.configurations['ios.sim.release'], validScheme.session, new SimulatorDriver(client));
fail('should throw');
Expand All @@ -321,7 +374,7 @@ describe('Device', () => {
}
});

it(``, async () => {
it(`new Device() with invalid device config (no device name) should throw`, async () => {
try {
new Device(invalidDeviceNoDeviceName.configurations['ios.sim.release'], validScheme.session, new SimulatorDriver(client));
fail('should throw');
Expand Down Expand Up @@ -354,4 +407,74 @@ describe('Device', () => {
await device.finalizeArtifacts();
expect(sh.cp).toHaveBeenCalledTimes(0);
});

it(`launchApp({url:url}) should check if process is in background and use openURL() instead of launch args`, async () => {
const processId = 1;
device = validDevice();
device.deviceDriver.getBundleIdFromBinary.mockReturnValue('test.bundle');
device.deviceDriver.launch.mockReturnValueOnce(processId).mockReturnValueOnce(processId);

await device.prepare({launchApp: true});
await device.launchApp({url: 'url://me'});

expect(device.deviceDriver.openURL).toHaveBeenCalledTimes(1);
});

it(`launchApp({url:url}) should check if process is in background and if not use launch args`, async () => {
const launchParams = {url: 'url://me'};
const processId = 1;
const newProcessId = 2;

device = validDevice();
device.deviceDriver.getBundleIdFromBinary.mockReturnValue('test.bundle');
device.deviceDriver.launch.mockReturnValueOnce(processId).mockReturnValueOnce(newProcessId);

await device.prepare();
await device.launchApp(launchParams);

expect(device.deviceDriver.openURL).toHaveBeenCalledTimes(0);
});

it(`launchApp({url:url}) should check if process is in background and use openURL() instead of launch args`, async () => {
const launchParams = {url: 'url://me'};
const processId = 1;

device = validDevice();
device.deviceDriver.getBundleIdFromBinary.mockReturnValue('test.bundle');
device.deviceDriver.launch.mockReturnValue(processId);

await device.prepare({launchApp: true});
await device.launchApp(launchParams);

expect(device.deviceDriver.openURL).toHaveBeenCalledTimes(1);
});

it(`launchApp({userNotification:userNotification}) should check if process is in background and if it is use sendUserNotification`, async () => {
const launchParams = {userNotification: 'notification'};
const processId = 1;

device = validDevice();
device.deviceDriver.getBundleIdFromBinary.mockReturnValue('test.bundle');
device.deviceDriver.launch.mockReturnValueOnce(processId).mockReturnValueOnce(processId);

await device.prepare({launchApp: true});
await device.launchApp(launchParams);

expect(device.deviceDriver.sendUserNotification).toHaveBeenCalledWith(launchParams.userNotification);
});

it(`launchApp({userNotification:userNotification}) should check if process is in background and if not use launch args`, async () => {
const launchParams = {userNotification: 'notification'};
const processId = 1;
const newProcessId = 2;

device = validDevice();
device.deviceDriver.getBundleIdFromBinary.mockReturnValue('test.bundle');
device.deviceDriver.launch.mockReturnValueOnce(processId).mockReturnValueOnce(newProcessId);

await device.prepare();
await device.launchApp(launchParams);

expect(device.deviceDriver.sendUserNotification).toHaveBeenCalledTimes(0);
});
});
4 changes: 4 additions & 0 deletions detox/src/devices/DeviceDriverBase.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ class DeviceDriverBase {
return await Promise.resolve('');
}

async sendToHome() {
return await Promise.resolve('');
}

async relaunchApp() {
return await Promise.resolve('');
}
Expand Down
4 changes: 4 additions & 0 deletions detox/src/devices/DeviceDriverBase.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ describe('DeviceDriverBase', () => {
expect(await deviceDriver.terminate()).toBeDefined();
});

it(`sendToHome() - should be defined`, async() => {
expect(await deviceDriver.sendToHome()).toBeDefined();
});

it(`relaunchApp() - should be defined`, async() => {
expect(await deviceDriver.relaunchApp()).toBeDefined();
});
Expand Down
8 changes: 7 additions & 1 deletion detox/src/devices/Fbsimctl.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,11 +118,17 @@ class Fbsimctl {
`SIMCTL_CHILD_DYLD_INSERT_LIBRARIES="${this._getFrameworkPath()}" ` +
`/usr/bin/xcrun simctl launch --stdout=${logsInfo.simStdout} --stderr=${logsInfo.simStderr} ` +
`${udid} ${bundleId} --args ${launchArgs.join(' ')}`;
await exec.execWithRetriesAndLogs(launchBin, undefined, {
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`);
return parseInt(result.stdout.trim().split(':')[1]);
}

getLogsPaths(udid) {
Expand Down
8 changes: 8 additions & 0 deletions detox/src/devices/Fbsimctl.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ describe('Fbsimctl', () => {

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);
});
Expand All @@ -120,6 +121,13 @@ describe('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);
Expand Down
Loading

0 comments on commit cd4d8a1

Please sign in to comment.