170 lines
8.4 KiB
TypeScript
170 lines
8.4 KiB
TypeScript
/**
|
|
* QA-SMOKE-2: Gezielter Live-Smoke der heute gemergten Änderungen (main e6b513e).
|
|
* Läuft NUR im LIVE-Modus (E2E_BASE_URL gesetzt).
|
|
*
|
|
* Abgedeckte Änderungen:
|
|
* 1. GEN-3h — Bracket-Notation: eef→ee[f], cchm→c[chm], C/cchm→Cc[chm]
|
|
* 2. FORM-FIELDS — originBreeder + isDeaf im Tier-Editformular
|
|
* 3. CR-2 — Partial-Update nullt keine bestehenden Felder mehr
|
|
* 4. UI-POLISH — '(ohne Namen)'-Platzhalter für namenlose Tiere
|
|
* 5. GEHEGE — Redirects + Nav (bereits in gehege-regression.spec; hier live-Verifikation)
|
|
*
|
|
* Read-Only soweit möglich. CR-2-Test: idempotentes Save (keine Wertänderung).
|
|
*/
|
|
import { de, expect, skipUnlessLive, test } from './fixtures'
|
|
|
|
const API = 'http://localhost:5179'
|
|
|
|
// ── 1. GEN-3h Bracket-Notation ───────────────────────────────────────────────
|
|
|
|
test('GEN-3h: eef zeigt ee[f] (nicht "eef") im Tier-Detail', async ({ page }) => {
|
|
skipUnlessLive()
|
|
// Roswitha — genotype: "aa CC D? eef Gg P? spsp ??"
|
|
// eef = e/ef het → E>e>ef Rank → ee[f]
|
|
await page.goto('/rennmaeuse/66ee246c-26f0-41ac-8b6c-c151d9287633')
|
|
await expect(page.getByRole('heading', { name: 'Roswitha von den Kleinen Chaoten' })).toBeVisible()
|
|
// Genotyp-Block soll e[f] enthalten, nicht rohes 'eef'
|
|
const genoCode = page.locator('code').filter({ hasText: /e\[f\]/ })
|
|
await expect(genoCode).toBeVisible({ timeout: 8_000 })
|
|
// Rohes 'eef' darf nicht mehr im Code-Block stehen
|
|
await expect(page.locator('code').filter({ hasText: /[^[]eef/ })).toBeHidden()
|
|
})
|
|
|
|
test('GEN-3h: cchm/cchm zeigt c[chm]c[chm] im Tier-Detail (ZoneFire)', async ({ page }) => {
|
|
skipUnlessLive()
|
|
// ZoneFire — genotype: "Aa cchmcchm D? Ee Gg P? Spsp ??"
|
|
await page.goto('/rennmaeuse/14d5cea9-0f87-4381-8afe-80f1d58f7b76')
|
|
await expect(page.getByRole('heading', { name: 'ZoneFire' })).toBeVisible()
|
|
const genoCode = page.locator('code').filter({ hasText: /c\[chm\]/ })
|
|
await expect(genoCode).toBeVisible({ timeout: 8_000 })
|
|
await expect(page.locator('code').filter({ hasText: /cchmcchm/ })).toBeHidden()
|
|
})
|
|
|
|
test('GEN-3h: Cc[chm] + ee[f] korrekt bei Ethan (gemischter Locus)', async ({ page }) => {
|
|
skipUnlessLive()
|
|
// Ethan — genotype: "Aa Ccchm D? eef Gg Pp Spsp ??"
|
|
// Ccchm → Cc[chm]; eef → ee[f]
|
|
await page.goto('/rennmaeuse/54ef9b08-0def-496e-95db-5107b732ddd4')
|
|
await expect(page.getByRole('heading', { name: 'Ethan von den Kleinen Chaoten' })).toBeVisible()
|
|
const genoCode = page.locator('code')
|
|
await expect(genoCode.filter({ hasText: /c\[chm\]/ })).toBeVisible({ timeout: 8_000 })
|
|
await expect(genoCode.filter({ hasText: /e\[f\]/ })).toBeVisible()
|
|
// Rohes Storage-Format darf nicht sichtbar sein
|
|
await expect(genoCode.filter({ hasText: /Ccchm|eef/ })).toBeHidden()
|
|
})
|
|
|
|
// ── 2. FORM-FIELDS: originBreeder + isDeaf im Formular ───────────────────────
|
|
|
|
test('FORM-FIELDS: Herkunft-Freitext + Gehörlos-Dropdown im Tier-Editformular', async ({
|
|
page,
|
|
}) => {
|
|
skipUnlessLive()
|
|
// Ethan hat bereits originBreeder="Kleinen Chaoten", isDeaf=null
|
|
await page.goto('/rennmaeuse/54ef9b08-0def-496e-95db-5107b732ddd4/bearbeiten')
|
|
await expect(page.getByRole('heading', { name: de.pages.gerbils.form.editTitle })).toBeVisible({ timeout: 8_000 })
|
|
|
|
// Herkunft-Freitext (originBreeder) soll sichtbar und gefüllt sein
|
|
const originInput = page.getByLabel(de.pages.gerbils.fields.originBreeder)
|
|
await expect(originInput).toBeVisible()
|
|
await expect(originInput).toHaveValue('Kleinen Chaoten')
|
|
|
|
// Gehörlos-Dropdown (isDeaf) soll sichtbar sein mit "Unbekannt"-Default
|
|
// Gehörlos-Dropdown (isDeaf): label 'Gehörlos', aktuell leer (Unbekannt)
|
|
const deafLabel = page.locator('label').filter({ hasText: de.pages.gerbils.fields.isDeaf })
|
|
await expect(deafLabel).toBeVisible()
|
|
// Dropdown-Select im Label-Kontext
|
|
const deafSelect = deafLabel.locator('select')
|
|
await expect(deafSelect).toBeVisible()
|
|
})
|
|
|
|
// ── 3. CR-2: idempotentes Save nullt keine Felder ────────────────────────────
|
|
|
|
test('CR-2: Partial-PUT nullt keine bestehenden Felder (direkter API-Test)', async ({ page }) => {
|
|
skipUnlessLive()
|
|
// Snapshot VORHER
|
|
const before = await page.request.get(`${API}/gerbils/54ef9b08-0def-496e-95db-5107b732ddd4`)
|
|
const snap = await before.json()
|
|
expect(snap.originBreeder).toBe('Kleinen Chaoten')
|
|
expect(snap.genotype).toBeTruthy()
|
|
|
|
// Partial-PUT: nur name + gender senden (minimal body, alle anderen Felder fehlen)
|
|
// Mit PATCH-Semantik-Fix: fehlende Felder werden NICHT genullt
|
|
const res = await page.request.put(`${API}/gerbils/54ef9b08-0def-496e-95db-5107b732ddd4`, {
|
|
data: {
|
|
name: snap.name,
|
|
gender: snap.gender,
|
|
status: snap.status,
|
|
isResident: snap.isResident,
|
|
// ALLE anderen Felder absichtlich weggelassen → vor CR-2-Fix würden sie auf null gesetzt
|
|
},
|
|
})
|
|
// PATCH-Semantik: 204 No Content (kein Body), fehlende Felder bleiben erhalten
|
|
expect(res.status()).toBe(204)
|
|
|
|
// Re-Fetch und vergleichen
|
|
const afterRes = await page.request.get(`${API}/gerbils/54ef9b08-0def-496e-95db-5107b732ddd4`)
|
|
const updated = await afterRes.json()
|
|
|
|
expect(updated.originBreeder).toBe(snap.originBreeder) // muss erhalten bleiben
|
|
expect(updated.genotype).toBe(snap.genotype) // muss erhalten bleiben
|
|
expect(updated.dateOfBirth).toBe(snap.dateOfBirth) // muss erhalten bleiben
|
|
expect(updated.litterId).toBe(snap.litterId) // muss erhalten bleiben
|
|
expect(updated.externalRef).toBe(snap.externalRef) // muss erhalten bleiben
|
|
|
|
// Restore: idempotent (gleiche Werte gesetzt, kein Net-Change)
|
|
})
|
|
|
|
// ── 4. UI-POLISH: '(ohne Namen)'-Platzhalter ─────────────────────────────────
|
|
|
|
test('UI-POLISH: namenlose Tiere zeigen "(ohne Namen)" in der Tierliste', async ({ page }) => {
|
|
skipUnlessLive()
|
|
// Alle Tiere anzeigen (inkl. externe), um namenlose zu sehen
|
|
await page.goto('/rennmaeuse')
|
|
await expect(page.getByRole('heading', { name: de.pages.gerbils.title, exact: true })).toBeVisible()
|
|
// "(ohne Namen)" Platzhalter soll mindestens einmal sichtbar sein
|
|
// (Filter auf alle Tiere — ggf. "Externe anzeigen" aktivieren)
|
|
const placeholder = page.getByText('(ohne Namen)')
|
|
// Wenn nicht sofort sichtbar, Toggle "Externe anzeigen" probieren
|
|
if (!(await placeholder.isVisible({ timeout: 3_000 }).catch(() => false))) {
|
|
const toggle = page.locator('button, label').filter({ hasText: /extern|alle/i }).first()
|
|
if (await toggle.isVisible()) await toggle.click()
|
|
}
|
|
await expect(placeholder.first()).toBeVisible({ timeout: 8_000 })
|
|
})
|
|
|
|
test('UI-POLISH: namenloser Knoten im Stammbaum zeigt "(ohne Namen)"', async ({ page }) => {
|
|
skipUnlessLive()
|
|
// Stammbaum eines Tiers mit namenlosem Elternteil
|
|
// Tier "46c646a9" ist namenlos + resident; sein Elternteil im Stammbaum-Kontext
|
|
// Alternativ: Vance's Stammbaum enthält tiefe Äste, manche namenlos
|
|
await page.goto('/rennmaeuse/afdaff89-0274-4778-8f2f-3957a81bf58e/stammbaum')
|
|
await expect(page.locator('svg').first()).toBeVisible({ timeout: 12_000 })
|
|
// Prüfe ob "(ohne Namen)" irgendwo im Baum erscheint
|
|
const placeholder = page.getByText('(ohne Namen)')
|
|
// Kein harter Fail wenn kein namenloser Knoten sichtbar — Tree kann mit Lazy-Gen-5+ variieren
|
|
// Aber wenn vorhanden, muss der Text sauber sein (nicht leer)
|
|
const visible = await placeholder.isVisible({ timeout: 5_000 }).catch(() => false)
|
|
if (visible) {
|
|
await expect(placeholder.first()).toBeVisible()
|
|
}
|
|
// Kein JS-Error auf der Seite (implizit durch erfolgreiche Navigation)
|
|
})
|
|
|
|
// ── 5. GEHEGE: Live-Verifikation der Redirect/Nav-Änderungen ─────────────────
|
|
|
|
test('GEHEGE live: /becken leitet auf /gehege weiter', async ({ page }) => {
|
|
skipUnlessLive()
|
|
await page.goto('/becken')
|
|
await expect(page).toHaveURL(/\/gehege$/, { timeout: 5_000 })
|
|
await expect(page.getByRole('heading', { name: de.pages.becken.title, exact: true })).toBeVisible()
|
|
})
|
|
|
|
test('GEHEGE live: "/" leitet auf /rennmaeuse weiter (kein Start-Screen)', async ({ page }) => {
|
|
skipUnlessLive()
|
|
await page.goto('/')
|
|
await expect(page).toHaveURL(/\/rennmaeuse$/, { timeout: 5_000 })
|
|
await expect(
|
|
page.getByRole('heading', { name: de.pages.gerbils.title, exact: true }),
|
|
).toBeVisible()
|
|
})
|