Skip to content

Run Maestro tests

Run Maestro tests #6

name: Run Maestro tests
env:
# The name of the main module repository
main_project_module: app
# The name in the Play Store
playstore_name: Soundscape
on:
# Allows you to run this workflow manually from the Actions tab. With no
# inputs it downloads and tests the latest GitHub release. An optional tag
# can be given to test an older release instead.
workflow_dispatch:
inputs:
release_tag:
description: 'Release tag to test (e.g. soundscape-1.0.12). Leave empty for the latest release.'
required: false
type: string
# Allows another workflow (e.g. run-tests) to call this as a follow-on job,
# passing the name of an APK artifact it uploaded earlier in the same run.
workflow_call:
inputs:
apk_artifact:
description: 'Name of an APK artifact uploaded earlier in the same run to install and test.'
required: false
type: string
fail_on_error:
description: 'Fail the run if any Maestro flow fails. Callers can set this false to keep Maestro non-blocking.'
required: false
type: boolean
default: true
jobs:
maestro-test:
runs-on: ubuntu-latest
environment: development
permissions:
contents: read
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
# When called from another workflow, grab the APK that workflow built and
# uploaded earlier in the same run.
- name: Download APK from calling workflow
if: ${{ inputs.apk_artifact != '' }}
uses: actions/download-artifact@v8
with:
name: ${{ inputs.apk_artifact }}
path: apk
# When run standalone, download the APK from a GitHub release. Defaults to
# the latest release unless a tag was supplied.
- name: Download APK from release
if: ${{ inputs.apk_artifact == '' }}
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ inputs.release_tag }}
run: |
mkdir -p apk
if [ -z "$RELEASE_TAG" ]; then
RELEASE_TAG=$(gh release view --json tagName -q .tagName)
echo "No tag supplied, using latest release: $RELEASE_TAG"
else
echo "Using requested release: $RELEASE_TAG"
fi
gh release download "$RELEASE_TAG" --pattern 'release-apk-*.zip' --dir apk
unzip -o apk/release-apk-*.zip -d apk
# The artifact (debug build) and the release zip (release build) put the
# APK at different paths, so locate it rather than hard-coding the name.
- name: Locate APK
id: find-apk
run: |
apk_path=$(find apk -name '*.apk' | head -n1)
if [ -z "$apk_path" ]; then
echo "No APK found to test" >&2
exit 1
fi
echo "Found APK: $apk_path"
echo "apk_path=$apk_path" >> "$GITHUB_OUTPUT"
- name: Enable KVM
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: Install Maestro
run: MAESTRO_VERSION=2.6.0 curl -Ls 'https://get.maestro.mobile.dev' | bash
# Fetch the Glasgow offline map extract on the runner host; it is pushed
# onto the emulator in the Maestro Tests step below.
- name: Download offline map extract
run: |
curl -Lsf -o glasgow-gb.pmtiles \
'https://pub-0a3501283b024ab3bbfbb6d1e217f5d0.r2.dev/street-metadata/glasgow-gb.pmtiles'
- name: Maestro Tests
id: maestro-tests
uses: reactivecircus/android-emulator-runner@v2
continue-on-error: true # IMPORTANT: allow pipeline to continue to Android Test Report step
with:
api-level: 34
arch: x86_64
ram-size: 2048M
enable-hw-keyboard: false
target: default
script: |
adb logcat -c # clear logs
touch app/emulator.log # create log file
chmod 777 app/emulator.log # allow writing to log file
adb logcat >> app/emulator.log & # pipe all logcat messages into log file as a background process
adb install ${{ steps.find-apk.outputs.apk_path }}
# Push the Glasgow offline map extract into the app's offline-extracts
# directory and pin the emulator GPS to the STA office, so the
# location-dependent flows run against known map data and a fixed
# location. The extracts directory is the app-specific external files
# dir + "/Download" (see GeoEngine.offlineExtractPath); scoped storage
# blocks writes there even for the shell user, so we use root - the
# emulator runs a userdebug image, so "adb root" is available.
#
# The android-emulator-runner runs each script line in its own
# "sh -c", so a shell variable assigned on one line is empty by the
# next; the extract directory path is therefore inlined rather than
# held in a variable. It is the app-specific external files dir +
# "/Download" (see GeoEngine.offlineExtractPath).
adb root
adb wait-for-device
adb shell mkdir -p /storage/emulated/0/Android/data/org.scottishtecharmy.soundscape/files/Download
adb push glasgow-gb.pmtiles /storage/emulated/0/Android/data/org.scottishtecharmy.soundscape/files/Download/
# The .geojson metadata sidecar (findExtracts reads "<pmtiles>.geojson")
# is committed under .github/fixtures as it is too small to be worth a
# download, unlike the pmtiles itself.
adb push .github/fixtures/glasgow-gb.pmtiles.geojson /storage/emulated/0/Android/data/org.scottishtecharmy.soundscape/files/Download/
# adb emu geo fix takes longitude then latitude.
adb emu geo fix -3.223538 55.955360
mkdir maestro_outputs
# Run every flow even if an earlier one fails, then fail the step at
# the end if any of them failed.
status=0
$HOME/.maestro/bin/maestro test --format=junit --output=report1.xml --test-output-dir=maestro_outputs --no-ansi maestro/Onboarding.yaml || status=1
$HOME/.maestro/bin/maestro test --format=junit --output=report2.xml --test-output-dir=maestro_outputs --no-ansi maestro/HomePage.yaml || status=1
$HOME/.maestro/bin/maestro test --format=junit --output=report3.xml --test-output-dir=maestro_outputs --no-ansi maestro/LocationDetails.yaml || status=1
$HOME/.maestro/bin/maestro test --format=junit --output=report4.xml --test-output-dir=maestro_outputs --no-ansi maestro/PlacesNearby.yaml || status=1
$HOME/.maestro/bin/maestro test --format=junit --output=report5.xml --test-output-dir=maestro_outputs --no-ansi maestro/MarkersAndRoutes.yaml || status=1
$HOME/.maestro/bin/maestro test --format=junit --output=report6.xml --test-output-dir=maestro_outputs --no-ansi maestro/RouteCreation.yaml || status=1
exit $status
- name: Upload maestro reports
uses: actions/upload-artifact@v6
with:
name: maestro-reports
path: maestro_outputs
- name: Upload Failing Test Report Log
if: steps.maestro-tests.outcome != 'success' # upload the generated log on failure of the tests job
uses: actions/upload-artifact@v6
with:
name: logs
path: app/emulator.log # path to where the log is
- name: Raise error on test fail # set a red tick on the workflow run if the tests failed
# inputs.fail_on_error is unset for a manual (workflow_dispatch) run, in
# which case we still want to fail; a caller can pass false to suppress.
if: steps.maestro-tests.outcome != 'success' && inputs.fail_on_error != false
run: exit 1