QA-1: smoke specs - Navigation, Tiere CRUD+Tabs, Becken/Kontakte+409, Wuerfe+Jungtier, Probeverpaarung+SCHECKE-Warnung, Stammbaum re-root+print, Statistik (24 flows x 2 viewports, German assertions)
This commit is contained in:
87
gerbil-manager-web/e2e/becken-kontakte.spec.ts
Normal file
87
gerbil-manager-web/e2e/becken-kontakte.spec.ts
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
/** QA-1: Becken + Kontakte (FEAT-2) — CRUD, Belegung, 409-Konfliktpfad. */
|
||||||
|
import { acceptNextDialog, de, expect, gotoSection, skipUnlessMock, test, uniqueName } from './fixtures'
|
||||||
|
|
||||||
|
const tb = de.pages.becken
|
||||||
|
const tk = de.pages.kontakte
|
||||||
|
|
||||||
|
test.describe('Becken', () => {
|
||||||
|
test('Belegung: Detail zeigt Bewohner mit Link zur Tierakte', async ({ page }) => {
|
||||||
|
skipUnlessMock()
|
||||||
|
await page.goto('/becken')
|
||||||
|
await page.getByRole('link', { name: /Großbecken/ }).click()
|
||||||
|
await expect(page.getByRole('heading', { name: 'Großbecken' })).toBeVisible()
|
||||||
|
await expect(page.getByText(`2 ${tb.occupancy.countMany}`)).toBeVisible()
|
||||||
|
await expect(page.getByText(tb.occupancy.title)).toBeVisible()
|
||||||
|
await page.getByRole('link', { name: /Krümel/ }).click()
|
||||||
|
await expect(page.getByText(de.pages.gerbils.detail.masterData)).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Löschen eines belegten Beckens meldet den 409-Konflikt auf Deutsch', async ({ page }) => {
|
||||||
|
skipUnlessMock()
|
||||||
|
await page.goto('/becken')
|
||||||
|
await page.getByRole('link', { name: /Großbecken/ }).click()
|
||||||
|
acceptNextDialog(page)
|
||||||
|
await page.getByRole('button', { name: tb.delete.action }).click()
|
||||||
|
await expect(page.getByText(tb.delete.conflict)).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Becken anlegen, bearbeiten und (leer) löschen', async ({ page }) => {
|
||||||
|
await gotoSection(page, de.nav.enclosures)
|
||||||
|
await page.getByRole('link', { name: tb.newButton }).click()
|
||||||
|
|
||||||
|
// Pflichtfeld-Validierung
|
||||||
|
await page.getByRole('button', { name: tb.form.save, exact: true }).click()
|
||||||
|
await expect(page.getByText(tb.validation.nameRequired)).toBeVisible()
|
||||||
|
|
||||||
|
const name = uniqueName('Becken')
|
||||||
|
await page.getByLabel(`${tb.fields.name} *`).fill(name)
|
||||||
|
await page.getByLabel(tb.fields.notes).fill('Übergangsbecken')
|
||||||
|
await page.getByRole('button', { name: tb.form.save, exact: true }).click()
|
||||||
|
await expect(page.getByRole('heading', { name })).toBeVisible()
|
||||||
|
await expect(page.getByText(tb.occupancy.empty)).toBeVisible()
|
||||||
|
|
||||||
|
// Leeres Becken löschen -> zurück zur Liste
|
||||||
|
acceptNextDialog(page)
|
||||||
|
await page.getByRole('button', { name: tb.delete.action }).click()
|
||||||
|
await expect(page.getByRole('heading', { name: tb.title, exact: true })).toBeVisible()
|
||||||
|
await expect(page.getByRole('link', { name })).toBeHidden()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test.describe('Kontakte', () => {
|
||||||
|
test('Detail zeigt verknüpfte Tiere mit Rollen-Beschriftung', async ({ page }) => {
|
||||||
|
skipUnlessMock()
|
||||||
|
await page.goto('/kontakte')
|
||||||
|
await page.getByRole('link', { name: /Zoohandlung Meier/ }).click()
|
||||||
|
await expect(page.getByRole('heading', { name: 'Zoohandlung Meier' })).toBeVisible()
|
||||||
|
await expect(page.getByText(tk.linked.title)).toBeVisible()
|
||||||
|
// "Herkunft von Krümel"
|
||||||
|
await expect(page.getByText(`${tk.linked.originRole} Krümel`)).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Löschen eines referenzierten Kontakts meldet den 409-Konflikt auf Deutsch', async ({ page }) => {
|
||||||
|
skipUnlessMock()
|
||||||
|
await page.goto('/kontakte')
|
||||||
|
await page.getByRole('link', { name: /Familie Huber/ }).click()
|
||||||
|
// Pauli (abgegeben) referenziert Familie Huber als Abnehmer
|
||||||
|
await expect(page.getByText(`${tk.linked.receiverRole} Pauli`)).toBeVisible()
|
||||||
|
acceptNextDialog(page)
|
||||||
|
await page.getByRole('button', { name: tk.delete.action }).click()
|
||||||
|
await expect(page.getByText(tk.delete.conflict)).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Kontakt anlegen und (unverknüpft) wieder löschen', async ({ page }) => {
|
||||||
|
await gotoSection(page, de.nav.contacts)
|
||||||
|
await page.getByRole('link', { name: tk.newButton }).click()
|
||||||
|
const name = uniqueName('Kontakt')
|
||||||
|
await page.getByLabel(`${tk.fields.name} *`).fill(name)
|
||||||
|
await page.getByLabel(tk.fields.contactInfo).fill('test@example.de')
|
||||||
|
await page.getByRole('button', { name: tk.form.save, exact: true }).click()
|
||||||
|
await expect(page.getByRole('heading', { name })).toBeVisible()
|
||||||
|
await expect(page.getByText(tk.linked.empty)).toBeVisible()
|
||||||
|
|
||||||
|
acceptNextDialog(page)
|
||||||
|
await page.getByRole('button', { name: tk.delete.action }).click()
|
||||||
|
await expect(page.getByRole('heading', { name: tk.title, exact: true })).toBeVisible()
|
||||||
|
})
|
||||||
|
})
|
||||||
40
gerbil-manager-web/e2e/genetik.spec.ts
Normal file
40
gerbil-manager-web/e2e/genetik.spec.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
/** QA-1: Genetik (FEAT-5/GEN-1) — Probeverpaarung inkl. SCHECKE-Warnung. */
|
||||||
|
import { de, expect, gotoSection, test } from './fixtures'
|
||||||
|
|
||||||
|
const t = de.pages.genetik
|
||||||
|
|
||||||
|
function genotypeInput(page: import('@playwright/test').Page, parent: string) {
|
||||||
|
return page
|
||||||
|
.locator('fieldset.parent-selector')
|
||||||
|
.filter({ has: page.getByText(parent, { exact: true }) })
|
||||||
|
.getByLabel(t.genotypeLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
test('Probeverpaarung rechnet client-seitig und zeigt Nachkommen-Farbschläge', async ({ page }) => {
|
||||||
|
await gotoSection(page, de.nav.genetics)
|
||||||
|
await expect(page.getByText(t.subtitle)).toBeVisible()
|
||||||
|
await expect(page.getByText(t.needBoth)).toBeVisible()
|
||||||
|
|
||||||
|
await genotypeInput(page, t.father).fill('AA CC DD EE GG PP spsp rere')
|
||||||
|
await genotypeInput(page, t.mother).fill('aa CC DD EE GG PP spsp rere')
|
||||||
|
|
||||||
|
await expect(page.getByRole('heading', { name: t.resultsTitle })).toBeVisible()
|
||||||
|
// AA × aa -> alle Nachkommen Aa = Agouti (Farbschlag-Karte)
|
||||||
|
await expect(page.locator('.farbschlag-card').filter({ hasText: 'Agouti' }).first()).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Schecke × Schecke zeigt die deutsche Letalwarnung', async ({ page }) => {
|
||||||
|
await gotoSection(page, de.nav.genetics)
|
||||||
|
// Beide Eltern Spsp (Schecke): SpSp ist letal -> Warnhinweis
|
||||||
|
await genotypeInput(page, t.father).fill('Aa CC DD EE GG PP Spsp rere')
|
||||||
|
await genotypeInput(page, t.mother).fill('Aa CC DD EE GG PP Spsp rere')
|
||||||
|
|
||||||
|
await expect(page.getByRole('heading', { name: t.warningsTitle })).toBeVisible()
|
||||||
|
await expect(page.getByText(de.genetics.warnings.SCHECKE_LETHAL)).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('ungültiger Genotyp zeigt die deutsche Fehlermeldung', async ({ page }) => {
|
||||||
|
await gotoSection(page, de.nav.genetics)
|
||||||
|
await genotypeInput(page, t.father).fill('kein genotyp')
|
||||||
|
await expect(page.getByText(t.genotypeInvalid)).toBeVisible()
|
||||||
|
})
|
||||||
34
gerbil-manager-web/e2e/navigation.spec.ts
Normal file
34
gerbil-manager-web/e2e/navigation.spec.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
/** QA-1: Grundnavigation — alle Bereiche erreichbar (Smartphone: „Mehr“-Blatt). */
|
||||||
|
import { de, expect, gotoSection, test } from './fixtures'
|
||||||
|
|
||||||
|
test('alle Bereiche sind über die Navigation erreichbar', async ({ page }) => {
|
||||||
|
await page.goto('/')
|
||||||
|
await expect(page.getByRole('heading', { name: de.app.title })).toBeVisible()
|
||||||
|
await expect(page.getByRole('heading', { name: de.pages.home.title })).toBeVisible()
|
||||||
|
|
||||||
|
await gotoSection(page, de.nav.gerbils)
|
||||||
|
await expect(page.getByRole('heading', { name: de.pages.gerbils.title, exact: true })).toBeVisible()
|
||||||
|
|
||||||
|
await gotoSection(page, de.nav.litters)
|
||||||
|
await expect(page.getByRole('heading', { name: de.pages.litters.title, exact: true })).toBeVisible()
|
||||||
|
|
||||||
|
await gotoSection(page, de.nav.genetics)
|
||||||
|
await expect(page.getByRole('heading', { name: de.pages.genetik.title, exact: true })).toBeVisible()
|
||||||
|
|
||||||
|
// Sekundärbereiche (auf dem Smartphone hinter „Mehr“)
|
||||||
|
await gotoSection(page, de.nav.enclosures)
|
||||||
|
await expect(page.getByRole('heading', { name: de.pages.becken.title, exact: true })).toBeVisible()
|
||||||
|
|
||||||
|
await gotoSection(page, de.nav.contacts)
|
||||||
|
await expect(page.getByRole('heading', { name: de.pages.kontakte.title, exact: true })).toBeVisible()
|
||||||
|
|
||||||
|
await gotoSection(page, de.nav.statistics)
|
||||||
|
await expect(page.getByRole('heading', { name: de.pages.statistik.title })).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('unbekannte Route zeigt die deutsche 404-Seite', async ({ page }) => {
|
||||||
|
await page.goto('/diese-seite-gibt-es-nicht')
|
||||||
|
await expect(page.getByText(de.pages.notFound.message)).toBeVisible()
|
||||||
|
await page.getByRole('link', { name: de.pages.notFound.backHome }).click()
|
||||||
|
await expect(page.getByRole('heading', { name: de.pages.home.title })).toBeVisible()
|
||||||
|
})
|
||||||
55
gerbil-manager-web/e2e/stammbaum.spec.ts
Normal file
55
gerbil-manager-web/e2e/stammbaum.spec.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
/** QA-1: Stammbaum (FEAT-4) — interaktiver Viewer, Re-Root, Druckansicht. */
|
||||||
|
import { de, expect, skipUnlessMock, test } from './fixtures'
|
||||||
|
|
||||||
|
const t = de.pages.stammbaum
|
||||||
|
|
||||||
|
test('Viewer rendert die Ahnen-Karten und re-rootet per Tipp aufs Elterntier', async ({ page }) => {
|
||||||
|
skipUnlessMock()
|
||||||
|
await page.goto('/rennmaeuse/kruemel/stammbaum')
|
||||||
|
await expect(page.locator('.pedigree-card').first()).toBeVisible()
|
||||||
|
await expect(page.getByRole('heading', { name: t.titleFor('Krümel') })).toBeVisible()
|
||||||
|
|
||||||
|
// Erst die Ansicht einpassen, damit alle Karten im SVG-Viewport liegen
|
||||||
|
// (sonst tippt der Klick auf dem Laptop ins Leere).
|
||||||
|
const fit = page.getByRole('button', { name: t.zoomFit })
|
||||||
|
if (await fit.isVisible()) await fit.click()
|
||||||
|
|
||||||
|
// Tipp auf den Vater -> Stammbaum wurzelt neu auf Fridolin.
|
||||||
|
// (force: die d3-Baum-Animation bewegt die Karten noch eine Weile —
|
||||||
|
// Playwrights Stabilitäts-Check würde sonst dauerhaft warten.)
|
||||||
|
await page.waitForTimeout(900)
|
||||||
|
await page
|
||||||
|
.locator('.pedigree-card:not(.pedigree-card--root):not(.pedigree-card--unknown)')
|
||||||
|
.filter({ hasText: 'Fridolin' })
|
||||||
|
.first()
|
||||||
|
.click({ force: true })
|
||||||
|
await page.waitForURL('**/rennmaeuse/fridolin/stammbaum')
|
||||||
|
await expect(page.getByRole('heading', { name: t.titleFor('Fridolin') })).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Inzuchtkoeffizient wird angezeigt', async ({ page }) => {
|
||||||
|
skipUnlessMock()
|
||||||
|
await page.goto('/rennmaeuse/kruemel/stammbaum')
|
||||||
|
const inzucht = page.locator('.stammbaum-inzucht')
|
||||||
|
await expect(inzucht).toBeVisible()
|
||||||
|
await expect(inzucht).toContainText(t.inbreeding.label)
|
||||||
|
// 0.0625 -> de-DE mit max. 1 Nachkommastelle -> "6,3 %"
|
||||||
|
await expect(inzucht).toContainText('6,3')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Druckansicht (Ahnentafel) erscheint im Print-Medium', async ({ page }) => {
|
||||||
|
skipUnlessMock()
|
||||||
|
await page.goto('/rennmaeuse/kruemel/stammbaum')
|
||||||
|
await expect(page.locator('.pedigree-card').first()).toBeVisible()
|
||||||
|
|
||||||
|
await page.emulateMedia({ media: 'print' })
|
||||||
|
// Spaltenüberschriften der Ahnentafel + Erstellt-am-Zeile
|
||||||
|
await expect(page.getByText(t.printView.generations[1], { exact: true })).toBeVisible()
|
||||||
|
await expect(page.getByText(new RegExp(t.printView.createdOn))).toBeVisible()
|
||||||
|
await page.emulateMedia({ media: 'screen' })
|
||||||
|
})
|
||||||
|
|
||||||
|
test('unbekanntes Tier zeigt den deutschen Nicht-gefunden-Zustand', async ({ page }) => {
|
||||||
|
await page.goto('/rennmaeuse/gibt-es-nicht/stammbaum')
|
||||||
|
await expect(page.getByText(t.notFound).or(page.getByText(de.api.errors.notFound))).toBeVisible()
|
||||||
|
})
|
||||||
31
gerbil-manager-web/e2e/statistik.spec.ts
Normal file
31
gerbil-manager-web/e2e/statistik.spec.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
/** QA-1: Statistik & Berichte (FEAT-7) — Auswertungen rendern. */
|
||||||
|
import { de, expect, gotoSection, skipUnlessMock, test } from './fixtures'
|
||||||
|
|
||||||
|
const t = de.pages.statistik
|
||||||
|
|
||||||
|
test('Statistikseite lädt (leer oder mit Daten, aber deutsch)', async ({ page }) => {
|
||||||
|
await gotoSection(page, de.nav.statistics)
|
||||||
|
await expect(page.getByRole('heading', { name: t.title })).toBeVisible()
|
||||||
|
// Live ohne Daten: leerer Zustand; sonst die Auswertungs-Karten
|
||||||
|
await expect(
|
||||||
|
page.getByText(t.empty).or(page.getByRole('heading', { name: t.littersPerYear })),
|
||||||
|
).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('alle fünf Auswertungen rendern mit den Seed-Daten', async ({ page }) => {
|
||||||
|
skipUnlessMock()
|
||||||
|
await gotoSection(page, de.nav.statistics)
|
||||||
|
|
||||||
|
await expect(page.getByRole('heading', { name: t.littersPerYear })).toBeVisible()
|
||||||
|
await expect(page.getByRole('heading', { name: t.avgLitterSize })).toBeVisible()
|
||||||
|
await expect(page.getByRole('heading', { name: t.farbschlag })).toBeVisible()
|
||||||
|
await expect(page.getByRole('heading', { name: t.population })).toBeVisible()
|
||||||
|
await expect(page.getByRole('heading', { name: t.losses })).toBeVisible()
|
||||||
|
|
||||||
|
// Diagramme: Balken (Würfe/Jahr), horizontale Farbschlag-Balken, Bestandskurve
|
||||||
|
await expect(page.locator('svg').first()).toBeVisible()
|
||||||
|
await expect(page.locator('.hbar').first()).toBeVisible()
|
||||||
|
await expect(page.locator('svg.line-chart')).toBeVisible()
|
||||||
|
// Farbschlag-Verteilung enthält Agouti (mehrere aktive Agouti-Tiere im Seed)
|
||||||
|
await expect(page.locator('.hbar__label', { hasText: 'Agouti' })).toBeVisible()
|
||||||
|
})
|
||||||
80
gerbil-manager-web/e2e/tiere.spec.ts
Normal file
80
gerbil-manager-web/e2e/tiere.spec.ts
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
/** QA-1: Tiere (Rennmäuse) — Liste/Suche, CRUD, Detail-Tabs (FEAT-1 + FEAT-6). */
|
||||||
|
import { de, expect, gotoSection, skipUnlessMock, test, uniqueName } from './fixtures'
|
||||||
|
|
||||||
|
const t = de.pages.gerbils
|
||||||
|
const tabs = de.pages.tierTabs
|
||||||
|
|
||||||
|
test('Liste lädt und die Namenssuche filtert', async ({ page }) => {
|
||||||
|
skipUnlessMock()
|
||||||
|
await page.goto('/rennmaeuse')
|
||||||
|
await expect(page.getByRole('link', { name: /Krümel/ })).toBeVisible()
|
||||||
|
await expect(page.getByRole('link', { name: /Fridolin/ })).toBeVisible()
|
||||||
|
|
||||||
|
await page.getByPlaceholder(t.searchPlaceholder).first().fill('Krüm')
|
||||||
|
await expect(page.getByRole('link', { name: /Krümel/ })).toBeVisible()
|
||||||
|
await expect(page.getByRole('link', { name: /Fridolin/ })).toBeHidden()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Neue Rennmaus anlegen — Validierung + Erfolg', async ({ page }) => {
|
||||||
|
await gotoSection(page, de.nav.gerbils)
|
||||||
|
await page.getByRole('link', { name: t.newButton }).click()
|
||||||
|
await expect(page.getByRole('heading', { name: t.form.createTitle })).toBeVisible()
|
||||||
|
|
||||||
|
// Pflichtfeld-Validierung auf Deutsch
|
||||||
|
await page.getByRole('button', { name: t.form.save, exact: true }).click()
|
||||||
|
await expect(page.getByText(t.validation.nameRequired)).toBeVisible()
|
||||||
|
await expect(page.getByText(t.validation.genderRequired)).toBeVisible()
|
||||||
|
|
||||||
|
const name = uniqueName('Testmaus')
|
||||||
|
await page.getByLabel(`${t.fields.name} *`).fill(name)
|
||||||
|
await page.getByLabel(`${t.fields.gender} *`).selectOption({ label: t.genderLabels.female })
|
||||||
|
await page.getByRole('button', { name: t.form.save, exact: true }).click()
|
||||||
|
|
||||||
|
// Erfolg: Detailseite des neuen Tieres
|
||||||
|
await expect(page.getByRole('heading', { name })).toBeVisible()
|
||||||
|
await expect(page.getByText(t.detail.masterData)).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Detailseite zeigt Stammdaten + Tab-Inhalte (Gesundheit/Gewicht/Fotos)', async ({ page }) => {
|
||||||
|
skipUnlessMock()
|
||||||
|
await page.goto('/rennmaeuse/kruemel')
|
||||||
|
await expect(page.getByRole('heading', { name: 'Krümel' })).toBeVisible()
|
||||||
|
await expect(page.getByText(t.detail.masterData)).toBeVisible()
|
||||||
|
|
||||||
|
// Fotos (Standard-Tab): leerer Zustand + Datei-Pflicht-Validierung
|
||||||
|
await expect(page.getByText(tabs.photos.empty)).toBeVisible()
|
||||||
|
await page.getByRole('button', { name: tabs.photos.uploadButton }).click()
|
||||||
|
await expect(page.getByText(tabs.photos.validation.fileRequired)).toBeVisible()
|
||||||
|
|
||||||
|
// Gesundheit: Seed-Eintrag sichtbar, neuen Eintrag anlegen
|
||||||
|
await page.getByRole('tab', { name: t.detail.tabs.health }).click()
|
||||||
|
await expect(page.getByText('Jahresimpfung')).toBeVisible()
|
||||||
|
await expect(page.getByText(tabs.health.typeLabels.Vaccination, { exact: true })).toBeVisible()
|
||||||
|
await page.getByRole('button', { name: tabs.health.newButton }).click()
|
||||||
|
// 'Art' exakt ansteuern ('Tierarzt' enthält 'arzt'; Label-Text enthält die Optionen)
|
||||||
|
await page
|
||||||
|
.locator('label.field', { has: page.locator(`span:text-is("${tabs.health.fields.type}")`) })
|
||||||
|
.locator('select')
|
||||||
|
.selectOption({ label: tabs.health.typeLabels.Examination })
|
||||||
|
await page.getByLabel(`${tabs.health.fields.description} *`).fill('Routinekontrolle, alles gut')
|
||||||
|
await page.getByRole('button', { name: tabs.actions.save, exact: true }).click()
|
||||||
|
await expect(page.getByText('Routinekontrolle, alles gut')).toBeVisible()
|
||||||
|
|
||||||
|
// Gewicht: Verlaufskurve (2 Seed-Einträge) + Schnell-Erfassung
|
||||||
|
await page.getByRole('tab', { name: t.detail.tabs.weight }).click()
|
||||||
|
await expect(page.locator('svg.line-chart')).toBeVisible()
|
||||||
|
await expect(page.getByText(/78\s*g/)).toBeVisible()
|
||||||
|
await page.getByLabel(tabs.weight.fields.weightGrams).fill('85')
|
||||||
|
await page.getByRole('button', { name: tabs.weight.addButton, exact: true }).click()
|
||||||
|
await expect(page.getByText(/85\s*g/)).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Tier bearbeiten — Notizen ändern', async ({ page }) => {
|
||||||
|
skipUnlessMock()
|
||||||
|
await page.goto('/rennmaeuse/kruemel/bearbeiten')
|
||||||
|
await expect(page.getByRole('heading', { name: t.form.editTitle })).toBeVisible()
|
||||||
|
await page.getByLabel(t.fields.notes).fill('Sehr zutraulich.')
|
||||||
|
await page.getByRole('button', { name: t.form.save, exact: true }).click()
|
||||||
|
await expect(page.getByRole('heading', { name: 'Krümel' })).toBeVisible()
|
||||||
|
await expect(page.getByText('Sehr zutraulich.')).toBeVisible()
|
||||||
|
})
|
||||||
60
gerbil-manager-web/e2e/wuerfe.spec.ts
Normal file
60
gerbil-manager-web/e2e/wuerfe.spec.ts
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
/** QA-1: Zucht (FEAT-3) — Wurf anlegen, Detail, Jungtier erfassen. */
|
||||||
|
import { de, expect, gotoSection, skipUnlessMock, test, uniqueName } from './fixtures'
|
||||||
|
|
||||||
|
const t = de.pages.litters
|
||||||
|
const tg = de.pages.gerbils
|
||||||
|
|
||||||
|
test('Wurf anlegen — Abgabedatum wird mit +35 Tagen vorgeschlagen', async ({ page }) => {
|
||||||
|
await gotoSection(page, de.nav.litters)
|
||||||
|
await page.getByRole('link', { name: t.newButton }).click()
|
||||||
|
await expect(page.getByRole('heading', { name: t.form.createTitle })).toBeVisible()
|
||||||
|
|
||||||
|
// Pflichtfeld-Validierung
|
||||||
|
await page.getByRole('button', { name: t.form.save, exact: true }).click()
|
||||||
|
await expect(page.getByText(t.validation.nameRequired)).toBeVisible()
|
||||||
|
await expect(page.getByText(t.validation.dateRequired)).toBeVisible()
|
||||||
|
|
||||||
|
const name = uniqueName('Wurf')
|
||||||
|
await page.getByLabel(`${t.fields.name} *`).fill(name)
|
||||||
|
await page.getByLabel(`${t.fields.date} *`).fill('2026-01-01')
|
||||||
|
// Vorschlag: Wurfdatum + 35 Tage
|
||||||
|
await expect(page.getByLabel(t.fields.expectedGoHomeDate)).toHaveValue('2026-02-05')
|
||||||
|
await page.getByRole('button', { name: t.form.save, exact: true }).click()
|
||||||
|
|
||||||
|
await expect(page.getByRole('heading', { name })).toBeVisible()
|
||||||
|
await expect(page.getByText(t.detail.parents)).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Wurf-Detail zeigt Elterntiere und erwartete Farbschläge', async ({ page }) => {
|
||||||
|
skipUnlessMock()
|
||||||
|
await page.goto('/wuerfe/w-kruemel')
|
||||||
|
await expect(page.getByRole('heading', { name: 'Wurf K' })).toBeVisible()
|
||||||
|
await expect(page.getByRole('link', { name: 'Fridolin' })).toBeVisible()
|
||||||
|
await expect(page.getByRole('link', { name: 'Luna' })).toBeVisible()
|
||||||
|
// Beide Eltern haben Genotypen -> Farbvorhersage rendert
|
||||||
|
await expect(page.getByText(t.detail.expectedColors).first()).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Jungtier erfassen — Formular ist mit Wurf + Geburtsdatum vorbelegt', async ({ page }) => {
|
||||||
|
skipUnlessMock()
|
||||||
|
await page.goto('/wuerfe/w-kruemel')
|
||||||
|
await page.getByRole('link', { name: t.detail.registerJuvenile }).click()
|
||||||
|
await expect(page.getByRole('heading', { name: tg.form.createTitle })).toBeVisible()
|
||||||
|
|
||||||
|
// Vorbelegung aus ?litterId=&dob=
|
||||||
|
await expect(page.getByLabel(tg.fields.litter)).toHaveValue('w-kruemel')
|
||||||
|
await expect(page.getByLabel(tg.fields.dateOfBirth)).toHaveValue('2025-03-12')
|
||||||
|
|
||||||
|
const name = uniqueName('Jungtier')
|
||||||
|
await page.getByLabel(`${tg.fields.name} *`).fill(name)
|
||||||
|
await page.getByLabel(`${tg.fields.gender} *`).selectOption({ label: tg.genderLabels.male })
|
||||||
|
await page.getByRole('button', { name: tg.form.save, exact: true }).click()
|
||||||
|
await expect(page.getByRole('heading', { name })).toBeVisible()
|
||||||
|
|
||||||
|
// Das Jungtier hängt am Wurf
|
||||||
|
await expect(page.getByText('Wurf K')).toBeVisible()
|
||||||
|
|
||||||
|
// ... und erscheint auf der Wurf-Detailseite unter „Jungtiere“
|
||||||
|
await page.goto('/wuerfe/w-kruemel')
|
||||||
|
await expect(page.getByRole('link', { name: new RegExp(name) })).toBeVisible()
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user