Files
tabatago/docs/maestro-e2e-testing-strategy.md
Millian Lamiaux cd065d07c3 feat: explore tab, React Query data layer, programs, sync, analytics, testing infrastructure
- 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
2026-03-24 12:04:48 +01:00

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)

  1. Onboarding → First Workout
  2. Browse → Select Workout → Play → Complete
  3. Subscription Purchase Flow
  4. Background/Foreground During Workout

P1 - High Priority

  1. Category Filtering
  2. Collection Navigation
  3. Trainer Workout Discovery
  4. Profile Settings & Data Persistence

P2 - Medium Priority

  1. Activity History Tracking
  2. Offline Mode Behavior
  3. Deep Linking
  4. 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 items
  • collection-card-{index} - Collection items
  • trainer-card-{index} - Trainer items
  • done-button - Complete workout button
  • yearly-plan / monthly-plan - Subscription plans
  • start-workout-button - Start workout CTA
  • category-{name} - Category filter buttons
  • tab-{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

  1. Tests fail on first run

    • Clear app state: maestro test --clear-state
    • Increase timeout in config.yaml
  2. Element not found

    • Verify testID is set correctly
    • Add wait times before assertions
    • Check for animations completing
  3. Purchase tests fail

    • Ensure USE_MOCK_PURCHASE=true in test env
    • Use sandbox/test products
  4. Slow tests

    • Use waitForAnimationToEnd with shorter timeouts
    • Disable animations in test builds

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)

  1. Create .maestro/ directory structure
  2. Write config.yaml
  3. Implement P0 critical test flows
  4. Add testIDs to React Native components
  5. Run tests locally
  6. Setup CI/CD pipeline
  7. Implement P1 core test flows
  8. Add visual regression tests
  9. Setup nightly regression suite

Resources


Created: March 17, 2026
Status: Implementation Pending (Waiting for feature completion)