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

# Error details

```
TimeoutError: page.waitForResponse: Timeout 15000ms exceeded while waiting for event "response"
```

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

```

# Test source

```ts
  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 | 
  314 |   test('district profile loads candidates filtered to district', async ({ page }) => {
  315 |     const responsePromise = page.waitForResponse(resp => {
  316 |       if (!resp.url().includes('/api/graphql')) return false;
  317 |       if (resp.request().method() !== 'POST') return false;
  318 |       const body = resp.request().postData() || '';
  319 |       return body.includes('browseEntities');
  320 |     }, { timeout: DATA_LOAD_TIMEOUT });
  321 | 
> 322 |     await page.goto(DISTRICT_PATH);
      |                ^ Error: page.goto: net::ERR_ABORTED; maybe frame was detached?
  323 |     const response = await responsePromise;
  324 | 
  325 |     const requestBody = JSON.parse(response.request().postData() || '{}');
  326 |     const filter = requestBody.variables?.filter || '';
  327 |     expect(filter).toContain('office:H');
  328 |     expect(filter).toContain('office_state:TX');
  329 |     expect(filter).toContain('office_district:10');
  330 | 
  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, baseURL }) => {
  406 |     await premiumVisitor(page.context(), baseURL); // donor records are premium (sites#277)
  407 |     const scores = await getBrowseScores(page, '/individuals/');
  408 |     expect(scores.length).toBeGreaterThan(1);
  409 |     // A stale search index scores every donor 0 and the order means nothing.
  410 |     expect(scores[0], 'top individual donor should have contributions').toBeGreaterThan(0);
  411 |     for (let i = 1; i < scores.length; i++) {
  412 |       expect(scores[i - 1]).toBeGreaterThanOrEqual(scores[i]);
  413 |     }
  414 |   });
  415 | 
  416 |   test('employers sorted by total employee contributions descending', async ({ page }) => {
  417 |     const scores = await getBrowseScores(page, '/employers/');
  418 |     expect(scores.length).toBeGreaterThan(1);
  419 |     for (let i = 1; i < scores.length; i++) {
  420 |       expect(scores[i - 1]).toBeGreaterThanOrEqual(scores[i]);
  421 |     }
  422 |   });
```