256 lines
9.3 KiB
JavaScript
256 lines
9.3 KiB
JavaScript
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();
|
|
});
|
|
});
|