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

# Error details

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

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

- Expected  -  1
+ Received  + 52

- Array []
+ Array [
+   "/politicians/10e488e1-4d9d-19d9-c96e-869dd0c45757/ -> 503",
+   "/politicians/ffd8f1ef-a47c-ad4f-d23a-b4bbc0b92fd3/ -> 503",
+   "/politicians/8eaa6bab-c1a3-3687-7e4c-c1ff0dde4553/ -> 503",
+   "/politicians/0b4c0d0f-e8ec-b57e-6054-09260103ccd1/ -> 503",
+   "/politicians/b6f801af-5a54-420b-b63d-4e4a69540d14/ -> 503",
+   "/politicians/502b3f1b-f119-87ec-965b-b2bd622c947d/ -> 503",
+   "/politicians/3fc5ac3c-26f7-cd7b-b632-4ddf8b29ff92/ -> 503",
+   "/politicians/f4d462f8-addd-1c43-7a18-e6542446a3aa/ -> 503",
+   "/politicians/9d383d64-a369-0e65-e4ff-c419209eb7f2/ -> 503",
+   "/politicians/3c3efbb7-1f2f-eadc-99dc-72c550fd4a63/ -> 503",
+   "/politicians/55a68bb1-7d8a-e606-fd56-f9e1d423e92c/ -> 503",
+   "/politicians/0b70128e-9639-94d4-1e31-0dc27783a511/ -> 503",
+   "/politicians/2a7de561-5356-faa0-5052-63aed4ddcd27/ -> 503",
+   "/politicians/5b09fb7f-2092-bf70-e88f-6cf5facc4627/ -> 503",
+   "/politicians/8dc4579f-7a76-c4bf-f62e-90985e629a23/ -> 503",
+   "/politicians/972721e4-173f-5925-b1c4-535ff83ffd0b/ -> 503",
+   "/politicians/e7801495-a90c-cca4-b927-e2c01302f203/ -> 503",
+   "/politicians/fb735f81-5e62-7ef2-55d3-99328c68dffe/ -> 503",
+   "/politicians/a0a0c2d7-d390-576a-065b-215948e1408b/ -> 503",
+   "/politicians/f210f66b-2da5-effc-6f6d-7c8d542a3018/ -> 503",
+   "/politicians/796448fd-3ee5-3420-7ee3-b2f397cb270d/ -> 503",
+   "/politicians/d8f3cc02-45cd-8e07-b8c7-a0fdd57fb7fb/ -> 503",
+   "/politicians/3fb15bb3-d791-1f57-1cb7-409127c51411/ -> 503",
+   "/politicians/865439e2-6d93-7d19-fa94-c6de36d8b403/ -> 503",
+   "/politicians/44087072-9125-0776-c672-a81d7ec95de6/ -> 503",
+   "/politicians/264e3be5-51a4-ea82-fdf4-5bd4d98dd5d9/ -> 503",
+   "/politicians/ab6bf360-388b-e006-acaa-17a45b4184d4/ -> 503",
+   "/politicians/f691ba17-df86-8a34-33c5-1086c5a5ee49/ -> 503",
+   "/politicians/16091b04-e2b6-afce-c421-3eb15b5a8e7a/ -> 503",
+   "/politicians/bfcafdd4-62da-f107-22fb-ff5413c33fa4/ -> 503",
+   "/politicians/ac444ee4-a581-d942-62ae-9a620be14321/ -> 503",
+   "/politicians/45709efc-0864-5b11-b38e-d56ad0be1107/ -> 503",
+   "/politicians/86f4ffe9-3484-b620-89e2-eb1b2c6ae7a3/ -> 503",
+   "/politicians/1e3a95c1-2210-01f9-3648-3581de7b7056/ -> 503",
+   "/politicians/1b22096d-cd65-6817-0c9a-35d81d1d5aa0/ -> 503",
+   "/politicians/6c4071ec-b193-f654-d8ef-1f95775d17cd/ -> 503",
+   "/politicians/216af8b0-f136-06f7-32cc-a8bbe029c69f/ -> 503",
+   "/politicians/943653f0-b72d-8ccd-6363-1512069d775f/ -> 503",
+   "/politicians/4f62825b-6f6a-c85c-bac7-641dd0397f9f/ -> 503",
+   "/politicians/e73d7693-8ffd-8f14-40d3-26aa6c4b83a9/ -> 503",
+   "/politicians/ac04e4e9-804e-2714-6e54-d8a0f575fcdf/ -> 503",
+   "/politicians/cb3073ab-8fc7-3286-5ed2-cb384e799b80/ -> 503",
+   "/politicians/87a6346a-9617-56ef-38ce-faea8f36638a/ -> 503",
+   "/politicians/308a7031-cb67-3510-4cc6-88666bc5b2b6/ -> 503",
+   "/politicians/365eaaff-4fc2-0dcb-1dbf-5c824aa2ae5a/ -> 503",
+   "/politicians/e35d80b7-6f5e-45e9-9493-2f8f8cb8499b/ -> 503",
+   "/politicians/a62a533a-7d34-a658-3683-cb16957bb514/ -> 503",
+   "/politicians/ef7f33e5-4520-942f-98a1-54134927bbf4/ -> 503",
+   "/politicians/abecc483-1be3-f38c-76d1-015cc0f211f6/ -> 503",
+   "/politicians/32ca234a-cff0-9480-953b-c525ff3139d4/ -> 503",
+ ]
```

# Page snapshot

```yaml
- generic [active] [ref=e1]:
  - link "← Back to Home" [ref=e2] [cursor=pointer]:
    - /url: /
  - banner [ref=e3]:
    - heading "Candidates" [level=1] [ref=e4]
    - paragraph [ref=e5]: Presidential, Senate & House candidates
  - generic [ref=e6]:
    - generic [ref=e7]:
      - textbox "Search politicians by name, state, party..." [ref=e8]
      - button "Search" [ref=e9] [cursor=pointer]
    - generic [ref=e10]:
      - button "All" [ref=e11] [cursor=pointer]
      - button "Current" [ref=e12] [cursor=pointer]
      - button "President" [ref=e13] [cursor=pointer]
      - button "Senate" [ref=e14] [cursor=pointer]
      - button "House" [ref=e15] [cursor=pointer]
    - generic [ref=e16]:
      - heading "All Politicians" [level=3] [ref=e18]
      - generic [ref=e19]:
        - link "TRUMP, DONALD J. President | REP | $123,035,749" [ref=e20] [cursor=pointer]:
          - /url: /politicians/10e488e1-4d9d-19d9-c96e-869dd0c45757/
          - generic [ref=e21]: TRUMP, DONALD J.
          - generic [ref=e22]: President | REP | $123,035,749
        - link "BIDEN, JOSEPH R JR President | DEM | $43,567,714" [ref=e23] [cursor=pointer]:
          - /url: /politicians/ffd8f1ef-a47c-ad4f-d23a-b4bbc0b92fd3/
          - generic [ref=e24]: BIDEN, JOSEPH R JR
          - generic [ref=e25]: President | DEM | $43,567,714
        - link "BUSH, GEORGE W President | REP | $34,175,936" [ref=e26] [cursor=pointer]:
          - /url: /politicians/8eaa6bab-c1a3-3687-7e4c-c1ff0dde4553/
          - generic [ref=e27]: BUSH, GEORGE W
          - generic [ref=e28]: President | REP | $34,175,936
        - link "KERRY, JOHN F President | DEM | $33,783,633" [ref=e29] [cursor=pointer]:
          - /url: /politicians/0b4c0d0f-e8ec-b57e-6054-09260103ccd1/
          - generic [ref=e30]: KERRY, JOHN F
          - generic [ref=e31]: President | DEM | $33,783,633
        - link "HOYER, STENY House - MD-05 | DEM | $30,889,627" [ref=e32] [cursor=pointer]:
          - /url: /politicians/b6f801af-5a54-420b-b63d-4e4a69540d14/
          - generic [ref=e33]: HOYER, STENY
          - generic [ref=e34]: House - MD-05 | DEM | $30,889,627
        - link "OBAMA, BARACK / JOSEPH R. BIDEN President | DEM | $28,986,892" [ref=e35] [cursor=pointer]:
          - /url: /politicians/502b3f1b-f119-87ec-965b-b2bd622c947d/
          - generic [ref=e36]: OBAMA, BARACK / JOSEPH R. BIDEN
          - generic [ref=e37]: President | DEM | $28,986,892
        - link "CLINTON, HILLARY RODHAM / TIMOTHY MICHAEL KAINE President | DEM | $26,107,105" [ref=e38] [cursor=pointer]:
          - /url: /politicians/3fc5ac3c-26f7-cd7b-b632-4ddf8b29ff92/
          - generic [ref=e39]: CLINTON, HILLARY RODHAM / TIMOTHY MICHAEL KAINE
          - generic [ref=e40]: President | DEM | $26,107,105
        - link "MCCONNELL, MITCH Senate - KY-00 | REP | $25,468,649" [ref=e41] [cursor=pointer]:
          - /url: /politicians/f4d462f8-addd-1c43-7a18-e6542446a3aa/
          - generic [ref=e42]: MCCONNELL, MITCH
          - generic [ref=e43]: Senate - KY-00 | REP | $25,468,649
        - link "MCCAIN, JOHN S. President | REP | $24,312,723" [ref=e44] [cursor=pointer]:
          - /url: /politicians/9d383d64-a369-0e65-e4ff-c419209eb7f2/
          - generic [ref=e45]: MCCAIN, JOHN S.
          - generic [ref=e46]: President | REP | $24,312,723
        - link "CORNYN, JOHN SEN Senate - TX-00 | REP | $23,551,027" [ref=e47] [cursor=pointer]:
          - /url: /politicians/3c3efbb7-1f2f-eadc-99dc-72c550fd4a63/
          - generic [ref=e48]: CORNYN, JOHN SEN
          - generic [ref=e49]: Senate - TX-00 | REP | $23,551,027
        - link "ROMNEY, MITT / PAUL D. RYAN President | REP | $23,380,774" [ref=e50] [cursor=pointer]:
          - /url: /politicians/55a68bb1-7d8a-e606-fd56-f9e1d423e92c/
          - generic [ref=e51]: ROMNEY, MITT / PAUL D. RYAN
          - generic [ref=e52]: President | REP | $23,380,774
        - link "PALLONE, FRANK JR House - NJ-06 | DEM | $22,516,887" [ref=e53] [cursor=pointer]:
          - /url: /politicians/0b70128e-9639-94d4-1e31-0dc27783a511/
          - generic [ref=e54]: PALLONE, FRANK JR
          - generic [ref=e55]: House - NJ-06 | DEM | $22,516,887
        - link "BOXER, BARBARA Senate - CA-00 | DEM | $22,144,588" [ref=e56] [cursor=pointer]:
          - /url: /politicians/2a7de561-5356-faa0-5052-63aed4ddcd27/
          - generic [ref=e57]: BOXER, BARBARA
          - generic [ref=e58]: Senate - CA-00 | DEM | $22,144,588
        - link "NEAL, RICHARD E House - MA-01 | DEM | $21,519,107" [ref=e59] [cursor=pointer]:
          - /url: /politicians/5b09fb7f-2092-bf70-e88f-6cf5facc4627/
          - generic [ref=e60]: NEAL, RICHARD E
          - generic [ref=e61]: House - MA-01 | DEM | $21,519,107
        - link "CLINTON, WILLIAM JEFFERSON President | DEM | $20,985,513" [ref=e62] [cursor=pointer]:
          - /url: /politicians/8dc4579f-7a76-c4bf-f62e-90985e629a23/
          - generic [ref=e63]: CLINTON, WILLIAM JEFFERSON
          - generic [ref=e64]: President | DEM | $20,985,513
        - link "CLYBURN, JAMES E House - SC-06 | DEM | $20,345,292" [ref=e65] [cursor=pointer]:
          - /url: /politicians/972721e4-173f-5925-b1c4-535ff83ffd0b/
          - generic [ref=e66]: CLYBURN, JAMES E
          - generic [ref=e67]: House - SC-06 | DEM | $20,345,292
        - link "BUSH, GEORGE President | REP | $19,728,671" [ref=e68] [cursor=pointer]:
          - /url: /politicians/e7801495-a90c-cca4-b927-e2c01302f203/
          - generic [ref=e69]: BUSH, GEORGE
          - generic [ref=e70]: President | REP | $19,728,671
        - link "MCCARTHY, KEVIN House - CA-20 | REP | $19,595,999" [ref=e71] [cursor=pointer]:
          - /url: /politicians/fb735f81-5e62-7ef2-55d3-99328c68dffe/
          - generic [ref=e72]: MCCARTHY, KEVIN
          - generic [ref=e73]: House - CA-20 | REP | $19,595,999
        - link "GRASSLEY, CHARLES E Senate - IA-00 | REP | $19,298,690" [ref=e74] [cursor=pointer]:
          - /url: /politicians/a0a0c2d7-d390-576a-065b-215948e1408b/
          - generic [ref=e75]: GRASSLEY, CHARLES E
          - generic [ref=e76]: Senate - IA-00 | REP | $19,298,690
        - link "MURRAY, PATTY Senate - WA-00 | DEM | $18,023,936" [ref=e77] [cursor=pointer]:
          - /url: /politicians/f210f66b-2da5-effc-6f6d-7c8d542a3018/
          - generic [ref=e78]: MURRAY, PATTY
          - generic [ref=e79]: Senate - WA-00 | DEM | $18,023,936
        - link "UPTON, FREDERICK STEPHEN House - MI-06 | REP | $17,789,780" [ref=e80] [cursor=pointer]:
          - /url: /politicians/796448fd-3ee5-3420-7ee3-b2f397cb270d/
          - generic [ref=e81]: UPTON, FREDERICK STEPHEN
          - generic [ref=e82]: House - MI-06 | REP | $17,789,780
        - link "BROWN, SHERROD Senate - OH-00 | DEM | $17,654,881" [ref=e83] [cursor=pointer]:
          - /url: /politicians/d8f3cc02-45cd-8e07-b8c7-a0fdd57fb7fb/
          - generic [ref=e84]: BROWN, SHERROD
          - generic [ref=e85]: Senate - OH-00 | DEM | $17,654,881
        - link "SCHUMER, CHARLES E. Senate - NY-00 | DEM | $17,030,277" [ref=e86] [cursor=pointer]:
          - /url: /politicians/3fb15bb3-d791-1f57-1cb7-409127c51411/
          - generic [ref=e87]: SCHUMER, CHARLES E.
          - generic [ref=e88]: Senate - NY-00 | DEM | $17,030,277
        - link "CASEY, ROBERT P. JR. Senate - PA-00 | DEM | $16,856,237" [ref=e89] [cursor=pointer]:
          - /url: /politicians/865439e2-6d93-7d19-fa94-c6de36d8b403/
          - generic [ref=e90]: CASEY, ROBERT P. JR.
          - generic [ref=e91]: Senate - PA-00 | DEM | $16,856,237
        - link "PERDUE, DAVID Senate - GA-00 | REP | $16,766,349" [ref=e92] [cursor=pointer]:
          - /url: /politicians/44087072-9125-0776-c672-a81d7ec95de6/
          - generic [ref=e93]: PERDUE, DAVID
          - generic [ref=e94]: Senate - GA-00 | REP | $16,766,349
        - link "COLLINS, SUSAN M. Senate - ME-00 | REP | $16,019,749" [ref=e95] [cursor=pointer]:
          - /url: /politicians/264e3be5-51a4-ea82-fdf4-5bd4d98dd5d9/
          - generic [ref=e96]: COLLINS, SUSAN M.
          - generic [ref=e97]: Senate - ME-00 | REP | $16,019,749
        - link "RUBIO, MARCO Senate - FL-00 | REP | $16,017,533" [ref=e98] [cursor=pointer]:
          - /url: /politicians/ab6bf360-388b-e006-acaa-17a45b4184d4/
          - generic [ref=e99]: RUBIO, MARCO
          - generic [ref=e100]: Senate - FL-00 | REP | $16,017,533
        - link "BOEHNER, JOHN A. House - OH-08 | REP | $15,936,813" [ref=e101] [cursor=pointer]:
          - /url: /politicians/f691ba17-df86-8a34-33c5-1086c5a5ee49/
          - generic [ref=e102]: BOEHNER, JOHN A.
          - generic [ref=e103]: House - OH-08 | REP | $15,936,813
        - link "HATCH, ORRIN G Senate - UT-00 | REP | $15,763,114" [ref=e104] [cursor=pointer]:
          - /url: /politicians/16091b04-e2b6-afce-c421-3eb15b5a8e7a/
          - generic [ref=e105]: HATCH, ORRIN G
          - generic [ref=e106]: Senate - UT-00 | REP | $15,763,114
        - link "PELOSI, NANCY House - CA-11 | DEM | $15,534,599" [ref=e107] [cursor=pointer]:
          - /url: /politicians/bfcafdd4-62da-f107-22fb-ff5413c33fa4/
          - generic [ref=e108]: PELOSI, NANCY
          - generic [ref=e109]: House - CA-11 | DEM | $15,534,599
        - link "SPECTER, ARLEN Senate - PA-00 | DEM | $15,516,359" [ref=e110] [cursor=pointer]:
          - /url: /politicians/ac444ee4-a581-d942-62ae-9a620be14321/
          - generic [ref=e111]: SPECTER, ARLEN
          - generic [ref=e112]: Senate - PA-00 | DEM | $15,516,359
        - link "SCALISE, STEVE MR House - LA-01 | REP | $15,486,740" [ref=e113] [cursor=pointer]:
          - /url: /politicians/45709efc-0864-5b11-b38e-d56ad0be1107/
          - generic [ref=e114]: SCALISE, STEVE MR
          - generic [ref=e115]: House - LA-01 | REP | $15,486,740
        - link "MCMORRIS RODGERS, CATHY House - WA-05 | REP | $15,415,457" [ref=e116] [cursor=pointer]:
          - /url: /politicians/86f4ffe9-3484-b620-89e2-eb1b2c6ae7a3/
          - generic [ref=e117]: MCMORRIS RODGERS, CATHY
          - generic [ref=e118]: House - WA-05 | REP | $15,415,457
        - link "NELSON, BILL Senate - FL-00 | DEM | $15,274,337" [ref=e119] [cursor=pointer]:
          - /url: /politicians/1e3a95c1-2210-01f9-3648-3581de7b7056/
          - generic [ref=e120]: NELSON, BILL
          - generic [ref=e121]: Senate - FL-00 | DEM | $15,274,337
        - link "BURR, RICHARD M Senate - NC-00 | REP | $15,226,656" [ref=e122] [cursor=pointer]:
          - /url: /politicians/1b22096d-cd65-6817-0c9a-35d81d1d5aa0/
          - generic [ref=e123]: BURR, RICHARD M
          - generic [ref=e124]: Senate - NC-00 | REP | $15,226,656
        - link "GUTHRIE, S. BRETT BRETT HON. House - KY-02 | REP | $15,215,261" [ref=e125] [cursor=pointer]:
          - /url: /politicians/6c4071ec-b193-f654-d8ef-1f95775d17cd/
          - generic [ref=e126]: GUTHRIE, S. BRETT BRETT HON.
          - generic [ref=e127]: House - KY-02 | REP | $15,215,261
        - link "GRAVES, SAMUEL B. JR. House - MO-06 | REP | $15,039,770" [ref=e128] [cursor=pointer]:
          - /url: /politicians/216af8b0-f136-06f7-32cc-a8bbe029c69f/
          - generic [ref=e129]: GRAVES, SAMUEL B. JR.
          - generic [ref=e130]: House - MO-06 | REP | $15,039,770
        - link "THUNE, JOHN R. Senate - SD-00 | REP | $14,928,843" [ref=e131] [cursor=pointer]:
          - /url: /politicians/943653f0-b72d-8ccd-6363-1512069d775f/
          - generic [ref=e132]: THUNE, JOHN R.
          - generic [ref=e133]: Senate - SD-00 | REP | $14,928,843
        - link "THOMPSON, MIKE MR. House - CA-04 | DEM | $14,859,377" [ref=e134] [cursor=pointer]:
          - /url: /politicians/4f62825b-6f6a-c85c-bac7-641dd0397f9f/
          - generic [ref=e135]: THOMPSON, MIKE MR.
          - generic [ref=e136]: House - CA-04 | DEM | $14,859,377
        - link "SESSIONS, PETE House - TX-17 | REP | $14,850,213" [ref=e137] [cursor=pointer]:
          - /url: /politicians/e73d7693-8ffd-8f14-40d3-26aa6c4b83a9/
          - generic [ref=e138]: SESSIONS, PETE
          - generic [ref=e139]: House - TX-17 | REP | $14,850,213
        - link "BRADY, KEVIN House - TX-08 | REP | $14,827,980" [ref=e140] [cursor=pointer]:
          - /url: /politicians/ac04e4e9-804e-2714-6e54-d8a0f575fcdf/
          - generic [ref=e141]: BRADY, KEVIN
          - generic [ref=e142]: House - TX-08 | REP | $14,827,980
        - link "DINGELL, JOHN D. MR. House - MI-12 | DEM | $14,768,209" [ref=e143] [cursor=pointer]:
          - /url: /politicians/cb3073ab-8fc7-3286-5ed2-cb384e799b80/
          - generic [ref=e144]: DINGELL, JOHN D. MR.
          - generic [ref=e145]: House - MI-12 | DEM | $14,768,209
        - link "DOLE, ROBERT J President | REP | $14,708,837" [ref=e146] [cursor=pointer]:
          - /url: /politicians/87a6346a-9617-56ef-38ce-faea8f36638a/
          - generic [ref=e147]: DOLE, ROBERT J
          - generic [ref=e148]: President | REP | $14,708,837
        - link "FEINSTEIN, DIANNE Senate - CA-00 | DEM | $14,688,507" [ref=e149] [cursor=pointer]:
          - /url: /politicians/308a7031-cb67-3510-4cc6-88666bc5b2b6/
          - generic [ref=e150]: FEINSTEIN, DIANNE
          - generic [ref=e151]: Senate - CA-00 | DEM | $14,688,507
        - link "BAUCUS, MAX Senate - MT-00 | DEM | $14,503,323" [ref=e152] [cursor=pointer]:
          - /url: /politicians/365eaaff-4fc2-0dcb-1dbf-5c824aa2ae5a/
          - generic [ref=e153]: BAUCUS, MAX
          - generic [ref=e154]: Senate - MT-00 | DEM | $14,503,323
        - link "GORE, AL President | DEM | $14,443,257" [ref=e155] [cursor=pointer]:
          - /url: /politicians/e35d80b7-6f5e-45e9-9493-2f8f8cb8499b/
          - generic [ref=e156]: GORE, AL
          - generic [ref=e157]: President | DEM | $14,443,257
        - link "WYDEN, RONALD L. Senate - OR-00 | DEM | $14,293,815" [ref=e158] [cursor=pointer]:
          - /url: /politicians/a62a533a-7d34-a658-3683-cb16957bb514/
          - generic [ref=e159]: WYDEN, RONALD L.
          - generic [ref=e160]: Senate - OR-00 | DEM | $14,293,815
        - link "TIBERI, PATRICK J. House - OH-12 | REP | $14,095,235" [ref=e161] [cursor=pointer]:
          - /url: /politicians/ef7f33e5-4520-942f-98a1-54134927bbf4/
          - generic [ref=e162]: TIBERI, PATRICK J.
          - generic [ref=e163]: House - OH-12 | REP | $14,095,235
        - link "SHIMKUS, JOHN M House - IL-15 | REP | $13,903,926" [ref=e164] [cursor=pointer]:
          - /url: /politicians/abecc483-1be3-f38c-76d1-015cc0f211f6/
          - generic [ref=e165]: SHIMKUS, JOHN M
          - generic [ref=e166]: House - IL-15 | REP | $13,903,926
        - link "LANDRIEU, MARY L Senate - LA-00 | DEM | $13,861,014" [ref=e167] [cursor=pointer]:
          - /url: /politicians/32ca234a-cff0-9480-953b-c525ff3139d4/
          - generic [ref=e168]: LANDRIEU, MARY L
          - generic [ref=e169]: Senate - LA-00 | DEM | $13,861,014
      - generic [ref=e170]:
        - generic [ref=e171]: Showing 1-50
        - generic [ref=e172]:
          - button "First" [disabled] [ref=e173]
          - button "Prev" [disabled] [ref=e174]
          - button "1" [ref=e175] [cursor=pointer]
          - button "Next" [ref=e176] [cursor=pointer]
  - main [ref=e177]
```

# Test source

```ts
  1   | import { test, expect, APIRequestContext } from '@playwright/test';
  2   | import { canonicalPath, getRandomItems, 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 and /districts/ lists .district-result behind a state
  21  | // filter; their sitemap tests cover them)
  22  | const LINKED_INDEXES = ['/candidates/', '/committees/', '/parties/', '/offices/', '/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: /candidates/ 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 |       const res = await request.get(`/${col}/no-such-entity-zz/`, { maxRedirects: 0 });
  110 |       expect(res.status(), `/${col}/no-such-entity-zz/ should serve the shell`).toBe(200);
  111 |       expect(res.headers()['content-type'] ?? '', `/${col}/ shell is HTML`).toContain('text/html');
  112 |     }
  113 |   });
  114 | });
  115 | 
```