I recently got a chance to work within a Frontend team which delivers iOS and Android applications to our customers. One of the pain point in the team was the difficulty in app testing and release so I spent some time on investigating the best approach for the team. The team is using React Native based Expo and at this stage highly replying on manual testing on physical devices. They are currently putting efforts on automated testing based on Detox.
Expo EAS
Expo provides a React Native build tool called Expo Application Services (EAS), it allows you to build and submit applications easily. By default when you run eas build
, it will create a build on their cloud services and you will have to pay for priority pipeline execution. The Free plan at the time of writing only allows you to build one pipeline at a time. In our case we already have CircleCI subscription so it would be better if we can build app bundles there. Fortunately there is a --local
option which allows building app bundles locally. You will still need to set up EAS on Expo, for detail guide, please refer to their setup guide. Here is an example command we use to build our app bundles using CircleCI MacOS and JVM executor.
eas build --profile preview --platform ios --clear-cache --local --non-interactive --output "my-app.ipa"
We use eas submit
to automatically push master/production build to app store and play store after manual approval. The key here is that for BrowserStack we need to use internal distribution profile so that we can install on their devices. You don’t have to add their remote devices to Expo as BrowserStack re-signs the bundles upon upload so that they can be installed on their devices.
BrowserStack
To make current manual testing easier I decided to introduce BrowserStack App Live, it allows the team to test the app using remote real devices and it offers quite a lot of iOS and Android options. It also comes with local testing which allows us to connect to our backend services hosted behind our vpn. There are some options for getting access to the app. You can upload app .ipa
and .apk
bundles, or install via Apple TestFlight and Google Play. To test push notification, please refer to guide here. If you are uploading the .ipa
Apple bundle, unfortunately it is not supported at the time of writing, the only way to test push notification on iOS is installation via TestFlight.
Overall Pipeline View
Here is what our overall pipeline looks like:
For branch build we have a manual trigger to push bundles to BrowserStack as we don’t want every commit to go through manual QA process. We only trigger this once the PR has been reviewed and ready for QA. The bundles are also uploaded to S3 so that we can retrieve them and install locally easily. You will need to register your test physical devices with Expo using eas device:create
so that the bundles can be installed. See internal distribution setup here.
For master/main branch build we went through the similar process with the only difference that builds are automatically pushed to BrowserStack with a manual trigger to push to production following production profile build. We use standard-version to automatically increase our app version so that every build is able to be released. The BrowserStack preview here is quite useful during our review session and following that we will decide whether the candidate version is good enough for release.
CircleCI config
Here are some useful CircleCI steps for the pipeline described above:
Build Android Using OpenJDK executor
eas-build-android:
executor: android
parameters:
profile:
description: distribution profile
type: string
default: preview
environment:
PROFILE: << parameters.profile >>
steps:
- attach_workspace:
at: ~/
- node/install:
install-yarn: true
node-version: '16.9.1'
- run: echo -n $PLAY_STORE_SERVICE_ACCOUNT_KEY | base64 -d > serviceAccountKey.json
- run:
name: android build
command: |
mkdir android_sdk
curl -O https://dl.google.com/android/repository/commandlinetools-linux-8512546_latest.zip
unzip commandlinetools-linux-8512546_latest.zip
mv cmdline-tools android_sdk
mkdir -p android_sdk/cmdline-tools/tools
mv -t android_sdk/cmdline-tools/tools android_sdk/cmdline-tools/lib android_sdk/cmdline-tools/bin android_sdk/cmdline-tools/source.properties android_sdk/cmdline-tools/NOTICE.txt
export ANDROID_SDK_ROOT=$HOME/project/android_sdk
yes | $ANDROID_SDK_ROOT/cmdline-tools/tools/bin/sdkmanager --licenses || if [[ $? -eq 141 ]]; then true; else exit $?; fi
npm install -g expo-cli eas-cli
npm ci
eas build --profile << parameters.profile >> --platform android --clear-cache --local --non-interactive --output "${CIRCLE_BRANCH//\//-}-${PROFILE}-${CIRCLE_SHA1:0:7}.apk"
Build iOS using MacOS executor
eas-build-ios:
executor: ios
parameters:
profile:
description: eas profile
type: string
default: preview
environment:
PROFILE: << parameters.profile >>
steps:
- attach_workspace:
at: ~/
- node/install:
install-yarn: true
node-version: '16.9.1'
- run:
name: ios build
command: |
npm install -g expo-cli eas-cli
npm ci
eas build --profile << parameters.profile >> --platform ios --clear-cache --local --non-interactive --output "${CIRCLE_BRANCH//\//-}-${PROFILE}-${CIRCLE_SHA1:0:7}.ipa"
We are using CircleCI NodeJS Orbs to install NodeJS on the executors. For Android we pass in service account key from pipeline secret and need to download Command Line Tools too.
Overall the pipeline works quite well for us and we have significant improvements on development process. Now product manager/designer and manual QA could easily download the bundle and play with the app on either BrowserStack or our test physical devices.