# 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/sitemap.spec.ts >> sitemap URL validation >> candidates sitemap - sample URLs resolve
- Location: tests/ui/sitemap.spec.ts:235:7

# Error details

```
Error: Errors: /candidates/sitemap-candidates.xml lists 50975 URLs (limit 50000)

expect(received).toHaveLength(expected)

Expected length: 0
Received length: 1
Received array:  ["/candidates/sitemap-candidates.xml lists 50975 URLs (limit 50000)"]
```

# Page snapshot

```yaml
- generic [active] [ref=f5e1]:
  - banner [ref=f5e2]:
    - heading "PITTMAN, LEROY JAMES III" [level=1] [ref=f5e3]
    - paragraph [ref=f5e4]: Republican Party candidate for U.S. Representative - North Carolina District 08
    - paragraph [ref=f5e5]: "FEC ID: 0b3a2b76-b2ad-81ec-8b6b-4970a9480bcd"
  - generic [ref=f5e6]:
    - heading "Profile" [level=2] [ref=f5e7]
    - generic [ref=f5e8]:
      - term [ref=f5e9]: Party
      - definition [ref=f5e10]:
        - link "Republican Party" [ref=f5e11] [cursor=pointer]:
          - /url: https://us.elect.info/parties/republican/
      - term [ref=f5e12]: Office
      - definition [ref=f5e13]:
        - link "U.S. Representative - North Carolina District 08" [ref=f5e14] [cursor=pointer]:
          - /url: https://us.elect.info/offices/house/nc-08/
      - term [ref=f5e15]: District
      - definition [ref=f5e16]:
        - link "North Carolina District 08" [ref=f5e17] [cursor=pointer]:
          - /url: https://us.elect.info/districts/nc/08/
      - term [ref=f5e18]: Status
      - definition [ref=f5e19]: Prior Candidate (Not Current) (Challenger)
      - term [ref=f5e20]: Home Location
      - definition [ref=f5e21]: MONROE, North Carolina 28110
  - generic [ref=f5e22]:
    - heading "Campaign Timeline" [level=2] [ref=f5e23]
    - generic [ref=f5e24]:
      - term [ref=f5e25]: Election Years
      - definition [ref=f5e26]:
        - generic [ref=f5e27]:
          - link "2000" [ref=f5e28] [cursor=pointer]:
            - /url: https://us.elect.info/elections/2000/
          - text: ","
          - link "1998" [ref=f5e29] [cursor=pointer]:
            - /url: https://us.elect.info/elections/1998/
          - text: ","
          - link "1996" [ref=f5e30] [cursor=pointer]:
            - /url: https://us.elect.info/elections/1996/
      - term [ref=f5e31]: First Campaign
      - definition [ref=f5e32]: "1996"
      - term [ref=f5e33]: Most Recent
      - definition [ref=f5e34]: "1996"
  - generic [ref=f5e35]:
    - heading "Campaign Funding" [level=2] [ref=f5e36]
    - generic [ref=f5e37]:
      - term [ref=f5e38]: Total Received
      - definition [ref=f5e39]:
        - strong [ref=f5e40]: $16K
      - term [ref=f5e41]: Contributing Committees
      - definition [ref=f5e42]: "12"
    - heading "Top Contributors" [level=3] [ref=f5e43]
    - table [ref=f5e44]:
      - rowgroup [ref=f5e45]:
        - row [ref=f5e46]:
          - columnheader "Committee" [ref=f5e47]
          - columnheader "Amount" [ref=f5e48]
          - columnheader "Contributions" [ref=f5e49]
          - columnheader "Cycles" [ref=f5e50]
      - rowgroup [ref=f5e51]:
        - row [ref=f5e52]:
          - cell [ref=f5e53]:
            - link "NATIONAL FEDERATION OF INDEPENDENT BUSINESS FEDERAL POLITICAL ACTION COMMITTEE" [ref=f5e54] [cursor=pointer]:
              - /url: https://us.elect.info/committees/pac-qualified/national-federation-of-independent-business-federal-political-action-committee-51c9476f-b6e3-a33d-6844-edd708a96a8d/
          - cell "$5K" [ref=f5e55]
          - cell "2" [ref=f5e56]
          - cell [ref=f5e57]:
            - link "1996" [ref=f5e59] [cursor=pointer]:
              - /url: https://us.elect.info/elections/1996/
        - row [ref=f5e60]:
          - cell [ref=f5e61]:
            - link "SIXTY VOTES COALITION" [ref=f5e62] [cursor=pointer]:
              - /url: https://us.elect.info/committees/pac-qualified/sixty-votes-coalition-87567665-7f7d-e1fb-db10-53530bbbe5f7/
          - cell "$3K" [ref=f5e63]
          - cell "1" [ref=f5e64]
          - cell [ref=f5e65]:
            - link "1996" [ref=f5e67] [cursor=pointer]:
              - /url: https://us.elect.info/elections/1996/
        - row [ref=f5e68]:
          - cell [ref=f5e69]:
            - link "EAGLE FORUM PAC" [ref=f5e70] [cursor=pointer]:
              - /url: https://us.elect.info/committees/pac-qualified/eagle-forum-pac-e5690d42-cc40-d16f-3b47-65a69cbf8847/
          - cell "$3K" [ref=f5e71]
          - cell "1" [ref=f5e72]
          - cell [ref=f5e73]:
            - link "1996" [ref=f5e75] [cursor=pointer]:
              - /url: https://us.elect.info/elections/1996/
        - row [ref=f5e76]:
          - cell [ref=f5e77]:
            - link "REPUBLICAN NATIONAL COALITION FOR LIFE POLITICAL ACTION COMMITTEE" [ref=f5e78] [cursor=pointer]:
              - /url: https://us.elect.info/committees/pac-qualified/republican-national-coalition-for-life-political-action-committee-219e19f5-e8d5-94f5-96ef-5a586d221c07/
          - cell "$2K" [ref=f5e79]
          - cell "1" [ref=f5e80]
          - cell [ref=f5e81]:
            - link "1996" [ref=f5e83] [cursor=pointer]:
              - /url: https://us.elect.info/elections/1996/
        - row [ref=f5e84]:
          - cell [ref=f5e85]:
            - link "FAITH FAMILY & FREEDOM PAC" [ref=f5e86] [cursor=pointer]:
              - /url: https://us.elect.info/committees/pac-qualified/faith-family-freedom-pac-f2a8e69b-4977-23ae-6080-52ba89dbc31b/
          - cell "$1K" [ref=f5e87]
          - cell "1" [ref=f5e88]
          - cell [ref=f5e89]:
            - link "1996" [ref=f5e91] [cursor=pointer]:
              - /url: https://us.elect.info/elections/1996/
        - row [ref=f5e92]:
          - cell [ref=f5e93]:
            - link "CITIZENS ALLIED FOR FREE ENTERPRISE" [ref=f5e94] [cursor=pointer]:
              - /url: https://us.elect.info/committees/pac-qualified/citizens-allied-for-free-enterprise-62113425-614f-156f-4fe3-45a6f6b9b073/
          - cell "$1K" [ref=f5e95]
          - cell "3" [ref=f5e96]
          - cell [ref=f5e97]:
            - link "1996" [ref=f5e99] [cursor=pointer]:
              - /url: https://us.elect.info/elections/1996/
        - row [ref=f5e100]:
          - cell [ref=f5e101]:
            - link "AMERICAN FREE ENTERPRISE PAC" [ref=f5e102] [cursor=pointer]:
              - /url: https://us.elect.info/committees/pac-qualified/american-free-enterprise-pac-13d21d7f-3d47-2086-e385-3f1728deafcd/
          - cell "$1K" [ref=f5e103]
          - cell "1" [ref=f5e104]
          - cell [ref=f5e105]:
            - link "1996" [ref=f5e107] [cursor=pointer]:
              - /url: https://us.elect.info/elections/1996/
        - row [ref=f5e108]:
          - cell [ref=f5e109]:
            - link "WACHOVIA BANK N.A. NORTH CAROLINA EMPLOYEES POLITICAL ACTION COMMITTEE" [ref=f5e110] [cursor=pointer]:
              - /url: https://us.elect.info/committees/pac-qualified/wachovia-bank-na-north-carolina-employees-political-action-committee-8c0c08d8-4f40-c209-d3aa-c832eef557e1/
          - cell "$200" [ref=f5e111]
          - cell "1" [ref=f5e112]
          - cell [ref=f5e113]:
            - link "1996" [ref=f5e115] [cursor=pointer]:
              - /url: https://us.elect.info/elections/1996/
        - row [ref=f5e116]:
          - cell [ref=f5e117]:
            - link "NATIONAL FEDERATION OF INDEPENDENT BUSINESS" [ref=f5e118] [cursor=pointer]:
              - /url: https://us.elect.info/committees/communication-cost/national-federation-of-independent-business-22f00329-6a76-668f-71f9-e3506bbeba97/
          - cell "$167" [ref=f5e119]
          - cell "1" [ref=f5e120]
          - cell [ref=f5e121]:
            - link "1996" [ref=f5e123] [cursor=pointer]:
              - /url: https://us.elect.info/elections/1996/
  - generic [ref=f5e124]:
    - heading "Supporting Committees" [level=2] [ref=f5e125]
    - generic [ref=f5e126]:
      - term [ref=f5e127]: Principal Committee
      - definition [ref=f5e128]: C00303800
    - heading "Other Supporting Committees" [level=3] [ref=f5e129]
    - list [ref=f5e130]:
      - listitem [ref=f5e131]:
        - link "LEROY PITTMAN FOR U S CONGRESS" [ref=f5e132] [cursor=pointer]:
          - /url: https://us.elect.info/committees/house/leroy-pittman-for-u-s-congress-2b11ce6f-0ab4-5c67-7154-5717a1fea24c/
        - generic [ref=f5e133]: (House Campaign)
  - contentinfo [ref=f5e134]:
    - paragraph [ref=f5e135]:
      - link "elect.info" [ref=f5e136] [cursor=pointer]:
        - /url: https://us.elect.info/
      - text: "| Data from"
      - link "Federal Election Commission" [ref=f5e137] [cursor=pointer]:
        - /url: https://www.fec.gov/
      - text: "| Generated 2026-02-09"
```

# Test source

```ts
  151 |     console.log(`${indent}Detected sitemap index, sampling ${CHILD_SITEMAP_SAMPLE_SIZE} child sitemaps`);
  152 | 
  153 |     const childSitemaps = getRandomItems(urls, CHILD_SITEMAP_SAMPLE_SIZE, sitemapPath);
  154 |     for (const childUrl of childSitemaps) {
  155 |       const childPath = toRelativePath(childUrl);
  156 |       console.log(`${indent}Processing child sitemap: ${childPath}`);
  157 | 
  158 |       const childResult = await testSitemapUrls(page, baseUrl, childPath, depth + 1);
  159 |       result.tested += childResult.tested;
  160 |       result.errors.push(...childResult.errors);
  161 |     }
  162 |   } else {
  163 |     // This is a regular sitemap with page URLs - sample and test
  164 |     const sampleUrls = getRandomItems(urls, SAMPLE_SIZE, sitemapPath);
  165 | 
  166 |     for (const url of sampleUrls) {
  167 |       const path = toRelativePath(url);
  168 | 
  169 |       // Check if this URL is itself a sitemap (nested sitemap within urlset)
  170 |       if (isSitemapUrl(url)) {
  171 |         console.log(`${indent}Found nested sitemap URL: ${path}`);
  172 |         const nestedResult = await testSitemapUrls(page, baseUrl, path, depth + 1);
  173 |         result.tested += nestedResult.tested;
  174 |         result.errors.push(...nestedResult.errors);
  175 |       } else {
  176 |         // Regular page URL - verify it loads
  177 |         console.log(`${indent}Testing page: ${path}`);
  178 |         const pageResponse = await page.goto(path, { timeout: PAGE_TIMEOUT });
  179 |         result.tested++;
  180 | 
  181 |         if (pageResponse?.status() !== 200) {
  182 |           const error = `Expected 200 for ${path}, got ${pageResponse?.status()}`;
  183 |           console.log(`${indent}  ERROR: ${error}`);
  184 |           result.errors.push(error);
  185 |         } else {
  186 |           // Verify page has content
  187 |           const h1Visible = await page.locator('h1').isVisible().catch(() => false);
  188 |           if (!h1Visible) {
  189 |             const error = `Page ${path} loaded but has no visible h1`;
  190 |             console.log(`${indent}  WARNING: ${error}`);
  191 |             // Don't treat missing h1 as hard failure, just log it
  192 |           }
  193 |         }
  194 |       }
  195 |     }
  196 |   }
  197 | 
  198 |   console.log(`${indent}Tested ${result.tested} URLs from ${sitemapPath}`);
  199 |   return result;
  200 | }
  201 | 
  202 | test.describe('sitemap URL validation', () => {
  203 | 
  204 |   test('sitemap index loads and contains child sitemaps', async ({ page }) => {
  205 |     const response = await page.goto('/sitemap.xml');
  206 |     expect(response?.status()).toBe(200);
  207 | 
  208 |     const content = await page.content();
  209 |     expect(content).toContain('sitemapindex');
  210 |     // Candidates and committees are listed per lead character of the slug (rundeck Sitemaps
  211 |     // LeadBuckets), not through their per-collection index: an index inside an index is not followed.
  212 |     for (const collection of ['candidates', 'committees']) {
  213 |       for (const bucket of LEAD_BUCKETS) {
  214 |         expect(content).toContain(`/${collection}/sitemap-${collection}-${bucket}.xml`);
  215 |       }
  216 |       expect(content).not.toContain(`/${collection}/sitemap-${collection}.xml`);
  217 |     }
  218 |   });
  219 | 
  220 |   test('sitemap index lists no nested index', async ({ request }) => {
  221 |     const index = await (await request.get('/sitemap.xml')).text();
  222 |     const children = parseUrlsFromSitemap(index).map(u => new URL(u).pathname);
  223 |     expect(children.length).toBeGreaterThan(0);
  224 |     const nested: string[] = [];
  225 |     for (const path of children) {
  226 |       // The first bytes say urlset or sitemapindex; the bucket files run to megabytes.
  227 |       const res = await request.get(path, { headers: { Range: 'bytes=0-1023' } });
  228 |       if (res.status() === 404 && !STRICT) continue;
  229 |       expect([200, 206], `${path} should load`).toContain(res.status());
  230 |       if (!(await res.text()).includes('<urlset')) nested.push(path);
  231 |     }
  232 |     expect(nested, 'children of /sitemap.xml that are not urlsets').toEqual([]);
  233 |   });
  234 | 
  235 |   test('candidates sitemap - sample URLs resolve', async ({ page }) => {
  236 |     test.setTimeout(120000);
  237 | 
  238 |     // Navigate to a simple page first so we have a page context for fetch
  239 |     await page.goto('/');
  240 |     const baseUrl = new URL(page.url()).origin;
  241 | 
  242 |     const result = await testSitemapUrls(page, baseUrl, '/candidates/sitemap-candidates.xml');
  243 | 
  244 |     if (result.tested === 0 && !STRICT) {
  245 |       test.skip();
  246 |       return;
  247 |     }
  248 |     expect(result.tested, `sitemap sampled no URLs; errors: ${result.errors.join(', ')}`).toBeGreaterThan(0);
  249 | 
  250 |     console.log(`Tested ${result.tested} URLs from candidates sitemap`);
> 251 |     expect(result.errors, `Errors: ${result.errors.join(', ')}`).toHaveLength(0);
      |                                                                  ^ Error: Errors: /candidates/sitemap-candidates.xml lists 50975 URLs (limit 50000)
  252 |   });
  253 | 
  254 |   test('committees sitemap - sample URLs resolve', async ({ page }) => {
  255 |     test.setTimeout(120000);
  256 | 
  257 |     // Navigate to a simple page first so we have a page context for fetch
  258 |     await page.goto('/');
  259 |     const baseUrl = new URL(page.url()).origin;
  260 | 
  261 |     const result = await testSitemapUrls(page, baseUrl, '/committees/sitemap-committees.xml');
  262 | 
  263 |     if (result.tested === 0 && !STRICT) {
  264 |       test.skip();
  265 |       return;
  266 |     }
  267 |     expect(result.tested, `sitemap sampled no URLs; errors: ${result.errors.join(', ')}`).toBeGreaterThan(0);
  268 | 
  269 |     console.log(`Tested ${result.tested} URLs from committees sitemap`);
  270 |     expect(result.errors, `Errors: ${result.errors.join(', ')}`).toHaveLength(0);
  271 |   });
  272 | 
  273 |   test('parties sitemap - sample URLs resolve', async ({ page }) => {
  274 |     await page.goto('/');
  275 |     const baseUrl = new URL(page.url()).origin;
  276 | 
  277 |     const result = await testSitemapUrls(page, baseUrl, '/parties/sitemap-parties.xml');
  278 | 
  279 |     if (result.tested === 0 && !STRICT) {
  280 |       test.skip();
  281 |       return;
  282 |     }
  283 |     expect(result.tested, `sitemap sampled no URLs; errors: ${result.errors.join(', ')}`).toBeGreaterThan(0);
  284 | 
  285 |     console.log(`Tested ${result.tested} URLs from parties sitemap`);
  286 |     expect(result.errors, `Errors: ${result.errors.join(', ')}`).toHaveLength(0);
  287 |   });
  288 | 
  289 |   test('offices sitemap - sample URLs resolve', async ({ page }) => {
  290 |     await page.goto('/');
  291 |     const baseUrl = new URL(page.url()).origin;
  292 | 
  293 |     const result = await testSitemapUrls(page, baseUrl, '/offices/sitemap-offices.xml');
  294 | 
  295 |     if (result.tested === 0 && !STRICT) {
  296 |       test.skip();
  297 |       return;
  298 |     }
  299 |     expect(result.tested, `sitemap sampled no URLs; errors: ${result.errors.join(', ')}`).toBeGreaterThan(0);
  300 | 
  301 |     console.log(`Tested ${result.tested} URLs from offices sitemap`);
  302 |     expect(result.errors, `Errors: ${result.errors.join(', ')}`).toHaveLength(0);
  303 |   });
  304 | 
  305 |   test('districts sitemap - sample URLs resolve', async ({ page }) => {
  306 |     await page.goto('/');
  307 |     const baseUrl = new URL(page.url()).origin;
  308 | 
  309 |     const result = await testSitemapUrls(page, baseUrl, '/districts/sitemap-districts.xml');
  310 | 
  311 |     if (result.tested === 0 && !STRICT) {
  312 |       test.skip();
  313 |       return;
  314 |     }
  315 |     expect(result.tested, `sitemap sampled no URLs; errors: ${result.errors.join(', ')}`).toBeGreaterThan(0);
  316 | 
  317 |     console.log(`Tested ${result.tested} URLs from districts sitemap`);
  318 |     expect(result.errors, `Errors: ${result.errors.join(', ')}`).toHaveLength(0);
  319 |   });
  320 | 
  321 |   test('states sitemap - sample URLs resolve', async ({ page }) => {
  322 |     await page.goto('/');
  323 |     const baseUrl = new URL(page.url()).origin;
  324 | 
  325 |     const result = await testSitemapUrls(page, baseUrl, '/states/sitemap-states.xml');
  326 | 
  327 |     if (result.tested === 0 && !STRICT) {
  328 |       test.skip();
  329 |       return;
  330 |     }
  331 |     expect(result.tested, `sitemap sampled no URLs; errors: ${result.errors.join(', ')}`).toBeGreaterThan(0);
  332 | 
  333 |     console.log(`Tested ${result.tested} URLs from states sitemap`);
  334 |     expect(result.errors, `Errors: ${result.errors.join(', ')}`).toHaveLength(0);
  335 |   });
  336 | 
  337 |   test('elections sitemap - sample URLs resolve', async ({ page }) => {
  338 |     await page.goto('/');
  339 |     const baseUrl = new URL(page.url()).origin;
  340 | 
  341 |     const result = await testSitemapUrls(page, baseUrl, '/elections/sitemap-elections.xml');
  342 | 
  343 |     if (result.tested === 0 && !STRICT) {
  344 |       test.skip();
  345 |       return;
  346 |     }
  347 |     expect(result.tested, `sitemap sampled no URLs; errors: ${result.errors.join(', ')}`).toBeGreaterThan(0);
  348 | 
  349 |     console.log(`Tested ${result.tested} URLs from elections sitemap`);
  350 |     expect(result.errors, `Errors: ${result.errors.join(', ')}`).toHaveLength(0);
  351 |   });
```