Files
germantax-e2e-mrfz66zi/e2e/tests/support/world.ts
2026-07-11 08:20:01 +02:00

110 lines
3.5 KiB
TypeScript

import { World, setWorldConstructor } from '@cucumber/cucumber'
import {
chromium,
type Browser,
type BrowserContext,
type Page,
} from '@playwright/test'
import { config } from './types.js'
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({
locale: process.env.E2E_LOCALE ?? 'de-DE',
})
await this.context.addInitScript((apiUrl) => {
;(window as typeof window & { __E2E_API_URL?: string }).__E2E_API_URL = apiUrl
}, config.apiUrl)
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)
await this.page.waitForFunction(
() => document.documentElement.dataset.appHydrated === 'true',
undefined,
{ timeout: config.responseTimeout },
)
await this.page.waitForLoadState('networkidle', { timeout: 1000 }).catch(() => {})
await this.page.waitForTimeout(500)
}
async login(email: string, password: string) {
// Use Playwright's request API (same cookie jar, no SameSite restrictions)
// so CSRF double-submit works across the auth-js proxy boundary.
const csrfRes = await this.page.request.get(`${config.apiUrl}/auth/csrf`)
const { csrfToken } = (await csrfRes.json()) as { csrfToken: string }
const body = new URLSearchParams({ email, password, csrfToken, callbackUrl: '/' })
await this.page.request.post(`${config.apiUrl}/auth/callback/credentials`, {
headers: { 'content-type': 'application/x-www-form-urlencoded' },
data: body.toString(),
})
// Navigate to the app after auth cookies are set
await this.gotoApp('/companies')
}
async logout() {
await this.gotoApp('/logout')
}
async callRpc(name: string, data: Record<string, unknown> = {}) {
const apiUrl = config.apiUrl
const result = await this.page.evaluate(
async ({ apiUrl, name, data }) => {
const res = await fetch(`${apiUrl}/rpc/${name}`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ data }),
})
if (!res.ok) {
const body = await res.text().catch(() => '')
throw new Error(`RPC ${name} failed (${res.status}): ${body}`)
}
return res.json()
},
{ apiUrl, name, data },
)
return result
}
async expectText(text: string) {
const locator = this.page.getByText(text, { exact: false })
const deadline = Date.now() + config.responseTimeout
while (Date.now() < deadline) {
const count = await locator.count()
for (let i = 0; i < count; i++) {
if (await locator.nth(i).isVisible().catch(() => false)) {
return
}
}
await this.page.waitForTimeout(100)
}
throw new Error(`Timed out waiting for visible text: ${text}`)
}
async getPageText(): Promise<string> {
return this.page.innerText('body')
}
}
setWorldConstructor(AppWorld)