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

# Error details

```
Error: expect(locator).toBeVisible() failed

Locator: locator('.entity-search-result').first()
Expected: visible
Timeout: 15000ms
Error: element(s) not found

Call log:
  - Expect "toBeVisible" locator('.entity-search-result').first() with timeout 15000ms
  - waiting for locator('.entity-search-result').first()

```

```yaml
- link "← Back to Home":
  - /url: /
- banner:
  - heading "Employers" [level=1]
  - paragraph: Organizations with contributing employees
- textbox "Search employers by name..."
- button "Search"
- heading "All Employers" [level=3]
- paragraph: "Failed to load: 400"
- main
```

# Test source

```ts
  331 |     const json = await response.json();
  332 |     const results = json.data?.browseEntities?.results || [];
  333 |     expect(results.length).toBeGreaterThan(0);
  334 |   });
  335 | 
  336 |   test('district profile candidates sorted by total contributions descending', async ({ page }) => {
  337 |     const responsePromise = page.waitForResponse(resp => {
  338 |       if (!resp.url().includes('/api/graphql')) return false;
  339 |       if (resp.request().method() !== 'POST') return false;
  340 |       const body = resp.request().postData() || '';
  341 |       return body.includes('browseEntities');
  342 |     }, { timeout: DATA_LOAD_TIMEOUT });
  343 | 
  344 |     await page.goto(DISTRICT_PATH);
  345 |     const response = await responsePromise;
  346 | 
  347 |     const json = await response.json();
  348 |     const results = json.data?.browseEntities?.results || [];
  349 |     expect(results.length).toBeGreaterThan(1);
  350 | 
  351 |     const contributions = results.map((r: { metadata?: string }) => {
  352 |       try {
  353 |         const meta = JSON.parse(r.metadata || '{}');
  354 |         return meta.total_contributions || 0;
  355 |       } catch { return 0; }
  356 |     });
  357 | 
  358 |     for (let i = 1; i < contributions.length; i++) {
  359 |       expect(contributions[i - 1]).toBeGreaterThanOrEqual(contributions[i]);
  360 |     }
  361 |   });
  362 | });
  363 | 
  364 | // Helper to intercept browseEntities GraphQL response and extract score values
  365 | async function getBrowseScores(page: import('@playwright/test').Page, url: string): Promise<number[]> {
  366 |   const responsePromise = page.waitForResponse(resp => {
  367 |     if (!resp.url().includes('/api/graphql')) return false;
  368 |     if (resp.request().method() !== 'POST') return false;
  369 |     const body = resp.request().postData() || '';
  370 |     return body.includes('browseEntities');
  371 |   }, { timeout: DATA_LOAD_TIMEOUT });
  372 |   await page.goto(url);
  373 |   const response = await responsePromise;
  374 |   const json = await response.json();
  375 |   const results = json.data?.browseEntities?.results || [];
  376 |   return results.map((r: { score?: number }) => r.score || 0);
  377 | }
  378 | 
  379 | test.describe('entity browse sort order - quantitative fields', () => {
  380 | 
  381 |   test('candidates sorted by total contributions descending', async ({ page }) => {
  382 |     const scores = await getBrowseScores(page, '/candidates/');
  383 |     expect(scores.length).toBeGreaterThan(1);
  384 |     for (let i = 1; i < scores.length; i++) {
  385 |       expect(scores[i - 1]).toBeGreaterThanOrEqual(scores[i]);
  386 |     }
  387 |   });
  388 | 
  389 |   test('committees sorted by total receipts descending', async ({ page }) => {
  390 |     const scores = await getBrowseScores(page, '/committees/');
  391 |     expect(scores.length).toBeGreaterThan(1);
  392 |     for (let i = 1; i < scores.length; i++) {
  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 |     // A stale search index scores every donor 0 and the order means nothing.
  409 |     expect(scores[0], 'top individual donor should have contributions').toBeGreaterThan(0);
  410 |     for (let i = 1; i < scores.length; i++) {
  411 |       expect(scores[i - 1]).toBeGreaterThanOrEqual(scores[i]);
  412 |     }
  413 |   });
  414 | 
  415 |   test('employers sorted by total employee contributions descending', async ({ page }) => {
  416 |     const scores = await getBrowseScores(page, '/employers/');
  417 |     expect(scores.length).toBeGreaterThan(1);
  418 |     for (let i = 1; i < scores.length; i++) {
  419 |       expect(scores[i - 1]).toBeGreaterThanOrEqual(scores[i]);
  420 |     }
  421 |   });
  422 | });
  423 | 
  424 | test.describe('us.elect.info entity sections - additional index + detail', () => {
  425 | 
  426 |   test('employers section - index and random pages', async ({ page }) => {
  427 |     const indexResponse = await page.goto('/employers/');
  428 |     expect(indexResponse?.status(), '/employers/ should answer 200').toBe(200);
  429 |     await expect(page.locator('h1')).toContainText(/Employers/i);
  430 | 
> 431 |     await expect(page.locator('.entity-search-result').first()).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
      |                                                                 ^ Error: expect(locator).toBeVisible() failed
  432 | 
  433 |     const links = await page.locator('.entity-search-result').all();
  434 |     expect(links.length).toBeGreaterThan(0);
  435 | 
  436 |     const hrefs = await Promise.all(links.slice(0, 20).map(l => l.getAttribute('href')));
  437 |     const randomLinks = getRandomItems(hrefs.filter(Boolean) as string[], 3);
  438 | 
  439 |     for (const href of randomLinks) {
  440 |       const entityResponse = await page.goto(toRelativePath(href));
  441 |       expect(entityResponse?.status(), `${toRelativePath(href)} should answer 200`).toBe(200);
  442 |       await expect(page.locator('h1')).toBeVisible();
  443 |     }
  444 |   });
  445 | 
  446 |   test('vendors section - index and random pages', async ({ page }) => {
  447 |     const indexResponse = await page.goto('/vendors/');
  448 |     expect(indexResponse?.status(), '/vendors/ should answer 200').toBe(200);
  449 |     await expect(page.locator('h1')).toContainText(/Vendors/i);
  450 | 
  451 |     await expect(page.locator('.entity-search-result').first()).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
  452 | 
  453 |     const links = await page.locator('.entity-search-result').all();
  454 |     expect(links.length).toBeGreaterThan(0);
  455 | 
  456 |     const hrefs = await Promise.all(links.slice(0, 20).map(l => l.getAttribute('href')));
  457 |     const randomLinks = getRandomItems(hrefs.filter(Boolean) as string[], 3);
  458 | 
  459 |     for (const href of randomLinks) {
  460 |       const entityResponse = await page.goto(toRelativePath(href));
  461 |       expect(entityResponse?.status(), `${toRelativePath(href)} should answer 200`).toBe(200);
  462 |       await expect(page.locator('h1')).toBeVisible();
  463 |     }
  464 |   });
  465 | 
  466 |   test('counties section - index and random detail pages', async ({ page }) => {
  467 |     await page.goto('/counties/');
  468 | 
  469 |     // Map view is default — verify Leaflet map renders with GeoJSON state shapes
  470 |     await expect(page.locator('#map.leaflet-container')).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
  471 |     await expect(page.locator('#map .leaflet-interactive').first()).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
  472 |     const shapes = await page.locator('#map .leaflet-interactive').all();
  473 |     expect(shapes.length).toBeGreaterThan(0);
  474 | 
  475 |     // Switch to List view to test county results and detail navigation
  476 |     await page.locator('.toggle-btn[data-view="list"]').click();
  477 | 
  478 |     // Wait for results to load
  479 |     await page.waitForSelector('.county-result, .county-error', { timeout: DATA_LOAD_TIMEOUT });
  480 |     const results = await page.locator('.county-result').all();
  481 |     expect(results.length).toBeGreaterThan(0);
  482 | 
  483 |     // County results are <a> tags - pick random ones and visit detail pages
  484 |     const hrefs = await Promise.all(results.slice(0, 20).map(r => r.getAttribute('href')));
  485 |     const randomLinks = getRandomItems(hrefs.filter(Boolean) as string[], 3);
  486 | 
  487 |     for (const href of randomLinks) {
  488 |       const entityResponse = await page.goto(toRelativePath(href));
  489 |       expect(entityResponse?.status(), `${toRelativePath(href)} should answer 200`).toBe(200);
  490 |       await expect(page.locator('h1')).toBeVisible();
  491 |     }
  492 |   });
  493 | 
  494 |   test('places section - index and random detail pages', async ({ page }) => {
  495 |     await page.goto('/places/');
  496 | 
  497 |     // Map view is default — verify Leaflet map renders with GeoJSON state shapes
  498 |     await expect(page.locator('#map.leaflet-container')).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
  499 |     await expect(page.locator('#map .leaflet-interactive').first()).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
  500 |     const shapes = await page.locator('#map .leaflet-interactive').all();
  501 |     expect(shapes.length).toBeGreaterThan(0);
  502 | 
  503 |     // Switch to List view to test place results and detail navigation
  504 |     await page.locator('.toggle-btn[data-view="list"]').click();
  505 | 
  506 |     // Wait for results to load
  507 |     await page.waitForSelector('.place-result, .place-error', { timeout: DATA_LOAD_TIMEOUT });
  508 |     const results = await page.locator('.place-result').all();
  509 |     expect(results.length).toBeGreaterThan(0);
  510 | 
  511 |     // Pick random results and visit detail pages
  512 |     const hrefs = await Promise.all(results.slice(0, 20).map(r => r.getAttribute('href')));
  513 |     const randomLinks = getRandomItems(hrefs.filter(Boolean) as string[], 3);
  514 | 
  515 |     for (const href of randomLinks) {
  516 |       const entityResponse = await page.goto(toRelativePath(href));
  517 |       expect(entityResponse?.status(), `${toRelativePath(href)} should answer 200`).toBe(200);
  518 |       await expect(page.locator('h1')).toBeVisible();
  519 |     }
  520 |   });
  521 | 
  522 |   test('postalcodes section - index and random detail pages', async ({ page }) => {
  523 |     await page.goto('/postalcodes/');
  524 | 
  525 |     // Map view is default — verify Leaflet map renders with GeoJSON state shapes
  526 |     await expect(page.locator('#map.leaflet-container')).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
  527 |     await expect(page.locator('#map .leaflet-interactive').first()).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
  528 |     const shapes = await page.locator('#map .leaflet-interactive').all();
  529 |     expect(shapes.length).toBeGreaterThan(0);
  530 | 
  531 |     // Switch to List view to test postal code results and detail navigation
```