# 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-support card follows the independent-expenditure data
- Location: tests/ui/money-flow.spec.ts:78:9

# Error details

```
Error: expect(locator).toHaveCount(expected) failed

Locator:  locator('#funding .mf-toggle')
Expected: 1
Received: 0
Timeout:  15000ms

Call log:
  - Expect "toHaveCount" locator('#funding .mf-toggle') with timeout 15000ms
  - waiting for locator('#funding .mf-toggle')
    34 × locator resolved to 0 elements
       - unexpected value "0"

```

# Page snapshot

```yaml
- generic [active] [ref=e1]:
  - heading "503 Service Temporarily Unavailable" [level=1] [ref=e3]
  - separator [ref=e4]
  - generic [ref=e5]: nginx/1.31.6
```

# Test source

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