# ABOUTME: CI workflow for Pierre Mobile app (React Native/Expo)
# ABOUTME: Runs unit tests, type checking, linting, and Maestro E2E tests on push/PR
name: Mobile Tests
on:
push:
branches: [ "main", "debug/*", "feature/*", "claude/*" ]
paths:
- 'frontend-mobile/**'
- 'packages/**'
- 'src/routes/auth.rs'
- 'src/routes/coaches.rs'
- 'src/routes/chat.rs'
- 'src/models/**'
- '.github/workflows/mobile-tests.yml'
pull_request:
branches: [ main ]
paths:
- 'frontend-mobile/**'
- 'packages/**'
- 'src/routes/auth.rs'
- 'src/routes/coaches.rs'
- 'src/routes/chat.rs'
- 'src/models/**'
workflow_dispatch: # Allow manual triggering
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
# Security: Explicit permissions following principle of least privilege
permissions:
contents: read
jobs:
mobile-unit-tests:
name: Mobile Unit Tests
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: "1.3.4"
- name: Install shared-types dependencies
working-directory: packages/shared-types
run: bun install
- name: Install api-client dependencies
working-directory: packages/api-client
run: bun install
- name: Install mobile dependencies
working-directory: frontend-mobile
run: bun install --frozen-lockfile
- name: Type check
working-directory: frontend-mobile
run: bun run typecheck
- name: Run unit tests
working-directory: frontend-mobile
run: bun run test -- --coverage
- name: Upload coverage report
uses: actions/upload-artifact@v4
if: always()
with:
name: mobile-coverage
path: frontend-mobile/coverage/
retention-days: 7
# Build the Rust server once on Linux, shared by integration tests and Android E2E
build-server-linux:
name: Build Server (Linux)
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@1.92.0
- name: Cache Rust dependencies
uses: actions/cache@v4
with:
path: |
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-mobile-server-1.92.0-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-mobile-server-1.92.0-
${{ runner.os }}-cargo-
- name: Build Pierre server (release)
run: cargo build --release
env:
CARGO_INCREMENTAL: 0
- name: Upload server binaries
uses: actions/upload-artifact@v4
with:
name: pierre-server-linux
path: |
target/release/pierre-mcp-server
target/release/pierre-cli
retention-days: 1
# Build the Rust server once on macOS for iOS E2E tests
build-server-macos:
name: Build Server (macOS)
runs-on: macos-latest
# Only needed when E2E tests will run
if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/claude/') || github.event_name == 'workflow_dispatch'
timeout-minutes: 30
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@1.92.0
- name: Cache Rust dependencies
uses: actions/cache@v4
with:
path: |
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-mobile-server-1.92.0-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-mobile-server-1.92.0-
${{ runner.os }}-cargo-
- name: Build Pierre server (release)
run: cargo build --release
env:
CARGO_INCREMENTAL: 0
- name: Upload server binaries
uses: actions/upload-artifact@v4
with:
name: pierre-server-macos
path: |
target/release/pierre-mcp-server
target/release/pierre-cli
retention-days: 1
mobile-integration-tests:
name: Mobile Integration Tests (Real Server)
needs: build-server-linux
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Download server binaries
uses: actions/download-artifact@v4
with:
name: pierre-server-linux
path: target/release/
- name: Make binaries executable
run: chmod +x target/release/pierre-mcp-server target/release/pierre-cli
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: "1.3.4"
- name: Install shared-types dependencies
working-directory: packages/shared-types
run: bun install
- name: Install api-client dependencies
working-directory: packages/api-client
run: bun install
- name: Install mobile dependencies
working-directory: frontend-mobile
run: bun install --frozen-lockfile
- name: Create data directory
run: mkdir -p data
- name: Start Pierre server
run: |
./target/release/pierre-mcp-server &
echo "Waiting for server to start..."
for i in {1..30}; do
if curl -s http://localhost:8081/health > /dev/null; then
echo "Server is healthy!"
break
fi
echo "Attempt $i: Server not ready yet..."
sleep 2
done
env:
DATABASE_URL: "sqlite:${{ github.workspace }}/data/mobile-integration-test.db"
PIERRE_MASTER_ENCRYPTION_KEY: "rEFe91l6lqLahoyl9OSzum9dKa40VvV5RYj8bHGNTeo="
PIERRE_RSA_KEY_SIZE: "2048"
HTTP_PORT: "8081"
RUST_LOG: "warn"
STRAVA_CLIENT_ID: "test_client_id_ci"
STRAVA_CLIENT_SECRET: "test_client_secret_ci"
STRAVA_REDIRECT_URI: "http://localhost:8081/auth/strava/callback"
- name: Run mobile integration tests
working-directory: frontend-mobile
run: bun run e2e:integration
env:
CI: true
BACKEND_URL: "http://localhost:8081"
- name: Upload integration test results
uses: actions/upload-artifact@v4
if: failure()
with:
name: mobile-integration-test-results
path: frontend-mobile/integration/
retention-days: 7
mobile-e2e-maestro:
name: Mobile E2E Tests (Maestro - iOS/Expo Go)
needs: build-server-macos
runs-on: macos-latest
# Run on main, claude/* branches, or when explicitly triggered
if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/claude/') || github.event_name == 'workflow_dispatch'
timeout-minutes: 60
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Download server binaries
uses: actions/download-artifact@v4
with:
name: pierre-server-macos
path: target/release/
- name: Make binaries executable
run: chmod +x target/release/pierre-mcp-server target/release/pierre-cli
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: "1.3.4"
- name: Install shared-types dependencies
working-directory: packages/shared-types
run: bun install
- name: Install api-client dependencies
working-directory: packages/api-client
run: bun install
- name: Remove expo-dev-client for E2E build
working-directory: frontend-mobile
run: |
# Remove expo-dev-client from package.json BEFORE install
# This package interferes with Maestro testing - its native code
# tries to connect to a dev server that doesn't exist in CI
cat package.json | jq 'del(.dependencies["expo-dev-client"])' > package.json.tmp
mv package.json.tmp package.json
- name: Install mobile dependencies
working-directory: frontend-mobile
run: bun install
- name: Install Maestro CLI
run: |
curl -Ls "https://get.maestro.mobile.dev" | bash
echo "$HOME/.maestro/bin" >> $GITHUB_PATH
- name: Create data directory
run: mkdir -p data
- name: Start Pierre server
run: |
./target/release/pierre-mcp-server &
echo "Waiting for server to start..."
for i in {1..30}; do
if curl -s http://localhost:8081/health > /dev/null; then
echo "Server is healthy!"
break
fi
echo "Attempt $i: Server not ready yet..."
sleep 2
done
env:
DATABASE_URL: "sqlite:${{ github.workspace }}/data/maestro-test.db"
PIERRE_MASTER_ENCRYPTION_KEY: "rEFe91l6lqLahoyl9OSzum9dKa40VvV5RYj8bHGNTeo="
PIERRE_RSA_KEY_SIZE: "2048"
HTTP_PORT: "8081"
RUST_LOG: "warn"
STRAVA_CLIENT_ID: "test_client_id_ci"
STRAVA_CLIENT_SECRET: "test_client_secret_ci"
STRAVA_REDIRECT_URI: "http://localhost:8081/auth/strava/callback"
- name: Seed test user for Maestro tests
run: |
./target/release/pierre-cli user create --email mobiletest@pierre.dev --password MobileTest123! --force
env:
DATABASE_URL: "sqlite:${{ github.workspace }}/data/maestro-test.db"
PIERRE_MASTER_ENCRYPTION_KEY: "rEFe91l6lqLahoyl9OSzum9dKa40VvV5RYj8bHGNTeo="
RUST_LOG: "warn"
- name: List available simulators
run: xcrun simctl list devices available
- name: Boot iOS Simulator
run: |
SIMULATOR_UDID=$(xcrun simctl list devices available | grep "iPhone 16 Pro (" | head -1 | grep -oE '[0-9A-Fa-f-]{36}')
if [ -z "$SIMULATOR_UDID" ]; then
SIMULATOR_UDID=$(xcrun simctl list devices available | grep "iPhone 16 (" | head -1 | grep -oE '[0-9A-Fa-f-]{36}')
fi
if [ -z "$SIMULATOR_UDID" ]; then
SIMULATOR_UDID=$(xcrun simctl list devices available | grep "iPhone" | head -1 | grep -oE '[0-9A-Fa-f-]{36}')
fi
if [ -z "$SIMULATOR_UDID" ]; then
echo "No iPhone simulator found!"
exit 1
fi
echo "Booting simulator: $SIMULATOR_UDID"
xcrun simctl boot "$SIMULATOR_UDID" || echo "Simulator may already be booted"
echo "SIMULATOR_UDID=$SIMULATOR_UDID" >> $GITHUB_ENV
- name: Adapt Maestro tests for Expo Go
run: |
# Replace native app ID with Expo Go app ID in all Maestro YAML files
find frontend-mobile/.maestro -name "*.yaml" -exec sed -i '' 's/appId: com.pierre.fitness/appId: host.exp.Exponent/g' {} +
# Override launch helper for Expo Go: clearState ensures fresh auth state.
# openLink launches Expo Go with the URL directly (no launchApp needed).
# The "Open in Expo Go?" dialog does NOT appear during tests - only during
# initial setup (handled by terminate+launch+openurl in the setup step).
# After loading, the developer menu shows "Continue" (clearState resets
# the "has been seen" preference). We tap it, then wait for login-screen.
# CRITICAL: Use 127.0.0.1 not localhost (macOS resolves localhost to IPv6 first)
cat > frontend-mobile/.maestro/helpers/launch-app.yaml << 'LAUNCH_EOF'
appId: host.exp.Exponent
---
- stopApp
- clearState
- openLink: "exp://127.0.0.1:8082"
- extendedWaitUntil:
visible: "Continue"
timeout: 120000
- tapOn: "Continue"
- extendedWaitUntil:
visible:
id: "login-screen"
timeout: 30000
LAUNCH_EOF
echo "=== Generated launch-app.yaml ==="
cat frontend-mobile/.maestro/helpers/launch-app.yaml
- name: Strip Expo owner for CI
working-directory: frontend-mobile
run: |
# Remove owner and eas config to avoid "Input is required" auth error in CI
# The owner field triggers Expo CLI to verify project ownership, which needs
# interactive login or EXPO_TOKEN that we don't have in CI
# Using node for safe AST-aware modification (sed breaks brace matching)
node << 'STRIP_EOF'
const fs = require('fs');
const config = require('./app.config.js');
delete config.owner;
if (config.extra) delete config.extra.eas;
const header = '// ABOUTME: Expo configuration for Pierre mobile app\n// ABOUTME: Uses Expo Go for development; CI-stripped owner/eas fields\n\n';
fs.writeFileSync('app.config.js', header + 'module.exports = ' + JSON.stringify(config, null, 2) + ';\n');
STRIP_EOF
echo "=== app.config.js (stripped owner/eas) ==="
grep -n "owner\|eas\|projectId" app.config.js || echo "Owner/eas fields removed successfully"
- name: Start Metro, install Expo Go, and open app
working-directory: frontend-mobile
run: |
# Use --ios to both start Metro AND install Expo Go in one process.
# The --ios flag will try to open exp://<network-IP>:8082 which fails
# (simulator can't reach the host's network IP), but Metro keeps running
# and Expo Go gets installed. We then override with 127.0.0.1.
echo "Starting Metro + installing Expo Go..."
npx expo start --go --ios --port 8082 2>&1 | tee /tmp/metro.log &
echo "METRO_PID=$!" >> $GITHUB_ENV
# Wait for Expo Go to be installed
echo "Waiting for Expo Go to be installed..."
for i in {1..90}; do
if xcrun simctl get_app_container "$SIMULATOR_UDID" host.exp.Exponent 2>/dev/null; then
echo "Expo Go is installed! (attempt $i)"
break
fi
if [ "$i" -eq 90 ]; then
echo "ERROR: Expo Go installation timed out!"
cat /tmp/metro.log
exit 1
fi
sleep 2
done
# The --ios flag triggers an "Open in Expo Go?" iOS dialog that blocks
# URL processing (nobody taps "Open" in CI). Terminate Expo Go to
# dismiss the dialog, then relaunch it so we can forward URLs without
# the dialog (URLs sent to an already-running app skip the dialog).
echo "Terminating Expo Go to dismiss system URL dialog..."
xcrun simctl terminate "$SIMULATOR_UDID" host.exp.Exponent || true
sleep 2
# Wait for Metro HTTP to be ready
echo "Waiting for Metro to be ready..."
for i in {1..90}; do
if curl -s http://127.0.0.1:8082 > /dev/null 2>&1; then
echo "Metro is ready! (attempt $i)"
break
fi
if [ "$i" -eq 90 ]; then
echo "ERROR: Metro failed to start within 180s!"
cat /tmp/metro.log
exit 1
fi
sleep 2
done
# Launch Expo Go first (opens to its home screen), THEN open the URL.
# When Expo Go is already running, openurl forwards the URL directly
# to the app without showing the iOS "Open in Expo Go?" dialog.
echo "Launching Expo Go on simulator $SIMULATOR_UDID..."
xcrun simctl launch "$SIMULATOR_UDID" host.exp.Exponent
sleep 3
echo "Opening deep link with 127.0.0.1..."
xcrun simctl openurl "$SIMULATOR_UDID" "exp://127.0.0.1:8082"
# Wait for the JS bundle to compile (can take 30s-5min depending on CI runner)
echo "Waiting for JS bundle to compile..."
for i in {1..180}; do
if grep -q "Bundled" /tmp/metro.log 2>/dev/null; then
echo "Bundle compiled! (waited ~$((i*2))s)"
break
fi
if [ "$i" -eq 180 ]; then
echo "ERROR: Bundle compilation timed out after 360s!"
cat /tmp/metro.log
exit 1
fi
sleep 2
done
# Give the app time to render after bundle loads
sleep 15
# Verify Metro is still alive
if curl -s http://127.0.0.1:8082 > /dev/null 2>&1; then
echo "Metro is still running - good!"
else
echo "ERROR: Metro died! Check /tmp/metro.log"
tail -30 /tmp/metro.log
exit 1
fi
echo "=== Metro log summary ==="
grep -iE "bundled|error|warn" /tmp/metro.log | tail -10 || echo "No relevant messages"
env:
EXPO_PUBLIC_API_URL: "http://127.0.0.1:8081"
- name: Debug - capture simulator state before tests
if: always()
run: |
echo "=== Simulator UDID: $SIMULATOR_UDID ==="
echo "=== Installed apps on simulator ==="
xcrun simctl listapps "$SIMULATOR_UDID" 2>/dev/null | grep -A2 "CFBundleIdentifier" | head -30 || echo "Failed to list apps"
echo ""
echo "=== Simulator screenshot ==="
xcrun simctl io "$SIMULATOR_UDID" screenshot /tmp/pre-maestro-screenshot.png || echo "Screenshot failed"
echo ""
echo "=== Running processes ==="
ps aux | grep -iE "expo|metro|node" | grep -v grep || echo "No relevant processes"
echo ""
echo "=== Check Metro ==="
curl -s http://127.0.0.1:8082 | head -20 || echo "Metro not responding"
echo ""
echo "=== Check Pierre server ==="
curl -s http://127.0.0.1:8081/health || echo "Server not responding"
echo ""
echo "=== Maestro hierarchy dump (what Maestro can see) ==="
$HOME/.maestro/bin/maestro --device "$SIMULATOR_UDID" hierarchy 2>&1 | head -100 || echo "Hierarchy dump failed"
echo ""
echo "=== Metro log (last 30 lines) ==="
tail -30 /tmp/metro.log 2>/dev/null || echo "No metro log"
echo ""
echo "=== Expo install log (last 20 lines) ==="
tail -20 /tmp/expo-install.log 2>/dev/null || echo "No expo install log"
echo ""
echo "=== Booted simulators ==="
xcrun simctl list devices booted 2>/dev/null || echo "Failed to list booted devices"
- name: Run Maestro tests
working-directory: frontend-mobile
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
echo "Running full E2E suite (manual trigger)..."
$HOME/.maestro/bin/maestro test --device "$SIMULATOR_UDID" .maestro/ --format junit --output maestro-results.xml
else
echo "Running CI smoke tests (push trigger)..."
$HOME/.maestro/bin/maestro test --device "$SIMULATOR_UDID" \
.maestro/login/ \
.maestro/chat/01-basic-visibility.yaml \
.maestro/coaches/01-basic-rendering.yaml \
.maestro/store/01-basic-rendering.yaml \
.maestro/settings/01-screen-rendering.yaml \
.maestro/social/01-friends-screen.yaml \
--format junit --output maestro-results.xml
fi
env:
TEST_EMAIL: "mobiletest@pierre.dev"
TEST_PASSWORD: "MobileTest123!"
BACKEND_URL: "http://localhost:8081"
- name: Upload Maestro test results
uses: actions/upload-artifact@v4
if: always()
with:
name: maestro-test-results-ios
path: |
frontend-mobile/maestro-results.xml
frontend-mobile/.maestro/
/tmp/pre-maestro-screenshot.png
/tmp/metro.log
/tmp/expo-install.log
retention-days: 7
mobile-e2e-maestro-android:
name: Mobile E2E Tests - Android (Maestro)
needs: build-server-linux
runs-on: ubuntu-latest
# Run on main, claude/* branches, or when explicitly triggered
if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/claude/') || github.event_name == 'workflow_dispatch'
timeout-minutes: 60
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Download server binaries
uses: actions/download-artifact@v4
with:
name: pierre-server-linux
path: target/release/
- name: Make binaries executable
run: chmod +x target/release/pierre-mcp-server target/release/pierre-cli
- name: Setup Java 17
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: "1.3.4"
- name: Install shared-types dependencies
working-directory: packages/shared-types
run: bun install
- name: Install api-client dependencies
working-directory: packages/api-client
run: bun install
- name: Remove expo-dev-client for E2E build
working-directory: frontend-mobile
run: |
# Remove expo-dev-client from package.json BEFORE install
# This package interferes with Maestro testing - its native code
# tries to connect to a dev server that doesn't exist in CI
cat package.json | jq 'del(.dependencies["expo-dev-client"])' > package.json.tmp
mv package.json.tmp package.json
- name: Install mobile dependencies
working-directory: frontend-mobile
run: bun install
- name: Strip Expo owner for CI
working-directory: frontend-mobile
run: |
# Remove owner and eas config to avoid "Input is required" auth error in CI
node << 'STRIP_EOF'
const fs = require('fs');
const config = require('./app.config.js');
delete config.owner;
if (config.extra) delete config.extra.eas;
const header = '// ABOUTME: Expo configuration for Pierre mobile app\n// ABOUTME: Uses Expo Go for development; CI-stripped owner/eas fields\n\n';
fs.writeFileSync('app.config.js', header + 'module.exports = ' + JSON.stringify(config, null, 2) + ';\n');
STRIP_EOF
echo "=== app.config.js (stripped owner/eas) ==="
grep -n "owner\|eas\|projectId" app.config.js || echo "Owner/eas fields removed successfully"
- name: Install Maestro CLI
run: |
curl -Ls "https://get.maestro.mobile.dev" | bash
echo "$HOME/.maestro/bin" >> $GITHUB_PATH
- name: Free disk space for emulator
run: |
sudo rm -rf /usr/share/dotnet
sudo rm -rf /usr/local/lib/android/sdk/ndk
sudo rm -rf /opt/ghc
sudo rm -rf /usr/local/share/boost
- name: Create data directory
run: mkdir -p data
- name: Start Pierre server
run: |
./target/release/pierre-mcp-server &
echo "Waiting for server to start..."
for i in {1..30}; do
if curl -s http://localhost:8081/health > /dev/null; then
echo "Server is healthy!"
break
fi
echo "Attempt $i: Server not ready yet..."
sleep 2
done
env:
DATABASE_URL: "sqlite:${{ github.workspace }}/data/maestro-android-test.db"
PIERRE_MASTER_ENCRYPTION_KEY: "rEFe91l6lqLahoyl9OSzum9dKa40VvV5RYj8bHGNTeo="
PIERRE_RSA_KEY_SIZE: "2048"
HTTP_PORT: "8081"
RUST_LOG: "warn"
STRAVA_CLIENT_ID: "test_client_id_ci"
STRAVA_CLIENT_SECRET: "test_client_secret_ci"
STRAVA_REDIRECT_URI: "http://localhost:8081/auth/strava/callback"
- name: Seed test user for Maestro tests
run: |
./target/release/pierre-cli user create --email mobiletest@pierre.dev --password MobileTest123! --force
env:
DATABASE_URL: "sqlite:${{ github.workspace }}/data/maestro-android-test.db"
PIERRE_MASTER_ENCRYPTION_KEY: "rEFe91l6lqLahoyl9OSzum9dKa40VvV5RYj8bHGNTeo="
RUST_LOG: "warn"
- name: Enable KVM for Android emulator
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: Adapt Maestro tests for Expo Go
run: |
# Replace native app ID with Android Expo Go package name (lowercase)
find frontend-mobile/.maestro -name "*.yaml" -exec sed -i 's/appId: com.pierre.fitness/appId: host.exp.exponent/g' {} +
# Override launch helper: clearState ensures fresh auth state.
# openLink launches Expo Go directly with the URL.
cat > frontend-mobile/.maestro/helpers/launch-app.yaml << 'LAUNCH_EOF'
appId: host.exp.exponent
---
- stopApp
- clearState
- openLink: "exp://10.0.2.2:8082"
- extendedWaitUntil:
visible: "Continue"
timeout: 120000
- tapOn: "Continue"
- extendedWaitUntil:
visible:
id: "login-screen"
timeout: 30000
LAUNCH_EOF
echo "=== Generated launch-app.yaml ==="
cat frontend-mobile/.maestro/helpers/launch-app.yaml
- name: Create Android E2E test script
run: |
cat > /tmp/run-android-e2e.sh << 'SCRIPT_EOF'
#!/bin/bash
set -e
cd frontend-mobile
npx expo start --go --android --port 8082 &
echo "Waiting for Metro bundler to be ready..."
for i in $(seq 1 90); do
if curl -s http://localhost:8082 > /dev/null 2>&1; then
echo "Metro is ready!"
break
fi
if [ "$i" -eq 90 ]; then
echo "Metro failed to start within timeout!"
exit 1
fi
echo "Attempt $i: Metro not ready yet..."
sleep 2
done
# Wait for Expo Go to install and load the initial bundle
sleep 30
if [ "$MAESTRO_FULL_SUITE" = "true" ]; then
echo "Running full E2E suite (manual trigger)..."
$HOME/.maestro/bin/maestro test .maestro/ --format junit --output maestro-results-android.xml
else
echo "Running CI smoke tests (push trigger)..."
$HOME/.maestro/bin/maestro test \
.maestro/login/ \
.maestro/chat/01-basic-visibility.yaml \
.maestro/coaches/01-basic-rendering.yaml \
.maestro/store/01-basic-rendering.yaml \
.maestro/settings/01-screen-rendering.yaml \
.maestro/social/01-friends-screen.yaml \
--format junit --output maestro-results-android.xml
fi
SCRIPT_EOF
chmod +x /tmp/run-android-e2e.sh
- name: Run Maestro tests on Android
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 33
target: google_apis
arch: x86_64
avd-name: maestro_test_emulator
force-avd-creation: true
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: true
emulator-boot-timeout: 600
disk-size: 4096M
script: bash /tmp/run-android-e2e.sh
env:
CI: "true"
EXPO_PUBLIC_API_URL: "http://10.0.2.2:8081"
TEST_EMAIL: "mobiletest@pierre.dev"
TEST_PASSWORD: "MobileTest123!"
BACKEND_URL: "http://10.0.2.2:8081"
MAESTRO_FULL_SUITE: ${{ github.event_name == 'workflow_dispatch' && 'true' || 'false' }}
- name: Upload Maestro test results
uses: actions/upload-artifact@v4
if: always()
with:
name: maestro-test-results-android
path: |
frontend-mobile/maestro-results-android.xml
frontend-mobile/.maestro/
retention-days: 7