# 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/entities.spec.ts >> us.elect.info entity sections - additional index + detail >> tracts section - state/county filter and random detail pages
- Location: tests/ui/entities.spec.ts:604:7

# Error details

```
Error: page.goto: net::ERR_CONNECTION_REFUSED at http://10.1.74.226/tracts/
Call log:
  - navigating to "http://10.1.74.226/tracts/", waiting until "load"

```

# Test source

```ts
  506 | 
  507 |     // Wait for results to load
  508 |     await page.waitForSelector('.place-result, .place-error', { timeout: DATA_LOAD_TIMEOUT });
  509 |     const results = await page.locator('.place-result').all();
  510 |     expect(results.length).toBeGreaterThan(0);
  511 | 
  512 |     // Pick random results and visit detail pages
  513 |     const hrefs = await Promise.all(results.slice(0, 20).map(r => r.getAttribute('href')));
  514 |     const randomLinks = getRandomItems(hrefs.filter(Boolean) as string[], 3);
  515 | 
  516 |     for (const href of randomLinks) {
  517 |       const entityResponse = await page.goto(toRelativePath(href));
  518 |       expect(entityResponse?.status(), `${toRelativePath(href)} should answer 200`).toBe(200);
  519 |       await expect(page.locator('h1')).toBeVisible();
  520 |     }
  521 |   });
  522 | 
  523 |   test('postalcodes section - index and random detail pages', async ({ page }) => {
  524 |     await page.goto('/postalcodes/');
  525 | 
  526 |     // Map view is default — verify Leaflet map renders with GeoJSON state shapes
  527 |     await expect(page.locator('#map.leaflet-container')).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
  528 |     await expect(page.locator('#map .leaflet-interactive').first()).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
  529 |     const shapes = await page.locator('#map .leaflet-interactive').all();
  530 |     expect(shapes.length).toBeGreaterThan(0);
  531 | 
  532 |     // Switch to List view to test postal code results and detail navigation
  533 |     await page.locator('.toggle-btn[data-view="list"]').click();
  534 | 
  535 |     // Wait for results to load
  536 |     await page.waitForSelector('.postal-result, .postal-error', { timeout: DATA_LOAD_TIMEOUT });
  537 |     const results = await page.locator('.postal-result').all();
  538 |     expect(results.length).toBeGreaterThan(0);
  539 | 
  540 |     // Pick random results and visit detail pages
  541 |     const hrefs = await Promise.all(results.slice(0, 20).map(r => r.getAttribute('href')));
  542 |     const randomLinks = getRandomItems(hrefs.filter(Boolean) as string[], 3);
  543 | 
  544 |     for (const href of randomLinks) {
  545 |       const entityResponse = await page.goto(toRelativePath(href));
  546 |       expect(entityResponse?.status(), `${toRelativePath(href)} should answer 200`).toBe(200);
  547 |       await expect(page.locator('h1')).toBeVisible();
  548 |     }
  549 |   });
  550 | 
  551 |   test('precincts section - map view and state filter', async ({ page }) => {
  552 |     await page.goto('/precincts/');
  553 | 
  554 |     // Map view is default — verify Leaflet map renders with GeoJSON state shapes
  555 |     await expect(page.locator('#map.leaflet-container')).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
  556 |     await expect(page.locator('#map .leaflet-interactive').first()).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
  557 |     const shapes = await page.locator('#map .leaflet-interactive').all();
  558 |     expect(shapes.length).toBeGreaterThan(0);
  559 | 
  560 |     // Switch to List view to test state filter and precinct results
  561 |     await page.locator('.toggle-btn[data-view="list"]').click();
  562 | 
  563 |     // Select a state first (precincts require state selection)
  564 |     const stateSelect = page.locator('#precinct-state-select');
  565 |     await expect(stateSelect).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
  566 |     await stateSelect.selectOption('TX');
  567 | 
  568 |     // Wait for precinct results to load
  569 |     await page.waitForSelector('.precinct-result, .precinct-error', { timeout: DATA_LOAD_TIMEOUT });
  570 |     const results = await page.locator('.precinct-result').all();
  571 |     expect(results.length).toBeGreaterThan(0);
  572 | 
  573 |     // pct_dem_lead is stored as a ratio; browse cards must show percentage points.
  574 |     // Votes and margin are read from the same card in one evaluate: the list can
  575 |     // re-render between separate reads, which compared one card's votes with
  576 |     // another card's margin (flaky in preview-e2e-full-dq8n6, 2026-10-12). Polled,
  577 |     // so a list still settling is retried and a real mismatch still fails.
  578 |     const first = page.locator('.precinct-result').first();
  579 |     await expect.poll(async () => first.evaluate((card) => {
  580 |       const votes = (sel: string) => Number(card.querySelector(sel)?.textContent?.replace(/[^0-9]/g, '') || 0);
  581 |       const dem = votes('.precinct-vote-dem');
  582 |       const rep = votes('.precinct-vote-rep');
  583 |       const total = votes('.precinct-vote-total');
  584 |       const margin = total ? (dem - rep) / total * 100 : 0;
  585 |       const expected = margin === 0 ? 'Tie' : `${margin > 0 ? 'D' : 'R'}+${Math.abs(margin).toFixed(1)}%`;
  586 |       const shown = card.querySelector('.precinct-margin')?.textContent?.trim() ?? '';
  587 |       return shown === expected ? 'consistent' : `card shows ${shown}, votes give ${expected}`;
  588 |     }), { timeout: 10000, message: 'precinct card margin matches its votes' }).toBe('consistent');
  589 | 
  590 |     // Pick random results and visit detail pages
  591 |     const hrefs = await Promise.all(results.slice(0, 20).map(r => r.getAttribute('href')));
  592 |     const validHrefs = hrefs.filter(Boolean) as string[];
  593 | 
  594 |     if (validHrefs.length > 0) {
  595 |       const randomLinks = getRandomItems(validHrefs, 3);
  596 |       for (const href of randomLinks) {
  597 |         const entityResponse = await page.goto(toRelativePath(href));
  598 |         expect(entityResponse?.status(), `${toRelativePath(href)} should answer 200`).toBe(200);
  599 |         await expect(page.locator('h1')).toBeVisible();
  600 |       }
  601 |     }
  602 |   });
  603 | 
  604 |   test('tracts section - state/county filter and random detail pages', async ({ page }) => {
  605 |     test.setTimeout(120000); // Tracts require sequential state→county→tract interactions with GraphQL
> 606 |     await page.goto('/tracts/');
      |                ^ Error: page.goto: net::ERR_CONNECTION_REFUSED at http://10.1.74.226/tracts/
  607 | 
  608 |     // Switch to List view — tract-index.js loads dynamically on list view activation
  609 |     await page.locator('.toggle-btn[data-view="list"]').click();
  610 | 
  611 |     // Wait for state dropdown to populate (tract-index.js loads states via GraphQL)
  612 |     const stateSelect = page.locator('#tract-state-select');
  613 |     await page.waitForFunction(() => {
  614 |       const select = document.querySelector('#tract-state-select') as HTMLSelectElement;
  615 |       return select && select.options.length > 1;
  616 |     }, { timeout: 45000 });
  617 | 
  618 |     // Select Texas
  619 |     await stateSelect.selectOption('TX');
  620 | 
  621 |     // Wait for county dropdown to populate
  622 |     const countySelect = page.locator('#tract-county-select');
  623 |     await expect(countySelect).toBeEnabled({ timeout: DATA_LOAD_TIMEOUT });
  624 |     await page.waitForFunction(() => {
  625 |       const select = document.querySelector('#tract-county-select') as HTMLSelectElement;
  626 |       return select && select.options.length > 1;
  627 |     }, { timeout: DATA_LOAD_TIMEOUT });
  628 | 
  629 |     // Select first county
  630 |     const options = await countySelect.locator('option').all();
  631 |     if (options.length > 1) {
  632 |       const value = await options[1].getAttribute('value');
  633 |       if (value) await countySelect.selectOption(value);
  634 |     }
  635 | 
  636 |     // Wait for tract results to load
  637 |     await page.waitForSelector('.tract-result, .tract-error', { timeout: DATA_LOAD_TIMEOUT });
  638 |     const results = await page.locator('.tract-result').all();
  639 |     expect(results.length).toBeGreaterThan(0);
  640 | 
  641 |     // Pick random results and visit detail pages
  642 |     const hrefs = await Promise.all(results.slice(0, 20).map(r => r.getAttribute('href')));
  643 |     const validHrefs = hrefs.filter(Boolean) as string[];
  644 | 
  645 |     if (validHrefs.length > 0) {
  646 |       const randomLinks = getRandomItems(validHrefs, 3);
  647 |       for (const href of randomLinks) {
  648 |         const entityResponse = await page.goto(toRelativePath(href));
  649 |         expect(entityResponse?.status(), `${toRelativePath(href)} should answer 200`).toBe(200);
  650 |         await expect(page.locator('h1')).toBeVisible();
  651 |       }
  652 |     }
  653 |   });
  654 | 
  655 | });
  656 | 
  657 | test.describe('us.elect.info entity sections - individuals (premium)', () => {
  658 |   test.use({
  659 |     httpCredentials: hasAuthCredentials ? {
  660 |       username: process.env.PLAYWRIGHT_AUTH_USER!,
  661 |       password: process.env.PLAYWRIGHT_AUTH_PASS || '',
  662 |     } : undefined
  663 |   });
  664 | 
  665 |   test('individuals section - index and random pages', async ({ page, baseURL }) => {
  666 |     test.skip(!hasAuthCredentials, 'Requires PLAYWRIGHT_AUTH_USER/PLAYWRIGHT_AUTH_PASS');
  667 |     await premiumVisitor(page.context(), baseURL);
  668 | 
  669 |     const indexResponse = await page.goto('/individuals/');
  670 |     expect(indexResponse?.status(), '/individuals/ should answer 200').toBe(200);
  671 |     await expect(page.locator('h1')).toContainText(/Individual/i);
  672 | 
  673 |     await expect(page.locator('.entity-search-result').first()).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
  674 | 
  675 |     const links = await page.locator('.entity-search-result').all();
  676 |     expect(links.length).toBeGreaterThan(0);
  677 | 
  678 |     const hrefs = await Promise.all(links.slice(0, 20).map(l => l.getAttribute('href')));
  679 |     const randomLinks = getRandomItems(hrefs.filter(Boolean) as string[], 3);
  680 | 
  681 |     for (const href of randomLinks) {
  682 |       const entityResponse = await page.goto(toRelativePath(href));
  683 |       expect(entityResponse?.status(), `${toRelativePath(href)} should answer 200`).toBe(200);
  684 |       // Search hits must carry graph Individual ids: the detail page renders
  685 |       // "Individual Not Found" (still a 200 shell) for any other id.
  686 |       await expect(page.locator('.leaf-detail-page h1')).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
  687 |       await expect(page.locator('.leaf-detail-page h1')).not.toHaveText(/Not Found/i);
  688 |     }
  689 |   });
  690 | });
  691 | 
```