# 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/layout.spec.ts >> landing page layout >> /offices/ loads with correct heading
- Location: tests/ui/layout.spec.ts:37:9

# 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.22/offices/", waiting until "load"

```

# Test source

```ts
  1  | import { test, expect } from '@playwright/test';
  2  | import { skipIfPremiumGated } from './helpers';
  3  | 
  4  | // Wait timeout for async data loading (GraphQL browseEntities)
  5  | const DATA_LOAD_TIMEOUT = 15000;
  6  | 
  7  | // Entity index pages that use insertSearchUI from base.ts
  8  | const ENTITY_INDEX_PAGES = [
  9  |   { path: '/offices/', heading: /Offices/i },
  10 |   { path: '/candidates/', heading: /Candidates/i },
  11 |   { path: '/committees/', heading: /Committees/i },
  12 |   { path: '/districts/', heading: /Districts/i },
  13 |   { path: '/elections/', heading: /Election/i },
  14 |   { path: '/employers/', heading: /Employers/i },
  15 |   { path: '/individuals/', heading: /Individual/i, premium: true },
  16 |   { path: '/parties/', heading: /Parties/i },
  17 |   { path: '/vendors/', heading: /Vendors/i },
  18 |   { path: '/counties/', heading: /Counties/i },
  19 |   { path: '/places/', heading: /Places/i },
  20 |   { path: '/precincts/', heading: /Precincts/i },
  21 |   { path: '/tracts/', heading: /Tracts/i },
  22 |   { path: '/postalcodes/', heading: /Postal/i },
  23 |   { path: '/blocks/', heading: /Blocks/i, premium: true },
  24 | ];
  25 | 
  26 | // All landing pages including special pages
  27 | // (/codesources/ and /contributors/ are disabled - see disabled-pages.spec.ts)
  28 | const ALL_LANDING_PAGES = [
  29 |   ...ENTITY_INDEX_PAGES,
  30 |   { path: '/states/', heading: /States/i },
  31 |   { path: '/datasources/', heading: /Data Sources/i },
  32 |   { path: '/', heading: /US Elections/i },
  33 | ];
  34 | 
  35 | test.describe('landing page layout', () => {
  36 |   for (const { path, heading, premium } of ALL_LANDING_PAGES as Array<{ path: string; heading: RegExp; premium?: boolean }>) {
  37 |     test(`${path} loads with correct heading`, async ({ page, request }) => {
  38 |       if (premium) await skipIfPremiumGated(request, path);
> 39 |       await page.goto(path);
     |                  ^ Error: page.goto: net::ERR_ABORTED; maybe frame was detached?
  40 |       await expect(page.locator('h1')).toContainText(heading);
  41 |     });
  42 |   }
  43 | });
  44 | 
  45 | test.describe('entity index layout - search UI above results', () => {
  46 |   for (const { path, heading, premium } of ENTITY_INDEX_PAGES as Array<{ path: string; heading: RegExp; premium?: boolean }>) {
  47 |     test(`${path} has no static search description in main`, async ({ page, request }) => {
  48 |       if (premium) await skipIfPremiumGated(request, path);
  49 |       await page.goto(path);
  50 |       await expect(page.locator('h1')).toContainText(heading);
  51 | 
  52 |       // The <main> element should have no <section> or <p> children
  53 |       // (the search UI is injected by JS before <main>, not inside it)
  54 |       const mainSections = page.locator('main > section');
  55 |       await expect(mainSections).toHaveCount(0);
  56 |     });
  57 |   }
  58 | 
  59 |   test('offices - search container appears before main in DOM', async ({ page }) => {
  60 |     await page.goto('/offices/');
  61 |     await expect(page.locator('.entity-search-container')).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
  62 | 
  63 |     // Verify search container is a sibling before <main>, not inside it
  64 |     const searchIsBeforeMain = await page.evaluate(() => {
  65 |       const searchContainer = document.querySelector('.entity-search-container');
  66 |       const main = document.querySelector('main');
  67 |       if (!searchContainer || !main) return false;
  68 |       // compareDocumentPosition bit 4 = DOCUMENT_POSITION_FOLLOWING
  69 |       return (searchContainer.compareDocumentPosition(main) & Node.DOCUMENT_POSITION_FOLLOWING) !== 0;
  70 |     });
  71 |     expect(searchIsBeforeMain).toBe(true);
  72 |   });
  73 | 
  74 |   test('candidates - search container appears before main in DOM', async ({ page }) => {
  75 |     await page.goto('/candidates/');
  76 |     await expect(page.locator('.entity-search-container')).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
  77 | 
  78 |     const searchIsBeforeMain = await page.evaluate(() => {
  79 |       const searchContainer = document.querySelector('.entity-search-container');
  80 |       const main = document.querySelector('main');
  81 |       if (!searchContainer || !main) return false;
  82 |       return (searchContainer.compareDocumentPosition(main) & Node.DOCUMENT_POSITION_FOLLOWING) !== 0;
  83 |     });
  84 |     expect(searchIsBeforeMain).toBe(true);
  85 |   });
  86 | });
  87 | 
```