# 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**
5. **Category Filtering**
6. **Collection Navigation**
7. **Trainer Workout Discovery**
8. **Profile Settings & Data Persistence**

### **P2 - Medium Priority**
9. **Activity History Tracking**
10. **Offline Mode Behavior**
11. **Deep Linking**
12. **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

```bash
# macOS/Linux
curl -Ls "https://get.maestro.mobile.dev" | bash

# Verify installation
maestro --version
```

### 2. Setup Test Directory Structure

```bash
mkdir -p .maestro/flows/{critical,core,regression}
mkdir -p .maestro/helpers
mkdir -p .maestro/environments
```

### 3. Maestro Configuration (`config.yaml`)

```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`

```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`

```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`

```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`

```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`

```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`

```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`)

```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

```bash
# 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)

```yaml
# .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:

```tsx
// 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:

```bash
# 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

```bash
# 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

- [Maestro Documentation](https://maestro.mobile.dev/)
- [Maestro YAML Reference](https://maestro.mobile.dev/api-reference/commands)
- [React Native Testing with Maestro](https://maestro.mobile.dev/platform-support/react-native)
- [Maestro Best Practices](https://maestro.mobile.dev/advanced/best-practices)

---

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