Skip to content

Tests e2e — inventaire détaillé (spec → test → assertions)

Liste exhaustive de chaque test de la suite et de chaque vérification qu'il fait, flow par flow. À mettre à jour en même temps que e2e-coverage.md à chaque test ajouté/retiré. Conventions de lecture : UI = ce que l'utilisateur voit (sélecteur → attendu) ; contrat = ce que l'app a envoyé au backend mocké (mockBackend.captured/waitForCaptured). Sauf mention « connecté » (seedAuth), les tests tournent en invité.

15 specs documentés ici (par device ; la suite tourne sur mobile-chrome et mobile-safari). ⚠️ Inventaire en retard sur la suite réelle : session-expired.spec.ts, special-offers.spec.ts et logout-race.spec.ts (repro AUTH_BUG.md H1, vert depuis le fix epoch de session) n'ont pas encore de section (voir e2e-coverage.md pour leur résumé).


smoke.spec.ts — démarrage app (1 test)

home route boots and renders the app shell from the mocked backend

  • UI : #app non vide ; .dev-banner = e2e (preuve que la build tourne en mode Vite e2e, origines backend factices) ; barre de recherche (.gradient-search-wrapper) visible.
  • Contrat : GET /api/travels/seaside/categories et GET /travels capturés — et, implicitement, le teardown fail-loud prouve que tout le trafic de cold start a un handler.

test.use({ analyticsConsent: 'none' }) — désactive le seed auto 'refused' de la fixture pour exercer le chemin indécis (ADR 0019). « Zéro requête PostHog » est implicite dans chaque test de la suite (teardown fail-loud + token vidé en e2e).

  • UI : Home rendue dessous (travel-card visible) ; system-modal visible avec le heading Améliorer l'application et les boutons Accepter / Refuser ; URL porte overlay=system-modal (vraie entrée d'historique).

« Accepter » persists the decision and never asks again

  • UI : tap Accepter → modal fermé (toBeHidden), URL revenue sur /.
  • État : localStorage.analyticsConsent = {"status":"accepted"} ; page.reload() → pas de modal.

« Refuser » persists the refusal and never asks again

  • Miroir du précédent avec {"status":"refused"}.

dismissing via the native back keeps the question for the next launch

  • UI : goBack() → modal fermé sans quitter /.
  • État : décision toujours indécise → page.reload() re-présente le modal.

home.spec.ts — Home / (6 tests)

renders the catalogue feed from the mocked API

  • UI : 2 cards travel-card (rhin + colmar, par data-travel-slug) ; la card rhin contient nom + 7 jours ; card upcoming-departures avec Prochains départs et une tiny-travel-card rhin ; pas d'en-tête Résultats sans recherche active.
  • Contrat : GET /travels avec seaside=false.

a travel card navigates to the travel details and back returns to home

  • UI : tap card → URL /voyage/croisiere-sur-le-rhin, travel-title = nom ; goBack() → URL /, card à nouveau visible.

the seaside promo card navigates to the seaside listing

  • UI : le lien promo a[href="/seasides"] navigue vers /seasides et la card majorque s'affiche.

logged-in home greets the customer with their pending trip — connecté, world.bookings = [makeBooking()]

  • Contrat : GET /api/customers et GET /api/bookings au chargement.
  • UI : heading Mes voyages + nom du voyage réservé.
  • Refetch à chaque activation : nav card → détail → back → les compteurs captured('GET /api/bookings') et captured('GET /api/customers') augmentent (points/réservations crédités côté serveur remontent sans redémarrage).
  • Contrat : le dernier GET /api/customers porte le cache-buster ?_=.

the pending-trip card derives a seaside duration from the occurrence — connecté, booking balnéaire

  • Monde muté avant goto : la card « Mes voyages » (PendingTripCard) pointe sur un booking dont le voyage balnéaire a duration mis à undefined (réel : les Seaside n'ont pas de TravelDay[]).
  • UI : la card affiche la durée calculée depuis l'occurrence (start/end), pas le champ backend absent — durée attendue dérivée des mêmes instants de la fixture (robuste au DST / à la date de run).

promo cards interleave into a long catalogue feed — monde muté : 14 voyages catalogue

  • UI : 1ʳᵉ page = 10 cards ; scroll sur la dernière → infinite scroll charge la page 2 → 14 cards.
  • UI : la carte loyalty (Obtenez des points de fidélité) apparaît (slot fixe + slot interleavé tous les 4 après le 8ᵉ voyage) ; promo seaside La mer pour pas cher visible.

search.spec.ts — overlay recherche Home (8 tests)

tapping the search bar opens the ?overlay=search panel

  • UI : tap search-bar → URL contient ?overlay=search, search-input visible.

browser back closes the overlay without leaving home

  • UI : goBack() → param overlay disparu, input caché, toujours 2 cards (ADR 0009 : overlay = entrée d'historique).

a text search hits the search endpoint and renders matching results

  • Contrat : un GET /travels porte search=rhin et seaside=false.
  • UI : URL ?search=1 (marqueur), heading 1 Résultats, 1 card (rhin).

back from search results clears the filter and restores the full catalogue

  • UI : goBack() depuis les résultats → marqueur disparu, 2 cards, en-tête Résultats caché.

cancelling the overlay restores the previous filter snapshot

  • UI : après une recherche rhin, rouvrir l'overlay, taper un autre terme, sortir par le BackButton de l'overlay (= annulation) → les résultats 1 Résultats/card rhin sont intacts ; rouvrir → l'input est revenu à rhin (restauration du snapshot).

the Découvrir tab resets an active search to the full catalogue

  • UI : après une recherche (1 card), tap sur l'onglet Home de la bottom-nav (#bottomBar a[href="/"]) → 2 cards, en-tête Résultats caché.

a search with no matches shows the empty-state message

  • UI : heading 0 Résultats + copy aucun voyage ne correspond… ; le lien Consultez le catalogue complet recharge les 2 cards.

an interest category narrows the listing query

  • Contrat : tap bouton Courses 1 jour → un GET /travels porte category=courses-1-jour.

seaside.spec.ts — listing /seasides (4 tests)

lists only seaside travels, requested with seaside=true — monde muté : majorque duration: 0

  • Contrat : un GET /travels porte seaside=true.
  • UI : 1 seule card (majorque) ; la card d'un voyage sans durée n'affiche pas de chip jours (les vrais balnéaires arrivent sans durée — rotation 2/3 semaines).

a destination category fires the Baln-prefixed filter

  • Contrat : tap seaside-category Majorque → GET /travels avec seasideCategoryName=Baln MAJORQUE (format Baln {NOM}).

the plain text field searches the seaside listing on submit

  • Contrat : seaside-search-input + Enter → GET /travels avec search=majorque et seaside=true.
  • UI : heading 1 Résultats.

a seaside card opens its travel details

  • UI : tap card → URL /voyage/sejour-balneaire-a-majorque, travel-title correct.

travel-details.spec.ts/voyage/:slug + sous-pages (7 tests)

renders the travel from GET /travels/:slug

  • UI : travel-title, description, 7 jours ; highlights (Les vignobles de la Moselle), heading Prestations incluses + item ; barre du bas : Dès + suffixe p.p. en ch. double visible (multi-day) ; travel-book-cta = Réserver, href=/reservation/croisiere-sur-le-rhin.

a one-day trip prices without the double-room qualifier

  • UI : sur colmar, Dès + 89 CHF visibles, p.p. en ch. double caché (pas de chambre sur une course d'un jour).

the departures sub-page lists occurrence dates and is URL-addressable

  • UI : lien départs → URL /departures, arrêts Sion/Lausanne visibles ; back → détail ; chargement URL directe OK (sous-page = route, ADR 0012).

the itinerary sub-page opens when the travel has day-by-day content — monde muté (1 jour d'itinéraire)

  • UI : lien itinéraire → URL /itinerary, contenu du jour visible.

the comments sub-page shows approved reviews — monde muté (1 avis approuvé + 1 non approuvé)

  • UI : l'avis approuvé s'affiche ; le non-approuvé jamais (filtre backend).

« Réserver » enters the booking wizard on the departure-date step

  • UI : CTA → URL /reservation/:slug/departure-date, 2 wizard-date-option.

an unknown slug renders no travel content

  • Contrat : GET /api/travels/voyage-inexistant (404 mock).
  • UI : ni titre ni CTA (pas de crash).

auth-login.spec.ts — login / session / logout (5 tests)

submitting the empty form shows the French validation errors

  • UI : submit vide → Champ requis, URL reste /login.

a successful login stores the session and lands on home

  • Contrat : POST /api/authentification body exact { emailOrCustomerNumber, password } ; puis GET /api/customers + GET /api/bookings.
  • UI : atterrissage /, cards visibles.

a rejected login (401) stays on the login page, logged out — override 401

  • UI : reste sur /login ; contrat : aucun GET /api/customers (pas de fetch profil sur échec).

a seeded session survives a reload without logging in again — connecté

  • Contrat : après reload(), GET /api/customers rejoué ; aucun POST /api/authentification/refresh (JWT exp futur).

logout clears the persisted session and returns home — connecté

  • UI : heading Déconnexion, bouton Oui, déconnecter → URL /.
  • État : localStorage authemail/token nuls.

auth-signup.spec.ts — création de compte (4 tests)

an already-used email is rejected by the availability check — override 200

  • UI : blur du champ email → Cette addresse est déjà utilisée!.

a free email passes the availability check silently

  • Contrat : GET /api/customers/email-exists capturé ; UI : pas de message (404 = libre).

submitting the empty form surfaces the required-field errors

  • UI : Champ requis visible, URL reste /create-account.

a complete signup POSTs the customer and logs the new account in

  • Contrat : POST /api/customerseMail, birthdate en yyyy-MM-dd (jamais ISO datetime), civility='Madam', name/firstName, country='CH' ; puis auto-login (POST /api/authentification) + GET /api/customers.
  • UI : atterrissage /.

booking-wizard-catalog.spec.ts — wizard voyage catalogue multi-day (11 tests)

full happy path: 2 adults + 1 child through payment — connecté ; monde muté : catalog.vehicles = []

  • UI : CTA → departure-date ; 2 dates, la 1ʳᵉ affiche Dès 1'490 CHF ; étape accommodations : disposition familiale 2 adultes + 1 bébé, incrément ; accommodations-passengers : 3 formulaires ; le bébé n'a pas de champ téléphone ; seat-selection : seat-3/seat-4 à l'état taken, sélection de 3 sièges libres — le plan vient de occurrence.vehicles (le catalog est vidé : toute régression vers le catalog casserait l'étape) ; paiement : payment-submit désactivé tant que les cartes (Choix de paiement / Méthode / Contact) ne sont pas validées, puis activé ; totaux payment-full-price = payment-total = 3'600.00 CHF ; résumé liste les passagers avec la date de naissance en heure locale 12.04.1986 (une conversion UTC afficherait le 11 — détectable grâce au timezoneId: 'Europe/Zurich' épinglé dans playwright.config.ts).
  • Contrat PUT /api/customers (Valider de la carte Contact, connecté) : body scalaires uniquement (aucune valeur objet/array — le trim anti-hang iOS), birthdate en yyyy-MM-dd, clubMembershipStart/End absents (client sans Club — pas de 1970-01-01), fullName='Claire Dupont'.
  • Contrat POST /api/mobile/booking/initialize : occurrenceId='occ-rhin-1', status=999 (PendingWebOrMobile), seller='Mobile', numberOfPassengers=3, 3 passagers à p.price=1200 (3600/3), amountToPay=3600, roomTypeCategories non vide, customer.eMail du compte, returnUrl/booking/confirmation ; dates wire locales sans fuseau (ADR 0016) : les 3 birthdates saisies = exactement 1986-04-12T00:00:00/1984-09-03T00:00:00/2024-02-01T00:00:00, bookingDate et roomTypeCategories[0].days[0].checkIn au shape UNZONED_DATETIME.
  • Saferpay : popup sur https://saferpay.e2e.test/pay (saferpay-stub visible) ; deep-link retour → confirmation-title='Paiement confirmé', CTA Voir ma réservation, GET …/payment-callback capturé.

guest books the double room and creates the Horizon account at payment — invité

  • UI : formulaires passagers vierges (#firstname vide) ; carte Contact remplie à la main ; totaux 2'980.00 CHF.
  • Contrat initialize : p.price=1490 ×2, amountToPay=2980, customer.eMail/city = les données saisies (c'est ce payload qui crée le client Horizon invité) ; birthdates saisies exactes 1977-02-14T00:00:00/1979-08-22T00:00:00 (locales sans fuseau).
  • UI : confirmation guest (confirmation par e-mail, pas de CTA secondaire).

deposit + gift code: acompte and balance lines, flag and gift id in the payload — connecté ; monde muté : depositPercentage: 35

  • UI : radio Payer un acompte (sans % affiché) ; code NOEL200 validé → carte auto-repliée sur Bon appliqué 200.00 ; résumé : ligne Acompte (exact), Solde à régler ultérieurement, payment-balance='1'937.00 CHF', payment-total='843.00 CHF' (2'980×35% − 200).
  • Contrat initialize : paymentDepositOnly=true, amountToPay=paymentAmount=843, general.amount=2780 (total complet sur le booking), gifts=[{id:'gift-noel'}].

global supplements enter the totals, the summary and the payload — connecté ; monde muté : supplément flat 5 CHF/pers sur l'occurrence

  • UI : payment-full-price='2'990.00 CHF' (2'980 + 5×2) ; ligne dont Supplément carburant = 10.00 CHF ; payment-total='2'990.00 CHF'.
  • Contrat initialize : booking.globalSupplements=[{globalSupplementId:'gs-carburant', description, amount:10}] (Horizon ne l'applique que si le client l'envoie) ; p.price=1490 inchangé (supplément au niveau booking) ; amountToPay=2990.

loyalty points reduce the total and the balance is optimistically debited — connecté (120 pts)

  • UI : section Ajouter vos points de fidélité : Vous avez 120 points, bouton AjouterVous économisez, payment-total='2'968.00 CHF' (−12 CHF).
  • Contrat initialize : general.consumeLoyaltyPoints=true (tout-ou-rien), amountToPay=2968.
  • UI post-confirmation : retour Home → la carte loyalty affiche Obtenez des points de fidélité et plus 120 points de fidélité (solde optimiste 120−120=0 en attendant le débit du worker Horizon).

the room-count filter narrows dispositions only while expanded — monde muté : 6 dispositions pricées

  • UI : accordéon Filtrer par nombre de personnes visible (>5 dispositions) ; replié → 6 room-option ; ouvert + Adulte(s) monté à 3 → 2 dispositions (triple, quad — capacité ≥ groupe) ; replié à nouveau → 6 (filtre actif seulement accordéon ouvert).

insurance dropdown shows when the room type has an unpriced option — invité ; monde muté : option supérieure sans prix d'occurrence, assurances priceStart: 1

  • Régression sorrente-capri : le room type porte une option non tarifée sur l'occurrence ; la double option-less (2'980 / 2 = 1'490/pers) est réservée → le #insuranceId du formulaire passager est visible et propose Assurance annulation + Aucune assurance (le filtre price la chambre/option réservée — l'ancien raccourci rooms[0]/options[0] donnait prix 0 → tout exclu → select masqué).

the wizard ✕ truncates history to [Home, travel-details]

  • UI : entrée Home → détail → wizard → étape 2 ; tap ✕ (bouton Close) → URL /voyage/:slug ; un seul back → Home (historique tronqué à [Home, travel-details], ADR 0014). (Le test réaligne history.state.position à 0 après le 1ᵉʳ goto : la page Playwright boote sur about:blank, décalant le compteur d'une entrée vs un vrai cold start.)

each wizard step mirrors to the URL and back returns exactly one step

  • UI : avancer d'une étape → URL /accommodations ; goBack()/departure-date, la date sélectionnée a toujours la classe active (état conservé).
  • UI : goto /payment à froid → redirigé /departure-date (bookingConstructor non persisté).

a lone non-bookable occurrence short-circuits to the waiting list — monde muté : bookingState FULL

  • UI : CTA détail = Liste d'attente ; tap → URL /waiting-list, wizard-waiting-list-submit visible.

booking-wizard-oneday.spec.ts — wizard course d'un jour (5 tests)

guest books a day trip for two adults through payment — invité

  • UI : CTA → passenger-count directement (occurrence unique = étape date skippée) ; tier adulte 89 CHF, 69/49 visibles ; les 4 tranches client : Adolescent de 12 ans révolus à 18 ans, Enfant de 3 ans révolus à 12 ans, tier bébé = Bébé de moins de 3 ans et 0 CHF (toujours gratuit) ; wizard-continue désactivé à 0 passager.
  • UI passenger-names : pas de champ #insuranceId ni #mealPlan (parcours sans chambre) ; tap sur le « Suivant » pseudo-disabled avec formulaires vides → Champ requis surligné (validation guidante) ; options du #loadingStopId = liste plate de villes avec places restantes : exactement [Sélectionner, Lausanne (40 places), Sion (40 places)] (quota 50 − occupancy 10).
  • UI : 2 sièges sélectionnés ; paiement : carte Choix de paiement absente (pas d'acompte one-day) ; contact saisi à la main ; totaux 178.00 CHF.
  • Contrat initialize : occ-colmar-1, 2 passagers p.price=89 (prix par tranche), amountToPay=178, customer = données invité ; birthdates saisies exactes 1972-11-19T00:00:00/1975-03-05T00:00:00, bookingDate au shape UNZONED_DATETIME.
  • UI : confirmation guest (mail, un seul CTA).

logged-in customer books with prefilled details through payment — connecté

  • UI : passager 1 prérempli (prénom/nom/date) + téléphone prérempli depuis customer.mobile ; paiement prérempli (#contact-city='Sion', #contact-email du compte) ; PUT /api/customers au Valider de Contact ; totaux 89.00 CHF ; Vous gagnez 18 points de fidélité = mobileWinningPoints du calculateur backend (mock : ceil(8.9)×2 — pas de formule front).
  • Contrat initialize : 1 passager price=89, amountToPay=89, customer.eMail du compte ; birthdate préremplie du compte = shape UNZONED_DATETIME + jour calendaire de world.customer.birthdate (localDay — fixture midi-local, pas de T00:00:00).
  • UI : confirmation connectée (Voir ma réservation).

an occurrence without a vehicle plan skips seat selection — monde muté : noVehiclePlan: true

  • UI : après les infos passager, « Suivant » atterrit directement sur payment (étape sièges skippée).

a one-day travel without stops never asks for a departure place — monde muté : occurrence sans drives

  • UI : #loadingStopId absent du formulaire ; le formulaire se valide sans lieu de départ (required-si-proposé) et « Suivant » mène à seat-selection.

a gift larger than the trip price is capped and shows the leftover — override gift {value: 500, remainingValue: 300}

  • UI : après NOEL200 : Bon appliqué : 178.00 CHF (= remainingValue plafonné au prix du voyage, ni 500 ni 300), Il vous restera 122.00 CHF sur ce bon cadeau. (300−178), payment-total='0 CHF'. (UI seulement — pas de submit à 0.)

booking-wizard-seaside.spec.ts — wizard balnéaire + vol-sec (4 tests)

logged-in customer books the double room with a full-board upcharge — connecté

  • UI : CTA → accommodations directement (les deux étapes date skippées, occurrence unique) ; seule la chambre double pricée s'affiche (familiale sans prix d'occurrence cachée) ; passager 1 prérempli ; #mealPlan visible et obligatoire — #mealPlan et #insuranceId démarrent à '' (choix conscient forcé) ; passager 1 : full board (2, +140) + assurance réelle (ins-annulation, 45) ; passager 2 : demi-pension incluse (1), ins-none ; plan de sièges servi par catalog.vehicles (seat-3 taken) ; totaux 2'765.00 CHF (2'580+140+45) ; PUT /api/customers au Valider Contact.
  • Contrat initialize : occ-majorque-1, passengers[0].price=1430 (chambre+pension), [1].price=1290, mealPlan=[2,1] par passager, assurance exclue de p.price, amountToPay=2765, roomTypeCategories fabriqué sur acc-hotel-majorque ; dates wire : p1 prérempli = jour de world.customer.birthdate, p2 saisi = 1984-09-03T00:00:00 exact, bookingDate + checkIn du séjour fabriqué au shape UNZONED_DATETIME.
  • UI : confirmation connectée.

guest books the double room through payment — invité

  • UI : formulaires vierges ; pension incluse + ins-none ; contact à la main ; totaux 2'580.00 CHF.
  • Contrat initialize : p.price=1290 ×2, amountToPay=2580, customer.eMail invité ; birthdates saisies exactes 1988-01-20T00:00:00/1990-06-15T00:00:00.
  • UI : confirmation guest.

an accommodation without a junior band relabels the child tier « Enfant » — monde muté : accommodation child 6-16 / junior 0-0 / adult 16+, familiale pricée

  • UI : la disposition familiale affiche 2 adultes + 1 enfant (jamais bébé — seule tranche sous-adulte) ; après réservation de la familiale, le 3ᵉ formulaire porte le label Date de naissance (0-15 ans) (tranche élargie [0, childMax)) avec attrs min/max posés sur l'input date.

logged-in customer books one adult and a free baby through payment — vol-sec (SEASIDE sans accommodation), connecté, monde muté : makeVolSecSeasideTravel() poussé

  • UI : CTA → passenger-count (chemin room-less) ; tranches vol-sec : adulte 890 CHF, Enfant de 2 ans révolus à 12 ans 490 CHF, Bébé de moins de 2 ans 0 CHF ; étape passagers multi-type (fenêtre par groupe passenger-type-group) : l'adulte est prérempli du compte, #mealPlan/#insuranceId absents ; le bébé n'a pas de champ téléphone ; paiement : Bébés de moins de 2 ans listé, totaux 890.00 CHF (le bébé n'ajoute rien).
  • Contrat initialize : occ-vol-sec-1, adulte price=890, bébé price=0 et mealPlan absent, amountToPay=890 ; dates wire : bébé saisi = ${babyBirthday}T00:00:00 exact (const partagée fill/assert), adulte prérempli = shape UNZONED_DATETIME + jour de world.customer.birthdate.

payment-callback.spec.ts/booking/confirmation (5 tests)

  • Contrat : GET …/payment-callback avec bookingId=booking-new-1.
  • UI : Paiement confirmé, sous-titre espace client, Voir ma réservation + Retour à l'accueil ; le CTA primaire deep-link /booking/details/booking-new-1.

success as a guest promises the e-mail confirmation instead

  • UI : sous-titre confirmation par e-mail, CTA unique Retour à l'accueil, pas de secondaire (route détail auth-gated).

a CANCELED transaction renders the failure copy — override callback success:false/CANCELED

  • UI : Échec du paiement, Veuillez réessayer, Retour à l'accueil.

a callback endpoint error still renders the failure screen — override 500

  • UI : Échec du paiement.

leaving the confirmation replaces history — back cannot re-enter it — connecté

  • UI : quitter via le CTA puis goBack() → l'URL ne re-matche jamais /booking/confirmation (page terminale en router.replace, ADR 0009).

booking-details.spec.ts/booking/details/:bookingId (5 tests)

  • Contrat : le miss du store au cold start déclenche GET /api/bookings (retry du lookup — plus d'écran d'erreur post-paiement).
  • UI : heading = nom du voyage, 7 jours, 1 voyageur, Claire Dupont ; bouton Facture (exact — booking CONFIRMED).

the invoice download carries a cache-buster

  • Contrat : tap FactureGET /api/bookings/booking-1/confirmation-invoice avec ?_= non nul (sinon le cache HTTP natif resservirait un PDF factice pré-worker).

a pending booking greys the invoice button without firing the request — booking PENDING_WEB_OR_MOBILE

  • UI : bouton Facture bientôt disponible, classe pseudo-disabled (tappable, pas disabled natif).
  • Contrat : tap → aucune requête invoice capturée.

returning to the page picks up a booking refreshed by Home

  • UI : passager renommé côté serveur (override GET /api/bookings) pendant un aller Home (le back flottant est un lien /) → retour via la PendingTripCard → la page kept-alive re-capture le booking : Clara Dupont affiché.
  • UI : goto sans session → URL /login (requiresAuth).

loyalty-points.spec.ts/loyalty-points (4 tests)

the balance card shows points, CHF value, pending chip and expiry alert — connecté ; customer muté : 1050 pts, 60 en attente, un lot de 200 expirant

  • UI : heading Votre solde de points, 1'050, Équivalent à … 105 CHF ; chip +60 points en attente → tap → copy crédités automatiquement au retour ; bande expiration 200 points + expirent le ; barre de palier modulo 1000 : 50 pts (exact) et Plus que … 950 points.

a round balance shows the palier call-to-action instead of a countdown — connecté, 1000 pts

  • UI : la carte affiche le total réel 1'000 ; la barre (1000 % 1000 = 0) affiche Cumulez des points pour atteindre votre prochain palier de 100 CHF !.

the history caps at 3 movements and expands inline — connecté, 5 mouvements

  • UI : 3 .loyalty-history-item max, libellés FR de l'enum (Achat de voyage, Utilisation de points visibles ; Création de compte caché) ; Voir tout l'historique → 5 items sans refetch (compteur GET /api/customers inchangé) ; Réduire → 3.

a guest sees the program explanation without the balance blocks — invité

  • UI : Comment ça fonctionne visible ; heading solde et Historique des mouvements cachés ; simulateur Calculez vos rabais : défauts 5'000 CHF × app = 1'000 points ; bascule Site web / agence500 (le ×2 mobile).

personal-info.spec.ts/profile/edit (2 tests)

the login email and client number are readonly — connecté

  • UI : #account-email = email du compte + attr readonly ; #account-navno = C-10042 + readonly.

« Enregistrer » PUTs a scalar-only, date-only profile — connecté

  • UI : #city prérempli Sion, édité en Martigny, Enregistrer.
  • Contrat PUT /api/customers : city='Martigny' ; birthdate en yyyy-MM-dd strict ; aucune valeur objet/array dans le body (trim anti-hang iOS). Trou connu, non asserté : sur ce chemin, le formulaire seede les dates Club absentes avec epoch (new Date(0) dans PersonalInfoForm) → le PUT porte clubMembershipStart/End = 1970-01-01. Le comportement correct (champs omis) est verrouillé sur la carte Contact du paiement (booking-wizard-catalog.spec.ts).

Voir aussi

  • e2e-coverage.md — vue d'ensemble par domaine + les trous connus (le backlog).
  • e2e-overview.md — comment lire un spec (UI vs contrat), le harnais.
  • e2e-world.md — le dataset par défaut et les factories que ces tests mutent.

Contributors

No contributors

Changelog

No recent changes