# 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/leaf-details.spec.ts >> leaf collection client-side detail pages >> vendor live clients come from moneyCounterparties when the graph has no ranks (sites#194)
- Location: tests/ui/leaf-details.spec.ts:520:7

# Error details

```
Error: page.goto: net::ERR_CONNECTION_REFUSED at http://10.1.105.22/vendors/vendor-194/
Call log:
  - navigating to "http://10.1.105.22/vendors/vendor-194/", waiting until "load"

```

# Test source

```ts
  454 |     await expect(page.locator('#committees .leaf-list-footer')).toHaveText('Showing top 2 of 35 committees');
  455 |     const committees = graphqlBodies.find(body => body.includes('IndividualCommittees')) || '';
  456 |     expect(committees).toContain('relationship: CONTRIBUTED_TO, direction: OUT, first: 20');
  457 |     expect(graphqlBodies.every(body => !body.includes('contributedToCommitteesConnection'))).toBe(true);
  458 |   });
  459 | 
  460 |   test('individual Top Committees: Sankey by category, decoded types, refund columns only when refunded (sites#413)', async ({ page, request }) => {
  461 |     await skipIfPremiumGated(request, '/individuals/');
  462 |     const errors: string[] = [];
  463 |     page.on('pageerror', error => errors.push(error.message));
  464 |     await page.route('**/_resolve/**', route => route.fulfill({ status: 404, body: '' }));
  465 |     await page.route('**/api/graphql', async route => {
  466 |       const body = route.request().postData() || '';
  467 |       const row = (id: string, name: string, committeeType: string, party: string | null, amount: number) =>
  468 |         ({ id, name, committeeType, party, affinityDem: null, affinityRep: null, amount, gross: amount, refunded: 0, count: 1 });
  469 |       const data = body.includes('IndividualCommittees')
  470 |         ? {
  471 |             moneyCounterparties: {
  472 |               deferred: false,
  473 |               totalCount: 4,
  474 |               refundedInFullCount: 0,
  475 |               counterparties: [
  476 |                 row('c-sp', 'BIG SUPER PAC', 'O', null, 175000000),
  477 |                 row('c-pty', 'PARTY COMMITTEE', 'Y', 'DEM', 250000),
  478 |                 row('c-s', 'SENATOR FOR SENATE', 'S', 'DEM', 3300),
  479 |                 row('c-h', 'REP FOR CONGRESS', 'H', 'REP', 1000),
  480 |               ],
  481 |             },
  482 |           }
  483 |         : {
  484 |             individuals: [{
  485 |               id: 'ind-sankey', name: 'DONOR, BIG', city: 'NEW YORK', state: 'NY', zip_code: '10001',
  486 |               employer: 'SELF', occupation: 'INVESTOR', total_contributions: 175254300, contribution_count: 4,
  487 |               committee_count: 4, first_contribution: '2020-01-01', last_contribution: '2024-10-01', cycles: '2020,2024',
  488 |               employedByEmployersConnection: { edges: [] },
  489 |               residesInPlacesConnection: { edges: [] },
  490 |               residesInPostalCodesConnection: { edges: [] },
  491 |             }],
  492 |           };
  493 |       await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ data }) });
  494 |     });
  495 | 
  496 |     const response = await page.goto('/individuals/ind-sankey/');
  497 |     expect(response?.status()).toBe(200);
  498 |     await expect(page.locator('#committees h2')).toHaveText('Top Committees', { timeout: DATA_LOAD_TIMEOUT });
  499 |     await expect(page.locator('#committees .money-flow-chart canvas')).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
  500 |     await expect(page.locator('#committees .mf-note')).toBeVisible();
  501 |     const option = await page.locator('#committees').evaluate(el => window.electinfoMoneyFlow!.inspect!(el)!.option as any);
  502 |     const series = option.series[0];
  503 |     const depth = Object.fromEntries(series.data.map((d: any) => [d.name, d.depth]));
  504 |     expect(depth['focal']).toBe(0);
  505 |     expect(depth['mid:Super PACs']).toBe(1);
  506 |     expect(depth['out:c-h']).toBe(2);
  507 |     const toHouse = series.links.find((l: any) => l.target === 'out:c-h');
  508 |     expect(toHouse.raw.amount).toBe(1000);
  509 |     expect(toHouse.value).toBeGreaterThan(1000);
  510 | 
  511 |     await page.locator('#committees .mf-view-btn[data-view="table"]').click();
  512 |     await expect(page.locator('#committees .mf-note')).toBeHidden();
  513 |     await expect(page.locator('#committees .leaf-relationship-list thead th')).toHaveText(['Committee', 'Type', 'Amount', 'Contributions']);
  514 |     const first = page.locator('#committees .leaf-relationship-list tbody tr').first();
  515 |     await expect(first.locator('td').nth(1)).toHaveText('Super PAC');
  516 |     await expect(first.locator('td').nth(1).locator('span')).toHaveAttribute('title', 'FEC committee type O');
  517 |     expect(errors).toEqual([]);
  518 |   });
  519 | 
  520 |   test('vendor live clients come from moneyCounterparties when the graph has no ranks (sites#194)', async ({ page }) => {
  521 |     const graphqlBodies: string[] = [];
  522 |     await page.route('**/_resolve/**', route => route.fulfill({ status: 404, body: '' }));
  523 |     await page.route('**/api/graphql', async route => {
  524 |       const body = route.request().postData() || '';
  525 |       graphqlBodies.push(body);
  526 |       let data: unknown;
  527 |       if (body.includes('VendorRankedClients')) {
  528 |         data = { vendorClients: { ranked: false, clientCount: null, totalReceived: null, otherCount: null, otherAmount: null, clients: [] } };
  529 |       } else if (body.includes('VendorClientList')) {
  530 |         data = {
  531 |           moneyCounterparties: {
  532 |             deferred: false,
  533 |             totalCount: 3,
  534 |             counterparties: [
  535 |               { id: 'c-a', name: 'ALPHA FOR CONGRESS', committeeType: 'H', party: 'DEM', affinityDem: null, affinityRep: null,
  536 |                 amount: 5000, count: 4, purposes: 'MEDIA,PRINTING' },
  537 |               { id: 'c-b', name: 'BETA PAC', committeeType: 'Q', party: null, affinityDem: 0.2, affinityRep: 0.8,
  538 |                 amount: 3000, count: 2, purposes: null },
  539 |             ],
  540 |           },
  541 |         };
  542 |       } else {
  543 |         data = {
  544 |           vendors: [{
  545 |             id: 'vendor-194', name: 'LIVE VENDOR', name_normalized: 'live vendor', city: 'DALLAS', state: 'TX',
  546 |             total_received: 9000, payment_count: 7, purpose_categories: 'MEDIA', client_count: 3,
  547 |             first_payment: '2020-01-01', last_payment: '2024-01-01', cycles: '2020,2024',
  548 |           }],
  549 |         };
  550 |       }
  551 |       await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ data }) });
  552 |     });
  553 | 
> 554 |     const response = await page.goto('/vendors/vendor-194/');
      |                                 ^ Error: page.goto: net::ERR_CONNECTION_REFUSED at http://10.1.105.22/vendors/vendor-194/
  555 |     expect(response?.status()).toBe(200);
  556 |     await expect(page.locator('.leaf-detail-page h1')).toHaveText('LIVE VENDOR', { timeout: DATA_LOAD_TIMEOUT });
  557 |     await expect(page.locator('#clients .leaf-list-footer')).toHaveText('Showing top 2 of 3 committees');
  558 |     await expect(page.locator('#clients .money-flow-chart canvas')).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
  559 |     await page.locator('#clients .mf-view-btn[data-view="table"]').click();
  560 |     const rows = page.locator('#clients .leaf-relationship-list tbody tr');
  561 |     await expect(rows).toHaveCount(2);
  562 |     await expect(rows.first()).toContainText('MEDIA,PRINTING');
  563 |     const list = graphqlBodies.find(body => body.includes('VendorClientList')) || '';
  564 |     expect(list).toContain('relationship: PAYS, direction: IN, first: 20');
  565 |     expect(graphqlBodies.every(body => !body.includes('committeesPaysConnection'))).toBe(true);
  566 |   });
  567 | });
  568 | 
```