# 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 >> hub employer renders totals without issuing a live connection query
- Location: tests/ui/leaf-details.spec.ts:164:7

# Error details

```
Error: page.goto: net::ERR_CONNECTION_REFUSED at http://10.1.105.22/employers/hub%20test/
Call log:
  - navigating to "http://10.1.105.22/employers/hub%20test/", waiting until "load"

```

# Test source

```ts
  100 |     await expect(page.locator('#clients .leaf-list-footer')).toContainText(/Showing (top \d+ of [\d,]+|all [\d,]+) committees/, {
  101 |       timeout: DATA_LOAD_TIMEOUT,
  102 |     });
  103 |     await expect(page.locator('#clients .leaf-relationship-list')).toHaveCount(1);
  104 |     // The ranked list (sites#261) replaces the live list once the graph has PAYS ranks; until then
  105 |     // moneyCounterparties (sites#194), or on an API without it the bounded edge-sorted connection.
  106 |     const live = bodies.find(body => body.includes('query VendorClients('));
  107 |     if (live) {
  108 |       expect(live).toContain('sort: [{ edge: { total_amount: DESC } }]');
  109 |       expect(live.match(/first:\s*20/g) || []).toHaveLength(1);
  110 |     }
  111 |     // Sankey is the default view; Table brings the list back.
  112 |     await expect(page.locator('#clients .money-flow-chart canvas')).toBeVisible({ timeout: DATA_LOAD_TIMEOUT });
  113 |     await page.locator('#clients .mf-view-btn[data-view="table"]').click();
  114 |     await expect(page.locator('#clients .leaf-relationship-list')).toBeVisible();
  115 |     await page.locator('#clients .mf-view-btn[data-view="sankey"]').click();
  116 |   });
  117 | 
  118 |   test('block detail renders voter data and bounded geography links', async ({ page, request }) => {
  119 |     await skipIfPremiumGated(request, '/blocks/');
  120 |     const bodies = await openLeafDetail(page, samples.block);
  121 |     const detail = bodies.find(body => body.includes('BlockDetail')) || '';
  122 |     expect(detail.match(/first:\s*20/g) || []).toHaveLength(2);
  123 |     await expect(page.locator('#registration')).toContainText('Registered');
  124 |     await expect(page.locator('#geography a').first()).toBeVisible();
  125 |   });
  126 | 
  127 |   test('block map links (/blocks/<geoid>/) resolve to the block by its derived id', async ({ page, request }) => {
  128 |     await skipIfPremiumGated(request, '/blocks/');
  129 |     const bodies = await openLeafDetail(page, { ...samples.block, path: '/blocks/461030114001020/' });
  130 |     const detail = bodies.find(body => body.includes('BlockDetail')) || '';
  131 |     expect(detail).toContain('a2e648d3-3cd7-613b-1bfe-85ea19fad69a');
  132 |     await expect(page.locator('#overview')).toContainText('461030114001020');
  133 |   });
  134 | 
  135 |   test('precinct detail bootstraps directly and renders results', async ({ page }) => {
  136 |     const bodies = await openLeafDetail(page, samples.precinct);
  137 |     const detail = bodies.find(body => body.includes('PrecinctDetail')) || '';
  138 |     expect(detail.match(/first:\s*20/g) || []).toHaveLength(1);
  139 |     await expect(page.locator('#results')).toContainText('Democratic');
  140 |     await expect(page.locator('#results')).toContainText('D+51.7%');
  141 |     await expect(page.locator('#county a').first()).toBeVisible();
  142 |   });
  143 | 
  144 |   test('tract detail remains client-rendered under the generalized leaf route', async ({ page }) => {
  145 |     const graphqlBodies: string[] = [];
  146 |     page.on('request', request => {
  147 |       if (request.method() === 'POST' && request.url().includes('/api/graphql')) {
  148 |         graphqlBodies.push(request.postData() || '');
  149 |       }
  150 |     });
  151 | 
  152 |     const response = await page.goto('/tracts/48453000101/');
  153 |     expect(response?.status()).toBe(200);
  154 |     await expect(page.locator('header h1')).toContainText('Census Tract 1.01', { timeout: DATA_LOAD_TIMEOUT });
  155 |     await expect(page).toHaveTitle(/elect\.info$/i);
  156 |     await expect(page.locator('meta[name="robots"]')).toHaveAttribute('content', 'noindex, follow');
  157 |     await expect(page.locator('link[rel="canonical"]')).toHaveAttribute('href', /^https:\/\/us\.elect\.info\/tracts\//);
  158 | 
  159 |     const detail = graphqlBodies.find(body => body.includes('TractDetail')) || '';
  160 |     expect(detail).toContain('variables');
  161 |     expect(detail.match(/limit:\s*20/g) || []).toHaveLength(3);
  162 |   });
  163 | 
  164 |   test('hub employer renders totals without issuing a live connection query', async ({ page }) => {
  165 |     const graphqlBodies: string[] = [];
  166 |     const resolveUrls: string[] = [];
  167 |     await page.route('**/_resolve/**', async route => {
  168 |       resolveUrls.push(route.request().url());
  169 |       await route.fulfill({
  170 |         status: 200,
  171 |         contentType: 'application/json',
  172 |         body: JSON.stringify({ canonical_path: '/employers/hub-employer/' }),
  173 |       });
  174 |     });
  175 |     await page.route('**/api/graphql', async route => {
  176 |       const body = route.request().postData() || '';
  177 |       graphqlBodies.push(body);
  178 |       await route.fulfill({
  179 |         status: 200,
  180 |         contentType: 'application/json',
  181 |         body: JSON.stringify({
  182 |           data: {
  183 |             employers: [{
  184 |               id: 'hub-test',
  185 |               name: 'HUB EMPLOYER',
  186 |               name_normalized: 'hub employer',
  187 |               total_employee_contributions: 123456789,
  188 |               employee_donor_count: 50000,
  189 |               contribution_count: 75000,
  190 |               committee_count: 100,
  191 |               first_contribution: '2020-01-01',
  192 |               last_contribution: '2026-01-01',
  193 |               cycles: '2020,2022,2024,2026',
  194 |             }],
  195 |           },
  196 |         }),
  197 |       });
  198 |     });
  199 | 
> 200 |     const response = await page.goto('/employers/hub%20test/');
      |                                 ^ Error: page.goto: net::ERR_CONNECTION_REFUSED at http://10.1.105.22/employers/hub%20test/
  201 |     expect(response?.status()).toBe(200);
  202 |     await expect(page.locator('.leaf-detail-page h1')).toHaveText('HUB EMPLOYER', { timeout: DATA_LOAD_TIMEOUT });
  203 |     await expect(page.locator('.leaf-hub-deferred')).toContainText('50,000 related records');
  204 |     await expect(page.locator('link[rel="canonical"]')).toHaveAttribute(
  205 |       'href', 'https://us.elect.info/employers/hub-employer/'
  206 |     );
  207 |     expect(resolveUrls).toHaveLength(1);
  208 |     expect(new URL(resolveUrls[0]).pathname).toBe('/_resolve/employers/hub%20test');
  209 |     expect(graphqlBodies).toHaveLength(1);
  210 |     expect(graphqlBodies[0]).toContain('EmployerProfile');
  211 |     expect(graphqlBodies[0]).not.toContain('individualsEmployedByConnection');
  212 |   });
  213 | 
  214 |   test('hub vendor resolves an alias without issuing a live connection query', async ({ page }) => {
  215 |     const graphqlBodies: string[] = [];
  216 |     await page.route('**/_resolve/vendors/**', async route => {
  217 |       const path = new URL(route.request().url()).pathname;
  218 |       if (path.endsWith('/hub-alias')) {
  219 |         await route.fulfill({ status: 301, headers: { Location: '/vendors/hub-canonical/' } });
  220 |         return;
  221 |       }
  222 |       await route.fulfill({
  223 |         status: 200,
  224 |         contentType: 'application/json',
  225 |         body: JSON.stringify({
  226 |           entity_id: 'hub-vendor-id',
  227 |           canonical_path: '/vendors/hub-canonical/',
  228 |         }),
  229 |       });
  230 |     });
  231 |     await page.route('**/api/graphql', async route => {
  232 |       const body = route.request().postData() || '';
  233 |       graphqlBodies.push(body);
  234 |       const id = JSON.parse(body).variables?.id;
  235 |       if (body.includes('VendorPurposeFlow')) {
  236 |         await route.fulfill({ json: { data: { vendorPurposeFlow: {
  237 |           taxonomyVersion: null, totalAmount: 0, groups: [], links: [], otherLinks: [],
  238 |         } } } });
  239 |         return;
  240 |       }
  241 |       await route.fulfill({
  242 |         status: 200,
  243 |         contentType: 'application/json',
  244 |         body: JSON.stringify({
  245 |           data: {
  246 |             vendors: id === 'hub-vendor-id' ? [{
  247 |               id: 'hub-vendor-id',
  248 |               name: 'HUB VENDOR',
  249 |               name_normalized: 'hub vendor',
  250 |               city: 'AUSTIN',
  251 |               state: 'TX',
  252 |               total_received: 987654321,
  253 |               payment_count: 75000,
  254 |               purpose_categories: 'MEDIA',
  255 |               client_count: 50000,
  256 |               first_payment: '2020-01-01',
  257 |               last_payment: '2026-01-01',
  258 |               cycles: '2020,2022,2024,2026',
  259 |             }] : [],
  260 |           },
  261 |         }),
  262 |       });
  263 |     });
  264 | 
  265 |     const response = await page.goto('/vendors/consulting/hub-alias/');
  266 |     expect(response?.status()).toBe(200);
  267 |     await expect(page.locator('.leaf-detail-page h1')).toHaveText('HUB VENDOR', { timeout: DATA_LOAD_TIMEOUT });
  268 |     await expect(page.locator('.leaf-hub-deferred')).toContainText('50,000 related records');
  269 |     await expect(page.locator('link[rel="canonical"]')).toHaveAttribute(
  270 |       'href', 'https://us.elect.info/vendors/hub-canonical/'
  271 |     );
  272 |     // Alias/canonical profiles and bounded ranked/purpose reads are allowed.
  273 |     // The hub guard must still suppress both live-client query implementations.
  274 |     expect(graphqlBodies.filter(body => body.includes('VendorProfile'))).toHaveLength(2);
  275 |     expect(graphqlBodies.filter(body => body.includes('VendorRankedClients'))).toHaveLength(1);
  276 |     const purposeReads = graphqlBodies.filter(body => body.includes('VendorPurposeFlow'));
  277 |     expect(purposeReads).toHaveLength(1);
  278 |     expect(JSON.parse(purposeReads[0]).variables).toEqual({ id: 'hub-vendor-id', cycle: null });
  279 |     expect(graphqlBodies.some(body => body.includes('"id":"hub-vendor-id"'))).toBe(true);
  280 |     expect(graphqlBodies.every(body => !body.includes('committeesPaysConnection'))).toBe(true);
  281 |     expect(graphqlBodies.every(body => !body.includes('moneyCounterparties'))).toBe(true);
  282 |     await expect(page.locator('#purposes .purpose-raw')).toBeVisible();
  283 |   });
  284 | 
  285 |   test('hub vendor lists its ranked top clients with a top-N-of-P footer and Sankey (sites#261)', async ({ page }) => {
  286 |     const graphqlBodies: string[] = [];
  287 |     await page.route('**/_resolve/**', route => route.fulfill({
  288 |       status: 200,
  289 |       contentType: 'application/json',
  290 |       body: JSON.stringify({ entity_id: 'hub-ranked', canonical_path: '/vendors/hub-ranked/' }),
  291 |     }));
  292 |     await page.route('**/api/graphql', async route => {
  293 |       const body = route.request().postData() || '';
  294 |       graphqlBodies.push(body);
  295 |       const data = body.includes('VendorRankedClients')
  296 |         ? {
  297 |             vendorClients: {
  298 |               ranked: true,
  299 |               clientCount: 61204,
  300 |               totalReceived: 987654321,
```