Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

iOS Device Management #1269

Merged
merged 11 commits into from
Dec 7, 2023
9 changes: 9 additions & 0 deletions __mocks__/appium-ios-device.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// __mocks__/appium-ios-device.ts
const aid: any = {
utilities: {
getConnectedDevices: jest.fn(() => Promise.resolve(['1234'])),
getDeviceInfo: jest.fn(() => Promise.resolve({ name: 'iPhone 14 Pro Max', isAvailable: true })),
},
};

module.exports = aid;
3 changes: 2 additions & 1 deletion packages/sdk-apple/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
},
"dependencies": {
"@rnv/sdk-react-native": "1.0.0-rc.1",
"appium-ios-device": "2.7.10",
"compare-versions": "3.6.0",
"ios-mobileprovision-finder": "1.1.0",
"xcode": "2.1.0"
Expand All @@ -45,4 +46,4 @@
"access": "public"
},
"gitHead": "48ef244c6ec2e206cbfd72fe8770d8dc03387591"
}
}
23 changes: 0 additions & 23 deletions packages/sdk-apple/src/__mocks__/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,26 +127,3 @@ export const simctlSimJson = {
],
}
}

export const xctraceDevices = `
== Devices ==
Mihai’s Jacket (741EF5F2-EC1A-352D-457F-2B8E5EA6C10A)

== Simulators ==
Apple TV Simulator (16.0) (9B1DE6B2-F79C-42FE-9924-B7B398B33419)
Apple TV Simulator (16.1) (741EF5F2-ACA2-440D-ABB4-99B10C0FFFAC)
Apple TV 4K (2nd generation) Simulator (16.0) (80DCD18A-92AB-4D04-BC23-670FA763091D)
Apple TV 4K (3rd generation) Simulator (16.1) (B6E2EF0C-EDBF-4FA8-8954-CFBEE7120E17)
Apple TV 4K (3rd generation) (at 1080p) Simulator (16.1) (501C0BBC-26B6-4642-B3FE-43B128FF6F4A)
Apple TV 4K (at 1080p) (2nd generation) Simulator (16.0) (4D5B6984-4B61-47F6-BFE2-4840E092A57B)
iPad (10th generation) Simulator (16.2) (ADE58171-412D-40B2-A02F-C8D9F4486776)
iPad Air (5th generation) Simulator (16.2) (E6B35619-9082-4F5C-AADE-F3D441A4182B)
iPad Pro (11-inch) (4th generation) Simulator (16.2) (CFEAB0D2-0E45-4287-ADB1-DEDBF690C13D)
iPad Pro (12.9-inch) (6th generation) Simulator (16.2) (6E1F3D1F-6B37-4415-B487-5C15E82292C7)
iPad mini (6th generation) Simulator (16.2) (88761714-71CC-4439-BED3-C4A9933A3C62)
iPhone 14 Simulator (16.2) (0BEDB188-352D-4215-8471-E9E27C670486)
iPhone 14 Plus Simulator (16.2) (F48330F4-FAF4-4DDE-B011-1B865B80403D)
iPhone 14 Pro Simulator (16.2) (5E37904F-F486-457F-BC42-6DA72F5435B6)
iPhone 14 Pro Max Simulator (16.2) (6AE9AC96-1150-4EF6-996B-BEBD0914C6A5)
iPhone SE (3rd generation) Simulator (16.2) (A3CE2617-4071-4759-BC87-2F687FEA50A7)
`
20 changes: 7 additions & 13 deletions packages/sdk-apple/src/__tests__/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createRnvApi, createRnvContext } from '@rnv/core';
import type { PromptParams } from "@rnv/core";
import { getIosDeviceToRunOn } from '../runner';
import { simctlSimJson, xctraceDevices } from '../__mocks__/data';
import { simctlSimJson } from '../__mocks__/data';

beforeEach(() => {
createRnvContext({ program: { platform: 'ios' } });
Expand All @@ -21,9 +21,7 @@ describe('getIosDeviceToRunOn', () => {
// configureRuntimeDefaults isn't called so setting it manually
ctx.runtime.isTargetTrue = true;

executeAsync
.mockReturnValueOnce(Promise.resolve(xctraceDevices))
.mockReturnValueOnce(Promise.resolve(JSON.stringify(simctlSimJson)));
executeAsync.mockReturnValueOnce(Promise.resolve(JSON.stringify(simctlSimJson)));

inquirerPrompt.mockImplementation(async ({ type, name, choices }: PromptParams) => {
if (type === 'confirm') {
Expand All @@ -40,16 +38,14 @@ describe('getIosDeviceToRunOn', () => {
});

const deviceArgs = await getIosDeviceToRunOn(ctx);
expect(executeAsync).toHaveBeenCalledTimes(2);
expect(executeAsync).toHaveBeenCalledTimes(1);
expect(deviceArgs).toBe('--simulator iPhone\\ 14');
});

it('should return a device to run on without pick', async () => {
const ctx = getContext();

executeAsync
.mockReturnValueOnce(Promise.resolve(xctraceDevices))
.mockReturnValueOnce(Promise.resolve(JSON.stringify(simctlSimJson)));
executeAsync.mockReturnValueOnce(Promise.resolve(JSON.stringify(simctlSimJson)));

inquirerPrompt.mockImplementation(async ({ type, name, choices }: PromptParams) => {
if (type === 'confirm') {
Expand All @@ -66,7 +62,7 @@ describe('getIosDeviceToRunOn', () => {
});

const deviceArgs = await getIosDeviceToRunOn(ctx);
expect(executeAsync).toHaveBeenCalledTimes(2);
expect(executeAsync).toHaveBeenCalledTimes(1);
expect(deviceArgs).toBe('--simulator iPhone\\ SE\\ (3rd\\ generation)');
});

Expand All @@ -76,12 +72,10 @@ describe('getIosDeviceToRunOn', () => {
// configureRuntimeDefaults isn't called so setting it manually
ctx.runtime.target = 'iPhone 14 Pro Max';

executeAsync
.mockReturnValueOnce(Promise.resolve(xctraceDevices))
.mockReturnValueOnce(Promise.resolve(JSON.stringify(simctlSimJson)));
executeAsync.mockReturnValueOnce(Promise.resolve(JSON.stringify(simctlSimJson)));

const deviceArgs = await getIosDeviceToRunOn(ctx);
expect(executeAsync).toHaveBeenCalledTimes(2);
expect(executeAsync).toHaveBeenCalledTimes(1);
expect(deviceArgs).toContain('--simulator');
// expect(deviceArgs).toBe('--simulator iPhone\\ 14\\ Plus'); // FIXME: This is failing
});
Expand Down
91 changes: 37 additions & 54 deletions packages/sdk-apple/src/deviceManager.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import child_process from 'child_process';
import { utilities } from 'appium-ios-device';
import {
chalk,
logToSummary,
Expand All @@ -12,27 +13,27 @@ import {
inquirerPrompt,
RnvPlatform,
} from '@rnv/core';
import { AppleDevice } from './types';
import { AppiumAppleDevice, AppleDevice } from './types';

export const getAppleDevices = async (c: RnvContext, ignoreDevices?: boolean, ignoreSimulators?: boolean) => {
const { platform } = c;

logTask('getAppleDevices', `ignoreDevices:${ignoreDevices} ignoreSimulators${ignoreSimulators}`);
logTask('getAppleDevices', `ignoreDevices:${ignoreDevices} ignoreSimulators:${ignoreSimulators}`);

const {
program: { skipTargetCheck },
} = c;

let devicesAndSims;
let isXcode13 = false;
try {
// xcode >= 13
isXcode13 = true;
devicesAndSims = await executeAsync('xcrun xctrace list devices');
} catch {
// xcode < 13
devicesAndSims = await executeAsync('xcrun instruments -s');
}
const connectedDevicesIds = await utilities.getConnectedDevices();
const connectedDevicesArray = await Promise.all(
connectedDevicesIds.map(async (id: string) => {
const info = await utilities.getDeviceInfo(id);
return {
udid: id,
...info,
};
})
);
const res = await executeAsync('xcrun simctl list --json');
const simctl = JSON.parse(res.toString());
const availableSims: Array<AppleDevice> = [];
Expand All @@ -50,11 +51,7 @@ export const getAppleDevices = async (c: RnvContext, ignoreDevices?: boolean, ig
});
}

let parseFunction = _parseIOSDevicesList;
if (isXcode13) {
parseFunction = _parseNewIOSDevicesList;
}
const devicesArr = parseFunction(devicesAndSims, platform, ignoreDevices, ignoreSimulators);
const devicesArr = _parseNewIOSDevicesList(connectedDevicesArray, platform, ignoreDevices);

const simulatorsArr = _parseIOSDevicesList(availableSims, platform, ignoreDevices, ignoreSimulators);
let allDevices = [...devicesArr, ...simulatorsArr];
Expand All @@ -75,51 +72,37 @@ export const getAppleDevices = async (c: RnvContext, ignoreDevices?: boolean, ig
};

const _parseNewIOSDevicesList = (
rawDevices: string | Array<AppleDevice>,
rawDevices: Array<AppiumAppleDevice>,
platform: RnvPlatform,
ignoreDevices = false
) => {
const devices: Array<AppleDevice> = [];
if (ignoreDevices) return devices;
const decideIcon = (device: AppleDevice) => {
const { name, isDevice } = device;
switch (platform) {
case IOS:
if (name?.includes('iPhone') || name?.includes('iPad') || name?.includes('iPod')) {
let icon = 'Phone 📱';
if (name.includes('iPad')) icon = 'Tablet 💊';
return icon;
}
return undefined;
case TVOS:
if (name?.includes('TV') && !name?.includes('iPhone') && !name?.includes('iPad')) {
return 'TV 📺';
}
return undefined;
default:
if (isDevice) {
return 'Apple Device';
}
return undefined;
const decideIcon = (device: AppiumAppleDevice) => {
const { ProductName, DeviceClass } = device;
if (ProductName?.includes('iPhone') || ProductName?.includes('iPad') || ProductName?.includes('iPod')) {
let icon = 'Phone 📱';
if (DeviceClass?.includes('iPad')) icon = 'Tablet 💊';
return icon;
}
if (ProductName?.includes('TV') && !ProductName?.includes('iPhone') && !ProductName?.includes('iPad')) {
return 'TV 📺';
}
return 'Apple Device';
};
if (typeof rawDevices === 'string') {
const lines = rawDevices.split('\n');
const devicesOnly: Array<string> = lines.slice(1, lines.indexOf(''));
devicesOnly.forEach((device) => {
const udid = device.match(/\(([^()]*)\)$/)?.[1];
const name = device.split(' (').slice(0, -1).join(' (');
const icon = decideIcon({ name, isDevice: true });
devices.push({
udid,
name,
icon,
isDevice: true,
});
});
}

return devices;
return rawDevices.map((device) => {
const { DeviceName, ProductVersion, udid } = device;
const version = ProductVersion;
const icon = decideIcon(device);
return {
udid,
name: DeviceName,
icon,
version,
isDevice: true,
};
});
};

const _parseIOSDevicesList = (
Expand Down
2 changes: 1 addition & 1 deletion packages/sdk-apple/src/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ const runCocoaPods = async (c: Context) => {
requiredPodPermissions = Array.from(new Set(requiredPodPermissions));
}

// new arch support
// new arch support
const newArchEnabled = getConfigProp(c, c.platform, 'newArchEnabled', false);

const env: any = {
Expand Down
13 changes: 13 additions & 0 deletions packages/sdk-apple/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,19 @@ export type AppleDevice = {
isDevice?: boolean;
};

export type AppiumAppleDevice = {
udid: string;
CPUArchitecture: string;
DeviceName: string;
HardwareModel: string;
HumanReadableProductVersionString: string;
ProductName: string;
ProductType: string;
ProductVersion: string;
SupportedDeviceFamilies: number[];
DeviceClass?: string;
};

export type Payload = {
pluginConfigiOS: {
exportOptions: string;
Expand Down
1 change: 1 addition & 0 deletions packages/sdk-apple/types.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
declare module 'appium-ios-device'