diff --git a/.github/workflow-scripts/__tests__/maestro-ios-test.js b/.github/workflow-scripts/__tests__/maestro-ios-test.js new file mode 100644 index 000000000000..e77c0c05cfd0 --- /dev/null +++ b/.github/workflow-scripts/__tests__/maestro-ios-test.js @@ -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', + }); + }); +}); diff --git a/.github/workflow-scripts/maestro-ios.js b/.github/workflow-scripts/maestro-ios.js index bad5f8a35248..0fcae5a54b89 100644 --- a/.github/workflow-scripts/maestro-ios.js +++ b/.github/workflow-scripts/maestro-ios.js @@ -23,25 +23,28 @@ node maestro-android.js /^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') @@ -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'); @@ -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, +}; diff --git a/.github/workflows/e2e-ios-rntester.yml b/.github/workflows/e2e-ios-rntester.yml index 941711c111e9..999af94846f7 100644 --- a/.github/workflows/e2e-ios-rntester.yml +++ b/.github/workflows/e2e-ios-rntester.yml @@ -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: @@ -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 diff --git a/.github/workflows/e2e-ios-templateapp.yml b/.github/workflows/e2e-ios-templateapp.yml index dd4761471ca7..b203e9c84b24 100644 --- a/.github/workflows/e2e-ios-templateapp.yml +++ b/.github/workflows/e2e-ios-templateapp.yml @@ -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: @@ -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