Skip to content

Commit e652702

Browse files
committed
feat: ios, android platforms: autoselect devices
1 parent 57b8bf4 commit e652702

4 files changed

Lines changed: 143 additions & 27 deletions

File tree

packages/platform-android/src/config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export const AndroidDeviceSchema = z.discriminatedUnion('type', [
2626

2727
export const AndroidPlatformConfigSchema = z.object({
2828
name: z.string().min(1, 'Name is required'),
29-
device: AndroidDeviceSchema,
29+
device: AndroidDeviceSchema.optional(),
3030
bundleId: z.string().min(1, 'Bundle ID is required'),
3131
activityName: z
3232
.string()

packages/platform-android/src/runner.ts

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,25 +11,57 @@ import {
1111
import { getAdbId } from './adb-id.js';
1212
import * as adb from './adb.js';
1313
import { getDeviceName } from './utils.js';
14+
import { color, logger } from '@react-native-harness/tools';
1415

1516
const getAndroidRunner = async (
1617
config: AndroidPlatformConfig,
1718
harnessConfig: Config
1819
): Promise<HarnessPlatformRunner> => {
1920
const parsedConfig = AndroidPlatformConfigSchema.parse(config);
20-
const adbId = await getAdbId(parsedConfig.device);
2121

22-
if (!adbId) {
23-
throw new DeviceNotFoundError(getDeviceName(parsedConfig.device));
22+
const logTag = color.bgMagentaBright('[AndroidRunner]');
23+
24+
let adbId;
25+
let deviceName = '';
26+
if (parsedConfig.device) {
27+
adbId = await getAdbId(parsedConfig.device);
28+
deviceName = getDeviceName(parsedConfig.device);
29+
30+
if (!adbId) {
31+
throw new DeviceNotFoundError(getDeviceName(parsedConfig.device));
32+
}
33+
} else {
34+
const devicesIds = await adb.getDeviceIds();
35+
if (devicesIds.length === 0) {
36+
throw new DeviceNotFoundError('No android device found');
37+
}
38+
39+
for (const deviceId of devicesIds) {
40+
const deviceInfo = await adb.getDeviceInfo(deviceId);
41+
logger.debug(
42+
`${logTag} Found device id: ${color.bold(deviceId)} ${color.bgBlue(
43+
'[' + deviceInfo?.manufacturer + ' - ' + deviceInfo?.model + ']'
44+
)}`
45+
);
46+
}
47+
adbId = devicesIds[0];
48+
const selectedDeviceInfo = await adb.getDeviceInfo(adbId);
49+
deviceName = `${selectedDeviceInfo?.manufacturer} ${selectedDeviceInfo?.model}`;
50+
logger.info(
51+
`${logTag} Selected device id: ${color.bold(adbId)} ${color.bgBlue(
52+
'[' +
53+
selectedDeviceInfo?.manufacturer +
54+
' - ' +
55+
selectedDeviceInfo?.model +
56+
']'
57+
)}`
58+
);
2459
}
2560

2661
const isInstalled = await adb.isAppInstalled(adbId, parsedConfig.bundleId);
2762

2863
if (!isInstalled) {
29-
throw new AppNotInstalledError(
30-
parsedConfig.bundleId,
31-
getDeviceName(parsedConfig.device)
32-
);
64+
throw new AppNotInstalledError(parsedConfig.bundleId, deviceName);
3365
}
3466

3567
await Promise.all([

packages/platform-ios/src/config.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ import { z } from 'zod';
22

33
export const AppleSimulatorSchema = z.object({
44
type: z.literal('simulator'),
5-
name: z.string().min(1, 'Name is required'),
6-
systemVersion: z.string().min(1, 'System version is required'),
5+
name: z.string().optional(),
6+
systemVersion: z.string().optional(),
77
});
88

99
export const ApplePhysicalDeviceSchema = z.object({
1010
type: z.literal('physical'),
11-
name: z.string().min(1, 'Name is required'),
11+
name: z.string().optional(),
1212
});
1313

1414
export const AppleDeviceSchema = z.discriminatedUnion('type', [

packages/platform-ios/src/instance.ts

Lines changed: 100 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,28 +11,77 @@ import {
1111
import * as simctl from './xcrun/simctl.js';
1212
import * as devicectl from './xcrun/devicectl.js';
1313
import { getDeviceName } from './utils.js';
14+
import { color, logger } from '@react-native-harness/tools';
1415

1516
export const getAppleSimulatorPlatformInstance = async (
1617
config: ApplePlatformConfig
1718
): Promise<HarnessPlatformRunner> => {
1819
assertAppleDeviceSimulator(config.device);
1920

20-
const udid = await simctl.getSimulatorId(
21-
config.device.name,
22-
config.device.systemVersion
23-
);
21+
const logTag = color.bgMagentaBright('[AppleSimulatorPlatform]');
2422

25-
if (!udid) {
26-
throw new DeviceNotFoundError(getDeviceName(config.device));
23+
let udid;
24+
let deviceName = '';
25+
if (config.device.name && config.device.systemVersion) {
26+
deviceName = getDeviceName(config.device);
27+
udid = await simctl.getSimulatorId(
28+
config.device.name,
29+
config.device.systemVersion
30+
);
31+
if (!udid) {
32+
throw new DeviceNotFoundError(getDeviceName(config.device));
33+
}
34+
} else {
35+
let selectedSimulator;
36+
let simulators = await simctl.getSimulators();
37+
// Filter only iOS devices
38+
const prefix = 'com.apple.CoreSimulator.SimRuntime.';
39+
simulators = simulators
40+
.filter((s) =>
41+
s.runtime.startsWith('com.apple.CoreSimulator.SimRuntime.iOS')
42+
)
43+
.map((s) => ({
44+
...s,
45+
runtime: s.runtime.slice(prefix.length),
46+
}));
47+
48+
// Filter only available devices
49+
simulators = simulators.filter((simulator) => simulator.isAvailable);
50+
simulators.forEach((simulator) => {
51+
logger.debug(
52+
`${logTag}: Found simulator id: ${color.bold(simulator.udid)} ${color.bgBlue(
53+
'[' + simulator.name + ' - ' + simulator.runtime + ']'
54+
)} - State: ${simulator.state}`
55+
);
56+
});
57+
if (simulators.length === 0) {
58+
throw new DeviceNotFoundError('any');
59+
} else {
60+
// Prefer booted simulator
61+
selectedSimulator = simulators.find(
62+
(simulator) => simulator.state === 'Booted'
63+
);
64+
if (!selectedSimulator) {
65+
selectedSimulator = simulators[0];
66+
}
67+
68+
udid = selectedSimulator.udid;
69+
deviceName = `${selectedSimulator.name} (${selectedSimulator.runtime}) (simulator)`;
70+
71+
logger.info(
72+
`${logTag}: Selected simulator id: ${color.bold(
73+
selectedSimulator.udid
74+
)} ${color.bgBlue(
75+
'[' + selectedSimulator.name + ' - ' + selectedSimulator.runtime + ']'
76+
)} - State: ${selectedSimulator.state}`
77+
);
78+
}
2779
}
2880

2981
const isInstalled = await simctl.isAppInstalled(udid, config.bundleId);
3082

3183
if (!isInstalled) {
32-
throw new AppNotInstalledError(
33-
config.bundleId,
34-
getDeviceName(config.device)
35-
);
84+
throw new AppNotInstalledError(config.bundleId, deviceName);
3685
}
3786

3887
const simulatorStatus = await simctl.getSimulatorStatus(udid);
@@ -67,24 +116,59 @@ export const getAppleSimulatorPlatformInstance = async (
67116
};
68117
};
69118

119+
const getDeviceString = (device: devicectl.AppleDeviceInfo) => {
120+
return color.bgBlue(
121+
`[${device.deviceProperties.name} - ${device.hardwareProperties.marketingName} - ${device.hardwareProperties.productType} - OS: ${device.deviceProperties.osVersionNumber}]`
122+
);
123+
};
124+
70125
export const getApplePhysicalDevicePlatformInstance = async (
71126
config: ApplePlatformConfig
72127
): Promise<HarnessPlatformRunner> => {
73128
assertAppleDevicePhysical(config.device);
74129

75-
const deviceId = await devicectl.getDeviceId(config.device.name);
130+
const logTag = color.bgMagentaBright('[ApplePhysicalDevicePlatform]');
131+
let deviceId;
132+
let deviceName = '';
133+
134+
if (config.device.name) {
135+
deviceName = getDeviceName(config.device);
136+
deviceId = await devicectl.getDeviceId(config.device.name);
137+
if (!deviceId) {
138+
throw new DeviceNotFoundError(getDeviceName(config.device));
139+
}
140+
} else {
141+
const devicesIds = await devicectl.listDevices();
142+
devicesIds.forEach((device) => {
143+
logger.info(
144+
`${logTag} Found device id: ${color.bold(
145+
device.identifier
146+
)} ${getDeviceString(device)}`
147+
);
148+
});
149+
if (devicesIds.length === 0) {
150+
throw new DeviceNotFoundError('any');
151+
} else {
152+
const selectedDevice = devicesIds[0];
153+
deviceId = selectedDevice.identifier;
154+
deviceName = `${selectedDevice.deviceProperties.name} (${selectedDevice.deviceProperties.osVersionNumber}) (physical)`;
155+
156+
logger.info(
157+
`${logTag} Selected device id: ${color.bold(
158+
selectedDevice.identifier
159+
)} ${getDeviceString(selectedDevice)}`
160+
);
161+
}
162+
}
76163

77164
if (!deviceId) {
78-
throw new DeviceNotFoundError(getDeviceName(config.device));
165+
throw new DeviceNotFoundError(deviceName);
79166
}
80167

81168
const isAvailable = await devicectl.isAppInstalled(deviceId, config.bundleId);
82169

83170
if (!isAvailable) {
84-
throw new AppNotInstalledError(
85-
config.bundleId,
86-
getDeviceName(config.device)
87-
);
171+
throw new AppNotInstalledError(config.bundleId, deviceName);
88172
}
89173

90174
return {

0 commit comments

Comments
 (0)