# Instructions

- Following Playwright test failed.
- Explain why, be concise, respect Playwright best practices.
- Provide a snippet of code with the fix, if possible.

# Test info

- Name: login-contrast.e2e.test.ts >> LoginScene contrast >> focused input border-color is accent #22D3EE (rgb 34,211,238)
- Location: test/e2e/login-contrast.e2e.test.ts:101:3

# Error details

```
TimeoutError: page.waitForSelector: Timeout 15000ms exceeded.
Call log:
  - waiting for locator('form#login-form h1') to be visible

```

# Page snapshot

```yaml
- generic [ref=e2]: "{\"error\":\"invite token required\"}"
```

# Test source

```ts
  1   | // apps/client/test/e2e/login-contrast.e2e.test.ts
  2   | // [int->REQ-CLI-06] [unit->REQ-CLI-02]
  3   | //
  4   | // Plan 06-10 Task 2 — WCAG AA contrast regression guard for the LoginScene
  5   | // DOM form. Closes UAT Finding #1 (D-23): black-on-near-black text against
  6   | // the #0A0E1A background at Windows 125% scaling.
  7   | //
  8   | // Selector strategy (Task 0 / D-33 verified):
  9   | //   - Form root:       form#login-form
  10  | //   - Heading:         form#login-form h1
  11  | //   - Username label:  form#login-form label[for="username"]
  12  | //     (wrapping <label for="username"> that contains input#username)
  13  | //   - Password label:  form#login-form label[for="password"]
  14  | //   - Username input:  form#login-form input#username
  15  | //   - Password input:  form#login-form input#password
  16  | //   - Submit CTA:      form#login-form button[type="submit"]
  17  | //   - Error region:    form#login-form #error
  18  | //
  19  | // All selectors derived from ACTUAL markup in apps/client/public/forms/login.html
  20  | // (Task 0 inspection). Contrast ratios pre-verified by hand before this file
  21  | // was written (Task 0 WCAG table — all text pairs ≥ 4.5:1).
  22  | //
  23  | // Hand-calculated WCAG contrast ratios (Task 0 / D-33):
  24  | //   #F3F4F6 on #0A0E1A  → 17.50:1  (heading, labels)
  25  | //   #F3F4F6 on #1F2937  → 13.34:1  (input text on secondary bg)
  26  | //   #0A0E1A on #22D3EE  → 10.65:1  (CTA text on accent)
  27  | //   #EF4444 on #0A0E1A  →  5.12:1  (error text — passes ≥ 4.5:1)
  28  | //
  29  | // Test structure: 6 contrast probes (≥ 4.5:1) + 1 focus-border probe (#22D3EE).
  30  | 
  31  | import { test, expect, computeContrastInPage } from './fixtures.js';
  32  | 
  33  | // Helper: evaluate the in-page contrast function for a selector
  34  | type ContrastResult = { fg: string; bg: string; ratio: number };
  35  | 
  36  | async function getContrast(
  37  |   page: import('@playwright/test').Page,
  38  |   selector: string,
  39  | ): Promise<ContrastResult> {
  40  |   // eslint-disable-next-line @typescript-eslint/no-implied-eval
  41  |   return page.evaluate(
  42  |     new Function('selector', `return (${computeContrastInPage})(selector)`) as (
  43  |       sel: string,
  44  |     ) => ContrastResult,
  45  |     selector,
  46  |   );
  47  | }
  48  | 
  49  | // ---------------------------------------------------------------------------
  50  | // Suite: LoginScene contrast — WCAG AA ≥ 4.5:1 for every visible text element
  51  | // ---------------------------------------------------------------------------
  52  | test.describe('LoginScene contrast', () => {
  53  |   test.beforeEach(async ({ page }) => {
  54  |     // Navigate to the root; LoginScene mounts the DOM form on load.
  55  |     // Wait for the form heading to confirm the form is present in the DOM.
  56  |     await page.goto('/');
> 57  |     await page.waitForSelector('form#login-form h1', { timeout: 15_000 });
      |                ^ TimeoutError: page.waitForSelector: Timeout 15000ms exceeded.
  58  |   });
  59  | 
  60  |   // --- Test 1: heading contrast ---
  61  |   test('heading has ≥ 4.5:1 contrast ratio on #0A0E1A background', async ({
  62  |     page,
  63  |   }) => {
  64  |     const result = await getContrast(page, 'form#login-form h1');
  65  |     expect(result.ratio, `Heading contrast: fg=${result.fg} bg=${result.bg}`).toBeGreaterThanOrEqual(4.5);
  66  |   });
  67  | 
  68  |   // --- Test 2a: Username label contrast ---
  69  |   test('username label has ≥ 4.5:1 contrast ratio', async ({ page }) => {
  70  |     const result = await getContrast(page, 'form#login-form label[for="username"]');
  71  |     expect(result.ratio, `Username label contrast: fg=${result.fg} bg=${result.bg}`).toBeGreaterThanOrEqual(4.5);
  72  |   });
  73  | 
  74  |   // --- Test 2b: Password label contrast ---
  75  |   test('password label has ≥ 4.5:1 contrast ratio', async ({ page }) => {
  76  |     const result = await getContrast(page, 'form#login-form label[for="password"]');
  77  |     expect(result.ratio, `Password label contrast: fg=${result.fg} bg=${result.bg}`).toBeGreaterThanOrEqual(4.5);
  78  |   });
  79  | 
  80  |   // --- Test 2c: Username input text contrast ---
  81  |   test('username input text has ≥ 4.5:1 contrast ratio', async ({ page }) => {
  82  |     const result = await getContrast(page, 'form#login-form input#username');
  83  |     expect(result.ratio, `Username input contrast: fg=${result.fg} bg=${result.bg}`).toBeGreaterThanOrEqual(4.5);
  84  |   });
  85  | 
  86  |   // --- Test 2d: Password input text contrast ---
  87  |   test('password input text has ≥ 4.5:1 contrast ratio', async ({ page }) => {
  88  |     const result = await getContrast(page, 'form#login-form input#password');
  89  |     expect(result.ratio, `Password input contrast: fg=${result.fg} bg=${result.bg}`).toBeGreaterThanOrEqual(4.5);
  90  |   });
  91  | 
  92  |   // --- Test 2e: CTA button contrast ---
  93  |   test('submit CTA button text has ≥ 4.5:1 contrast ratio', async ({
  94  |     page,
  95  |   }) => {
  96  |     const result = await getContrast(page, 'form#login-form button[type="submit"]');
  97  |     expect(result.ratio, `CTA button contrast: fg=${result.fg} bg=${result.bg}`).toBeGreaterThanOrEqual(4.5);
  98  |   });
  99  | 
  100 |   // --- Test 3: Focus border color = #22D3EE (accent) ---
  101 |   test('focused input border-color is accent #22D3EE (rgb 34,211,238)', async ({
  102 |     page,
  103 |   }) => {
  104 |     // Focus the username input and read its computed border-color
  105 |     const borderColor = await page.evaluate(() => {
  106 |       const el = document.querySelector('form#login-form input#username') as HTMLElement | null;
  107 |       if (!el) throw new Error('Username input not found');
  108 |       el.focus();
  109 |       return window.getComputedStyle(el).borderColor;
  110 |     });
  111 |     // Playwright normalize: browser may return "rgb(34, 211, 238)" or similar
  112 |     expect(borderColor).toMatch(/rgb\(34,\s*211,\s*238\)/);
  113 |   });
  114 | });
  115 | 
```