Ajoute une couche end-to-end où un Chromium drive la SPA + API ensemble
contre une DB Postgres séparée, avec Stripe entièrement mocké au niveau
API. 6 scénarios couverts (signup + onboarding + 4 sur le billing trial).
Architecture :
- DB `rubis_test_e2e` séparée, TRUNCATE entre tests (~50 ms reset)
- Routes test-only `/__test__/*` gated par NODE_ENV=test_e2e
(reset, install Stripe mock, fire webhook, lire org state, last-org)
- Stripe mocké via __setStripeForTests — pas d'appel réseau
- Playwright spawn API + SPA automatiquement (webServer config)
- CORS étendu à test_e2e pour le cross-origin localhost:5173 → :3333
Scénarios :
- signup.spec.ts : signup → onboarding 3 étapes → dashboard (assert rubis hero)
- billing-trial.spec.ts :
• démarrer essai 14j → redirect Stripe Checkout (mock)
• fallback Free 2 factures continue l'onboarding
• webhook checkout.completed → org en trialing + trial_ends_at
• retour ?trial=cancel après abandon
• inspection DB : stripeCustomerId posé après start-trial
Scripts :
- pnpm e2e (headless)
- pnpm e2e:headed (Chromium visible)
- pnpm e2e:ui (mode interactif Playwright)
- pnpm e2e:setup (crée + migre rubis_test_e2e via docker exec)
Documentation : docs/tech/e2e-tests.md — architecture, scénarios,
extensions, CI, troubleshooting.
Limites assumées :
- L'UI Stripe Checkout (3DS, formulaire CB) n'est pas testée — externe.
Pour ça : playbook manuel docs/tech/stripe-trial-e2e-playbook.md.
- Le rendu du banner "Essai Pro" n'est pas asserté en E2E à cause de
TanStack Query staleTime — couvert par les tests vitest à la place.
État global du chantier billing : 127 tests japa + 6 Playwright + 11
vitest = couverture multi-niveaux.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
6.5 KiB
Tests E2E Playwright
Version : 0.1 · Dernière maj : 2026-05-18 Stack : Playwright + Chromium · DB séparée
rubis_test_e2e· Stripe mocké
Tests end-to-end qui simulent un vrai user interagissant avec la SPA + landing dans un Chromium headless (ou headed). Validation complète de la chaîne navigation, formulaires, redirections, hydratation Tanstack, fetch API, persistence DB.
Complémentaires aux tests :
- Unit (
apps/api/tests/unit/) : logique pure, fonctions isolées. - Functional HTTP (
apps/api/tests/functional/) : routes API via japa ApiClient. - Vitest SPA (
apps/web/src/**/*.test.tsx) : hooks + composants isolés. - E2E Playwright (
e2e/tests/) ← vous êtes ici. Navigateur réel, full stack.
Pré-requis (une fois)
-
PostgreSQL local accessible (généralement via docker-compose dev) :
pnpm dev:up # spin up Postgres + Redis + Mailpit + MinIO -
Setup de la DB de test :
pnpm e2e:setupCrée
rubis_test_e2e(si absente) et applique toutes les migrations Adonis. -
Chromium Playwright (déjà fait si tu as
pnpm installaprès ce commit) :pnpm exec playwright install chromium
Lancement
# Headless (CI / dev rapide)
pnpm e2e
# Headed (regarder le navigateur en action — debug)
pnpm e2e:headed
# UI interactive (mode dev Playwright, idéal pour itérer)
pnpm e2e:ui
Playwright spawn lui-même l'API Adonis (port 3333) et la SPA Vite (port 5173) en mode test_e2e avant de lancer les tests. Pas besoin de pnpm dev à part. Si tu as déjà ton stack qui tourne, set E2E_SKIP_WEBSERVER=1.
Variables d'env
| Variable | Default | Rôle |
|---|---|---|
E2E_PG_DB_NAME |
rubis_test_e2e |
Nom de la DB de test |
E2E_WEB_URL |
http://localhost:5173 |
Base URL SPA |
E2E_API_URL |
http://localhost:3333 |
Base URL API (pour les helpers reset/mock) |
E2E_SKIP_WEBSERVER |
unset | Si défini, Playwright n'essaie pas de spawn API+SPA |
CI |
unset | Active retries + reporter github + traces |
Architecture
Stripe mocké côté API
Le SDK Stripe est remplacé par un mock déterministe via __setStripeForTests (cf. apps/api/app/services/stripe.ts). En NODE_ENV=test_e2e, les endpoints suivants sont exposés (gated, 404 sinon) :
| Endpoint | Rôle |
|---|---|
POST /__test__/reset |
TRUNCATE des tables applicatives + ré-install mock par défaut |
POST /__test__/stripe/mock |
Switch de scenario (trial_happy, trial_decline) |
POST /__test__/stripe/webhook |
Simule un event Stripe sans signer manuellement |
GET /__test__/state/org/:id |
Inspection directe DB d'une org |
Ces endpoints sont invisibles en prod : le contrôleur throw 404 si NODE_ENV !== 'test_e2e'. Aucun risque de fuite.
DB de test isolée
rubis_test_e2e est physiquement séparée de rubis (dev). Chaque test commence par await resetDb() qui TRUNCATE les tables. Aucune pollution croisée.
Le TRUNCATE CASCADE prend ~50 ms ; bien plus rapide qu'un drop/recreate. Tests séquentiels (workers: 1) pour ne pas avoir 2 truncates en parallèle.
Mocking Stripe au niveau navigateur ?
Non. On mocke côté API, pas côté navigateur. Pourquoi : la SPA appelle de toute façon notre API, et notre API appelle Stripe. Mocker côté navigateur (via Playwright route()) ne couvrirait que les appels directs SPA → Stripe, ce qui n'existe pas dans notre archi (tout passe par l'API).
L'avantage : on teste l'intégralité de notre code applicatif comme en prod, seul Stripe est rempl.
Limites — ce qui n'est PAS testé
- L'UI Stripe Checkout (3DS challenge, formulaire CB) — externe à notre app, mocké. Pour valider : utiliser le playbook manuel stripe-trial-e2e-playbook.md avec Stripe Test Clocks.
- Le prélèvement réel à J+14 — nécessite Stripe Test Clocks + advance, hors scope automation.
- Les emails sortants — capturés par Mailpit en dev, mais les tests E2E ne vérifient pas le rendu HTML pixel-perfect (le visual regression viendrait en V2).
Scénarios couverts (V1)
| Fichier | Scénarios |
|---|---|
tests/signup.spec.ts |
Signup + onboarding 3 étapes → dashboard |
tests/billing-trial.spec.ts |
Démarrer essai 14j (mock Stripe), fallback Free, retour ?trial=cancel, inspection DB post-action |
Chaque scénario tourne en ~3 s en headless. La suite complète : ~30 s.
Étendre
Ajouter un nouveau test : créer e2e/tests/<feature>.spec.ts qui suit le pattern :
import { test, expect } from '@playwright/test'
import { resetDb } from './helpers/api'
test.describe('Ma feature', () => {
test.beforeEach(async () => { await resetDb() })
test('mon scénario', async ({ page }) => {
await page.goto('/signup')
// ...
})
})
Pour simuler un webhook Stripe au milieu du scénario :
import { fireStripeWebhook } from './helpers/api'
await fireStripeWebhook({
type: 'customer.subscription.trial_will_end',
data: { object: { customer: 'cus_e2e_mock', id: 'sub_xxx' } },
})
Pour inspecter l'état DB sans naviguer dans le SPA :
import { getOrgState } from './helpers/api'
const state = await getOrgState(orgId)
expect(state?.plan).toBe('pro')
CI
À ajouter dans le pipeline Gitea (suit le pattern de apps/api actuel) :
# .gitea/workflows/e2e.yml (à créer)
e2e:
steps:
- run: pnpm install
- run: pnpm dev:up # Postgres + Redis + Mailpit
- run: pnpm exec playwright install --with-deps chromium
- run: pnpm e2e:setup
- run: pnpm e2e
- if: failure()
uses: actions/upload-artifact@v4
with:
name: playwright-report
path: playwright-report/
Tant que la CI E2E n'est pas montée, les tests se lancent uniquement en local avant les déploiements sensibles (changement signup, billing, onboarding).
Troubleshooting
- « could not connect to server: Connection refused » →
pnpm dev:uppour démarrer Postgres. - « Database "rubis_test_e2e" does not exist » →
pnpm e2e:setup. - « Timed out waiting for http://localhost:3333/api/v1/health » → l'API ne boot pas. Vérifier
apps/api/.env(APP_KEY notamment). Lancer manuellementcd apps/api && NODE_ENV=test_e2e pnpm devpour voir les erreurs. - « Stripe SDK not initialized » → l'endpoint
/__test__/stripe/mockn'a pas été appelé.resetDb()le fait par défaut au début de chaque test. - Tests flaky → augmenter
timeoutdans playwright.config.ts. La SPA peut être lente sur certaines machines.