Exécution — Tests E2E module Parents (+ modules liés)¶
Note mise à jour en temps réel au fur et à mesure de la progression. Compte utilisé :
test.agent@projetesm.test/ commune KANDI, profil PSYCHOLOGUE (mêmes droits que la session de bugs Parents qui a précédé ce test).
Origine¶
Suite à une session de correction manuelle de 2 bugs sur le module Parents (validation Zod sur
téléphone/commune vides en modification, message d'erreur de mutation persistant entre deux
enregistrements différents — voir [[project_rbac_frontend_permission_based]] pour le contexte RBAC
général), Bernard a demandé une couverture E2E réelle du module Parents et de ses modules liés,
avec ce même pattern Test_E2E/*.mjs (Playwright brut + helpers/auth.mjs, pas
@playwright/test) que les sessions précédentes — aucune suite @playwright/test n'existait
pour Parents avant cette session (vérifié : FrontEnd/tests/e2e/ n'a pas de dossier parents/,
seule couverture existante = Test_E2E/08_acteurs_terrain.mjs, un simple contrôle de chargement de
page sans CRUD).
Module lié identifié par lecture du schéma Prisma (Enfant.parentId → Parent, relation inverse
Parent.enfants) : Enfants, via le champ <select id="parentId"> dans
EnfantsPage.tsx/EnfantDetailPage.tsx. Le use case frontend AssocierEnfantUseCase existe mais
n'est câblé sur aucune page (grep : zéro usage de useAssocierEnfant dans tout src/Modules) —
donc hors périmètre testable via UI réelle (code mort, pas un flux utilisateur).
Scripts créés¶
Test_E2E/24_parents.mjs— CRUD Parents + 2 régressions ciblées :- Création avec téléphone/commune vides (bug corrigé :
"Le téléphone ne peut pas être vide"ne doit plus apparaître) - Modification sans toucher un champ optionnel resté vide (même bug, chemin modification)
- Persistance de l'erreur de mutation entre deux fiches différentes — reproduite avec
context.setOffline(true)(vraie coupure réseau simulée par Playwright, pas un mock) pour forcer une erreur réseau réelle sur la 1ère fiche, puis vérifier qu'elle ne fuit pas sur la 2e Test_E2E/24b_parents_lien_enfant.mjs— module lié Enfants : création d'un enfant avec un Parent assigné via le select, vérification sur la fiche détail (/enfants/:id) que le nom du parent s'affiche correctement (et pas l'ID brut ou un champ vide)
Bug trouvé et corrigé — parent créé sans commune devient invisible pour son créateur¶
Isolé via curl pur (aucune variable navigateur), en observant que 24_parents.mjs échouait sur
"parent visible dans la liste après création" malgré un POST 201 réussi et le enregistrement bien
présent en base (vérifié par requête Prisma directe dans le conteneur) :
Cause : communeCode est un champ optionnel sur Parent (CreerParentSchema,
parentSchema frontend). Un parent créé sans commune a communeCode: null. Mais
ListerParentsUseCase.executerLogique force filtres.communeCode = donnees.communeCodeScope pour
tout utilisateur scopé à une commune (ServiceAutorisationDonnees.construireScopeCommune) — un
enregistrement à communeCode: null ne matche jamais un filtre communeCode = 'KANDI'. Résultat :
un agent de terrain scopé à une commune qui crée un parent sans préciser la commune ne peut plus
jamais le revoir lui-même (seul un profil ACCES_TOUTES_COMMUNES le verrait, en listant sans
filtre). Le module Enfants avait déjà résolu ce problème en rendant communeCode obligatoire à
la création (enfants.schemas.ts : "Le code commune est requis") — Parents ne l'avait pas fait,
incohérence entre les deux modules jumeaux.
Corrigé (aligné sur le pattern Enfants) :
- Backend : CreerParentSchema.communeCode passé de .optional().nullable() à
z.string().min(1, 'La commune est requise') (parents.schemas.ts) ; CreerParentUseCase
utilise désormais donnees.communeCode! (non-null garanti par le schéma, même pattern que
genre! déjà en place).
- Frontend : parentSchema.communeCode passé de .optional() à
z.string().min(1, 'La commune est requise'), label "Commune " + affichage de l'erreur
(ParentsPage.tsx).
- ModifierParentSchema.communeCode non touché* (reste .optional().nullable() avec message
"ne peut pas être vide" si fourni vide) — mais comme le formulaire d'édition envoie toujours
la valeur du select, et que le select est maintenant validé requis côté client sur le même
schéma partagé création/édition, l'effet pratique est le même : impossible de sauvegarder sans
commune, dans les deux sens.
Redéployé (deploy.sh --target backend) et reverifié via 24_parents.mjs : 12/12 ✅.
Piège méthodologique rencontré et documenté (pas un bug applicatif)¶
context.setOffline()ne simule pas une erreur réseau dans cette app :apiClient.tsa un vrai mode offline-first (SyncManager/offlineQueue) — une mutation hors-ligne est mise en file d'attente et renvoie un succès simulé ("Saisie enregistrée localement..."), sans jamais lever d'erreur. Comportement voulu (résilience terrain, connectivité rurale), pas un bug. Corrigé dans le script de test : interception de la requête PUT viapage.route()pour forcer un vrai code 500, ce qui déclenche le chemin d'erreur réel sans toucher au mode offline.- Faux négatif Playwright : un
locator('text=...')non scopé matchait le doublon caché de la vue mobile (div.sm:hidden, rendue dans le DOM même à un viewport desktop, juste masquée en CSS) au lieu de la ligne réellement visible du tableau desktop. Le parent était bien créé et affiché (confirmé par capture d'écran) — seul le test était mal ciblé. Corrigé en scopant le locator àtableavant de chercher la ligne.
Résultat — 24_parents.mjs (CRUD + régressions)¶
12/12 ✅ après les 2 corrections ci-dessus (orpheline commune + test réseau) : navigation, blocage création sans commune, création avec téléphone vide, modification sans toucher un champ optionnel vide, erreur serveur (500) affichée puis non persistante sur un enregistrement différent.
Journal d'exécution¶
- 17:58 — Début de session, lecture mémoire + exploration schéma Prisma (relation
Enfant.parentId → Parent) pour identifier le module lié - 18:09 — Scripts
24_parents.mjset24b_parents_lien_enfant.mjsécrits (patternTest_E2E/*.mjsexistant,helpers/auth.mjs) - 18:10 — 1er run : Playwright Chromium absent, installé (
npx playwright install chromium) - 18:12 — 2e run : bug orpheline-commune découvert (voir ci-dessus), isolé via
curlpur - 18:15 — Fix backend + frontend (commune requise), redéploiement backend
- 18:20 — Faux négatif Playwright identifié et corrigé (vue mobile cachée)
- 18:22 — 24_parents.mjs : 12/12 ✅
- 18:23 —
24b_parents_lien_enfant.mjs: même faux négatif Playwright (vue mobile cachée) sur "enfant visible dans la liste", corrigé de la même façon (locator scopé àtable) - 18:23 — 24b_parents_lien_enfant.mjs : 4/4 ✅ — parent bien assigné via le select
#parentIdd'EnfantsPage.tsx, affiché correctement sur la fiche détail (/enfants/:id, section "Rattachements") - 18:23 — Nettoyage des données de test (1 enfant + 1 parent
E2E_*), un enfantE2E_*d'une session antérieure conservé (référencé par une contrainte FKappuis_alimentaires, hors périmètre de cette session, convention du dossier = pas de nettoyage forcé) - Session terminée : 16/16 assertions ✅ sur les 2 scripts, 1 bug réel trouvé et corrigé (commune orpheline), 2 pièges méthodologiques de test documentés (offline-first vs erreur réseau, faux négatif Playwright vue mobile/desktop)
Bilan¶
| Script | Résultat | Bugs applicatifs trouvés |
|---|---|---|
24_parents.mjs |
12/12 ✅ | 1 (commune orpheline — corrigé) |
24b_parents_lien_enfant.mjs |
4/4 ✅ | 0 |
Les 2 bugs signalés par Bernard en amont de cette session (validation téléphone/commune vides, persistance de l'erreur de mutation entre 2 fiches) sont confirmés corrigés par ces tests. Un 3e bug, non signalé, a été trouvé par le test lui-même (commune orpheline) — exactement le scénario que la demande de couverture E2E visait à prévenir.
Fichiers créés : Test_E2E/24_parents.mjs, Test_E2E/24b_parents_lien_enfant.mjs (conservés,
pas de nettoyage, convention du dossier).
Bug n°4 trouvé APRÈS la session de tests — effacer un téléphone existant ne l'efface jamais¶
Bernard a testé manuellement juste après le commit général : modifier un parent en vidant son
téléphone → il réapparaît systématiquement après enregistrement, à 3 reprises. Non couvert par
24_parents.mjs, qui testait uniquement "laisser vide dès le départ", jamais "vider un champ
déjà rempli" — angle mort identifié directement par l'angle mort du test, pas par le test lui-même.
Cause : ParentRepositoryPrisma.modifier() (backend) n'ajoute un champ à l'objet data du
prisma.parent.update() que si !== undefined — un champ absent de la requête PUT est traité
comme "ne pas y toucher", pas comme "vider". Le fix de la session précédente
(values.telephone || undefined dans onSubmit, pour éviter l'erreur de validation Zod sur "")
convertissait un champ vidé en undefined → JSON.stringify l'omet du corps de la requête → le
backend ne touche jamais la colonne → l'ancienne valeur survit indéfiniment. Le fix précédent avait
résolu "vide dès le départ" mais cassé "vider un champ déjà rempli" — même symptôme apparent
("téléphone vide"), cause exactement inverse.
Corrigé : values.telephone || null (et niveauInstruction, activitePrincipale) au lieu de
|| undefined — un champ vidé est désormais envoyé comme null explicite, que le backend accepte
déjà (.optional().nullable() sur les 3 schémas Zod concernés, aucun changement backend requis).
Types frontend (CreateParentForm) élargis à string | null.
Vérifié avec un 3e script dédié, Test_E2E/24c_parents_effacer_telephone.mjs (5/5 ✅) :
téléphone pré-rempli lu dans le formulaire, corps de requête PUT intercepté pour confirmer
telephone: null (pas absent, pas ""), puis rechargement complet de la page (pas juste
vérification du cache local) pour confirmer la persistance réelle en base.
Redéployé (deploy.sh --target frontend) immédiatement, Bernard étant sur le site de production
au moment du signalement.
Leçon : undefined vs null vs absence de clé n'est jamais un détail cosmétique sur un
endpoint PATCH-like qui fait if (champ !== undefined) data.champ = ... — trois sémantiques
distinctes ("ignorer", "vider", "valeur invalide") se resserrent facilement en une seule par
réflexe de nettoyage frontend. Réflexe à vérifier sur tout futur champ optionnel effaçable dans ce
projet : que devient une valeur VIDÉE (pas juste jamais renseignée) après un cycle
remplir→vider→enregistrer→recharger ?
Bugs n°5-8 — module Accompagnements (hors périmètre initial de cette session)¶
Bernard a signalé 4 problèmes supplémentaires en testant manuellement, hors du périmètre explicitement demandé pour cette session (Parents + module lié Enfants uniquement — le module Accompagnements/Prise en charge holistique n'a jamais été dans le champ des tests E2E écrits ici). Question légitime posée en retour : "quelle utilité des tests E2E si on a ces erreurs ?" — réponse honnête : ces 4 modules n'ont simplement jamais été testés par cette session, ni par aucune suite E2E existante à ma connaissance.
Bug n°5 (critique, confirmé et corrigé) — les 4 volets de la fiche enfant n'affichent jamais leurs données¶
Symptôme : ajouter un Appui alimentaire, un Soutien psychologique, une Prise en charge sanitaire ou une Séance d'alphabétisation à un enfant → la création réussit (confirmé en base), mais le compteur reste à 0 et la liste affiche "Aucun ... enregistré" sur la fiche enfant.
Isolé en 3 étapes : (1) curl direct confirme que le POST crée bien l'enregistrement en
base ; (2) curl sur le GET de listing renvoie bien l'enregistrement avec le bon total ; (3)
Playwright avec interception réseau confirme que le navigateur reçoit la bonne réponse JSON — donc
le bug est dans le parsing frontend, pas dans la requête ni la réponse serveur.
Cause : les 4 repositories frontend (AppuiAlimentaireRepository, SoutienPsychologiqueRepository,
PriseEnChargeSanitaireRepository, SeanceAlphabetisationRepository) lisaient
(res.data as any)?.items — mais l'enveloppe réelle de l'API imbrique un niveau de plus :
res.data.donnees.items. res.data n'a jamais de propriété .items directe → toujours [].
Bug de copier-coller identique dans les 4 fichiers ; deux fichiers voisins du même dossier
(SuiviRepository, CasOrienteRepository) avaient déjà le bon pattern (res.data?.donnees),
prouvant que le pattern correct existait déjà dans le module au moment où ces 4-là ont été écrits.
Corrigé : const donnees = (res.data as any)?.donnees; const items = donnees?.items ?? [];
dans les 4 fichiers. Vérifié avec un script de repro dédié (session propre Playwright, sans état
résiduel) : compteur passé de "(0)" à "(1)" sur les 4 volets après correction, pour un enfant réel
(Luc ZINSOU, créé par Bernard).
Bug n°6 (UX, corrigé) — champ "Psychologue" du Soutien psychologique en texte libre¶
Bernard : "dans une commune il n'y a qu'un seul psychologue, pourquoi son nom n'est pas proposé
automatiquement, et s'il y en avait deux une liste devrait s'afficher". Vérifié : le champ était un
<input type="text"> pur, alors que usePsychologues() (liste des utilisateurs au profil
PSYCHOLOGUE, avec communeCode) existait déjà et était utilisé ailleurs dans la même page
(EnfantDetailPage.tsx, formulaire "Cas orienté / référencement").
Corrigé : champ transformé en <select> alimenté par usePsychologues(), filtré par la
commune de l'enfant (repli sur la liste complète si aucun résultat pour cette commune), avec
auto-sélection si un seul candidat. Un lien "Psychologue hors liste ?" bascule vers la saisie
libre historique (le champ reste une chaîne texte en base, aucune migration de schéma). Vérifié
sur Luc ZINSOU (KANDI, un seul psychologue "TEST PSYCHOLOGUE" en base) : select proposé avec cette
seule option, pré-sélectionnée automatiquement.
Bug n°7 (non reproduit, probable artefact de session) — fiche de Luc ZINSOU "impossible à charger"¶
Non reproduit : curl direct sur GET /enfants/:id renvoie 200 ; une session Playwright
neuve (sans service worker ni storage résiduel) charge la fiche sans erreur console ni requête en
échec. Explication la plus probable : plusieurs déploiements frontend se sont succédé pendant cette
même plage horaire (fix téléphone vidé, fix commune obligatoire) pendant que l'onglet de Bernard
était probablement resté ouvert avec un ancien bundle JS en mémoire — même classe de problème que
le bug PWA silencieux diagnostiqué plus tôt dans la session ([[project_parents_commune_obligatoire]]
et le fix registerType: 'prompt'). À reconfirmer si le problème persiste après un rechargement
forcé (Ctrl+Shift+R).
Bilan bugs 5-8¶
| # | Symptôme | Statut |
|---|---|---|
| 5 | 4 volets Accompagnements invisibles après création | ✅ corrigé, vérifié, redéployé |
| 6 | Champ Psychologue en texte libre, pas de suggestion | ✅ corrigé (select + auto-sélection), vérifié |
| 7 | Fiche Luc ZINSOU "impossible à charger" | ❌ non reproduit — probable artefact de déploiement, à reconfirmer |
Redéployé (deploy.sh --target frontend) immédiatement après vérification.
Amélioration n°9 — champ "Agent" du Suivi de la prise en charge¶
Bernard a proposé, en discussion (pas un bug rapporté) : le champ "Agent" du Suivi (fiche enfant et fiche insertion — micro-subvention/installation CQM) était un texte libre à retaper à chaque fois, alors que chaque agent saisit ses propres activités. Accord trouvé après échange : select pré-rempli avec l'agent connecté, mais modifiable vers un collègue de la même commune (pas n'importe quel agent), avec repli en saisie libre pour le cas rare hors liste.
Implémenté en réutilisant l'infrastructure existante plutôt qu'en la dupliquant :
l'endpoint GET /api/v1/esm/formations/agents (déjà utilisé pour le champ "Animé par" des
formations) scope déjà automatiquement par la commune de l'utilisateur connecté
(context.getCommuneCode() côté handler) et filtre sur les profils PSYCHOLOGUE/ASSISTANT_SOCIAL
— exactement le besoin, aucune route ni use case backend à créer. Le hook frontend useAgents()
(module Formations) est importé directement dans EnfantDetailPage.tsx (Suivi holistique) et
InsertionDetailPage.tsx (Suivi micro-subvention/CQM), cohérent avec le pattern d'imports
inter-modules déjà en usage dans ce frontend (ex. useParents importé depuis le module Enfants).
Vérifié sur les deux fiches (Luc ZINSOU / KANDI et un parcours d'insertion réel) : select affiche
les 3 agents de KANDI (AgentMeal E2E, TEST PSYCHOLOGUE, TestAssistant Agent), pré-sélectionné sur
"TEST PSYCHOLOGUE" (l'utilisateur connecté). Lien "Agent hors liste ?" bascule vers la saisie
libre historique. Aucun changement de schéma (le champ agentSuivi reste une chaîne texte en
base — le select écrit juste "nom prenom" dedans, comme pour le champ Psychologue plus haut).
Redéployé (deploy.sh --target frontend).