From ab483c38d837f2c1690972103b462616bb0a8089 Mon Sep 17 00:00:00 2001 From: Masked-Kunsiquat <130736043+Masked-Kunsiquat@users.noreply.github.com> Date: Sun, 28 Dec 2025 02:01:06 -0500 Subject: [PATCH 1/5] feat: add E2E testing infrastructure with Maestro MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Set up comprehensive end-to-end testing and demo generation system using Maestro for automated UI testing and creating marketing materials. **Infrastructure:** - Add `.maestro/demo-flow.yaml` - 30-second feature showcase flow - Add E2E build profile to `eas.json` for Android/iOS test builds - Add npm scripts for E2E workflow (`e2e:build`, `e2e:test`, `e2e:demo`) **Documentation:** - Add `.maestro/README.md` - Complete guide (setup, usage, optimization) - Add `.maestro/QUICKSTART.md` - 3-step quick start guide - Update `CLAUDE.md` with E2E commands **Helper Scripts:** - Add `.maestro/convert-to-gif.sh` - Bash script for videoβ†’GIF conversion (macOS/Linux) - Add `.maestro/convert-to-gif.bat` - Windows batch script for conversion - Add `.maestro/.gitignore` - Exclude recordings, keep configs **Demo Flow Features:** - Quick trip creation demo ("Coffee Run") - Weekend Ski Trip sample data showcase - Expense browsing with scroll - Settlement recommendations display - Statistics with pie charts & participant breakdown - Participant detail view with toggle interaction - All element selectors based on actual UI code (no guessing!) 🎬 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- .maestro/.gitignore | 12 ++ .maestro/QUICKSTART.md | 188 +++++++++++++++++ .maestro/README.md | 392 ++++++++++++++++++++++++++++++++++++ .maestro/convert-to-gif.bat | 82 ++++++++ .maestro/convert-to-gif.sh | 103 ++++++++++ .maestro/demo-flow.yaml | 150 ++++++++++++++ CLAUDE.md | 19 ++ eas.json | 14 ++ package.json | 6 +- 9 files changed, 965 insertions(+), 1 deletion(-) create mode 100644 .maestro/.gitignore create mode 100644 .maestro/QUICKSTART.md create mode 100644 .maestro/README.md create mode 100644 .maestro/convert-to-gif.bat create mode 100644 .maestro/convert-to-gif.sh create mode 100644 .maestro/demo-flow.yaml diff --git a/.maestro/.gitignore b/.maestro/.gitignore new file mode 100644 index 00000000..eff720b6 --- /dev/null +++ b/.maestro/.gitignore @@ -0,0 +1,12 @@ +# Ignore Maestro recordings and temporary files +*.mp4 +*.mov +*.gif +*.avi + +# But keep the demo flow and documentation +!README.md +!QUICKSTART.md +!*.yaml +!*.sh +!*.bat diff --git a/.maestro/QUICKSTART.md b/.maestro/QUICKSTART.md new file mode 100644 index 00000000..a8495d9b --- /dev/null +++ b/.maestro/QUICKSTART.md @@ -0,0 +1,188 @@ +# E2E Demo Generation - Quick Start + +Generate a demo GIF of CrewSplit in 3 steps! 🎬 + +## Prerequisites + +1. **Install Maestro CLI** + ```bash + curl -Ls "https://get.maestro.mobile.dev" | bash + ``` + +2. **Install ffmpeg** (for GIF conversion) + ```bash + # macOS + brew install ffmpeg + + # Ubuntu/Debian + sudo apt install ffmpeg + + # Windows + choco install ffmpeg + ``` + +3. **Optional: Install gifsicle** (for optimization) + ```bash + brew install gifsicle # macOS + sudo apt install gifsicle # Ubuntu + ``` + +## Generate Demo (3 Steps) + +### Step 1: Build E2E App + +```bash +npm run e2e:build:android +``` + +This creates an optimized APK for testing. The build will be saved locally. + +### Step 2: Run Demo Flow with Recording + +```bash +npm run e2e:demo +``` + +This runs the demo flow and records a video. The recording is saved to `~/.maestro/tests/[timestamp]/recording.mp4`. + +### Step 3: Convert to GIF + +#### Option A: Using the helper script (recommended) + +```bash +# macOS/Linux +./.maestro/convert-to-gif.sh ~/.maestro/tests/latest/recording.mp4 + +# Windows +.maestro\convert-to-gif.bat %USERPROFILE%\.maestro\tests\latest\recording.mp4 +``` + +#### Option B: Manual conversion with ffmpeg + +```bash +ffmpeg -i recording.mp4 \ + -vf "fps=15,scale=720:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" \ + -loop 0 \ + demo.gif +``` + +**Done!** Your demo GIF is ready πŸŽ‰ + +--- + +## One-Liner (after initial setup) + +```bash +npm run e2e:demo && ./.maestro/convert-to-gif.sh +``` + +--- + +## Tips + +### Reduce GIF Size + +If your GIF is >10MB (GitHub limit): + +```bash +# Lower resolution (600px width) +./.maestro/convert-to-gif.sh recording.mp4 demo.gif 600 + +# Lower resolution (480px width) +./.maestro/convert-to-gif.sh recording.mp4 demo.gif 480 + +# OR trim the video first +ffmpeg -i recording.mp4 -ss 5 -t 20 trimmed.mp4 +./.maestro/convert-to-gif.sh trimmed.mp4 +``` + +### Speed Up/Slow Down + +```bash +# Speed up 1.5x +ffmpeg -i recording.mp4 -filter:v "setpts=0.67*PTS" -an recording-fast.mp4 +./.maestro/convert-to-gif.sh recording-fast.mp4 + +# Slow down 0.75x +ffmpeg -i recording.mp4 -filter:v "setpts=1.33*PTS" recording-slow.mp4 +./.maestro/convert-to-gif.sh recording-slow.mp4 +``` + +### Different Quality Levels + +```bash +# High quality (larger file) +ffmpeg -i recording.mp4 -vf "fps=20,scale=1080:-1" demo-hq.gif + +# Medium quality (recommended) +ffmpeg -i recording.mp4 -vf "fps=15,scale=720:-1" demo.gif + +# Low quality (smaller file) +ffmpeg -i recording.mp4 -vf "fps=10,scale=480:-1" demo-small.gif +``` + +--- + +## Troubleshooting + +### "maestro: command not found" + +Add Maestro to your PATH: + +```bash +export PATH="$PATH:$HOME/.maestro/bin" + +# Add to ~/.bashrc or ~/.zshrc to make permanent +echo 'export PATH="$PATH:$HOME/.maestro/bin"' >> ~/.zshrc +``` + +### "App not installed" + +Specify the APK path explicitly: + +```bash +maestro test .maestro/demo-flow.yaml --app ./path/to/app.apk +``` + +### "Element not found" errors + +The flow might need adjustment for your UI. Edit [.maestro/demo-flow.yaml](.maestro/demo-flow.yaml) and: +- Add `optional: true` to non-critical taps +- Increase `timeout` values +- Adjust element text/IDs to match your app + +### Recording file not found + +Check Maestro recordings directory: + +```bash +# Find latest recording +ls -lt ~/.maestro/tests/ | head -5 + +# On Windows +dir %USERPROFILE%\.maestro\tests /o-d +``` + +--- + +## Next Steps + +- **Customize the flow**: Edit [.maestro/demo-flow.yaml](.maestro/demo-flow.yaml) +- **Create variations**: Copy and modify for different scenarios +- **Full documentation**: See [README.md](README.md) for advanced options + +--- + +## Quick Reference + +| Task | Command | +|------|---------| +| Build E2E app | `npm run e2e:build:android` | +| Run demo flow | `npm run e2e:demo` | +| Convert to GIF | `./.maestro/convert-to-gif.sh recording.mp4` | +| Optimize GIF | `gifsicle -O3 --lossy=80 -o out.gif in.gif` | +| Test locally | `maestro test .maestro/demo-flow.yaml` | + +--- + +**🎬 Happy Demo-ing!** diff --git a/.maestro/README.md b/.maestro/README.md new file mode 100644 index 00000000..3bc15af1 --- /dev/null +++ b/.maestro/README.md @@ -0,0 +1,392 @@ +# CrewSplit E2E Testing & Demo Generation + +This directory contains Maestro flows for end-to-end testing and generating demo GIFs/videos of the CrewSplit app. + +## Overview + +- **Tool**: [Maestro](https://maestro.dev/) - Mobile UI testing framework +- **Purpose**: Automated E2E tests + Demo video/GIF generation +- **Platform Support**: Android (APK) & iOS (Simulator) + +## Quick Start + +### 1. Install Maestro CLI + +```bash +# macOS/Linux +curl -Ls "https://get.maestro.mobile.dev" | bash + +# Windows (via WSL) +curl -Ls "https://get.maestro.mobile.dev" | bash + +# Verify installation +maestro --version +``` + +### 2. Build the App for E2E + +```bash +# Build Android APK for E2E testing +eas build --profile e2e-test --platform android --local + +# Build iOS for simulator (macOS only) +eas build --profile e2e-test --platform ios --local +``` + +This creates optimized builds in the `e2e-test` profile configured in [eas.json](../eas.json). + +### 3. Run Maestro Flows Locally + +#### Option A: Run against local dev build + +```bash +# Start Expo dev server +npm start + +# In another terminal, run Maestro flow +maestro test .maestro/demo-flow.yaml +``` + +#### Option B: Run against APK/App build + +```bash +# Android +maestro test .maestro/demo-flow.yaml --app path/to/app.apk + +# iOS (simulator) +maestro test .maestro/demo-flow.yaml --app path/to/app.app +``` + +### 4. Record Demo Video/GIF + +Maestro automatically records screen during test execution: + +```bash +# Run with video recording (default format: .mp4) +maestro test .maestro/demo-flow.yaml --format mp4 + +# Recording will be saved to: ~/.maestro/tests/[timestamp]/recording.mp4 +``` + +## Available Flows + +### `demo-flow.yaml` + +**Purpose**: Complete feature showcase for marketing/documentation + +**Duration**: ~30 seconds at 1x speed + +**Showcases**: +- βœ… Quick trip creation UX +- βœ… Browsing expenses with sample data (Weekend Ski Trip) +- βœ… Settlement recommendations +- βœ… Statistics with pie charts & breakdowns +- βœ… Participant detail view +- βœ… Settlement detail modal + +**Ideal for**: README demos, social media, onboarding videos + +--- + +## Converting Video to GIF + +Maestro outputs `.mp4` videos by default. To create GIFs for GitHub/documentation: + +### Option 1: Using `ffmpeg` (Recommended) + +```bash +# Install ffmpeg +# macOS +brew install ffmpeg + +# Ubuntu/Debian +sudo apt install ffmpeg + +# Windows (via Chocolatey) +choco install ffmpeg + +# Convert MP4 to optimized GIF +ffmpeg -i recording.mp4 \ + -vf "fps=15,scale=720:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" \ + -loop 0 \ + demo.gif + +# For smaller file size (lower quality) +ffmpeg -i recording.mp4 \ + -vf "fps=10,scale=480:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" \ + -loop 0 \ + demo-small.gif +``` + +**Parameters explained**: +- `fps=15` - Frame rate (15fps = smooth, smaller than 30fps) +- `scale=720:-1` - Width 720px, height auto-scaled +- `loop=0` - Infinite loop +- `palettegen/paletteuse` - High-quality color optimization + +### Option 2: Using Online Tools + +1. **Ezgif.com** - https://ezgif.com/video-to-gif + - Upload `.mp4` + - Adjust size, frame rate, speed + - Download optimized GIF + +2. **CloudConvert** - https://cloudconvert.com/mp4-to-gif + - Batch conversion + - Quality presets + +### Option 3: Using Gifski (Best Quality) + +```bash +# Install gifski +brew install gifski + +# Convert with highest quality +gifski -o demo.gif recording.mp4 --fps 20 --quality 90 --width 720 +``` + +--- + +## GIF Optimization Tips + +### Target Specs for Demos + +| Platform | Max Size | Recommended Resolution | FPS | Duration | +|----------|----------|----------------------|-----|----------| +| GitHub README | 10MB | 720px width | 15-20 | <30s | +| Twitter/X | 15MB | 720px width | 20 | <30s | +| Discord | 8MB | 600px width | 15 | <20s | +| Documentation | 5MB | 600px width | 10-15 | <30s | + +### Reducing File Size + +**1. Lower FPS**: 10-15fps instead of 20+ +```bash +ffmpeg -i recording.mp4 -vf "fps=10,scale=720:-1" demo.gif +``` + +**2. Reduce dimensions**: 600px or 480px width +```bash +ffmpeg -i recording.mp4 -vf "fps=15,scale=600:-1" demo.gif +``` + +**3. Trim duration**: Cut to only essential parts +```bash +# Extract 5-25 second segment +ffmpeg -i recording.mp4 -ss 5 -t 20 -vf "fps=15,scale=720:-1" demo.gif +``` + +**4. Optimize with gifsicle**: +```bash +brew install gifsicle +gifsicle -O3 --lossy=80 -o demo-optimized.gif demo.gif +``` + +--- + +## Adjusting Demo Speed + +If the flow is too slow/fast: + +### Speed Up Video Before Converting + +```bash +# 1.5x speed +ffmpeg -i recording.mp4 -filter:v "setpts=0.67*PTS" -an recording-fast.mp4 + +# 2x speed +ffmpeg -i recording.mp4 -filter:v "setpts=0.5*PTS" -an recording-2x.mp4 + +# Then convert to GIF +ffmpeg -i recording-fast.mp4 -vf "fps=15,scale=720:-1" demo.gif +``` + +### Slow Down Video + +```bash +# 0.75x speed (slower) +ffmpeg -i recording.mp4 -filter:v "setpts=1.33*PTS" recording-slow.mp4 +``` + +--- + +## Running on EAS Cloud + +To run E2E tests in CI/CD with EAS: + +### 1. Create EAS Workflow + +Create `.eas/workflows/e2e-test.yml`: + +```yaml +build: + name: Build E2E Test APK + steps: + - eas/checkout + - eas/install_dependencies + - eas/prebuild + - eas/build: + profile: e2e-test + platform: android + +test: + name: Run Maestro E2E Tests + steps: + - eas/checkout + - eas/install_dependencies + - run: + name: Install Maestro CLI + command: | + curl -Ls "https://get.maestro.mobile.dev" | bash + export PATH="$PATH:$HOME/.maestro/bin" + - run: + name: Run demo flow + command: maestro test .maestro/demo-flow.yaml --app build-output.apk +``` + +### 2. Trigger on PR + +Configure trigger in workflow file: + +```yaml +on: + pull_request: + branches: [main] +``` + +--- + +## Customizing Flows + +### Adding New Test Scenarios + +Create new `.yaml` files in `.maestro/`: + +```bash +.maestro/ +β”œβ”€β”€ demo-flow.yaml # Main demo (30s) +β”œβ”€β”€ quick-demo.yaml # Short version (15s) +β”œβ”€β”€ settlement-focus.yaml # Only settlement features +└── multi-currency-demo.yaml # Multi-currency showcase +``` + +### Maestro Flow Syntax Reference + +```yaml +# Launch app +- launchApp + +# Tap on element (by text) +- tapOn: "Button Text" + +# Tap on element (by ID) +- tapOn: + id: "element-id" + +# Input text +- inputText: "Text to type" + +# Scroll +- scroll + +# Wait (milliseconds) +- wait: 1000 + +# Assert element is visible +- assertVisible: "Expected Text" + +# Navigate back +- back +``` + +**Full docs**: https://maestro.dev/reference/commands + +--- + +## Troubleshooting + +### "App not installed" error + +```bash +# Make sure app is built and path is correct +maestro test .maestro/demo-flow.yaml --app ./path/to/app.apk +``` + +### "Element not found" errors + +- Check element IDs/text in flow match actual UI +- Add `optional: true` for non-critical taps +- Increase `timeout` values for slow screens + +### Recording not saved + +```bash +# Check Maestro recordings directory +ls ~/.maestro/tests/ + +# Latest recording +ls -lt ~/.maestro/tests/ | head -5 +``` + +### GIF too large + +- Reduce resolution: `scale=480:-1` instead of `720:-1` +- Lower FPS: `fps=10` instead of `fps=15` +- Trim duration: Use `-ss` and `-t` flags +- Use lossy compression: `gifsicle --lossy=80` + +--- + +## Sample Data Setup + +The demo flow expects the **Weekend Ski Trip** sample to be loaded. To ensure consistent demos: + +### Auto-load Sample Data on App Launch (for E2E builds) + +Add to app initialization (e.g., `app/_layout.tsx`): + +```typescript +// Only for E2E/demo builds +if (process.env.DETOX_ENABLED === 'true') { + const { loadSampleTrip } = useSampleData(); + + useEffect(() => { + loadSampleTrip('weekend_ski_trip'); + }, []); +} +``` + +This ensures fresh sample data on every E2E run. + +--- + +## Resources + +- **Maestro Docs**: https://maestro.dev/ +- **Maestro CLI Reference**: https://maestro.dev/reference/cli +- **FFmpeg Docs**: https://ffmpeg.org/documentation.html +- **Gifski**: https://gif.ski/ +- **EAS Workflows**: https://docs.expo.dev/eas/workflows/ + +--- + +## Quick Reference: End-to-End Workflow + +```bash +# 1. Build E2E app +eas build --profile e2e-test --platform android --local + +# 2. Run Maestro flow with recording +maestro test .maestro/demo-flow.yaml --app app.apk + +# 3. Convert to GIF +ffmpeg -i ~/.maestro/tests/latest/recording.mp4 \ + -vf "fps=15,scale=720:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" \ + -loop 0 \ + demo.gif + +# 4. Optimize GIF +gifsicle -O3 --lossy=80 -o demo-optimized.gif demo.gif + +# Done! πŸŽ‰ +``` diff --git a/.maestro/convert-to-gif.bat b/.maestro/convert-to-gif.bat new file mode 100644 index 00000000..2952e666 --- /dev/null +++ b/.maestro/convert-to-gif.bat @@ -0,0 +1,82 @@ +@echo off +REM Convert Maestro recording to optimized GIF (Windows version) +REM Usage: convert-to-gif.bat [output.gif] [width] + +setlocal EnableDelayedExpansion + +REM Check if ffmpeg is installed +where ffmpeg >nul 2>&1 +if %errorlevel% neq 0 ( + echo [Warning] ffmpeg not found. Please install it: + echo choco install ffmpeg + echo OR download from: https://ffmpeg.org/download.html + exit /b 1 +) + +REM Parse arguments +set INPUT=%~1 +set OUTPUT=%~2 +set WIDTH=%~3 + +if "%OUTPUT%"=="" set OUTPUT=demo.gif +if "%WIDTH%"=="" set WIDTH=720 + +if "%INPUT%"=="" ( + echo Usage: %~nx0 ^ [output.gif] [width] + echo. + echo Examples: + echo %~nx0 recording.mp4 + echo %~nx0 recording.mp4 my-demo.gif + echo %~nx0 recording.mp4 my-demo.gif 600 + exit /b 1 +) + +if not exist "%INPUT%" ( + echo [Error] Input file not found: %INPUT% + exit /b 1 +) + +echo [Converting] Video to GIF... +echo Input: %INPUT% +echo Output: %OUTPUT% +echo Width: %WIDTH%px +echo. + +REM Convert with high-quality palette +ffmpeg -i "%INPUT%" -vf "fps=15,scale=%WIDTH%:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" -loop 0 "%OUTPUT%" -y + +if %errorlevel% neq 0 ( + echo [Error] Conversion failed + exit /b 1 +) + +REM Check if gifsicle is available +where gifsicle >nul 2>&1 +if %errorlevel% equ 0 ( + echo [Optimizing] GIF with gifsicle... + set TEMP_GIF=%OUTPUT%.tmp + move "%OUTPUT%" "!TEMP_GIF!" >nul + gifsicle -O3 --lossy=80 -o "%OUTPUT%" "!TEMP_GIF!" + del "!TEMP_GIF!" +) + +REM Get file size +for %%A in ("%OUTPUT%") do set SIZE=%%~zA +set /a SIZE_MB=!SIZE! / 1024 / 1024 + +echo. +echo [Success] GIF created successfully! +echo File: %OUTPUT% +echo Size: !SIZE_MB!MB + +if !SIZE_MB! gtr 10 ( + echo. + echo [Warning] GIF is !SIZE_MB!MB (GitHub limit is 10MB^) + echo. + echo To reduce size, try: + echo * Lower width: %~nx0 %INPUT% %OUTPUT% 600 + echo * Lower width: %~nx0 %INPUT% %OUTPUT% 480 +) + +echo. +echo [Done] Conversion complete! diff --git a/.maestro/convert-to-gif.sh b/.maestro/convert-to-gif.sh new file mode 100644 index 00000000..9947c5a7 --- /dev/null +++ b/.maestro/convert-to-gif.sh @@ -0,0 +1,103 @@ +#!/bin/bash +# Convert Maestro recording to optimized GIF +# Usage: ./convert-to-gif.sh [output.gif] [width] + +set -e + +# Colors for output +GREEN='\033[0;32m' +BLUE='\033[0;34m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Check if ffmpeg is installed +if ! command -v ffmpeg &> /dev/null; then + echo -e "${YELLOW}⚠️ ffmpeg not found. Please install it:${NC}" + echo " macOS: brew install ffmpeg" + echo " Ubuntu: sudo apt install ffmpeg" + echo " Windows: choco install ffmpeg" + exit 1 +fi + +# Parse arguments +INPUT="${1}" +OUTPUT="${2:-demo.gif}" +WIDTH="${3:-720}" + +if [ -z "$INPUT" ]; then + echo -e "${YELLOW}Usage: $0 [output.gif] [width]${NC}" + echo "" + echo "Examples:" + echo " $0 recording.mp4" + echo " $0 recording.mp4 my-demo.gif" + echo " $0 recording.mp4 my-demo.gif 600" + exit 1 +fi + +if [ ! -f "$INPUT" ]; then + echo -e "${YELLOW}❌ Input file not found: $INPUT${NC}" + + # Try to find latest Maestro recording + LATEST_RECORDING=$(find ~/.maestro/tests -name "recording.mp4" -type f -print0 2>/dev/null | xargs -0 ls -t 2>/dev/null | head -1) + + if [ -n "$LATEST_RECORDING" ]; then + echo -e "${BLUE}πŸ’‘ Found latest Maestro recording:${NC}" + echo " $LATEST_RECORDING" + echo "" + read -p "Use this file? (y/n) " -n 1 -r + echo + if [[ $REPLY =~ ^[Yy]$ ]]; then + INPUT="$LATEST_RECORDING" + else + exit 1 + fi + else + exit 1 + fi +fi + +echo -e "${BLUE}🎬 Converting video to GIF...${NC}" +echo " Input: $INPUT" +echo " Output: $OUTPUT" +echo " Width: ${WIDTH}px" +echo "" + +# Convert with high-quality palette +ffmpeg -i "$INPUT" \ + -vf "fps=15,scale=${WIDTH}:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" \ + -loop 0 \ + "$OUTPUT" \ + -y + +# Check if gifsicle is available for optimization +if command -v gifsicle &> /dev/null; then + echo -e "${BLUE}πŸ”§ Optimizing GIF with gifsicle...${NC}" + TEMP_GIF="${OUTPUT}.tmp" + mv "$OUTPUT" "$TEMP_GIF" + gifsicle -O3 --lossy=80 -o "$OUTPUT" "$TEMP_GIF" + rm "$TEMP_GIF" +fi + +# Get file size +SIZE=$(du -h "$OUTPUT" | cut -f1) + +echo "" +echo -e "${GREEN}βœ… GIF created successfully!${NC}" +echo " File: $OUTPUT" +echo " Size: $SIZE" +echo "" + +# Check if size is too large +SIZE_BYTES=$(stat -f%z "$OUTPUT" 2>/dev/null || stat -c%s "$OUTPUT" 2>/dev/null) +SIZE_MB=$((SIZE_BYTES / 1024 / 1024)) + +if [ $SIZE_MB -gt 10 ]; then + echo -e "${YELLOW}⚠️ Warning: GIF is ${SIZE_MB}MB (GitHub limit is 10MB)${NC}" + echo "" + echo "To reduce size, try:" + echo " β€’ Lower width: $0 $INPUT $OUTPUT 600" + echo " β€’ Lower width: $0 $INPUT $OUTPUT 480" + echo " β€’ Trim video: ffmpeg -i $INPUT -ss 5 -t 20 trimmed.mp4" +fi + +echo -e "${BLUE}πŸŽ‰ Done!${NC}" diff --git a/.maestro/demo-flow.yaml b/.maestro/demo-flow.yaml new file mode 100644 index 00000000..0ce01d12 --- /dev/null +++ b/.maestro/demo-flow.yaml @@ -0,0 +1,150 @@ +appId: com.crewsplit.app +--- +# CrewSplit Demo Flow - Full Feature Showcase +# Duration: ~30 seconds at 1x speed +# This flow demonstrates: trip creation, expense tracking, settlements, statistics, and participant details + +# ============================================ +# SCENE 1: Quick Create (5 seconds) +# ============================================ +- launchApp: + clearState: true + +# Wait for app to fully load with sample data +- waitForAnimationToEnd: + timeout: 5000 + +# Small delay to show home screen with sample trips +- wait: 1000 + +# Tap the "Create Trip" button in the footer +- tapOn: "Create Trip" + +# Wait for create trip screen to appear +- waitForAnimationToEnd + +# Tap the trip name input field (should auto-focus but tap to ensure) +- tapOn: "Trip name" + +# Type trip name +- inputText: "Coffee Run" + +# Tap the "Create Trip" button at bottom +- tapOn: "Create Trip" + +# Wait for navigation back to home +- waitForAnimationToEnd +- wait: 500 + +# ============================================ +# SCENE 2: Browse Rich Sample Data (15 seconds) +# ============================================ + +# Tap into the Weekend Ski Trip sample +- tapOn: "Weekend Ski Trip" + +# Wait for trip dashboard to load +- waitForAnimationToEnd +- wait: 1000 + +# Tap the "Expenses" action card +- tapOn: "Expenses" + +# Wait for expenses list to load +- waitForAnimationToEnd +- wait: 1000 + +# Scroll to show variety of expenses +- scroll + +# Wait to show scrolled content +- wait: 500 + +# Tap into first expense to show detail +- tapOn: + index: 0 +- wait: 1000 + +# Go back to expenses list +- back +- waitForAnimationToEnd + +# Go back to trip dashboard +- back +- waitForAnimationToEnd + +# Navigate to Settlement card +- tapOn: "Settlement" + +# Wait for settlement screen +- waitForAnimationToEnd +- wait: 1500 + +# ============================================ +# SCENE 3: Deep Insights - Statistics (8 seconds) +# ============================================ + +# Go back to dashboard +- back +- waitForAnimationToEnd + +# Navigate to Statistics card +- tapOn: "Statistics" + +# Wait for statistics screen with charts +- waitForAnimationToEnd + +# Pause to show pie chart and summary stats +- wait: 2000 + +# Scroll down to see participant breakdown +- scroll + +# Wait to show participant list +- wait: 1000 + +# Tap into first participant detail +- tapOn: + index: 0 + +# Wait for participant detail screen +- waitForAnimationToEnd +- wait: 2000 + +# Optional: Toggle between views +- tapOn: "Part Of" +- wait: 500 +- tapOn: "Paid By" +- wait: 500 + +# ============================================ +# SCENE 4: Settlement Detail (2 seconds) +# ============================================ + +# Navigate back to statistics +- back +- waitForAnimationToEnd + +# Go back to dashboard +- back +- waitForAnimationToEnd + +# Go to Settlement card again +- tapOn: "Settlement" +- waitForAnimationToEnd +- wait: 1000 + +# Tap on first settlement card +- tapOn: + index: 0 + +# Show settlement detail (with "Mark as Paid" button visible) +- wait: 1500 + +# Dismiss modal +- back + +# Final pause to show settlements list +- wait: 1000 + +# End of demo flow diff --git a/CLAUDE.md b/CLAUDE.md index 1c55d47d..f2cc0f89 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -52,6 +52,25 @@ The project uses automated GitHub workflows for versioning and releases: **Important**: Only version bumps trigger builds. Merging without changing version won't create tags or builds. +### E2E Testing & Demo Generation + +The project uses Maestro for end-to-end testing and creating demo videos/GIFs: + +```bash +npm run e2e:build:android # Build E2E test APK +npm run e2e:build:ios # Build E2E test app (iOS simulator) +npm run e2e:test # Run Maestro test flow +npm run e2e:demo # Run flow with video recording +``` + +**Quick demo generation**: +```bash +npm run e2e:demo +./.maestro/convert-to-gif.sh # Convert recording to optimized GIF +``` + +See [.maestro/QUICKSTART.md](.maestro/QUICKSTART.md) for full guide. + ## Architecture Principles ### 1. Agent-Based Role System diff --git a/eas.json b/eas.json index 0e3b94a5..4ea2b1f1 100644 --- a/eas.json +++ b/eas.json @@ -16,6 +16,20 @@ "android": { "buildType": "apk" } + }, + "e2e-test": { + "distribution": "internal", + "android": { + "buildType": "apk", + "gradleCommand": ":app:assembleRelease" + }, + "ios": { + "simulator": true, + "buildConfiguration": "Release" + }, + "env": { + "DETOX_ENABLED": "true" + } } }, "submit": { diff --git a/package.json b/package.json index 40f5c465..155fbe65 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,11 @@ "verify-migrations": "node scripts/verify-migrations.js", "arch-test": "dependency-cruiser --config .dependency-cruiser.js --output-type err src", "arch-test:graph": "dependency-cruiser --config .dependency-cruiser.js --output-type dot src | dot -T svg > architecture-graph.svg", - "arch-test:html": "dependency-cruiser --config .dependency-cruiser.js --output-type err-html --output-to architecture-report.html src" + "arch-test:html": "dependency-cruiser --config .dependency-cruiser.js --output-type err-html --output-to architecture-report.html src", + "e2e:build:android": "eas build --profile e2e-test --platform android --local", + "e2e:build:ios": "eas build --profile e2e-test --platform ios --local", + "e2e:test": "maestro test .maestro/demo-flow.yaml", + "e2e:demo": "maestro test .maestro/demo-flow.yaml --format mp4" }, "dependencies": { "@expo/vector-icons": "^15.0.3", From e82cfe25bf1bfdf4156d8e85af7f709ae6a34cbe Mon Sep 17 00:00:00 2001 From: Masked-Kunsiquat <130736043+Masked-Kunsiquat@users.noreply.github.com> Date: Sun, 28 Dec 2025 23:38:43 -0500 Subject: [PATCH 2/5] feat: configure E2E tests to run on EAS cloud MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update E2E infrastructure to run Maestro tests on EAS cloud instead of requiring local Docker builds. **EAS Workflow Configuration:** - Add `.eas/workflows/e2e-test-android.yml` - Workflow for automated E2E testing - Tests trigger automatically on PRs to `main` or `expo-e2e` branches - Can be manually triggered via `npm run e2e:cloud` - No Docker or local builds required - runs entirely on EAS infrastructure **Build Profile Updates:** - Update `eas.json` e2e-test profile with `withoutCredentials: true` for cloud execution - Simplify profile by removing unnecessary gradle commands and distribution settings - Change env var from `DETOX_ENABLED` to `E2E_ENABLED` for clarity **Package Scripts:** - Replace `e2e:build:android`/`e2e:build:ios` with `e2e:cloud` (cloud execution) - Rename `e2e:test` to `e2e:local` (local Maestro execution) - Keep `e2e:demo` for local video recording **Documentation Updates:** - Update `.maestro/README.md` with EAS cloud workflow instructions - Update `.maestro/QUICKSTART.md` to prioritize cloud execution - Update `CLAUDE.md` with new E2E commands and cloud-first approach - Emphasize "no Docker required" throughout docs **Key Benefits:** - βœ… No Docker Desktop required (addresses resource concerns) - βœ… No local builds needed - βœ… Automatic execution on pull requests - βœ… Integrated with EAS dashboard for results πŸ€– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- .eas/workflows/e2e-test-android.yml | 22 +++++ .maestro/QUICKSTART.md | 47 ++++++++--- .maestro/README.md | 125 +++++++++++++++------------- CLAUDE.md | 21 +++-- eas.json | 10 +-- package.json | 5 +- 6 files changed, 142 insertions(+), 88 deletions(-) create mode 100644 .eas/workflows/e2e-test-android.yml diff --git a/.eas/workflows/e2e-test-android.yml b/.eas/workflows/e2e-test-android.yml new file mode 100644 index 00000000..057f9386 --- /dev/null +++ b/.eas/workflows/e2e-test-android.yml @@ -0,0 +1,22 @@ +name: e2e-test-android + +on: + pull_request: + branches: ['main', 'expo-e2e'] + workflow_dispatch: # Allow manual trigger + +jobs: + build_android_for_e2e: + name: Build Android APK for E2E + type: build + params: + platform: android + profile: e2e-test + + maestro_test: + name: Run Maestro E2E Tests + needs: [build_android_for_e2e] + type: maestro + params: + build_id: ${{ needs.build_android_for_e2e.outputs.build_id }} + flow_path: ['.maestro/demo-flow.yaml'] diff --git a/.maestro/QUICKSTART.md b/.maestro/QUICKSTART.md index a8495d9b..f2af25ea 100644 --- a/.maestro/QUICKSTART.md +++ b/.maestro/QUICKSTART.md @@ -1,8 +1,28 @@ -# E2E Demo Generation - Quick Start +# E2E Testing - Quick Start -Generate a demo GIF of CrewSplit in 3 steps! 🎬 +Run CrewSplit E2E tests on EAS cloud or generate demo GIFs locally! 🎬 -## Prerequisites +## Option A: Run Tests on EAS Cloud (Recommended) + +**No setup required!** Tests run automatically on pull requests. + +### Manual Trigger + +```bash +# Run E2E workflow on EAS cloud +npx eas-cli@latest workflow:run .eas/workflows/e2e-test-android.yml +``` + +**Benefits:** +- βœ… No Docker, no local builds +- βœ… Runs entirely in the cloud +- βœ… Automatic on PRs to `main` or `expo-e2e` + +--- + +## Option B: Generate Demo GIF Locally + +### Prerequisites 1. **Install Maestro CLI** ```bash @@ -27,20 +47,20 @@ Generate a demo GIF of CrewSplit in 3 steps! 🎬 sudo apt install gifsicle # Ubuntu ``` -## Generate Demo (3 Steps) +## Generate Demo (2 Steps) -### Step 1: Build E2E App +### Step 1: Run Against Dev Build ```bash -npm run e2e:build:android +# Start Expo +npm start ``` -This creates an optimized APK for testing. The build will be saved locally. - ### Step 2: Run Demo Flow with Recording ```bash -npm run e2e:demo +# In another terminal, run Maestro flow +maestro test .maestro/demo-flow.yaml --format mp4 ``` This runs the demo flow and records a video. The recording is saved to `~/.maestro/tests/[timestamp]/recording.mp4`. @@ -70,10 +90,15 @@ ffmpeg -i recording.mp4 \ --- -## One-Liner (after initial setup) +## Quick Commands ```bash -npm run e2e:demo && ./.maestro/convert-to-gif.sh +# Run E2E tests on EAS cloud +npx eas-cli@latest workflow:run .eas/workflows/e2e-test-android.yml + +# Generate demo GIF locally +npm start # Terminal 1 +maestro test .maestro/demo-flow.yaml --format mp4 && ./.maestro/convert-to-gif.sh # Terminal 2 ``` --- diff --git a/.maestro/README.md b/.maestro/README.md index 3bc15af1..76f74f8b 100644 --- a/.maestro/README.md +++ b/.maestro/README.md @@ -10,7 +10,24 @@ This directory contains Maestro flows for end-to-end testing and generating demo ## Quick Start -### 1. Install Maestro CLI +### Option A: Run on EAS Cloud (Recommended - No Docker!) + +**Automatic on Pull Requests:** +E2E tests run automatically when you create a PR to `main` or `expo-e2e` branches. + +**Manual Trigger:** +```bash +# Run E2E test workflow on EAS cloud +npx eas-cli@latest workflow:run .eas/workflows/e2e-test-android.yml +``` + +EAS handles everything: builds the APK, runs Maestro tests, and provides results. No local setup needed! + +--- + +### Option B: Run Locally (For Demo Recording) + +**1. Install Maestro CLI** ```bash # macOS/Linux @@ -23,21 +40,7 @@ curl -Ls "https://get.maestro.mobile.dev" | bash maestro --version ``` -### 2. Build the App for E2E - -```bash -# Build Android APK for E2E testing -eas build --profile e2e-test --platform android --local - -# Build iOS for simulator (macOS only) -eas build --profile e2e-test --platform ios --local -``` - -This creates optimized builds in the `e2e-test` profile configured in [eas.json](../eas.json). - -### 3. Run Maestro Flows Locally - -#### Option A: Run against local dev build +**2. Run Against Dev Build** ```bash # Start Expo dev server @@ -47,17 +50,7 @@ npm start maestro test .maestro/demo-flow.yaml ``` -#### Option B: Run against APK/App build - -```bash -# Android -maestro test .maestro/demo-flow.yaml --app path/to/app.apk - -# iOS (simulator) -maestro test .maestro/demo-flow.yaml --app path/to/app.app -``` - -### 4. Record Demo Video/GIF +**3. Record Demo Video** Maestro automatically records screen during test execution: @@ -212,48 +205,60 @@ ffmpeg -i recording.mp4 -filter:v "setpts=1.33*PTS" recording-slow.mp4 ## Running on EAS Cloud -To run E2E tests in CI/CD with EAS: +### Workflow Configuration -### 1. Create EAS Workflow - -Create `.eas/workflows/e2e-test.yml`: +EAS workflows are defined in [`.eas/workflows/e2e-test-android.yml`](../.eas/workflows/e2e-test-android.yml): ```yaml -build: - name: Build E2E Test APK - steps: - - eas/checkout - - eas/install_dependencies - - eas/prebuild - - eas/build: - profile: e2e-test - platform: android - -test: - name: Run Maestro E2E Tests - steps: - - eas/checkout - - eas/install_dependencies - - run: - name: Install Maestro CLI - command: | - curl -Ls "https://get.maestro.mobile.dev" | bash - export PATH="$PATH:$HOME/.maestro/bin" - - run: - name: Run demo flow - command: maestro test .maestro/demo-flow.yaml --app build-output.apk +name: e2e-test-android + +on: + pull_request: + branches: ['main', 'expo-e2e'] + workflow_dispatch: # Allow manual trigger + +jobs: + build_android_for_e2e: + name: Build Android APK for E2E + type: build + params: + platform: android + profile: e2e-test + + maestro_test: + name: Run Maestro E2E Tests + needs: [build_android_for_e2e] + type: maestro + params: + build_id: ${{ needs.build_android_for_e2e.outputs.build_id }} + flow_path: ['.maestro/demo-flow.yaml'] ``` -### 2. Trigger on PR +### How It Works -Configure trigger in workflow file: +1. **Trigger**: Workflow runs automatically on PRs or manually via CLI +2. **Build**: EAS builds the Android APK using the `e2e-test` profile +3. **Test**: Maestro runs `demo-flow.yaml` on EAS cloud infrastructure +4. **Results**: Test results appear in the EAS dashboard -```yaml -on: - pull_request: - branches: [main] +### Manual Execution + +```bash +# Run workflow manually +npx eas-cli@latest workflow:run .eas/workflows/e2e-test-android.yml + +# View workflow status +npx eas-cli@latest workflow:list ``` +### Benefits of EAS Cloud + +- βœ… **No Docker required** - EAS handles infrastructure +- βœ… **No local builds** - Runs entirely in the cloud +- βœ… **Automatic on PRs** - Catches regressions before merge +- βœ… **Parallel execution** - Can run multiple tests simultaneously +- βœ… **Built-in reporting** - Results integrated with EAS dashboard + --- ## Customizing Flows diff --git a/CLAUDE.md b/CLAUDE.md index f2cc0f89..ca82d2c1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -54,19 +54,24 @@ The project uses automated GitHub workflows for versioning and releases: ### E2E Testing & Demo Generation -The project uses Maestro for end-to-end testing and creating demo videos/GIFs: +The project uses Maestro for end-to-end testing on EAS cloud (no Docker required!): ```bash -npm run e2e:build:android # Build E2E test APK -npm run e2e:build:ios # Build E2E test app (iOS simulator) -npm run e2e:test # Run Maestro test flow -npm run e2e:demo # Run flow with video recording +npm run e2e:cloud # Run E2E tests on EAS cloud (recommended) +npm run e2e:local # Run Maestro flow locally +npm run e2e:demo # Run locally with video recording ``` -**Quick demo generation**: +**EAS Cloud Testing** (automatic on PRs): +- Tests run automatically on pull requests to `main` or `expo-e2e` +- No local setup, Docker, or builds needed +- Results appear in EAS dashboard + +**Local Demo Generation**: ```bash -npm run e2e:demo -./.maestro/convert-to-gif.sh # Convert recording to optimized GIF +npm start # Terminal 1 +npm run e2e:demo # Terminal 2 +./.maestro/convert-to-gif.sh # Convert to GIF ``` See [.maestro/QUICKSTART.md](.maestro/QUICKSTART.md) for full guide. diff --git a/eas.json b/eas.json index 4ea2b1f1..48fb84ea 100644 --- a/eas.json +++ b/eas.json @@ -18,17 +18,15 @@ } }, "e2e-test": { - "distribution": "internal", + "withoutCredentials": true, "android": { - "buildType": "apk", - "gradleCommand": ":app:assembleRelease" + "buildType": "apk" }, "ios": { - "simulator": true, - "buildConfiguration": "Release" + "simulator": true }, "env": { - "DETOX_ENABLED": "true" + "E2E_ENABLED": "true" } } }, diff --git a/package.json b/package.json index 155fbe65..554519c6 100644 --- a/package.json +++ b/package.json @@ -14,9 +14,8 @@ "arch-test": "dependency-cruiser --config .dependency-cruiser.js --output-type err src", "arch-test:graph": "dependency-cruiser --config .dependency-cruiser.js --output-type dot src | dot -T svg > architecture-graph.svg", "arch-test:html": "dependency-cruiser --config .dependency-cruiser.js --output-type err-html --output-to architecture-report.html src", - "e2e:build:android": "eas build --profile e2e-test --platform android --local", - "e2e:build:ios": "eas build --profile e2e-test --platform ios --local", - "e2e:test": "maestro test .maestro/demo-flow.yaml", + "e2e:cloud": "npx eas-cli@latest workflow:run .eas/workflows/e2e-test-android.yml", + "e2e:local": "maestro test .maestro/demo-flow.yaml", "e2e:demo": "maestro test .maestro/demo-flow.yaml --format mp4" }, "dependencies": { From 229366be1fdb40771ec786c61e030fcce31eaabe Mon Sep 17 00:00:00 2001 From: Masked-Kunsiquat <130736043+Masked-Kunsiquat@users.noreply.github.com> Date: Sun, 28 Dec 2025 23:44:23 -0500 Subject: [PATCH 3/5] fix: correct workflow_dispatch syntax in EAS workflow --- .eas/workflows/e2e-test-android.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.eas/workflows/e2e-test-android.yml b/.eas/workflows/e2e-test-android.yml index 057f9386..b20e5736 100644 --- a/.eas/workflows/e2e-test-android.yml +++ b/.eas/workflows/e2e-test-android.yml @@ -3,7 +3,7 @@ name: e2e-test-android on: pull_request: branches: ['main', 'expo-e2e'] - workflow_dispatch: # Allow manual trigger + workflow_dispatch: {} jobs: build_android_for_e2e: From 077d6aaceaa5aa1e381cb9854b21bb57481cc6ee Mon Sep 17 00:00:00 2001 From: Masked-Kunsiquat <130736043+Masked-Kunsiquat@users.noreply.github.com> Date: Sun, 28 Dec 2025 23:53:53 -0500 Subject: [PATCH 4/5] fix: remove invalid wait commands from Maestro flow Maestro doesn't support 'wait: ' syntax. Removed all wait commands and rely on waitForAnimationToEnd instead, which is the recommended Maestro approach. --- .maestro/demo-flow.yaml | 29 +---------------------------- 1 file changed, 1 insertion(+), 28 deletions(-) diff --git a/.maestro/demo-flow.yaml b/.maestro/demo-flow.yaml index 0ce01d12..8a555ced 100644 --- a/.maestro/demo-flow.yaml +++ b/.maestro/demo-flow.yaml @@ -14,9 +14,6 @@ appId: com.crewsplit.app - waitForAnimationToEnd: timeout: 5000 -# Small delay to show home screen with sample trips -- wait: 1000 - # Tap the "Create Trip" button in the footer - tapOn: "Create Trip" @@ -34,7 +31,6 @@ appId: com.crewsplit.app # Wait for navigation back to home - waitForAnimationToEnd -- wait: 500 # ============================================ # SCENE 2: Browse Rich Sample Data (15 seconds) @@ -45,25 +41,19 @@ appId: com.crewsplit.app # Wait for trip dashboard to load - waitForAnimationToEnd -- wait: 1000 # Tap the "Expenses" action card - tapOn: "Expenses" # Wait for expenses list to load - waitForAnimationToEnd -- wait: 1000 # Scroll to show variety of expenses - scroll -# Wait to show scrolled content -- wait: 500 - # Tap into first expense to show detail - tapOn: index: 0 -- wait: 1000 # Go back to expenses list - back @@ -78,7 +68,6 @@ appId: com.crewsplit.app # Wait for settlement screen - waitForAnimationToEnd -- wait: 1500 # ============================================ # SCENE 3: Deep Insights - Statistics (8 seconds) @@ -94,28 +83,19 @@ appId: com.crewsplit.app # Wait for statistics screen with charts - waitForAnimationToEnd -# Pause to show pie chart and summary stats -- wait: 2000 - # Scroll down to see participant breakdown - scroll -# Wait to show participant list -- wait: 1000 - # Tap into first participant detail - tapOn: index: 0 # Wait for participant detail screen - waitForAnimationToEnd -- wait: 2000 -# Optional: Toggle between views +# Toggle between views - tapOn: "Part Of" -- wait: 500 - tapOn: "Paid By" -- wait: 500 # ============================================ # SCENE 4: Settlement Detail (2 seconds) @@ -132,19 +112,12 @@ appId: com.crewsplit.app # Go to Settlement card again - tapOn: "Settlement" - waitForAnimationToEnd -- wait: 1000 # Tap on first settlement card - tapOn: index: 0 -# Show settlement detail (with "Mark as Paid" button visible) -- wait: 1500 - # Dismiss modal - back -# Final pause to show settlements list -- wait: 1000 - # End of demo flow From ccce9d915a5c6f721abc4e695dd4d93018f99c16 Mon Sep 17 00:00:00 2001 From: Masked-Kunsiquat <130736043+Masked-Kunsiquat@users.noreply.github.com> Date: Mon, 29 Dec 2025 15:43:36 -0500 Subject: [PATCH 5/5] stuff --- .eas/workflows/e2e-test-android.yml | 6 +- .maestro/local-setup.md | 101 ++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 .maestro/local-setup.md diff --git a/.eas/workflows/e2e-test-android.yml b/.eas/workflows/e2e-test-android.yml index b20e5736..d76e5aca 100644 --- a/.eas/workflows/e2e-test-android.yml +++ b/.eas/workflows/e2e-test-android.yml @@ -1,9 +1,11 @@ name: e2e-test-android on: - pull_request: - branches: ['main', 'expo-e2e'] + # Only manual trigger - saves EAS build quota workflow_dispatch: {} + # Uncomment below to auto-run on PRs (uses build quota): + # pull_request: + # branches: ['main', 'expo-e2e'] jobs: build_android_for_e2e: diff --git a/.maestro/local-setup.md b/.maestro/local-setup.md new file mode 100644 index 00000000..2f25e4ec --- /dev/null +++ b/.maestro/local-setup.md @@ -0,0 +1,101 @@ +# Local Maestro Testing Setup + +## One-Time Setup + +### 1. Build the APK + +```bash +# Generate native Android project +npx expo prebuild --platform android --clean + +# Build release APK +cd android && ./gradlew assembleRelease +``` + +The APK will be at: `android/app/build/outputs/apk/release/app-release.apk` + +### 2. Install on Your Phone + +```bash +# Install via ADB +adb install android/app/build/outputs/apk/release/app-release.apk + +# Or manually: +# - Copy APK to phone +# - Open file manager on phone +# - Tap APK to install +``` + +--- + +## Running Tests Locally + +Once the APK is installed: + +```bash +# Make sure phone is connected via ADB +adb devices + +# Run Maestro test +npm run e2e:local + +# Record demo video +npm run e2e:demo +``` + +--- + +## Rebuilding After Code Changes + +When you change the app code: + +```bash +# Quick rebuild (if android/ folder exists) +cd android && ./gradlew assembleRelease + +# Then reinstall +adb install -r android/app/build/outputs/apk/release/app-release.apk +``` + +**Note:** The `-r` flag reinstalls without uninstalling first, preserving app data. + +--- + +## Troubleshooting + +### "App not found" + +Make sure the APK is installed: +```bash +adb shell pm list packages | grep crewsplit +``` + +Should show: `package:com.crewsplit.app` + +### "Device not found" + +Enable USB debugging on your phone and connect via ADB: +```bash +adb devices +``` + +### Build errors + +Clean and rebuild: +```bash +cd android +./gradlew clean +./gradlew assembleRelease +``` + +--- + +## Alternative: Use EAS Cloud + +To save local build time, use EAS cloud (manual trigger only to save quota): + +```bash +npm run e2e:cloud +``` + +This builds and tests on EAS infrastructure (uses 1 build from your quota).