import { World, setWorldConstructor } from '@cucumber/cucumber' import { chromium, type Browser, type BrowserContext, type Page, } from '@playwright/test' import { config } from './types.js' /** * AppWorld — generic Playwright world for portal-style apps. * * Exposes the primitives every feature needs: * - openBrowser / closeBrowser — lifecycle * - gotoApp(path) — navigate within the frontend * - login(email, password) — fill + submit /login, wait for redirect * - logout() — visit /logout * - expectText(text) — assert visible text * * Project-specific helpers (e.g. createBooking, assignRoom) belong in * step files, not here. Keep this generic. */ export class AppWorld extends World { browser!: Browser context!: BrowserContext page!: Page async openBrowser() { const headed = process.env.HEADED === '1' || process.env.HEADED === 'true' this.browser = await chromium.launch({ headless: !headed, slowMo: headed ? 150 : 0, }) this.context = await this.browser.newContext( process.env.E2E_LOCALE ? { locale: process.env.E2E_LOCALE } : undefined, ) this.page = await this.context.newPage() this.page.setDefaultTimeout(config.responseTimeout) } async closeBrowser() { await this.context?.close() await this.browser?.close() } async gotoApp(path: string) { const url = path.startsWith('http') ? path : `${config.appUrl}${path.startsWith('/') ? path : `/${path}`}` await this.page.goto(url) } /** * Fills the login form on /login and waits for navigation away from it. * Assumes the login form has inputs named/typed `email` and a password * input. Override per-project if the form differs. */ async login(email: string, password: string) { await this.gotoApp('/login') await this.page.locator('input[type="email"]').fill(email) await this.page.locator('input[type="password"]').fill(password) await this.page.getByRole('button', { name: /(sign in|login|anmelden)/i }).click() await this.page.waitForURL((url) => !url.pathname.startsWith('/login'), { timeout: config.responseTimeout, }) } async logout() { await this.gotoApp('/logout') } async expectText(text: string) { await this.page.getByText(text, { exact: false }).first().waitFor({ state: 'visible', timeout: config.responseTimeout, }) } async getPageText(): Promise { return this.page.innerText('body') } } setWorldConstructor(AppWorld)