import { test, expect } from '@playwright/test' import { join } from 'node:path' import { resetDb } from './helpers/api' /** * Scénarios import OCR via /factures/import. * * En mode `test_e2e`, le provider OCR est `mock` (cf. .env de l'API * spawn par Playwright config), donc l'upload d'un PDF déclenche le * MockOcrProvider qui génère des champs plausibles depuis le filename * sans appel réseau. * * On utilise un vrai PDF du dossier fixtures (qui est ignoré par git * mais existe en local) pour tester l'upload multipart réel. Si le * dossier est vide (CI fresh), les tests sont skip. */ const FIXTURE_PDF = join( process.cwd(), 'e2e', 'fixtures', 'invoices', 'facture-pas-en-retard-echeance-30j-001.pdf', ) async function signupAndOnboard(page: import('@playwright/test').Page, tag = 'imp') { 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 Test') 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('Import factures via dropzone', () => { test.beforeEach(async () => { await resetDb() }) test('upload PDF → drafts générés + redirection /import/$batchId', async ({ page, }) => { // Skip si pas de fixture (CI fresh) try { // existsSync via Playwright fs const { existsSync } = await import('node:fs') if (!existsSync(FIXTURE_PDF)) { test.skip(true, `Pas de PDF fixture (${FIXTURE_PDF}) — skip`) } } catch { test.skip(true, 'fs indispo') } await signupAndOnboard(page, 'imp-upload') await page.goto('/factures/import') await expect( page.getByRole('heading', { name: /importer.*plusieurs.*factures/i }), ).toBeVisible() // Le file input est caché derrière le Dropzone — Playwright peut // setInputFiles directement même sur un input hidden. const fileInput = page.locator('input[type="file"]') await fileInput.setInputFiles(FIXTURE_PDF) // Le SPA fait un POST multipart à /api/v1/invoices/upload puis // navigate sur /factures/import/$batchId await page.waitForURL(/\/factures\/import\/[a-f0-9-]+/, { timeout: 15_000 }) // Au moins 1 draft visible (le filename apparaît quelque part) await expect( page.getByText(/facture-pas-en-retard/).first(), ).toBeVisible({ timeout: 5_000 }) }) test('page /factures/import affiche le dropzone vide', async ({ page }) => { await signupAndOnboard(page, 'imp-empty') await page.goto('/factures/import') // Texte du dropzone await expect( page.getByText(/PDF.*PNG.*JPG.*fichiers.*simultané/i), ).toBeVisible() // Bouton parcourir await expect(page.getByRole('button', { name: /parcourir.*fichiers/i })).toBeVisible() }) })