Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ matrix:
node_js: "8"
- osx_image: xcode10
node_js: "10"
- osx_image: xcode10.1
node_js: "8"
- osx_image: xcode10.1
node_js: "10"
script:
- _FORCE_LOGS=1 npm run test && npm run e2e-test
after_success:
Expand Down
32 changes: 19 additions & 13 deletions lib/simctl.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ import { retryInterval, waitForCondition } from 'asyncbox';
import { logger, fs, tempDir } from 'appium-support';
import _ from 'lodash';

const log = logger.getLogger('simctl');

const IOS_PLATFORM = 'iOS';
const log = logger.getLogger('simctl');

/**
* Execute the particular simctl command and return the output.
Expand Down Expand Up @@ -425,14 +424,14 @@ async function eraseDevice (udid, timeout = 1000) {
* Parse the list of existing Simulator devices to represent
* it as convenient mapping.
*
* @param {?string} platform [iOS] - The platform name, for example 'watchOS'.
* @param {?string} platform - The platform name, for example 'watchOS'.
* @return {Object} The resulting mapping. Each key is platform version,
* for example '10.3' and the corresponding value is an
* array of the matching {@link DeviceInfo} instances.
* @throws {Error} If the corresponding simctl subcommand command
* returns non-zero return code.
*/
async function getDevicesByParsing (platform = IOS_PLATFORM) {
async function getDevicesByParsing (platform) {
// get the list of devices
const {stdout} = await simExec('list', 0, ['devices']);

Expand All @@ -445,7 +444,9 @@ async function getDevicesByParsing (platform = IOS_PLATFORM) {
// ...
// so, get the `-- iOS X.X --` line to find the sdk (X.X)
// and the rest of the listing in order to later find the devices
const deviceSectionRe = new RegExp(`\\-\\-\\s+${_.escapeRegExp(platform)}\\s+(\\S+)\\s+\\-\\-(\\n\\s{4}.+)*`, 'mgi');
const deviceSectionRe = _.isEmpty(platform)
? new RegExp(`\\-\\-\\s+\\S+\\s+(\\S+)\\s+\\-\\-(\\n\\s{4}.+)*`, 'mgi')
: new RegExp(`\\-\\-\\s+${_.escapeRegExp(platform)}\\s+(\\S+)\\s+\\-\\-(\\n\\s{4}.+)*`, 'mgi');
const matches = [];
let match;
// make an entry for each sdk version
Expand All @@ -461,7 +462,7 @@ async function getDevicesByParsing (platform = IOS_PLATFORM) {
const devices = {};
for (match of matches) {
const sdk = match[1];
devices[sdk] = [];
devices[sdk] = devices[sdk] || [];
// split the full match into lines and remove the first
for (const line of match[0].split('\n').slice(1)) {
// a line is something like
Expand Down Expand Up @@ -493,7 +494,7 @@ async function getDevicesByParsing (platform = IOS_PLATFORM) {
* @param {?string} forSdk - The sdk version,
* for which the devices list should be parsed,
* for example '10.3'.
* @param {?string} platform [iOS] - The platform name, for example 'watchOS'.
* @param {?string} platform - The platform name, for example 'watchOS'.
* @return {Object|Array<DeviceInfo>} If _forSdk_ is set then the list
* of devices for the particular platform version.
* Otherwise the same result as for {@link getDevicesByParsing}
Expand All @@ -502,7 +503,7 @@ async function getDevicesByParsing (platform = IOS_PLATFORM) {
* returns non-zero return code or if no matching
* platform version is found in the system.
*/
async function getDevices (forSdk = null, platform = IOS_PLATFORM) {
async function getDevices (forSdk = null, platform) {
let devices = {};
try {
const {stdout} = await simExec('list', 0, ['devices', '-j']);
Expand All @@ -520,19 +521,23 @@ async function getDevices (forSdk = null, platform = IOS_PLATFORM) {
* }
* }
*/
const versionMatchRe = new RegExp(`^${_.escapeRegExp(platform)}\\s+(\\S+)`, 'i');
const versionMatchRe = _.isEmpty(platform)
? new RegExp(`^\\S+\\s+(\\S+)`, 'i')
: new RegExp(`^${_.escapeRegExp(platform)}\\s+(\\S+)`, 'i');
for (const [sdkName, entries] of _.toPairs(JSON.parse(stdout).devices)) {
const versionMatch = versionMatchRe.exec(sdkName);
if (!versionMatch) {
continue;
}
devices[versionMatch[1]] = entries.map((el) => {
const sdk = versionMatch[1];
devices[sdk] = devices[sdk] || [];
devices[sdk].push(...entries.map((el) => {
delete el.availability;
return {
sdk,
...el,
sdk: versionMatch[1]
};
});
}));
}
} catch (err) {
log.debug(`Unable to get JSON device list: ${err.stack}`);
Expand All @@ -556,12 +561,13 @@ async function getDevices (forSdk = null, platform = IOS_PLATFORM) {
throw new Error(errMsg);
}


/**
* Get the runtime for the particular platform version.
*
* @param {string} platformVersion - The platform version name,
* for example '10.3'.
* @param {?string} platform [iOS] - The platform name, for example 'watchOS'.
* @param {?string} platform - The platform name, for example 'watchOS'.
* @return {string} The corresponding runtime name for the given
* platform version.
*/
Expand Down
100 changes: 59 additions & 41 deletions test/simctl-e2e-specs.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,17 @@ describe('simctl', function () {
this.timeout(MOCHA_TIMEOUT);

let randName;
let randDeviceUdid = null;
let validSdks = [];
let sdk;

before(async function () {
let devices = await getDevices();
const devices = await getDevices();
validSdks = _.keys(devices).sort((a, b) => a - b);
if (!validSdks.length) {
throw new Error('No valid SDKs');
}
console.log(`Found valid SDKs: ${validSdks.join(', ')}`); // eslint-disable-line no-console
sdk = _.last(validSdks);

// need to find a random name that does not already exist
// give it 5 tries
Expand All @@ -50,26 +51,67 @@ describe('simctl', function () {
}
});

it('should create a device', async function () {
let udid = await createDevice(randName, DEVICE_NAME, _.last(validSdks));
(typeof udid).should.equal('string');
udid.length.should.equal(36);
it('should retrieve a device with compatible properties', async function () {
const devices = (await getDevices())[sdk];
const firstDevice = devices[0];
const expectedList = ['name', 'sdk', 'state', 'udid'];
firstDevice.should.have.any.keys(...expectedList);
});

it('should get devices', async function () {
let sdkDevices = await getDevices(_.last(validSdks));
_.map(sdkDevices, 'name').should.include(randName);
randDeviceUdid = sdkDevices.filter((d) => d.name === randName)[0].udid;
});
describe('createDevice', function () {
let udid;
after(async function () {
if (udid) {
await deleteDevice(udid, 16000);
}
});

it('should create a device', async function () {
udid = await createDevice(randName, DEVICE_NAME, sdk);
(typeof udid).should.equal('string');
udid.length.should.equal(36);
});

it('should erase devices', async function () {
await eraseDevice(randDeviceUdid, 16000);
it('should create a device and be able to see it in devices list right away', async function () {
const numSimsBefore = (await getDevices())[sdk].length;
udid = await createDevice('node-simctl test', DEVICE_NAME, sdk);
const numSimsAfter = (await getDevices())[sdk].length;
numSimsAfter.should.equal(numSimsBefore + 1);
});
});

it('should delete devices', async function () {
await deleteDevice(randDeviceUdid);
let sdkDevices = await getDevices(_.last(validSdks));
_.map(sdkDevices, 'name').should.not.include(randName);
describe('device manipulation', function () {
let udid;
const name = 'node-simctl test';
beforeEach(async function () {
udid = await createDevice('node-simctl test', DEVICE_NAME, sdk);
});
afterEach(async function () {
if (udid) {
await deleteDevice(udid, 16000);
}
});
it('should get devices', async function () {
const sdkDevices = await getDevices(sdk);
_.map(sdkDevices, 'name').should.include(name);
});

it('should erase devices', async function () {
await eraseDevice(udid, 16000);
});

it('should delete devices', async function () {
await deleteDevice(udid);
const sdkDevices = await getDevices(sdk);
_.map(sdkDevices, 'name').should.not.include(udid);

// so we do not delete again
udid = null;
});

it('should not fail to shutdown a shutdown simulator', async function () {
await shutdown(udid).should.eventually.not.be.rejected;
});
});

it('should return a nice error for invalid usage', async function () {
Expand All @@ -83,30 +125,6 @@ describe('simctl', function () {
err.message.should.include('Invalid device type: bar');
});

it('should create a device and be able to see it in devices list right away', async function () {
let sdk = _.last(validSdks);
let numSimsBefore = (await getDevices())[sdk].length;
let udid = await createDevice('node-simctl test', DEVICE_NAME, sdk);
let numSimsAfter = (await getDevices())[sdk].length;
numSimsAfter.should.equal(numSimsBefore + 1);
await deleteDevice(udid);
});

it('should create a device with compatible properties', async function () {
let sdk = _.last(validSdks);
let devices = (await getDevices())[sdk];
let firstDevice = devices[0];
let expectedList = ['name', 'sdk', 'state', 'udid'];
Object.keys(firstDevice).sort().should.eql(expectedList);
});

it('should not fail to shutdown a shutdown simulator', async function () {
let sdk = _.last(validSdks);
let udid = await createDevice('node-simctl test', DEVICE_NAME, sdk);
await shutdown(udid).should.eventually.not.be.rejected;
await deleteDevice(udid);
});

describe('on running Simulator', function () {
if (process.env.TRAVIS) {
this.retries(3);
Expand Down