# 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/routing.spec.ts >> routing: every index link resolves >> /districts/ first-page links all answer 200 or 301
- Location: tests/ui/routing.spec.ts:73:9

# Error details

```
Error: /districts/ links that do not resolve

expect(received).toEqual(expected) // deep equality

- Expected  -  1
+ Received  + 52

- Array []
+ Array [
+   "/districts/ak-00/ -> 503",
+   "/districts/al-01/ -> 503",
+   "/districts/al-02/ -> 503",
+   "/districts/al-03/ -> 503",
+   "/districts/al-04/ -> 503",
+   "/districts/al-05/ -> 503",
+   "/districts/al-06/ -> 503",
+   "/districts/al-07/ -> 503",
+   "/districts/ar-01/ -> 503",
+   "/districts/ar-02/ -> 503",
+   "/districts/ar-03/ -> 503",
+   "/districts/ar-04/ -> 503",
+   "/districts/as-00/ -> 503",
+   "/districts/az-01/ -> 503",
+   "/districts/az-02/ -> 503",
+   "/districts/az-03/ -> 503",
+   "/districts/az-04/ -> 503",
+   "/districts/az-05/ -> 503",
+   "/districts/az-06/ -> 503",
+   "/districts/az-07/ -> 503",
+   "/districts/az-08/ -> 503",
+   "/districts/az-09/ -> 503",
+   "/districts/ca-01/ -> 503",
+   "/districts/ca-02/ -> 503",
+   "/districts/ca-03/ -> 503",
+   "/districts/ca-04/ -> 503",
+   "/districts/ca-05/ -> 503",
+   "/districts/ca-06/ -> 503",
+   "/districts/ca-07/ -> 503",
+   "/districts/ca-08/ -> 503",
+   "/districts/ca-09/ -> 503",
+   "/districts/ca-10/ -> 503",
+   "/districts/ca-11/ -> 503",
+   "/districts/ca-12/ -> 503",
+   "/districts/ca-13/ -> 503",
+   "/districts/ca-14/ -> 503",
+   "/districts/ca-15/ -> 503",
+   "/districts/ca-16/ -> 503",
+   "/districts/ca-17/ -> 503",
+   "/districts/ca-18/ -> 503",
+   "/districts/ca-19/ -> 503",
+   "/districts/ca-20/ -> 503",
+   "/districts/ca-21/ -> 503",
+   "/districts/ca-22/ -> 503",
+   "/districts/ca-23/ -> 503",
+   "/districts/ca-24/ -> 503",
+   "/districts/ca-25/ -> 503",
+   "/districts/ca-26/ -> 503",
+   "/districts/ca-27/ -> 503",
+   "/districts/ca-28/ -> 503",
+ ]
```

# Page snapshot

```yaml
- generic [active] [ref=e1]:
  - link "← Back to Home" [ref=e2] [cursor=pointer]:
    - /url: /
  - banner [ref=e3]:
    - heading "Districts" [level=1] [ref=e4]
    - paragraph [ref=e5]: Congressional, State Senate and State House districts
  - generic [ref=e6]:
    - button "Map" [ref=e7] [cursor=pointer]
    - button "List" [ref=e8] [cursor=pointer]
  - main [ref=e9]:
    - generic [ref=e11]:
      - generic [ref=e12]:
        - textbox "Search districts by name..." [ref=e13]
        - button "Search" [ref=e14] [cursor=pointer]
      - generic [ref=e15]:
        - button "All" [pressed] [ref=e16] [cursor=pointer]
        - button "Congressional" [ref=e17] [cursor=pointer]
        - button "State Senate" [ref=e18] [cursor=pointer]
        - button "State House" [ref=e19] [cursor=pointer]
      - generic [ref=e20]:
        - generic [ref=e21]: "State:"
        - combobox "State:" [ref=e22] [cursor=pointer]:
          - option "All states" [selected]
          - option "Alabama"
          - option "Alaska"
          - option "American Samoa"
          - option "Arizona"
          - option "Arkansas"
          - option "California"
          - option "Colorado"
          - option "Connecticut"
          - option "Delaware"
          - option "District of Columbia"
          - option "Florida"
          - option "Georgia"
          - option "Guam"
          - 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 "Northern Mariana Islands"
          - option "Ohio"
          - option "Oklahoma"
          - option "Oregon"
          - option "Pennsylvania"
          - option "Puerto Rico"
          - option "Rhode Island"
          - option "South Carolina"
          - option "South Dakota"
          - option "Tennessee"
          - option "Texas"
          - option "U.S. Virgin Islands"
          - option "Utah"
          - option "Vermont"
          - option "Virginia"
          - option "Washington"
          - option "West Virginia"
          - option "Wisconsin"
          - option "Wyoming"
      - generic [ref=e23]:
        - heading "All Districts" [level=3] [ref=e25]
        - generic [ref=e26]:
          - link "AK-00 · Congressional Alaska" [ref=e27] [cursor=pointer]:
            - /url: /districts/ak-00/
            - generic [ref=e28]: AK-00 · Congressional
            - generic [ref=e29]: Alaska
          - link "AL-01 · Congressional Alabama" [ref=e30] [cursor=pointer]:
            - /url: /districts/al-01/
            - generic [ref=e31]: AL-01 · Congressional
            - generic [ref=e32]: Alabama
          - link "AL-02 · Congressional Alabama" [ref=e33] [cursor=pointer]:
            - /url: /districts/al-02/
            - generic [ref=e34]: AL-02 · Congressional
            - generic [ref=e35]: Alabama
          - link "AL-03 · Congressional Alabama" [ref=e36] [cursor=pointer]:
            - /url: /districts/al-03/
            - generic [ref=e37]: AL-03 · Congressional
            - generic [ref=e38]: Alabama
          - link "AL-04 · Congressional Alabama" [ref=e39] [cursor=pointer]:
            - /url: /districts/al-04/
            - generic [ref=e40]: AL-04 · Congressional
            - generic [ref=e41]: Alabama
          - link "AL-05 · Congressional Alabama" [ref=e42] [cursor=pointer]:
            - /url: /districts/al-05/
            - generic [ref=e43]: AL-05 · Congressional
            - generic [ref=e44]: Alabama
          - link "AL-06 · Congressional Alabama" [ref=e45] [cursor=pointer]:
            - /url: /districts/al-06/
            - generic [ref=e46]: AL-06 · Congressional
            - generic [ref=e47]: Alabama
          - link "AL-07 · Congressional Alabama" [ref=e48] [cursor=pointer]:
            - /url: /districts/al-07/
            - generic [ref=e49]: AL-07 · Congressional
            - generic [ref=e50]: Alabama
          - link "AR-01 · Congressional Arkansas" [ref=e51] [cursor=pointer]:
            - /url: /districts/ar-01/
            - generic [ref=e52]: AR-01 · Congressional
            - generic [ref=e53]: Arkansas
          - link "AR-02 · Congressional Arkansas" [ref=e54] [cursor=pointer]:
            - /url: /districts/ar-02/
            - generic [ref=e55]: AR-02 · Congressional
            - generic [ref=e56]: Arkansas
          - link "AR-03 · Congressional Arkansas" [ref=e57] [cursor=pointer]:
            - /url: /districts/ar-03/
            - generic [ref=e58]: AR-03 · Congressional
            - generic [ref=e59]: Arkansas
          - link "AR-04 · Congressional Arkansas" [ref=e60] [cursor=pointer]:
            - /url: /districts/ar-04/
            - generic [ref=e61]: AR-04 · Congressional
            - generic [ref=e62]: Arkansas
          - link "AS-00 · Congressional American Samoa" [ref=e63] [cursor=pointer]:
            - /url: /districts/as-00/
            - generic [ref=e64]: AS-00 · Congressional
            - generic [ref=e65]: American Samoa
          - link "AZ-01 · Congressional Arizona" [ref=e66] [cursor=pointer]:
            - /url: /districts/az-01/
            - generic [ref=e67]: AZ-01 · Congressional
            - generic [ref=e68]: Arizona
          - link "AZ-02 · Congressional Arizona" [ref=e69] [cursor=pointer]:
            - /url: /districts/az-02/
            - generic [ref=e70]: AZ-02 · Congressional
            - generic [ref=e71]: Arizona
          - link "AZ-03 · Congressional Arizona" [ref=e72] [cursor=pointer]:
            - /url: /districts/az-03/
            - generic [ref=e73]: AZ-03 · Congressional
            - generic [ref=e74]: Arizona
          - link "AZ-04 · Congressional Arizona" [ref=e75] [cursor=pointer]:
            - /url: /districts/az-04/
            - generic [ref=e76]: AZ-04 · Congressional
            - generic [ref=e77]: Arizona
          - link "AZ-05 · Congressional Arizona" [ref=e78] [cursor=pointer]:
            - /url: /districts/az-05/
            - generic [ref=e79]: AZ-05 · Congressional
            - generic [ref=e80]: Arizona
          - link "AZ-06 · Congressional Arizona" [ref=e81] [cursor=pointer]:
            - /url: /districts/az-06/
            - generic [ref=e82]: AZ-06 · Congressional
            - generic [ref=e83]: Arizona
          - link "AZ-07 · Congressional Arizona" [ref=e84] [cursor=pointer]:
            - /url: /districts/az-07/
            - generic [ref=e85]: AZ-07 · Congressional
            - generic [ref=e86]: Arizona
          - link "AZ-08 · Congressional Arizona" [ref=e87] [cursor=pointer]:
            - /url: /districts/az-08/
            - generic [ref=e88]: AZ-08 · Congressional
            - generic [ref=e89]: Arizona
          - link "AZ-09 · Congressional Arizona" [ref=e90] [cursor=pointer]:
            - /url: /districts/az-09/
            - generic [ref=e91]: AZ-09 · Congressional
            - generic [ref=e92]: Arizona
          - link "CA-01 · Congressional California" [ref=e93] [cursor=pointer]:
            - /url: /districts/ca-01/
            - generic [ref=e94]: CA-01 · Congressional
            - generic [ref=e95]: California
          - link "CA-02 · Congressional California" [ref=e96] [cursor=pointer]:
            - /url: /districts/ca-02/
            - generic [ref=e97]: CA-02 · Congressional
            - generic [ref=e98]: California
          - link "CA-03 · Congressional California" [ref=e99] [cursor=pointer]:
            - /url: /districts/ca-03/
            - generic [ref=e100]: CA-03 · Congressional
            - generic [ref=e101]: California
          - link "CA-04 · Congressional California" [ref=e102] [cursor=pointer]:
            - /url: /districts/ca-04/
            - generic [ref=e103]: CA-04 · Congressional
            - generic [ref=e104]: California
          - link "CA-05 · Congressional California" [ref=e105] [cursor=pointer]:
            - /url: /districts/ca-05/
            - generic [ref=e106]: CA-05 · Congressional
            - generic [ref=e107]: California
          - link "CA-06 · Congressional California" [ref=e108] [cursor=pointer]:
            - /url: /districts/ca-06/
            - generic [ref=e109]: CA-06 · Congressional
            - generic [ref=e110]: California
          - link "CA-07 · Congressional California" [ref=e111] [cursor=pointer]:
            - /url: /districts/ca-07/
            - generic [ref=e112]: CA-07 · Congressional
            - generic [ref=e113]: California
          - link "CA-08 · Congressional California" [ref=e114] [cursor=pointer]:
            - /url: /districts/ca-08/
            - generic [ref=e115]: CA-08 · Congressional
            - generic [ref=e116]: California
          - link "CA-09 · Congressional California" [ref=e117] [cursor=pointer]:
            - /url: /districts/ca-09/
            - generic [ref=e118]: CA-09 · Congressional
            - generic [ref=e119]: California
          - link "CA-10 · Congressional California" [ref=e120] [cursor=pointer]:
            - /url: /districts/ca-10/
            - generic [ref=e121]: CA-10 · Congressional
            - generic [ref=e122]: California
          - link "CA-11 · Congressional California" [ref=e123] [cursor=pointer]:
            - /url: /districts/ca-11/
            - generic [ref=e124]: CA-11 · Congressional
            - generic [ref=e125]: California
          - link "CA-12 · Congressional California" [ref=e126] [cursor=pointer]:
            - /url: /districts/ca-12/
            - generic [ref=e127]: CA-12 · Congressional
            - generic [ref=e128]: California
          - link "CA-13 · Congressional California" [ref=e129] [cursor=pointer]:
            - /url: /districts/ca-13/
            - generic [ref=e130]: CA-13 · Congressional
            - generic [ref=e131]: California
          - link "CA-14 · Congressional California" [ref=e132] [cursor=pointer]:
            - /url: /districts/ca-14/
            - generic [ref=e133]: CA-14 · Congressional
            - generic [ref=e134]: California
          - link "CA-15 · Congressional California" [ref=e135] [cursor=pointer]:
            - /url: /districts/ca-15/
            - generic [ref=e136]: CA-15 · Congressional
            - generic [ref=e137]: California
          - link "CA-16 · Congressional California" [ref=e138] [cursor=pointer]:
            - /url: /districts/ca-16/
            - generic [ref=e139]: CA-16 · Congressional
            - generic [ref=e140]: California
          - link "CA-17 · Congressional California" [ref=e141] [cursor=pointer]:
            - /url: /districts/ca-17/
            - generic [ref=e142]: CA-17 · Congressional
            - generic [ref=e143]: California
          - link "CA-18 · Congressional California" [ref=e144] [cursor=pointer]:
            - /url: /districts/ca-18/
            - generic [ref=e145]: CA-18 · Congressional
            - generic [ref=e146]: California
          - link "CA-19 · Congressional California" [ref=e147] [cursor=pointer]:
            - /url: /districts/ca-19/
            - generic [ref=e148]: CA-19 · Congressional
            - generic [ref=e149]: California
          - link "CA-20 · Congressional California" [ref=e150] [cursor=pointer]:
            - /url: /districts/ca-20/
            - generic [ref=e151]: CA-20 · Congressional
            - generic [ref=e152]: California
          - link "CA-21 · Congressional California" [ref=e153] [cursor=pointer]:
            - /url: /districts/ca-21/
            - generic [ref=e154]: CA-21 · Congressional
            - generic [ref=e155]: California
          - link "CA-22 · Congressional California" [ref=e156] [cursor=pointer]:
            - /url: /districts/ca-22/
            - generic [ref=e157]: CA-22 · Congressional
            - generic [ref=e158]: California
          - link "CA-23 · Congressional California" [ref=e159] [cursor=pointer]:
            - /url: /districts/ca-23/
            - generic [ref=e160]: CA-23 · Congressional
            - generic [ref=e161]: California
          - link "CA-24 · Congressional California" [ref=e162] [cursor=pointer]:
            - /url: /districts/ca-24/
            - generic [ref=e163]: CA-24 · Congressional
            - generic [ref=e164]: California
          - link "CA-25 · Congressional California" [ref=e165] [cursor=pointer]:
            - /url: /districts/ca-25/
            - generic [ref=e166]: CA-25 · Congressional
            - generic [ref=e167]: California
          - link "CA-26 · Congressional California" [ref=e168] [cursor=pointer]:
            - /url: /districts/ca-26/
            - generic [ref=e169]: CA-26 · Congressional
            - generic [ref=e170]: California
          - link "CA-27 · Congressional California" [ref=e171] [cursor=pointer]:
            - /url: /districts/ca-27/
            - generic [ref=e172]: CA-27 · Congressional
            - generic [ref=e173]: California
          - link "CA-28 · Congressional California" [ref=e174] [cursor=pointer]:
            - /url: /districts/ca-28/
            - generic [ref=e175]: CA-28 · Congressional
            - generic [ref=e176]: California
        - generic [ref=e177]:
          - generic [ref=e178]: Showing 1-50
          - generic [ref=e179]:
            - button "First" [disabled] [ref=e180]
            - button "Prev" [disabled] [ref=e181]
            - button "1" [ref=e182] [cursor=pointer]
            - button "Next" [ref=e183] [cursor=pointer]
```

# Test source

```ts
  1   | import { test, expect, APIRequestContext } from '@playwright/test';
  2   | import { canonicalPath, getRandomItems, premiumGated, toRelativePath } from './helpers';
  3   | 
  4   | /*
  5   |  * Routing coverage across every collection (electinfo/rundeck#700, ops#514).
  6   |  *
  7   |  * What slipped past the existing specs:
  8   |  *   - the preview nginx lacked the /politicians/ -> /candidates/ rewrite the
  9   |  *     production config has (sites#162) -- only 3 random index links were followed;
  10  |  *   - unknown paths fall through to the Payload slug lookup, which answers
  11  |  *     400/500 instead of 404.
  12  |  */
  13  | 
  14  | const ALIASES = process.env.EXPECT_ALIASES === '1';
  15  | 
  16  | /** Collections whose sitemap lists canonical URLs (rundeck PagePaths Sitemaps + CyclePages). */
  17  | const SITEMAP_COLLECTIONS = ['candidates', 'committees', 'parties', 'offices', 'districts', 'states', 'elections'];
  18  | 
  19  | /** Indexes whose first results page lists entity links (.entity-search-result). */
  20  | // (/states/ lists map chips — its sitemap test covers it; /districts/ uses the
  21  | // shared EntityIndex and lists .entity-search-result directly, so it is included here)
  22  | const LINKED_INDEXES = ['/candidates/', '/committees/', '/parties/', '/offices/', '/districts/', '/elections/', '/employers/', '/vendors/'];
  23  | 
  24  | const locs = (xml: string) => [...xml.matchAll(/<loc>([^<]+)<\/loc>/g)].map(m => m[1]);
  25  | 
  26  | /** Canonical page URLs from a collection's sitemap, following one level of sitemap index. */
  27  | async function sitemapLocs(request: APIRequestContext, collection: string): Promise<string[]> {
  28  |   const path = `/${collection}/sitemap-${collection}.xml`;
  29  |   const res = await request.get(path);
  30  |   expect(res.status(), `${path} should answer 200`).toBe(200);
  31  |   const xml = await res.text();
  32  |   if (!xml.includes('<sitemapindex')) return locs(xml);
  33  |   const [child] = getRandomItems(locs(xml), 1, `child:${path}`);
  34  |   const childPath = toRelativePath(child);
  35  |   const childRes = await request.get(childPath);
  36  |   expect(childRes.status(), `${childPath} should answer 200`).toBe(200);
  37  |   return locs(await childRes.text());
  38  | }
  39  | 
  40  | test.describe('routing: canonical URLs per collection', () => {
  41  |   test.skip(!ALIASES, 'Set EXPECT_ALIASES=1 against a target with published page paths');
  42  | 
  43  |   for (const collection of SITEMAP_COLLECTIONS) {
  44  |     test(`${collection}: sitemap URLs serve their page with rel=canonical = the sitemap URL`, async ({ request }) => {
  45  |       // Entity pages only: a sitemap may also list the collection's index (/elections/)
  46  |       const entityLocs = (await sitemapLocs(request, collection)).filter(l => toRelativePath(l) !== `/${collection}/`);
  47  |       const sample = getRandomItems(entityLocs, 3);
  48  |       expect(sample.length, `${collection} sitemap should list URLs`).toBeGreaterThan(0);
  49  |       for (const loc of sample) {
  50  |         const path = toRelativePath(loc);
  51  |         const res = await request.get(path, { maxRedirects: 0 });
  52  |         expect(res.status(), `${path} (from the ${collection} sitemap) should answer 200`).toBe(200);
  53  |         expect(canonicalPath(await res.text()), `${path} rel=canonical`).toBe(path);
  54  |       }
  55  |     });
  56  |   }
  57  | 
  58  | 
  59  |   test('party badge links by FEC code redirect to the party page', async ({ request }) => {
  60  |     // us.elect.news links parties as /parties/<code>/ (sites#164)
  61  |     for (const code of ['rep', 'dem']) {
  62  |       const res = await request.get(`/parties/${code}/`, { maxRedirects: 0 });
  63  |       expect(res.status(), `/parties/${code}/ should redirect permanently`).toBe(301);
  64  |       const target = res.headers()['location'] ?? '';
  65  |       const page = await request.get(target, { maxRedirects: 0 });
  66  |       expect(page.status(), `/parties/${code}/ -> ${target} should answer 200`).toBe(200);
  67  |     }
  68  |   });
  69  | });
  70  | 
  71  | test.describe('routing: every index link resolves', () => {
  72  |   for (const index of LINKED_INDEXES) {
  73  |     test(`${index} first-page links all answer 200 or 301`, async ({ page, request }) => {
  74  |       test.setTimeout(120000);
  75  |       const res = await page.goto(index);
  76  |       expect(res?.status(), `${index} should answer 200`).toBe(200);
  77  |       await expect(page.locator('.entity-search-result').first(), `${index} should list results`).toBeVisible({ timeout: 15000 });
  78  |       const hrefs = (await page.locator('.entity-search-result').evaluateAll(els => els.map(e => e.getAttribute('href'))))
  79  |         .filter((h): h is string => !!h)
  80  |         .map(toRelativePath);
  81  |       const broken: string[] = [];
  82  |       for (const href of [...new Set(hrefs)]) {
  83  |         const r = await request.get(href, { maxRedirects: 0 });
  84  |         if (r.status() !== 200 && r.status() !== 301) broken.push(`${href} -> ${r.status()}`);
  85  |       }
> 86  |       expect(broken, `${index} links that do not resolve`).toEqual([]);
      |                                                            ^ Error: /districts/ links that do not resolve
  87  |     });
  88  |   }
  89  | });
  90  | 
  91  | test.describe('routing: unknown addresses', () => {
  92  |   test('an unknown entity path answers 404', async ({ request }) => {
  93  |     // A miss in /_resolve (or the API being unavailable) ends in nginx's own
  94  |     // 404. It used to fall through to Payload's slug-lookup, which answered
  95  |     // 400/500 (electinfo/sites#218). Only the alias-routed collections apply:
  96  |     // leaf collections are covered below.
  97  |     for (const path of ['/candidates/no-such-entity-zz/', '/committees/no-such-entity-zz/']) {
  98  |       const res = await request.get(path, { maxRedirects: 0 });
  99  |       expect(res.status(), `${path} should answer 404`).toBe(404);
  100 |     }
  101 |   });
  102 | 
  103 |   test('an unknown leaf entity path serves the collection shell', async ({ request }) => {
  104 |     // Leaf collections (individuals, employers, vendors, blocks, precincts,
  105 |     // tracts) render client-side from the baked collection shell (sites#209),
  106 |     // so nginx answers every subpath with that shell and the runtime reports
  107 |     // a missing entity itself. There is no server-side 404 to expect here.
  108 |     for (const col of ['individuals', 'employers', 'vendors', 'blocks', 'precincts', 'tracts']) {
  109 |       // A premium section closed to this visitor answers sign-in or 403 instead (auth.spec.ts).
  110 |       if (await premiumGated(request, `/${col}/`)) continue;
  111 |       const res = await request.get(`/${col}/no-such-entity-zz/`, { maxRedirects: 0 });
  112 |       expect(res.status(), `/${col}/no-such-entity-zz/ should serve the shell`).toBe(200);
  113 |       expect(res.headers()['content-type'] ?? '', `/${col}/ shell is HTML`).toContain('text/html');
  114 |     }
  115 |   });
  116 | });
  117 | 
```