Chore/marco changes #3

Merged
Lago merged 6 commits from chore/marco-changes into main 2026-05-31 12:13:56 +02:00
Showing only changes of commit 28db852453 - Show all commits
+255
View File
@@ -0,0 +1,255 @@
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();
});
});