/** * E2E: Settings → Notifications turns push on for this browser (the Push API is stubbed: headless * Chromium has no push service), sets quiet hours and switches nudges off; all of it survives a * reload. */ import { expect, type Page, test } from "@playwright/test"; import { captureScreenshot, completeOnboarding, openUserSettings, rpc, signup } from "./helpers"; const ENDPOINT = "https://push.example.test/e2e-device"; /** * A fake Notification permission and service worker registration, kept in sessionStorage so they * survive a reload. Headless Chromium reports notifications as denied even when granted. */ async function stubPushApi(page: Page, endpoint: string) { await page.addInitScript((pushEndpoint) => { const permissionKey = "e2e:notification-permission"; const notification = Object.defineProperties(function Notification() {}, { permission: { get: () => sessionStorage.getItem(permissionKey) ?? "default" }, requestPermission: { value: async () => { sessionStorage.setItem(permissionKey, "granted"); return "granted"; }, }, }); Object.defineProperty(window, "Notification", { configurable: true, writable: true, value: notification, }); const storageKey = "e2e:push-subscription"; const current = () => { const stored = sessionStorage.getItem(storageKey); if (!stored) return null; const saved = JSON.parse(stored) as { serverKey: number[] }; return { endpoint: pushEndpoint, options: { applicationServerKey: new Uint8Array(saved.serverKey).buffer }, toJSON: () => ({ endpoint: pushEndpoint, keys: { p256dh: `B${"A".repeat(86)}`, auth: "A".repeat(22) }, }), unsubscribe: async () => { sessionStorage.removeItem(storageKey); return true; }, }; }; const registration = { scope: `${location.origin}/`, pushManager: { getSubscription: async () => current(), subscribe: async (options: { applicationServerKey: Uint8Array }) => { sessionStorage.setItem( storageKey, JSON.stringify({ serverKey: [...new Uint8Array(options.applicationServerKey)] }), ); return current(); }, }, }; Object.defineProperty(navigator, "serviceWorker", { configurable: true, value: { register: async () => registration, getRegistration: async () => registration, ready: Promise.resolve(registration), }, }); if (!("PushManager" in window)) { Object.defineProperty(window, "PushManager", { configurable: true, value: class {} }); } }, endpoint); } test("notification settings: push on this device, quiet hours and nudges", async ({ page, }, testInfo) => { await stubPushApi(page, ENDPOINT); await signup(page, `notifications-${Date.now()}@rakazo.test`, "password12", "Push Tester"); await completeOnboarding(page); let settings = await openUserSettings(page, "notifications"); const panel = settings.getByTestId("notification-settings"); const push = panel.getByTestId("notification-push-switch"); await expect(push).not.toBeChecked(); const subscribed = page.waitForRequest("**/rpc/notifications/webPush/subscribe"); await push.click(); expect((await subscribed).postDataJSON()).toMatchObject({ json: { endpoint: ENDPOINT } }); await expect(push).toBeChecked(); const quiet = panel.getByTestId("notification-quiet-hours-switch"); await expect(quiet).not.toBeChecked(); await quiet.click(); await expect(quiet).toBeChecked(); const start = panel.getByTestId("notification-quiet-hours-start"); await expect(start).toHaveValue("22:00"); await expect(panel.getByTestId("notification-quiet-hours-end")).toHaveValue("07:00"); const saved = page.waitForResponse( (response) => response.url().includes("/rpc/notifications/settings/update") && response.request().postData()?.includes("23:30") === true, ); await start.fill("23:30"); await saved; const nudges = panel.getByTestId("notification-nudges-switch"); await expect(nudges).toBeChecked(); await nudges.click(); await expect(nudges).not.toBeChecked(); await expect .poll(() => rpc<{ nudges: boolean }>(page, "notifications/settings/get", null)) .toMatchObject({ nudges: false, quietHours: { start: "23:30", end: "07:00" } }); await captureScreenshot(page, testInfo, "notification-settings"); await page.reload(); settings = await openUserSettings(page, "notifications"); await expect(settings.getByTestId("notification-push-switch")).toBeChecked(); await expect(settings.getByTestId("notification-quiet-hours-start")).toHaveValue("23:30"); await expect(settings.getByTestId("notification-nudges-switch")).not.toBeChecked(); const unsubscribed = page.waitForRequest("**/rpc/notifications/webPush/unsubscribe"); await settings.getByTestId("notification-push-switch").click(); expect((await unsubscribed).postDataJSON()).toMatchObject({ json: { endpoint: ENDPOINT } }); await expect(settings.getByTestId("notification-push-switch")).not.toBeChecked(); });