diff --git a/.github/actions/maestro-ios/action.yml b/.github/actions/maestro-ios/action.yml index 6ebc7327fb0b..def69fd78092 100644 --- a/.github/actions/maestro-ios/action.yml +++ b/.github/actions/maestro-ios/action.yml @@ -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 @@ -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 diff --git a/.github/workflow-scripts/__tests__/maestro-ios-test.js b/.github/workflow-scripts/__tests__/maestro-ios-test.js index 8b604ae1da66..8b23a47a3418 100644 --- a/.github/workflow-scripts/__tests__/maestro-ios-test.js +++ b/.github/workflow-scripts/__tests__/maestro-ios-test.js @@ -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', + ); + }); }); diff --git a/.github/workflow-scripts/maestro-ios.js b/.github/workflow-scripts/maestro-ios.js index 6b7a536f72d2..02ed4a08d5d0 100644 --- a/.github/workflow-scripts/maestro-ios.js +++ b/.github/workflow-scripts/maestro-ios.js @@ -12,7 +12,7 @@ const fs = require('fs'); const usage = ` === Usage === -node maestro-android.js +node maestro-ios.js [device_model] [device_os] @param {string} appPath - Path to the app APK @param {string} appId - App ID that needs to be launched @@ -20,18 +20,40 @@ node maestro-android.js 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) { @@ -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}`); } @@ -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:'); @@ -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 ?? ''}`); + console.info(`DEVICE_OS: ${deviceOS ?? ''}`); console.info('==============================\n'); - const simulator = findAvailableSimulator(); + const simulator = findAvailableSimulator(deviceModel, deviceOS); launchSimulator(simulator); installAppOnSimulator(appPath); bringSimulatorInForeground(); diff --git a/.github/workflows/e2e-ios-rntester.yml b/.github/workflows/e2e-ios-rntester.yml index f7ec8894d3d9..e0f69be743ae 100644 --- a/.github/workflows/e2e-ios-rntester.yml +++ b/.github/workflows/e2e-ios-rntester.yml @@ -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' }} diff --git a/.github/workflows/maestro-cloud-rntester.yml b/.github/workflows/maestro-cloud-rntester.yml index a620ca368810..7f245bbcbebf 100644 --- a/.github/workflows/maestro-cloud-rntester.yml +++ b/.github/workflows/maestro-cloud-rntester.yml @@ -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 @@ -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 }} diff --git a/.github/workflows/test-all.yml b/.github/workflows/test-all.yml index f1ee1caab331..237f4ad66f70 100644 --- a/.github/workflows/test-all.yml +++ b/.github/workflows/test-all.yml @@ -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