From 0b9c94c5377ee687abe0c011b35b753b2b924ee3 Mon Sep 17 00:00:00 2001 From: Paul Gschwendtner Date: Mon, 22 Oct 2018 19:16:42 +0200 Subject: [PATCH] build: run browserstack on circleci --- .circleci/config.yml | 20 ++++++++ .travis.yml | 1 - scripts/browserstack/start-tunnel.sh | 53 +++++++++++----------- scripts/browserstack/stop-tunnel.sh | 20 ++++++-- scripts/browserstack/wait-tunnel.sh | 14 ++++-- scripts/circleci/run-browserstack-tests.sh | 26 +++++++++++ test/browser-providers.js | 3 +- test/karma.conf.js | 12 ++++- 8 files changed, 110 insertions(+), 39 deletions(-) create mode 100755 scripts/circleci/run-browserstack-tests.sh diff --git a/.circleci/config.yml b/.circleci/config.yml index 28e7bc558ce0..9aea0ac46ce4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -125,6 +125,25 @@ jobs: - *save_cache + # ---------------------------------------------------------------------------- + # Job that runs the unit tests on Browserstack. The browsers that will be used + # to run the unit tests on Browserstack are set in: test/browser-providers.js + # ---------------------------------------------------------------------------- + tests_browserstack: + <<: *job_defaults + resource_class: xlarge + environment: + BROWSER_STACK_USERNAME: "angularteam1" + BROWSER_STACK_ACCESS_KEY: "CaXMeMHD9pr5PHg8N7Jq" + steps: + - *checkout_code + - *restore_cache + - *yarn_install + + - run: ./scripts/circleci/run-browserstack-tests.sh + + - *save_cache + # -------------------------------------- # Job that builds the demo-app with AOT # -------------------------------------- @@ -189,6 +208,7 @@ workflows: unit_tests: jobs: - tests_local_browsers + - tests_browserstack integration_tests: jobs: diff --git a/.travis.yml b/.travis.yml index 422ac36bbb9f..7a313c7897aa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,7 +27,6 @@ jobs: include: - env: "MODE=payload" - env: "MODE=saucelabs_required" - - env: "MODE=browserstack_required" - env: "DEPLOY_MODE=build-artifacts" if: type = push - env: "DEPLOY_MODE=docs-content" diff --git a/scripts/browserstack/start-tunnel.sh b/scripts/browserstack/start-tunnel.sh index 65df8e8949d5..533f851426f4 100755 --- a/scripts/browserstack/start-tunnel.sh +++ b/scripts/browserstack/start-tunnel.sh @@ -2,49 +2,48 @@ set -e -o pipefail -# Workaround for Travis CI cookbook https://github.com/travis-ci/travis-ci/issues/4862, -# where $PATH will be extended with relative paths to the NPM binaries. -PATH=`echo ${PATH} | sed -e 's/:\.\/node_modules\/\.bin//'` +tunnelFileName="BrowserStackLocal-linux-x64.zip" +tunnelUrl="https://www.browserstack.com/browserstack-local/${tunnelFileName}" -TUNNEL_FILE="BrowserStackLocal-linux-x64.zip" -TUNNEL_URL="https://www.browserstack.com/browserstack-local/${TUNNEL_FILE}" -TUNNEL_DIR="/tmp/browserstack-tunnel" -TUNNEL_LOG="${LOGS_DIR}/browserstack-tunnel.log" - -BROWSER_STACK_ACCESS_KEY=`echo ${BROWSER_STACK_ACCESS_KEY} | rev` +tunnelTmpDir="/tmp/material-browserstack" +tunnelLogFile="${tunnelTmpDir}/browserstack-local.log" +tunnelReadyFile="${tunnelTmpDir}/readyfile" +tunnelErrorFile="${tunnelTmpDir}/errorfile" # Cleanup and create the folder structure for the tunnel connector. -rm -rf ${TUNNEL_DIR} ${BROWSER_PROVIDER_READY_FILE} -mkdir -p ${TUNNEL_DIR} -touch ${TUNNEL_LOG} +rm -rf ${tunnelTmpDir} ${tunnelReadyFile} ${tunnelErrorFile} +mkdir -p ${tunnelTmpDir} +touch ${tunnelLogFile} -cd ${TUNNEL_DIR} +# Go into temporary tunnel directory. +cd ${tunnelTmpDir} # Download the browserstack local binaries. -curl ${TUNNEL_URL} -o ${TUNNEL_FILE} 2> /dev/null 1> /dev/null +curl ${tunnelUrl} -o ${tunnelFileName} 2> /dev/null 1> /dev/null # Extract the browserstack local binaries from the tarball. mkdir -p browserstack-tunnel -unzip -q ${TUNNEL_FILE} -d browserstack-tunnel +unzip -q ${tunnelFileName} -d browserstack-tunnel -# Cleanup the download directory. -rm ${TUNNEL_FILE} +# Cleanup the downloaded zip archive. +rm ${tunnelFileName} ARGS="" -# Set tunnel-id only on Travis, to make local testing easier. -if [ ! -z "${TRAVIS_JOB_ID}" ]; then - ARGS="${ARGS} --local-identifier ${TRAVIS_JOB_ID}" +if [ ! -z "${CIRCLE_BUILD_NUM}" ]; then + ARGS="${ARGS} --local-identifier ${CIRCLE_BUILD_NUM}" fi -echo "Starting Browserstack Local in the background, logging into: ${TUNNEL_LOG}" +echo "Starting Browserstack Local in the background, logging into: ${tunnelLogFile}" # Extension to the BrowserStackLocal binaries, because those can't create a readyfile. function create_ready_file { + # Process ID for the BrowserStack local asynchronous instance. + tunnelProcessPid=${1} # To be able to exit the tail properly we need to have a sub shell spawned, which is # used to track the state of tail. - { sleep 120; touch ${BROWSER_PROVIDER_ERROR_FILE}; } & + { sleep 120; touch ${tunnelErrorFile}; } & TIMER_PID=${!} @@ -54,16 +53,18 @@ function create_ready_file { # When the tail recognizes the `Ctrl-C` log message the BrowserStack Tunnel is up. { - tail -n0 -f ${TUNNEL_LOG} --pid ${TIMER_PID} | { sed '/Ctrl/q' && kill -9 ${TIMER_PID}; }; + tail -n0 -f ${tunnelLogFile} --pid ${TIMER_PID} | { sed '/Ctrl/q' && kill -9 ${TIMER_PID}; }; } &> /dev/null echo echo "BrowserStack Tunnel ready" - touch ${BROWSER_PROVIDER_READY_FILE} + # Create the readyfile and write the PID for BrowserStack Local into it. + echo ${tunnelProcessPid} > ${tunnelReadyFile} } -browserstack-tunnel/BrowserStackLocal -k ${BROWSER_STACK_ACCESS_KEY} ${ARGS} 2>&1 >> ${TUNNEL_LOG} & +browserstack-tunnel/BrowserStackLocal -k ${BROWSER_STACK_ACCESS_KEY} ${ARGS} 2>&1 >> \ + ${tunnelLogFile} & # Wait for the tunnel to be ready and create the readyfile with the Browserstack PID -create_ready_file & +create_ready_file ${!} & diff --git a/scripts/browserstack/stop-tunnel.sh b/scripts/browserstack/stop-tunnel.sh index f95970de9f63..2f19808b3b47 100755 --- a/scripts/browserstack/stop-tunnel.sh +++ b/scripts/browserstack/stop-tunnel.sh @@ -2,15 +2,27 @@ set -e -o pipefail +tunnelTmpDir="/tmp/material-browserstack" +tunnelReadyFile="${tunnelTmpDir}/readyfile" -echo "Shutting down Browserstack tunnel" +if [[ ! -f ${tunnelReadyFile} ]]; then + echo "BrowserStack tunnel has not been started. Cannot stop tunnel.." + exit 1 +fi -killall BrowserStackLocal +echo "Shutting down Browserstack tunnel.." -while [[ -n `ps -ef | grep "BrowserStackLocal" | grep -v "grep"` ]]; do +# The process id for the BrowserStack local instance is stored inside of the readyfile. +tunnelProcessId=$(cat ${tunnelReadyFile}) + +# Kill the process by using the PID that has been read from the readyfile. Note that +# we cannot use killall because CircleCI base container images don't have it installed. +kill ${tunnelProcessId} + +while (ps -p ${tunnelProcessId} &> /dev/null); do printf "." sleep .5 done echo "" -echo "Browserstack tunnel has been shut down" \ No newline at end of file +echo "Browserstack tunnel has been shut down" diff --git a/scripts/browserstack/wait-tunnel.sh b/scripts/browserstack/wait-tunnel.sh index 6b1aaa112bdc..6a7e845b31a1 100755 --- a/scripts/browserstack/wait-tunnel.sh +++ b/scripts/browserstack/wait-tunnel.sh @@ -1,13 +1,17 @@ #!/bin/bash -TUNNEL_LOG="$LOGS_DIR/browserstack-tunnel.log" +tunnelTmpDir="/tmp/material-browserstack" +tunnelLogFile="${tunnelTmpDir}/browserstack-local.log" +tunnelReadyFile="${tunnelTmpDir}/readyfile" +tunnelErrorFile="${tunnelTmpDir}/errorfile" + WAIT_DELAY=30 # Method that prints the logfile output of the browserstack tunnel. printLog() { - echo "Logfile output of Browserstack tunnel (${TUNNEL_LOG}):" + echo "Logfile output of Browserstack tunnel (${tunnelLogFile}):" echo "" - cat ${TUNNEL_LOG} + cat ${tunnelLogFile} } # Wait for Connect to be ready before exiting @@ -15,14 +19,14 @@ printLog() { let "counter=0" # Exit the process if there are errors reported. Print the tunnel log to the console. -if [ -f $BROWSER_PROVIDER_ERROR_FILE ]; then +if [ -f ${tunnelErrorFile} ]; then echo echo "An error occurred while starting the tunnel. See error:" printLog exit 5 fi -while [ ! -f $BROWSER_PROVIDER_READY_FILE ]; do +while [ ! -f ${tunnelReadyFile} ]; do let "counter++" # Counter needs to be multiplied by two because the while loop only sleeps a half second. diff --git a/scripts/circleci/run-browserstack-tests.sh b/scripts/circleci/run-browserstack-tests.sh new file mode 100755 index 000000000000..55f4309ef195 --- /dev/null +++ b/scripts/circleci/run-browserstack-tests.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +# In case any command failed, we want to immediately exit the script with the +# proper exit code. +set -e + +# Go to project directory. +cd $(dirname ${0})/../.. + +# Decode access token and make it accessible for child processes. +export BROWSER_STACK_ACCESS_KEY=`echo ${BROWSER_STACK_ACCESS_KEY} | rev` + +# Start tunnel and wait for it being ready. +./scripts/browserstack/start-tunnel.sh +./scripts/browserstack/wait-tunnel.sh + +# Setup the test platform environment variable that will be read +# by the Karma configuration script. +export TEST_PLATFORM="browserstack" + +# Run the unit tests on Browserstack with Karma. +yarn gulp ci:test + +# Kill the Browserstack tunnel. This is necessary in order to avoid rate-limit +# errors that cause the unit tests to be flaky. +./scripts/browserstack/stop-tunnel.sh diff --git a/test/browser-providers.js b/test/browser-providers.js index a97d3dcb1775..87722c1a9d23 100644 --- a/test/browser-providers.js +++ b/test/browser-providers.js @@ -71,8 +71,7 @@ function decodeToken(token) { return (token || '').split('').reverse().join(''); } - -/** Ensures that the Travis access keys work properly. */ +/** Ensures that the Saucelabs and Browserstack access keys work properly. */ if (process.env.TRAVIS) { process.env.SAUCE_ACCESS_KEY = decodeToken(process.env.SAUCE_ACCESS_KEY); process.env.BROWSER_STACK_ACCESS_KEY = decodeToken(process.env.BROWSER_STACK_ACCESS_KEY); diff --git a/test/karma.conf.js b/test/karma.conf.js index a8ae3f9b2e3c..4e0561faba83 100644 --- a/test/karma.conf.js +++ b/test/karma.conf.js @@ -101,7 +101,17 @@ module.exports = (config) => { }); if (process.env['CIRCLECI']) { - config.browsers = platformMap[process.env['TEST_PLATFORM']]; + const tunnelIdentifier = process.env['CIRCLE_BUILD_NUM']; + const buildIdentifier = `angular-material-${tunnelIdentifier}`; + const testPlatform = process.env['TEST_PLATFORM']; + + if (testPlatform === 'browserstack') { + config.browserStack.build = buildIdentifier; + config.browserStack.tunnelIdentifier = tunnelIdentifier; + } + + // Configure Karma launch the browsers that belong to the given test platform. + config.browsers = platformMap[testPlatform]; } if (process.env['TRAVIS']) {