From c31948b997ba7d204c848096b3a9dad8d4d539e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20F=C3=B6rtsch?= Date: Wed, 11 Mar 2026 11:42:00 +0100 Subject: [PATCH] add playwright e2e tests for onboarding, contact tracker - install playwright, configure for chromium with vite dev server - add onboarding flow test: redirect, form fill, navigation to prozess - add contact tracker test: create contact, verify in list - fix onboarding form navigating to /onboarding instead of /prozess - fix date rendering crash in contact list (cast datum to text) - exclude @electric-sql/pglite from vite optimizeDeps (WASM bundle) - add test-results/, playwright-report/ to .gitignore Co-Authored-By: Claude Opus 4.6 --- .gitignore | 4 +++ bun.lock | 9 ++++++ e2e/kontakte.spec.ts | 29 +++++++++++++++++++ e2e/onboarding.spec.ts | 18 ++++++++++++ package.json | 1 + playwright.config.ts | 13 +++++++++ src/features/kontakte/hooks.ts | 2 +- .../onboarding/components/onboarding-form.tsx | 2 +- vite.config.ts | 3 ++ 9 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 e2e/kontakte.spec.ts create mode 100644 e2e/onboarding.spec.ts create mode 100644 playwright.config.ts diff --git a/.gitignore b/.gitignore index 1faa5d4..e8b503e 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,10 @@ dist/ # Mise local overrides .mise.local.toml +# Playwright +test-results/ +playwright-report/ + # Logs *.log diff --git a/bun.lock b/bun.lock index 59d9238..4d3ed12 100644 --- a/bun.lock +++ b/bun.lock @@ -25,6 +25,7 @@ }, "devDependencies": { "@biomejs/biome": "^2.4.6", + "@playwright/test": "^1.58.2", "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.2", "@types/node": "^25.4.0", @@ -352,6 +353,8 @@ "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + "@playwright/test": ["@playwright/test@1.58.2", "", { "dependencies": { "playwright": "1.58.2" }, "bin": { "playwright": "cli.js" } }, "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA=="], + "@radix-ui/number": ["@radix-ui/number@1.1.1", "", {}, "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g=="], "@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="], @@ -1112,6 +1115,10 @@ "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + "playwright": ["playwright@1.58.2", "", { "dependencies": { "playwright-core": "1.58.2" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A=="], + + "playwright-core": ["playwright-core@1.58.2", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg=="], + "possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="], "postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="], @@ -1480,6 +1487,8 @@ "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + "playwright/fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="], + "readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], "recast/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], diff --git a/e2e/kontakte.spec.ts b/e2e/kontakte.spec.ts new file mode 100644 index 0000000..0d16f66 --- /dev/null +++ b/e2e/kontakte.spec.ts @@ -0,0 +1,29 @@ +import { expect, test } from "@playwright/test"; + +test.beforeEach(async ({ page }) => { + await page.goto("/onboarding"); + // Wait for PGlite to initialize and the form to render + await page.getByLabel("Name").waitFor({ timeout: 15000 }); + + await page.getByLabel("Name").fill("Test User"); + await page.getByLabel("PLZ").fill("10115"); + await page.getByLabel("Ort").fill("Berlin"); + await page.getByLabel("Krankenkasse").fill("TK"); + await page.getByRole("button", { name: "Weiter" }).click(); + await expect(page).toHaveURL(/prozess/, { timeout: 10000 }); +}); + +test("add a new contact and see it in the list", async ({ page }) => { + await page.getByRole("link", { name: "Kontakte" }).click(); + await expect(page).toHaveURL(/kontakte/); + + await page.getByRole("link", { name: /Neu/ }).click(); + await expect(page).toHaveURL(/kontakte\/neu/); + + await page.getByLabel("Name", { exact: false }).first().fill("Dr. Schmidt"); + await page.getByLabel("Stadt").fill("Berlin"); + await page.getByRole("button", { name: "Speichern" }).click(); + + await expect(page).toHaveURL(/\/kontakte$/, { timeout: 10000 }); + await expect(page.getByText("Dr. Schmidt")).toBeVisible({ timeout: 10000 }); +}); diff --git a/e2e/onboarding.spec.ts b/e2e/onboarding.spec.ts new file mode 100644 index 0000000..69a9ba6 --- /dev/null +++ b/e2e/onboarding.spec.ts @@ -0,0 +1,18 @@ +import { expect, test } from "@playwright/test"; + +test("onboarding flow redirects new user and saves profile", async ({ + page, +}) => { + await page.goto("/"); + // PGlite WASM initialization takes time; wait for the redirect to complete + await expect(page).toHaveURL(/onboarding/, { timeout: 15000 }); + + await page.getByLabel("Name").fill("Max Mustermann"); + await page.getByLabel("PLZ").fill("10115"); + await page.getByLabel("Ort").fill("Berlin"); + await page.getByLabel("Krankenkasse").fill("TK"); + await page.getByRole("button", { name: "Weiter" }).click(); + + await expect(page).toHaveURL(/prozess/, { timeout: 10000 }); + await expect(page.getByText("Dein Fortschritt")).toBeVisible(); +}); diff --git a/package.json b/package.json index 7d6080a..16b922e 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ }, "devDependencies": { "@biomejs/biome": "^2.4.6", + "@playwright/test": "^1.58.2", "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.2", "@types/node": "^25.4.0", diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..6ba1458 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from "@playwright/test"; + +export default defineConfig({ + testDir: "e2e", + webServer: { + command: "bun dev", + port: 5173, + reuseExistingServer: true, + }, + use: { + baseURL: "http://localhost:5173", + }, +}); diff --git a/src/features/kontakte/hooks.ts b/src/features/kontakte/hooks.ts index 1de5580..ec63e62 100644 --- a/src/features/kontakte/hooks.ts +++ b/src/features/kontakte/hooks.ts @@ -25,7 +25,7 @@ export function useTherapeutenListe() { return useDbQuery(` SELECT t.id, t.name, t.stadt, t.therapieform, - (SELECT k.datum FROM kontakt k WHERE k.therapeut_id = t.id ORDER BY k.datum DESC LIMIT 1) as letzter_kontakt, + (SELECT k.datum::text FROM kontakt k WHERE k.therapeut_id = t.id ORDER BY k.datum DESC LIMIT 1) as letzter_kontakt, (SELECT k.ergebnis FROM kontakt k WHERE k.therapeut_id = t.id ORDER BY k.datum DESC LIMIT 1) as letztes_ergebnis, (SELECT COUNT(*) FROM kontakt k WHERE k.therapeut_id = t.id) as kontakte_gesamt FROM therapeut t diff --git a/src/features/onboarding/components/onboarding-form.tsx b/src/features/onboarding/components/onboarding-form.tsx index fa1abc9..d6513ee 100644 --- a/src/features/onboarding/components/onboarding-form.tsx +++ b/src/features/onboarding/components/onboarding-form.tsx @@ -30,7 +30,7 @@ export function OnboardingForm() { value.aktueller_schritt, ], ); - navigate({ to: "/onboarding" }); + navigate({ to: "/prozess" }); }, }); diff --git a/vite.config.ts b/vite.config.ts index 4385553..24c043c 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -19,6 +19,9 @@ export default defineConfig({ manifest: false, }), ], + optimizeDeps: { + exclude: ["@electric-sql/pglite"], + }, resolve: { alias: { "@": path.resolve(__dirname, "./src"),