# 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: tests/ui/identity-bar.spec.ts >> identity bar (sites#355) >> one row at 320px: identity and account actions left, controls right
- Location: tests/ui/identity-bar.spec.ts:40:9

# Error details

```
Error: a single row

expect(received).toBeLessThan(expected)

Expected: < 56
Received:   156.421875
```

# Page snapshot

```yaml
- generic [active] [ref=e1]:
  - banner [ref=e2]:
    - heading "US Elections" [level=1] [ref=e3]
    - paragraph [ref=e4]: Election Data Explorer
  - generic [ref=e5]:
    - generic [ref=e6]:
      - generic [ref=e7]:
        - text: Jjane.reporter@example.org
        - generic [ref=e8]: MemberVoters
      - generic [ref=e9]:
        - button "Request access" [ref=e10]: Request accessRequest
        - link "Log out" [ref=e11] [cursor=pointer]:
          - /url: /outpost.goauthentik.io/sign_out
    - toolbar "Member tools" [ref=e12]:
      - button "Save search" [ref=e13]
      - 'button "Export CSV (Needs Individual donors access: request it)" [disabled] [ref=e17]': Export CSV
      - button "All cycles" [ref=e23]
  - generic [ref=e27]:
    - generic [ref=e28]:
      - generic [ref=e29]: Electoral
      - generic [ref=e30]:
        - link "🏛️ 290 Parties" [ref=e31] [cursor=pointer]:
          - /url: /parties/
          - generic [ref=e32]: 🏛️
          - generic [ref=e33]:
            - generic [ref=e34]: "290"
            - generic [ref=e35]: Parties
        - link "🎖️ 746 Offices" [ref=e36] [cursor=pointer]:
          - /url: /offices/
          - generic [ref=e37]: 🎖️
          - generic [ref=e38]:
            - generic [ref=e39]: "746"
            - generic [ref=e40]: Offices
        - link "👤 57,328 Politicians" [ref=e41] [cursor=pointer]:
          - /url: /politicians/
          - generic [ref=e42]: 👤
          - generic [ref=e43]:
            - generic [ref=e44]: 57,328
            - generic [ref=e45]: Politicians
        - link "🗳️ - Voters" [ref=e46] [cursor=pointer]:
          - /url: /voters/
          - generic [ref=e47]: 🗳️
          - generic [ref=e48]:
            - generic [ref=e49]: "-"
            - generic [ref=e50]: Voters
    - generic [ref=e51]:
      - generic [ref=e52]: Financial
      - generic [ref=e53]:
        - link "💼 89,712 Committees" [ref=e54] [cursor=pointer]:
          - /url: /committees/
          - generic [ref=e55]: 💼
          - generic [ref=e56]:
            - generic [ref=e57]: 89,712
            - generic [ref=e58]: Committees
        - link "🏢 1,398,441 Vendors" [ref=e59] [cursor=pointer]:
          - /url: /vendors/
          - generic [ref=e60]: 🏢
          - generic [ref=e61]:
            - generic [ref=e62]: 1,398,441
            - generic [ref=e63]: Vendors
        - link "🏭 5,711,245 Employers" [ref=e64] [cursor=pointer]:
          - /url: /employers/
          - generic [ref=e65]: 🏭
          - generic [ref=e66]:
            - generic [ref=e67]: 5,711,245
            - generic [ref=e68]: Employers
        - generic [ref=e69]:
          - generic [ref=e70]: 🧑
          - generic [ref=e71]:
            - generic [ref=e72]: 34,032,401
            - generic [ref=e73]: Individual Donors
    - generic [ref=e74]:
      - generic [ref=e75]: Geography
      - generic [ref=e76]:
        - link "🗺️ 52 States" [ref=e77] [cursor=pointer]:
          - /url: /states/
          - generic [ref=e78]: 🗺️
          - generic [ref=e79]: "52"
          - generic [ref=e80]: States
        - link "📍 444 Districts" [ref=e81] [cursor=pointer]:
          - /url: /districts/
          - generic [ref=e82]: 📍
          - generic [ref=e83]: "444"
          - generic [ref=e84]: Districts
        - link "🗂️ 3,222 Counties" [ref=e85] [cursor=pointer]:
          - /url: /counties/
          - generic [ref=e86]: 🗂️
          - generic [ref=e87]: 3,222
          - generic [ref=e88]: Counties
        - link "🏘️ 32,114 Places" [ref=e89] [cursor=pointer]:
          - /url: /places/
          - generic [ref=e90]: 🏘️
          - generic [ref=e91]: 32,114
          - generic [ref=e92]: Places
      - generic [ref=e93]:
        - link "📮 33,791 Postal Codes" [ref=e94] [cursor=pointer]:
          - /url: /postalcodes/
          - generic [ref=e95]: 📮
          - generic [ref=e96]: 33,791
          - generic [ref=e97]: Postal Codes
        - link "📐 85,382 Census Tracts" [ref=e98] [cursor=pointer]:
          - /url: /tracts/
          - generic [ref=e99]: 📐
          - generic [ref=e100]: 85,382
          - generic [ref=e101]: Census Tracts
        - link "🗳️ 163,242 Precincts" [ref=e102] [cursor=pointer]:
          - /url: /precincts/
          - generic [ref=e103]: 🗳️
          - generic [ref=e104]: 163,242
          - generic [ref=e105]: Precincts
        - generic [ref=e106]:
          - generic [ref=e107]: 🧱
          - generic [ref=e108]: 8,126,956
          - generic [ref=e109]: Census Blocks
    - generic [ref=e110]:
      - generic [ref=e111]: Other
      - generic [ref=e112]:
        - link "🗳️ 24 Elections" [ref=e113] [cursor=pointer]:
          - /url: /elections/
          - generic [ref=e114]: 🗳️
          - generic [ref=e115]: "24"
          - generic [ref=e116]: Elections
        - link "📊 - Data Sources" [ref=e117] [cursor=pointer]:
          - /url: /datasources/
          - generic [ref=e118]: 📊
          - generic [ref=e119]: "-"
          - generic [ref=e120]: Data Sources
  - contentinfo [ref=e121]:
    - link "elect.info" [ref=e122] [cursor=pointer]:
      - /url: https://www.elect.info
    - text: "|"
    - link "Trust Center" [ref=e123] [cursor=pointer]:
      - /url: https://trust.elect.info
    - text: "|"
    - link "Privacy Policy" [ref=e124] [cursor=pointer]:
      - /url: https://trust.elect.info/#privacy-policy
    - text: "|"
    - link "Terms of Use" [ref=e125] [cursor=pointer]:
      - /url: https://trust.elect.info/#terms
```

# Test source

```ts
  1   | import { test, expect, Page } from '@playwright/test';
  2   | 
  3   | // Full-width identity bar and member controls (electinfo/sites#355), shared by
  4   | // us.elect.info and us.elect.news (sites/shared/identity/). Controls are
  5   | // visible to everyone and usable only when signed in (and entitled, if a
  6   | // control names an area). /auth/whoami and /auth/account/me are mocked, so
  7   | // these run on CI pods (Authentik off) as well as on preview.
  8   | 
  9   | type Entitlements = { voters?: boolean; individuals?: boolean; blocks?: boolean; curator?: boolean };
  10  | 
  11  | // Two test controls, queued before the bar loads like a site script would.
  12  | const ICON = '<svg viewBox="0 0 16 16"><rect width="16" height="16" fill="currentColor"/></svg>';
  13  | const CONTROLS = `
  14  |   window.__activated = [];
  15  |   (window.electinfoBarControls = window.electinfoBarControls || []).push(
  16  |     { id: 'save', label: 'Save search', icon: '${ICON}', onActivate: function (c) { window.__activated.push('save:' + c.email); } },
  17  |     { id: 'export', label: 'Export CSV', icon: '${ICON}', requires: 'individuals', onActivate: function () { window.__activated.push('export'); } }
  18  |   );`;
  19  | 
  20  | async function open(page: Page, email: string, entitlements: Entitlements = {}, controls = true): Promise<void> {
  21  |   if (controls) await page.addInitScript(CONTROLS);
  22  |   await page.route('**/auth/whoami*', r => r.fulfill({ json: { enabled: 'true', email, elevated: false, entitlements } }));
  23  |   await page.route('**/auth/account/me*', r => r.fulfill({ json: { email, requests: [], agreement: null, granted: [] } }));
  24  |   await page.goto('/');
  25  |   await expect(page.locator('#auth-slot.ei-bar')).toBeVisible();
  26  | }
  27  | 
  28  | const activated = (page: Page): Promise<string[]> => page.evaluate(() => (window as unknown as { __activated: string[] }).__activated);
  29  | 
  30  | test.describe('identity bar (sites#355)', () => {
  31  |   test('spans the page width under the header', async ({ page }) => {
  32  |     await open(page, '');
  33  |     const bar = await page.locator('#auth-slot').boundingBox();
  34  |     const header = await page.locator('body > header').boundingBox();
  35  |     expect(bar && header && Math.abs(bar.width - header.width) <= 2, 'bar as wide as the header').toBe(true);
  36  |     expect(bar && header && bar.y >= header.y + header.height, 'bar below the header').toBe(true);
  37  |   });
  38  | 
  39  |   for (const width of [1280, 390, 320]) {
  40  |     test(`one row at ${width}px: identity and account actions left, controls right`, async ({ page }) => {
  41  |       await page.setViewportSize({ width, height: 800 });
  42  |       await open(page, 'jane.reporter@example.org', { voters: true });
  43  |       const box = async (sel: string) => (await page.locator(sel).boundingBox())!;
  44  |       const bar = await box('#auth-slot');
  45  |       const identity = await box('#auth-slot .ei-bar__identity');
  46  |       const controls = await box('#auth-slot .ei-bar__controls');
  47  |       const logout = await box('#auth-slot a.auth-link');
  48  |       for (const [name, b] of [['identity', identity], ['controls', controls], ['Log out', logout]] as const) {
  49  |         expect(b.y >= bar.y && b.y + b.height <= bar.y + bar.height, `${name} inside the bar's single row`).toBe(true);
  50  |       }
> 51  |       expect(bar.height, 'a single row').toBeLessThan(56);
      |                                          ^ Error: a single row
  52  |       expect(identity.x - bar.x, 'identity left-justified').toBeLessThan(16);
  53  |       expect(bar.x + bar.width - (controls.x + controls.width), 'controls right-justified').toBeLessThan(16);
  54  |       expect(controls.x >= identity.x + identity.width, 'controls after the identity').toBe(true);
  55  |       expect(logout.x + logout.width <= controls.x, 'account actions end before the controls').toBe(true);
  56  |       for (const id of ['save', 'export']) {
  57  |         const c = await box(`#auth-slot [data-control="${id}"]`);
  58  |         expect(c.x >= controls.x - 1 && c.x + c.width <= controls.x + controls.width + 1, `${id} fully visible`).toBe(true);
  59  |       }
  60  |       expect(await page.evaluate(() => document.documentElement.scrollWidth - document.documentElement.clientWidth)).toBeLessThanOrEqual(0);
  61  |     });
  62  |   }
  63  | 
  64  |   for (const width of [1280, 390, 320]) {
  65  |     test(`signed out at ${width}px: Log in and Create an account are buttons in one row`, async ({ page }) => {
  66  |       await page.setViewportSize({ width, height: 800 });
  67  |       await open(page, '');
  68  |       const login = page.locator('#auth-slot a.auth-link');
  69  |       const register = page.locator('#auth-slot a.ei-identity__register');
  70  |       await expect(login).toHaveText('Log in');
  71  |       await expect(register).toBeVisible();
  72  |       await expect(register).toHaveClass(/ei-identity__action/);
  73  |       await expect(register).toHaveText(width <= 480 ? 'Sign up' : 'Create an account', { useInnerText: true });
  74  |       const [l, r, c, bar] = await Promise.all([login.boundingBox(), register.boundingBox(), page.locator('#auth-slot .ei-bar__controls').boundingBox(), page.locator('#auth-slot').boundingBox()]);
  75  |       expect(Math.abs(l!.height - r!.height), 'same height as Log in').toBeLessThanOrEqual(1);
  76  |       expect(r!.x + r!.width <= c!.x, 'before the controls').toBe(true);
  77  |       expect(bar!.height, 'a single row').toBeLessThan(56);
  78  |     });
  79  |   }
  80  | 
  81  |   test('signed out: controls are shown locked and only offer sign-in', async ({ page }) => {
  82  |     await open(page, '');
  83  |     const save = page.locator('#auth-slot [data-control="save"]');
  84  |     await expect(page.locator('#auth-slot .ei-bar__controls')).toBeVisible();
  85  |     await expect(save).toBeVisible();
  86  |     await expect(save).toHaveAttribute('aria-disabled', 'true');
  87  |     await expect(save).toHaveClass(/ei-control--locked/);
  88  |     await expect(page.locator('#auth-slot a.auth-link')).toHaveText('Log in');
  89  |     await save.click({ force: true });
  90  |     const panel = page.locator('dialog#ei-signin');
  91  |     await expect(panel).toBeVisible();
  92  |     await expect(panel.locator('h2')).toHaveText('Sign in to use Save search');
  93  |     expect(await activated(page)).toEqual([]);
  94  |   });
  95  | 
  96  |   test('signed in: member controls work; area controls stay locked and offer Request access', async ({ page }) => {
  97  |     await open(page, 'jane@example.org');
  98  |     const save = page.locator('#auth-slot [data-control="save"]');
  99  |     await expect(save).not.toHaveAttribute('aria-disabled', 'true');
  100 |     await save.click();
  101 |     expect(await activated(page)).toEqual(['save:jane@example.org']);
  102 | 
  103 |     const exp = page.locator('#auth-slot [data-control="export"]');
  104 |     await expect(exp).toHaveAttribute('aria-disabled', 'true');
  105 |     await exp.click({ force: true });
  106 |     const form = page.locator('dialog#ei-request');
  107 |     await expect(form).toBeVisible();
  108 |     await expect(form.locator('input[name="requested"][value="individuals"]')).toBeChecked();
  109 |     expect(await activated(page)).toEqual(['save:jane@example.org']);
  110 |   });
  111 | 
  112 |   test('entitled: the area control works', async ({ page }) => {
  113 |     await open(page, 'jane@example.org', { individuals: true });
  114 |     await page.locator('#auth-slot [data-control="export"]').click();
  115 |     expect(await activated(page)).toEqual(['export']);
  116 |   });
  117 | 
  118 |   test('controls registered after the bar rendered appear', async ({ page }) => {
  119 |     // us.elect.info always registers the cycle facet (sites#194), so the toolbar is never empty here.
  120 |     await open(page, 'jane@example.org', {}, false);
  121 |     await expect(page.locator('#auth-slot [data-control="cycle"]')).toBeVisible();
  122 |     await page.evaluate(() => {
  123 |       (window as unknown as { electinfoBarControls: { push: (c: object) => void } }).electinfoBarControls.push({ id: 'late', label: 'Alerts' });
  124 |     });
  125 |     await expect(page.locator('#auth-slot [data-control="late"]')).toBeVisible();
  126 |   });
  127 | });
  128 | 
```