react-native/scripts/run-ci-e2e-tests.js

233 lines
8.3 KiB
JavaScript
Raw Normal View History

/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
'use strict';
/**
* This script tests that React Native end to end installation/bootstrap works for different platforms
* Available arguments:
* --ios - 'react-native init' and check iOS app doesn't redbox
* --tvos - 'react-native init' and check tvOS app doesn't redbox
* --android - 'react-native init' and check Android app doesn't redbox
* --js - 'react-native init' and only check the packager returns a bundle
* --skip-cli-install - to skip react-native-cli global installation (for local debugging)
* --retries [num] - how many times to retry possible flaky commands: yarn add and running tests, default 1
*/
/*eslint-disable no-undef */
require('shelljs/global');
const spawn = require('child_process').spawn;
const argv = require('yargs').argv;
const path = require('path');
const SCRIPTS = __dirname;
const ROOT = path.normalize(path.join(__dirname, '..'));
const tryExecNTimes = require('./try-n-times');
const TEMP = exec('mktemp -d /tmp/react-native-XXXXXXXX').stdout.trim();
// To make sure we actually installed the local version
// of react-native, we will create a temp file inside the template
// and check that it exists after `react-native init
const MARKER_IOS = exec(`mktemp ${ROOT}/local-cli/templates/HelloWorld/ios/HelloWorld/XXXXXXXX`).stdout.trim();
const MARKER_ANDROID = exec(`mktemp ${ROOT}/local-cli/templates/HelloWorld/android/XXXXXXXX`).stdout.trim();
const numberOfRetries = argv.retries || 1;
let SERVER_PID;
let APPIUM_PID;
let exitCode;
try {
// install CLI
cd('react-native-cli');
exec('yarn pack');
const CLI_PACKAGE = path.join(ROOT, 'react-native-cli', 'react-native-cli-*.tgz');
cd('..');
if (!argv['skip-cli-install']) {
if (exec(`sudo yarn global add ${CLI_PACKAGE}`).code) {
echo('Could not install react-native-cli globally.');
echo('Run with --skip-cli-install to skip this step');
exitCode = 1;
throw Error(exitCode);
}
}
if (argv.android) {
if (exec('./gradlew :ReactAndroid:installArchives -Pjobs=1 -Dorg.gradle.jvmargs="-Xmx512m -XX:+HeapDumpOnOutOfMemoryError"').code) {
echo('Failed to compile Android binaries');
exitCode = 1;
throw Error(exitCode);
}
}
if (exec('yarn pack').code) {
echo('Failed to pack react-native');
exitCode = 1;
throw Error(exitCode);
}
const PACKAGE = path.join(ROOT, 'react-native-*.tgz');
cd(TEMP);
if (tryExecNTimes(
() => {
exec('sleep 10s');
return exec(`react-native init EndToEndTest --version ${PACKAGE}`).code;
},
numberOfRetries,
() => rm('-rf', 'EndToEndTest'))) {
echo('Failed to execute react-native init');
echo('Most common reason is npm registry connectivity, try again');
exitCode = 1;
throw Error(exitCode);
}
cd('EndToEndTest');
if (argv.android) {
echo('Running an Android e2e test');
echo('Installing e2e framework');
if (tryExecNTimes(
() => exec('yarn add --dev appium@1.5.1 mocha@2.4.5 wd@0.3.11 colors@1.0.3 pretty-data2@0.40.1', { silent: true }).code,
numberOfRetries)) {
echo('Failed to install appium');
echo('Most common reason is npm registry connectivity, try again');
exitCode = 1;
throw Error(exitCode);
}
cp(`${SCRIPTS}/android-e2e-test.js`, 'android-e2e-test.js');
cd('android');
echo('Downloading Maven deps');
exec('./gradlew :app:copyDownloadableDepsToLibs');
// Make sure we installed local version of react-native
if (!test('-e', path.basename(MARKER_ANDROID))) {
echo('Android marker was not found, react native init command failed?');
exitCode = 1;
throw Error(exitCode);
}
cd('..');
exec('keytool -genkey -v -keystore android/keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"');
echo(`Starting appium server, ${APPIUM_PID}`);
const appiumProcess = spawn('node', ['./node_modules/.bin/appium']);
APPIUM_PID = appiumProcess.pid;
echo('Building the app');
if (exec('buck build android/app').code) {
echo('could not execute Buck build, is it installed and in PATH?');
exitCode = 1;
throw Error(exitCode);
}
echo(`Starting packager server, ${SERVER_PID}`);
// shelljs exec('', {async: true}) does not emit stdout events, so we rely on good old spawn
const packagerProcess = spawn('yarn', ['start', '--max-workers 1'], {
Add --maxWorkers flag and allow transformers to run in-band. Summary: This diff cleans up some cruft and adds some features: * It removes the usage of an env variable to control workers. * It removes the lazy and handwavy calculation on how many workers to use for jest-haste-map. Jest itself uses the maximum amount of workers available and it has never been reported as an issue – especially since it is a one-time startup cost of about 3 seconds on a cold cache only. * It adds a `--max-workers` flag to replace the env variable. This one is able to control both the number of workers for `jest-haste-map` as well as the transformers. * It makes the transformers run in the parent process if 1 or fewer workers are are specified. This should help with debugging. Once you approve this diff, I will publish a new version of metro to npm and update the version used in RN and remove the use of the env variable altogether: https://our.intern.facebook.com/intern/biggrep/?corpus=xplat&filename=&case=false&view=default&extre=&s=REACT_NATIVE_MAX_WORKERS&engine=apr_strmatch&context=false&filter[uninteresting]=false&filter[intern]=false&filter[test]=false&grep_regex= Note: the process of adding a CLI option is really broken. Commander also has a weird API. We should consider building a better public API for Metro and then consider how to build a new CLI on top of it and simplify our internal integration. I really don't like how Metro is integrated across pieces of the RN cli in ways that is hard to manage. But that is a larger task for another time :) Reviewed By: jeanlauliac Differential Revision: D5217726 fbshipit-source-id: 74efddbb87755a9e744c816fbc62efa21f6a79bf
2017-06-13 16:11:57 +00:00
env: process.env
});
SERVER_PID = packagerProcess.pid;
// wait a bit to allow packager to startup
exec('sleep 15s');
echo('Executing android e2e test');
if (tryExecNTimes(
() => {
exec('sleep 10s');
return exec('node node_modules/.bin/_mocha android-e2e-test.js').code;
},
numberOfRetries)) {
echo('Failed to run Android e2e tests');
echo('Most likely the code is broken');
exitCode = 1;
throw Error(exitCode);
}
}
if (argv.ios || argv.tvos) {
var iosTestType = (argv.tvos ? 'tvOS' : 'iOS');
echo('Running the ' + iosTestType + 'app');
cd('ios');
// Make sure we installed local version of react-native
if (!test('-e', path.join('EndToEndTest', path.basename(MARKER_IOS)))) {
echo('iOS marker was not found, `react-native init` command failed?');
exitCode = 1;
throw Error(exitCode);
}
// shelljs exec('', {async: true}) does not emit stdout events, so we rely on good old spawn
const packagerEnv = Object.create(process.env);
packagerEnv.REACT_NATIVE_MAX_WORKERS = 1;
const packagerProcess = spawn('yarn', ['start', '--nonPersistent'],
{
stdio: 'inherit',
env: packagerEnv
});
SERVER_PID = packagerProcess.pid;
exec('sleep 15s');
// prepare cache to reduce chances of possible red screen "Can't fibd variable __fbBatchedBridge..."
Move HelloWorld template to a single index.js entry point Summary: This change (initially discussed in https://github.com/react-community/create-react-native-app/issues/26) moves the HelloWorld project template from two nearly identical entry points (`index.android.js` and `index.ios.js`) to a single, minimal `index.js` entry point. The root component is created in `App.js`. This unifies the project structure between `react-native init` and Create React Native App and allows CRNA's eject to use the entry point from the HelloWorld template without any hacks to customize it. Also examples in the docs can be just copy-pasted to `App.js` the same way in both HelloWorld and CRNA apps without having to first learn about `AppRegistry.registerComponent`. * Created a new project from the template using `./scripts/test-manual-e2e.sh` and verified that: * The app builds, starts and runs both on Android and iOS. * Editing and reloading changes works. * The new files (`index.js`, `App.js`, `__tests__/App.js`) get created in the project folder. <img width="559" alt="screen shot 2017-08-01 at 19 10 51" src="https://user-images.githubusercontent.com/497214/28835171-300a12b6-76ed-11e7-81b2-623639c3b8f6.png"> <img width="467" alt="screen shot 2017-08-01 at 19 09 12" src="https://user-images.githubusercontent.com/497214/28835180-33d285e0-76ed-11e7-8d68-2b3bc44bf585.png"> <!-- Thank you for sending the PR! If you changed any code, please provide us with clear instructions on how you verified your changes work. In other words, a test plan is *required*. Bonus points for screenshots and videos! Please read the Contribution Guidelines at https://github.com/facebook/react-native/blob/master/CONTRIBUTING.md to learn more about contributing to React Native. Happy contributing! --> Closes https://github.com/facebook/react-native/pull/15312 Differential Revision: D5556276 Pulled By: hramos fbshipit-source-id: 068fdf7e51381c2bc50321522f2be0db47296c5e
2017-08-03 20:52:06 +00:00
exec('response=$(curl --write-out %{http_code} --silent --output /dev/null localhost:8081/index.bundle?platform=ios&dev=true)');
echo(`Starting packager server, ${SERVER_PID}`);
echo('Executing ' + iosTestType + ' e2e test');
if (tryExecNTimes(
() => {
exec('sleep 10s');
if (argv.tvos) {
return exec('xcodebuild -destination "platform=tvOS Simulator,name=Apple TV 1080p,OS=10.0" -scheme EndToEndTest-tvOS -sdk appletvsimulator test | xcpretty && exit ${PIPESTATUS[0]}').code;
} else {
return exec('xcodebuild -destination "platform=iOS Simulator,name=iPhone 5s,OS=10.3.1" -scheme EndToEndTest -sdk iphonesimulator test | xcpretty && exit ${PIPESTATUS[0]}').code;
}
},
numberOfRetries)) {
echo('Failed to run ' + iosTestType + ' e2e tests');
echo('Most likely the code is broken');
exitCode = 1;
throw Error(exitCode);
}
cd('..');
}
if (argv.js) {
// Check the packager produces a bundle (doesn't throw an error)
Move HelloWorld template to a single index.js entry point Summary: This change (initially discussed in https://github.com/react-community/create-react-native-app/issues/26) moves the HelloWorld project template from two nearly identical entry points (`index.android.js` and `index.ios.js`) to a single, minimal `index.js` entry point. The root component is created in `App.js`. This unifies the project structure between `react-native init` and Create React Native App and allows CRNA's eject to use the entry point from the HelloWorld template without any hacks to customize it. Also examples in the docs can be just copy-pasted to `App.js` the same way in both HelloWorld and CRNA apps without having to first learn about `AppRegistry.registerComponent`. * Created a new project from the template using `./scripts/test-manual-e2e.sh` and verified that: * The app builds, starts and runs both on Android and iOS. * Editing and reloading changes works. * The new files (`index.js`, `App.js`, `__tests__/App.js`) get created in the project folder. <img width="559" alt="screen shot 2017-08-01 at 19 10 51" src="https://user-images.githubusercontent.com/497214/28835171-300a12b6-76ed-11e7-81b2-623639c3b8f6.png"> <img width="467" alt="screen shot 2017-08-01 at 19 09 12" src="https://user-images.githubusercontent.com/497214/28835180-33d285e0-76ed-11e7-8d68-2b3bc44bf585.png"> <!-- Thank you for sending the PR! If you changed any code, please provide us with clear instructions on how you verified your changes work. In other words, a test plan is *required*. Bonus points for screenshots and videos! Please read the Contribution Guidelines at https://github.com/facebook/react-native/blob/master/CONTRIBUTING.md to learn more about contributing to React Native. Happy contributing! --> Closes https://github.com/facebook/react-native/pull/15312 Differential Revision: D5556276 Pulled By: hramos fbshipit-source-id: 068fdf7e51381c2bc50321522f2be0db47296c5e
2017-08-03 20:52:06 +00:00
if (exec('react-native bundle --max-workers 1 --platform android --dev true --entry-file index.js --bundle-output android-bundle.js').code) {
echo('Could not build Android bundle');
exitCode = 1;
throw Error(exitCode);
}
Move HelloWorld template to a single index.js entry point Summary: This change (initially discussed in https://github.com/react-community/create-react-native-app/issues/26) moves the HelloWorld project template from two nearly identical entry points (`index.android.js` and `index.ios.js`) to a single, minimal `index.js` entry point. The root component is created in `App.js`. This unifies the project structure between `react-native init` and Create React Native App and allows CRNA's eject to use the entry point from the HelloWorld template without any hacks to customize it. Also examples in the docs can be just copy-pasted to `App.js` the same way in both HelloWorld and CRNA apps without having to first learn about `AppRegistry.registerComponent`. * Created a new project from the template using `./scripts/test-manual-e2e.sh` and verified that: * The app builds, starts and runs both on Android and iOS. * Editing and reloading changes works. * The new files (`index.js`, `App.js`, `__tests__/App.js`) get created in the project folder. <img width="559" alt="screen shot 2017-08-01 at 19 10 51" src="https://user-images.githubusercontent.com/497214/28835171-300a12b6-76ed-11e7-81b2-623639c3b8f6.png"> <img width="467" alt="screen shot 2017-08-01 at 19 09 12" src="https://user-images.githubusercontent.com/497214/28835180-33d285e0-76ed-11e7-8d68-2b3bc44bf585.png"> <!-- Thank you for sending the PR! If you changed any code, please provide us with clear instructions on how you verified your changes work. In other words, a test plan is *required*. Bonus points for screenshots and videos! Please read the Contribution Guidelines at https://github.com/facebook/react-native/blob/master/CONTRIBUTING.md to learn more about contributing to React Native. Happy contributing! --> Closes https://github.com/facebook/react-native/pull/15312 Differential Revision: D5556276 Pulled By: hramos fbshipit-source-id: 068fdf7e51381c2bc50321522f2be0db47296c5e
2017-08-03 20:52:06 +00:00
if (exec('react-native --max-workers 1 bundle --platform ios --dev true --entry-file index.js --bundle-output ios-bundle.js').code) {
echo('Could not build iOS bundle');
exitCode = 1;
throw Error(exitCode);
}
if (exec(`${ROOT}/node_modules/.bin/flow check`).code) {
echo('Flow check does not pass');
exitCode = 1;
throw Error(exitCode);
}
if (exec('yarn test').code) {
echo('Jest test failure');
exitCode = 1;
throw Error(exitCode);
}
}
exitCode = 0;
} finally {
cd(ROOT);
rm(MARKER_IOS);
rm(MARKER_ANDROID);
if (SERVER_PID) {
echo(`Killing packager ${SERVER_PID}`);
exec(`kill -9 ${SERVER_PID}`);
// this is quite drastic but packager starts a daemon that we can't kill by killing the parent process
// it will be fixed in April (quote David Aurelio), so until then we will kill the zombie by the port number
exec("lsof -i tcp:8081 | awk 'NR!=1 {print $2}' | xargs kill");
}
if (APPIUM_PID) {
echo(`Killing appium ${APPIUM_PID}`);
exec(`kill -9 ${APPIUM_PID}`);
}
}
exit(exitCode);
/*eslint-enable no-undef */