# 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/data-contracts.spec.ts >> graph data contracts >> county state_fips + county_fips is the GEOID tracts and blocks join on
- Location: tests/ui/data-contracts.spec.ts:25:7

# Error details

```
Error: apiRequestContext.post: connect ECONNREFUSED 10.1.105.22:80
Call log:
  - → POST http://10.1.105.22/api/graphql
    - user-agent: Playwright/1.64.1 (x64; ubuntu 22.04) node/20.9 CI/1
    - accept: */*
    - accept-encoding: gzip,deflate,br
    - content-type: application/json
    - content-length: 140

```

# Test source

```ts
  54  |  */
  55  | export function getRandomItems<T>(array: T[], count: number, label?: string): T[] {
  56  |   let name = label;
  57  |   if (!name) {
  58  |     try {
  59  |       name = test.info().title;
  60  |     } catch {
  61  |       name = 'sample';
  62  |     }
  63  |   }
  64  |   const rand = mulberry32(hash(`${SAMPLE_SEED}:${name}`));
  65  |   const shuffled = [...array];
  66  |   for (let i = shuffled.length - 1; i > 0; i--) {
  67  |     const j = Math.floor(rand() * (i + 1));
  68  |     [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
  69  |   }
  70  |   const picked = shuffled.slice(0, Math.min(count, array.length));
  71  |   const show = (x: T) => (x !== null && typeof x === 'object' ? JSON.stringify(x) : String(x));
  72  |   console.log(`[sample seed=${SAMPLE_SEED}] ${name}: ${picked.map(show).join(', ')}`);
  73  |   return picked;
  74  | }
  75  | 
  76  | /** Path of a URL (absolute or relative). */
  77  | export function toRelativePath(url: string): string {
  78  |   try {
  79  |     return new URL(url).pathname;
  80  |   } catch {
  81  |     return url;
  82  |   }
  83  | }
  84  | 
  85  | /** The path of the page's rel=canonical, whatever host the page was generated for. */
  86  | export function canonicalPath(html: string): string | null {
  87  |   const m = html.match(/<link[^>]+rel=['"]canonical['"][^>]+href=['"]([^'"]+)['"]/i)
  88  |     ?? html.match(/<link[^>]+href=['"]([^'"]+)['"][^>]+rel=['"]canonical['"]/i);
  89  |   if (!m) return null;
  90  |   try {
  91  |     return new URL(m[1]).pathname;
  92  |   } catch {
  93  |     return m[1];
  94  |   }
  95  | }
  96  | 
  97  | /**
  98  |  * A visitor token for the target's /api/graphql gate (electinfo/sites#277), signed
  99  |  * like nginx's njs/visitor_token.js: HS256 over {aud: host, tier, sid, iat, exp}.
  100 |  * Only with EI_VT_KEY (the target's signing key; the Tekton preview runs get it from
  101 |  * Vault), else null: a target without the gate needs no token.
  102 |  */
  103 | function mint(tier: 'anon' | 'member' | 'premium', baseURL: string): { token: string; exp: number } | null {
  104 |   const key = process.env.EI_VT_KEY;
  105 |   if (!key) return null;
  106 |   const b64url = (text: string) => Buffer.from(text).toString('base64url');
  107 |   const now = Math.floor(Date.now() / 1000);
  108 |   const exp = now + 3 * 3600;
  109 |   const claims = { aud: new URL(baseURL).host.toLowerCase(), tier, sid: `playwright-${SAMPLE_SEED}`, iat: now, exp };
  110 |   const unsigned = `${b64url(JSON.stringify({ alg: 'HS256', typ: 'JWT' }))}.${b64url(JSON.stringify(claims))}`;
  111 |   return { token: `${unsigned}.${createHmac('sha256', key).update(unsigned).digest('base64url')}`, exp };
  112 | }
  113 | 
  114 | export function mintVisitorToken(tier: 'anon' | 'member' | 'premium', baseURL = process.env.BASE_URL || 'http://localhost'): string | null {
  115 |   return mint(tier, baseURL)?.token ?? null;
  116 | }
  117 | 
  118 | /**
  119 |  * Browse as a premium visitor: the browser context gets a premium visitor token
  120 |  * (as nginx would set it), and /api/token is answered without a new cookie, so
  121 |  * auth.js's refresh for the signed-out test identity keeps it. Donor and voter
  122 |  * records are premium (sites#277). Returns false without EI_VT_KEY (a target
  123 |  * without the gate serves the pages anyway).
  124 |  */
  125 | export async function premiumVisitor(context: BrowserContext, baseURL = process.env.BASE_URL || 'http://localhost'): Promise<boolean> {
  126 |   const minted = mint('premium', baseURL);
  127 |   if (!minted) return false;
  128 |   const { hostname, protocol } = new URL(baseURL);
  129 |   const secure = protocol === 'https:';
  130 |   await context.addCookies([
  131 |     { name: 'ei_vt', value: minted.token, domain: hostname, path: '/api/', httpOnly: true, sameSite: 'Strict', secure, expires: minted.exp },
  132 |     { name: 'ei_vt_exp', value: `${minted.exp}.premium`, domain: hostname, path: '/', sameSite: 'Strict', secure, expires: minted.exp },
  133 |   ]);
  134 |   await context.route('**/api/token', route => route.fulfill({ status: 204, headers: { 'cache-control': 'no-store' } }));
  135 |   return true;
  136 | }
  137 | 
  138 | /**
  139 |  * Headers for a spec's own /api/graphql calls: a premium visitor token when the run
  140 |  * has the key, since anon tokens may only send the site's persisted documents and
  141 |  * the specs send their own. Without the key, none (targets without the gate).
  142 |  */
  143 | export function visitorHeaders(): Record<string, string> {
  144 |   const token = mintVisitorToken('premium');
  145 |   return token ? { Cookie: `ei_vt=${token}` } : {};
  146 | }
  147 | 
  148 | /** Run a GraphQL query against the target's /api/graphql proxy; fails with the query on HTTP or GraphQL errors. */
  149 | export async function gql<T = any>(
  150 |   request: APIRequestContext,
  151 |   query: string,
  152 |   variables: Record<string, unknown> = {},
  153 | ): Promise<T> {
> 154 |   const res: APIResponse = await request.post('/api/graphql', { data: { query, variables }, headers: visitorHeaders() });
      |                                          ^ Error: apiRequestContext.post: connect ECONNREFUSED 10.1.105.22:80
  155 |   const label = query.replace(/\s+/g, ' ').trim().slice(0, 120);
  156 |   expect(res.status(), `POST /api/graphql ${label} should answer 200`).toBe(200);
  157 |   const body = await res.json();
  158 |   expect(body.errors, `GraphQL errors for ${label}: ${JSON.stringify(body.errors)}`).toBeUndefined();
  159 |   return body.data as T;
  160 | }
  161 | 
```