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 VPS178.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 Application → Manifest
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 Application → Service 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.
- 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 → Application → Storage → IndexedDB → esm_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
listerAvecCachene 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)