# 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/entity-filters.spec.ts >> individual donor filters >> state, cycle, lifetime total and lean narrow the list and land in the URL
- Location: tests/ui/entity-filters.spec.ts:29:7

# Error details

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

Locator: locator('.entity-search-result').first()
Expected substring: ", TX"
Timeout: 5000ms
Error: element(s) not found

Call log:
  - Expect "toContainText" locator('.entity-search-result').first() with timeout 5000ms
  - waiting for locator('.entity-search-result').first()

```

```yaml
- link "← Back to Home":
  - /url: /
- banner:
  - heading "Individual Donors" [level=1]
  - paragraph: Contributors to federal campaigns
- textbox "Search individual donors by name, city, employer..."
- button "Search"
- text: State
- combobox "State":
  - option "Any"
  - option "Alabama"
  - option "Alaska"
  - option "Arizona"
  - option "Arkansas"
  - option "California"
  - option "Colorado"
  - option "Connecticut"
  - option "Delaware"
  - option "District of Columbia"
  - option "Florida"
  - option "Georgia"
  - option "Hawaii"
  - option "Idaho"
  - option "Illinois"
  - option "Indiana"
  - option "Iowa"
  - option "Kansas"
  - option "Kentucky"
  - option "Louisiana"
  - option "Maine"
  - option "Maryland"
  - option "Massachusetts"
  - option "Michigan"
  - option "Minnesota"
  - option "Mississippi"
  - option "Missouri"
  - option "Montana"
  - option "Nebraska"
  - option "Nevada"
  - option "New Hampshire"
  - option "New Jersey"
  - option "New Mexico"
  - option "New York"
  - option "North Carolina"
  - option "North Dakota"
  - option "Ohio"
  - option "Oklahoma"
  - option "Oregon"
  - option "Pennsylvania"
  - option "Puerto Rico"
  - option "Rhode Island"
  - option "South Carolina"
  - option "South Dakota"
  - option "Tennessee"
  - option "Texas" [selected]
  - option "Utah"
  - option "Vermont"
  - option "Virginia"
  - option "Washington"
  - option "West Virginia"
  - option "Wisconsin"
  - option "Wyoming"
- text: Gave in cycle
- group:
  - text: "2024"
  - checkbox "2026"
  - text: "2026"
  - checkbox "2024" [checked]
  - text: "2024"
  - checkbox "2022"
  - text: "2022"
  - checkbox "2020"
  - text: "2020"
  - checkbox "2018"
  - text: "2018"
  - checkbox "2016"
  - text: "2016"
  - checkbox "2014"
  - text: "2014"
  - checkbox "2012"
  - text: "2012"
  - checkbox "2010"
  - text: "2010"
  - checkbox "2008"
  - text: "2008"
  - checkbox "2006"
  - text: "2006"
  - checkbox "2004"
  - text: "2004"
  - checkbox "2002"
  - text: "2002"
  - checkbox "2000"
  - text: "2000"
- text: Lifetime total
- combobox "Lifetime total":
  - option "Any"
  - option "$200+"
  - option "$1K+"
  - option "$10K+" [selected]
  - option "$100K+"
  - option "$1M+"
- text: Party lean (giving)
- combobox "Party lean (giving)":
  - option "Any"
  - option "Strong Democratic (80%+ D)"
  - option "Lean Democratic (60–80% D)"
  - option "Mixed (40–60% D)"
  - option "Lean Republican (60–80% R)"
  - option "Strong Republican (80%+ R)" [selected]
- text: Last gave
- combobox "Last gave":
  - option "Any" [selected]
  - option "In 2025–26"
  - option "Since 2023"
  - option "Since 2021"
  - option "Not since 2020"
- text: Employer
- textbox "Employer":
  - /placeholder: e.g. Google
- text: Occupation
- textbox "Occupation":
  - /placeholder: e.g. attorney
- text: City
- textbox "City":
  - /placeholder: e.g. Austin
- text: ZIP
- textbox "ZIP":
  - /placeholder: 787 or 78701
- button "Clear filters"
- 'heading "All Individual Donors · Texas · 2024 · Lifetime total: $10K+ · Party lean (giving): Strong Republican (80%+ R)" [level=3]'
- text: 0 matches
- paragraph: No entities found.
- main
```

# Test source

```ts
  1  | import { test, expect, type APIRequestContext } from '@playwright/test';
  2  | import { visitorHeaders, premiumVisitor } from './helpers';
  3  | 
  4  | // Structured filters on the Individual Donors page (electinfo/sites#277).
  5  | // Voter filters are covered in voters.spec.ts.
  6  | 
  7  | const DATA_LOAD_TIMEOUT = 30_000;
  8  | 
  9  | /** The CI test pod proxies to the production API, which gains filters only with the release. */
  10 | async function apiHasFilters(request: APIRequestContext): Promise<boolean> {
  11 |   const res = await request.post('/api/graphql', {
  12 |     data: { query: '{ __type(name: "EntityFilterInput") { name } }' },
  13 |     headers: visitorHeaders(),
  14 |   });
  15 |   if (res.status() !== 200) return false;
  16 |   const body = await res.json() as { data?: { __type?: { name?: string } | null } };
  17 |   return body.data?.__type?.name === 'EntityFilterInput';
  18 | }
  19 | 
  20 | test.describe('individual donor filters', () => {
  21 |   test.beforeEach(async ({ request }) => {
  22 |     test.skip(!(await apiHasFilters(request)), 'the API behind this host has no structured filters yet');
  23 |   });
  24 |   // Donor records are premium (sites#277).
  25 |   test.beforeEach(async ({ context, baseURL }) => {
  26 |     await premiumVisitor(context, baseURL);
  27 |   });
  28 | 
  29 |   test('state, cycle, lifetime total and lean narrow the list and land in the URL', async ({ page }) => {
  30 |     await page.goto('/individuals/');
  31 |     const results = page.locator('.entity-search-result');
  32 |     await expect(results.first()).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
  33 | 
  34 |     await page.selectOption('#entity-filter-state', 'TX');
  35 |     await expect(results.first()).toContainText(', TX', { timeout: DATA_LOAD_TIMEOUT });
  36 |     await page.selectOption('#entity-filter-total', '10000');
  37 |     await page.selectOption('#entity-filter-lean', 'strong-r');
  38 |     await page.locator('#entity-filter-cycle summary').click();
  39 |     await page.locator('#entity-filter-cycle input[value="2024"]').check();
  40 | 
  41 |     await expect(page).toHaveURL(/f_state=TX/);
  42 |     await expect(page).toHaveURL(/f_total=10000/);
  43 |     await expect(page).toHaveURL(/f_lean=strong-r/);
  44 |     await expect(page).toHaveURL(/f_cycle=2024/);
  45 |     await expect(page.locator('#entity-results-count')).toHaveText(/^[\d,]+ match(es)?$/, { timeout: DATA_LOAD_TIMEOUT });
> 46 |     await expect(results.first()).toContainText(', TX');
     |                                   ^ Error: expect(locator).toContainText(expected) failed
  47 |     await expect(results.first()).toContainText(/REP: (8\d|9\d|100)%/);
  48 |   });
  49 | 
  50 |   test('text filters: employer and ZIP prefix', async ({ page }) => {
  51 |     await page.goto('/individuals/?f_employer=google&f_zip=940');
  52 |     const results = page.locator('.entity-search-result');
  53 |     await expect(results.first()).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
  54 |     await expect(results.first()).toContainText(/GOOGLE/i);
  55 |     await expect(page.locator('#entity-filter-employer')).toHaveValue('google');
  56 |   });
  57 | 
  58 |   test('an unknown filter field is refused by the API', async ({ request }) => {
  59 |     const res = await request.post('/api/graphql', {
  60 |       data: { query: '{ browseEntities(entityType: Individual, filters: [{ field: "name", values: ["x"] }]) { total } }' },
  61 |       headers: visitorHeaders(),
  62 |     });
  63 |     const body = await res.json() as { errors?: Array<{ message: string }> };
  64 |     expect(body.errors?.[0]?.message).toContain('Unknown filter field');
  65 |   });
  66 | });
  67 | 
```