import { test, expect } from '@playwright/test'; test.describe('Booking Flow End-to-End', () => { const ADMIN_URL = 'http://localhost:55581'; const ADMIN_EMAIL = 'admin@mccars.local'; const ADMIN_PASSWORD = 'mc-cars-admin'; // Generate unique test data per run to avoid conflicts const ts = Date.now(); const testEmails = [ `test-day-${ts}@playwright.test`, `test-weekend-${ts}@playwright.test`, `test-custom-${ts}@playwright.test`, ]; const testNames = [ 'Test Testerson Day', 'Test Testerson Weekend', 'Test Testerson Custom', ]; /** * Helper: fill out the booking form for a given mietdauer type. * Returns nothing - the form submission is handled by the page's JS. */ async function submitBooking(page, type, index) { // Scroll to booking section await page.locator('#buchen').scrollIntoViewIfNeeded(); await page.waitForTimeout(500); // Step 1: Select vehicle const carSelect = page.locator('#bpfCar'); await expect(carSelect).toBeVisible({ timeout: 10000 }); // Select first available vehicle option (skip the placeholder) const options = await carSelect.locator('option').all(); expect(options.length).toBeGreaterThan(1); const firstVehicle = await options[1].innerText(); await carSelect.selectOption({ label: firstVehicle }); // Step 2: Select mietdauer type const presetBtn = page.locator(`.bpf-preset[data-preset="${type}"]`); await expect(presetBtn).toBeVisible(); await presetBtn.click(); // Step 3: Pick date(s) based on type if (type === 'day') { // Pick a date 7 days from now const futureDate = new Date(); futureDate.setDate(futureDate.getDate() + 7); const dateStr = futureDate.toISOString().split('T')[0]; const dateInput = page.locator('#bpfDayDate'); await dateInput.fill(dateStr); } else if (type === 'weekend') { // Pick next Saturday const nextSaturday = new Date(); const daysUntilSaturday = (6 - nextSaturday.getDay() + 7) % 7 || 7; nextSaturday.setDate(nextSaturday.getDate() + daysUntilSaturday); const dateStr = nextSaturday.toISOString().split('T')[0]; const dateInput = page.locator('#bpfWeekendDate'); await dateInput.fill(dateStr); } else if (type === 'custom') { // Pick start date 14 days from now, end date 17 days from now (4 days = individuell) const startDate = new Date(); startDate.setDate(startDate.getDate() + 14); const endDate = new Date(startDate); endDate.setDate(endDate.getDate() + 3); const fromStr = startDate.toISOString().split('T')[0]; const toStr = endDate.toISOString().split('T')[0]; await page.locator('#bpfFrom').fill(fromStr); await page.locator('#bpfTo').fill(toStr); } // Click Weiter to go to step 2 await page.locator('#bpfNext1').click(); await page.waitForTimeout(300); // Step 2: Fill contact info await expect(page.locator('#bpfName')).toBeVisible(); await page.locator('#bpfName').fill(testNames[index]); await page.locator('#bpfEmail').fill(testEmails[index]); await page.locator('#bpfPhone').fill('+43 660 1234567'); await page.locator('#bpfMessage').fill(`Test booking via playwright - ${type}`); // Click Weiter to go to step 3 await page.locator('#bpfNext2').click(); await page.waitForTimeout(300); // Step 3: Submit (skip file uploads - they are optional) await expect(page.locator('#bpfSubmit')).toBeVisible(); await page.locator('#bpfSubmit').click(); // Wait for success toast await expect(page.locator('#toast.show')).toBeVisible({ timeout: 10000 }); await page.waitForTimeout(1000); } test('Complete booking flow: 1 Tag, Wochenende, Individuell → 3 leads in admin → disqualify all', async ({ page, context }) => { // ======================================== // PART 1: Submit 3 bookings on main site // ======================================== await page.goto('/'); await page.waitForLoadState('domcontentloaded'); await page.waitForTimeout(2000); // Booking 1: 1 Tag await submitBooking(page, 'day', 0); // Booking 2: Wochenende await submitBooking(page, 'weekend', 1); // Booking 3: Individuell await submitBooking(page, 'custom', 2); // ======================================== // PART 2: Verify 3 leads in admin panel // ======================================== const adminCtx = await test.info().project.use.baseBrowserType?.newContext() ?? context; const adminPage = await adminCtx.newPage(); adminPage.setDefaultTimeout(30000); await adminPage.goto(ADMIN_URL); await adminPage.waitForLoadState('domcontentloaded'); await adminPage.waitForTimeout(2000); // Login const loginForm = adminPage.locator('#loginForm'); await expect(loginForm).toBeVisible({ timeout: 10000 }); await adminPage.locator('#loginForm [name="email"]').fill(ADMIN_EMAIL); await adminPage.locator('#loginForm [name="password"]').fill(ADMIN_PASSWORD); await adminPage.locator('#loginForm [type="submit"]').click(); // Wait a moment for login to process await adminPage.waitForTimeout(3000); // Check for login error const loginError = adminPage.locator('#loginError'); if (await loginError.isVisible()) { const errorMsg = await loginError.textContent(); throw new Error(`Login failed: ${errorMsg}`); } // Check if password rotation is required (first login) const rotateView = adminPage.locator('#rotateView'); if (await rotateView.isVisible({ timeout: 2000 })) { // Set a new password (must be different from bootstrap) const newPw = 'Playwright-Test-PW-2026!'; await adminPage.locator('#rotateForm [name="pw1"]').fill(newPw); await adminPage.locator('#rotateForm [name="pw2"]').fill(newPw); await adminPage.locator('#rotateForm [type="submit"]').click(); await adminPage.waitForTimeout(2000); } // Wait for admin view to load await expect(adminPage.locator('#adminView')).toBeVisible({ timeout: 15000 }); await adminPage.waitForTimeout(2000); // Ensure leads tab is active (it's the default) const leadsTab = adminPage.locator('[data-tab="leads"]'); const leadsTabClass = await leadsTab.getAttribute('class'); if (!leadsTabClass?.includes('active')) { await leadsTab.click(); await adminPage.waitForTimeout(1000); } // Wait for our test leads to appear by checking for their emails in the table // We wait for at least one of our test emails to appear, then verify all 3 await adminPage.waitForFunction( ([emails]) => { const rows = document.querySelectorAll('#leadsTable tbody tr'); let found = 0; for (const row of rows) { const text = row.textContent; for (const email of emails) { if (text.includes(email)) { found++; break; } } } return found >= 3; }, testEmails, { timeout: 30000 } ); await adminPage.waitForTimeout(1000); // Find our test leads by email pattern const allRows = adminPage.locator('#leadsTable tbody tr'); const totalRows = await allRows.count(); const testRowIndices = []; for (let i = 0; i < totalRows; i++) { const row = allRows.nth(i); const rowText = await row.textContent(); if (testEmails.some(email => rowText.includes(email))) { testRowIndices.push(i); } } expect(testRowIndices.length).toBe(3); // ======================================== // PART 3: Disqualify all 3 test leads // ======================================== // Disqualify each lead one at a time, re-finding it after each disqualification // since the table re-renders and indices shift. for (const email of testEmails) { // Find the row for this email const rows = adminPage.locator('#leadsTable tbody tr'); const count = await rows.count(); let found = false; for (let i = 0; i < count; i++) { const rowText = await rows.nth(i).textContent(); if (rowText.includes(email)) { // Click disqualify button const disqBtn = rows.nth(i).locator('[data-disq]'); if (await disqBtn.isVisible()) { await disqBtn.click(); await adminPage.waitForTimeout(1500); found = true; break; } } } expect(found).toBe(true, `Lead with email ${email} not found or could not be disqualified`); } // Wait for disqualifications to process await adminPage.waitForTimeout(2000); // Refresh page to ensure fresh data after disqualifications await adminPage.reload(); await expect(adminPage.locator('#adminView')).toBeVisible({ timeout: 15000 }); await adminPage.waitForTimeout(3000); // Verify our test leads are now disqualified (no longer in active view) const remainingRows = adminPage.locator('#leadsTable tbody tr'); const remainingCount = await remainingRows.count(); let foundTestLead = false; for (let i = 0; i < remainingCount; i++) { const rowText = await remainingRows.nth(i).textContent(); if (testEmails.some(email => rowText.includes(email))) { foundTestLead = true; break; } } expect(foundTestLead).toBe(false); await adminPage.close(); }); });