/** * User stories covered: Feature 4 — In-app notification bell * - Header contains a notification bell icon for any logged-in user * - Bell has an aria-label containing "notification" * - Clicking the bell opens a dropdown/panel with notification items or empty state * - Unread badge is visible when there are unread notifications * * Note: Seed data may not include unread notifications for every user. The badge * visibility test is conditional; the bell render and panel-open tests are hard assertions. * * Created: 2026-05-17 */ import { test, expect } from "@playwright/test"; import { login, USERS } from "./helpers/login"; test.describe("Feature 4 — In-app notification bell", () => { test("US-4a: notification bell button is visible in header for TECHNICAL user", async ({ page, }) => { await login(page, USERS.TECH); // The bell button is rendered by NotificationBell component in Header // Its aria-label is: "Notifications" or "Notifications (N unread)" const bell = page.getByRole("button", { name: /notification/i }); await expect(bell).toBeVisible(); console.log("✓ Notification bell button visible"); }); test("US-4a: notification bell is visible for MANAGER user", async ({ page, }) => { await login(page, USERS.MANAGER); const bell = page.getByRole("button", { name: /notification/i }); await expect(bell).toBeVisible(); console.log("✓ Notification bell visible for Manager"); }); test("US-4a: notification bell is visible for ACCOUNTS user", async ({ page, }) => { await login(page, USERS.ACCOUNTS); const bell = page.getByRole("button", { name: /notification/i }); await expect(bell).toBeVisible(); console.log("✓ Notification bell visible for Accounts"); }); test("US-4c: clicking the bell opens a notification panel", async ({ page, }) => { await login(page, USERS.TECH); const bell = page.getByRole("button", { name: /notification/i }); await bell.click(); // The panel header says "Notifications" await expect(page.getByRole("heading", { name: /notifications/i })).toBeVisible({ timeout: 5_000, }); console.log("✓ Notification panel opens after clicking bell"); }); test("US-4c: notification panel shows items or empty-state message", async ({ page, }) => { await login(page, USERS.TECH); await page.getByRole("button", { name: /notification/i }).click(); // Either there are notification items OR the empty state message const hasItems = await page.locator("text=No notifications yet").isVisible(); const hasNotifications = (await page.locator("[class*='divide'] > *").count()) > 0; expect(hasItems || hasNotifications).toBeTruthy(); console.log( hasItems ? "✓ Empty state message visible (no notifications)" : "✓ Notification items visible in panel" ); }); test("US-4b: unread badge appears on bell when there are unread notifications", async ({ page, }) => { // Trigger a notification by going through the approval flow // This is stateful — we just check that if a badge is rendered, it has positive count await login(page, USERS.TECH); const bell = page.getByRole("button", { name: /notification/i }); // Check if badge exists — it only renders when unreadCount > 0 const badge = page.locator("button[aria-label*='unread'] span, button[title='Notifications'] span"); const badgeCount = await badge.count(); if (badgeCount > 0) { // Badge text should be a positive integer or "99+" const badgeText = await badge.first().innerText(); expect(/^\d+\+?$/.test(badgeText.trim())).toBeTruthy(); console.log(`✓ Unread badge shows count: ${badgeText.trim()}`); } else { // No unread notifications — bell aria-label should just say "Notifications" await expect(bell).toHaveAttribute("aria-label", "Notifications"); console.log("✓ No unread notifications — badge correctly absent"); } }); });