# MOBILE QA AUTONOMOUS EXPERT — RUFLO + CLAUDE CODE

> **Appareils configurés :** Pixel 9 Pro XL (Android) · iPhone 17 (iOS)  
> **Stack :** Ruflo v3.5 · Claude Code · ADB · xcrun simctl · Appium

---

## PHASE 0 — INITIALISATION DU SWARM

> **Règle absolue :** tout ce bloc s'exécute en UN SEUL MESSAGE, simultanément.

```javascript
// 1. Init swarm hiérarchique anti-drift
mcp__claude -
  flow__swarm_init({
    topology: "hierarchical",
    maxAgents: 6,
    strategy: "specialized",
    name: "mobile-qa-swarm",
  });

// 2. Stocker le contexte de session en mémoire partagée
mcp__claude -
  flow__memory_usage({
    action: "store",
    key: "project/qa-session",
    value: {
      started: Date.now(),
      platforms: ["android", "ios"],
      android: {
        avd_name: "Pixel_9_Pro_XL",
        adb: "/<android_sdk_adb_directory>/Library/Android/sdk/platform-tools/adb",
        emulator:
          "/<android_sdk_adb_directory>/Library/Android/sdk/emulator/emulator",
      },
      ios: {
        device_name: "iPhone 17",
        udid: "[Simulateur_iOS_UDID]",
        runtime: "iOS 26.4 bêta",
      },
      app: {
        android_id: "example.com.app",
        ios_id: "example.com.app",
        name: "AppName",
        framework: "flutter",
      },
      credentials: {
        staff: { email: "example@gmail.com", password: "Password" },
        tenant: { email: "example2@gmail.com", password: "Password2" },
        dual_role: {
          email: "example2@gmail.com",
          password: "Password2",
        },
        api_env: "staging",
      },
      mission: "autonomous_qa_audit",
    },
  });

// 3. Spawn des 6 agents spécialisés (TOUS simultanément)
mcp__claude - flow__agent_spawn({ type: "coordinator", name: "QA Lead" });
mcp__claude - flow__agent_spawn({ type: "tester", name: "Android Tester" });
mcp__claude - flow__agent_spawn({ type: "tester", name: "iOS Tester" });
mcp__claude - flow__agent_spawn({ type: "analyst", name: "Bug Analyst" });
mcp__claude - flow__agent_spawn({ type: "reviewer", name: "UX Reviewer" });
mcp__claude - flow__agent_spawn({ type: "architect", name: "Report Writer" });

// 4. Todos batch — toujours en un seul appel
TodoWrite({
  todos: [
    {
      id: "env-check",
      content: "Vérifier et démarrer émulateur Android + simulateur iOS",
      status: "in_progress",
      priority: "high",
    },
    {
      id: "static-audit",
      content: "Analyse statique du code source (Claude Code)",
      status: "pending",
      priority: "high",
    },
    {
      id: "android-explore",
      content: "Exploration autonome Android (Pixel 9 Pro XL)",
      status: "pending",
      priority: "high",
    },
    {
      id: "ios-explore",
      content: "Exploration autonome iOS (iPhone 17)",
      status: "pending",
      priority: "high",
    },
    {
      id: "scenarios",
      content: "Exécuter les scénarios de test guidés",
      status: "pending",
      priority: "high",
    },
    {
      id: "bug-analysis",
      content: "Analyser, dédupliquer et prioriser les bugs",
      status: "pending",
      priority: "medium",
    },
    {
      id: "screenshots",
      content: "Annoter les captures d'écran des bugs détectés",
      status: "pending",
      priority: "medium",
    },
    {
      id: "report",
      content: "Générer le rapport QA professionnel (3 formats)",
      status: "pending",
      priority: "high",
    },
  ],
});
```

---

## PHASE 1 — VÉRIFICATION ET DÉMARRAGE DES APPAREILS

**Android Tester** et **iOS Tester** exécutent ces commandes EN PARALLÈLE.

### Android — Pixel 9 Pro XL

```bash
# Vérifier si l'émulateur est déjà actif
/<android_sdk_adb_directory>/Library/Android/sdk/platform-tools/adb devices

# Si aucun appareil listé → démarrer
/<android_sdk_adb_directory>/Library/Android/sdk/emulator/emulator \
  -avd Pixel_9_Pro_XL \
  -no-snapshot \
  -no-audio &

# Attendre la détection réseau de l'appareil
/<android_sdk_adb_directory>/Library/Android/sdk/platform-tools/adb wait-for-device

# Attendre le boot complet (retourne "1" quand prêt)
until [ "$(/<android_sdk_adb_directory>/Library/Android/sdk/platform-tools/adb shell \
  getprop sys.boot_completed 2>/dev/null | tr -d '\r')" = "1" ]; do
  sleep 3
done

# Activer l'accessibilité Flutter (nécessaire pour lire l'arbre UI)
/<android_sdk_adb_directory>/Library/Android/sdk/platform-tools/adb \
  shell settings put secure accessibility_enabled 1

# Confirmer
/<android_sdk_adb_directory>/Library/Android/sdk/platform-tools/adb \
  shell getprop ro.product.model
# Résultat attendu : "sdk_gphone64_arm64" ou "Pixel 9 Pro XL"
```

### iOS — iPhone 17

```bash
# Vérifier via UDID (unique, stable)
xcrun simctl list devices booted | grep "7D2D3ABA"

# Si non Booted → démarrer via UDID
xcrun simctl boot [Simulateur_iOS_UDID]
open -a Simulator

# Attendre boot complet
xcrun simctl bootstatus [Simulateur_iOS_UDID] -b

# Confirmer
xcrun simctl list devices booted | grep "7D2D3ABA"
# Résultat attendu : iPhone 17 ([Simulateur_iOS_UDID]) (Booted)

# Note iOS 26.4 bêta : si une commande xcrun simctl échoue,
# capturer l'erreur, la stocker en mémoire et continuer.
```

> **Si le démarrage échoue :** capturer le message d'erreur, le stocker en mémoire avec `mcp__claude-flow__memory_usage`, et continuer avec la plateforme disponible. Ne jamais bloquer sur un échec de démarrage.

---

## PHASE 2 — ANALYSE STATIQUE DU CODE SOURCE

**QA Lead** délègue à **Claude Code** (outils Read, Grep, Glob) — sans lancer l'app.

```
Analyse statique à effectuer — AppName (Flutter/Dart) :

Framework : Flutter · Dart · Provider · Dio
Répertoire source : app/lib/

1. STRUCTURE DU PROJET
   - Cartographier tous les fichiers d'écrans sous app/lib/screens/
   - Identifier le système de navigation (router_manager.dart — named routes)
   - Lister les ViewModels Provider sous app/lib/core/viewmodels/

2. DÉTECTION DE PATTERNS PROBLÉMATIQUES (Dart/Flutter)
   Recherche avec Grep sur app/lib/ :
   - Appels Dio sans try/catch (pattern : await http.get|post|put|delete sans bloc catch)
   - setState() appelé sans vérification `if (!mounted) return` avant
   - `print(` laissés en production (remplacer par debugPrint ou retirer)
   - `Timer(` ou `Timer.periodic(` sans `.cancel()` associé dans dispose()
   - Widgets Image/NetworkImage sans width/height définis
   - Formulaires sans validation côté client (TextFormField sans validator:)
   - Strings hardcodées non passées par S.of(context) (i18n FR/EN manquante)
   - Clés API ou tokens visibles dans le code source
   - `Semantics(` ou `Tooltip(` manquants sur les éléments interactifs critiques
   - WebSocket (web_socket_channel) non fermé dans dispose()
   - Permissions (permission_handler) demandées sans message d'explication préalable
     → Permissions attendues : CAMERA, LOCATION, MICROPHONE, READ_EXTERNAL_STORAGE

3. CARTOGRAPHIE DES FLOWS CRITIQUES AppName
   - Auth : app/lib/screens/auth/ (sign_in, sign_up, password_forgotten)
   - Dashboard agence : app/lib/screens/app/agence_space/dashboard/
   - Bail/Annonces : app/lib/screens/app/agence_space/rentals/ + locations/
   - Paiements : app/lib/screens/app/agence_space/payments/
   - Inspections : app/lib/screens/app/agence_space/inspections/
   - Espace locataire : app/lib/screens/app/tenant_space/
   - Chat WebSocket : app/lib/screens/app/messaging/

4. FORMAT DE SORTIE ATTENDU (JSON strict)
{
  "framework": "flutter",
  "screens_map": ["SignIn", "HomeScreen", "Dashboard", "..."],
  "critical_flows": ["auth", "dashboard", "bail", "paiement", "inspection"],
  "static_bugs": [
    {
      "id": "STATIC-001",
      "severity": "high",
      "type": "missing_error_handling",
      "file": "app/lib/core/services/property_repository.dart",
      "line": 44,
      "description": "Appel Dio sans gestion d'erreur explicite"
    }
  ],
  "risk_areas": ["paiement", "inspection_signature", "bail_creation"]
}
```

Stocker immédiatement le résultat :

```javascript
mcp__claude -
  flow__memory_usage({
    action: "store",
    key: "qa/static-analysis",
    value: {
      /* résultat JSON ci-dessus */
    },
  });
```

---

## PHASE 3 — EXPLORATION AUTONOME

**Android Tester** (Pixel 9 Pro XL) et **iOS Tester** (iPhone 17) tournent EN PARALLÈLE.  
Budget : **20 actions maximum par plateforme.**

### Boucle d'exploration — répéter jusqu'à `done` ou budget épuisé

#### ÉTAPE A — Capturer l'état de l'écran

```bash
# Android — paths absolus
/<android_sdk_adb_directory>/Library/Android/sdk/platform-tools/adb \
  exec-out screencap -p > /tmp/screen_android_{N}.png

/<android_sdk_adb_directory>/Library/Android/sdk/platform-tools/adb \
  shell uiautomator dump /sdcard/ui.xml

/<android_sdk_adb_directory>/Library/Android/sdk/platform-tools/adb \
  pull /sdcard/ui.xml /tmp/ui_android_{N}.xml

# Android — paths absolus
/<android_sdk_adb_directory>/Library/Android/sdk/platform-tools/adb \
  exec-out screencap -p > /tmp/screen_android_{N}.png

# Redimensionner le screenshot Android (max 1000px)
python3 -c "
from PIL import Image
img = Image.open('/tmp/screen_android_{N}.png')
img.thumbnail((1000, 1000))
img.save('/tmp/screen_android_{N}.png')
"

/<android_sdk_adb_directory>/Library/Android/sdk/platform-tools/adb \
  shell uiautomator dump /sdcard/ui.xml

/<android_sdk_adb_directory>/Library/Android/sdk/platform-tools/adb \
  pull /sdcard/ui.xml /tmp/ui_android_{N}.xml

# Flutter : filtrer les éléments sans texte utile avant envoi
python3 -c "
import xml.etree.ElementTree as ET
tree = ET.parse('/tmp/ui_android_{N}.xml')
nodes = [n for n in tree.iter()
         if n.get('clickable') == 'true'
         or n.get('focusable') == 'true'
         or (n.get('text') and n.get('text').strip())]
output = '\n'.join([ET.tostring(n, encoding='unicode') for n in nodes[:60]])
print(output[:8000])
" > /tmp/ui_android_{N}_filtered.txt

# iOS — UDID explicite, pas 'booted'
xcrun simctl io [Simulateur_iOS_UDID] \
  screenshot /tmp/screen_ios_{N}.png

# Redimensionner le screenshot iOS (max 1000px)
python3 -c "
from PIL import Image
img = Image.open('/tmp/screen_ios_{N}.png')
img.thumbnail((1000, 1000))
img.save('/tmp/screen_ios_{N}.png')
"
```

> Limiter l'arbre UI à 8 000 caractères avant envoi à Claude pour contrôler les tokens.

#### ÉTAPE B — Analyser avec Claude Vision

Envoyer le screenshot + l'arbre UI tronqué avec ce prompt d'analyse :

```
Tu es un expert QA mobile senior. Analyse cet écran et retourne UNIQUEMENT ce JSON valide, sans texte autour :

{
  "screen_name": "nom exact de l'écran visible",
  "bugs": [
    {
      "id": "ANDROID-001",
      "severity": "critical|high|medium|low",
      "type": "crash|ui_overlap|truncation|contrast|accessibility|ux|performance|navigation",
      "title": "titre court (moins de 10 mots)",
      "description": "description précise et factuelle du problème",
      "reproduction": "étapes exactes pour reproduire",
      "location": {
        "x": 0,
        "y": 0,
        "element": "description de l'élément UI concerné"
      },
      "screenshot": "screen_android_001.png"
    }
  ],
  "next_action": {
    "type": "tap|swipe|input|back|scroll|done",
    "x": 0,
    "y": 0,
    "text": "",
    "direction": "up|down|left|right",
    "reasoning": "justification de cette prochaine action"
  }
}

CRITÈRES DE BUG OBLIGATOIRES — signaler si :
- Éléments qui se chevauchent ou se tronquent
- Contraste texte/fond insuffisant (ratio < 4.5:1)
- Boutons trop petits (< 44pt iOS / < 48dp Android)
- Libellés manquants sur éléments interactifs (accessibilité)
- Messages d'erreur absents ou trop techniques
- Incohérence visuelle avec les autres écrans déjà visités
- Spinner infini sans timeout visible
- Texte qui déborde de son conteneur
```

#### ÉTAPE C — Exécuter l'action recommandée

> **Prérequis iOS :** `idb_companion` doit être démarré avant les interactions.  
> `xcrun simctl ui` ne supporte **pas** tap/swipe — utiliser `idb` exclusivement.
>
> ```bash
> # Démarrer idb_companion une seule fois au début de la session
> IOS_UDID=$(xcrun simctl list devices booted | grep "iPhone 17" | grep -oE '[A-F0-9-]{36}' | head -1)
> idb_companion --udid $IOS_UDID --grpc-port 10882 &
> idb connect 127.0.0.1 10882
> ```

```bash
# TAP
# Android
/<android_sdk_adb_directory>/Library/Android/sdk/platform-tools/adb \
  shell input tap {x} {y}
# iOS — idb UNIQUEMENT (xcrun simctl ui ne supporte pas tap/swipe)
idb ui tap --udid [Simulateur_iOS_UDID] {x} {y}

# SWIPE
# Android
/<android_sdk_adb_directory>/Library/Android/sdk/platform-tools/adb \
  shell input swipe {x1} {y1} {x2} {y2} 300
# iOS — idb UNIQUEMENT
idb ui swipe --udid [Simulateur_iOS_UDID] {x1} {y1} {x2} {y2}

# SAISIE TEXTE
# Android
/<android_sdk_adb_directory>/Library/Android/sdk/platform-tools/adb \
  shell input text '{text}'
# iOS — idb UNIQUEMENT
idb ui type --udid [Simulateur_iOS_UDID] '{text}'

# RETOUR ARRIÈRE
# Android
/<android_sdk_adb_directory>/Library/Android/sdk/platform-tools/adb \
  shell input keyevent KEYCODE_BACK
# iOS — idb UNIQUEMENT
idb ui key-press --udid [Simulateur_iOS_UDID] DELETE

# SCROLL
# Android
/<android_sdk_adb_directory>/Library/Android/sdk/platform-tools/adb \
  shell input swipe 540 1200 540 400 400
# iOS — idb UNIQUEMENT
idb ui swipe --udid [Simulateur_iOS_UDID] 195 700 195 300
```

> **Règle de vérification navigation :** après CHAQUE tap de navigation dans le drawer, faire un screenshot immédiat et confirmer que `screen_name` dans le JSON d'analyse correspond à l'écran cible avant de passer à l'action suivante. Si l'écran n'est pas le bon, ne pas continuer : documenter la confusion et reprendre depuis le drawer.

#### ÉTAPE D — Stocker chaque bug immédiatement

```javascript
mcp__claude -
  flow__memory_usage({
    action: "store",
    key: "qa/bugs/{platform}/{bug_id}",
    value: {
      /* bug object complet */
    },
  });
```

### CARTE DU DRAWER — Structure exacte de l'app AppName

> **Règle absolue :** avant chaque tap sur un item du drawer, faire un screenshot + lire le nom de l'écran visible. Vérifier après chaque tap que l'écran cible est bien atteint avant de continuer.

```
SECTION DISCOVERY (espace public)
  ├── Home (index 1)
  ├── Espace annonces (index 2)
  └── Agent immobilier (index 3)

SECTION ESPACE PRO (staff agence uniquement)
  ├── Dashboard (index 4)
  ├── Mes agences (index 5)
  │
  ├── [EXPAND] Biens (index 6)
  │     ├── Mes propriétés (index 21)
  │     ├── Locations (index 22)          ← liste des biens en location
  │     └── Inventaires (index 35)
  │
  ├── [EXPAND] Baux & Locataires (index 18)  ← SECTION BAIL
  │     ├── Mes locataires (index 7)
  │     ├── Baux (index 8)               ← RentalsScreen → NEW RENTAL via FAB (+)
  │     └── Réservations (index 33)
  │
  ├── [EXPAND] Opérations Terrain (index 30)  ← ≠ BAIL — État des lieux uniquement
  │     ├── Check-in / Entrée (index 31)  ← État des lieux ENTRÉE (≠ bail)
  │     ├── Check-out / Sortie (index 32) ← État des lieux SORTIE (≠ bail)
  │     ├── Incidents (index 34)
  │     ├── Entretiens (index 36)
  │     ├── Demandes de service (index 37)
  │     ├── Maintenance (index 38)
  │     ├── Inventaire (index 39)
  │     ├── Tâches (index 41)
  │     ├── Planification (index 42)
  │     └── Fournisseurs (index 43)
  │
  ├── [EXPAND] Finances (index 14)
  │     ├── Quittances (index 19)
  │     ├── Paiements (index 10)
  │     ├── Dépenses (index 9)
  │     ├── Recouvrement (index 26)
  │     └── Grand Livre (index 70)
  │
  ├── Collaborateurs (index 51)
  └── Leads (index 52)

SECTION ESPACE LOCATAIRE
  ├── Mes Biens (index 54)
  ├── Mes Quittances (index 55)
  └── Signaler un incident (index 56)

SECTION GÉNÉRALE
  ├── Mon profil (index 11)
  ├── Paramètres (index 13)
  └── À propos (index 12)
```

**Chemin exact vers création de bail :**

```
Ouvrir drawer → "Baux & Locataires" (expand) → "Baux" → RentalsScreen → FAB (+) → NewRentalScreen (wizard 3 étapes : Bien → Tarifs → Conditions)
```

**⚠️ ATTENTION CONFUSION FRÉQUENTE :**

- `Baux` (index 8) = contrats de location → c'est ici pour créer un bail
- `Check-in / Entrée` (index 31) = état des lieux d'entrée → ce n'est PAS un bail
- `Locations` (index 22) = liste des biens disponibles → pas de création de bail ici

### Flows à couvrir — dans cet ordre de priorité

| Priorité | Flow                         | Description                                                                            |
| -------- | ---------------------------- | -------------------------------------------------------------------------------------- |
| 1        | Onboarding                   | Premier lancement, splash, permissions (CAMERA, LOCATION, MICROPHONE)                  |
| 2        | Authentification             | Inscription, connexion, mot de passe oublié                                            |
| 3        | Dashboard agence             | Connexion en tant que staff → valider chargement KPIs, charts SfCharts, stats sans 429 |
| 4        | Switch de rôle               | WorkspaceSwitcherSheet → basculer entre espace agence et espace locataire              |
| 5        | Création de bail             | Formulaire CreateUserAnnounceRequest complet (loyer, dépôt de garantie, locataire)     |
| 6        | Enregistrement d'un paiement | NewPaymentScreen — sélection type, montant, méthode de paiement                        |
| 7        | Inspection + signature       | Flux multi-étapes avec capture photo et signature électronique                         |
| 8        | Formulaires génériques       | Saisie valide + invalide + bords limites                                               |
| 9        | Navigation complète          | Tous les onglets, tous les écrans accessibles depuis le drawer                         |
| 10       | Erreur réseau                | Mode avion → action → reconnexion (vérifier reprise WebSocket chat)                    |
| 11       | Changement de langue         | FR ↔ EN via Profil → vérifier que toutes les chaînes se traduisent                     |
| 12       | Rotation                     | Portrait → paysage sur Dashboard, formulaire bail, carte Google Maps                   |
| 13       | Grande police                | Accessibilité → taille de texte maximum sur les écrans financiers                      |
| 14       | Dark mode                    | Vérifier contraste sur Dashboard, formulaires, liste de biens                          |

### Gestion des crashes

Si un crash est détecté pendant l'exploration :

```bash
# Android
/<android_sdk_adb_directory>/Library/Android/sdk/platform-tools/adb logcat -d -t 200 > /tmp/crash_android_{N}.log

/<android_sdk_adb_directory>/Library/Android/sdk/platform-tools/adb shell am force-stop example.com.app

/<android_sdk_adb_directory>/Library/Android/sdk/platform-tools/adb shell monkey -p example.com.app -c android.intent.category.LAUNCHER 1

# iOS — UDID explicite
xcrun simctl spawn [Simulateur_iOS_UDID] log show --last 3m --predicate 'eventMessage contains "crash" OR eventMessage contains "Exception"' > /tmp/crash_ios_{N}.log

xcrun simctl launch [Simulateur_iOS_UDID] example.com.app
```

Créer un bug de sévérité `critical` avec les logs en pièce jointe, puis continuer.

---

## PHASE 4 — SCÉNARIOS DE TEST GUIDÉS

**QA Lead** orchestre ces scénarios sur les DEUX plateformes EN PARALLÈLE.  
**Bug Analyst** vérifie chaque assertion par analyse visuelle du screenshot.

### Scénario 1 — Connexion avec données valides (CRITIQUE)

```
Steps :
  1. Naviguer vers l'écran de connexion
  2. Saisir un email valide dans le champ email
  3. Saisir un mot de passe valide (8+ caractères)
  4. Appuyer sur le bouton de connexion principal

Assertions :
  - Redirection vers l'écran principal dans les 3 secondes
  - Aucun message d'erreur visible à l'écran
  - Le nom ou avatar de l'utilisateur est affiché
  - Aucun spinner infini
```

### Scénario 2 — Connexion avec données invalides (CRITIQUE)

```
Steps :
  1. Saisir un email mal formaté (exemple : notanemail)
  2. Laisser le champ mot de passe vide
  3. Appuyer sur le bouton de connexion

Assertions :
  - Un message d'erreur de validation s'affiche
  - Le formulaire ne se soumet pas
  - Le message d'erreur est compréhensible (pas un code technique)
  - Le champ en erreur est visuellement identifié (rouge, icône)
```

### Scénario 3 — Navigation complète

```
Steps :
  1. Depuis l'écran principal, appuyer sur chaque élément de navigation
  2. Dans chaque section, scroller jusqu'en bas de page
  3. Revenir à l'écran principal via le bouton de navigation

Assertions :
  - Aucun crash pendant la navigation
  - L'onglet actif est visuellement mis en évidence
  - Le contenu de chaque section se charge correctement
  - Le scroll ne provoque pas de lag visible
```

### Scénario 4 — Résilience réseau (CRITIQUE)

```bash
# Activer le mode avion
# Android :
adb shell am broadcast -a android.intent.action.AIRPLANE_MODE --ez state true
adb shell settings put global airplane_mode_on 1

# iOS :
# Note : impossible via simctl — utiliser Network Link Conditioner ou
# couper le réseau de la machine hôte temporairement
```

```
Steps :
  1. Activer le mode avion (Android) ou couper le réseau (iOS)
  2. Tenter une action qui nécessite le réseau (refresh, chargement)
  3. Observer le comportement de l'app
  4. Rétablir le réseau
  5. Observer la reprise

Assertions :
  - Un message d'erreur "friendly" s'affiche (pas de crash)
  - L'app ne reste pas bloquée sur un spinner infini
  - L'app se remet à jour automatiquement après reconnexion
  - Aucune perte de données saisies par l'utilisateur
```

### Scénario 5 — Accessibilité et localisation

```
Steps :
  1. Activer TalkBack (Android) ou VoiceOver (iOS) via les settings système
  2. Naviguer vers l'écran principal
  3. Activer la grande taille de police (accessibilité système → max)
  4. Parcourir les 3 écrans principaux
  5. Aller dans Profil → changer la langue de FR en EN
  6. Vérifier les 3 écrans principaux en anglais
  7. Repasser en FR et vérifier le retour

Assertions :
  - Chaque bouton interactif a un label vocal
  - Le texte ne déborde pas avec la grande police
  - Les images décoratives sont marquées comme telles
  - L'ordre de focus du clavier est logique
  - Tous les libellés d'écran passent bien en anglais (aucun texte en FR résiduel)
  - Aucune chaîne i18n vide ou placeholder visible ({key_missing})
```

### Scénario 6 — Dashboard agence sans rate limiting (CRITIQUE AppName)

```
Steps :
  1. Se connecter avec un compte staff d'agence
  2. Arriver sur le Dashboard
  3. Naviguer vers l'écran Home (espace public)
  4. Revenir au Dashboard (3 fois de suite en moins de 30 secondes)

Assertions :
  - Aucune réponse 429 sur /features-announces, /property-types, /agency/stats
  - Les KPIs (revenus, taux d'occupation) s'affichent à chaque retour
  - Les charts SfCharts (recouvrement hebdo, revenus mensuels) se chargent sans spinner infini
  - Le badge d'alertes urgentes reflète des données fraîches
```

### Scénario 7 — Création de bail complet (CRITIQUE AppName)

```
Navigation exacte (obligatoire — ne pas dévier) :
  a. Ouvrir le drawer (swipe depuis le bord gauche ou hamburger)
  b. Vérifier screenshot : écran = drawer visible
  c. Tapper "Baux & Locataires" pour déplier la section (index 18)
     ⚠️ NE PAS tapper "Locations" (index 22) — c'est la liste des biens
     ⚠️ NE PAS tapper "Opérations Terrain" (index 30) — c'est les états des lieux
  d. Vérifier screenshot : les sous-items "Mes locataires", "Baux", "Réservations" sont visibles
  e. Tapper "Baux" (le deuxième sous-item, pas "Mes locataires")
  f. Vérifier screenshot : screen_name = "RentalsScreen" (liste des baux)
  g. Tapper le bouton FAB (+) en bas à droite
  h. Vérifier screenshot : screen_name = "NewRentalScreen" (formulaire wizard — Étape 1 "Bien")

Steps (une fois sur NewRentalScreen) :
  1. Étape 1 "Bien" : sélectionner un bien existant dans la liste
  2. Tapper "Suivant" → Étape 2 "Tarifs"
  3. Étape 2 "Tarifs" : saisir loyer = 150 000, dépôt de garantie = 300 000
  4. Tapper "Suivant" → Étape 3 "Conditions"
  5. Étape 3 "Conditions" : sélectionner type bail = "Bail nu (résidence principale)", date début = aujourd'hui, durée = 12 mois
  6. Sélectionner un locataire existant
  7. Valider le formulaire
  8. Vérifier l'écran de confirmation

Assertions :
  - Le formulaire accepte les montants en FCFA (pas de bug de formatage monétaire)
  - Le dépôt de garantie peut être supérieur au loyer (cas métier valide)
  - Un message de succès s'affiche après création
  - Le bail apparaît dans la liste des baux actifs
  - Aucune erreur de validation intempestive sur les champs numériques

Steps erreur (test validation) :
  1. Saisir un loyer négatif (-100)
  2. Laisser la date de fin vide
  3. Tenter de soumettre

Assertions erreur :
  - Le formulaire bloque la soumission
  - Les champs en erreur sont visuellement identifiés
  - Les messages d'erreur sont en français (si langue FR active)
```

### Scénario 8 — Switch rôle agence ↔ locataire (AppName)

```
Steps :
  1. Se connecter avec un compte ayant les deux rôles (staff + locataire)
  2. Sur l'écran Home, appuyer sur "Espace Pro"
  3. Depuis le WorkspaceSwitcherSheet, sélectionner "Espace locataire"
  4. Vérifier l'espace locataire (mes baux, mes quittances, mes paiements)
  5. Rebasculer vers l'espace agence

Assertions :
  - Le WorkspaceSwitcherSheet s'affiche avec les deux options
  - L'espace locataire affiche uniquement les données du locataire connecté
  - Le retour vers l'espace agence recharge le Dashboard sans 429
  - Aucune fuite de données entre les deux espaces (données agence invisible en espace locataire)
```

**Pour CHAQUE assertion**, **Bug Analyst** retourne :

```json
{
  "scenario": "Connexion valide",
  "assertion": "Redirection dans 3 secondes",
  "passed": true,
  "evidence": "Screenshot montre l'écran HomeScreen chargé, spinner absent",
  "screenshot": "screen_android_scenario1_step4.png"
}
```

---

## PHASE 5 — ANALYSE ET DÉDUPLICATION DES BUGS

**Bug Analyst** récupère tous les bugs depuis la mémoire partagée :

```javascript
mcp__claude -
  flow__memory_usage({
    action: "retrieve",
    key: "qa/bugs/*",
  });
```

Règles d'analyse à appliquer dans cet ordre :

1. **Déduplication** — même bug présent sur Android et iOS → un seul bug, champ `platforms: ["android", "ios"]`
2. **Escalade de sévérité** — bug présent sur les 2 plateformes → sévérité monte d'un niveau (low → medium, medium → high, high → critical)
3. **Regroupement** — même type de bug sur plusieurs écrans → regrouper avec `affected_screens: [...]`
4. **Prioritisation finale** — critical > high > medium > low, puis par fréquence d'apparition
5. **Déduplication avec l'analyse statique** — si un bug statique a été confirmé dynamiquement → fusionner les entrées

---

## PHASE 6 — ANNOTATION DES SCREENSHOTS

**UX Reviewer** annote chaque screenshot avec la localisation précise du bug :

```python
from PIL import Image, ImageDraw, ImageFont
import io, os

def annotate_screenshot(img_path: str, bug: dict) -> str:
    img = Image.open(img_path)
    draw = ImageDraw.Draw(img)
    x, y = bug['location']['x'], bug['location']['y']

    # Rectangle rouge autour de la zone problématique
    draw.rectangle([x - 5, y - 5, x + 120, y + 50], outline='red', width=3)

    # Badge avec l'ID du bug
    draw.rectangle([x - 5, y - 28, x + 90, y - 5], fill='red')
    draw.text((x + 2, y - 24), bug['id'], fill='white')

    # Sauvegarder le screenshot annoté
    output_path = img_path.replace('.png', f'_bug_{bug["id"]}.png')
    img.save(output_path, format='PNG')
    return output_path

# Exécuter pour chaque bug avec une location définie
for bug in all_bugs:
    if bug.get('location') and bug['location'].get('x'):
        annotated = annotate_screenshot(bug['screenshot'], bug)
        bug['screenshot_annotated'] = annotated
```

---

## PHASE 7 — RAPPORT QA PROFESSIONNEL

**Report Writer** génère les 3 livrables EN PARALLÈLE dans `./qa-reports/`.

### 7A — rapport-qa.md (Markdown — pour PR comments et équipe)

```markdown
# Rapport QA Mobile — {DATE} {HEURE}

**App :** {APP_NAME}  
**Session :** {DURATION}s | **Écrans testés :** {N} | **Actions totales :** {N}  
**Appareils :** Pixel 9 Pro XL (Android {VERSION}) · iPhone 17 (iOS {VERSION})

---

## Score qualité global

| Plateforme | Score  | Critical | High | Medium | Low |
| ---------- | ------ | -------- | ---- | ------ | --- |
| Android    | {X}/10 | {n}      | {n}  | {n}    | {n} |
| iOS        | {X}/10 | {n}      | {n}  | {n}    | {n} |

---

## Bugs critiques — Bloquer la release

{Pour chaque bug critical :}

### {ID} — {TITLE}

**Plateforme :** {platforms} | **Écran :** {screen} | **Type :** {type}  
**Description :** {description}  
**Reproduction :** {reproduction}  
**Screenshot :** voir {screenshot_annotated}

---

## Bugs high — À corriger avant la prochaine release

{...}

## Bugs medium / low — Backlog

{...}

## Analyse statique

**Fichiers analysés :** {N}  
**Problèmes détectés :** {static_bugs summary}

## Scénarios de test

| Scénario            | Android | iOS   |
| ------------------- | ------- | ----- |
| Connexion valide    | ✅/❌   | ✅/❌ |
| Connexion invalide  | ✅/❌   | ✅/❌ |
| Navigation complète | ✅/❌   | ✅/❌ |
| Résilience réseau   | ✅/❌   | ✅/❌ |
| Accessibilité       | ✅/❌   | ✅/❌ |

## Top 5 recommandations

1. {recommandation prioritaire}
2. {recommandation}
3. {recommandation}
4. {recommandation}
5. {recommandation}
```

### 7B — rapport-qa.json (JSON structuré — pour CI/CD et intégrations)

```json
{
  "session": {
    "date": "",
    "duration_seconds": 0,
    "platforms": ["android", "ios"],
    "devices": {
      "android": "Pixel 9 Pro XL",
      "ios": "iPhone 17"
    },
    "screens_tested": 0,
    "actions_taken": 0
  },
  "summary": {
    "total_bugs": 0,
    "quality_score": { "android": 0, "ios": 0 },
    "by_severity": { "critical": 0, "high": 0, "medium": 0, "low": 0 },
    "by_platform": { "android_only": 0, "ios_only": 0, "both": 0 }
  },
  "bugs": [
    {
      "id": "",
      "title": "",
      "severity": "critical|high|medium|low",
      "type": "",
      "platforms": [],
      "affected_screens": [],
      "description": "",
      "reproduction": "",
      "location": { "x": 0, "y": 0, "element": "" },
      "screenshots": [],
      "screenshots_annotated": [],
      "status": "open"
    }
  ],
  "static_analysis": {
    "framework": "",
    "files_scanned": 0,
    "issues_found": []
  },
  "test_scenarios": {
    "total": 5,
    "passed": 0,
    "failed": 0,
    "results": []
  }
}
```

### 7C — alerte-slack.json (Slack webhook — si bugs critiques détectés)

```json
{
  "text": "🚨 *QA Mobile — {N} bugs critiques détectés*",
  "blocks": [
    {
      "type": "header",
      "text": {
        "type": "plain_text",
        "text": "Rapport QA — {APP_NAME} — {DATE}"
      }
    },
    {
      "type": "section",
      "fields": [
        {
          "type": "mrkdwn",
          "text": "*Android (Pixel 9 Pro XL)*\nScore : {X}/10 · {N} critical"
        },
        {
          "type": "mrkdwn",
          "text": "*iOS (iPhone 17)*\nScore : {X}/10 · {N} critical"
        }
      ]
    },
    {
      "type": "section",
      "text": {
        "type": "mrkdwn",
        "text": "*Bugs critiques :*\n• {BUG-001} — {title}\n• {BUG-002} — {title}"
      }
    }
  ]
}
```

Envoyer avec :

```bash
curl -X POST -H 'Content-type: application/json' \
  --data @./qa-reports/alerte-slack.json \
  {SLACK_WEBHOOK_URL}
```

---

## RÈGLES D'EXÉCUTION ABSOLUES

| Règle                  | Description                                                                |
| ---------------------- | -------------------------------------------------------------------------- |
| Parallélisme           | Android et iOS tournent simultanément, jamais séquentiellement             |
| Mémoire immédiate      | Stocker chaque bug en mémoire dès sa détection, ne pas attendre la fin     |
| Crash → continuer      | Capturer logs + crash bug, relancer l'app, continuer l'exploration         |
| Écran non réactif      | Screenshot + log + passer à l'action suivante (ne jamais bloquer)          |
| Budget strict          | 20 actions max par plateforme — prioriser les flows critiques              |
| Topologie fixe         | Hiérarchique obligatoire — QA Lead coordonne, agents spécialisés exécutent |
| Aucun fichier parasite | Seulement les rapports finaux et screenshots annotés dans `./qa-reports/`  |

---

## VARIABLES À PERSONNALISER

Remplace ces valeurs avant de lancer le prompt dans Claude Code :

| Variable              | Description                | Valeur AppName                                |
| --------------------- | -------------------------- | --------------------------------------------- |
| `{APP_ID_ANDROID}`    | Package ID Android         | `example.com.app`                             |
| `{APP_ID_IOS}`        | Bundle ID iOS              | `example.com.app`                             |
| `{APP_NAME}`          | Nom lisible de l'app       | `AppName`                                     |
| `{CRITICAL_FLOWS}`    | Flows à tester en priorité | `auth, dashboard, bail, paiement, inspection` |
| `{SLACK_WEBHOOK_URL}` | URL webhook Slack          | optionnel                                     |
| `{ACTION_BUDGET}`     | Max actions par plateforme | 20                                            |

> Les appareils sont déjà configurés : **Pixel 9 Pro XL** (Android) et **iPhone 17** (iOS).  
> Ne pas modifier `avd_name` et `ios_device` dans la Phase 0.

---

## COMMANDE DE LANCEMENT

```bash
# Étape 1 — Installer Ruflo si pas encore fait
npx ruflo@latest init --wizard

# Étape 2 — Vérifier que Claude Code est installé
npm install -g @anthropic-ai/claude-code

# Étape 3 — Lancer la session QA complète
claude --dangerously-skip-permissions \
  "Exécute le plan QA mobile complet défini dans mobile-qa-prompt.md.
   App Android : example.com.app.
   App iOS : example.com.app.
   Simulateur iOS UDID : [Simulateur_iOS_UDID] (iOS 26.4).
   Émulateur Android : Pixel_9_Pro_XL.
   ADB path : /<android_sdk_adb_directory>/Library/Android/sdk/platform-tools/adb.
   Emulator path : /<android_sdk_adb_directory>/Library/Android/sdk/emulator/emulator.
   Lance Android et iOS en parallèle.
   Produis tous les rapports dans ./qa-reports/.
   Utilise le swarm Ruflo avec topologie hiérarchique."
```
