# 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/auth.spec.ts >> sign-in panel >> Log In opens the sign-in panel with Google and email
- Location: tests/ui/auth.spec.ts:177:7

# Error details

```
Error: /auth/whoami answers 200

expect(received).toBe(expected) // Object.is equality

Expected: 200
Received: 302
```

# Test source

```ts
  1   | import { test, expect } from '@playwright/test';
  2   | 
  3   | // Sign-in and premium sections (electinfo/sites#270). /auth/whoami reports
  4   | // whether Authentik is on for the host under test:
  5   | //   - CI test pods: enabled "false" (no Traefik in front), so nothing is gated
  6   | //     and the homepage keeps its premium cards greyed out;
  7   | //   - preview.elect.info front door: enabled "true", signed in as the
  8   | //     preview-e2e service account (preview-e2e@e2e.elect.news, ops#703): a plain
  9   | //     registered visitor, not elevated, so the premium-section tests skip there.
  10  | const PREMIUM = [
  11  |   { path: '/voters/', label: 'Voters' },
  12  |   { path: '/individuals/', label: 'Individual Donors' },
  13  |   { path: '/blocks/', label: 'Census Blocks' },
  14  | ];
  15  | 
  16  | interface WhoAmI {
  17  |   enabled: string;
  18  |   email: string;
  19  |   elevated: boolean;
  20  |   entitlements: { voters: boolean; individuals: boolean; blocks: boolean; curator: boolean; preview: boolean };
  21  | }
  22  | 
  23  | async function whoami(request: import('@playwright/test').APIRequestContext): Promise<WhoAmI> {
  24  |   const res = await request.get('/auth/whoami', { maxRedirects: 0 });
> 25  |   expect(res.status(), '/auth/whoami answers 200').toBe(200);
      |                                                    ^ Error: /auth/whoami answers 200
  26  |   expect(res.headers()['cache-control']).toContain('no-store');
  27  |   return (await res.json()) as WhoAmI;
  28  | }
  29  | 
  30  | test.describe('sign-in and premium sections', () => {
  31  |   test('/auth/whoami returns well-formed JSON', async ({ request }) => {
  32  |     const me = await whoami(request);
  33  |     expect(['true', 'false']).toContain(me.enabled);
  34  |     expect(typeof me.email).toBe('string');
  35  |     expect(typeof me.elevated).toBe('boolean');
  36  |     // Per-section entitlements (sites#305), always all four booleans.
  37  |     expect(Object.keys(me.entitlements).sort()).toEqual(['blocks', 'curator', 'individuals', 'preview', 'voters']);
  38  |     for (const v of Object.values(me.entitlements)) expect(typeof v).toBe('boolean');
  39  |     expect(me.elevated).toBe(me.entitlements.voters || me.entitlements.individuals || me.entitlements.blocks);
  40  |     if (me.enabled === 'false') {
  41  |       expect(me.email).toBe('');
  42  |       expect(me.elevated).toBe(false);
  43  |       expect(Object.values(me.entitlements).some(Boolean)).toBe(false);
  44  |     }
  45  |   });
  46  | 
  47  |   test('a forged identity header grants nothing where Authentik is off', async ({ request }) => {
  48  |     const res = await request.get('/auth/whoami', {
  49  |       headers: { 'X-authentik-email': 'someone@elect.info' },
  50  |     });
  51  |     const me = (await res.json()) as WhoAmI;
  52  |     test.skip(me.enabled === 'true', 'host has Authentik: Traefik sets the header');
  53  |     expect(me.email).toBe('');
  54  |     expect(me.elevated).toBe(false);
  55  |   });
  56  | 
  57  |   for (const group of ['elect-info-premium', 'ei-voter-viewers', 'ei-donor-viewers', 'ei-block-viewers', 'elect-news-curators', 'ei-preview-viewers']) {
  58  |     test(`a forged ${group} group header grants nothing where Authentik is off`, async ({ request }) => {
  59  |       const res = await request.get('/auth/whoami', {
  60  |         headers: { 'X-authentik-email': 'someone@example.com', 'X-authentik-groups': group },
  61  |       });
  62  |       const me = (await res.json()) as WhoAmI;
  63  |       test.skip(me.enabled === 'true', 'host has Authentik: Traefik sets the headers');
  64  |       expect(me.email).toBe('');
  65  |       expect(me.elevated).toBe(false);
  66  |       expect(Object.values(me.entitlements).some(Boolean)).toBe(false);
  67  |     });
  68  |   }
  69  | 
  70  |   for (const { path } of PREMIUM) {
  71  |     test(`${path} answers 200 for the test identity`, async ({ page, request }) => {
  72  |       const me = await whoami(request);
  73  |       test.skip(me.enabled === 'true' && !me.elevated, 'signed in without elevated access');
  74  |       const response = await page.goto(path);
  75  |       expect(response?.status()).toBe(200);
  76  |     });
  77  |   }
  78  | 
  79  |   // The placeholder became the voter search page (sites#277, tests/voters.spec.ts).
  80  |   test('/voters/ is the voter search page', async ({ page, request }) => {
  81  |     const me = await whoami(request);
  82  |     // A signed-in visitor without the voters entitlement (the front door as preview-e2e, ops#703) gets the premium notice.
  83  |     test.skip(me.enabled === 'true' && me.email !== '' && !me.entitlements.voters, 'signed in without voters access');
  84  |     await page.goto('/voters/');
  85  |     await expect(page.locator('h1')).toHaveText('Voters');
  86  |     await expect(page.locator('script[src="/js/voter-index.js"]')).toHaveCount(1);
  87  |     await expect(page.locator('.coming-soon')).toHaveCount(0);
  88  |   });
  89  | 
  90  |   test('the premium notice page is not directly reachable', async ({ page }) => {
  91  |     const response = await page.goto('/premium-required.html');
  92  |     expect(response?.status()).toBe(404);
  93  |   });
  94  | 
  95  |   test('homepage premium cards follow the account', async ({ page, request }) => {
  96  |     const me = await whoami(request);
  97  |     await page.goto('/');
  98  |     for (const { path, label } of PREMIUM) {
  99  |       const card = page.locator(`[data-premium-href="${path}"]`);
  100 |       await expect(card).toHaveCount(1);
  101 |       await expect(card).toContainText(label);
  102 |       const area = path.replace(/\//g, '') as 'voters' | 'individuals' | 'blocks';
  103 |       if (me.enabled === 'true' && me.entitlements[area]) {
  104 |         await expect(page.locator(`a[data-premium-href="${path}"]`)).toHaveAttribute('href', path);
  105 |         await expect(card).not.toHaveClass(/entity-card--disabled/);
  106 |       } else {
  107 |         await expect(card).toHaveClass(/entity-card--disabled/);
  108 |         await expect(page.locator(`a[data-premium-href="${path}"]`)).toHaveCount(0);
  109 |       }
  110 |     }
  111 |   });
  112 | 
  113 |   test('account slot shows the signed-in email where Authentik is on', async ({ page, request }) => {
  114 |     const me = await whoami(request);
  115 |     await page.goto('/');
  116 |     const slot = page.locator('#auth-slot');
  117 |     if (me.enabled !== 'true') {
  118 |       await expect(slot).toBeEmpty();
  119 |       return;
  120 |     }
  121 |     if (me.email) {
  122 |       await expect(slot.locator('.auth-email')).toHaveText(me.email);
  123 |       await expect(slot.locator('a.auth-link', { hasText: 'Log out' })).toHaveAttribute('href', '/outpost.goauthentik.io/sign_out');
  124 |       await expect(slot.locator('.ei-chip').first()).toHaveText('Registered');
  125 |     } else {
```