Skip to content
Closed
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
12 changes: 11 additions & 1 deletion .github/actions/maestro-ios/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ inputs:
required: false
default: '.'
description: The directory from which metro should be started
device-model:
required: false
default: ''
description: Maestro device model name, such as iPhone-17-Pro
device-os:
required: false
default: ''
description: Maestro device OS name, such as iOS-26-2

runs:
using: composite
Expand Down Expand Up @@ -63,7 +71,9 @@ runs:
"${{ inputs.maestro-flow }}" \
"Hermes" \
"${{ inputs.flavor }}" \
"${{ inputs.working-directory }}"
"${{ inputs.working-directory }}" \
"${{ inputs.device-model }}" \
"${{ inputs.device-os }}"
- name: Store video record
if: always()
uses: actions/upload-artifact@v6
Expand Down
29 changes: 29 additions & 0 deletions .github/workflow-scripts/__tests__/maestro-ios-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,33 @@ describe('Maestro iOS runner', () => {
udid: 'new-pro',
});
});

it('selects the configured device model and OS', () => {
childProcess.execSync.mockReturnValue(
JSON.stringify({
devices: {
'com.apple.CoreSimulator.SimRuntime.iOS-26-0': [
{name: 'iPhone 17 Pro', udid: 'wrong-runtime'},
],
'com.apple.CoreSimulator.SimRuntime.iOS-26-2': [
{name: 'iPhone 17 Pro Max', udid: 'wrong-model'},
{name: 'iPhone 17 Pro', udid: 'expected'},
],
},
}),
);

expect(findAvailableSimulator('iPhone-17-Pro', 'iOS-26-2')).toEqual({
name: 'iPhone 17 Pro',
udid: 'expected',
});
});

it('fails when the configured simulator is unavailable', () => {
childProcess.execSync.mockReturnValue(JSON.stringify({devices: {}}));

expect(() => findAvailableSimulator('iPhone-17-Pro', 'iOS-26-2')).toThrow(
'Unable to find iPhone 17 Pro simulator on iOS-26-2',
);
});
});
40 changes: 33 additions & 7 deletions .github/workflow-scripts/maestro-ios.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,48 @@ const fs = require('fs');

const usage = `
=== Usage ===
node maestro-android.js <path to app> <app_id> <maestro_flow> <flavor> <working_directory>
node maestro-ios.js <path to app> <app_id> <maestro_flow> <jsengine> <flavor> <working_directory> [device_model] [device_os]

@param {string} appPath - Path to the app APK
@param {string} appId - App ID that needs to be launched
@param {string} maestroFlow - Path to the maestro flow to be executed
@param {string} jsengine - The JSEngine to use for the test
@param {string} flavor - Flavor of the app to be launched. Can be 'Release' or 'Debug'
@param {string} workingDirectory - Working directory from where to run Metro
@param {string} deviceModel - Optional Maestro device model, such as iPhone-17-Pro
@param {string} deviceOS - Optional Maestro device OS, such as iOS-26-2
==============
`;

const MAX_ATTEMPTS = 5;

function findAvailableSimulator() {
function findAvailableSimulator(deviceModel, deviceOS) {
const output = childProcess.execSync(
'xcrun simctl list devices available -j',
);
const devices = Object.values(JSON.parse(String(output)).devices)
.flat()
.reverse();
const devicesByRuntime = JSON.parse(String(output)).devices;

if ((deviceModel == null) !== (deviceOS == null)) {
throw new Error('Device model and OS must be configured together');
}

if (deviceModel != null && deviceOS != null) {
const runtime = `com.apple.CoreSimulator.SimRuntime.${deviceOS}`;
const simulatorName = deviceModel.replaceAll('-', ' ');
const simulator = devicesByRuntime[runtime]?.find(
device => device.name === simulatorName,
);

if (simulator == null) {
throw new Error(
`Unable to find ${simulatorName} simulator on ${deviceOS}`,
);
}

return simulator;
}

const devices = Object.values(devicesByRuntime).flat().reverse();
const simulator = devices.find(device => /^iPhone .* Pro$/.test(device.name));

if (simulator == null) {
Expand Down Expand Up @@ -153,7 +175,7 @@ function executeFlows(appId, udid, maestroFlow, jsengine) {
}

async function main(args = process.argv.slice(2)) {
if (args.length !== 6) {
if (args.length < 6 || args.length > 8) {
throw new Error(`Invalid number of arguments.\n${usage}`);
}

Expand All @@ -163,6 +185,8 @@ async function main(args = process.argv.slice(2)) {
const jsengine = args[3];
const isDebug = args[4] === 'Debug';
const workingDirectory = args[5];
const deviceModel = args[6] || null;
const deviceOS = args[7] || null;

console.info('\n==============================');
console.info('Running tests for iOS with the following parameters:');
Expand All @@ -172,9 +196,11 @@ async function main(args = process.argv.slice(2)) {
console.info(`JS_ENGINE: ${jsengine}`);
console.info(`IS_DEBUG: ${isDebug}`);
console.info(`WORKING_DIRECTORY: ${workingDirectory}`);
console.info(`DEVICE_MODEL: ${deviceModel ?? '<automatic>'}`);
console.info(`DEVICE_OS: ${deviceOS ?? '<automatic>'}`);
console.info('==============================\n');

const simulator = findAvailableSimulator();
const simulator = findAvailableSimulator(deviceModel, deviceOS);
launchSimulator(simulator);
installAppOnSimulator(appPath);
bringSimulatorInForeground();
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/e2e-ios-rntester.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ jobs:
app-id: com.meta.RNTester.localDevelopment
maestro-flow: ./packages/rn-tester/.maestro/
flavor: ${{ matrix.flavor }}
device-model: iPhone-17-Pro
device-os: iOS-26-2
- name: Report status
id: report-status
if: ${{ always() && steps.run-tests.outcome == 'failure' }}
Expand Down
10 changes: 10 additions & 0 deletions .github/workflows/maestro-cloud-rntester.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ on:
description: 'RNTester application identifier'
required: true
type: string
device-model:
description: 'Device model passed to Maestro Cloud'
required: false
type: string
device-os:
description: 'Device OS passed to Maestro Cloud'
required: false
type: string
exclude-tags:
description: 'Maestro flow tags to exclude'
required: true
Expand Down Expand Up @@ -70,6 +78,8 @@ jobs:
workspace: packages/rn-tester/.maestro
branch: ${{ github.head_ref || github.ref_name }}
name: RNTester ${{ inputs.platform }} - ${{ github.event.pull_request.title || github.sha }}
device-model: ${{ inputs.device-model }}
device-os: ${{ inputs.device-os }}
exclude-tags: ${{ inputs.exclude-tags }}
env: |
APP_ID=${{ inputs.app-id }}
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/test-all.yml
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,8 @@ jobs:
download-path: /tmp/RNTesterBuild/RNTester.app
app-file: /tmp/RNTesterBuild/RNTester.app
app-id: com.meta.RNTester.localDevelopment
device-model: iPhone-17-Pro
device-os: iOS-26-2
exclude-tags: android-only
secrets: inherit

Expand Down
Loading