# 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 >> office profile - dynamic candidates >> office profile loads candidates filtered to office
- Location: tests/ui/entities.spec.ts:196:7

# Error details

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

```
Error: response.json: Test timeout of 30000ms exceeded.
```

# Page snapshot

```yaml
- generic [active] [ref=e1]:
  - banner [ref=e2]:
    - heading "President of the United States" [level=1] [ref=e3]
    - paragraph [ref=e4]: Federal Office Profile
  - generic [ref=e5]:
    - heading "Overview" [level=2] [ref=e6]
    - generic [ref=e7]:
      - term [ref=e8]: Office Type
      - definition [ref=e9]: President
      - term [ref=e10]: State
      - definition [ref=e11]:
        - link "United States" [ref=e12] [cursor=pointer]:
          - /url: https://us.elect.info/states/f4d08ba1-2eba-4f98-f93c-490cb3926e8e/
      - term [ref=e13]: Total Candidates
      - definition [ref=e14]: 6,956
      - term [ref=e15]: Election Years
      - definition [ref=e16]: 1972,1976,1980,1982,1984,1988,1990,1992,1994,1996,1998,2000,2002,2004,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,2021,2022,2023,2024,2025,2026,2027,2028,2029,2030,2031,2032,2033,2034,2035,2036,2037,2038,2039,2040,2041,2042,2043,2044,2045,2046,2048,2052,2064,2089,2097,2106,2727,2777,2820,2916,2923,2929,2950,2965,2999
      - term [ref=e17]: Active Cycles
      - definition [ref=e18]:
        - generic [ref=e19]:
          - link "2026" [ref=e20] [cursor=pointer]:
            - /url: https://us.elect.info/elections/2026/
          - text: ","
          - link "2024" [ref=e21] [cursor=pointer]:
            - /url: https://us.elect.info/elections/2024/
          - text: ","
          - link "2022" [ref=e22] [cursor=pointer]:
            - /url: https://us.elect.info/elections/2022/
          - text: ","
          - link "2020" [ref=e23] [cursor=pointer]:
            - /url: https://us.elect.info/elections/2020/
          - text: ","
          - link "2018" [ref=e24] [cursor=pointer]:
            - /url: https://us.elect.info/elections/2018/
          - text: ","
          - link "2016" [ref=e25] [cursor=pointer]:
            - /url: https://us.elect.info/elections/2016/
          - text: ","
          - link "2014" [ref=e26] [cursor=pointer]:
            - /url: https://us.elect.info/elections/2014/
          - text: ","
          - link "2012" [ref=e27] [cursor=pointer]:
            - /url: https://us.elect.info/elections/2012/
          - text: ","
          - link "2010" [ref=e28] [cursor=pointer]:
            - /url: https://us.elect.info/elections/2010/
          - text: ","
          - link "2008" [ref=e29] [cursor=pointer]:
            - /url: https://us.elect.info/elections/2008/
          - text: ","
          - link "2006" [ref=e30] [cursor=pointer]:
            - /url: https://us.elect.info/elections/2006/
          - text: ","
          - link "2004" [ref=e31] [cursor=pointer]:
            - /url: https://us.elect.info/elections/2004/
          - text: ","
          - link "2002" [ref=e32] [cursor=pointer]:
            - /url: https://us.elect.info/elections/2002/
          - text: ","
          - link "2000" [ref=e33] [cursor=pointer]:
            - /url: https://us.elect.info/elections/2000/
          - text: ","
          - link "1998" [ref=e34] [cursor=pointer]:
            - /url: https://us.elect.info/elections/1998/
          - text: ","
          - link "1996" [ref=e35] [cursor=pointer]:
            - /url: https://us.elect.info/elections/1996/
          - text: ","
          - link "1994" [ref=e36] [cursor=pointer]:
            - /url: https://us.elect.info/elections/1994/
          - text: ","
          - link "1992" [ref=e37] [cursor=pointer]:
            - /url: https://us.elect.info/elections/1992/
          - text: ","
          - link "1990" [ref=e38] [cursor=pointer]:
            - /url: https://us.elect.info/elections/1990/
          - text: ","
          - link "1988" [ref=e39] [cursor=pointer]:
            - /url: https://us.elect.info/elections/1988/
          - text: ","
          - link "1986" [ref=e40] [cursor=pointer]:
            - /url: https://us.elect.info/elections/1986/
          - text: ","
          - link "1984" [ref=e41] [cursor=pointer]:
            - /url: https://us.elect.info/elections/1984/
          - text: ","
          - link "1982" [ref=e42] [cursor=pointer]:
            - /url: https://us.elect.info/elections/1982/
          - text: ","
          - link "1980" [ref=e43] [cursor=pointer]:
            - /url: https://us.elect.info/elections/1980/
  - generic [ref=e44]:
    - heading "Candidates" [level=2] [ref=e45]
    - generic [ref=e47]:
      - generic [ref=e48]:
        - textbox "Search candidates for this office..." [ref=e49]
        - button "Search" [ref=e50] [cursor=pointer]
      - generic [ref=e51]:
        - heading "Candidates" [level=3] [ref=e53]
        - paragraph [ref=e55]: "Failed to load: 400"
  - contentinfo [ref=e56]:
    - paragraph [ref=e57]:
      - link "elect.info" [ref=e58] [cursor=pointer]:
        - /url: https://us.elect.info/
      - text: "| Data from"
      - link "Federal Election Commission" [ref=e59] [cursor=pointer]:
        - /url: https://us.elect.info/datasources/fec.gov/
      - text: "| Generated 2026-10-09"
```

# Test source

```ts
  113 |     // Districts list view loads state options from GraphQL, then requires selection
  114 |     const stateSelect = page.locator('#district-state-select');
  115 |     await expect(stateSelect).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
  116 | 
  117 |     // Wait for state options to populate from GraphQL (more than just the placeholder)
  118 |     await page.waitForFunction(() => {
  119 |       const select = document.querySelector('#district-state-select') as HTMLSelectElement;
  120 |       return select && select.options.length > 1;
  121 |     }, { timeout: DATA_LOAD_TIMEOUT });
  122 | 
  123 |     await stateSelect.selectOption('CA');
  124 | 
  125 |     // Wait for district results to load
  126 |     await expect(page.locator('.district-result').first()).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
  127 | 
  128 |     const links = await page.locator('.district-result').all();
  129 |     expect(links.length).toBeGreaterThan(0);
  130 | 
  131 |     const hrefs = await Promise.all(links.slice(0, 20).map(l => l.getAttribute('href')));
  132 |     const randomLinks = getRandomItems(hrefs.filter(Boolean) as string[], 3);
  133 | 
  134 |     for (const href of randomLinks) {
  135 |       const entityResponse = await page.goto(toRelativePath(href));
  136 |       expect(entityResponse?.status(), `${toRelativePath(href)} should answer 200`).toBe(200);
  137 |       await expect(page.locator('h1')).toBeVisible();
  138 |     }
  139 |   });
  140 | 
  141 |   test('states section - index and random pages', async ({ page }) => {
  142 |     const indexResponse = await page.goto('/states/');
  143 |     expect(indexResponse?.status(), '/states/ should answer 200').toBe(200);
  144 |     await expect(page.locator('h1')).toContainText(/States/i);
  145 | 
  146 |     // Map view is default — verify Leaflet map renders with GeoJSON state shapes
  147 |     await expect(page.locator('#map.leaflet-container')).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
  148 |     await expect(page.locator('#map .leaflet-interactive').first()).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
  149 |     const shapes = await page.locator('#map .leaflet-interactive').all();
  150 |     expect(shapes.length).toBeGreaterThan(0);
  151 | 
  152 |     // Switch to Grid view to test state chips and detail navigation
  153 |     await page.locator('.toggle-btn[data-view="grid"]').click();
  154 | 
  155 |     await expect(page.locator('.state-chip').first()).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
  156 | 
  157 |     const links = await page.locator('.state-chip').all();
  158 |     expect(links.length).toBeGreaterThan(0);
  159 | 
  160 |     const hrefs = await Promise.all(links.slice(0, 20).map(l => l.getAttribute('href')));
  161 |     const randomLinks = getRandomItems(hrefs.filter(Boolean) as string[], 3);
  162 | 
  163 |     for (const href of randomLinks) {
  164 |       const entityResponse = await page.goto(toRelativePath(href));
  165 |       expect(entityResponse?.status(), `${toRelativePath(href)} should answer 200`).toBe(200);
  166 |       await expect(page.locator('h1')).toBeVisible();
  167 |     }
  168 |   });
  169 | 
  170 |   test('elections section - index and random pages', async ({ page }) => {
  171 |     const indexResponse = await page.goto('/elections/');
  172 |     expect(indexResponse?.status(), '/elections/ should answer 200').toBe(200);
  173 |     await expect(page.locator('h1')).toContainText(/Election/i);
  174 | 
  175 |     // Wait for GraphQL browseEntities to populate results
  176 |     await expect(page.locator('.entity-search-result').first()).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
  177 | 
  178 |     const links = await page.locator('.entity-search-result').all();
  179 |     expect(links.length).toBeGreaterThan(0);
  180 | 
  181 |     const hrefs = await Promise.all(links.slice(0, 20).map(l => l.getAttribute('href')));
  182 |     const randomLinks = getRandomItems(hrefs.filter(Boolean) as string[], 3);
  183 | 
  184 |     for (const href of randomLinks) {
  185 |       const entityResponse = await page.goto(toRelativePath(href));
  186 |       expect(entityResponse?.status(), `${toRelativePath(href)} should answer 200`).toBe(200);
  187 |       await expect(page.locator('h1')).toBeVisible();
  188 |     }
  189 |   });
  190 | });
  191 | 
  192 | test.describe('office profile - dynamic candidates', () => {
  193 |   // President of the United States office page
  194 |   const PRESIDENT_PATH = '/offices/684f6e2d-7105-932b-8b7f-94016580f727/';
  195 | 
  196 |   test('office profile loads candidates filtered to office', async ({ page }) => {
  197 |     // Intercept the browseEntities GraphQL request to verify filter is applied
  198 |     const responsePromise = page.waitForResponse(resp => {
  199 |       if (!resp.url().includes('/api/graphql')) return false;
  200 |       if (resp.request().method() !== 'POST') return false;
  201 |       const body = resp.request().postData() || '';
  202 |       return body.includes('browseEntities');
  203 |     }, { timeout: DATA_LOAD_TIMEOUT });
  204 | 
  205 |     await page.goto(PRESIDENT_PATH);
  206 |     const response = await responsePromise;
  207 | 
  208 |     // Verify the request includes the office filter
  209 |     const requestBody = JSON.parse(response.request().postData() || '{}');
  210 |     expect(requestBody.variables?.filter).toContain('office:P');
  211 | 
  212 |     // Verify the response has results
> 213 |     const json = await response.json();
      |                                 ^ Error: response.json: Test timeout of 30000ms exceeded.
  214 |     const results = json.data?.browseEntities?.results || [];
  215 |     expect(results.length).toBeGreaterThan(0);
  216 |   });
  217 | 
  218 |   test('office profile candidates sorted by total contributions descending', async ({ page }) => {
  219 |     const responsePromise = page.waitForResponse(resp => {
  220 |       if (!resp.url().includes('/api/graphql')) return false;
  221 |       if (resp.request().method() !== 'POST') return false;
  222 |       const body = resp.request().postData() || '';
  223 |       return body.includes('browseEntities');
  224 |     }, { timeout: DATA_LOAD_TIMEOUT });
  225 | 
  226 |     await page.goto(PRESIDENT_PATH);
  227 |     const response = await responsePromise;
  228 | 
  229 |     const json = await response.json();
  230 |     const results = json.data?.browseEntities?.results || [];
  231 |     expect(results.length).toBeGreaterThan(1);
  232 | 
  233 |     // Extract total_contributions from metadata to verify sort order
  234 |     const contributions = results.map((r: { metadata?: string }) => {
  235 |       try {
  236 |         const meta = JSON.parse(r.metadata || '{}');
  237 |         return meta.total_contributions || 0;
  238 |       } catch { return 0; }
  239 |     });
  240 | 
  241 |     for (let i = 1; i < contributions.length; i++) {
  242 |       expect(contributions[i - 1]).toBeGreaterThanOrEqual(contributions[i]);
  243 |     }
  244 |   });
  245 | 
  246 |   test('office profile renders candidate results in UI', async ({ page }) => {
  247 |     await page.goto(PRESIDENT_PATH);
  248 | 
  249 |     // Wait for candidate results to render
  250 |     await expect(page.locator('.entity-search-result').first()).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
  251 | 
  252 |     const results = await page.locator('.entity-search-result').all();
  253 |     expect(results.length).toBeGreaterThan(0);
  254 | 
  255 |     // First result should be a well-known presidential candidate with high contributions
  256 |     const firstResultText = await results[0].textContent();
  257 |     expect(firstResultText).toMatch(/\$/); // Should show dollar amount
  258 |   });
  259 | });
  260 | 
  261 | test.describe('state profile - dynamic candidates', () => {
  262 |   const TEXAS_PATH = '/states/tx/';
  263 | 
  264 |   test('state profile loads candidates filtered to state', async ({ page }) => {
  265 |     const responsePromise = page.waitForResponse(resp => {
  266 |       if (!resp.url().includes('/api/graphql')) return false;
  267 |       if (resp.request().method() !== 'POST') return false;
  268 |       const body = resp.request().postData() || '';
  269 |       return body.includes('browseEntities');
  270 |     }, { timeout: DATA_LOAD_TIMEOUT });
  271 | 
  272 |     await page.goto(TEXAS_PATH);
  273 |     const response = await responsePromise;
  274 | 
  275 |     const requestBody = JSON.parse(response.request().postData() || '{}');
  276 |     expect(requestBody.variables?.filter).toContain('office_state:TX');
  277 | 
  278 |     const json = await response.json();
  279 |     const results = json.data?.browseEntities?.results || [];
  280 |     expect(results.length).toBeGreaterThan(0);
  281 |   });
  282 | 
  283 |   test('state profile candidates sorted by total contributions descending', async ({ page }) => {
  284 |     const responsePromise = page.waitForResponse(resp => {
  285 |       if (!resp.url().includes('/api/graphql')) return false;
  286 |       if (resp.request().method() !== 'POST') return false;
  287 |       const body = resp.request().postData() || '';
  288 |       return body.includes('browseEntities');
  289 |     }, { timeout: DATA_LOAD_TIMEOUT });
  290 | 
  291 |     await page.goto(TEXAS_PATH);
  292 |     const response = await responsePromise;
  293 | 
  294 |     const json = await response.json();
  295 |     const results = json.data?.browseEntities?.results || [];
  296 |     expect(results.length).toBeGreaterThan(1);
  297 | 
  298 |     const contributions = results.map((r: { metadata?: string }) => {
  299 |       try {
  300 |         const meta = JSON.parse(r.metadata || '{}');
  301 |         return meta.total_contributions || 0;
  302 |       } catch { return 0; }
  303 |     });
  304 | 
  305 |     for (let i = 1; i < contributions.length; i++) {
  306 |       expect(contributions[i - 1]).toBeGreaterThanOrEqual(contributions[i]);
  307 |     }
  308 |   });
  309 | });
  310 | 
  311 | test.describe('district profile - dynamic candidates', () => {
  312 |   const DISTRICT_PATH = '/districts/tx-10/';
  313 | 
```