1/** 2 * Copyright (c) Meta Platforms, Inc. and affiliates. 3 * 4 * This source code is licensed under the MIT license found in the 5 * LICENSE file in the root directory of this source tree. 6 * 7 * @format 8 */ 9 10'use strict'; 11 12/** 13 * This script tests that React Native end to end installation/bootstrap works for different platforms 14 * Available arguments: 15 * --ios - 'react-native init' and check iOS app doesn't redbox 16 * --android - 'react-native init' and check Android app doesn't redbox 17 * --js - 'react-native init' and only check the packager returns a bundle 18 * --skip-cli-install - to skip react-native-cli global installation (for local debugging) 19 * --retries [num] - how many times to retry possible flaky commands: yarn add and running tests, default 1 20 */ 21 22const {cd, cp, echo, exec, exit, mv, rm} = require('shelljs'); 23const spawn = require('child_process').spawn; 24const argv = require('yargs').argv; 25const path = require('path'); 26 27const SCRIPTS = __dirname; 28const ROOT = path.normalize(path.join(__dirname, '..')); 29const tryExecNTimes = require('./try-n-times'); 30 31const REACT_NATIVE_TEMP_DIR = exec( 32 'mktemp -d /tmp/react-native-XXXXXXXX', 33).stdout.trim(); 34const REACT_NATIVE_APP_DIR = `${REACT_NATIVE_TEMP_DIR}/template`; 35const numberOfRetries = argv.retries || 1; 36let SERVER_PID; 37let APPIUM_PID; 38let exitCode; 39 40function describe(message) { 41 echo(`\n\n>>>>> ${message}\n\n\n`); 42} 43 44try { 45 if (argv.android) { 46 describe('Compile Android binaries'); 47 if ( 48 exec( 49 './gradlew :ReactAndroid:installArchives -Pjobs=1 -Dorg.gradle.jvmargs="-Xmx512m -XX:+HeapDumpOnOutOfMemoryError"', 50 ).code 51 ) { 52 echo('Failed to compile Android binaries'); 53 exitCode = 1; 54 throw Error(exitCode); 55 } 56 } 57 58 describe('Create react-native package'); 59 if (exec('node ./scripts/set-rn-version.js --version 1000.0.0').code) { 60 echo('Failed to set version and update package.json ready for release'); 61 exitCode = 1; 62 throw Error(exitCode); 63 } 64 65 if (exec('npm pack').code) { 66 echo('Failed to pack react-native'); 67 exitCode = 1; 68 throw Error(exitCode); 69 } 70 71 const REACT_NATIVE_PACKAGE = path.join(ROOT, 'react-native-*.tgz'); 72 73 describe('Scaffold a basic React Native app from template'); 74 exec(`rsync -a ${ROOT}/template ${REACT_NATIVE_TEMP_DIR}`); 75 cd(REACT_NATIVE_APP_DIR); 76 77 const METRO_CONFIG = path.join(ROOT, 'metro.config.js'); 78 const RN_GET_POLYFILLS = path.join(ROOT, 'rn-get-polyfills.js'); 79 const RN_POLYFILLS_PATH = 'packages/polyfills/'; 80 exec(`mkdir -p ${RN_POLYFILLS_PATH}`); 81 82 cp(METRO_CONFIG, '.'); 83 cp(RN_GET_POLYFILLS, '.'); 84 exec( 85 `rsync -a ${ROOT}/${RN_POLYFILLS_PATH} ${REACT_NATIVE_APP_DIR}/${RN_POLYFILLS_PATH}`, 86 ); 87 mv('_flowconfig', '.flowconfig'); 88 mv('_watchmanconfig', '.watchmanconfig'); 89 mv('_bundle', '.bundle'); 90 91 describe('Install React Native package'); 92 exec(`npm install ${REACT_NATIVE_PACKAGE}`); 93 94 describe('Install node_modules'); 95 if ( 96 tryExecNTimes( 97 () => { 98 return exec('npm install').code; 99 }, 100 numberOfRetries, 101 () => exec('sleep 10s'), 102 ) 103 ) { 104 echo('Failed to execute npm install'); 105 echo('Most common reason is npm registry connectivity, try again'); 106 exitCode = 1; 107 throw Error(exitCode); 108 } 109 exec('rm -rf ./node_modules/react-native/template'); 110 111 if (argv.android) { 112 describe('Install end-to-end framework'); 113 if ( 114 tryExecNTimes( 115 () => 116 exec( 117 'yarn add --dev [email protected] [email protected] [email protected] [email protected] [email protected]', 118 {silent: true}, 119 ).code, 120 numberOfRetries, 121 ) 122 ) { 123 echo('Failed to install appium'); 124 echo('Most common reason is npm registry connectivity, try again'); 125 exitCode = 1; 126 throw Error(exitCode); 127 } 128 cp(`${SCRIPTS}/android-e2e-test.js`, 'android-e2e-test.js'); 129 cd('android'); 130 describe('Download Maven deps'); 131 exec('./gradlew :app:copyDownloadableDepsToLibs'); 132 cd('..'); 133 134 describe('Generate key'); 135 exec('rm android/app/debug.keystore'); 136 if ( 137 exec( 138 'keytool -genkey -v -keystore android/app/debug.keystore -storepass android -alias androiddebugkey -keypass android -keyalg RSA -keysize 2048 -validity 10000 -dname "CN=Android Debug,O=Android,C=US"', 139 ).code 140 ) { 141 echo('Key could not be generated'); 142 exitCode = 1; 143 throw Error(exitCode); 144 } 145 146 describe(`Start appium server, ${APPIUM_PID}`); 147 const appiumProcess = spawn('node', ['./node_modules/.bin/appium']); 148 APPIUM_PID = appiumProcess.pid; 149 150 describe('Build the app'); 151 if (exec('react-native run-android').code) { 152 echo('could not execute react-native run-android'); 153 exitCode = 1; 154 throw Error(exitCode); 155 } 156 157 describe(`Start Metro, ${SERVER_PID}`); 158 // shelljs exec('', {async: true}) does not emit stdout events, so we rely on good old spawn 159 const packagerProcess = spawn('yarn', ['start', '--max-workers 1'], { 160 env: process.env, 161 }); 162 SERVER_PID = packagerProcess.pid; 163 // wait a bit to allow packager to startup 164 exec('sleep 15s'); 165 describe('Test: Android end-to-end test'); 166 if ( 167 tryExecNTimes( 168 () => { 169 return exec('node node_modules/.bin/_mocha android-e2e-test.js').code; 170 }, 171 numberOfRetries, 172 () => exec('sleep 10s'), 173 ) 174 ) { 175 echo('Failed to run Android end-to-end tests'); 176 echo('Most likely the code is broken'); 177 exitCode = 1; 178 throw Error(exitCode); 179 } 180 } 181 182 if (argv.ios) { 183 cd('ios'); 184 // shelljs exec('', {async: true}) does not emit stdout events, so we rely on good old spawn 185 const packagerEnv = Object.create(process.env); 186 packagerEnv.REACT_NATIVE_MAX_WORKERS = 1; 187 describe('Start Metro'); 188 const packagerProcess = spawn('yarn', ['start'], { 189 stdio: 'inherit', 190 env: packagerEnv, 191 }); 192 SERVER_PID = packagerProcess.pid; 193 exec('sleep 15s'); 194 // prepare cache to reduce chances of possible red screen "Can't find variable __fbBatchedBridge..." 195 exec( 196 'response=$(curl --write-out %{http_code} --silent --output /dev/null localhost:8081/index.bundle?platform=ios&dev=true)', 197 ); 198 echo(`Metro is running, ${SERVER_PID}`); 199 200 describe('Install CocoaPod dependencies'); 201 exec('bundle exec pod install'); 202 203 describe('Test: iOS end-to-end test'); 204 if ( 205 // TODO: Get target OS and simulator from .tests.env 206 tryExecNTimes( 207 () => { 208 return exec( 209 [ 210 'xcodebuild', 211 '-workspace', 212 '"HelloWorld.xcworkspace"', 213 '-destination', 214 '"platform=iOS Simulator,name=iPhone 8,OS=13.3"', 215 '-scheme', 216 '"HelloWorld"', 217 '-sdk', 218 'iphonesimulator', 219 '-UseModernBuildSystem=NO', 220 'test', 221 ].join(' ') + 222 ' | ' + 223 [ 224 'xcpretty', 225 '--report', 226 'junit', 227 '--output', 228 '"~/react-native/reports/junit/iOS-e2e/results.xml"', 229 ].join(' ') + 230 ' && exit ${PIPESTATUS[0]}', 231 ).code; 232 }, 233 numberOfRetries, 234 () => exec('sleep 10s'), 235 ) 236 ) { 237 echo('Failed to run iOS end-to-end tests'); 238 echo('Most likely the code is broken'); 239 exitCode = 1; 240 throw Error(exitCode); 241 } 242 cd('..'); 243 } 244 245 if (argv.js) { 246 // Check the packager produces a bundle (doesn't throw an error) 247 describe('Test: Verify packager can generate an Android bundle'); 248 if ( 249 exec( 250 'yarn react-native bundle --verbose --entry-file index.js --platform android --dev true --bundle-output android-bundle.js --max-workers 1', 251 ).code 252 ) { 253 echo('Could not build Android bundle'); 254 exitCode = 1; 255 throw Error(exitCode); 256 } 257 describe('Test: Verify packager can generate an iOS bundle'); 258 if ( 259 exec( 260 'yarn react-native bundle --entry-file index.js --platform ios --dev true --bundle-output ios-bundle.js --max-workers 1', 261 ).code 262 ) { 263 echo('Could not build iOS bundle'); 264 exitCode = 1; 265 throw Error(exitCode); 266 } 267 describe('Test: Flow check'); 268 // The resolve package included a test for a malformed package.json (see https://github.com/browserify/resolve/issues/89) 269 // that is failing the flow check. We're removing it. 270 rm('-rf', './node_modules/resolve/test/resolver/malformed_package_json'); 271 if (exec(`${ROOT}/node_modules/.bin/flow check`).code) { 272 echo('Flow check failed.'); 273 exitCode = 1; 274 throw Error(exitCode); 275 } 276 } 277 exitCode = 0; 278} finally { 279 describe('Clean up'); 280 if (SERVER_PID) { 281 echo(`Killing packager ${SERVER_PID}`); 282 exec(`kill -9 ${SERVER_PID}`); 283 // this is quite drastic but packager starts a daemon that we can't kill by killing the parent process 284 // it will be fixed in April (quote David Aurelio), so until then we will kill the zombie by the port number 285 exec("lsof -i tcp:8081 | awk 'NR!=1 {print $2}' | xargs kill"); 286 } 287 if (APPIUM_PID) { 288 echo(`Killing appium ${APPIUM_PID}`); 289 exec(`kill -9 ${APPIUM_PID}`); 290 } 291} 292exit(exitCode); 293