Covers 21 user story groups extracted from the last 15+ feature commits: - rebrand.spec.ts — PPMS branding on login page, sidebar, and tab title - dashboard/po-status-badges.js — color-coded status badges for submitter & manager - po-submit-button.spec.ts — Submit for Approval button visibility on DRAFT POs - notification-bell.spec.ts — in-app bell icon, unread badge, and panel open - export-gate.spec.ts — export buttons gated on MGR_APPROVED+ status; 403 on pre-approval - payment-history.spec.ts — /payments/history accessible to ACCOUNTS; redirects others - partial-receipt.spec.ts — per-item delivery tracking UI on paid POs - vendor-auto-verify.spec.ts — vendor verification status visible in admin - admin-bordered-buttons.spec.ts — Edit/Deactivate/Delete have border classes on admin pages - profile.spec.ts — profile page loads for all roles; signature section for MANAGER/SUPERUSER - inventory/items-tags.spec.ts — Cheapest/Closest tags and auto-sort by distance - inventory/cart-icon.spec.ts — cart header icon with badge; item/vendor detail pages - mobile/desktop-required.spec.ts — Desktop Required overlay for non-mobile roles + sign-out - mobile/manager-approvals.spec.ts — mobile card layout; edit form hidden; action buttons visible - mobile/accounts-payments.spec.ts — ACCOUNTS payments queue and buttons on mobile - mobile/bottom-nav.spec.ts — Home/Approvals/Profile tabs for MANAGER; Home/Payments/Profile for ACCOUNTS - approvals-edit-highlight.spec.ts — diff indicators on resubmitted POs Also adds shared helpers/login.ts with USERS constants, login(), createDraftPo(), and submitPo() — all using name-attribute selectors since PO form labels have no htmlFor binding. Playwright config updated: workers capped at 2 locally (was unlimited) to prevent auth concurrency failures, and retries set to 1 locally. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
100 lines
4 KiB
TypeScript
100 lines
4 KiB
TypeScript
/**
|
|
* 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");
|
|
}
|
|
});
|
|
});
|