92 lines
3.0 KiB
TypeScript
92 lines
3.0 KiB
TypeScript
/**
|
|
* QA-1: Test-Fixtures.
|
|
*
|
|
* - MOCK-Modus (Standard): installiert vor jedem Test den In-Memory-Mock
|
|
* (frischer Datenbestand pro Test).
|
|
* - LIVE-Modus (E2E_BASE_URL gesetzt): kein Mock; fixture-gebundene Tests
|
|
* überspringen sich über `skipUnlessMock()`.
|
|
*
|
|
* Deutsche Oberflächen-Texte werden DIREKT aus src/strings/de.ts importiert —
|
|
* die Suite bleibt damit automatisch synchron zur App.
|
|
*/
|
|
import { test as base, expect, type Page } from '@playwright/test'
|
|
import { de } from '../src/strings/de'
|
|
import { installMockApi } from './mock-api'
|
|
import type { MockDb } from './mock-data'
|
|
|
|
export const LIVE = Boolean(process.env.E2E_BASE_URL)
|
|
|
|
export { expect }
|
|
export { de }
|
|
|
|
interface Fixtures {
|
|
/** Im Mock-Modus der pro Test frische Datenbestand, sonst null. */
|
|
mockDb: MockDb | null
|
|
}
|
|
|
|
export const test = base.extend<Fixtures>({
|
|
mockDb: [
|
|
async ({ page }, use) => {
|
|
const db = LIVE ? null : await installMockApi(page)
|
|
await use(db)
|
|
},
|
|
{ auto: true },
|
|
],
|
|
})
|
|
|
|
/** Test überspringen, wenn er Seed-Daten des Mocks voraussetzt. */
|
|
export function skipUnlessMock() {
|
|
test.skip(LIVE, 'benötigt die Mock-Seed-Daten (läuft nicht im LIVE-Modus)')
|
|
}
|
|
|
|
/**
|
|
* Test überspringen im Mock-Modus — nur im LIVE-Modus sinnvoll.
|
|
* Live-Tests MÜSSEN Ergebnisse assertieren (z. B. Liste nicht leer,
|
|
* konkreter Wert sichtbar), nicht nur HTTP-200. Lesson: der Mock spiegelt
|
|
* den Frontend-Code, nicht das echte Backend — ein Bug bleibt unsichtbar,
|
|
* wenn wir nur testen, ob die Seite lädt.
|
|
*/
|
|
export function skipUnlessLive() {
|
|
test.skip(!LIVE, 'nur im LIVE-Modus sinnvoll (E2E_BASE_URL setzen)')
|
|
}
|
|
|
|
/**
|
|
* Zu einem Navigationsziel wechseln — auf dem Smartphone liegen Becken,
|
|
* Kontakte und Statistik hinter dem „Mehr“-Blatt.
|
|
*/
|
|
export async function gotoSection(page: Page, label: string) {
|
|
const nav = page.getByRole('navigation', { name: de.nav.mainNavigation })
|
|
// App noch nicht geladen (about:blank) -> erst zur Startseite
|
|
if (!(await nav.isVisible())) {
|
|
await page.goto('/')
|
|
await expect(nav).toBeVisible()
|
|
}
|
|
const link = nav.getByRole('link', { name: label })
|
|
if (!(await link.isVisible())) {
|
|
await nav.getByRole('button', { name: de.nav.more }).click()
|
|
await expect(link).toBeVisible()
|
|
}
|
|
await link.click()
|
|
}
|
|
|
|
/** window.confirm des nächsten Dialogs annehmen. */
|
|
export function acceptNextDialog(page: Page) {
|
|
page.once('dialog', (d) => void d.accept())
|
|
}
|
|
|
|
/**
|
|
* UX-MOBILE-1: Filter-Drawer öffnen, falls der Toggle-Button sichtbar ist
|
|
* (= Smartphone-Ansicht). Auf Desktop-Ansicht ist er per CSS versteckt, dann
|
|
* kein Klick nötig — Controls sind direkt sichtbar.
|
|
*/
|
|
export async function openFilterPanel(page: Page) {
|
|
const toggle = page.locator('.filter-panel__toggle')
|
|
if (await toggle.isVisible()) {
|
|
await toggle.click()
|
|
}
|
|
}
|
|
|
|
/** Eindeutiger Name für LIVE-taugliche Create-Flows. */
|
|
export const uniqueName = (prefix: string) =>
|
|
`${prefix} E2E ${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`
|