From fe1121de65ddf06e1ccf325f6bfe30e75e3ddfa4 Mon Sep 17 00:00:00 2001 From: Marcel Date: Tue, 31 Mar 2026 15:08:36 +0200 Subject: [PATCH] test(focus-rings): add failing Playwright tests for --c-focus-ring token and element ring colors Co-Authored-By: Claude Sonnet 4.6 --- frontend/e2e/focus-rings.spec.ts | 88 ++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 frontend/e2e/focus-rings.spec.ts diff --git a/frontend/e2e/focus-rings.spec.ts b/frontend/e2e/focus-rings.spec.ts new file mode 100644 index 00000000..1fcb6d44 --- /dev/null +++ b/frontend/e2e/focus-rings.spec.ts @@ -0,0 +1,88 @@ +import { test, expect } from '@playwright/test'; + +// Expected focus ring resolved colors +// Light: --c-focus-ring: #012851 (brand-navy) +const FOCUS_RING_LIGHT = 'rgb(1, 40, 81)'; +// Dark: --c-focus-ring: #a1dcd8 (brand-mint) +const FOCUS_RING_DARK = 'rgb(161, 220, 216)'; + +test.describe('Focus ring token — CSS custom property', () => { + test('--c-focus-ring is defined in light mode', async ({ page }) => { + await page.goto('/'); + await page.waitForSelector('[data-hydrated]'); + + const value = await page.evaluate(() => + getComputedStyle(document.documentElement).getPropertyValue('--c-focus-ring').trim() + ); + expect(value).toBe('#012851'); + }); + + test('--c-focus-ring is defined in dark mode', async ({ page }) => { + await page.goto('/'); + await page.waitForSelector('[data-hydrated]'); + await page.evaluate(() => document.documentElement.setAttribute('data-theme', 'dark')); + + const value = await page.evaluate(() => + getComputedStyle(document.documentElement).getPropertyValue('--c-focus-ring').trim() + ); + expect(value).toBe('#a1dcd8'); + }); +}); + +test.describe('Focus ring — header interactive elements', () => { + test('ThemeToggle has brand-navy ring in light mode', async ({ page }) => { + await page.goto('/'); + await page.waitForSelector('[data-hydrated]'); + + await page.getByRole('button', { name: /dark mode|dunkelmodus/i }).focus(); + const boxShadow = await page.evaluate( + () => getComputedStyle(document.activeElement as HTMLElement).boxShadow + ); + expect(boxShadow).toContain(FOCUS_RING_LIGHT); + }); + + test('AppNav link has brand-mint ring in dark mode', async ({ page }) => { + await page.goto('/'); + await page.waitForSelector('[data-hydrated]'); + await page.evaluate(() => document.documentElement.setAttribute('data-theme', 'dark')); + + // Focus first desktop nav link + await page.locator('header nav').getByRole('link').first().focus(); + const boxShadow = await page.evaluate( + () => getComputedStyle(document.activeElement as HTMLElement).boxShadow + ); + expect(boxShadow).toContain(FOCUS_RING_DARK); + }); +}); + +test.describe('Focus ring — form inputs', () => { + test.use({ storageState: { cookies: [], origins: [] } }); + + test('login username input has brand-mint ring in dark mode', async ({ page }) => { + await page.goto('/login'); + await page.evaluate(() => document.documentElement.setAttribute('data-theme', 'dark')); + + await page.locator('#username').focus(); + const boxShadow = await page.evaluate( + () => getComputedStyle(document.activeElement as HTMLElement).boxShadow + ); + expect(boxShadow).toContain(FOCUS_RING_DARK); + }); +}); + +test.describe('Focus ring — PersonTypeahead', () => { + test('PersonTypeahead input has brand-navy ring in light mode', async ({ page }) => { + await page.goto('/'); + await page.waitForSelector('[data-hydrated]'); + + // Open advanced filter panel to expose the sender PersonTypeahead + await page.getByRole('button', { name: /filter/i }).click(); + await page.waitForSelector('#senderId-search'); + + await page.locator('#senderId-search').focus(); + const boxShadow = await page.evaluate( + () => getComputedStyle(document.activeElement as HTMLElement).boxShadow + ); + expect(boxShadow).toContain(FOCUS_RING_LIGHT); + }); +});