diff --git a/frontend/e2e/geschichten.spec.ts b/frontend/e2e/geschichten.spec.ts index 1a05abb2..a69079fc 100644 --- a/frontend/e2e/geschichten.spec.ts +++ b/frontend/e2e/geschichten.spec.ts @@ -68,43 +68,71 @@ test.describe('Geschichten — writer + reader journey', () => { await page.goto('/geschichten'); await page.waitForSelector('[data-hydrated]'); - // We need two persons to filter by. Open the picker and pick one whose name - // the dev seed reliably contains. Then open the picker again and pick a - // second one. Picking is via the typeahead — we type, wait for the listbox, - // click the first option. - async function pickPerson(query: string) { + // We need two distinct persons to filter by, but we don't want to couple this + // test to specific seed names. Strategy: type a single broadly-occurring vowel + // ("e" is present in the vast majority of German names), open the listbox, + // and pick whichever option matches the predicate. + // + // option DOM ids encode the person id as `${listboxId}-option-${personId}`, + // so we can identify the *first different* option without knowing the seed. + const PROBE = 'e'; + + async function openPicker() { await page.getByRole('button', { name: /Person wählen/ }).click(); const input = page.getByRole('combobox', { name: /Person wählen/ }); - await input.fill(query); - // Wait for at least one option in the listbox, then click it - const firstOption = page.getByRole('option').first(); - await expect(firstOption).toBeVisible(); - await firstOption.click(); + await input.fill(PROBE); + // Wait for the listbox to be populated. + await expect(page.getByRole('option').first()).toBeVisible(); } - await pickPerson('a'); - await page.waitForURL(/personId=/); - const firstUrl = new URL(page.url()); - const firstIds = firstUrl.searchParams.getAll('personId'); - expect(firstIds).toHaveLength(1); + async function pickFirstOption(): Promise { + const opt = page.getByRole('option').first(); + const optId = (await opt.getAttribute('id')) ?? ''; + const personId = optId.split('-option-')[1] ?? ''; + expect(personId).not.toEqual(''); + await opt.click(); + return personId; + } - await pickPerson('b'); + async function pickFirstOptionDifferentFrom(excludeId: string): Promise { + // Iterate through visible options and return the first whose person id != excludeId. + const optionCount = await page.getByRole('option').count(); + for (let i = 0; i < optionCount; i++) { + const candidate = page.getByRole('option').nth(i); + const optId = (await candidate.getAttribute('id')) ?? ''; + const personId = optId.split('-option-')[1] ?? ''; + if (personId && personId !== excludeId) { + await candidate.click(); + return personId; + } + } + throw new Error( + `Expected at least two distinct persons matching "${PROBE}" in the seed, found only one.` + ); + } + + await openPicker(); + const firstId = await pickFirstOption(); + await page.waitForURL(/personId=/); + const firstIds = new URL(page.url()).searchParams.getAll('personId'); + expect(firstIds).toEqual([firstId]); + + await openPicker(); + const secondId = await pickFirstOptionDifferentFrom(firstId); await page.waitForURL((url) => url.searchParams.getAll('personId').length === 2); - const secondUrl = new URL(page.url()); - const secondIds = secondUrl.searchParams.getAll('personId'); - expect(secondIds).toHaveLength(2); - expect(secondIds[0]).toBe(firstIds[0]); // first one persists - expect(secondIds[1]).not.toBe(firstIds[0]); // second is different + const secondIds = new URL(page.url()).searchParams.getAll('personId'); + expect(secondIds).toEqual([firstId, secondId]); + expect(secondId).not.toEqual(firstId); // Two chips visible — find them by their remove-aria-label pattern const chipButtons = page.getByRole('button', { name: /aus Filter entfernen/ }); await expect(chipButtons).toHaveCount(2); - // Remove the first chip — URL drops to one param + // Remove the first chip — URL drops to one param, only the second id remains await chipButtons.first().click(); await page.waitForURL((url) => url.searchParams.getAll('personId').length === 1); const finalIds = new URL(page.url()).searchParams.getAll('personId'); - expect(finalIds).toEqual([secondIds[1]]); + expect(finalIds).toEqual([secondId]); }); test('AxeBuilder finds no critical violations on the index', async ({ page }) => {