# 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 >> places section - index and random detail pages
- Location: tests/ui/entities.spec.ts:492:7

# Error details

```
Test timeout of 30000ms exceeded.
```

```
Error: page.goto: net::ERR_ABORTED; maybe frame was detached?
Call log:
  - navigating to "http://10.1.105.57/places/", waiting until "load"

```

# Test source

```ts
  393 |       expect(scores[i - 1]).toBeGreaterThanOrEqual(scores[i]);
  394 |     }
  395 |   });
  396 | 
  397 |   test('vendors sorted by total received descending', async ({ page }) => {
  398 |     const scores = await getBrowseScores(page, '/vendors/');
  399 |     expect(scores.length).toBeGreaterThan(1);
  400 |     for (let i = 1; i < scores.length; i++) {
  401 |       expect(scores[i - 1]).toBeGreaterThanOrEqual(scores[i]);
  402 |     }
  403 |   });
  404 | 
  405 |   test('individuals sorted by total contributions descending', async ({ page }) => {
  406 |     const scores = await getBrowseScores(page, '/individuals/');
  407 |     expect(scores.length).toBeGreaterThan(1);
  408 |     for (let i = 1; i < scores.length; i++) {
  409 |       expect(scores[i - 1]).toBeGreaterThanOrEqual(scores[i]);
  410 |     }
  411 |   });
  412 | 
  413 |   test('employers sorted by total employee contributions descending', async ({ page }) => {
  414 |     const scores = await getBrowseScores(page, '/employers/');
  415 |     expect(scores.length).toBeGreaterThan(1);
  416 |     for (let i = 1; i < scores.length; i++) {
  417 |       expect(scores[i - 1]).toBeGreaterThanOrEqual(scores[i]);
  418 |     }
  419 |   });
  420 | });
  421 | 
  422 | test.describe('us.elect.info entity sections - additional index + detail', () => {
  423 | 
  424 |   test('employers section - index and random pages', async ({ page }) => {
  425 |     const indexResponse = await page.goto('/employers/');
  426 |     expect(indexResponse?.status(), '/employers/ should answer 200').toBe(200);
  427 |     await expect(page.locator('h1')).toContainText(/Employers/i);
  428 | 
  429 |     await expect(page.locator('.entity-search-result').first()).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
  430 | 
  431 |     const links = await page.locator('.entity-search-result').all();
  432 |     expect(links.length).toBeGreaterThan(0);
  433 | 
  434 |     const hrefs = await Promise.all(links.slice(0, 20).map(l => l.getAttribute('href')));
  435 |     const randomLinks = getRandomItems(hrefs.filter(Boolean) as string[], 3);
  436 | 
  437 |     for (const href of randomLinks) {
  438 |       const entityResponse = await page.goto(toRelativePath(href));
  439 |       expect(entityResponse?.status(), `${toRelativePath(href)} should answer 200`).toBe(200);
  440 |       await expect(page.locator('h1')).toBeVisible();
  441 |     }
  442 |   });
  443 | 
  444 |   test('vendors section - index and random pages', async ({ page }) => {
  445 |     const indexResponse = await page.goto('/vendors/');
  446 |     expect(indexResponse?.status(), '/vendors/ should answer 200').toBe(200);
  447 |     await expect(page.locator('h1')).toContainText(/Vendors/i);
  448 | 
  449 |     await expect(page.locator('.entity-search-result').first()).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
  450 | 
  451 |     const links = await page.locator('.entity-search-result').all();
  452 |     expect(links.length).toBeGreaterThan(0);
  453 | 
  454 |     const hrefs = await Promise.all(links.slice(0, 20).map(l => l.getAttribute('href')));
  455 |     const randomLinks = getRandomItems(hrefs.filter(Boolean) as string[], 3);
  456 | 
  457 |     for (const href of randomLinks) {
  458 |       const entityResponse = await page.goto(toRelativePath(href));
  459 |       expect(entityResponse?.status(), `${toRelativePath(href)} should answer 200`).toBe(200);
  460 |       await expect(page.locator('h1')).toBeVisible();
  461 |     }
  462 |   });
  463 | 
  464 |   test('counties section - index and random detail pages', async ({ page }) => {
  465 |     await page.goto('/counties/');
  466 | 
  467 |     // Map view is default — verify Leaflet map renders with GeoJSON state shapes
  468 |     await expect(page.locator('#map.leaflet-container')).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
  469 |     await expect(page.locator('#map .leaflet-interactive').first()).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
  470 |     const shapes = await page.locator('#map .leaflet-interactive').all();
  471 |     expect(shapes.length).toBeGreaterThan(0);
  472 | 
  473 |     // Switch to List view to test county results and detail navigation
  474 |     await page.locator('.toggle-btn[data-view="list"]').click();
  475 | 
  476 |     // Wait for results to load
  477 |     await page.waitForSelector('.county-result, .county-error', { timeout: DATA_LOAD_TIMEOUT });
  478 |     const results = await page.locator('.county-result').all();
  479 |     expect(results.length).toBeGreaterThan(0);
  480 | 
  481 |     // County results are <a> tags - pick random ones and visit detail pages
  482 |     const hrefs = await Promise.all(results.slice(0, 20).map(r => r.getAttribute('href')));
  483 |     const randomLinks = getRandomItems(hrefs.filter(Boolean) as string[], 3);
  484 | 
  485 |     for (const href of randomLinks) {
  486 |       const entityResponse = await page.goto(toRelativePath(href));
  487 |       expect(entityResponse?.status(), `${toRelativePath(href)} should answer 200`).toBe(200);
  488 |       await expect(page.locator('h1')).toBeVisible();
  489 |     }
  490 |   });
  491 | 
  492 |   test('places section - index and random detail pages', async ({ page }) => {
> 493 |     await page.goto('/places/');
      |                ^ Error: page.goto: net::ERR_ABORTED; maybe frame was detached?
  494 | 
  495 |     // Map view is default — verify Leaflet map renders with GeoJSON state shapes
  496 |     await expect(page.locator('#map.leaflet-container')).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
  497 |     await expect(page.locator('#map .leaflet-interactive').first()).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
  498 |     const shapes = await page.locator('#map .leaflet-interactive').all();
  499 |     expect(shapes.length).toBeGreaterThan(0);
  500 | 
  501 |     // Switch to List view to test place results and detail navigation
  502 |     await page.locator('.toggle-btn[data-view="list"]').click();
  503 | 
  504 |     // Wait for results to load
  505 |     await page.waitForSelector('.place-result, .place-error', { timeout: DATA_LOAD_TIMEOUT });
  506 |     const results = await page.locator('.place-result').all();
  507 |     expect(results.length).toBeGreaterThan(0);
  508 | 
  509 |     // Pick random results and visit detail pages
  510 |     const hrefs = await Promise.all(results.slice(0, 20).map(r => r.getAttribute('href')));
  511 |     const randomLinks = getRandomItems(hrefs.filter(Boolean) as string[], 3);
  512 | 
  513 |     for (const href of randomLinks) {
  514 |       const entityResponse = await page.goto(toRelativePath(href));
  515 |       expect(entityResponse?.status(), `${toRelativePath(href)} should answer 200`).toBe(200);
  516 |       await expect(page.locator('h1')).toBeVisible();
  517 |     }
  518 |   });
  519 | 
  520 |   test('postalcodes section - index and random detail pages', async ({ page }) => {
  521 |     await page.goto('/postalcodes/');
  522 | 
  523 |     // Map view is default — verify Leaflet map renders with GeoJSON state shapes
  524 |     await expect(page.locator('#map.leaflet-container')).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
  525 |     await expect(page.locator('#map .leaflet-interactive').first()).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
  526 |     const shapes = await page.locator('#map .leaflet-interactive').all();
  527 |     expect(shapes.length).toBeGreaterThan(0);
  528 | 
  529 |     // Switch to List view to test postal code results and detail navigation
  530 |     await page.locator('.toggle-btn[data-view="list"]').click();
  531 | 
  532 |     // Wait for results to load
  533 |     await page.waitForSelector('.postal-result, .postal-error', { timeout: DATA_LOAD_TIMEOUT });
  534 |     const results = await page.locator('.postal-result').all();
  535 |     expect(results.length).toBeGreaterThan(0);
  536 | 
  537 |     // Pick random results and visit detail pages
  538 |     const hrefs = await Promise.all(results.slice(0, 20).map(r => r.getAttribute('href')));
  539 |     const randomLinks = getRandomItems(hrefs.filter(Boolean) as string[], 3);
  540 | 
  541 |     for (const href of randomLinks) {
  542 |       const entityResponse = await page.goto(toRelativePath(href));
  543 |       expect(entityResponse?.status(), `${toRelativePath(href)} should answer 200`).toBe(200);
  544 |       await expect(page.locator('h1')).toBeVisible();
  545 |     }
  546 |   });
  547 | 
  548 |   test('precincts section - map view and state filter', async ({ page }) => {
  549 |     await page.goto('/precincts/');
  550 | 
  551 |     // Map view is default — verify Leaflet map renders with GeoJSON state shapes
  552 |     await expect(page.locator('#map.leaflet-container')).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
  553 |     await expect(page.locator('#map .leaflet-interactive').first()).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
  554 |     const shapes = await page.locator('#map .leaflet-interactive').all();
  555 |     expect(shapes.length).toBeGreaterThan(0);
  556 | 
  557 |     // Switch to List view to test state filter and precinct results
  558 |     await page.locator('.toggle-btn[data-view="list"]').click();
  559 | 
  560 |     // Select a state first (precincts require state selection)
  561 |     const stateSelect = page.locator('#precinct-state-select');
  562 |     await expect(stateSelect).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
  563 |     await stateSelect.selectOption('TX');
  564 | 
  565 |     // Wait for precinct results to load
  566 |     await page.waitForSelector('.precinct-result, .precinct-error', { timeout: DATA_LOAD_TIMEOUT });
  567 |     const results = await page.locator('.precinct-result').all();
  568 |     expect(results.length).toBeGreaterThan(0);
  569 | 
  570 |     // Pick random results and visit detail pages
  571 |     const hrefs = await Promise.all(results.slice(0, 20).map(r => r.getAttribute('href')));
  572 |     const validHrefs = hrefs.filter(Boolean) as string[];
  573 | 
  574 |     if (validHrefs.length > 0) {
  575 |       const randomLinks = getRandomItems(validHrefs, 3);
  576 |       for (const href of randomLinks) {
  577 |         const entityResponse = await page.goto(toRelativePath(href));
  578 |         expect(entityResponse?.status(), `${toRelativePath(href)} should answer 200`).toBe(200);
  579 |         await expect(page.locator('h1')).toBeVisible();
  580 |       }
  581 |     }
  582 |   });
  583 | 
  584 |   test('tracts section - state/county filter and random detail pages', async ({ page }) => {
  585 |     test.setTimeout(120000); // Tracts require sequential state→county→tract interactions with GraphQL
  586 |     await page.goto('/tracts/');
  587 | 
  588 |     // Switch to List view — tract-index.js loads dynamically on list view activation
  589 |     await page.locator('.toggle-btn[data-view="list"]').click();
  590 | 
  591 |     // Wait for state dropdown to populate (tract-index.js loads states via GraphQL)
  592 |     const stateSelect = page.locator('#tract-state-select');
  593 |     await page.waitForFunction(() => {
```