Files
bakery-ia/frontend/tests/onboarding/complete-registration-flow.spec.ts

432 lines
18 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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();
});
});
});