Skip to content

Commit

Permalink
Add 'otherApps' capability (#349)
Browse files Browse the repository at this point in the history
* Augmented initAUT to install apk's if a JSON array or string was provided in capability called 'otherApps'
* Goes through the list and downloads (if URL) the apps asynchronously and then installs all of them asynchronously using ADB install
  • Loading branch information
dpgraham committed Apr 2, 2018
1 parent 9a882f8 commit 0571cb5
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 2 deletions.
45 changes: 45 additions & 0 deletions lib/android-helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { path as unicodeIMEPath } from 'appium-android-ime';
import { path as settingsApkPath } from 'io.appium.settings';
import { path as unlockApkPath } from 'appium-unlock';
import Bootstrap from 'appium-android-bootstrap';
import B from 'bluebird';

import ADB from 'appium-adb';
import { default as unlocker, PIN_UNLOCK, PASSWORD_UNLOCK, PATTERN_UNLOCK, FINGERPRINT_UNLOCK } from './unlock-helpers';

Expand Down Expand Up @@ -301,6 +303,28 @@ helpers.installApk = async function (adb, opts = {}) {
}
};

/**
* Installs an array of apks
* @param {ADB} adb Instance of Appium ADB object
* @param {Object} opts Opts defined in driver.js
*/
helpers.installOtherApks = async function (otherApps, adb, opts) {
let {
androidInstallTimeout = PACKAGE_INSTALL_TIMEOUT,
autoGrantPermissions
} = opts;

// Install all of the APK's asynchronously
await B.all(otherApps.map((otherApp) => {
logger.debug(`Installing app: ${otherApp}`);
return adb.install(otherApp, {
grantPermissions: autoGrantPermissions,
timeout: androidInstallTimeout,
replace: true,
});
}));
};

helpers.initUnicodeKeyboard = async function (adb) {
logger.debug('Enabling Unicode keyboard support');
logger.debug("Pushing unicode ime to device...");
Expand Down Expand Up @@ -562,6 +586,27 @@ helpers.removeAllSessionWebSocketHandlers = async function (server, sessionId) {
}
};

/**
* Takes a desired capability and tries to JSON.parse it as an array,
* and either returns the parsed array or a singleton array.
*
* @param {any} cap A desired capability
*/
helpers.parseArray = function (cap) {
let parsedCaps;
try {
parsedCaps = JSON.parse(cap);
} catch (ign) { }

if (_.isArray(parsedCaps)) {
return parsedCaps;
} else if (_.isString(cap)) {
return [cap];
}

throw new Error(`must provide a string or JSON Array; received ${cap}`);
};

helpers.bootstrap = Bootstrap;
helpers.unlocker = unlocker;

Expand Down
3 changes: 3 additions & 0 deletions lib/desired-caps.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,9 @@ let commonCapConstraints = {
unlockKey: {
isString: true
},
otherApps: {
isString: true
},
};

let uiautomatorCapConstraints = {
Expand Down
16 changes: 15 additions & 1 deletion lib/driver.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { DEFAULT_ADB_PORT } from 'appium-adb';
import { fs, tempDir, util } from 'appium-support';
import { retryInterval } from 'asyncbox';
import { SharedPrefsBuilder } from 'shared-preferences-builder';
import B from 'bluebird';

const APP_EXTENSION = '.apk';
const DEVICE_PORT = 4724;
Expand Down Expand Up @@ -81,7 +82,7 @@ class AndroidDriver extends BaseDriver {
fullReset: false,
autoLaunch: true,
adbPort: DEFAULT_ADB_PORT,
androidInstallTimeout: 90000
androidInstallTimeout: 90000,
};
_.defaults(this.opts, defaultOpts);
if (!this.opts.javaVersion) {
Expand Down Expand Up @@ -294,6 +295,19 @@ class AndroidDriver extends BaseDriver {
let launchInfo = await helpers.getLaunchInfo(this.adb, this.opts);
Object.assign(this.opts, launchInfo);
Object.assign(this.caps, launchInfo);

// Install any "otherApps" that were specified in caps
if (this.opts.otherApps) {
let otherApps;
try {
otherApps = helpers.parseArray(this.opts.otherApps);
} catch (e) {
log.errorAndThrow(`Could not parse "otherApps" capability: ${e.message}`);
}
otherApps = await B.all(otherApps.map((app) => this.helpers.configureApp(app, APP_EXTENSION)));
await helpers.installOtherApks(otherApps, this.adb, this.opts);
}

// install app
if (!this.opts.app) {
if (this.opts.fullReset) {
Expand Down
43 changes: 42 additions & 1 deletion test/unit/android-helper-specs.js
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@ describe('Android Helpers', function () {
});
}));

describe('installApk', withMocks({adb, fs, helpers}, (mocks) => {
describe('installApk', withMocks({adb, fs, helpers}, function (mocks) {
//use mock appium capabilities for this test
const opts = {
app : 'local',
Expand Down Expand Up @@ -392,6 +392,38 @@ describe('Android Helpers', function () {
mocks.helpers.verify();
});
}));
describe('installOtherApks', withMocks({adb, fs, helpers}, function (mocks) {
const opts = {
app: 'local',
appPackage: 'pkg',
androidInstallTimeout: 90000
};

const fakeApk = '/path/to/fake/app.apk';
const otherFakeApk = '/path/to/other/fake/app.apk';

const expectedADBInstallOpts = {
grantPermissions: undefined,
timeout: opts.androidInstallTimeout,
replace: true,
};

it('should not call adb.install if otherApps is empty', async function () {
mocks.adb.expects('install').never();
await helpers.installOtherApks([], adb, opts);
mocks.adb.verify();
});
it('should call adb.install once if otherApps has one item', async function () {
mocks.adb.expects('install').once().withArgs(fakeApk, expectedADBInstallOpts);
await helpers.installOtherApks([fakeApk], adb, opts);
mocks.adb.verify();
});
it('should call adb.install twice if otherApps has two item', async function () {
mocks.adb.expects('install').twice();
await helpers.installOtherApks([fakeApk, otherFakeApk], adb, opts);
mocks.adb.verify();
});
}));
describe('initUnicodeKeyboard', withMocks({adb}, (mocks) => {
it('should install and enable unicodeIME', async function () {
mocks.adb.expects('install').once().returns('');
Expand Down Expand Up @@ -655,4 +687,13 @@ describe('Android Helpers', function () {
{pkg: 'org.chromium.webview_shell', activity: 'org.chromium.webview_shell.WebViewBrowserActivity'});
});
});

describe('#parseArray', function () {
it('should parse array string to array', function () {
helpers.parseArray('["a", "b", "c"]').should.eql(['a', 'b', 'c']);
});
it('should parse a simple string to one item array', function () {
helpers.parseArray('abc').should.eql(['abc']);
});
});
});
13 changes: 13 additions & 0 deletions test/unit/driver-specs.js
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,19 @@ describe('driver', function () {
await driver.initAUT();
mocks.helpers.verify();
});
it('should install "otherApps" if set in capabilities', async function () {
const otherApps = ["http://URL_FOR/fake/app.apk"];
const tempApps = ["/path/to/fake/app.apk"];
driver.opts = {appPackage: "app.package", appActivity: "act", fullReset: false, fastReset: false,
otherApps: `["${otherApps[0]}"]`
};
sandbox.stub(driver.helpers, 'configureApp')
.withArgs(otherApps[0], '.apk')
.returns(tempApps[0]);
mocks.helpers.expects("installOtherApks").once().withArgs(tempApps, driver.adb, driver.opts);
await driver.initAUT();
mocks.helpers.verify();
});
}));
describe('startAndroidSession', function () {
beforeEach(async function () {
Expand Down

0 comments on commit 0571cb5

Please sign in to comment.