// spec: e2e/plans/parent-notifications.plan.md §10 import { test, expect, type Page } from '@playwright/test' function digestToggle(page: Page) { return page .locator('.toggle-field-row') .filter({ has: page.locator('.toggle-field-label', { hasText: 'Daily Digest' }) }) .locator('button.toggle-btn') } function pushToggle(page: Page) { return page .locator('.toggle-field-row') .filter({ has: page.locator('.toggle-field-label', { hasText: 'Push Notifications' }) }) .locator('button.toggle-btn') } test.describe('User Profile Notification Settings', () => { test.describe.configure({ mode: 'serial' }) // --------------------------------------------------------------------------- // 10.1 Email Digest toggle is visible on User Profile page // --------------------------------------------------------------------------- test('Email Digest toggle is visible on User Profile page', async ({ page }) => { await page.goto('/parent/profile') await expect(digestToggle(page)).toBeVisible({ timeout: 5000 }) }) // --------------------------------------------------------------------------- // 10.2 Email Digest toggle initialises from the server value // --------------------------------------------------------------------------- test('Email Digest toggle initialises from the server value', async ({ page, request }) => { // Set known state: enabled await request.put('/api/user/profile', { data: { email_digest_enabled: true } }) await page.goto('/parent/profile') await expect(digestToggle(page)).toHaveAttribute('aria-pressed', 'true', { timeout: 5000 }) // Set known state: disabled await request.put('/api/user/profile', { data: { email_digest_enabled: false } }) await page.reload() await expect(digestToggle(page)).toHaveAttribute('aria-pressed', 'false', { timeout: 5000 }) // Restore default await request.put('/api/user/profile', { data: { email_digest_enabled: true } }) }) // --------------------------------------------------------------------------- // 10.3 Toggling Email Digest off sends email_digest_enabled: false to API // --------------------------------------------------------------------------- test('Toggling Email Digest off sends correct API payload', async ({ page, request }) => { await request.put('/api/user/profile', { data: { email_digest_enabled: true } }) await page.goto('/parent/profile') // Wait for profile to load and toggle to reflect server state await expect(digestToggle(page)).toHaveAttribute('aria-pressed', 'true', { timeout: 5000 }) let capturedBody: Record | null = null await page.route('**/api/user/profile', async (route) => { if (route.request().method() === 'PUT') { capturedBody = JSON.parse(route.request().postData() ?? '{}') } await route.continue() }) await digestToggle(page).click() // Submit the form (Save button) await page.getByRole('button', { name: 'Save' }).click() await page.waitForTimeout(500) expect(capturedBody).not.toBeNull() expect((capturedBody as Record)['email_digest_enabled']).toBe(false) await expect(digestToggle(page)).toHaveAttribute('aria-pressed', 'false', { timeout: 3000 }) // Restore await request.put('/api/user/profile', { data: { email_digest_enabled: true } }) }) // --------------------------------------------------------------------------- // 10.4 Toggling Email Digest on sends email_digest_enabled: true to API // --------------------------------------------------------------------------- test('Toggling Email Digest on sends correct API payload', async ({ page, request }) => { await request.put('/api/user/profile', { data: { email_digest_enabled: false } }) await page.goto('/parent/profile') // Wait for profile to load and toggle to reflect server state await expect(digestToggle(page)).toHaveAttribute('aria-pressed', 'false', { timeout: 5000 }) let capturedBody: Record | null = null await page.route('**/api/user/profile', async (route) => { if (route.request().method() === 'PUT') { capturedBody = JSON.parse(route.request().postData() ?? '{}') } await route.continue() }) await digestToggle(page).click() await page.getByRole('button', { name: 'Save' }).click() await page.waitForTimeout(500) expect(capturedBody).not.toBeNull() expect((capturedBody as Record)['email_digest_enabled']).toBe(true) await expect(digestToggle(page)).toHaveAttribute('aria-pressed', 'true', { timeout: 3000 }) // Restore await request.put('/api/user/profile', { data: { email_digest_enabled: true } }) }) // --------------------------------------------------------------------------- // 10.5 Push Notifications toggle is visible on User Profile page // --------------------------------------------------------------------------- test('Push Notifications toggle is visible on User Profile page', async ({ page }) => { await page.goto('/parent/profile') await expect(pushToggle(page)).toBeVisible({ timeout: 5000 }) }) // --------------------------------------------------------------------------- // 10.6 Push Notifications toggle initialises to off when no subscription exists // --------------------------------------------------------------------------- test('Push Notifications toggle initialises to off when no subscription exists', async ({ page, }) => { // Do NOT grant notifications permission — toggle should start unchecked await page.goto('/parent/profile') const toggle = pushToggle(page) await expect(toggle).toBeVisible({ timeout: 5000 }) // Either aria-pressed="false" or disabled (when browser denies permissions) const ariaPressed = await toggle.getAttribute('aria-pressed') expect(ariaPressed).toBe('false') }) // --------------------------------------------------------------------------- // 10.8 Push Notifications toggle is disabled when browser permission is denied // --------------------------------------------------------------------------- test('Push Notifications toggle is disabled when browser permission is denied', async ({ page, context, }) => { // Do not grant permission (leave as denied/prompt) await context.clearPermissions() await page.goto('/parent/profile') const toggle = pushToggle(page) await expect(toggle).toBeVisible({ timeout: 5000 }) // When permission is denied, the toggle should be disabled or aria-pressed="false" const isDisabled = await toggle.isDisabled().catch(() => false) const ariaPressed = await toggle.getAttribute('aria-pressed') // Either disabled or not pressed — permission denied prevents enabling it expect(isDisabled || ariaPressed === 'false').toBe(true) }) })