# 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/status-banner.spec.ts >> status banner (sites#522) >> a failing feed shows nothing
- Location: tests/ui/status-banner.spec.ts:56:7

# Error details

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

```

# Test source

```ts
  1   | import { test, expect, Page } from '@playwright/test';
  2   | 
  3   | // Status banner (electinfo/sites#522): open incidents of status.elect.info, read
  4   | // through /status/banner.json, at the top of every page that loads the identity
  5   | // bar (sites/shared/identity/status-banner.js). The feed is mocked here, so the
  6   | // specs run on CI pods, preview and prod alike; the last one checks nginx's own
  7   | // /status/banner.json answers JSON (an empty list when status.elect.info is quiet
  8   | // or unreachable).
  9   | 
  10  | type Impact = { componentId: string; componentName: string; status: string };
  11  | type Incident = {
  12  |   id: string;
  13  |   name: string;
  14  |   created_at: string;
  15  |   lastUpdateStatus: string;
  16  |   componentImpacts: Impact[];
  17  |   incidentUpdates: { id: string; status: string; description: string; publicIncidentUpdateDate: string; created_at: string }[];
  18  | };
  19  | 
  20  | const US = { componentId: 'c-us', componentName: 'US Elections (us.elect.info)' };
  21  | const NEWS = { componentId: 'c-news', componentName: 'US Election News (us.elect.news)' };
  22  | const SIGNIN = { componentId: 'c-signin', componentName: 'Sign-in and accounts' };
  23  | 
  24  | function incident(id: string, name: string, impacts: Impact[], text: string, ageMinutes = 5): Incident {
  25  |   const at = new Date(Date.now() - ageMinutes * 60_000).toISOString();
  26  |   return {
  27  |     id,
  28  |     name,
  29  |     created_at: at,
  30  |     lastUpdateStatus: 'IDENTIFIED',
  31  |     componentImpacts: impacts,
  32  |     incidentUpdates: [{ id: `${id}-u1`, status: 'IDENTIFIED', description: text, publicIncidentUpdateDate: at, created_at: at }],
  33  |   };
  34  | }
  35  | 
  36  | const maintenance = (id = 'm1', impacts: Impact[] = [{ ...US, status: 'UNDER_MAINTENANCE' }], ageMinutes = 5): Incident =>
  37  |   incident(id, 'Scheduled maintenance: new data being published', impacts,
  38  |     "We're publishing an update to the elect.info data. Some pages may briefly show older figures or be unavailable.", ageMinutes);
  39  | 
  40  | async function open(page: Page, incidents: Incident[] | 'error'): Promise<void> {
  41  |   await page.route('**/status/banner.json*', r =>
  42  |     incidents === 'error' ? r.fulfill({ status: 502, body: 'bad gateway' }) : r.fulfill({ json: { incidents } }));
> 43  |   await page.goto('/');
      |              ^ Error: page.goto: net::ERR_CONNECTION_REFUSED at http://10.1.105.22/
  44  |   await page.waitForFunction(() => 'electinfoStatusBanner' in window);
  45  | }
  46  | 
  47  | const banner = (page: Page) => page.locator('#ei-status-banner');
  48  | 
  49  | test.describe('status banner (sites#522)', () => {
  50  |   test('nothing is rendered on a quiet day', async ({ page }) => {
  51  |     await open(page, []);
  52  |     await page.waitForTimeout(300);
  53  |     await expect(banner(page)).toHaveCount(0);
  54  |   });
  55  | 
  56  |   test('a failing feed shows nothing', async ({ page }) => {
  57  |     await open(page, 'error');
  58  |     await page.waitForTimeout(300);
  59  |     await expect(banner(page)).toHaveCount(0);
  60  |   });
  61  | 
  62  |   test('a release maintenance notice: blue strip above the header, text and status link', async ({ page }) => {
  63  |     await open(page, [maintenance()]);
  64  |     const strip = banner(page).locator('.ei-status');
  65  |     await expect(strip).toHaveCount(1);
  66  |     await expect(strip).toHaveAttribute('data-kind', 'maintenance');
  67  |     await expect(strip).toContainText('Maintenance:');
  68  |     await expect(strip).toContainText('new data being published');
  69  |     await expect(strip).not.toContainText('Scheduled maintenance: new data');
  70  |     await expect(strip).toContainText('Some pages may briefly show older figures');
  71  |     await expect(strip.getByRole('link', { name: 'Status page' })).toHaveAttribute('href', 'https://status.elect.info/incident/m1');
  72  |     await expect(banner(page)).toHaveAttribute('role', 'status');
  73  |     expect(await page.evaluate(() => document.body.firstElementChild?.id), 'first element of the page').toBe('ei-status-banner');
  74  |     const box = await banner(page).boundingBox();
  75  |     const header = await page.locator('body > header, header').first().boundingBox();
  76  |     expect(box && header && box.y + box.height <= header.y, 'above the page header').toBe(true);
  77  |   });
  78  | 
  79  |   test('an outage shows first, as a service issue', async ({ page }) => {
  80  |     await open(page, [maintenance(), incident('o1', 'US Elections (us.elect.info): outage', [{ ...US, status: 'MAJOR_OUTAGE' }], 'Pages are not loading.')]);
  81  |     const strips = banner(page).locator('.ei-status');
  82  |     await expect(strips).toHaveCount(2);
  83  |     await expect(strips.nth(0)).toHaveAttribute('data-kind', 'incident');
  84  |     await expect(strips.nth(0)).toContainText('Service issue:');
  85  |     await expect(strips.nth(1)).toHaveAttribute('data-kind', 'maintenance');
  86  |   });
  87  | 
  88  |   test('only incidents for this site, plus site-less components such as sign-in', async ({ page }) => {
  89  |     await open(page, [
  90  |       incident('n1', 'US Election News (us.elect.news): outage', [{ ...NEWS, status: 'MAJOR_OUTAGE' }], 'News is down.'),
  91  |       incident('s1', 'Sign-in and accounts: outage', [{ ...SIGNIN, status: 'MAJOR_OUTAGE' }], 'Sign-in is failing.'),
  92  |     ]);
  93  |     // On the prod and preview hosts the news outage stays off us.elect.info; any other
  94  |     // host (a CI pod) shows every incident.
  95  |     const host = await page.evaluate(() => window.location.hostname);
  96  |     const usSite = host === 'us.elect.info' || host === 'preview.elect.info';
  97  |     const strips = banner(page).locator('.ei-status');
  98  |     await expect(strips).toHaveCount(usSite ? 1 : 2);
  99  |     await expect(banner(page)).toContainText('Sign-in and accounts');
  100 |     if (usSite) await expect(banner(page)).not.toContainText('News is down');
  101 |   });
  102 | 
  103 |   test('a maintenance notice older than 12 h is ignored (left open by a failed release)', async ({ page }) => {
  104 |     await open(page, [maintenance('m-old', undefined, 13 * 60)]);
  105 |     await page.waitForTimeout(300);
  106 |     await expect(banner(page)).toHaveCount(0);
  107 |   });
  108 | 
  109 |   test('Dismiss hides it for the session, until the incident is updated', async ({ page }) => {
  110 |     await open(page, [maintenance()]);
  111 |     await banner(page).getByRole('button', { name: 'Dismiss this notice' }).click();
  112 |     await expect(banner(page)).toHaveCount(0);
  113 |     await page.reload();
  114 |     await page.waitForFunction(() => 'electinfoStatusBanner' in window);
  115 |     await page.waitForTimeout(300);
  116 |     await expect(banner(page)).toHaveCount(0);
  117 | 
  118 |     const updated = maintenance();
  119 |     updated.incidentUpdates.unshift({ ...updated.incidentUpdates[0], id: 'm1-u2', description: 'Nearly done.', publicIncidentUpdateDate: new Date().toISOString() });
  120 |     await page.unroute('**/status/banner.json*');
  121 |     await page.route('**/status/banner.json*', r => r.fulfill({ json: { incidents: [updated] } }));
  122 |     await page.evaluate(() => (window as unknown as { electinfoStatusBanner: { check: () => Promise<void> } }).electinfoStatusBanner.check());
  123 |     await expect(banner(page)).toContainText('Nearly done.');
  124 |   });
  125 | 
  126 |   test('feed text is shown as text, never as HTML', async ({ page }) => {
  127 |     const evil = incident('x1', '<img src=x onerror="window.__pwned=1">Outage', [{ ...US, status: 'MAJOR_OUTAGE' }], '<b>bold</b>');
  128 |     await open(page, [evil]);
  129 |     await expect(banner(page)).toContainText('<img src=x');
  130 |     await expect(banner(page).locator('img, b')).toHaveCount(0);
  131 |     expect(await page.evaluate(() => (window as unknown as { __pwned?: number }).__pwned)).toBeUndefined();
  132 |   });
  133 | 
  134 |   test('a banner goes away when the incident resolves, without a reload', async ({ page }) => {
  135 |     await open(page, [maintenance()]);
  136 |     await expect(banner(page)).toHaveCount(1);
  137 |     await page.unroute('**/status/banner.json*');
  138 |     await page.route('**/status/banner.json*', r => r.fulfill({ json: { incidents: [] } }));
  139 |     await page.evaluate(() => (window as unknown as { electinfoStatusBanner: { check: () => Promise<void> } }).electinfoStatusBanner.check());
  140 |     await expect(banner(page)).toHaveCount(0);
  141 |   });
  142 | 
  143 |   test('nginx serves /status/banner.json as JSON', async ({ request }) => {
```