import { test, expect } from '@playwright/test'; import { acceptCookieConsent, waitForNavigation } from '../helpers/utils'; import path from 'path'; /** * Complete User Registration and Onboarding Flow Test * * This test suite covers the entire journey from registration to onboarding completion: * 1. User Registration (3-step process: basic info, subscription, payment) * 2. Onboarding Wizard (multiple steps based on business type and tier) */ test.describe('Complete Registration and Onboarding Flow', () => { // Generate unique test user for each test run const generateTestUser = () => ({ fullName: `Test User ${Date.now()}`, email: `test.user.${Date.now()}@bakery-test.com`, password: 'SecurePass123!@#', plan: 'starter' // or 'professional', 'enterprise' }); test('should complete full registration and onboarding flow for starter plan', async ({ page }) => { const testUser = generateTestUser(); // ============================================ // PHASE 1: REGISTRATION // ============================================ await test.step('Navigate to registration page', async () => { await page.goto('/register'); await acceptCookieConsent(page); // Verify we're on the registration page await expect(page.getByRole('heading', { name: /crear cuenta|create account/i })).toBeVisible(); }); await test.step('Fill basic information (Step 1/3)', async () => { // Fill in user details await page.getByLabel(/nombre completo|full name/i).fill(testUser.fullName); await page.getByLabel(/correo|email/i).fill(testUser.email); // Fill password await page.getByLabel(/^contraseña|^password/i).first().fill(testUser.password); // Fill confirm password await page.getByLabel(/confirmar contraseña|confirm password/i).fill(testUser.password); // Wait for password match indicator await expect(page.getByText(/las contraseñas coinciden|passwords match/i)).toBeVisible({ timeout: 5000 }); // Accept terms and conditions await page.getByRole('checkbox', { name: /acepto los términos|accept terms/i }).check(); // Optional consents await page.getByRole('checkbox', { name: /marketing|promociones/i }).check(); await page.getByRole('checkbox', { name: /analytics|analíticas/i }).check(); // Proceed to next step await page.getByRole('button', { name: /siguiente|next/i }).click(); }); await test.step('Select subscription plan (Step 2/3)', async () => { // Wait for subscription page await expect(page.getByRole('heading', { name: /selecciona tu plan|select.*plan/i })).toBeVisible({ timeout: 10000 }); // Select starter plan (default is usually selected) const starterPlanButton = page.getByRole('button', { name: /starter|básico/i }).first(); if (await starterPlanButton.isVisible().catch(() => false)) { await starterPlanButton.click(); } // Proceed to payment await page.getByRole('button', { name: /siguiente|next/i }).click(); }); await test.step('Complete payment information (Step 3/3)', async () => { // Wait for payment page await expect(page.getByRole('heading', { name: /información de pago|payment info/i })).toBeVisible({ timeout: 10000 }); // Check if bypass payment toggle exists (for testing environments) const bypassToggle = page.getByRole('checkbox', { name: /bypass|omitir|skip.*payment/i }); const hasBypassOption = await bypassToggle.isVisible({ timeout: 2000 }).catch(() => false); if (hasBypassOption) { // Enable bypass for test environment await bypassToggle.check(); await page.getByRole('button', { name: /completar registro|complete registration/i }).click(); } else { // Fill Stripe test card details const cardFrame = page.frameLocator('iframe[name*="__privateStripeFrame"]').first(); // Wait for Stripe iframe to load await page.waitForTimeout(2000); // Fill card number (Stripe test card) await cardFrame.getByPlaceholder(/card number|número.*tarjeta/i).fill('4242424242424242'); // Fill expiry date await cardFrame.getByPlaceholder(/mm.*yy|expir/i).fill('1234'); // Fill CVC await cardFrame.getByPlaceholder(/cvc|cvv/i).fill('123'); // Fill postal code if visible const postalCode = cardFrame.getByPlaceholder(/postal|zip/i); if (await postalCode.isVisible().catch(() => false)) { await postalCode.fill('12345'); } // Submit payment await page.getByRole('button', { name: /completar registro|complete registration|submit/i }).click(); } // Wait for redirect to onboarding await page.waitForURL(/\/app\/onboarding/, { timeout: 15000 }); }); // ============================================ // PHASE 2: ONBOARDING WIZARD // ============================================ await test.step('Complete onboarding wizard', async () => { // Wait for onboarding page to load await expect(page.locator('body')).toContainText(/onboarding|configuración|bienvenido/i, { timeout: 10000 }); // The onboarding has multiple steps, we'll navigate through them let stepCount = 0; const maxSteps = 15; // Maximum expected steps while (stepCount < maxSteps) { // Wait a bit for the step to render await page.waitForTimeout(1000); // Look for various button types that indicate progression const nextButton = page.getByRole('button', { name: /next|siguiente|continuar/i }); const skipButton = page.getByRole('button', { name: /skip|omitir|saltar/i }); const finishButton = page.getByRole('button', { name: /finish|finalizar|completar/i }); const goToDashboardButton = page.getByRole('button', { name: /ir al panel|go to dashboard/i }); // Check what step we're on and fill accordingly const bodyText = await page.locator('body').textContent(); // Step: Bakery Type Selection if (bodyText?.match(/tipo.*panadería|bakery.*type|selecciona.*tipo/i)) { console.log('📍 Step: Bakery Type Selection'); const retailOption = page.getByRole('button', { name: /retail|minorista|venta/i }).first(); if (await retailOption.isVisible({ timeout: 2000 }).catch(() => false)) { await retailOption.click(); } } // Step: Tenant Setup (Register Bakery) else if (bodyText?.match(/registra.*panadería|register.*bakery|datos.*negocio/i)) { console.log('📍 Step: Tenant Setup'); // Fill bakery name if not filled const nameInput = page.getByLabel(/nombre.*panadería|bakery.*name|nombre.*negocio/i); if (await nameInput.isVisible({ timeout: 2000 }).catch(() => false)) { const currentValue = await nameInput.inputValue(); if (!currentValue) { await nameInput.fill(`Test Bakery ${Date.now()}`); } } // Fill address const addressInput = page.getByLabel(/dirección|address/i); if (await addressInput.isVisible({ timeout: 2000 }).catch(() => false)) { const currentValue = await addressInput.inputValue(); if (!currentValue) { await addressInput.fill('Calle Test 123'); } } // Fill postal code const postalInput = page.getByLabel(/código postal|postal.*code|zip/i); if (await postalInput.isVisible({ timeout: 2000 }).catch(() => false)) { const currentValue = await postalInput.inputValue(); if (!currentValue) { await postalInput.fill('28001'); } } // Fill city const cityInput = page.getByLabel(/ciudad|city/i); if (await cityInput.isVisible({ timeout: 2000 }).catch(() => false)) { const currentValue = await cityInput.inputValue(); if (!currentValue) { await cityInput.fill('Madrid'); } } // Fill phone const phoneInput = page.getByLabel(/teléfono|phone/i); if (await phoneInput.isVisible({ timeout: 2000 }).catch(() => false)) { const currentValue = await phoneInput.inputValue(); if (!currentValue) { await phoneInput.fill('666555444'); } } } // Step: Upload Sales Data else if (bodyText?.match(/subir.*datos|upload.*sales|archivo.*ventas/i)) { console.log('📍 Step: Upload Sales Data'); // Try to skip this step if possible if (await skipButton.isVisible({ timeout: 2000 }).catch(() => false)) { await skipButton.click(); stepCount++; continue; } // Otherwise, we'd need to upload a test CSV file // For now, we'll just proceed if there's a next button } // Step: Inventory Review else if (bodyText?.match(/revisar.*inventario|inventory.*review|productos/i)) { console.log('📍 Step: Inventory Review'); // Usually can skip or proceed with empty } // Step: Initial Stock Entry else if (bodyText?.match(/stock.*inicial|initial.*stock|cantidad/i)) { console.log('📍 Step: Initial Stock Entry'); // Look for "Set all to 0" or skip button const setAllZeroButton = page.getByRole('button', { name: /establecer.*0|set.*all.*0/i }); if (await setAllZeroButton.isVisible({ timeout: 2000 }).catch(() => false)) { await setAllZeroButton.click(); } } // Step: Suppliers Setup else if (bodyText?.match(/proveedores|suppliers/i)) { console.log('📍 Step: Suppliers Setup'); // Can skip for now } // Step: Recipes Setup else if (bodyText?.match(/recetas|recipes/i)) { console.log('📍 Step: Recipes Setup'); // Can skip for now } // Step: ML Training else if (bodyText?.match(/entrenamiento|training|modelo.*ia|ai.*model/i)) { console.log('📍 Step: ML Training'); // Look for skip to dashboard button if (await goToDashboardButton.isVisible({ timeout: 5000 }).catch(() => false)) { await goToDashboardButton.click(); // Should redirect to dashboard await page.waitForURL(/\/app\/(dashboard|operations)/, { timeout: 10000 }); break; } } // Step: Completion else if (bodyText?.match(/completado|completed|felicidades|congratulations/i)) { console.log('📍 Step: Completion'); if (await goToDashboardButton.isVisible({ timeout: 2000 }).catch(() => false)) { await goToDashboardButton.click(); await page.waitForURL(/\/app\/(dashboard|operations)/, { timeout: 10000 }); break; } else if (await finishButton.isVisible({ timeout: 2000 }).catch(() => false)) { await finishButton.click(); await page.waitForURL(/\/app\/(dashboard|operations)/, { timeout: 10000 }); break; } } // Try to proceed to next step if (await nextButton.isVisible({ timeout: 2000 }).catch(() => false)) { await nextButton.click(); stepCount++; } else if (await skipButton.isVisible({ timeout: 2000 }).catch(() => false)) { await skipButton.click(); stepCount++; } else if (await finishButton.isVisible({ timeout: 2000 }).catch(() => false)) { await finishButton.click(); // Should redirect to dashboard await page.waitForURL(/\/app\/(dashboard|operations)/, { timeout: 10000 }); break; } else { // No more navigation buttons found console.log('ℹ️ No more navigation buttons found, checking if we reached dashboard'); // Check if we're already at dashboard if (page.url().includes('/app/dashboard') || page.url().includes('/app/operations')) { break; } // If we can't find any buttons and we're still on onboarding, something might be wrong if (page.url().includes('/onboarding')) { console.warn('⚠️ Still on onboarding page but no navigation buttons found'); // Take a screenshot for debugging await page.screenshot({ path: `test-results/onboarding-stuck-step-${stepCount}.png` }); } break; } // Safety check: wait a bit between steps await page.waitForTimeout(500); } console.log(`✅ Completed ${stepCount} onboarding steps`); }); await test.step('Verify successful onboarding completion', async () => { // Should be redirected to dashboard await expect(page).toHaveURL(/\/app\/(dashboard|operations)/, { timeout: 10000 }); // Verify dashboard elements are visible await expect(page.locator('body')).toContainText(/dashboard|panel|operaciones|operations/i, { timeout: 5000 }); // Take final screenshot await page.screenshot({ path: 'test-results/onboarding-completed.png', fullPage: true }); }); }); test('should complete onboarding with manual data entry (no file upload)', async ({ page }) => { const testUser = generateTestUser(); // Quick registration (simplified for this test) await page.goto('/register'); await acceptCookieConsent(page); // Fill registration form quickly await page.getByLabel(/nombre completo|full name/i).fill(testUser.fullName); await page.getByLabel(/correo|email/i).fill(testUser.email); await page.getByLabel(/^contraseña|^password/i).first().fill(testUser.password); await page.getByLabel(/confirmar contraseña|confirm password/i).fill(testUser.password); await page.getByRole('checkbox', { name: /acepto los términos|accept terms/i }).check(); // Proceed through registration await page.getByRole('button', { name: /siguiente|next/i }).click(); // Select plan (if subscription step appears) await page.waitForTimeout(2000); const nextButtonAfterPlan = page.getByRole('button', { name: /siguiente|next/i }); if (await nextButtonAfterPlan.isVisible({ timeout: 3000 }).catch(() => false)) { await nextButtonAfterPlan.click(); } // Handle payment page const bypassToggle = page.getByRole('checkbox', { name: /bypass|omitir|skip.*payment/i }); if (await bypassToggle.isVisible({ timeout: 3000 }).catch(() => false)) { await bypassToggle.check(); await page.getByRole('button', { name: /completar registro|complete registration/i }).click(); } // Wait for onboarding await page.waitForURL(/\/app\/onboarding/, { timeout: 15000 }); // Test specific scenario: Skip file upload and add products manually await test.step('Navigate to manual product entry', async () => { // Look for file upload step and skip it let attempts = 0; while (attempts < 10) { const bodyText = await page.locator('body').textContent(); if (bodyText?.match(/subir.*archivo|upload.*file|sales.*data/i)) { // Found upload step, try to skip const skipButton = page.getByRole('button', { name: /skip|omitir|saltar|manual/i }); if (await skipButton.isVisible({ timeout: 2000 }).catch(() => false)) { await skipButton.click(); break; } } // Try next button if available const nextButton = page.getByRole('button', { name: /next|siguiente/i }); if (await nextButton.isVisible({ timeout: 2000 }).catch(() => false)) { await nextButton.click(); attempts++; } else { break; } await page.waitForTimeout(500); } }); }); test('should handle validation errors gracefully', async ({ page }) => { await page.goto('/register'); await acceptCookieConsent(page); await test.step('Test password validation', async () => { // Try weak password await page.getByLabel(/nombre completo|full name/i).fill('Test User'); await page.getByLabel(/correo|email/i).fill('test@example.com'); await page.getByLabel(/^contraseña|^password/i).first().fill('weak'); // Next button should be disabled or show error const nextButton = page.getByRole('button', { name: /siguiente|next/i }); // Either button is disabled or we see password criteria errors const isDisabled = await nextButton.isDisabled(); const hasPasswordError = await page.getByText(/mínimo|caracteres|mayúscula|minúscula/i).isVisible().catch(() => false); expect(isDisabled || hasPasswordError).toBe(true); }); await test.step('Test email validation', async () => { // Try invalid email await page.getByLabel(/correo|email/i).fill('invalid-email'); await page.getByLabel(/^contraseña|^password/i).first().click(); // Blur the email field // Should show email error await expect(page.getByText(/email.*válido|valid.*email/i)).toBeVisible({ timeout: 3000 }); }); await test.step('Test terms acceptance requirement', async () => { // Fill valid data but don't accept terms await page.getByLabel(/nombre completo|full name/i).fill('Test User'); await page.getByLabel(/correo|email/i).fill('test@example.com'); await page.getByLabel(/^contraseña|^password/i).first().fill('SecurePass123!'); await page.getByLabel(/confirmar contraseña|confirm password/i).fill('SecurePass123!'); // Try to proceed without accepting terms const nextButton = page.getByRole('button', { name: /siguiente|next/i }); // Button should be disabled await expect(nextButton).toBeDisabled(); }); }); });