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} = require('shelljs'); 23const spawn = require('child_process').spawn; 24const argv = require('yargs').argv; 25const path = require('path'); 26 27const forEachPackage = require('./monorepo/for-each-package'); 28const setupVerdaccio = require('./setup-verdaccio'); 29 30const SCRIPTS = __dirname; 31const ROOT = path.normalize(path.join(__dirname, '..')); 32const tryExecNTimes = require('./try-n-times'); 33 34const REACT_NATIVE_TEMP_DIR = exec( 35 'mktemp -d /tmp/react-native-XXXXXXXX', 36).stdout.trim(); 37const REACT_NATIVE_APP_DIR = `${REACT_NATIVE_TEMP_DIR}/template`; 38const numberOfRetries = argv.retries || 1; 39 40const VERDACCIO_CONFIG_PATH = path.join(ROOT, '.circleci/verdaccio.yml'); 41 42let SERVER_PID; 43let APPIUM_PID; 44let VERDACCIO_PID; 45let exitCode; 46 47function describe(message) { 48 echo(`\n\n>>>>> ${message}\n\n\n`); 49} 50 51try { 52 if (argv.android) { 53 describe('Compile Android binaries'); 54 if ( 55 exec( 56 './gradlew :ReactAndroid:installArchives -Pjobs=1 -Dorg.gradle.jvmargs="-Xmx512m -XX:+HeapDumpOnOutOfMemoryError"', 57 ).code 58 ) { 59 echo('Failed to compile Android binaries'); 60 exitCode = 1; 61 throw Error(exitCode); 62 } 63 } 64 65 describe('Create react-native package'); 66 if (exec('node ./scripts/set-rn-version.js --version 1000.0.0').code) { 67 echo('Failed to set version and update package.json ready for release'); 68 exitCode = 1; 69 throw Error(exitCode); 70 } 71 72 if (exec('npm pack').code) { 73 echo('Failed to pack react-native'); 74 exitCode = 1; 75 throw Error(exitCode); 76 } 77 78 const REACT_NATIVE_PACKAGE = path.join(ROOT, 'react-native-*.tgz'); 79 80 describe('Set up Verdaccio'); 81 VERDACCIO_PID = setupVerdaccio(ROOT, VERDACCIO_CONFIG_PATH); 82 83 describe('Publish packages'); 84 forEachPackage( 85 (packageAbsolutePath, packageRelativePathFromRoot, packageManifest) => { 86 if (packageManifest.private) { 87 return; 88 } 89 90 exec( 91 'npm publish --registry http://localhost:4873 --yes --access public', 92 {cwd: packageAbsolutePath}, 93 ); 94 }, 95 ); 96 97 describe('Scaffold a basic React Native app from template'); 98 exec(`rsync -a ${ROOT}/template ${REACT_NATIVE_TEMP_DIR}`); 99 cd(REACT_NATIVE_APP_DIR); 100 101 mv('_bundle', '.bundle'); 102 mv('_eslintrc.js', '.eslintrc.js'); 103 mv('_prettierrc.js', '.prettierrc.js'); 104 mv('_watchmanconfig', '.watchmanconfig'); 105 106 describe('Install React Native package'); 107 exec(`npm install ${REACT_NATIVE_PACKAGE}`); 108 109 describe('Install node_modules'); 110 if ( 111 tryExecNTimes( 112 () => { 113 return exec('npm install').code; 114 }, 115 numberOfRetries, 116 () => exec('sleep 10s'), 117 ) 118 ) { 119 echo('Failed to execute npm install'); 120 echo('Most common reason is npm registry connectivity, try again'); 121 exitCode = 1; 122 throw Error(exitCode); 123 } 124 exec('rm -rf ./node_modules/react-native/template'); 125 126 if (argv.android) { 127 describe('Install end-to-end framework'); 128 if ( 129 tryExecNTimes( 130 () => 131 exec( 132 'yarn add --dev [email protected] [email protected] [email protected] [email protected] [email protected]', 133 {silent: true}, 134 ).code, 135 numberOfRetries, 136 ) 137 ) { 138 echo('Failed to install appium'); 139 echo('Most common reason is npm registry connectivity, try again'); 140 exitCode = 1; 141 throw Error(exitCode); 142 } 143 cp(`${SCRIPTS}/android-e2e-test.js`, 'android-e2e-test.js'); 144 cd('android'); 145 describe('Download Maven deps'); 146 exec('./gradlew :app:copyDownloadableDepsToLibs'); 147 cd('..'); 148 149 describe('Generate key'); 150 exec('rm android/app/debug.keystore'); 151 if ( 152 exec( 153 '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"', 154 ).code 155 ) { 156 echo('Key could not be generated'); 157 exitCode = 1; 158 throw Error(exitCode); 159 } 160 161 describe(`Start appium server, ${APPIUM_PID}`); 162 const appiumProcess = spawn('node', ['./node_modules/.bin/appium']); 163 APPIUM_PID = appiumProcess.pid; 164 165 describe('Build the app'); 166 if (exec('react-native run-android').code) { 167 echo('could not execute react-native run-android'); 168 exitCode = 1; 169 throw Error(exitCode); 170 } 171 172 describe(`Start Metro, ${SERVER_PID}`); 173 // shelljs exec('', {async: true}) does not emit stdout events, so we rely on good old spawn 174 const packagerProcess = spawn('yarn', ['start', '--max-workers 1']); 175 SERVER_PID = packagerProcess.pid; 176 // wait a bit to allow packager to startup 177 exec('sleep 15s'); 178 describe('Test: Android end-to-end test'); 179 if ( 180 tryExecNTimes( 181 () => { 182 return exec('node node_modules/.bin/_mocha android-e2e-test.js').code; 183 }, 184 numberOfRetries, 185 () => exec('sleep 10s'), 186 ) 187 ) { 188 echo('Failed to run Android end-to-end tests'); 189 echo('Most likely the code is broken'); 190 exitCode = 1; 191 throw Error(exitCode); 192 } 193 } 194 195 if (argv.ios) { 196 cd('ios'); 197 // shelljs exec('', {async: true}) does not emit stdout events, so we rely on good old spawn 198 const packagerEnv = Object.create(process.env); 199 packagerEnv.REACT_NATIVE_MAX_WORKERS = 1; 200 describe('Start Metro'); 201 const packagerProcess = spawn('yarn', ['start'], { 202 stdio: 'inherit', 203 env: packagerEnv, 204 }); 205 SERVER_PID = packagerProcess.pid; 206 exec('sleep 15s'); 207 // prepare cache to reduce chances of possible red screen "Can't find variable __fbBatchedBridge..." 208 exec( 209 'response=$(curl --write-out %{http_code} --silent --output /dev/null localhost:8081/index.bundle?platform=ios&dev=true)', 210 ); 211 echo(`Metro is running, ${SERVER_PID}`); 212 213 describe('Install CocoaPod dependencies'); 214 exec('bundle exec pod install'); 215 216 describe('Test: iOS end-to-end test'); 217 if ( 218 // TODO: Get target OS and simulator from .tests.env 219 tryExecNTimes( 220 () => { 221 return exec( 222 [ 223 'xcodebuild', 224 '-workspace', 225 '"HelloWorld.xcworkspace"', 226 '-destination', 227 '"platform=iOS Simulator,name=iPhone 8,OS=13.3"', 228 '-scheme', 229 '"HelloWorld"', 230 '-sdk', 231 'iphonesimulator', 232 '-UseModernBuildSystem=NO', 233 'test', 234 ].join(' ') + 235 ' | ' + 236 [ 237 'xcpretty', 238 '--report', 239 'junit', 240 '--output', 241 '"~/react-native/reports/junit/iOS-e2e/results.xml"', 242 ].join(' ') + 243 ' && exit ${PIPESTATUS[0]}', 244 ).code; 245 }, 246 numberOfRetries, 247 () => exec('sleep 10s'), 248 ) 249 ) { 250 echo('Failed to run iOS end-to-end tests'); 251 echo('Most likely the code is broken'); 252 exitCode = 1; 253 throw Error(exitCode); 254 } 255 cd('..'); 256 } 257 258 if (argv.js) { 259 // Check the packager produces a bundle (doesn't throw an error) 260 describe('Test: Verify packager can generate an Android bundle'); 261 if ( 262 exec( 263 'yarn react-native bundle --verbose --entry-file index.js --platform android --dev true --bundle-output android-bundle.js --max-workers 1', 264 ).code 265 ) { 266 echo('Could not build Android bundle'); 267 exitCode = 1; 268 throw Error(exitCode); 269 } 270 271 describe('Test: Verify packager can generate an iOS bundle'); 272 if ( 273 exec( 274 'yarn react-native bundle --entry-file index.js --platform ios --dev true --bundle-output ios-bundle.js --max-workers 1', 275 ).code 276 ) { 277 echo('Could not build iOS bundle'); 278 exitCode = 1; 279 throw Error(exitCode); 280 } 281 282 describe('Test: TypeScript typechecking'); 283 if (exec('yarn tsc').code) { 284 echo('Typechecking errors were found'); 285 exitCode = 1; 286 throw Error(exitCode); 287 } 288 289 describe('Test: Jest tests'); 290 if (exec('yarn test').code) { 291 echo('Jest tests failed'); 292 exitCode = 1; 293 throw Error(exitCode); 294 } 295 296 // TODO: ESLint infinitely hangs when running in the environment created by 297 // this script, but not projects generated by `react-native init`. 298 /* 299 describe('Test: ESLint/Prettier linting and formatting'); 300 if (exec('yarn lint').code) { 301 echo('linting errors were found'); 302 exitCode = 1; 303 throw Error(exitCode); 304 }*/ 305 } 306 exitCode = 0; 307} finally { 308 describe('Clean up'); 309 if (SERVER_PID) { 310 echo(`Killing packager ${SERVER_PID}`); 311 exec(`kill -9 ${SERVER_PID}`); 312 // this is quite drastic but packager starts a daemon that we can't kill by killing the parent process 313 // it will be fixed in April (quote David Aurelio), so until then we will kill the zombie by the port number 314 exec("lsof -i tcp:8081 | awk 'NR!=1 {print $2}' | xargs kill"); 315 } 316 if (APPIUM_PID) { 317 echo(`Killing appium ${APPIUM_PID}`); 318 exec(`kill -9 ${APPIUM_PID}`); 319 } 320 if (VERDACCIO_PID) { 321 echo(`Killing verdaccio ${VERDACCIO_PID}`); 322 exec(`kill -9 ${VERDACCIO_PID}`); 323 } 324} 325exit(exitCode); 326