Aller au contenu

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)

  1. context.setOffline() ne simule pas une erreur réseau dans cette app : apiClient.ts a 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 via page.route() pour forcer un vrai code 500, ce qui déclenche le chemin d'erreur réel sans toucher au mode offline.
  2. 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 à table avant 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.mjs et 24b_parents_lien_enfant.mjs écrits (pattern Test_E2E/*.mjs existant, 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 curl pur
  • 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 #parentId d'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 enfant E2E_* d'une session antérieure conservé (référencé par une contrainte FK appuis_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 undefinedJSON.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).