From 3013b53b19072642c669c03af71a465b496cedc9 Mon Sep 17 00:00:00 2001 From: Lucas Berger Date: Sun, 14 Jun 2026 22:44:44 -0400 Subject: [PATCH] test(18-04): playwright-cli timezone round-trip e2e spec - 6 desktop tests covering the full 18-04 acceptance criteria: timezone section visible, combobox pre-filled, save disabled when unchanged, save enables on change, persists across reload, use-detected affordance sets browser zone - All 6 pass against the real 18-02 API endpoints --- apps/pwa/e2e/timezone-verify.spec.ts | 92 ++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 apps/pwa/e2e/timezone-verify.spec.ts diff --git a/apps/pwa/e2e/timezone-verify.spec.ts b/apps/pwa/e2e/timezone-verify.spec.ts new file mode 100644 index 0000000..1b4ac21 --- /dev/null +++ b/apps/pwa/e2e/timezone-verify.spec.ts @@ -0,0 +1,92 @@ +/** + * timezone-verify.spec.ts — 18-04 round-trip verification + * + * Verifies the admin Timezone section with the real 18-02 API endpoints. + * Runs on desktop profile only (admin UI is desktop-focused). + * + * NOTE: the IANA picker input is type="text" with list="iana-zones" which gives + * it the ARIA combobox role (not textbox) in Chromium. + */ +import { test, expect } from '@playwright/test'; + +test.describe('Admin Timezone section — 18-04 round-trip', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/admin'); + await expect(page.getByRole('heading', { name: 'Admin Settings' })).toBeVisible(); + // Wait for the Timezone section to load (requires the 18-02 GET endpoint) + const tzSection = page.getByRole('region', { name: 'Timezone' }); + await expect(tzSection).toBeVisible({ timeout: 15_000 }); + }); + + test('Timezone section is visible on /admin', async ({ page }) => { + await expect(page.getByRole('region', { name: 'Timezone' })).toBeVisible(); + }); + + test('Timezone input (combobox) is visible and pre-filled', async ({ page }) => { + // ARIA role for is combobox + const input = page.getByRole('combobox', { name: 'Household timezone' }); + await expect(input).toBeVisible(); + const val = await input.inputValue(); + expect(val.length, 'Input should have a non-empty timezone').toBeGreaterThan(0); + }); + + test('Save button is disabled when timezone is unchanged', async ({ page }) => { + const tzSection = page.getByRole('region', { name: 'Timezone' }); + const saveBtn = tzSection.getByRole('button', { name: /Save|Saving/ }); + await expect(saveBtn).toBeVisible(); + await expect(saveBtn).toBeDisabled(); + }); + + test('Changing the input enables Save', async ({ page }) => { + const tzSection = page.getByRole('region', { name: 'Timezone' }); + const input = page.getByRole('combobox', { name: 'Household timezone' }); + await input.fill('America/Chicago'); + const saveBtn = tzSection.getByRole('button', { name: /Save/ }); + await expect(saveBtn).toBeEnabled(); + }); + + test('Save persists timezone across reload', async ({ page }) => { + const input = page.getByRole('combobox', { name: 'Household timezone' }); + const tzSection = page.getByRole('region', { name: 'Timezone' }); + + // Set to a known value + await input.fill('America/Chicago'); + const saveBtn = tzSection.getByRole('button', { name: /^Save$/ }); + await expect(saveBtn).toBeEnabled(); + await saveBtn.click(); + // Wait for save to complete (button re-disables when stored === input) + await expect(saveBtn).toBeDisabled({ timeout: 15_000 }); + + // Reload and verify persistence + await page.reload(); + await expect(page.getByRole('heading', { name: 'Admin Settings' })).toBeVisible(); + await expect(tzSection).toBeVisible({ timeout: 15_000 }); + const inputAfterReload = page.getByRole('combobox', { name: 'Household timezone' }); + await expect(inputAfterReload).toHaveValue('America/Chicago'); + + // system-default note should be gone after explicit set + await expect(tzSection.getByText('Using system default')).toHaveCount(0); + }); + + test('"Use detected" affordance sets input to browser zone', async ({ page }) => { + const tzSection = page.getByRole('region', { name: 'Timezone' }); + const detectedBtn = tzSection.getByRole('button', { name: /Use detected:/ }); + + // The button is only visible when detected TZ != stored TZ + const count = await detectedBtn.count(); + if (count === 0) { + test.skip(); + return; + } + + // Get the detected zone from the button label + const btnText = await detectedBtn.textContent(); + const match = btnText?.match(/Use detected:\s*(.+)/); + const detectedZone = match?.[1]?.trim(); + expect(detectedZone, 'Detected zone should be non-empty').toBeTruthy(); + + await detectedBtn.click(); + const input = page.getByRole('combobox', { name: 'Household timezone' }); + await expect(input).toHaveValue(detectedZone!); + }); +});