import { test, expect, type Locator, type Page } from '@playwright/test'; import { TEST_USERS } from './setup/test-users.js'; const LOGIN_URL = '/trips/login'; const PROFILE_URL = '/trips/profile'; function formatDate(offsetDays: number): string { const date = new Date(); date.setDate(date.getDate() + offsetDays); return date.toISOString().slice(0, 10); } async function loginAsLocalUser(page: Page, username: string, password: string): Promise { await page.goto(LOGIN_URL); await page.fill('input[name="identifier"]', username); await page.fill('input[name="password"]', password); await page.click('button[type="submit"]:has-text("Sign in locally")'); await page.waitForURL('**/trips/dashboard', { timeout: 15_000 }); } async function ensureSelfProfile(page: Page): Promise { await page.goto(PROFILE_URL); await page.fill('#first_name', 'E2E'); await page.fill('#last_name', 'User'); await page.fill('#email', TEST_USERS.regular.email); await page.getByRole('button', { name: 'Save profile' }).click(); await expect(page.getByRole('heading', { name: 'My Profile' })).toBeVisible(); } async function openAddToTripMenuItem(page: Page, label: string): Promise { await page.getByRole('button', { name: 'Add to trip' }).click(); await page.getByRole('button', { name: label, exact: true }).click(); } async function addLoggedInTraveller(page: Page): Promise { await page.getByRole('button', { name: "Who's travelling?" }).click(); const dialog = page.getByRole('dialog', { name: 'Add traveller' }); await expect(dialog).toBeVisible(); const selfButton = dialog.getByRole('button', { name: /You/ }).first(); if (await selfButton.isVisible().catch(() => false)) { await selfButton.click(); } else { const firstNameInput = dialog.getByLabel('First name *'); if (!(await firstNameInput.isVisible().catch(() => false))) { await dialog.getByRole('button', { name: 'Add someone new' }).click(); } await dialog.getByLabel('First name *').fill('E2E'); await dialog.getByLabel('Last name *').fill('User'); await dialog.getByLabel(/Email/).fill(TEST_USERS.regular.email); await dialog.getByRole('button', { name: 'Add traveller' }).click(); } await expect(dialog).toBeHidden(); } async function addNewTraveller( page: Page, firstName: string, lastName: string, email: string ): Promise { await openAddToTripMenuItem(page, 'Travellers'); const dialog = page.getByRole('dialog', { name: 'Add traveller' }); await expect(dialog).toBeVisible(); const firstNameInput = dialog.getByLabel('First name *'); if (!(await firstNameInput.isVisible().catch(() => false))) { await dialog.getByRole('button', { name: 'Add someone new' }).click(); } await dialog.getByLabel('First name *').fill(firstName); await dialog.getByLabel('Last name *').fill(lastName); await dialog.getByLabel(/Email/).fill(email); await dialog.getByRole('button', { name: 'Add traveller' }).click(); await expect(dialog).toBeHidden(); } async function addDestination(page: Page, cityQuery: string, startDate: string): Promise { await openAddToTripMenuItem(page, 'Destinations'); const dialog = page.getByRole('dialog', { name: 'Add destination' }); await expect(dialog).toBeVisible(); await dialog.getByLabel('City *').fill(cityQuery); const cityOption = dialog.locator('ul button').filter({ hasText: cityQuery }).first(); await expect(cityOption).toBeVisible(); await cityOption.click(); await dialog.getByLabel('Arrival').fill(startDate); await dialog.getByRole('button', { name: 'Add to trip' }).click(); await expect(dialog).toBeHidden(); } async function addFlight(page: Page, departureDate: string): Promise { await openAddToTripMenuItem(page, 'Transportation'); const transportDialog = page.getByRole('dialog', { name: 'Add transportation' }); await expect(transportDialog).toBeVisible(); await transportDialog.getByRole('button', { name: /Flight/ }).click(); const form = transportDialog.locator('form'); await expect(form.locator('input[name="segments[0][departure_date]"]')).toBeVisible(); await form.locator('input[name="segments[0][departure_date]"]').fill(departureDate); await form.getByPlaceholder('Search airline or enter code').fill('UA'); await form.locator('input[name="segments[0][flight_number]"]').fill('1001'); await form.getByPlaceholder('Code or search').nth(0).fill('SFO'); await form.getByPlaceholder('Code or search').nth(1).fill('LAX'); await expect(form.getByRole('button', { name: 'Add transportation' })).toBeEnabled(); await form.getByRole('button', { name: 'Add transportation' }).click(); await expect(transportDialog).toBeHidden(); } async function addLodging(page: Page, lodgingName: string): Promise { await openAddToTripMenuItem(page, 'Lodgings'); const dialog = page.getByRole('dialog', { name: 'Add lodging' }); await expect(dialog).toBeVisible(); await dialog.getByLabel('Name *').fill(lodgingName); await dialog.getByRole('button', { name: 'Add lodging' }).click(); await expect(dialog).toBeHidden(); } async function expectPlanSectionsAndDetails( page: Page, tripName: string, destinationQuery: string, flightText: RegExp, lodgingName: string, travellerLocators: Locator[] ): Promise { await page.getByRole('link', { name: 'Upcoming Trips' }).click(); await expect(page.getByRole('heading', { name: 'Upcoming Trips' })).toBeVisible(); await expect(page.getByRole('link', { name: tripName })).toBeVisible(); await page.getByRole('link', { name: tripName }).click(); await expect(page.getByRole('heading', { name: tripName })).toBeVisible(); await expect(page.getByRole('heading', { name: 'Destinations' })).toBeVisible(); await expect(page.getByRole('heading', { name: 'Transportation' })).toBeVisible(); await expect(page.getByRole('heading', { name: 'Lodgings' })).toBeVisible(); await expect(page.getByText(destinationQuery, { exact: false })).toBeVisible(); await expect(page.getByText(flightText)).toBeVisible(); await expect(page.getByText(lodgingName, { exact: true })).toBeVisible(); for (const traveller of travellerLocators) { await expect(traveller).toBeVisible(); } } test.beforeEach(async ({ context }) => { await context.clearCookies(); }); test('regular user can plan a detailed future trip with no end date', async ({ page }) => { const startDate = formatDate(30); const tripName = `E2E Future Trip ${Date.now()}`; const tripDescription = 'Future trip created in Playwright e2e scenario'; const destinationQuery = 'Tokyo'; const lodgingName = `E2E Hotel ${Date.now()}`; const newTravellerFirstName = 'Jamie'; const newTravellerLastName = 'Companion'; const newTravellerEmail = `jamie+${Date.now()}@test.local`; await loginAsLocalUser(page, TEST_USERS.regular.username, TEST_USERS.regular.password); await ensureSelfProfile(page); await page.getByRole('link', { name: 'Plan New Trip' }).click(); await expect(page.getByRole('heading', { name: 'Plan New Trip' })).toBeVisible(); await page.getByLabel('Trip name *').fill(tripName); await page.getByLabel('Start date').fill(startDate); await page.getByLabel('End date').fill(''); await page.getByLabel('Description').fill(tripDescription); await page.getByRole('button', { name: 'Save' }).click(); await page.waitForURL('**/trips/trips/*', { timeout: 15_000 }); await expect(page.getByRole('heading', { name: tripName })).toBeVisible(); await addLoggedInTraveller(page); await addNewTraveller(page, newTravellerFirstName, newTravellerLastName, newTravellerEmail); await addDestination(page, destinationQuery, startDate); await addFlight(page, startDate); await addLodging(page, lodgingName); await expectPlanSectionsAndDetails(page, tripName, destinationQuery, /UA\s*1001/, lodgingName, [ page.getByText('E2E User', { exact: false }), page.getByText(`${newTravellerFirstName} ${newTravellerLastName}`, { exact: false }) ]); });