# 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/money-flow.spec.ts >> money-flow card toggle >> candidate: #ie-oppose card follows the independent-expenditure data
- Location: tests/ui/money-flow.spec.ts:79:9

# Error details

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

```
Error: apiRequestContext.post: Request context disposed.
Call log:
  - → POST http://10.1.105.75/api/graphql
    - user-agent: Playwright/1.64.1 (x64; ubuntu 22.04) node/20.9 CI/1
    - accept: */*
    - accept-encoding: gzip,deflate,br
    - content-type: application/json
    - content-length: 104

```

# Test source

```ts
  1   | import { test, expect } from '@playwright/test';
  2   | import { visitorHeaders } from './helpers';
  3   | 
  4   | // Money-flow Sankey (electinfo/sites#178). nginx injects /js/money-flow.js
  5   | // into Candidate and Committee detail pages served from the static-pages
  6   | // mount; the us-pages renderer (sites#210, on preview) writes the same tag
  7   | // into the pages it renders. The script fetches moneyFlow from /api/graphql and gives each money
  8   | // card a Sankey | Table toggle, Sankey by default.
  9   | //
  10  | // Every us target has these pages and a moneyFlow API: the CI test pod mounts
  11  | // the production static pages and proxies to api-graphql, and preview serves
  12  | // beta-pages through api-graphql-preview. So the specs run everywhere, on the
  13  | // DNC and Harris pages, and check that no other kind of page gets the script.
  14  | 
  15  | const SCRIPT = '<script src="/js/money-flow.js" defer></script>';
  16  | const DNC = '/committees/a75a9110-f1db-a945-39a8-7503ed3c04da/'; // C00010603
  17  | const HARRIS = '/candidates/a8897257-430c-2bf3-4498-349021b09ca3/'; // P00009423
  18  | const OTHER_PAGES = [
  19  |   '/states/032f8d61-f554-f435-2a6d-50b3c8cc01d7/', // AK
  20  |   '/elections/2024/',
  21  |   '/offices/president/',
  22  |   '/districts/ak/00/',
  23  | ];
  24  | 
  25  | test.describe('money-flow script injection', () => {
  26  |   for (const path of [DNC, HARRIS]) {
  27  |     test(`injected once on ${path}`, async ({ request }) => {
  28  |       const res = await request.get(path);
  29  |       expect(res.status()).toBe(200);
  30  |       const html = await res.text();
  31  |       expect(html.split(SCRIPT).length - 1).toBe(1);
  32  |     });
  33  |   }
  34  | 
  35  |   for (const path of OTHER_PAGES) {
  36  |     test(`not injected on ${path}`, async ({ request }) => {
  37  |       const res = await request.get(path);
  38  |       expect(res.status()).toBe(200);
  39  |       expect(await res.text()).not.toContain('money-flow.js');
  40  |     });
  41  |   }
  42  | });
  43  | 
  44  | test.describe('money-flow card toggle', () => {
  45  |   test('committee: each money card defaults to the Sankey', async ({ page }) => {
  46  |     const errors: string[] = [];
  47  |     page.on('pageerror', (e) => errors.push(String(e)));
  48  |     await page.goto(DNC);
  49  |     for (const id of ['incoming', 'outgoing', 'funded']) {
  50  |       const card = page.locator(`#${id}`);
  51  |       await expect(card.locator('.money-flow-chart canvas')).toHaveCount(1, { timeout: 15000 });
  52  |       await expect(card.locator('.mf-view-btn[data-view="sankey"]')).toHaveAttribute('aria-pressed', 'true');
  53  |       await expect(card.locator('ul.candidate-list')).toBeHidden();
  54  |     }
  55  |     await expect(page.locator('#supported .mf-toggle')).toHaveCount(0);
  56  |     expect(errors).toEqual([]);
  57  |   });
  58  | 
  59  |   test('candidate: Top Contributors toggles between Sankey and table', async ({ page }) => {
  60  |     await page.goto(HARRIS);
  61  |     const card = page.locator('#funding');
  62  |     await expect(card.locator('.money-flow-chart canvas')).toHaveCount(1, { timeout: 15000 });
  63  |     await expect(card.locator('table')).toBeHidden();
  64  |     await card.locator('.mf-view-btn[data-view="table"]').click();
  65  |     await expect(card.locator('table')).toBeVisible();
  66  |     await expect(card.locator('.money-flow-chart')).toBeHidden();
  67  |     await expect(card.locator('.mf-view-btn[data-view="table"]')).toHaveAttribute('aria-pressed', 'true');
  68  |   });
  69  | 
  70  |   // Independent expenditures (rundeck#757): each card is a Sankey once the graph
  71  |   // has IE_SUPPORTS / IE_OPPOSES edges, and keeps its static list until then.
  72  |   // Pages generated before the sections existed have none to mount.
  73  |   for (const [label, path, fecId, sectionId, side] of [
  74  |     ['candidate', HARRIS, 'P00009423', 'ie-support', 'ie-in'],
  75  |     ['committee', DNC, 'C00010603', 'ie-support', 'ie-out'],
  76  |     ['candidate', HARRIS, 'P00009423', 'ie-oppose', 'ie-opp-in'],
  77  |     ['committee', DNC, 'C00010603', 'ie-oppose', 'ie-opp-out'],
  78  |   ] as const) {
  79  |     test(`${label}: #${sectionId} card follows the independent-expenditure data`, async ({ page, request }) => {
  80  |       const errors: string[] = [];
  81  |       page.on('pageerror', (e) => errors.push(String(e)));
> 82  |       const res = await request.post('/api/graphql', {
      |                                 ^ Error: apiRequestContext.post: Request context disposed.
  83  |         data: { query: 'query($f: String!) { moneyFlow(fecId: $f) { nodes { side } } }', variables: { f: fecId } },
  84  |         headers: visitorHeaders(),
  85  |       });
  86  |       const nodes: { side: string }[] = (await res.json())?.data?.moneyFlow?.nodes ?? [];
  87  |       const ieNodes = nodes.filter((n) => n.side === side).length;
  88  | 
  89  |       await page.goto(path);
  90  |       const card = page.locator(`#${sectionId}`);
  91  |       // the contribution cards are mounted first, so wait for them before judging the IE card
  92  |       await expect(page.locator(label === 'candidate' ? '#funding .mf-toggle' : '#incoming .mf-toggle')).toHaveCount(1, { timeout: 15000 });
  93  |       if ((await card.count()) === 0) {
  94  |         expect(errors).toEqual([]);
  95  |         return;
  96  |       }
  97  |       const list = card.locator(label === 'candidate' ? 'table' : 'ul.candidate-list');
  98  |       if (ieNodes > 0) {
  99  |         await expect(card.locator('.money-flow-chart canvas')).toHaveCount(1, { timeout: 15000 });
  100 |         await expect(list).toBeHidden();
  101 |       } else {
  102 |         await expect(card.locator('.mf-toggle')).toHaveCount(0);
  103 |         await expect(list).toBeVisible();
  104 |       }
  105 |       expect(errors).toEqual([]);
  106 |     });
  107 |   }
  108 | });
  109 | 
```