# 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 and premium sections >> /voters/ is the coming-soon placeholder
- Location: tests/ui/auth.spec.ts:59:7

# Error details

```
Error: expect(locator).toContainText(expected) failed

Locator: locator('.coming-soon')
Expected substring: "coming soon"
Timeout: 5000ms
Error: element(s) not found

Call log:
  - Expect "toContainText" locator('.coming-soon') with timeout 5000ms
  - waiting for locator('.coming-soon')

```

```yaml
- link "← Back to Home":
  - /url: /
- banner:
  - heading "Voters" [level=1]
  - paragraph: 196,257,874 voter records
- main
```

# 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@elect.info, so elevated).
  9   | const PREMIUM = [
  10  |   { path: '/voters/', label: 'Voters' },
  11  |   { path: '/individuals/', label: 'Individual Donors' },
  12  |   { path: '/blocks/', label: 'Census Blocks' },
  13  | ];
  14  | 
  15  | interface WhoAmI {
  16  |   enabled: string;
  17  |   email: string;
  18  |   elevated: boolean;
  19  | }
  20  | 
  21  | async function whoami(request: import('@playwright/test').APIRequestContext): Promise<WhoAmI> {
  22  |   const res = await request.get('/auth/whoami', { maxRedirects: 0 });
  23  |   expect(res.status(), '/auth/whoami answers 200').toBe(200);
  24  |   expect(res.headers()['cache-control']).toContain('no-store');
  25  |   return (await res.json()) as WhoAmI;
  26  | }
  27  | 
  28  | test.describe('sign-in and premium sections', () => {
  29  |   test('/auth/whoami returns well-formed JSON', async ({ request }) => {
  30  |     const me = await whoami(request);
  31  |     expect(['true', 'false']).toContain(me.enabled);
  32  |     expect(typeof me.email).toBe('string');
  33  |     expect(typeof me.elevated).toBe('boolean');
  34  |     if (me.enabled === 'false') {
  35  |       expect(me.email).toBe('');
  36  |       expect(me.elevated).toBe(false);
  37  |     }
  38  |   });
  39  | 
  40  |   test('a forged identity header grants nothing where Authentik is off', async ({ request }) => {
  41  |     const res = await request.get('/auth/whoami', {
  42  |       headers: { 'X-authentik-email': 'someone@elect.info' },
  43  |     });
  44  |     const me = (await res.json()) as WhoAmI;
  45  |     test.skip(me.enabled === 'true', 'host has Authentik: Traefik sets the header');
  46  |     expect(me.email).toBe('');
  47  |     expect(me.elevated).toBe(false);
  48  |   });
  49  | 
  50  |   for (const { path } of PREMIUM) {
  51  |     test(`${path} answers 200 for the test identity`, async ({ page, request }) => {
  52  |       const me = await whoami(request);
  53  |       test.skip(me.enabled === 'true' && !me.elevated, 'signed in without elevated access');
  54  |       const response = await page.goto(path);
  55  |       expect(response?.status()).toBe(200);
  56  |     });
  57  |   }
  58  | 
  59  |   test('/voters/ is the coming-soon placeholder', async ({ page }) => {
  60  |     await page.goto('/voters/');
  61  |     await expect(page.locator('h1')).toHaveText('Voters');
> 62  |     await expect(page.locator('.coming-soon')).toContainText('coming soon');
      |                                                ^ Error: expect(locator).toContainText(expected) failed
  63  |   });
  64  | 
  65  |   test('the premium notice page is not directly reachable', async ({ page }) => {
  66  |     const response = await page.goto('/premium-required.html');
  67  |     expect(response?.status()).toBe(404);
  68  |   });
  69  | 
  70  |   test('homepage premium cards follow the account', async ({ page, request }) => {
  71  |     const me = await whoami(request);
  72  |     await page.goto('/');
  73  |     for (const { path, label } of PREMIUM) {
  74  |       const card = page.locator(`[data-premium-href="${path}"]`);
  75  |       await expect(card).toHaveCount(1);
  76  |       await expect(card).toContainText(label);
  77  |       if (me.enabled === 'true' && me.elevated) {
  78  |         await expect(page.locator(`a[data-premium-href="${path}"]`)).toHaveAttribute('href', path);
  79  |         await expect(card).not.toHaveClass(/entity-card--disabled/);
  80  |       } else {
  81  |         await expect(card).toHaveClass(/entity-card--disabled/);
  82  |         await expect(page.locator(`a[data-premium-href="${path}"]`)).toHaveCount(0);
  83  |       }
  84  |     }
  85  |   });
  86  | 
  87  |   test('account slot shows the signed-in email where Authentik is on', async ({ page, request }) => {
  88  |     const me = await whoami(request);
  89  |     await page.goto('/');
  90  |     const slot = page.locator('#auth-slot');
  91  |     if (me.enabled !== 'true') {
  92  |       await expect(slot).toBeEmpty();
  93  |       return;
  94  |     }
  95  |     if (me.email) {
  96  |       await expect(slot.locator('.auth-email')).toHaveText(me.email);
  97  |       await expect(slot.locator('a.auth-link')).toHaveAttribute('href', '/outpost.goauthentik.io/sign_out');
  98  |     } else {
  99  |       await expect(slot.locator('a.auth-link')).toHaveText('Log In');
  100 |     }
  101 |   });
  102 | });
  103 | 
```