# 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/voter-cycles.spec.ts >> voters and the cycle facet (sites#355) >> a cycle scopes the voter list; the page offers the cycle and voting-history filters
- Location: tests/ui/voter-cycles.spec.ts:39:7

# Error details

```
Test timeout of 30000ms exceeded while running "beforeEach" hook.
```

# Test source

```ts
  1   | import { test, expect, type Page, type Route } from '@playwright/test';
  2   | import { skipIfPremiumGated } from './helpers';
  3   | 
  4   | // Voters and the cycle facet (sites#355, rundeck#846). The voters index gains
  5   | // integer cycle arrays; the voters page sends ?cycle= and the voting-history
  6   | // filters, the homepage shows voters in the cycle, and an index built before
  7   | // the arrays (CYCLE_UNAVAILABLE) falls back to all cycles. GraphQL is mocked,
  8   | // so these run on CI pods and on preview alike.
  9   | 
  10  | const VOTER = {
  11  |   id: 'v-1', type: 'Voter', name: 'SMITH, JANE', score: 12, state: 'TX', city: 'AUSTIN', party: 'DEM',
  12  |   office: null, district: '37', metadata: JSON.stringify({ age: 44, zip: '78701' }), affinityDem: 0, affinityRep: 0,
  13  | };
  14  | 
  15  | interface Request { query: string; variables: Record<string, unknown> }
  16  | 
  17  | async function mockVoters(page: Page, options: { unavailable?: boolean } = {}): Promise<Request[]> {
  18  |   const requests: Request[] = [];
  19  |   await page.route('**/voters/api/graphql', async (route: Route) => {
  20  |     const body = route.request().postDataJSON() as Request;
  21  |     requests.push(body);
  22  |     if (body.query.includes('entityFacets')) return route.fulfill({ json: { data: { entityFacets: [] } } });
  23  |     if (options.unavailable && body.variables.cycle !== undefined) {
  24  |       return route.fulfill({ json: { data: null, errors: [{ message: 'Cycle history is not indexed for these records yet.', extensions: { code: 'CYCLE_UNAVAILABLE' } }] } });
  25  |     }
  26  |     const key = body.query.includes('searchEntities') ? 'searchEntities' : 'browseEntities';
  27  |     return route.fulfill({ json: { data: { [key]: { results: [VOTER], total: 1, page: 1, pageSize: 50, hasMore: false } } } });
  28  |   });
  29  |   return requests;
  30  | }
  31  | 
  32  | const lists = (requests: Request[]) => requests.filter(r => !r.query.includes('entityFacets'));
  33  | 
  34  | test.describe('voters and the cycle facet (sites#355)', () => {
> 35  |   test.beforeEach(async ({ request }) => {
      |        ^ Test timeout of 30000ms exceeded while running "beforeEach" hook.
  36  |     await skipIfPremiumGated(request, '/voters/');
  37  |   });
  38  | 
  39  |   test('a cycle scopes the voter list; the page offers the cycle and voting-history filters', async ({ page }) => {
  40  |     const requests = await mockVoters(page);
  41  |     await page.goto('/voters/?cycle=2024');
  42  |     await expect(page.locator('#entity-search-results-list')).toContainText('SMITH, JANE', { timeout: 15000 });
  43  |     expect(lists(requests)[0].variables.cycle).toBe(2024);
  44  |     await expect(page.locator('.ei-cycle-notice')).toContainText('voted in the 2023–2024 cycle');
  45  | 
  46  |     const cycle = page.locator('#entity-filter-cycle');
  47  |     await expect(cycle).toHaveValue('2024');
  48  |     await expect(cycle.locator('option').last()).toHaveText('2011–2012');
  49  |     await expect(page.locator('label[for="entity-filter-general"]')).toHaveText('Voted in the general (2023–2024)');
  50  | 
  51  |     await page.locator('#entity-filter-general').selectOption('yes');
  52  |     await expect.poll(() => JSON.stringify(lists(requests).at(-1)?.variables.filters)).toContain('"general"');
  53  |     expect(lists(requests).at(-1)!.variables).toMatchObject({ cycle: 2024, filters: [{ field: 'general', values: ['yes'] }] });
  54  |     await expect(page).toHaveURL(/f_general=yes/);
  55  | 
  56  |     // The cycle selector is the page's ?cycle=: choosing one reloads in that cycle, keeping the filters.
  57  |     await cycle.selectOption('2022');
  58  |     await expect(page).toHaveURL(/cycle=2022/);
  59  |     await expect(page).toHaveURL(/f_general=yes/);
  60  |     await expect(page.locator('label[for="entity-filter-general"]')).toHaveText('Voted in the general (2021–2022)');
  61  |     await expect.poll(() => lists(requests).at(-1)?.variables.cycle).toBe(2022);
  62  |     await page.locator('#entity-filter-cycle').selectOption('');
  63  |     await expect(page).not.toHaveURL(/cycle=/);
  64  |     await expect(page.locator('label[for="entity-filter-general"]')).toHaveText('Voted in the general (any cycle)');
  65  |   });
  66  | 
  67  |   test('without a cycle, the primary filter means any cycle and no cycle is sent', async ({ page }) => {
  68  |     const requests = await mockVoters(page);
  69  |     await page.goto('/voters/?f_primary=REP');
  70  |     await expect(page.locator('#entity-search-results-list')).toContainText('SMITH, JANE', { timeout: 15000 });
  71  |     expect(lists(requests)[0].variables.cycle).toBeUndefined();
  72  |     expect(lists(requests)[0].variables.filters).toEqual([{ field: 'primary', values: ['REP'] }]);
  73  |     await expect(page.locator('.ei-cycle-notice')).toHaveCount(0);
  74  |   });
  75  | 
  76  |   test('an index without cycle history falls back to all cycles, saying so', async ({ page }) => {
  77  |     const requests = await mockVoters(page, { unavailable: true });
  78  |     await page.goto('/voters/?cycle=2024');
  79  |     await expect(page.locator('#entity-search-results-list')).toContainText('SMITH, JANE', { timeout: 15000 });
  80  |     expect(lists(requests).map(r => r.variables.cycle)).toEqual([2024, undefined]);
  81  |     await expect(page.locator('.ei-cycle-notice')).toContainText('voter cycle history is still being indexed');
  82  |   });
  83  | 
  84  |   test('the voter detail page lists voting by cycle when the index has it', async ({ page }) => {
  85  |     await page.route('**/voters/api/graphql', route => route.fulfill({ json: { data: { voter: {
  86  |       id: 'v-1', name_last: 'SMITH', name_first: 'JANE', state: 'TX', total_votes: 12,
  87  |       cycles: [2016, 2020, 2024], general_cycles: [2020, 2024], dem_primary_cycles: [2024], rep_primary_cycles: [],
  88  |     } } } }));
  89  |     await page.goto('/voters/v-1/?cycle=2024');
  90  |     const section = page.locator('#cycles');
  91  |     await expect(section).toContainText('2023–2024: general, Democratic primary', { timeout: 15000 });
  92  |     await expect(section.locator('strong')).toHaveText('2023–2024: general, Democratic primary');
  93  |     await expect(section.locator('li')).toHaveText(['2023–2024: general, Democratic primary', '2019–2020: general', '2015–2016: other elections']);
  94  |   });
  95  | });
  96  | 
  97  | test.describe('homepage voters count in a cycle (sites#355)', () => {
  98  |   const counts = (voters: number | null, allCycles: string[]) => ({ data: { cycleEntityCounts: {
  99  |     counts: { blocks: 1, politicians: 2, committees: 3, counties: 5, cycles: 1, districts: 6, employers: 7,
  100 |       individuals: 8, offices: 9, parties: 10, places: 11, postalCodes: 12, precincts: 13, states: 14, tracts: 15, vendors: 16, voters },
  101 |     allCycles, pending: [],
  102 |   } } });
  103 | 
  104 |   test('shows voters who voted in the cycle', async ({ page }) => {
  105 |     let lifetime = 0;
  106 |     await page.route('**/api/voters/count', route => { lifetime++; return route.fulfill({ json: { count: 196000000 } }); });
  107 |     await page.route('**/api/graphql', route => route.fulfill({ json: counts(4200000, ['blocks']) }));
  108 |     await page.goto('/?cycle=2024');
  109 |     await expect(page.locator('#count-voters')).toHaveText('4,200,000', { timeout: 15000 });
  110 |     await expect(page.locator('#count-voters + .ei-count-scope')).toHaveCount(0);
  111 |     expect(lifetime).toBe(0);
  112 |   });
  113 | 
  114 |   test('before the voters index has cycles: the all-cycles total, labelled', async ({ page }) => {
  115 |     await page.route('**/api/voters/count', route => route.fulfill({ json: { count: 196000000 } }));
  116 |     await page.route('**/api/graphql', route => route.fulfill({ json: counts(null, ['blocks', 'voters']) }));
  117 |     await page.goto('/?cycle=2024');
  118 |     await expect(page.locator('#count-voters')).toHaveText('196,000,000', { timeout: 15000 });
  119 |     await expect(page.locator('#count-voters + .ei-count-scope')).toHaveText(' (all cycles)');
  120 |   });
  121 | });
  122 | 
```