import { expect, test } from '@playwright/test'; import { GoTrueClient, randomEmail, randomPassword, waitMailInterval } from '../../support/gotrue'; import { expectMailCount, waitForMail } from '../../support/mailpit'; import { freshBrowserContext, fillField, login, open, pressFirstButton, register, t, textOf } from '../../support/web'; const gotrue = new GoTrueClient(); /** Inhalt des Semantics-Baums, ohne die (je Lauf wechselnde) Adresse. */ async function screenText(page: import('@playwright/test').Page): Promise { // Eingabefelder (mit der je Lauf wechselnden Adresse) gehoeren nicht zum Vergleich. return (await page.locator('body').ariaSnapshot()).replace(/^\s*- textbox.*\n?/gm, '').trim(); } test.describe('KTO-01 Kriterium 4 (web): gleicher Bildschirm fuer neue und vorhandene Adresse', () => { test('KTO-01 Kriterium 4: Registrierung mit bestehender, bestaetigter Adresse zeigt exakt denselben Bildschirm wie mit neuer Adresse', async ({ page, browser, }) => { // Neue Adresse. await register(page, { email: randomEmail(), password: randomPassword() }); await expect(page).toHaveURL(/\/register\/check-email$/); await expect(textOf(page, t.checkEmailTitle)).toBeVisible(); const fresh = await screenText(page); // Vorhandene, bestaetigte Adresse (nur ueber die API angelegt). const existing = randomEmail(); await gotrue.createConfirmedUser(existing, randomPassword()); const second = await (await freshBrowserContext(browser)).newPage(); try { await register(second, { email: existing, password: randomPassword() }); await expect(second).toHaveURL(/\/register\/check-email$/); await expect(textOf(second, t.checkEmailTitle)).toBeVisible(); expect(await screenText(second)).toBe(fresh); // Es geht keine Mail an das bestehende Konto. await expectMailCount(existing, 0); } finally { await second.context().close(); } }); }); test.describe('KTO-01 Kriterium 6 (web): unbestaetigtes Konto', () => { test('KTO-01 Kriterium 6: Login mit richtigem Passwort zeigt Hinweis und Aktion; „erneut senden“ schickt eine zweite Mail', async ({ page, }) => { const email = randomEmail(); const password = randomPassword(); await gotrue.signUp(email, password); await waitForMail(email); await waitMailInterval(); await login(page, email, password); await expect(textOf(page, t.emailNotConfirmed)).toBeVisible(); await page.getByRole('button', { name: t.resendConfirmation }).click(); await expect(textOf(page, t.checkEmailResent)).toBeVisible(); await waitForMail(email, 2); }); }); test.describe('KTO-01 Kriterium 7 (web): keine Auskunft ueber Konten, Drosselungshinweis', () => { test('KTO-01 Kriterium 7: falsche E-Mail und falsches Passwort zeigen denselben Text', async ({ page, browser }) => { const email = randomEmail(); const password = randomPassword(); await gotrue.createConfirmedUser(email, password); await login(page, email, `${password}-falsch`); await expect(textOf(page, t.invalidCredentials)).toBeVisible(); const wrongPassword = await screenText(page); const ctx = await freshBrowserContext(browser); try { const other = await ctx.newPage(); await login(other, randomEmail('unknown'), password); await expect(textOf(other, t.invalidCredentials)).toBeVisible(); expect(await screenText(other)).toBe(wrongPassword); } finally { await ctx.close(); } }); /** * Die CLI drosselt nie (AUTH-CFG-13/A2), ein echter Test waere nie rot. Die 429-Antwort wird * deshalb per `page.route` simuliert; der echte Limiter wird im Projekt `throttle` geprueft. * CORS: Die App (localhost:3000) ruft Kong (127.0.0.1:54321) cross-origin auf; die simulierte * Antwort braucht deshalb die CORS-Header, sonst sieht die App einen Netzfehler statt 429. */ test('KTO-01 Kriterium 7: AUTH-CFG-10 bei 429 erscheint der Drosselungshinweis ohne Dauer', async ({ page }) => { await page.route('**/auth/v1/token?grant_type=password', async (route) => { const request = route.request(); if (request.method() === 'OPTIONS') return route.fallback(); await route.fulfill({ status: 429, contentType: 'application/json', headers: { 'access-control-allow-origin': request.headers()['origin'] ?? '*' }, body: JSON.stringify({ code: 429, error_code: 'over_request_rate_limit', msg: 'Request rate limit reached' }), }); }); await login(page, randomEmail(), randomPassword()); await expect(textOf(page, t.rateLimited)).toBeVisible(); await expect(textOf(page, t.invalidCredentials)).toHaveCount(0); }); }); test.describe('QUE-06 (web): Ablauf nur per Tastatur', () => { test('QUE-06 Kriterium 3: Anmelden nur mit Tab, Eingabe und Enter (E-Mail-Feld per Tab erreicht)', async ({ page }) => { const email = randomEmail(); const password = randomPassword(); await gotrue.createConfirmedUser(email, password); await open(page, '/login'); await expect(page.getByRole('button', { name: 'Sign in', exact: true })).toBeVisible(); // Kein Klick: Tab fokussiert das erste Feld (E-Mail), Tab das zweite (Passwort). await page.keyboard.press('Tab'); await expect(page.getByRole('textbox', { name: 'Email address' })).toBeFocused(); await page.keyboard.type(email); await page.keyboard.press('Tab'); await expect(page.getByRole('textbox', { name: 'Password' })).toBeFocused(); await page.keyboard.type(password); await page.keyboard.press('Enter'); await expect(textOf(page, t.home)).toBeVisible(); }); });