# 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/postalcodes.spec.ts >> ZIP code pages >> 78701 lists TX-37 and Travis County, linked
- Location: tests/ui/postalcodes.spec.ts:95:7

# Error details

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

Locator:  locator('#districts li')
Expected: 1
Received: 4
Timeout:  5000ms

Call log:
  - Expect "toHaveCount" locator('#districts li') with timeout 5000ms
  - waiting for locator('#districts li')
    14 × locator resolved to 4 elements
       - unexpected value "4"

```

# Page snapshot

```yaml
- generic [active] [ref=e1]:
  - banner [ref=e2]:
    - heading "ZIP Code 78701" [level=1] [ref=e3]
    - paragraph [ref=e4]: ZIP Code Tabulation Area
  - generic [ref=e5]:
    - generic [ref=e6]:
      - generic [ref=e7]: Not signed in
      - generic [ref=e9]:
        - link "Log in" [ref=e10] [cursor=pointer]:
          - /url: /outpost.goauthentik.io/start?rd=http%3A%2F%2Fpreview-us-elect-info.electinfo.svc.cluster.local%2Fpostalcodes%2F78701%2F
        - link "Create an account" [ref=e11] [cursor=pointer]:
          - /url: /signin/register?rd=http%3A%2F%2Fpreview-us-elect-info.electinfo.svc.cluster.local%2Fpostalcodes%2F78701%2F
    - toolbar "Member tools" [ref=e12]:
      - button "All cycles (Sign in to use the cycle facet)" [disabled] [ref=e13] [cursor=pointer]:
        - generic [ref=e17]: All cycles
      - button "Graph (Sign in to use Graph)" [disabled] [ref=e20] [cursor=pointer]:
        - generic [ref=e29]: Graph
  - generic [ref=e32]:
    - heading "Overview" [level=2] [ref=e33]
    - generic [ref=e34]:
      - term [ref=e35]: ZIP Code
      - definition [ref=e36]: "78701"
      - term [ref=e37]: Land Area
      - definition [ref=e38]: 1.51 sq mi
      - term [ref=e39]: Water Area
      - definition [ref=e40]: 0.09 sq mi
      - term [ref=e41]: Total Area
      - definition [ref=e42]: 1.60 sq mi
      - term [ref=e43]: Overlapping Districts
      - definition [ref=e44]: "1"
  - generic [ref=e45]:
    - heading "Boundary" [level=2] [ref=e46]
    - generic "Map of ZIP Code 78701" [ref=e47]:
      - generic:
        - generic:
          - img:
            - generic [ref=e48] [cursor=pointer]
      - generic:
        - generic [ref=e49]:
          - button "Zoom in" [ref=e50] [cursor=pointer]: +
          - button "Zoom out" [ref=e51] [cursor=pointer]: −
        - generic [ref=e52]:
          - link "Leaflet" [ref=e53] [cursor=pointer]:
            - /url: https://leafletjs.com
          - text: "| ©"
          - link "OpenStreetMap" [ref=e58] [cursor=pointer]:
            - /url: https://www.openstreetmap.org/copyright
    - paragraph [ref=e59]:
      - link "Boundary as GeoJSON" [ref=e60] [cursor=pointer]:
        - /url: /geo/zcta/78701.geojson
    - paragraph [ref=e61]: "Source: U.S. Census Bureau TIGER/Line boundaries."
  - generic [ref=e62]:
    - heading "Congressional Districts" [level=2] [ref=e63]
    - paragraph [ref=e64]: "This ZIP code overlaps with the following congressional districts:"
    - list [ref=e65]:
      - listitem [ref=e66]:
        - link "Texas District 37" [ref=e67] [cursor=pointer]:
          - /url: https://preview.elect.info/districts/tx-37/
        - text: (1.60 sq mi overlap)
      - listitem [ref=e68]:
        - link "Texas District 014" [ref=e69] [cursor=pointer]:
          - /url: https://preview.elect.info/districts/tx-sd-014/
        - text: (1.60 sq mi overlap)
      - listitem [ref=e70]:
        - link "Texas District 049" [ref=e71] [cursor=pointer]:
          - /url: https://preview.elect.info/districts/tx-hd-049/
        - text: (1.38 sq mi overlap)
      - listitem [ref=e72]:
        - link "Texas District 051" [ref=e73] [cursor=pointer]:
          - /url: https://preview.elect.info/districts/tx-hd-051/
        - text: (0.22 sq mi overlap)
    - paragraph [ref=e74]: Showing 4 of 1 overlapping districts
  - generic [ref=e75]:
    - heading "Counties" [level=2] [ref=e76]
    - paragraph [ref=e77]: "This ZIP code overlaps with the following counties:"
    - list [ref=e78]:
      - listitem [ref=e79]:
        - link "Travis County" [ref=e80] [cursor=pointer]:
          - /url: https://preview.elect.info/counties/travis-county-tx/
        - text: (1.60 sq mi overlap)
    - paragraph [ref=e81]: Showing 1 of 1 overlapping counties
  - contentinfo [ref=e82]:
    - paragraph [ref=e83]:
      - link "elect.info" [ref=e84] [cursor=pointer]:
        - /url: https://preview.elect.info/
      - text: "| Data from"
      - link "Federal Election Commission" [ref=e85] [cursor=pointer]:
        - /url: https://preview.elect.info/datasources/fec.gov/
      - text: "| Generated 2026-10-25"
```

# Test source

```ts
  1   | import { test, expect, type APIRequestContext } from '@playwright/test';
  2   | import { gql, visitorHeaders } from './helpers';
  3   | 
  4   | // ZIP codes from the OVERLAPS edges (electinfo/sites#284, rundeck#789): a ZCTA overlaps up to four
  5   | // congressional districts and several counties, with overlap areas. Checked through the API and on the
  6   | // ZIP pages, the us-pages renderer's (/postalcodes/<zip>/) and the client list (/postalcodes/?q=).
  7   | // Skipped where the API or the pages predate them (production until its release and graph swap).
  8   | 
  9   | const DATA_LOAD_TIMEOUT = 20_000;
  10  | 
  11  | async function apiHasOverlaps(request: APIRequestContext): Promise<boolean> {
  12  |   const res = await request.post('/api/graphql', {
  13  |     data: { query: '{ __type(name: "PostalCode") { fields { name } } }' },
  14  |     headers: visitorHeaders(),
  15  |   });
  16  |   if (res.status() !== 200) return false;
  17  |   const body = await res.json() as { data?: { __type?: { fields?: Array<{ name: string }> } | null } };
  18  |   return !!body.data?.__type?.fields?.some(f => f.name === 'overlapsCounties');
  19  | }
  20  | 
  21  | interface Overlap<N> {
  22  |   node: N;
  23  |   properties: { overlap_land_area: string | null; overlap_water_area: string | null };
  24  | }
  25  | 
  26  | interface ZipOverlaps {
  27  |   postalCodes: Array<{
  28  |     zip_code: string;
  29  |     overlapsDistrictsConnection: { edges: Array<Overlap<{ state: string; district_number: string }>> };
  30  |     overlapsCountiesConnection: { edges: Array<Overlap<{ name: string; state: string }>> };
  31  |   }>;
  32  | }
  33  | 
  34  | const ZIP_OVERLAPS = `query($zip: String!) { postalCodes(where: { zip_code: { eq: $zip } }) { zip_code
  35  |   overlapsDistrictsConnection { edges { node { state district_number } properties { overlap_land_area overlap_water_area } } }
  36  |   overlapsCountiesConnection { edges { node { name state } properties { overlap_land_area overlap_water_area } } } } }`;
  37  | 
  38  | test.describe('ZIP code overlaps (API)', () => {
  39  |   test.beforeEach(async ({ request }) => {
  40  |     test.skip(!(await apiHasOverlaps(request)), 'the API behind this host has no PostalCode OVERLAPS yet');
  41  |   });
  42  | 
  43  |   test('78701 overlaps TX-37 and Travis County, with land area', async ({ request }) => {
  44  |     const { postalCodes } = await gql<ZipOverlaps>(request, ZIP_OVERLAPS, { zip: '78701' });
  45  |     expect(postalCodes).toHaveLength(1);
  46  |     const districts = postalCodes[0].overlapsDistrictsConnection.edges;
  47  |     expect(districts.map(e => `${e.node.state}-${e.node.district_number}`)).toEqual(['TX-37']);
  48  |     expect(Number(districts[0].properties.overlap_land_area)).toBeGreaterThan(0);
  49  |     const counties = postalCodes[0].overlapsCountiesConnection.edges;
  50  |     expect(counties.map(e => `${e.node.name}, ${e.node.state}`)).toEqual(['Travis County, TX']);
  51  |     expect(Number(counties[0].properties.overlap_land_area)).toBeGreaterThan(0);
  52  |   });
  53  | 
  54  |   test('a ZIP split across districts and counties: 02467 (Chestnut Hill, MA)', async ({ request }) => {
  55  |     const { postalCodes } = await gql<ZipOverlaps>(request, ZIP_OVERLAPS, { zip: '02467' });
  56  |     const districts = postalCodes[0].overlapsDistrictsConnection.edges.map(e => `${e.node.state}-${e.node.district_number}`);
  57  |     expect(districts.sort()).toEqual(['MA-04', 'MA-07', 'MA-08']);
  58  |     expect(postalCodes[0].overlapsCountiesConnection.edges.length).toBe(3);
  59  |   });
  60  | 
  61  |   test('the edges read both ways: TX-37 and Travis County list 78701', async ({ request }) => {
  62  |     const data = await gql<{
  63  |       districts: Array<{ postalCodesOverlaps: Array<{ zip_code: string }> }>;
  64  |       counties: Array<{ postalCodesOverlaps: Array<{ zip_code: string }> }>;
  65  |     }>(request, `{
  66  |       districts(where: { state: { eq: "TX" }, district_number: { eq: "37" } }) { postalCodesOverlaps(where: { zip_code: { eq: "78701" } }) { zip_code } }
  67  |       counties(where: { state: { eq: "TX" }, name: { eq: "Travis County" } }) { postalCodesOverlaps(where: { zip_code: { eq: "78701" } }) { zip_code } }
  68  |     }`);
  69  |     expect(data.districts[0]?.postalCodesOverlaps.map(z => z.zip_code)).toEqual(['78701']);
  70  |     expect(data.counties[0]?.postalCodesOverlaps.map(z => z.zip_code)).toEqual(['78701']);
  71  |   });
  72  | 
  73  |   test('almost every ZIP has a county', async ({ request }) => {
  74  |     // Known gaps (2026-10-14): Connecticut's ZIPs map to its pre-2022 counties, which the graph replaced with
  75  |     // planning regions, and the territories have no County nodes: 305 of 33,791. More than that is a regression.
  76  |     const data = await gql<{ all: { totalCount: number }; none: { totalCount: number } }>(request, `{
  77  |       all: postalCodesConnection { totalCount }
  78  |       none: postalCodesConnection(where: { overlapsCountiesAggregate: { count: { eq: 0 } } }) { totalCount }
  79  |     }`);
  80  |     test.info().annotations.push({ type: 'ZIPs without a county', description: `${data.none.totalCount} of ${data.all.totalCount}` });
  81  |     expect(data.all.totalCount).toBeGreaterThan(30_000);
  82  |     expect(data.none.totalCount).toBeLessThanOrEqual(310);
  83  |   });
  84  | });
  85  | 
  86  | test.describe('ZIP code pages', () => {
  87  |   // The us-pages renderer marks its pages with x-graph-build; static pages predate the OVERLAPS edges.
  88  |   async function rendered(page: import('@playwright/test').Page, path: string) {
  89  |     const response = await page.goto(path);
  90  |     expect(response?.status(), `${path} should answer 200`).toBe(200);
  91  |     test.skip(!response?.headers()['x-graph-build'], 'this host serves static ZIP pages, not the us-pages renderer');
  92  |     return response;
  93  |   }
  94  | 
  95  |   test('78701 lists TX-37 and Travis County, linked', async ({ page }) => {
  96  |     await rendered(page, '/postalcodes/78701/');
  97  |     await expect(page.locator('h1')).toHaveText('ZIP Code 78701');
  98  |     const district = page.locator('#districts a', { hasText: 'Texas District 37' });
  99  |     await expect(district).toHaveAttribute('href', /\/districts\/tx-37\/$/);
> 100 |     await expect(page.locator('#districts li')).toHaveCount(1);
      |                                                 ^ Error: expect(locator).toHaveCount(expected) failed
  101 |     await expect(page.locator('#counties a', { hasText: 'Travis County' })).toHaveCount(1);
  102 |   });
  103 | 
  104 |   test('02467 lists three districts, largest overlap first, and three counties', async ({ page }) => {
  105 |     await rendered(page, '/postalcodes/02467/');
  106 |     const items = page.locator('#districts li');
  107 |     await expect(items).toHaveCount(3);
  108 |     await expect(items.first()).toContainText('Massachusetts District 04');
  109 |     await expect(page.locator('#districts .meta').last()).toHaveText('Showing 3 of 3 overlapping districts');
  110 |     await expect(page.locator('#counties li')).toHaveCount(3);
  111 |   });
  112 | 
  113 |   test('the water-only "ZZ" district is not listed: 60611 (Chicago, on Lake Michigan)', async ({ page }) => {
  114 |     await rendered(page, '/postalcodes/60611/');
  115 |     await expect(page.locator('#districts li').first()).toContainText('Illinois District');
  116 |     await expect(page.locator('#districts')).not.toContainText('ZZ');
  117 |     await expect(page.locator('#counties a', { hasText: 'Cook County' })).toHaveCount(1);
  118 |   });
  119 | 
  120 |   test('the ZIP list shows each ZIP with its districts', async ({ page, request }) => {
  121 |     test.skip(!(await apiHasOverlaps(request)), 'the API behind this host has no PostalCode OVERLAPS yet');
  122 |     // The index opens on the map; the list view (postalcode-index.js) loads on its toggle and reads ?q=.
  123 |     const listView = async (q: string) => {
  124 |       await page.goto(`/postalcodes/?q=${q}`);
  125 |       await page.locator('.toggle-btn[data-view="list"]').click();
  126 |     };
  127 |     await listView('78701');
  128 |     const result = page.locator('.postal-result', { hasText: '78701' }).first();
  129 |     await expect(result).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
  130 |     await expect(result.locator('.postal-result-meta')).toContainText('TX-37');
  131 | 
  132 |     await listView('60611');
  133 |     const chicago = page.locator('.postal-result', { hasText: '60611' }).first();
  134 |     await expect(chicago).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
  135 |     await expect(chicago.locator('.postal-result-meta')).toContainText('IL-');
  136 |     await expect(chicago.locator('.postal-result-meta')).not.toContainText('ZZ');
  137 |   });
  138 | });
  139 | 
```