mirror of
https://github.com/bulwarkmail/native.git
synced 2026-10-31 08:06:36 +00:00
227 lines
9.1 KiB
YAML
227 lines
9.1 KiB
YAML
name: iOS simulator tests
|
|
|
|
# Builds the app for the iOS Simulator (no signing needed), runs the Maestro
|
|
# flows in .maestro/ against it and uploads screenshots and a screen recording.
|
|
#
|
|
# The signed-in flow needs the E2E_SERVER and E2E_USERNAME variables and the
|
|
# E2E_PASSWORD secret (a test account on a server the runner can reach).
|
|
# Without them only the launch flow runs.
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
app_run_id:
|
|
description: "Reuse the simulator app from this earlier run instead of building (run id)"
|
|
type: string
|
|
required: false
|
|
release:
|
|
types: [published]
|
|
|
|
concurrency:
|
|
group: e2e-ios-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
simulator:
|
|
runs-on: macos-latest
|
|
timeout-minutes: 75
|
|
permissions:
|
|
contents: read
|
|
actions: read
|
|
env:
|
|
EXPO_NO_TELEMETRY: "1"
|
|
OUT: ${{ github.workspace }}/e2e-output
|
|
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- uses: actions/setup-node@v4
|
|
if: inputs.app_run_id == ''
|
|
with:
|
|
node-version: 20
|
|
cache: npm
|
|
|
|
- uses: actions/setup-java@v4
|
|
with:
|
|
distribution: temurin
|
|
java-version: 17
|
|
|
|
- name: Cache CocoaPods
|
|
if: inputs.app_run_id == ''
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: |
|
|
~/Library/Caches/CocoaPods
|
|
~/.cocoapods/repos
|
|
key: pods-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
|
|
restore-keys: pods-${{ runner.os }}-
|
|
|
|
- name: Install JS dependencies
|
|
if: inputs.app_run_id == ''
|
|
run: npm ci
|
|
|
|
- name: Generate the iOS project
|
|
if: inputs.app_run_id == ''
|
|
run: npx expo prebuild --platform ios --clean
|
|
|
|
- name: Build for the simulator
|
|
if: inputs.app_run_id == ''
|
|
run: |
|
|
WORKSPACE=$(ls -d ios/*.xcworkspace | head -1)
|
|
SCHEME=$(basename "$WORKSPACE" .xcworkspace)
|
|
# Release bundles the JS into the app, so no Metro server is needed.
|
|
# arm64 only: the runner is Apple silicon, and a second simulator
|
|
# architecture would double the build time.
|
|
mkdir -p "$OUT"
|
|
if ! xcodebuild build \
|
|
-workspace "$WORKSPACE" \
|
|
-scheme "$SCHEME" \
|
|
-configuration Release \
|
|
-sdk iphonesimulator \
|
|
-destination 'generic/platform=iOS Simulator' \
|
|
-derivedDataPath build \
|
|
ARCHS=arm64 \
|
|
CODE_SIGNING_ALLOWED=NO \
|
|
> "$OUT/xcodebuild.log" 2>&1; then
|
|
grep -E "error:|fatal error" "$OUT/xcodebuild.log" | head -n 50 || true
|
|
tail -n 80 "$OUT/xcodebuild.log"
|
|
exit 1
|
|
fi
|
|
# Packed as a tar: artifacts drop the executable bit, which would
|
|
# leave a reused app unable to launch.
|
|
tar -C build/Build/Products/Release-iphonesimulator -czf "$RUNNER_TEMP/simulator-app.tar.gz" BulwarkMobile.app
|
|
|
|
- name: Upload the simulator app
|
|
if: inputs.app_run_id == ''
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: simulator-app
|
|
path: ${{ runner.temp }}/simulator-app.tar.gz
|
|
if-no-files-found: error
|
|
|
|
- name: Download the simulator app from an earlier run
|
|
if: inputs.app_run_id != ''
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
name: simulator-app
|
|
run-id: ${{ inputs.app_run_id }}
|
|
github-token: ${{ github.token }}
|
|
path: ${{ runner.temp }}
|
|
|
|
- name: Unpack the simulator app
|
|
run: |
|
|
mkdir -p "$RUNNER_TEMP/app"
|
|
tar -C "$RUNNER_TEMP/app" -xzf "$RUNNER_TEMP/simulator-app.tar.gz"
|
|
echo "APP=$RUNNER_TEMP/app/BulwarkMobile.app" >> "$GITHUB_ENV"
|
|
|
|
- name: Boot a simulator
|
|
run: |
|
|
# The newest iPhone on the newest runtime the image ships.
|
|
UDID=$(xcrun simctl list devices available -j | jq -r '
|
|
[.devices | to_entries[]
|
|
| select(.key | test("SimRuntime.iOS"))
|
|
| .value[] | select(.name | startswith("iPhone")) | .udid] | last')
|
|
if [ -z "$UDID" ] || [ "$UDID" = "null" ]; then
|
|
xcrun simctl list devices available
|
|
echo "::error::No iPhone simulator available"
|
|
exit 1
|
|
fi
|
|
xcrun simctl boot "$UDID"
|
|
xcrun simctl bootstatus "$UDID" -b
|
|
# A fresh simulator shows the slide-to-type tip over the keyboard the
|
|
# first time a text field gets focus, and typed text goes nowhere.
|
|
xcrun simctl spawn "$UDID" defaults write com.apple.keyboard.preferences DidShowContinuousPathIntroduction -bool true
|
|
xcrun simctl spawn "$UDID" defaults write com.apple.keyboard.preferences KeyboardContinuousPathEnabled -bool false
|
|
# Predictions, autocorrect and password AutoFill can swallow or
|
|
# rewrite what XCTest types.
|
|
xcrun simctl spawn "$UDID" defaults write com.apple.keyboard.preferences KeyboardPrediction -bool false
|
|
xcrun simctl spawn "$UDID" defaults write com.apple.keyboard.preferences KeyboardAutocorrection -bool false
|
|
xcrun simctl spawn "$UDID" defaults write com.apple.keyboard.preferences KeyboardAutocapitalization -bool false
|
|
xcrun simctl spawn "$UDID" defaults write com.apple.WebUI AutoFillPasswords -bool false || true
|
|
xcrun simctl install "$UDID" "$APP"
|
|
echo "UDID=$UDID" >> "$GITHUB_ENV"
|
|
xcrun simctl list devices | grep "$UDID"
|
|
|
|
- name: Install Maestro
|
|
run: |
|
|
curl -fsSL "https://get.maestro.mobile.dev" | bash
|
|
echo "$HOME/.maestro/bin" >> "$GITHUB_PATH"
|
|
|
|
- name: Run the flows
|
|
env:
|
|
SERVER: ${{ vars.E2E_SERVER }}
|
|
USERNAME: ${{ vars.E2E_USERNAME }}
|
|
PASSWORD: ${{ secrets.E2E_PASSWORD }}
|
|
# The XCTest driver takes a while to come up on a cold runner.
|
|
MAESTRO_DRIVER_STARTUP_TIMEOUT: "600000"
|
|
MAESTRO_CLI_NO_ANALYTICS: "1"
|
|
run: |
|
|
maestro --version
|
|
mkdir -p "$OUT/screenshots"
|
|
|
|
xcrun simctl io "$UDID" recordVideo --codec=h264 --force "$OUT/run.mp4" &
|
|
RECORDER=$!
|
|
|
|
ARGS=()
|
|
if [ -n "$SERVER" ] && [ -n "$USERNAME" ] && [ -n "$PASSWORD" ]; then
|
|
ARGS+=(-e "SERVER=$SERVER" -e "USERNAME=$USERNAME" -e "PASSWORD=$PASSWORD")
|
|
else
|
|
echo "::notice::E2E_SERVER, E2E_USERNAME or E2E_PASSWORD is not set - running the launch flow only."
|
|
ARGS+=(--exclude-tags signed-in)
|
|
fi
|
|
|
|
# Warm-up with the launch flow alone. It types nothing, so its debug
|
|
# log holds no secrets and can be printed when the driver won't start.
|
|
# The real run below keeps debug output off: it would log the password,
|
|
# and artifacts here are public.
|
|
set +e
|
|
touch "$RUNNER_TEMP/flows-started"
|
|
(cd "$OUT/screenshots" && maestro --device "$UDID" test \
|
|
--debug-output "$RUNNER_TEMP/maestro-warmup" "$GITHUB_WORKSPACE/.maestro/launch.yaml")
|
|
STATUS=$?
|
|
echo "::group::Maestro warm-up log (typing)"
|
|
find "$RUNNER_TEMP/maestro-warmup" -name 'maestro.log' -exec grep -n -i -E "inputText|typeText|keyboard|input_text|Exception|error" {} \; | tail -n 120
|
|
echo "::endgroup::"
|
|
maestro --device "$UDID" hierarchy > "$OUT/hierarchy-warmup.json" 2>/dev/null || true
|
|
if [ $STATUS -ne 0 ]; then
|
|
echo "::group::Maestro warm-up log"
|
|
find "$RUNNER_TEMP/maestro-warmup" -name 'maestro.log' -exec tail -n 300 {} \;
|
|
echo "::endgroup::"
|
|
echo "::group::xcodebuild logs of the driver"
|
|
find "$HOME/Library/Logs" "$HOME/.maestro" "$TMPDIR" -name '*.log' -newer "$RUNNER_TEMP/flows-started" 2>/dev/null \
|
|
| grep -i -E 'xctest|xcodebuild|maestro' | grep -v maestro-warmup | head -5 \
|
|
| while read -r f; do echo "== $f"; tail -n 80 "$f"; done
|
|
echo "::endgroup::"
|
|
else
|
|
(cd "$OUT/screenshots" && maestro --device "$UDID" test \
|
|
--format junit --output "$OUT/report.xml" \
|
|
"${ARGS[@]}" "$GITHUB_WORKSPACE/.maestro")
|
|
STATUS=$?
|
|
fi
|
|
set -e
|
|
|
|
kill -INT "$RECORDER" || true
|
|
wait "$RECORDER" || true
|
|
|
|
# Newer Maestro versions write screenshots (and a screenshot of any
|
|
# failed step) under ~/.maestro/tests instead. Copy only the images:
|
|
# the JSON next to them holds the typed password.
|
|
find "$HOME/.maestro/tests" -name '*.png' -exec cp {} "$OUT/screenshots/" \; 2>/dev/null || true
|
|
ls -la "$OUT/screenshots"
|
|
exit $STATUS
|
|
|
|
- name: Grab the app log on failure
|
|
if: failure() && env.UDID != ''
|
|
run: |
|
|
xcrun simctl spawn "$UDID" log show --last 10m --style compact \
|
|
--predicate 'process CONTAINS "Bulwark"' > "$OUT/app.log" 2>&1 || true
|
|
tail -n 100 "$OUT/app.log" || true
|
|
|
|
- name: Upload screenshots and recording
|
|
if: always()
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: e2e-ios-${{ github.run_number }}
|
|
path: e2e-output/
|
|
if-no-files-found: ignore
|