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
41 changes: 41 additions & 0 deletions .github/workflow-scripts/__tests__/maestro-ios-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/

jest.mock('child_process', () => ({
execSync: jest.fn(),
spawn: jest.fn(),
}));
const childProcess = require('child_process');

const {findAvailableSimulator} = require('../maestro-ios');

describe('Maestro iOS runner', () => {
beforeEach(() => {
jest.clearAllMocks();
});

it('selects an iPhone Pro simulator from the latest runtime', () => {
childProcess.execSync.mockReturnValue(
JSON.stringify({
devices: {
'iOS 18.5': [{name: 'iPhone 16 Pro', udid: 'old-pro'}],
'iOS 26.5': [
{name: 'iPhone 17 Pro Max', udid: 'new-pro-max'},
{name: 'iPhone 17 Pro', udid: 'new-pro'},
],
},
}),
);

expect(findAvailableSimulator()).toEqual({
name: 'iPhone 17 Pro',
udid: 'new-pro',
});
});
});
83 changes: 47 additions & 36 deletions .github/workflow-scripts/maestro-ios.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,25 +23,28 @@ node maestro-android.js <path to app> <app_id> <maestro_flow> <flavor> <working_
==============
`;

const args = process.argv.slice(2);
const MAX_ATTEMPTS = 5;

if (args.length !== 6) {
throw new Error(`Invalid number of arguments.\n${usage}`);
}
function findAvailableSimulator() {
const output = childProcess.execSync(
'xcrun simctl list devices available -j',
);
const devices = Object.values(JSON.parse(String(output)).devices)
.flat()
.reverse();
const simulator = devices.find(device => /^iPhone .* Pro$/.test(device.name));

const APP_PATH = args[0];
const APP_ID = args[1];
const MAESTRO_FLOW = args[2];
const JS_ENGINE = args[3];
const IS_DEBUG = args[4] === 'Debug';
const WORKING_DIRECTORY = args[5];
if (simulator == null) {
throw new Error('Unable to find an available iPhone Pro simulator');
}

const MAX_ATTEMPTS = 5;
return simulator;
}

function launchSimulator(simulatorName) {
console.log(`Launching simulator ${simulatorName}`);
function launchSimulator(simulator) {
console.log(`Launching simulator ${simulator.name} (${simulator.udid})`);
try {
childProcess.execSync(`xcrun simctl boot "${simulatorName}"`);
childProcess.execSync(`xcrun simctl boot "${simulator.udid}"`);
} catch (error) {
if (
!error.message.includes('Unable to boot device in current state: Booted')
Expand All @@ -56,14 +59,6 @@ function installAppOnSimulator(appPath) {
childProcess.execSync(`xcrun simctl install booted "${appPath}"`);
}

function extractSimulatorUDID() {
console.log('Retrieving device UDID');
const command = `xcrun simctl list devices booted -j | jq -r '[.devices[]] | add | first | .udid'`;
const udid = String(childProcess.execSync(command)).trim();
console.log(`UDID is ${udid}`);
return udid;
}

function bringSimulatorInForeground() {
console.log('Bringing simulator in foreground');
childProcess.execSync('open -a simulator');
Expand Down Expand Up @@ -151,26 +146,42 @@ function executeTestsWithRetries(
}
}

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

const appPath = args[0];
const appId = args[1];
const maestroFlow = args[2];
const jsengine = args[3];
const isDebug = args[4] === 'Debug';
const workingDirectory = args[5];

console.info('\n==============================');
console.info('Running tests for iOS with the following parameters:');
console.info(`APP_PATH: ${APP_PATH}`);
console.info(`APP_ID: ${APP_ID}`);
console.info(`MAESTRO_FLOW: ${MAESTRO_FLOW}`);
console.info(`JS_ENGINE: ${JS_ENGINE}`);
console.info(`IS_DEBUG: ${IS_DEBUG}`);
console.info(`WORKING_DIRECTORY: ${WORKING_DIRECTORY}`);
console.info(`APP_PATH: ${appPath}`);
console.info(`APP_ID: ${appId}`);
console.info(`MAESTRO_FLOW: ${maestroFlow}`);
console.info(`JS_ENGINE: ${jsengine}`);
console.info(`IS_DEBUG: ${isDebug}`);
console.info(`WORKING_DIRECTORY: ${workingDirectory}`);
console.info('==============================\n');

const simulatorName = 'iPhone 16 Pro';
launchSimulator(simulatorName);
installAppOnSimulator(APP_PATH);
const udid = extractSimulatorUDID();
const simulator = findAvailableSimulator();
launchSimulator(simulator);
installAppOnSimulator(appPath);
bringSimulatorInForeground();
await launchAppOnSimulator(APP_ID, udid, IS_DEBUG);
executeTestsWithRetries(APP_ID, udid, MAESTRO_FLOW, JS_ENGINE, 1);
await launchAppOnSimulator(appId, simulator.udid, isDebug);
executeTestsWithRetries(appId, simulator.udid, maestroFlow, jsengine, 1);
console.log('Test finished');
process.exit(0);
}

main();
if (require.main === module) {
main();
}

module.exports = {
findAvailableSimulator,
};
4 changes: 1 addition & 3 deletions .github/workflows/e2e-ios-rntester.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ on:

jobs:
test:
runs-on: macos-15-large
runs-on: macos-26-large
outputs:
status: ${{ steps.report-status.outputs.status }}
strategy:
Expand All @@ -35,8 +35,6 @@ jobs:
path: /tmp/RNTesterBuild/RNTester.app
- name: Check downloaded folder content
run: ls -lR /tmp/RNTesterBuild
- name: Setup xcode
uses: ./.github/actions/setup-xcode
- name: Run E2E Tests
id: run-tests
continue-on-error: true
Expand Down
4 changes: 1 addition & 3 deletions .github/workflows/e2e-ios-templateapp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ on:

jobs:
test:
runs-on: macos-15-large
runs-on: macos-26-large
outputs:
status: ${{ steps.report-status.outputs.status }}
strategy:
Expand All @@ -26,8 +26,6 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Setup xcode
uses: ./.github/actions/setup-xcode
- name: Setup node.js
uses: ./.github/actions/setup-node
- name: Run yarn
Expand Down
Loading