Aller au contenu

Guide — Tests terrain complets PWA / Mode hors-ligne

Projet : ESM Caritas Bénin
Date : 2026-06-17 16:17
Contexte : La plateforme ESM est désormais une PWA avec mode hors-ligne complet (Workbox + Dexie + SyncManager). Ce guide décrit comment valider le comportement offline avant un déploiement terrain.


Prérequis

  • Chrome (Desktop ou Android) — Firefox ne supporte pas encore l'installation PWA
  • Safari pour les tests iOS
  • Backend ESM opérationnel sur http://localhost:4100 (ou le VPS 178.105.46.57)
  • Un compte utilisateur valide pour se connecter

Phase 1 — Tests DevTools (sur poste de développement)

Le service worker ne s'active pas en npm run dev. Toujours tester avec un build de production.

Lancer l'application en mode production local

# Depuis la racine du projet
npm run build -w FrontEnd
npx serve FrontEnd/dist -l 3100
# Ouvrir : http://localhost:3100

Test 1 — Manifest et installation PWA

Étapes : 1. Ouvrir Chrome DevTools (F12) → onglet ApplicationManifest 2. Vérifier : nom ESM Caritas Bénin, icônes présentes (192 / 512 / 180), couleur de thème #C00000 3. Onglet Service Workers → statut doit être activated and running 4. Dans la barre d'adresse Chrome, l'icône ➕ (installer l'app) doit apparaître 5. Cliquer sur ➕ → l'app s'installe sur le bureau ou le dock

Résultat attendu : Pas d'erreur dans le manifest. SW actif. App installable.


Test 2 — App shell hors-ligne (cache Workbox)

Étapes : 1. DevTools → onglet ApplicationService Workers → cocher Offline 2. Recharger la page (F5)

Résultat attendu : La page se charge normalement — pas d'écran d'erreur réseau Chrome (ERR_INTERNET_DISCONNECTED). L'interface apparaît, même sans connexion.

  1. Décocher Offline après le test.

Test 3 — Hydratation Dexie après chargement en ligne

Étapes : 1. Se connecter à l'app en ligne 2. Naviguer vers la liste des enfants — attendre que la liste se charge 3. DevTools → ApplicationStorageIndexedDBesm_offline → table enfants

Résultat attendu : - Les enfants chargés sont visibles dans IndexedDB - Table syncMeta contient une entrée lastSync avec un horodatage ISO récent

Si IndexedDB est vide après le chargement, le listerAvecCache ne reçoit pas de données du serveur. Vérifier la réponse réseau dans l'onglet Network.


Test 4 — Lecture hors-ligne depuis le cache local

Étapes : 1. Charger les listes (enfants, tuteurs, maîtres coraniques, accompagnements) en ligne 2. Cocher Offline dans DevTools 3. Naviguer vers chaque module

Résultat attendu : - Le bandeau rouge Mode hors-ligne apparaît en haut de page - Les listes s'affichent depuis Dexie (données identiques à ce qui était chargé en ligne) - Cliquer sur un enfant → sa fiche s'affiche (depuis le cache) - Pas d'écran blanc, pas de message d'erreur réseau

Modules à tester : - [ ] Enfants — liste + fiche détail - [ ] Tuteurs — liste + fiche détail - [ ] Maîtres coraniques — liste + fiche détail - [ ] Accompagnements — liste + fiche + liste des suivis - [ ] Évaluations — liste + dernière évaluation par enfant - [ ] Formations — liste + fiche + participations


Test 5 — Création hors-ligne (mutation enfilée)

Étapes : 1. Rester en mode Offline 2. Créer un nouvel enfant → remplir le formulaire → valider

Résultat attendu : - Message "Saisie enregistrée localement — sera synchronisée à la reconnexion" (pas d'erreur) - DevTools → IndexedDB → table pendingMutations → 1 entrée visible avec entite: "ENFANT", operation: "CREATE", et un id CUID généré côté client - Le bandeau indique 1 saisie en attente de synchronisation

Répéter avec : un suivi d'accompagnement, une évaluation, une formation.


Test 6 — Synchronisation automatique au retour réseau

Étapes : 1. Depuis l'état du Test 5 (1+ mutation en attente) 2. Décocher Offline (retour en ligne) 3. Observer le bandeau en haut de page

Résultat attendu (dans les 2 secondes) : - Bandeau jaune animé Synchronisation en cours… - Bandeau vert Synchronisation terminée — données à jour (pendant 3 secondes) - Table pendingMutations → vide - Vérifier que l'enfant créé existe sur le serveur :

# Remplacer <token> par un token JWT valide
curl -s -H "Authorization: Bearer <token>" \
  http://localhost:4100/api/v1/esm/enfants \
  | python3 -m json.tool | grep -A3 "nom"

Test 7 — Résolution de conflits (même donnée, deux agents)

Ce test simule deux agents terrain qui modifient le même enfant hors-ligne simultanément.

Étapes : 1. Ouvrir l'app dans deux onglets Chrome (simule deux appareils) 2. Charger la fiche d'un même enfant dans les deux onglets 3. Passer les deux onglets en mode Offline (DevTools de chaque onglet) 4. Dans l'onglet 1 : modifier le champ observations"Version Agent A" 5. Dans l'onglet 2 : modifier le même champ → "Version Agent B" 6. Remettre l'onglet 1 en ligne → attendre la sync (bandeau vert) 7. Remettre l'onglet 2 en ligne → attendre la sync

Résultat attendu : - La version de l'onglet 1 (timestamp plus ancien) est reçue par le serveur en premier - La version de l'onglet 2 (timestamp plus récent au moment de la sync) écrase la première — last-write-wins par updatedAt - Dans les logs backend, chercher statut: "MIS_A_JOUR" pour le deuxième envoi

C'est le comportement voulu : la dernière modification gagne. Si le programme nécessite un arbitrage humain des conflits, c'est une évolution à planifier.


Phase 2 — Tests sur téléphone réel

Exposer l'application depuis le poste local

# Option A : via localtunnel (rapide, pas de compte nécessaire)
npx localtunnel --port 3100
# → génère une URL https://xxx.loca.lt accessible depuis le téléphone

# Option B : si le frontend est déployé sur le VPS
# → tester directement sur https://tekdhi.dev (ou le sous-domaine configuré)

Checklist Android (Chrome)

# Action Résultat attendu
1 Ouvrir l'URL dans Chrome Android Page charge, interface visible
2 Menu Chrome → Ajouter à l'écran d'accueil Icône rouge ESM créée
3 Ouvrir depuis l'écran d'accueil App plein écran, pas de barre d'URL Chrome
4 Activer le mode Avion Bandeau rouge Mode hors-ligne apparaît
5 Naviguer dans les listes Données locales affichées normalement
6 Créer un enfant Message "enregistrée localement"
7 Désactiver le mode Avion Bandeau jaune puis vert → données synchronisées
8 Vérifier sur le serveur via curl ou l'interface admin L'enfant est créé

Checklist iOS (Safari)

# Action Résultat attendu
1 Ouvrir l'URL dans Safari Page charge
2 Bouton Partager (carré avec flèche) → Sur l'écran d'accueil Icône apple-touch-icon.png 180×180 utilisée
3 Ouvrir depuis l'écran d'accueil Mode standalone (sans barre Safari)
4 Mode Avion Bandeau rouge, navigation fonctionnelle
5 Saisie hors-ligne Mutation enfilée, message local
6 Retour réseau Sync automatique déclenchée

Note iOS : Le service worker est supporté sur iOS 16.4+. Sur les versions antérieures, l'app fonctionne en ligne mais pas en mode hors-ligne.


Test en zone à faible connectivité (throttling)

Simuler une connexion 2G pour valider les performances en conditions réelles :

DevTools → Network → sélectionner Slow 3G ou 2G

Vérifier que : - [ ] L'app reste utilisable (pas de timeout UI) - [ ] La sync passe malgré la latence (peut prendre 10-30 secondes) - [ ] Le bandeau Synchronisation en cours… reste affiché le temps nécessaire


Critères de validation finale

L'app est prête pour le terrain quand ces 3 scénarios passent sans erreur :

Scénario Critère
Zone sans réseau L'agent ouvre l'app, consulte les données, saisit un enfant — zéro message d'erreur
Retour en zone couverte La sync se déclenche automatiquement, les données arrivent sur le serveur
Installation iOS Icône sur l'écran d'accueil, app plein écran sans navigateur visible

Que faire si un test échoue ?

Symptôme Cause probable Action
App ne charge pas en mode Offline SW non enregistré (test en mode dev) Utiliser npm run build + npx serve
IndexedDB vide après navigation listerAvecCache ne reçoit pas de données Vérifier la réponse du serveur dans Network
Mutation non visible dans pendingMutations Pas en mode Offline au moment de la saisie Vérifier navigator.onLine dans la console
Bandeau sync ne disparaît pas Erreur lors du flush vers /sync Consulter les logs backend — chercher ERREUR_SERVEUR
Icône iOS pixelisée ou absente apple-touch-icon.png absent de public/ Vérifier FrontEnd/public/apple-touch-icon.png
Conflit non résolu correctement updatedAt non injecté dans la mutation Vérifier que generateOfflineId + updatedAt sont bien dans le payload

Fichiers clés de l'implémentation

FrontEnd/src/shared/infrastructure/db/
  ESMDatabase.ts          ← Schéma Dexie (8 tables + pendingMutations + syncMeta)
  offlineCache.ts         ← Helpers listerAvecCache / obtenirAvecCache

FrontEnd/src/shared/infrastructure/sync/
  SyncManager.ts          ← Flush mutations + pull delta, écoute navigator.onLine

FrontEnd/src/shared/infrastructure/http/
  offlineQueue.ts         ← Détection URL → entité, enfilage dans pendingMutations

FrontEnd/src/shared/components/ui/
  OfflineBanner.tsx       ← Bandeau rouge/jaune/vert

FrontEnd/public/
  icon-192.png            ← Icône Android
  icon-512.png            ← Icône Android (maskable)
  apple-touch-icon.png    ← Icône iOS (180×180)