- Replace browse tab with Supabase-connected explore tab with filters - Add React Query for data fetching with loading states - Add 3 structured programs with weekly progression - Add Supabase anonymous auth sync service - Add PostHog analytics with screen tracking and events - Add comprehensive test strategy (Vitest + Maestro E2E) - Add RevenueCat subscription system with DEV simulation - Add i18n translations for new screens (EN/FR/DE/ES) - Add data deletion modal, sync consent modal - Add assessment screen and program routes - Add GitHub Actions CI workflow - Update activity store with sync integration
14 KiB
14 KiB
Maestro E2E Testing Strategy for TabataFit
Executive Summary
Maestro is a mobile UI testing framework that uses YAML-based test flows. It's ideal for TabataFit because:
- ✅ Declarative YAML syntax (no code required)
- ✅ Built-in support for React Native
- ✅ Handles animations and async operations gracefully
- ✅ Excellent for regression testing critical user flows
- ✅ Can run on physical devices and simulators
Prerequisites
Before implementing these tests, ensure the following features are complete:
Required Features (Implement First)
- Onboarding flow (5 screens + paywall)
- Workout player with timer controls
- Browse/Workouts tab with workout cards
- Category filtering (Full Body, Core, Cardio, etc.)
- Collections feature
- Trainer profiles
- Subscription/paywall integration
- Workout completion screen
- Profile/settings screen
Nice to Have (Can Add Later)
- Activity history tracking
- Offline mode support
- Deep linking
- Push notifications
Priority Test Flows
P0 - Critical Flows (Must Test Every Release)
- Onboarding → First Workout
- Browse → Select Workout → Play → Complete
- Subscription Purchase Flow
- Background/Foreground During Workout
P1 - High Priority
- Category Filtering
- Collection Navigation
- Trainer Workout Discovery
- Profile Settings & Data Persistence
P2 - Medium Priority
- Activity History Tracking
- Offline Mode Behavior
- Deep Linking
- Push Notifications
Test Suite Structure
.maestro/
├── config.yaml # Global test configuration
├── flows/
│ ├── critical/ # P0 flows - Run on every PR
│ │ ├── onboarding.yaml
│ │ ├── workoutComplete.yaml
│ │ └── subscription.yaml
│ ├── core/ # P1 flows - Run before release
│ │ ├── browseAndPlay.yaml
│ │ ├── categoryFilter.yaml
│ │ ├── collections.yaml
│ │ └── trainers.yaml
│ └── regression/ # P2 flows - Run nightly
│ ├── activityHistory.yaml
│ ├── offlineMode.yaml
│ └── settings.yaml
├── helpers/
│ ├── common.yaml # Shared test steps
│ ├── assertions.yaml # Custom assertions
│ └── mock-data.yaml # Test data
└── environments/
├── staging.yaml
└── production.yaml
Installation & Setup
1. Install Maestro CLI
# macOS/Linux
curl -Ls "https://get.maestro.mobile.dev" | bash
# Verify installation
maestro --version
2. Setup Test Directory Structure
mkdir -p .maestro/flows/{critical,core,regression}
mkdir -p .maestro/helpers
mkdir -p .maestro/environments
3. Maestro Configuration (config.yaml)
# .maestro/config.yaml
appId: com.tabatafit.app
name: TabataFit E2E Tests
# Timeouts
timeout: 30000 # 30 seconds default
retries: 2
# Environment variables
env:
TEST_USER_NAME: "Test User"
TEST_USER_EMAIL: "test@example.com"
# Include flows
include:
- flows/critical/*.yaml
- flows/core/*.yaml
# Exclude on CI
exclude:
- flows/regression/offlineMode.yaml # Requires airplane mode
P0 Critical Test Flows
Test 1: Complete Onboarding Flow
File: .maestro/flows/critical/onboarding.yaml
appId: com.tabatafit.app
name: Complete Onboarding & First Workout
onFlowStart:
- clearState
steps:
# Splash/Loading
- waitForAnimationToEnd:
timeout: 5000
# Screen 1: Problem - "Not Enough Time"
- assertVisible: "Not enough time"
- tapOn: "Continue"
# Screen 2: Empathy
- assertVisible: "We get it"
- tapOn: "Continue"
# Screen 3: Solution
- assertVisible: "4-minute workouts"
- tapOn: "Continue"
# Screen 4: Wow Moment
- assertVisible: "Transform your body"
- tapOn: "Get Started"
# Screen 5: Personalization
- tapOn: "Name input"
- inputText: "Test User"
- tapOn: "Beginner"
- tapOn: "Lose Weight"
- tapOn: "3 times per week"
- tapOn: "Start My Journey"
# Screen 6: Paywall (or skip in test env)
- runScript: |
if (maestro.env.SKIP_PAYWALL === 'true') {
maestro.tapOn('Maybe Later');
}
# Should land on Home
- assertVisible: "Good morning|Good afternoon|Good evening"
- assertVisible: "Test User"
onFlowComplete:
- takeScreenshot: "onboarding-complete"
Test 2: Browse, Select, and Complete Workout
File: .maestro/flows/critical/workoutComplete.yaml
appId: com.tabatafit.app
name: Browse, Play & Complete Workout
steps:
# Navigate to Workouts tab
- tapOn: "Workouts"
- waitForAnimationToEnd
# Wait for data to load
- assertVisible: "All|Full Body|Core|Upper Body"
# Select first workout
- tapOn:
id: "workout-card-0"
optional: false
# Workout Detail Screen
- assertVisible: "Start Workout"
- tapOn: "Start Workout"
# Player Screen
- waitForAnimationToEnd:
timeout: 3000
# Verify timer is running
- assertVisible: "Get Ready|WORK|REST"
# Fast-forward through workout (simulation)
- repeat:
times: 3
commands:
- waitForAnimationToEnd:
timeout: 5000
- assertVisible: "WORK|REST"
# Complete workout
- tapOn:
id: "done-button"
optional: true
# Complete Screen
- assertVisible: "Workout Complete|Great Job"
- assertVisible: "Calories"
- assertVisible: "Duration"
# Return to home
- tapOn: "Done|Continue"
- assertVisible: "Home|Workouts"
onFlowComplete:
- takeScreenshot: "workout-completed"
Test 3: Subscription Purchase Flow
File: .maestro/flows/critical/subscription.yaml
appId: com.tabatafit.app
name: Subscription Purchase Flow
steps:
# Trigger paywall (via profile or workout limit)
- tapOn: "Profile"
- tapOn: "Upgrade to Premium"
# Paywall Screen
- assertVisible: "Unlock Everything|Premium"
- assertVisible: "yearly|monthly"
# Select plan
- tapOn:
id: "yearly-plan"
# Verify Apple Pay/Google Pay sheet appears
- assertVisible: "Subscribe|Confirm"
# Note: Actual purchase is mocked in test env
- runScript: |
if (maestro.env.USE_MOCK_PURCHASE === 'true') {
maestro.tapOn('Mock Purchase Success');
}
# Verify premium activated
- assertVisible: "Premium Active|You're all set"
tags:
- purchase
- revenue-critical
P1 Core Test Flows
Test 4: Category Filtering
File: .maestro/flows/core/categoryFilter.yaml
appId: com.tabatafit.app
name: Category Filtering
steps:
- tapOn: "Workouts"
- waitForAnimationToEnd
# Test each category
- tapOn: "Full Body"
- assertVisible: "Full Body"
- tapOn: "Core"
- assertVisible: "Core"
- tapOn: "Cardio"
- assertVisible: "Cardio"
- tapOn: "All"
- assertVisible: "All Workouts"
# Verify filter changes content
- runScript: |
const before = maestro.getElementText('workout-count');
maestro.tapOn('Core');
const after = maestro.getElementText('workout-count');
assert(before !== after, 'Filter should change workout count');
Test 5: Collection Navigation
File: .maestro/flows/core/collections.yaml
appId: com.tabatafit.app
name: Collection Navigation
steps:
- tapOn: "Browse"
- waitForAnimationToEnd
# Scroll to collections
- swipe:
direction: UP
duration: 1000
# Tap first collection
- tapOn:
id: "collection-card-0"
# Collection Detail Screen
- assertVisible: "Collection"
- assertVisible: "workouts"
# Start collection workout
- tapOn: "Start"
- assertVisible: "Player|Timer"
onFlowComplete:
- takeScreenshot: "collection-navigation"
Test 6: Trainer Discovery
File: .maestro/flows/core/trainers.yaml
appId: com.tabatafit.app
name: Trainer Discovery
steps:
- tapOn: "Browse"
# Navigate to trainers section
- swipe:
direction: UP
# Select trainer
- tapOn:
id: "trainer-card-0"
# Verify trainer profile
- assertVisible: "workouts"
# Select trainer's workout
- tapOn:
id: "workout-card-0"
# Should show workout detail
- assertVisible: "Start Workout"
Reusable Test Helpers
Common Actions (helpers/common.yaml)
# .maestro/helpers/common.yaml
appId: com.tabatafit.app
# Launch app fresh
- launchApp:
clearState: true
# Wait for data loading
- waitForDataLoad:
commands:
- waitForAnimationToEnd:
timeout: 3000
- assertVisible: ".*" # Any content loaded
# Handle permission dialogs
- handlePermissions:
commands:
- tapOn:
text: "Allow"
optional: true
- tapOn:
text: "OK"
optional: true
# Navigate to tab
- navigateToTab:
params:
tabName: ${tab}
commands:
- tapOn: ${tab}
# Start workout from detail
- startWorkout:
commands:
- tapOn: "Start Workout"
- waitForAnimationToEnd:
timeout: 5000
- assertVisible: "Get Ready|WORK"
Running Tests
Local Development
# Install Maestro
curl -Ls "https://get.maestro.mobile.dev" | bash
# Run single test
maestro test .maestro/flows/critical/onboarding.yaml
# Run all critical tests
maestro test .maestro/flows/critical/
# Run with specific environment
maestro test --env SKIP_PAYWALL=true .maestro/flows/
# Record video of test
maestro record .maestro/flows/critical/workoutComplete.yaml
# Run with tags
maestro test --include-tags=critical .maestro/flows/
CI/CD Integration (GitHub Actions)
# .github/workflows/maestro.yml
name: Maestro E2E Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
e2e-tests:
runs-on: macos-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node
uses: actions/setup-node@v3
- name: Install Maestro
run: curl -Ls "https://get.maestro.mobile.dev" | bash
- name: Start iOS Simulator
run: |
xcrun simctl boot "iPhone 15"
sleep 10
- name: Install App
run: |
npm install
npx expo prebuild
npx pod install
npx react-native run-ios --simulator="iPhone 15"
- name: Run Critical Tests
run: |
export MAESTRO_DRIVER_STARTUP_TIMEOUT=120000
maestro test .maestro/flows/critical/
- name: Upload Test Results
if: always()
uses: actions/upload-artifact@v3
with:
name: maestro-results
path: |
~/.maestro/tests/
*.png
Test Coverage Matrix
| Feature | Test File | Priority | Frequency | Status |
|---|---|---|---|---|
| Onboarding | onboarding.yaml |
P0 | Every PR | ⏳ Pending |
| Workout Play | workoutComplete.yaml |
P0 | Every PR | ⏳ Pending |
| Purchase | subscription.yaml |
P0 | Every PR | ⏳ Pending |
| Category Filter | categoryFilter.yaml |
P1 | Pre-release | ⏳ Pending |
| Collections | collections.yaml |
P1 | Pre-release | ⏳ Pending |
| Trainers | trainers.yaml |
P1 | Pre-release | ⏳ Pending |
| Activity | activityHistory.yaml |
P2 | Nightly | ⏳ Pending |
| Offline | offlineMode.yaml |
P2 | Weekly | ⏳ Pending |
React Native Prerequisites
Before running tests, add testID props to components for reliable selectors:
// WorkoutCard.tsx
<Pressable testID={`workout-card-${index}`}>
{/* ... */}
</Pressable>
// WorkoutPlayer.tsx
<Button testID="done-button" title="Done" />
// Paywall.tsx
<Pressable testID="yearly-plan">
{/* ... */}
</Pressable>
Required testIDs Checklist
workout-card-{index}- Workout list itemscollection-card-{index}- Collection itemstrainer-card-{index}- Trainer itemsdone-button- Complete workout buttonyearly-plan/monthly-plan- Subscription plansstart-workout-button- Start workout CTAcategory-{name}- Category filter buttonstab-{name}- Bottom navigation tabs
Environment Variables
Create .env.maestro file:
# Test Configuration
SKIP_PAYWALL=true
USE_MOCK_PURCHASE=true
TEST_USER_NAME=Test User
TEST_USER_EMAIL=test@example.com
# API Configuration (if needed)
API_BASE_URL=https://api-staging.tabatafit.com
Troubleshooting
Common Issues
-
Tests fail on first run
- Clear app state:
maestro test --clear-state - Increase timeout in config.yaml
- Clear app state:
-
Element not found
- Verify testID is set correctly
- Add wait times before assertions
- Check for animations completing
-
Purchase tests fail
- Ensure
USE_MOCK_PURCHASE=truein test env - Use sandbox/test products
- Ensure
-
Slow tests
- Use
waitForAnimationToEndwith shorter timeouts - Disable animations in test builds
- Use
Debug Commands
# Interactive mode
maestro studio
# View hierarchy
maestro hierarchy
# Record test execution
maestro record <test-file>
# Verbose logging
maestro test --verbose <test-file>
Next Steps (After Features Are Complete)
- ✅ Create
.maestro/directory structure - ✅ Write
config.yaml - ✅ Implement P0 critical test flows
- ✅ Add testIDs to React Native components
- ✅ Run tests locally
- ✅ Setup CI/CD pipeline
- ⏳ Implement P1 core test flows
- ⏳ Add visual regression tests
- ⏳ Setup nightly regression suite
Resources
- Maestro Documentation
- Maestro YAML Reference
- React Native Testing with Maestro
- Maestro Best Practices
Created: March 17, 2026
Status: Implementation Pending (Waiting for feature completion)