import { expect, type Page } from '@playwright/test'; const NEW_TRIP_URL = '/trips/trips/new'; function escapeRegex(text: string): string { return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } export function uniqueSuffix(): string { return `${Date.now()}-${Math.floor(Math.random() * 1000)}`; } export async function createTrip( page: Page, values: { name: string; startDate?: string; description?: string } ): Promise<{ tripUrl: string; tripId: string }> { await page.goto(NEW_TRIP_URL); await expect(page.getByRole('heading', { name: 'Plan New Trip' })).toBeVisible(); if (values.startDate) { await page.getByLabel('Start date').fill(values.startDate); } else { await page.getByRole('checkbox', { name: "I don't know yet" }).first().check(); } if (values.description) { await page.getByLabel('Description').fill(values.description); } await page.locator('input[name="name"]').fill(values.name); await page.getByRole('button', { name: 'Save' }).click(); await expect(page).toHaveURL(/\/trips\/trips\/(?!new$)[^/?#]+$/, { timeout: 15_000 }); const tripUrl = page.url(); const tripId = tripUrl.split('/').pop() ?? ''; return { tripUrl, tripId }; } export async function openAddToTripMenuItem(page: Page, label: string): Promise { const addToTripButton = page.getByRole('button', { name: /^Add to trip$/i }); if (await addToTripButton.isVisible().catch(() => false)) { await addToTripButton.click(); await page.getByRole('button', { name: label, exact: true }).click(); return; } // In the welcome state, button names include label + subtitle. await page .getByRole('button', { name: new RegExp(`^${escapeRegex(label)}\\b`, 'i') }) .first() .click(); } export async function openAddTraveller(page: Page): Promise { const existingDialog = page.getByRole('dialog', { name: 'Add traveller' }); if (await existingDialog.isVisible().catch(() => false)) return; const addToTripButton = page.getByRole('button', { name: /^Add to trip$/i }); if (await addToTripButton.isVisible().catch(() => false)) { await addToTripButton.click(); await page.getByRole('button', { name: 'Travellers', exact: true }).click(); return; } await page.getByRole('button', { name: /^Who's travelling\?/ }).click(); } export async function addTraveller( page: Page, values: { firstName: string; lastName: string; email?: string } ): Promise { await openAddTraveller(page); const dialog = page.getByRole('dialog', { name: 'Add traveller' }); await expect(dialog).toBeVisible(); const addNewButton = dialog.getByRole('button', { name: 'Add someone new' }); if (await addNewButton.isVisible().catch(() => false)) { await addNewButton.click(); } await dialog.getByLabel('First name', { exact: false }).fill(values.firstName); await dialog.getByLabel('Last name', { exact: false }).fill(values.lastName); if (values.email) { await dialog.getByLabel(/Email/).fill(values.email); } await dialog.getByRole('button', { name: 'Add traveller' }).click(); await expect(dialog).toBeHidden(); } export async function addFlight( page: Page, values: { departureDate: string; airlineCode: string; flightNumber: string; departureAirport: string; arrivalAirport: string; } ): Promise { await openAddToTripMenuItem(page, 'Transportation'); const dialog = page.getByRole('dialog', { name: 'Add transportation' }); await expect(dialog).toBeVisible(); await dialog.getByRole('button', { name: /Flight/ }).click(); const form = dialog.locator('form'); await expect(form.locator('input[name="segments[0][departure_date]"]')).toBeVisible(); await form.locator('input[name="segments[0][departure_date]"]').fill(values.departureDate); await form.getByPlaceholder('Search airline or enter code').fill(values.airlineCode); await form.locator('input[name="segments[0][flight_number]"]').fill(values.flightNumber); await form.getByPlaceholder('Code or search').nth(0).fill(values.departureAirport); await form.getByPlaceholder('Code or search').nth(1).fill(values.arrivalAirport); await form.getByRole('button', { name: 'Add transportation' }).click(); await expect(dialog).toBeHidden(); } export async function addLodging( page: Page, values: { name: string; guestNames?: string[] } ): Promise { await openAddToTripMenuItem(page, 'Lodgings'); const dialog = page.getByRole('dialog', { name: 'Add lodging' }); await expect(dialog).toBeVisible(); await dialog.getByLabel('Name', { exact: false }).fill(values.name); for (const guestName of values.guestNames ?? []) { await dialog.getByRole('checkbox', { name: guestName }).check(); } await dialog.getByRole('button', { name: 'Add lodging' }).click(); await expect(dialog).toBeHidden(); } export async function addPackingList( page: Page, values: { name: string; items: string[] } ): Promise { await openAddToTripMenuItem(page, 'Packing List'); const dialog = page .locator('[role="dialog"]') .filter({ has: page.getByRole('heading', { name: /Packing list/i }) }) .first(); await expect(dialog).toBeVisible(); await dialog.getByLabel('Name', { exact: false }).fill(values.name); const itemInputs = dialog.getByPlaceholder('Item'); await itemInputs.first().fill(values.items[0]); for (const item of values.items.slice(1)) { await dialog.getByRole('button', { name: 'Add item' }).click(); await itemInputs.last().fill(item); } await dialog.getByRole('button', { name: 'Add', exact: true }).click(); await expect(dialog).toBeHidden(); } export async function addActivity(page: Page, values: { name: string }): Promise { await openAddToTripMenuItem(page, 'Attractions & Activities'); const dialog = page .locator('[role="dialog"]') .filter({ has: page.getByRole('heading', { name: /Attraction & activity/i }) }) .first(); await expect(dialog).toBeVisible(); await dialog.getByLabel('Name', { exact: false }).fill(values.name); await dialog.getByRole('button', { name: 'Add' }).click(); await expect(dialog).toBeHidden(); }