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>
179 lines
6.5 KiB
Markdown
179 lines
6.5 KiB
Markdown
# 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)
|
|
|
|
1. **PostgreSQL** local accessible (généralement via docker-compose dev) :
|
|
```bash
|
|
pnpm dev:up # spin up Postgres + Redis + Mailpit + MinIO
|
|
```
|
|
2. **Setup de la DB de test** :
|
|
```bash
|
|
pnpm e2e:setup
|
|
```
|
|
Crée `rubis_test_e2e` (si absente) et applique toutes les migrations Adonis.
|
|
|
|
3. **Chromium Playwright** (déjà fait si tu as `pnpm install` après ce commit) :
|
|
```bash
|
|
pnpm exec playwright install chromium
|
|
```
|
|
|
|
---
|
|
|
|
## Lancement
|
|
|
|
```bash
|
|
# 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](./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 :
|
|
|
|
```ts
|
|
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 :
|
|
|
|
```ts
|
|
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 :
|
|
|
|
```ts
|
|
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) :
|
|
|
|
```yaml
|
|
# .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:up` pour 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 manuellement `cd apps/api && NODE_ENV=test_e2e pnpm dev` pour voir les erreurs.
|
|
- **« Stripe SDK not initialized »** → l'endpoint `/__test__/stripe/mock` n'a pas été appelé. `resetDb()` le fait par défaut au début de chaque test.
|
|
- **Tests flaky** → augmenter `timeout` dans playwright.config.ts. La SPA peut être lente sur certaines machines.
|