# 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/entities.spec.ts >> us.elect.info entity sections - additional index + detail >> postalcodes section - index and random detail pages
- Location: tests/ui/entities.spec.ts:533:7

# Error details

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

Locator: locator('#map .leaflet-interactive').first()
Expected: visible
Timeout: 15000ms
Error: element(s) not found

Call log:
  - Expect "toBeVisible" locator('#map .leaflet-interactive').first() with timeout 15000ms
  - waiting for locator('#map .leaflet-interactive').first()

```

```yaml
- link "← Back to Home":
  - /url: /
- banner:
  - heading "Postal Codes" [level=1]
  - paragraph: ZIP Code Tabulation Areas (ZCTA)
- button "Map"
- button "List"
- main:
  - button "Zoom in"
  - button "Zoom out"
  - link "Leaflet":
    - /url: https://leafletjs.com
  - text: ©
  - link "OpenStreetMap":
    - /url: https://www.openstreetmap.org/copyright
```

# Test source

```ts
  438 |     const indexResponse = await page.goto('/employers/');
  439 |     expect(indexResponse?.status()).toBe(200);
  440 |     await expect(page.locator('h1')).toContainText(/Employers/i);
  441 | 
  442 |     await expect(page.locator('.entity-search-result').first()).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
  443 | 
  444 |     const links = await page.locator('.entity-search-result').all();
  445 |     expect(links.length).toBeGreaterThan(0);
  446 | 
  447 |     const hrefs = await Promise.all(links.slice(0, 20).map(l => l.getAttribute('href')));
  448 |     const randomLinks = getRandomItems(hrefs.filter(Boolean) as string[], 3);
  449 | 
  450 |     for (const href of randomLinks) {
  451 |       const entityResponse = await page.goto(toRelativePath(href));
  452 |       expect(entityResponse?.status()).toBe(200);
  453 |       await expect(page.locator('h1')).toBeVisible();
  454 |     }
  455 |   });
  456 | 
  457 |   test('vendors section - index and random pages', async ({ page }) => {
  458 |     const indexResponse = await page.goto('/vendors/');
  459 |     expect(indexResponse?.status()).toBe(200);
  460 |     await expect(page.locator('h1')).toContainText(/Vendors/i);
  461 | 
  462 |     await expect(page.locator('.entity-search-result').first()).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
  463 | 
  464 |     const links = await page.locator('.entity-search-result').all();
  465 |     expect(links.length).toBeGreaterThan(0);
  466 | 
  467 |     const hrefs = await Promise.all(links.slice(0, 20).map(l => l.getAttribute('href')));
  468 |     const randomLinks = getRandomItems(hrefs.filter(Boolean) as string[], 3);
  469 | 
  470 |     for (const href of randomLinks) {
  471 |       const entityResponse = await page.goto(toRelativePath(href));
  472 |       expect(entityResponse?.status()).toBe(200);
  473 |       await expect(page.locator('h1')).toBeVisible();
  474 |     }
  475 |   });
  476 | 
  477 |   test('counties section - index and random detail pages', async ({ page }) => {
  478 |     await page.goto('/counties/');
  479 | 
  480 |     // Map view is default — verify Leaflet map renders with GeoJSON state shapes
  481 |     await expect(page.locator('#map.leaflet-container')).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
  482 |     await expect(page.locator('#map .leaflet-interactive').first()).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
  483 |     const shapes = await page.locator('#map .leaflet-interactive').all();
  484 |     expect(shapes.length).toBeGreaterThan(0);
  485 | 
  486 |     // Switch to List view to test county results and detail navigation
  487 |     await page.locator('.toggle-btn[data-view="list"]').click();
  488 | 
  489 |     // Wait for results to load
  490 |     await page.waitForSelector('.county-result, .county-error', { timeout: DATA_LOAD_TIMEOUT });
  491 |     const results = await page.locator('.county-result').all();
  492 |     expect(results.length).toBeGreaterThan(0);
  493 | 
  494 |     // County results are <a> tags - pick random ones and visit detail pages
  495 |     const hrefs = await Promise.all(results.slice(0, 20).map(r => r.getAttribute('href')));
  496 |     const randomLinks = getRandomItems(hrefs.filter(Boolean) as string[], 3);
  497 | 
  498 |     for (const href of randomLinks) {
  499 |       const entityResponse = await page.goto(toRelativePath(href));
  500 |       expect(entityResponse?.status()).toBe(200);
  501 |       await expect(page.locator('h1')).toBeVisible();
  502 |     }
  503 |   });
  504 | 
  505 |   test('places section - index and random detail pages', async ({ page }) => {
  506 |     await page.goto('/places/');
  507 | 
  508 |     // Map view is default — verify Leaflet map renders with GeoJSON state shapes
  509 |     await expect(page.locator('#map.leaflet-container')).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
  510 |     await expect(page.locator('#map .leaflet-interactive').first()).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
  511 |     const shapes = await page.locator('#map .leaflet-interactive').all();
  512 |     expect(shapes.length).toBeGreaterThan(0);
  513 | 
  514 |     // Switch to List view to test place results and detail navigation
  515 |     await page.locator('.toggle-btn[data-view="list"]').click();
  516 | 
  517 |     // Wait for results to load
  518 |     await page.waitForSelector('.place-result, .place-error', { timeout: DATA_LOAD_TIMEOUT });
  519 |     const results = await page.locator('.place-result').all();
  520 |     expect(results.length).toBeGreaterThan(0);
  521 | 
  522 |     // Pick random results and visit detail pages
  523 |     const hrefs = await Promise.all(results.slice(0, 20).map(r => r.getAttribute('href')));
  524 |     const randomLinks = getRandomItems(hrefs.filter(Boolean) as string[], 3);
  525 | 
  526 |     for (const href of randomLinks) {
  527 |       const entityResponse = await page.goto(toRelativePath(href));
  528 |       expect(entityResponse?.status()).toBe(200);
  529 |       await expect(page.locator('h1')).toBeVisible();
  530 |     }
  531 |   });
  532 | 
  533 |   test('postalcodes section - index and random detail pages', async ({ page }) => {
  534 |     await page.goto('/postalcodes/');
  535 | 
  536 |     // Map view is default — verify Leaflet map renders with GeoJSON state shapes
  537 |     await expect(page.locator('#map.leaflet-container')).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
> 538 |     await expect(page.locator('#map .leaflet-interactive').first()).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
      |                                                                     ^ Error: expect(locator).toBeVisible() failed
  539 |     const shapes = await page.locator('#map .leaflet-interactive').all();
  540 |     expect(shapes.length).toBeGreaterThan(0);
  541 | 
  542 |     // Switch to List view to test postal code results and detail navigation
  543 |     await page.locator('.toggle-btn[data-view="list"]').click();
  544 | 
  545 |     // Wait for results to load
  546 |     await page.waitForSelector('.postal-result, .postal-error', { timeout: DATA_LOAD_TIMEOUT });
  547 |     const results = await page.locator('.postal-result').all();
  548 |     expect(results.length).toBeGreaterThan(0);
  549 | 
  550 |     // Pick random results and visit detail pages
  551 |     const hrefs = await Promise.all(results.slice(0, 20).map(r => r.getAttribute('href')));
  552 |     const randomLinks = getRandomItems(hrefs.filter(Boolean) as string[], 3);
  553 | 
  554 |     for (const href of randomLinks) {
  555 |       const entityResponse = await page.goto(toRelativePath(href));
  556 |       expect(entityResponse?.status()).toBe(200);
  557 |       await expect(page.locator('h1')).toBeVisible();
  558 |     }
  559 |   });
  560 | 
  561 |   test('precincts section - map view and state filter', async ({ page }) => {
  562 |     await page.goto('/precincts/');
  563 | 
  564 |     // Map view is default — verify Leaflet map renders with GeoJSON state shapes
  565 |     await expect(page.locator('#map.leaflet-container')).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
  566 |     await expect(page.locator('#map .leaflet-interactive').first()).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
  567 |     const shapes = await page.locator('#map .leaflet-interactive').all();
  568 |     expect(shapes.length).toBeGreaterThan(0);
  569 | 
  570 |     // Switch to List view to test state filter and precinct results
  571 |     await page.locator('.toggle-btn[data-view="list"]').click();
  572 | 
  573 |     // Select a state first (precincts require state selection)
  574 |     const stateSelect = page.locator('#precinct-state-select');
  575 |     await expect(stateSelect).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
  576 |     await stateSelect.selectOption('TX');
  577 | 
  578 |     // Wait for precinct results to load
  579 |     await page.waitForSelector('.precinct-result, .precinct-error', { timeout: DATA_LOAD_TIMEOUT });
  580 |     const results = await page.locator('.precinct-result').all();
  581 |     expect(results.length).toBeGreaterThan(0);
  582 | 
  583 |     // Pick random results and visit detail pages
  584 |     const hrefs = await Promise.all(results.slice(0, 20).map(r => r.getAttribute('href')));
  585 |     const validHrefs = hrefs.filter(Boolean) as string[];
  586 | 
  587 |     if (validHrefs.length > 0) {
  588 |       const randomLinks = getRandomItems(validHrefs, 3);
  589 |       for (const href of randomLinks) {
  590 |         const entityResponse = await page.goto(toRelativePath(href));
  591 |         expect(entityResponse?.status()).toBe(200);
  592 |         await expect(page.locator('h1')).toBeVisible();
  593 |       }
  594 |     }
  595 |   });
  596 | 
  597 |   test('tracts section - state/county filter and random detail pages', async ({ page }) => {
  598 |     test.setTimeout(120000); // Tracts require sequential state→county→tract interactions with GraphQL
  599 |     await page.goto('/tracts/');
  600 | 
  601 |     // Switch to List view — tract-index.js loads dynamically on list view activation
  602 |     await page.locator('.toggle-btn[data-view="list"]').click();
  603 | 
  604 |     // Wait for state dropdown to populate (tract-index.js loads states via GraphQL)
  605 |     const stateSelect = page.locator('#tract-state-select');
  606 |     await page.waitForFunction(() => {
  607 |       const select = document.querySelector('#tract-state-select') as HTMLSelectElement;
  608 |       return select && select.options.length > 1;
  609 |     }, { timeout: 45000 });
  610 | 
  611 |     // Select Texas
  612 |     await stateSelect.selectOption('TX');
  613 | 
  614 |     // Wait for county dropdown to populate
  615 |     const countySelect = page.locator('#tract-county-select');
  616 |     await expect(countySelect).toBeEnabled({ timeout: DATA_LOAD_TIMEOUT });
  617 |     await page.waitForFunction(() => {
  618 |       const select = document.querySelector('#tract-county-select') as HTMLSelectElement;
  619 |       return select && select.options.length > 1;
  620 |     }, { timeout: DATA_LOAD_TIMEOUT });
  621 | 
  622 |     // Select first county
  623 |     const options = await countySelect.locator('option').all();
  624 |     if (options.length > 1) {
  625 |       const value = await options[1].getAttribute('value');
  626 |       if (value) await countySelect.selectOption(value);
  627 |     }
  628 | 
  629 |     // Wait for tract results to load
  630 |     await page.waitForSelector('.tract-result, .tract-error', { timeout: DATA_LOAD_TIMEOUT });
  631 |     const results = await page.locator('.tract-result').all();
  632 |     expect(results.length).toBeGreaterThan(0);
  633 | 
  634 |     // Pick random results and visit detail pages
  635 |     const hrefs = await Promise.all(results.slice(0, 20).map(r => r.getAttribute('href')));
  636 |     const validHrefs = hrefs.filter(Boolean) as string[];
  637 | 
  638 |     if (validHrefs.length > 0) {
```