# 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 >> committees sitemap - sample URLs resolve
- Location: tests/ui/sitemap.spec.ts:229:7

# Error details

```
Error: Errors: /committees/sitemap-committees.xml lists 83545 URLs (limit 50000)

expect(received).toHaveLength(expected)

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

# Page snapshot

```yaml
- generic [active] [ref=f5e1]:
  - banner [ref=f5e2]:
    - heading "MIKE FRANCE CT2" [level=1] [ref=f5e3]
    - paragraph [ref=f5e4]: House Campaign
    - paragraph [ref=f5e5]: "FEC ID: C00768689"
  - generic [ref=f5e6]:
    - heading "Overview" [level=2] [ref=f5e7]
    - generic [ref=f5e8]:
      - term [ref=f5e9]: Committee Type
      - definition [ref=f5e10]: House Campaign
      - term [ref=f5e11]: Designation
      - definition [ref=f5e12]: P
      - term [ref=f5e13]: Party Affiliation
      - definition [ref=f5e14]:
        - link "Republican Party" [ref=f5e15] [cursor=pointer]:
          - /url: https://us.elect.info/parties/republican-party/
      - term [ref=f5e16]: Treasurer
      - definition [ref=f5e17]: DATWYLER, THOMAS
      - term [ref=f5e18]: Location
      - definition [ref=f5e19]:
        - text: GALES FERRY,
        - link "Connecticut" [ref=f5e20] [cursor=pointer]:
          - /url: https://us.elect.info/states/connecticut/
        - text: "06335"
      - term [ref=f5e21]: Active Cycles
      - definition [ref=f5e22]:
        - link "2022" [ref=f5e23] [cursor=pointer]:
          - /url: https://us.elect.info/elections/2022/
        - text: ","
        - link "2024" [ref=f5e24] [cursor=pointer]:
          - /url: https://us.elect.info/elections/2024/
        - text: ","
        - link "2026" [ref=f5e25] [cursor=pointer]:
          - /url: https://us.elect.info/elections/2026/
  - generic [ref=f5e26]:
    - heading "Financial Summary" [level=2] [ref=f5e27]
    - generic [ref=f5e28]:
      - term [ref=f5e29]: Total Receipts
      - definition [ref=f5e30]:
        - strong [ref=f5e31]: $0
      - term [ref=f5e32]: Total Disbursements
      - definition [ref=f5e33]: $0
  - generic [ref=f5e34]:
    - heading "Connected Organizations" [level=2] [ref=f5e35]
    - generic [ref=f5e36]:
      - term [ref=f5e37]: Connected Organization
      - definition [ref=f5e38]: MIKE FRANCE VICTORY FUND
      - term [ref=f5e39]: Principal Candidate
      - definition [ref=f5e40]: H2CT02161
  - generic [ref=f5e41]:
    - heading "Candidates Supported" [level=2] [ref=f5e42]
    - paragraph [ref=f5e43]: "Candidates this committee officially supports:"
    - list [ref=f5e44]:
      - listitem [ref=f5e45]:
        - link "FRANCE, MIKE" [ref=f5e46] [cursor=pointer]:
          - /url: https://us.elect.info/candidates/house/ct-02/france-mike/
        - text: (2024)
      - listitem [ref=f5e47]:
        - link "FRANCE, MIKE" [ref=f5e48] [cursor=pointer]:
          - /url: https://us.elect.info/candidates/house/ct-02/france-mike/
        - text: (2022)
    - paragraph [ref=f5e49]: Showing 2 of 2
  - generic [ref=f5e50]:
    - generic [ref=f5e51]:
      - heading "Committees Funded" [level=2] [ref=f5e52]
      - group "View" [ref=f5e53]:
        - button "Sankey view" [pressed] [ref=f5e54] [cursor=pointer]
        - button "Table view" [ref=f5e58] [cursor=pointer]
    - paragraph [ref=f5e62]: "Committees receiving transfers from this committee:"
    - img "Committees Funded as a Sankey diagram. Switch to table view for the list." [ref=f5e63]
    - paragraph [ref=f5e66]: Showing 20 of 20
  - generic [ref=f5e67]:
    - generic [ref=f5e68]:
      - heading "Funded By" [level=2] [ref=f5e69]
      - group "View" [ref=f5e70]:
        - button "Sankey view" [pressed] [ref=f5e71] [cursor=pointer]
        - button "Table view" [ref=f5e75] [cursor=pointer]
    - paragraph [ref=f5e79]: "Committees that have transferred funds to this committee:"
    - img "Funded By as a Sankey diagram. Switch to table view for the list." [ref=f5e80]
    - paragraph [ref=f5e83]: Showing 5 of 5
  - contentinfo [ref=f5e84]:
    - paragraph [ref=f5e85]:
      - link "elect.info" [ref=f5e86] [cursor=pointer]:
        - /url: https://us.elect.info/
      - text: "| Data from"
      - link "Federal Election Commission" [ref=f5e87] [cursor=pointer]:
        - /url: https://www.fec.gov/
      - text: "| Generated 2026-01-16"
```

# Test source

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