Ajoute 6 scénarios Playwright qui couvrent les surfaces utilisateur
au-delà de l'auth/clients/factures : upload OCR via dropzone, listing
des plans pré-fournis, et la chaîne mailing complète end-to-end.
Import (2 tests) — import.spec.ts :
- Upload PDF via setInputFiles : MockOcrProvider extrait depuis le
filename → drafts créés → SPA redirige sur /factures/import/$batchId
→ on voit le filename listé dans les drafts
- Empty state /factures/import : dropzone + bouton "Parcourir" visibles
Plans (3 tests) — plans.spec.ts :
- Les 4 plans pré-fournis (Standard, Rapide, Patient, Ferme) sont
visibles dès le signup (provisionnés par provisionDefaultPlans)
- Clic "Créer un plan" → arrive sur le wizard /plans/nouveau
- Clic "Modifier" sur une PlanCard → page détail /plans/{slug}
Mailing (1 test, le plus précieux) — mailing.spec.ts :
- Helper helpers/mailpit.ts : clearMailpit, waitForMessageTo (poll
200ms / 10s timeout), getMessage (HTML + texte)
- Scénario : signup → onboarding → créer client avec email →
créer facture saisie manuelle → mark-paid → BullMQ worker enqueue
payment-thanks → email envoyé via SMTP → Mailpit catche →
test inspecte subject + body (numero, montant)
- Valide la chaîne complète : SPA → API → DB → BullMQ → Worker →
Mailpit, en moins de 5 s
Total après cette PR : 26 scénarios Playwright verts en 55 s.
Pré-requis runtime :
- `pnpm dev:up` (Postgres + Redis + Mailpit + MinIO)
- `pnpm e2e:setup` (création DB rubis_test_e2e + migrations)
- Mailpit accessible sur :8025 (clearMailpit l'utilise en beforeEach)
Note check-in flow : pas implémenté en E2E V1 — nécessite un endpoint
test_e2e pour générer un CheckinTask + clear token. Le flow checkin
est déjà couvert par les tests japa functional. À ajouter en PR 3 si
besoin de validation end-to-end UI.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
108 lines
4.3 KiB
TypeScript
108 lines
4.3 KiB
TypeScript
import { test, expect } from '@playwright/test'
|
|
import { resetDb } from './helpers/api'
|
|
import { clearMailpit, getMessage, waitForMessageTo } from './helpers/mailpit'
|
|
|
|
/**
|
|
* Scénarios mailing — la chaîne complète passe par Mailpit (SMTP local
|
|
* sur :1025, UI/API :8025).
|
|
*
|
|
* Le scénario validé ici : créer une facture, la marquer encaissée →
|
|
* BullMQ worker enqueue le job `payment-thanks` → email "Merci, paiement
|
|
* bien reçu" envoyé au client final → Mailpit catche → on inspecte
|
|
* subject + body via l'API Mailpit.
|
|
*
|
|
* Pré-requis :
|
|
* - Mailpit container up (`pnpm dev:up`)
|
|
* - Redis container up (BullMQ workers démarrent au boot API)
|
|
*
|
|
* Latence typique : ~2 s entre le clic mark-paid et Mailpit (job BullMQ
|
|
* + render React Email + envoi SMTP). On poll Mailpit jusqu'à 10 s.
|
|
*/
|
|
|
|
async function signupAndOnboard(
|
|
page: import('@playwright/test').Page,
|
|
tag = 'mail',
|
|
) {
|
|
const email = `${tag}+${Date.now()}@rubis.test`
|
|
await page.goto('/signup')
|
|
await page.getByLabel(/Prénom \/ Nom/i).fill('Test User')
|
|
await page.getByLabel(/Email professionnel/i).fill(email)
|
|
await page.getByLabel(/Mot de passe/i).fill('motdepasse-fort-123')
|
|
await page.getByRole('button', { name: /créer mon compte/i }).click()
|
|
await page.waitForURL(/\/onboarding\/compte/, { timeout: 10_000 })
|
|
await page.getByRole('button', { name: /continuer/i }).click()
|
|
await page.waitForURL(/\/onboarding\/entreprise/)
|
|
await page.getByLabel(/nom de l'entreprise/i).fill('Atelier Mail')
|
|
await page.getByRole('button', { name: /continuer/i }).click()
|
|
await page.waitForURL(/\/onboarding\/signature/)
|
|
await page.getByRole('button', { name: /terminer/i }).click()
|
|
await page.waitForURL('/', { timeout: 10_000 })
|
|
}
|
|
|
|
test.describe('Mailing — payment thanks via Mailpit', () => {
|
|
test.beforeEach(async () => {
|
|
await resetDb()
|
|
await clearMailpit()
|
|
})
|
|
|
|
test('mark-paid déclenche l\'email "Merci, paiement bien reçu" au client', async ({
|
|
page,
|
|
}) => {
|
|
const clientEmail = `client-mail-${Date.now()}@example.test`
|
|
|
|
await signupAndOnboard(page, 'mail-thanks')
|
|
|
|
// 1. Créer un client avec l'email qu'on va surveiller dans Mailpit
|
|
await page.goto('/clients')
|
|
await page.getByRole('button', { name: /nouveau client/i }).first().click()
|
|
await page.getByLabel(/^Nom du client$/i).fill('Boulangerie Mail Test')
|
|
await page.getByLabel(/^Email du contact$/i).fill(clientEmail)
|
|
await page.getByRole('button', { name: /^créer le client$/i }).click()
|
|
await expect(page.getByText('Boulangerie Mail Test').first()).toBeVisible({
|
|
timeout: 5_000,
|
|
})
|
|
|
|
// 2. Créer une facture saisie manuelle
|
|
await page.getByRole('button', { name: /^saisir$/i }).first().click()
|
|
await expect(page.getByRole('dialog')).toBeVisible()
|
|
|
|
const combobox = page.getByPlaceholder(/rechercher.*créer un client/i)
|
|
await combobox.fill('Boulangerie Mail')
|
|
await expect(
|
|
page.getByRole('option', { name: /Boulangerie Mail Test/i }),
|
|
).toBeVisible({ timeout: 5_000 })
|
|
await page.getByRole('option', { name: /Boulangerie Mail Test/i }).click()
|
|
|
|
await page.getByLabel(/N° de facture/i).fill('F-MAIL-001')
|
|
await page.getByLabel(/Montant TTC/i).fill('1240')
|
|
|
|
await page.getByLabel(/Plan de relance/i).click()
|
|
await page.getByRole('option').first().click()
|
|
|
|
await page.getByRole('button', { name: /^créer la facture$/i }).click()
|
|
|
|
// 3. Aller sur la liste, ouvrir le détail de la facture
|
|
await page.goto('/factures')
|
|
await expect(page.getByText('F-MAIL-001').first()).toBeVisible({ timeout: 5_000 })
|
|
await page.getByText('F-MAIL-001').first().click()
|
|
|
|
// 4. Cliquer "Marquer encaissée"
|
|
await page.getByRole('button', { name: /marquer encaissée/i }).click()
|
|
// Toast de confirmation
|
|
await expect(page.getByText(/encaissée.*rubis/i)).toBeVisible({ timeout: 5_000 })
|
|
|
|
// 5. Attendre l'email Mailpit au client
|
|
const msg = await waitForMessageTo(clientEmail, {
|
|
subjectIncludes: 'F-MAIL-001',
|
|
timeoutMs: 15_000,
|
|
})
|
|
expect(msg.From.Address).toBeTruthy()
|
|
|
|
// 6. Vérifier le contenu : le body mentionne le numero + montant
|
|
const detail = await getMessage(msg.ID)
|
|
expect(detail.Subject).toMatch(/paiement|reçu|F-MAIL-001/i)
|
|
expect(detail.Text + detail.HTML).toMatch(/F-MAIL-001/)
|
|
expect(detail.Text + detail.HTML).toMatch(/1\s*240/) // "1 240 €" ou variation
|
|
})
|
|
})
|