-
Notifications
You must be signed in to change notification settings - Fork 24.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Summary: Pull Request resolved: #48419 This change moves the E2E scripts for iOS to a JS script. This should make it much easier to modify the code in case we need to change it. ## Changelog [Internal] - Move e2e script from bash to JS Reviewed By: cortinico Differential Revision: D67737950 fbshipit-source-id: d0b0411c8a4d688c10e460e70b11dbfc83aaa135
- Loading branch information
1 parent
09995fc
commit 93117ea
Showing
2 changed files
with
174 additions
and
46 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -57,52 +57,13 @@ runs: | |
# Maestro can fail in case of flakyness, we have some retry logic. | ||
set +e | ||
echo "Launching iOS Simulator: iPhone 15 Pro" | ||
xcrun simctl boot "iPhone 15 Pro" | ||
echo "Installing app on Simulator" | ||
xcrun simctl install booted "${{ inputs.app-path }}" | ||
echo "Retrieving device UDID" | ||
UDID=$(xcrun simctl list devices booted -j | jq -r '[.devices[]] | add | first | .udid') | ||
echo "UDID is $UDID" | ||
echo "Bring simulator in foreground" | ||
open -a simulator | ||
echo "Launch the app" | ||
xcrun simctl launch $UDID ${{ inputs.app-id }} | ||
if [[ ${{ inputs.flavor }} == 'Debug' ]]; then | ||
# To give the app time to warm the metro's cache | ||
sleep 20 | ||
fi | ||
echo "Running tests with Maestro" | ||
export MAESTRO_DRIVER_STARTUP_TIMEOUT=1500000 # 25 min. CI is extremely slow | ||
# Add retries for flakyness | ||
MAX_ATTEMPTS=5 | ||
CURR_ATTEMPT=0 | ||
RESULT=1 | ||
while [[ $CURR_ATTEMPT -lt $MAX_ATTEMPTS ]] && [[ $RESULT -ne 0 ]]; do | ||
CURR_ATTEMPT=$((CURR_ATTEMPT+1)) | ||
echo "Attempt number $CURR_ATTEMPT" | ||
echo "Start video record using pid: video_record_${{ inputs.jsengine }}_$CURR_ATTEMPT.pid" | ||
xcrun simctl io booted recordVideo video_record_$CURR_ATTEMPT.mov & echo $! > video_record_${{ inputs.jsengine }}_$CURR_ATTEMPT.pid | ||
echo '$HOME/.maestro/bin/maestro --udid=$UDID test ${{ inputs.maestro-flow }} --format junit -e APP_ID=${{ inputs.app-id }}' | ||
$HOME/.maestro/bin/maestro --udid=$UDID test ${{ inputs.maestro-flow }} --format junit -e APP_ID=${{ inputs.app-id }} --debug-output /tmp/MaestroLogs | ||
RESULT=$? | ||
# Stop video | ||
kill -SIGINT $(cat video_record_${{ inputs.jsengine }}_$CURR_ATTEMPT.pid) | ||
done | ||
exit $RESULT | ||
node .github/workflow-scripts/maestro-ios.js \ | ||
"${{ inputs.app-path }}" \ | ||
"${{ inputs.app-id }}" \ | ||
"${{ inputs.maestro-flow }}" \ | ||
"${{ inputs.jsengine }}" \ | ||
"${{ inputs.flavor }}" \ | ||
"${{ inputs.working-directory }}" | ||
- name: Store video record | ||
if: always() | ||
uses: actions/[email protected] | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
/** | ||
* 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 | ||
*/ | ||
|
||
const childProcess = require('child_process'); | ||
const fs = require('fs'); | ||
|
||
const usage = ` | ||
=== Usage === | ||
node maestro-android.js <path to app> <app_id> <maestro_flow> <flavor> <working_directory> | ||
@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 | ||
============== | ||
`; | ||
|
||
const args = process.argv.slice(2); | ||
|
||
if (args.length !== 6) { | ||
throw new Error(`Invalid number of arguments.\n${usage}`); | ||
} | ||
|
||
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]; | ||
|
||
const MAX_ATTEMPTS = 5; | ||
|
||
function launchSimulator(simulatorName) { | ||
console.log(`Launching simulator ${simulatorName}`); | ||
try { | ||
childProcess.execSync(`xcrun simctl boot "${simulatorName}"`); | ||
} catch (error) { | ||
if ( | ||
!error.message.includes('Unable to boot device in current state: Booted') | ||
) { | ||
throw error; | ||
} | ||
} | ||
} | ||
|
||
function installAppOnSimulator(appPath) { | ||
console.log(`Installing app at path ${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'); | ||
} | ||
|
||
function sleep(ms) { | ||
return new Promise(resolve => { | ||
setTimeout(resolve, ms); | ||
}); | ||
} | ||
|
||
async function launchAppOnSimulator(appId, udid, isDebug) { | ||
console.log('Launch the app'); | ||
childProcess.execSync(`xcrun simctl launch "${udid}" "${appId}"`); | ||
|
||
if (isDebug) { | ||
console.log('Wait for metro to warm'); | ||
await sleep(20 * 1000); | ||
} | ||
} | ||
|
||
function startVideoRecording(jsengine, currentAttempt) { | ||
console.log( | ||
`Start video record using pid: video_record_${jsengine}_${currentAttempt}.pid`, | ||
); | ||
childProcess.exec( | ||
`xcrun simctl io booted recordVideo video_record_${jsengine}_${currentAttempt}.mov & echo $! > video_record_${jsengine}_${currentAttempt}.pid`, | ||
); | ||
} | ||
|
||
function stopVideoRecording(jsengine, currentAttempt) { | ||
console.log( | ||
`Stop video record using pid: video_record_${jsengine}_${currentAttempt}.pid`, | ||
); | ||
childProcess.exec( | ||
`kill -SIGINT $(cat video_record_${jsengine}_${currentAttempt}.pid)`, | ||
); | ||
} | ||
|
||
function executeTestsWithRetries( | ||
appId, | ||
udid, | ||
maestroFlow, | ||
jsengine, | ||
currentAttempt, | ||
) { | ||
try { | ||
startVideoRecording(jsengine, currentAttempt); | ||
|
||
const timeout = 1000 * 60 * 10; // 10 minutes | ||
const command = `$HOME/.maestro/bin/maestro --udid="${udid}" test "${maestroFlow}" --format junit -e APP_ID="${appId}"`; | ||
console.log(command); | ||
childProcess.execSync(`MAESTRO_DRIVER_STARTUP_TIMEOUT=1500000 ${command}`, { | ||
stdio: 'inherit', | ||
timeout, | ||
}); | ||
|
||
stopVideoRecording(jsengine, currentAttempt); | ||
} catch (error) { | ||
// Can't put this in the finally block because it will be executed after the | ||
// recursive call of executeTestsWithRetries | ||
stopVideoRecording(jsengine, currentAttempt); | ||
|
||
if (currentAttempt < MAX_ATTEMPTS) { | ||
executeTestsWithRetries( | ||
appId, | ||
udid, | ||
maestroFlow, | ||
jsengine, | ||
currentAttempt + 1, | ||
); | ||
} else { | ||
console.error(`Failed to execute flow after ${MAX_ATTEMPTS} attempts.`); | ||
throw error; | ||
} | ||
} | ||
} | ||
|
||
async function main() { | ||
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('==============================\n'); | ||
|
||
const simulatorName = 'iPhone 15 Pro'; | ||
launchSimulator(simulatorName); | ||
installAppOnSimulator(APP_PATH); | ||
const udid = extractSimulatorUDID(); | ||
bringSimulatorInForeground(); | ||
await launchAppOnSimulator(APP_ID, udid, IS_DEBUG); | ||
executeTestsWithRetries(APP_ID, udid, MAESTRO_FLOW, JS_ENGINE, 1); | ||
console.log('Test finished'); | ||
process.exit(0); | ||
} | ||
|
||
main(); |