Initial commit - production deployment

This commit is contained in:
2026-01-21 17:17:16 +01:00
commit c23d00dd92
2289 changed files with 638440 additions and 0 deletions

View File

@@ -0,0 +1,431 @@
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();
});
});
});

View File

@@ -0,0 +1,223 @@
import { test, expect } from '@playwright/test';
import path from 'path';
import { acceptCookieConsent } from '../helpers/utils';
test.describe('Onboarding File Upload', () => {
// Use authenticated state
test.use({ storageState: 'tests/.auth/user.json' });
test.beforeEach(async ({ page }) => {
// Navigate to onboarding
await page.goto('/app/onboarding');
// Accept cookie consent if present
await acceptCookieConsent(page);
});
test('should display file upload component', async ({ page }) => {
// Navigate to file upload step
// Might need to click through initial steps first
let foundFileUpload = false;
const maxSteps = 5;
for (let i = 0; i < maxSteps; i++) {
// Check if file input is visible
const fileInput = page.locator('input[type="file"]');
if (await fileInput.isVisible().catch(() => false)) {
foundFileUpload = true;
break;
}
// Try to go to next step
const nextButton = page.getByRole('button', { name: /next|siguiente|continuar/i });
if (!(await nextButton.isVisible().catch(() => false))) {
break;
}
await nextButton.click();
await page.waitForTimeout(500);
}
// Should have found file upload at some point
if (foundFileUpload) {
await expect(page.locator('input[type="file"]')).toBeVisible();
}
});
test('should upload a CSV file', async ({ page }) => {
// Navigate to file upload step
const maxSteps = 5;
let fileUploadFound = false;
for (let i = 0; i < maxSteps; i++) {
const fileInput = page.locator('input[type="file"]');
if (await fileInput.isVisible().catch(() => false)) {
fileUploadFound = true;
// Create a test CSV file path
const testFilePath = path.join(__dirname, '../fixtures/sample-inventory.csv');
// Try to upload (will fail gracefully if file doesn't exist)
try {
await fileInput.setInputFiles(testFilePath);
// Verify file is uploaded (look for file name in UI)
await expect(page.locator('body')).toContainText(/sample-inventory\.csv/i, {
timeout: 5000,
});
} catch (error) {
// File doesn't exist yet, that's okay for this test
console.log('Test file not found, skipping upload verification');
}
break;
}
const nextButton = page.getByRole('button', { name: /next|siguiente|continuar/i });
if (await nextButton.isVisible().catch(() => false)) {
await nextButton.click();
await page.waitForTimeout(500);
} else {
break;
}
}
});
test('should accept drag and drop file upload', async ({ page }) => {
// Navigate to file upload step
const maxSteps = 5;
for (let i = 0; i < maxSteps; i++) {
// Look for dropzone (react-dropzone creates a div)
const dropzone = page.locator('[role="presentation"], .dropzone, [data-testid*="dropzone"]').first();
if (await dropzone.isVisible().catch(() => false)) {
// Verify dropzone accepts files
await expect(dropzone).toBeVisible();
// Look for upload instructions
const bodyText = await page.locator('body').textContent();
expect(bodyText).toMatch(/drag|drop|upload|subir|arrastrar/i);
break;
}
const nextButton = page.getByRole('button', { name: /next|siguiente|continuar/i });
if (await nextButton.isVisible().catch(() => false)) {
await nextButton.click();
await page.waitForTimeout(500);
} else {
break;
}
}
});
test('should show error for invalid file type', async ({ page }) => {
// Navigate to file upload step
const maxSteps = 5;
for (let i = 0; i < maxSteps; i++) {
const fileInput = page.locator('input[type="file"]');
if (await fileInput.isVisible().catch(() => false)) {
// Try to upload an invalid file type (e.g., .txt when expecting .csv)
const testFilePath = path.join(__dirname, '../fixtures/invalid-file.txt');
try {
await fileInput.setInputFiles(testFilePath);
// Should show error message
await expect(page.locator('body')).toContainText(
/invalid|not supported|no válido|no compatible|type|tipo/i,
{ timeout: 5000 }
);
} catch (error) {
// Test file doesn't exist, that's okay
console.log('Test file not found, skipping validation test');
}
break;
}
const nextButton = page.getByRole('button', { name: /next|siguiente|continuar/i });
if (await nextButton.isVisible().catch(() => false)) {
await nextButton.click();
await page.waitForTimeout(500);
} else {
break;
}
}
});
test('should be able to remove uploaded file', async ({ page }) => {
// Navigate to file upload step
const maxSteps = 5;
for (let i = 0; i < maxSteps; i++) {
const fileInput = page.locator('input[type="file"]');
if (await fileInput.isVisible().catch(() => false)) {
const testFilePath = path.join(__dirname, '../fixtures/sample-inventory.csv');
try {
await fileInput.setInputFiles(testFilePath);
// Wait for file to be uploaded
await page.waitForTimeout(1000);
// Look for remove/delete button
const removeButton = page.getByRole('button', { name: /remove|delete|eliminar|quitar/i });
if (await removeButton.isVisible().catch(() => false)) {
await removeButton.click();
// File name should disappear
await expect(page.locator('body')).not.toContainText(/sample-inventory\.csv/i, {
timeout: 3000,
});
}
} catch (error) {
console.log('Could not test file removal');
}
break;
}
const nextButton = page.getByRole('button', { name: /next|siguiente|continuar/i });
if (await nextButton.isVisible().catch(() => false)) {
await nextButton.click();
await page.waitForTimeout(500);
} else {
break;
}
}
});
test('should show upload progress for large files', async ({ page }) => {
// Navigate to file upload step
const maxSteps = 5;
for (let i = 0; i < maxSteps; i++) {
const fileInput = page.locator('input[type="file"]');
if (await fileInput.isVisible().catch(() => false)) {
// Look for progress indicator elements
const progressBar = page.locator('[role="progressbar"], .progress-bar, [data-testid*="progress"]');
// Progress bar might not be visible until upload starts
// This test documents the expected behavior
break;
}
const nextButton = page.getByRole('button', { name: /next|siguiente|continuar/i });
if (await nextButton.isVisible().catch(() => false)) {
await nextButton.click();
await page.waitForTimeout(500);
} else {
break;
}
}
});
});

View File

@@ -0,0 +1,160 @@
import { test, expect } from '@playwright/test';
import { acceptCookieConsent } from '../helpers/utils';
test.describe('Onboarding Wizard Navigation', () => {
// Use authenticated state
test.use({ storageState: 'tests/.auth/user.json' });
test.beforeEach(async ({ page }) => {
// Navigate to onboarding
await page.goto('/app/onboarding');
// Accept cookie consent if present
await acceptCookieConsent(page);
});
test('should display first step of onboarding wizard', async ({ page }) => {
// Should show onboarding content
await expect(page.locator('body')).toContainText(/onboarding|bienvenido|welcome/i);
// Should have navigation buttons
const nextButton = page.getByRole('button', { name: /next|siguiente|continuar/i });
await expect(nextButton).toBeVisible();
});
test('should navigate through wizard steps', async ({ page }) => {
// Click through wizard steps
let currentStep = 1;
const maxSteps = 5; // Adjust based on your actual wizard steps
for (let step = 0; step < maxSteps; step++) {
// Look for Next/Continue button
const nextButton = page.getByRole('button', { name: /next|siguiente|continuar/i });
// If no next button, we might be at the end
if (!(await nextButton.isVisible().catch(() => false))) {
break;
}
// Click next
await nextButton.click();
// Wait for navigation/content change
await page.waitForTimeout(500);
currentStep++;
}
// Should have navigated through at least 2 steps
expect(currentStep).toBeGreaterThan(1);
});
test('should allow backward navigation', async ({ page }) => {
// Navigate to second step
const nextButton = page.getByRole('button', { name: /next|siguiente|continuar/i });
await nextButton.click();
await page.waitForTimeout(500);
// Look for back button
const backButton = page.getByRole('button', { name: /back|previous|atrás|anterior/i });
if (await backButton.isVisible().catch(() => false)) {
await backButton.click();
// Should go back to first step
await page.waitForTimeout(500);
// Verify we're back at the beginning
await expect(nextButton).toBeVisible();
}
});
test('should show progress indicator', async ({ page }) => {
// Look for progress indicators (stepper, progress bar, etc.)
const progressIndicators = [
page.locator('[role="progressbar"]'),
page.locator('.stepper'),
page.locator('.progress'),
page.locator('[data-testid*="progress"]'),
page.locator('[data-testid*="step"]'),
];
let foundProgress = false;
for (const indicator of progressIndicators) {
if (await indicator.isVisible().catch(() => false)) {
foundProgress = true;
break;
}
}
// Progress indicator should exist
expect(foundProgress).toBe(true);
});
test('should validate required fields before proceeding', async ({ page }) => {
// Try to click next without filling required fields
const nextButton = page.getByRole('button', { name: /next|siguiente|continuar/i });
// Get current URL
const urlBefore = page.url();
await nextButton.click();
await page.waitForTimeout(1000);
// Should either:
// 1. Show validation error, OR
// 2. Stay on the same step if validation prevents navigation
const urlAfter = page.url();
// If URL changed, we moved to next step (validation passed or not required)
// If URL same, validation likely blocked navigation
// Either way is valid, but let's check for validation messages if URL is same
if (urlBefore === urlAfter) {
// Look for validation messages
const bodyText = await page.locator('body').textContent();
// This step might have required fields
}
});
test('should be able to skip onboarding if option exists', async ({ page }) => {
// Look for skip button
const skipButton = page.getByRole('button', { name: /skip|omitir|later/i });
if (await skipButton.isVisible().catch(() => false)) {
await skipButton.click();
// Should redirect to main app
await expect(page).toHaveURL(/\/app\/(dashboard|operations)/, { timeout: 5000 });
}
});
test('should complete onboarding and redirect to dashboard', async ({ page }) => {
// Navigate through all steps quickly
const maxSteps = 10;
for (let i = 0; i < maxSteps; i++) {
// Look for Next button
const nextButton = page.getByRole('button', { name: /next|siguiente|continuar/i });
if (!(await nextButton.isVisible().catch(() => false))) {
// Might be at final step, look for Finish button
const finishButton = page.getByRole('button', { name: /finish|complete|finalizar|completar/i });
if (await finishButton.isVisible().catch(() => false)) {
await finishButton.click();
break;
} else {
// No more steps
break;
}
}
await nextButton.click();
await page.waitForTimeout(500);
}
// After completing, should redirect to dashboard
await expect(page).toHaveURL(/\/app/, { timeout: 10000 });
});
});