# 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/voters.spec.ts >> voter Location card (mocked) >> without graph matches (or a failed lookup) each value opens the filtered voters list
- Location: tests/ui/voters.spec.ts:213:7

# Error details

```
Test timeout of 30000ms exceeded.
```

```
Error: expect(locator).toBeVisible() failed

Locator: locator('section#location').locator('a').first()
Expected: visible
Error: element(s) not found

Call log:
  - Expect "toBeVisible" locator('section#location').locator('a').first() with timeout 30000ms
  - waiting for locator('section#location').locator('a').first()
  - Test timeout of 30000ms exceeded.

```

```yaml
- region "Status messages"
- banner
- main "Authentication form":
  - text: elect.info
  - heading "Sign in to elect.info" [level=1]
  - paragraph: Login to continue to us.elect.info.
  - text: Email
  - textbox "Email"
  - text: Password
  - textbox "Password":
    - /placeholder: Please enter your password
  - button "Log in"
  - group "Login sources":
    - text: or Login sources
    - button "Continue with Google"
  - group "Additional actions":
    - text: Additional actions Need an account?
    - link "Sign up.":
      - /url: /if/flow/elect-info-enrollment/?client_id=KI5Be8scLVeZN7n5zY8p5s8VbpyradOSKQvo9T2c&redirect_uri=https%3A%2F%2Fus.elect.info%2Foutpost.goauthentik.io%2Fcallback%3FX-authentik-auth-callback%3Dtrue&response_type=code&scope=openid+entitlements+ak_proxy+email+profile&state=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnb2F1dGhlbnRpay5pby9vdXRwb3N0L0tJNUJlOHNjTFZlWk43bjV6WThwNXM4VmJweXJhZE9TS1F2bzlUMmMiLCJzaWQiOiJaQllOUVUyRDZJSlBSQ1hDM1VLNFE0QVBTSU1NNzM2WUVYQlBJVFcyNEJXQjZFSFA3N1RRIiwic3RhdGUiOiJVVGpGUF9PMWFSUWpMRTJIWmxFbnRjaGhXc3FwTXdzd1pfeTRlT3RFYUY4IiwicmVkaXJlY3QiOiJodHRwczovL3VzLmVsZWN0LmluZm8vdm90ZXJzL3YtbG9jLyJ9.Lz8o0xlaFT2S52S2JOTn-3V3f6NefHr8M91He034l7g&next=%2Fapplication%2Fo%2Fauthorize%2F%3Fclient_id%3DKI5Be8scLVeZN7n5zY8p5s8VbpyradOSKQvo9T2c%26redirect_uri%3Dhttps%253A%252F%252Fus.elect.info%252Foutpost.goauthentik.io%252Fcallback%253FX-authentik-auth-callback%253Dtrue%26response_type%3Dcode%26scope%3Dopenid%2Bentitlements%2Bak_proxy%2Bemail%2Bprofile%26state%3DeyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnb2F1dGhlbnRpay5pby9vdXRwb3N0L0tJNUJlOHNjTFZlWk43bjV6WThwNXM4VmJweXJhZE9TS1F2bzlUMmMiLCJzaWQiOiJaQllOUVUyRDZJSlBSQ1hDM1VLNFE0QVBTSU1NNzM2WUVYQlBJVFcyNEJXQjZFSFA3N1RRIiwic3RhdGUiOiJVVGpGUF9PMWFSUWpMRTJIWmxFbnRjaGhXc3FwTXdzd1pfeTRlT3RFYUY4IiwicmVkaXJlY3QiOiJodHRwczovL3VzLmVsZWN0LmluZm8vdm90ZXJzL3YtbG9jLyJ9.Lz8o0xlaFT2S52S2JOTn-3V3f6NefHr8M91He034l7g
    - link "Forgot username or password?":
      - /url: /if/flow/elect-info-recovery/?client_id=KI5Be8scLVeZN7n5zY8p5s8VbpyradOSKQvo9T2c&redirect_uri=https%3A%2F%2Fus.elect.info%2Foutpost.goauthentik.io%2Fcallback%3FX-authentik-auth-callback%3Dtrue&response_type=code&scope=openid+entitlements+ak_proxy+email+profile&state=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnb2F1dGhlbnRpay5pby9vdXRwb3N0L0tJNUJlOHNjTFZlWk43bjV6WThwNXM4VmJweXJhZE9TS1F2bzlUMmMiLCJzaWQiOiJaQllOUVUyRDZJSlBSQ1hDM1VLNFE0QVBTSU1NNzM2WUVYQlBJVFcyNEJXQjZFSFA3N1RRIiwic3RhdGUiOiJVVGpGUF9PMWFSUWpMRTJIWmxFbnRjaGhXc3FwTXdzd1pfeTRlT3RFYUY4IiwicmVkaXJlY3QiOiJodHRwczovL3VzLmVsZWN0LmluZm8vdm90ZXJzL3YtbG9jLyJ9.Lz8o0xlaFT2S52S2JOTn-3V3f6NefHr8M91He034l7g&next=%2Fapplication%2Fo%2Fauthorize%2F%3Fclient_id%3DKI5Be8scLVeZN7n5zY8p5s8VbpyradOSKQvo9T2c%26redirect_uri%3Dhttps%253A%252F%252Fus.elect.info%252Foutpost.goauthentik.io%252Fcallback%253FX-authentik-auth-callback%253Dtrue%26response_type%3Dcode%26scope%3Dopenid%2Bentitlements%2Bak_proxy%2Bemail%2Bprofile%26state%3DeyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnb2F1dGhlbnRpay5pby9vdXRwb3N0L0tJNUJlOHNjTFZlWk43bjV6WThwNXM4VmJweXJhZE9TS1F2bzlUMmMiLCJzaWQiOiJaQllOUVUyRDZJSlBSQ1hDM1VLNFE0QVBTSU1NNzM2WUVYQlBJVFcyNEJXQjZFSFA3N1RRIiwic3RhdGUiOiJVVGpGUF9PMWFSUWpMRTJIWmxFbnRjaGhXc3FwTXdzd1pfeTRlT3RFYUY4IiwicmVkaXJlY3QiOiJodHRwczovL3VzLmVsZWN0LmluZm8vdm90ZXJzL3YtbG9jLyJ9.Lz8o0xlaFT2S52S2JOTn-3V3f6NefHr8M91He034l7g
```

# Test source

```ts
  122 |     await expect(page).not.toHaveURL(/f_state=/);
  123 |     await expect(page.locator('.entity-filter-control[data-key="county"]')).toBeHidden();
  124 |   });
  125 | 
  126 |   test('race and Civitech score filters (persuadable, high-turnout Latinx voters in Miami-Dade)', async ({ page }) => {
  127 |     await page.goto('/voters/?f_state=FL&f_county=MIAMI-DADE&f_race=LATINX&f_support_dem=tossup&f_turnout_pres=high');
  128 |     await expect(page.locator('.entity-search-result').first()).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
  129 |     await expect(page.locator('#entity-results-count')).toHaveText(/^[\d,]+ match(es)?$/);
  130 |     await expect(page.locator('#entity-results-title')).toContainText('Latinx');
  131 |     await expect(page.locator('#entity-filter-support_dem')).toHaveValue('tossup');
  132 |   });
  133 | 
  134 |   test('every Districts row links; legislative districts open the filtered voters list', async ({ page }) => {
  135 |     // A Rochester, NY voter with all four district codes and a precinct.
  136 |     await page.goto('/voters/0c13135d-8e3d-3aa4-1077-28a81eb5d10f/');
  137 |     const districts = page.locator('section#districts');
  138 |     await expect(districts.locator('a').first()).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
  139 |     const hrefs = await districts.locator('a').evaluateAll(as => as.map(a => a.getAttribute('href')));
  140 |     expect(hrefs).toEqual(expect.arrayContaining([
  141 |       '/districts/ny-25/',
  142 |       '/voters/?f_state=NY&f_cd=NY-25',
  143 |       '/voters/?f_state=NY&f_senate=NY-055',
  144 |       '/voters/?f_state=NY&f_house=NY-138',
  145 |       '/voters/?f_state=NY&f_precinct=36055Rochester_Ward_24_ED_6',
  146 |     ]));
  147 |     await expect(districts.locator('dd')).toHaveCount(4);
  148 |     expect(await districts.locator('dd:not(:has(a))').count()).toBe(0);
  149 | 
  150 |     // Location: state, place, ZIP and county pages (graph ids from the public API) plus the filtered voters list.
  151 |     const location = page.locator('section#location');
  152 |     await expect(location.locator('a[href^="/places/"]')).toHaveText('Rochester city', { timeout: DATA_LOAD_TIMEOUT });
  153 |     const locationHrefs = await location.locator('a').evaluateAll(as => as.map(a => a.getAttribute('href')));
  154 |     expect(locationHrefs).toEqual([
  155 |       '/states/ny/', '/voters/?f_state=NY',
  156 |       expect.stringMatching(/^\/places\/[0-9a-f-]+\/$/), '/voters/?f_state=NY&f_city=ROCHESTER',
  157 |       expect.stringMatching(/^\/postalcodes\/[0-9a-f-]+\/$/), '/voters/?f_state=NY&f_zip=14620',
  158 |       expect.stringMatching(/^\/counties\/[0-9a-f-]+\/$/), '/voters/?f_state=NY&f_county=MONROE',
  159 |     ]);
  160 |     await expect(location.locator('a[href^="/counties/"]')).toHaveText('Monroe County');
  161 |     await expect(page.locator('section#overview a')).toHaveCount(0);
  162 | 
  163 |     await districts.locator('a', { hasText: 'NY-055' }).click();
  164 |     await expect(page).toHaveURL(/\/voters\/\?f_state=NY&f_senate=NY-055$/);
  165 |     await expect(page.locator('.entity-search-result').first()).toContainText(', NY', { timeout: DATA_LOAD_TIMEOUT });
  166 |     await expect(page.locator('#entity-filter-senate')).toHaveValue('NY-055', { timeout: DATA_LOAD_TIMEOUT });
  167 |   });
  168 | 
  169 |   test('an unknown voter id renders Not Found', async ({ page }) => {
  170 |     await page.goto('/voters/00000000-0000-0000-0000-000000000000/');
  171 |     await expect(page.locator('.leaf-detail-page h1')).toHaveText(/Voter Not Found/, { timeout: DATA_LOAD_TIMEOUT });
  172 |   });
  173 | });
  174 | 
  175 | test.describe('voter Location card (mocked)', () => {
  176 |   const voter = {
  177 |     id: 'v-loc', name_last: 'MOLLICA', name_first: 'CAROL', state: 'MA', addr_state: 'MA',
  178 |     city: 'WRENTHAM', zip: '02093', county: 'NORFOLK', congressional_district: 'MA-04',
  179 |     state_senate_district: 'MA-D14', state_house_district: 'MA-104', precinct: '25021823150003',
  180 |   };
  181 | 
  182 |   test('State, Place, Postal code and County link their pages plus the filtered voters list; Overview and Districts drop them', async ({ page }) => {
  183 |     const lookups: string[] = [];
  184 |     await page.route('**/api/graphql', async route => {
  185 |       const body = route.request().postData() || '';
  186 |       if (route.request().url().includes('/voters/api/graphql')) {
  187 |         return route.fulfill({ json: { data: { voter } } });
  188 |       }
  189 |       lookups.push(body);
  190 |       return route.fulfill({ json: { data: {
  191 |         postalCodes: [{ id: 'zip-02093', zip_code: '02093', overlapsCounties: [
  192 |           { id: 'cty-bristol', name: 'Bristol County', state: 'MA' },
  193 |           { id: 'cty-norfolk', name: 'Norfolk County', state: 'MA' },
  194 |         ] }],
  195 |         places: [{ id: 'place-wrentham', name: 'Wrentham CDP' }],
  196 |       } } });
  197 |     });
  198 |     await page.goto('/voters/v-loc/');
  199 |     const location = page.locator('section#location');
  200 |     await expect(location.locator('a').first()).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
  201 |     expect(await location.locator('a').evaluateAll(as => as.map(a => a.getAttribute('href')))).toEqual([
  202 |       '/states/ma/', '/voters/?f_state=MA',
  203 |       '/places/place-wrentham/', '/voters/?f_state=MA&f_city=WRENTHAM',
  204 |       '/postalcodes/zip-02093/', '/voters/?f_state=MA&f_zip=02093',
  205 |       '/counties/cty-norfolk/', '/voters/?f_state=MA&f_county=NORFOLK',
  206 |     ]);
  207 |     await expect(location.locator('a[href="/counties/cty-norfolk/"]')).toHaveText('Norfolk County');
  208 |     expect(lookups.some(b => b.includes('query VoterLocation(') && b.includes('"city":"WRENTHAM"') && b.includes('"zip":"02093"'))).toBe(true);
  209 |     await expect(page.locator('section#overview')).not.toContainText('Wrentham');
  210 |     await expect(page.locator('section#districts dt')).toHaveText(['Congressional district', 'State senate district', 'State house district', 'Precinct']);
  211 |   });
  212 | 
  213 |   test('without graph matches (or a failed lookup) each value opens the filtered voters list', async ({ page }) => {
  214 |     await page.route('**/api/graphql', async route => {
  215 |       if (route.request().url().includes('/voters/api/graphql')) {
  216 |         return route.fulfill({ json: { data: { voter } } });
  217 |       }
  218 |       return route.fulfill({ status: 500, body: '' });
  219 |     });
  220 |     await page.goto('/voters/v-loc/');
  221 |     const location = page.locator('section#location');
> 222 |     await expect(location.locator('a').first()).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
      |                                                 ^ Error: expect(locator).toBeVisible() failed
  223 |     expect(await location.locator('a').evaluateAll(as => as.map(a => a.getAttribute('href')))).toEqual([
  224 |       '/states/ma/', '/voters/?f_state=MA',
  225 |       '/voters/?f_state=MA&f_city=WRENTHAM',
  226 |       '/voters/?f_state=MA&f_zip=02093',
  227 |       '/voters/?f_state=MA&f_county=NORFOLK',
  228 |     ]);
  229 |   });
  230 | });
  231 | 
```