# 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:127:7

# Error details

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

```

# Test source

```ts
  63  | }
  64  | 
  65  | test.describe('leaf collection client-side detail pages', () => {
  66  |   test('individual detail renders a bounded contribution profile', async ({ page }) => {
  67  |     const bodies = await openLeafDetail(page, samples.individual);
  68  |     const detail = bodies.find(body => body.includes('IndividualDetail')) || '';
  69  |     expect(detail).toContain('sort: [{ edge: { total_amount: DESC } }]');
  70  |     expect(detail.match(/first:\s*20/g) || []).toHaveLength(4);
  71  |     await expect(page.locator('#committees .leaf-relationship-list')).toBeVisible();
  72  |   });
  73  | 
  74  |   test('employer detail renders totals and a bounded donor list below threshold', async ({ page }) => {
  75  |     const bodies = await openLeafDetail(page, samples.employer);
  76  |     const donors = bodies.find(body => body.includes('EmployerDonors')) || '';
  77  |     expect(donors.match(/first:\s*20/g) || []).toHaveLength(1);
  78  |     await expect(page.locator('#activity')).toContainText('Employee donors');
  79  |     await expect(page.locator('#donors .leaf-relationship-list')).toBeVisible();
  80  |   });
  81  | 
  82  |   test('vendor detail renders totals and edge-sorted committee clients', async ({ page }) => {
  83  |     const bodies = await openLeafDetail(page, samples.vendor);
  84  |     const clients = bodies.find(body => body.includes('VendorClients')) || '';
  85  |     expect(clients).toContain('sort: [{ edge: { total_amount: DESC } }]');
  86  |     expect(clients.match(/first:\s*20/g) || []).toHaveLength(1);
  87  |     await expect(page.locator('#clients .leaf-relationship-list')).toBeVisible();
  88  |   });
  89  | 
  90  |   test('block detail renders voter data and bounded geography links', async ({ page }) => {
  91  |     const bodies = await openLeafDetail(page, samples.block);
  92  |     const detail = bodies.find(body => body.includes('BlockDetail')) || '';
  93  |     expect(detail.match(/first:\s*20/g) || []).toHaveLength(2);
  94  |     await expect(page.locator('#registration')).toContainText('Registered');
  95  |     await expect(page.locator('#geography a').first()).toBeVisible();
  96  |   });
  97  | 
  98  |   test('precinct detail bootstraps directly and renders results', async ({ page }) => {
  99  |     const bodies = await openLeafDetail(page, samples.precinct);
  100 |     const detail = bodies.find(body => body.includes('PrecinctDetail')) || '';
  101 |     expect(detail.match(/first:\s*20/g) || []).toHaveLength(1);
  102 |     await expect(page.locator('#results')).toContainText('Democratic');
  103 |     await expect(page.locator('#results')).toContainText('D+51.7%');
  104 |     await expect(page.locator('#county a').first()).toBeVisible();
  105 |   });
  106 | 
  107 |   test('tract detail remains client-rendered under the generalized leaf route', async ({ page }) => {
  108 |     const graphqlBodies: string[] = [];
  109 |     page.on('request', request => {
  110 |       if (request.method() === 'POST' && request.url().includes('/api/graphql')) {
  111 |         graphqlBodies.push(request.postData() || '');
  112 |       }
  113 |     });
  114 | 
  115 |     const response = await page.goto('/tracts/48453000101/');
  116 |     expect(response?.status()).toBe(200);
  117 |     await expect(page.locator('header h1')).toContainText('Census Tract 1.01', { timeout: DATA_LOAD_TIMEOUT });
  118 |     await expect(page).toHaveTitle(/elect\.info$/i);
  119 |     await expect(page.locator('meta[name="robots"]')).toHaveAttribute('content', 'noindex, follow');
  120 |     await expect(page.locator('link[rel="canonical"]')).toHaveAttribute('href', /^https:\/\/us\.elect\.info\/tracts\//);
  121 | 
  122 |     const detail = graphqlBodies.find(body => body.includes('TractDetail')) || '';
  123 |     expect(detail).toContain('variables');
  124 |     expect(detail.match(/limit:\s*20/g) || []).toHaveLength(3);
  125 |   });
  126 | 
  127 |   test('hub employer renders totals without issuing a live connection query', async ({ page }) => {
  128 |     const graphqlBodies: string[] = [];
  129 |     const resolveUrls: string[] = [];
  130 |     await page.route('**/_resolve/**', async route => {
  131 |       resolveUrls.push(route.request().url());
  132 |       await route.fulfill({
  133 |         status: 200,
  134 |         contentType: 'application/json',
  135 |         body: JSON.stringify({ canonical_path: '/employers/hub-employer/' }),
  136 |       });
  137 |     });
  138 |     await page.route('**/api/graphql', async route => {
  139 |       const body = route.request().postData() || '';
  140 |       graphqlBodies.push(body);
  141 |       await route.fulfill({
  142 |         status: 200,
  143 |         contentType: 'application/json',
  144 |         body: JSON.stringify({
  145 |           data: {
  146 |             employers: [{
  147 |               id: 'hub-test',
  148 |               name: 'HUB EMPLOYER',
  149 |               name_normalized: 'hub employer',
  150 |               total_employee_contributions: 123456789,
  151 |               employee_donor_count: 50000,
  152 |               contribution_count: 75000,
  153 |               committee_count: 100,
  154 |               first_contribution: '2020-01-01',
  155 |               last_contribution: '2026-01-01',
  156 |               cycles: '2020,2022,2024,2026',
  157 |             }],
  158 |           },
  159 |         }),
  160 |       });
  161 |     });
  162 | 
> 163 |     const response = await page.goto('/employers/hub%20test/');
      |                                 ^ Error: page.goto: net::ERR_CONNECTION_REFUSED at http://10.1.21.217/employers/hub%20test/
  164 |     expect(response?.status()).toBe(200);
  165 |     await expect(page.locator('.leaf-detail-page h1')).toHaveText('HUB EMPLOYER', { timeout: DATA_LOAD_TIMEOUT });
  166 |     await expect(page.locator('.leaf-hub-deferred')).toContainText('50,000 related records');
  167 |     await expect(page.locator('link[rel="canonical"]')).toHaveAttribute(
  168 |       'href', 'https://us.elect.info/employers/hub-employer/'
  169 |     );
  170 |     expect(resolveUrls).toHaveLength(1);
  171 |     expect(new URL(resolveUrls[0]).pathname).toBe('/_resolve/employers/hub%20test');
  172 |     expect(graphqlBodies).toHaveLength(1);
  173 |     expect(graphqlBodies[0]).toContain('EmployerProfile');
  174 |     expect(graphqlBodies[0]).not.toContain('individualsEmployedByConnection');
  175 |   });
  176 | 
  177 |   test('hub vendor resolves an alias without issuing a live connection query', async ({ page }) => {
  178 |     const graphqlBodies: string[] = [];
  179 |     await page.route('**/_resolve/vendors/**', async route => {
  180 |       const path = new URL(route.request().url()).pathname;
  181 |       if (path.endsWith('/hub-alias')) {
  182 |         await route.fulfill({ status: 301, headers: { Location: '/vendors/hub-canonical/' } });
  183 |         return;
  184 |       }
  185 |       await route.fulfill({
  186 |         status: 200,
  187 |         contentType: 'application/json',
  188 |         body: JSON.stringify({
  189 |           entity_id: 'hub-vendor-id',
  190 |           canonical_path: '/vendors/hub-canonical/',
  191 |         }),
  192 |       });
  193 |     });
  194 |     await page.route('**/api/graphql', async route => {
  195 |       const body = route.request().postData() || '';
  196 |       graphqlBodies.push(body);
  197 |       const id = JSON.parse(body).variables?.id;
  198 |       await route.fulfill({
  199 |         status: 200,
  200 |         contentType: 'application/json',
  201 |         body: JSON.stringify({
  202 |           data: {
  203 |             vendors: id === 'hub-vendor-id' ? [{
  204 |               id: 'hub-vendor-id',
  205 |               name: 'HUB VENDOR',
  206 |               name_normalized: 'hub vendor',
  207 |               city: 'AUSTIN',
  208 |               state: 'TX',
  209 |               total_received: 987654321,
  210 |               payment_count: 75000,
  211 |               purpose_categories: 'MEDIA',
  212 |               client_count: 50000,
  213 |               first_payment: '2020-01-01',
  214 |               last_payment: '2026-01-01',
  215 |               cycles: '2020,2022,2024,2026',
  216 |             }] : [],
  217 |           },
  218 |         }),
  219 |       });
  220 |     });
  221 | 
  222 |     const response = await page.goto('/vendors/consulting/hub-alias/');
  223 |     expect(response?.status()).toBe(200);
  224 |     await expect(page.locator('.leaf-detail-page h1')).toHaveText('HUB VENDOR', { timeout: DATA_LOAD_TIMEOUT });
  225 |     await expect(page.locator('.leaf-hub-deferred')).toContainText('50,000 related records');
  226 |     await expect(page.locator('link[rel="canonical"]')).toHaveAttribute(
  227 |       'href', 'https://us.elect.info/vendors/hub-canonical/'
  228 |     );
  229 |     expect(graphqlBodies).toHaveLength(2);
  230 |     expect(graphqlBodies.every(body => body.includes('VendorProfile'))).toBe(true);
  231 |     expect(graphqlBodies.some(body => body.includes('"id":"hub-vendor-id"'))).toBe(true);
  232 |     expect(graphqlBodies.every(body => !body.includes('committeesPaysConnection'))).toBe(true);
  233 |   });
  234 | });
  235 | 
```