# 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 >> entity counts stay above the accepted baseline
- Location: tests/ui/data-contracts.spec.ts:162:7

# Error details

```
Error: apiRequestContext.post: connect ECONNREFUSED 10.152.183.205:80
Call log:
  - → POST http://preview-us-elect-info.electinfo.svc.cluster.local/api/graphql
    - user-agent: Playwright/1.63.0 (x64; ubuntu 22.04) node/20.9 CI/1
    - accept: */*
    - accept-encoding: gzip,deflate,br
    - Cookie: ei_vt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJwcmV2aWV3LXVzLWVsZWN0LWluZm8uZWxlY3RpbmZvLnN2Yy5jbHVzdGVyLmxvY2FsIiwidGllciI6InByZW1pdW0iLCJzaWQiOiJwbGF5d3JpZ2h0LXByZXZpZXctZW5mb3JjZS0yMDI2MTAxNGIiLCJpYXQiOjE3OTE5ODcxNTksImV4cCI6MTc5MTk5Nzk1OX0.MPMcLWo7p4lqyGhNNQDHA4rj6IN6dg72sGx8jzNy-jc
    - content-type: application/json
    - content-length: 229

```

# Test source

```ts
  10  | export const SAMPLE_SEED = process.env.SAMPLE_SEED || new Date().toISOString().slice(0, 10);
  11  | 
  12  | function hash(s: string): number {
  13  |   let h = 2166136261;
  14  |   for (let i = 0; i < s.length; i++) {
  15  |     h ^= s.charCodeAt(i);
  16  |     h = Math.imul(h, 16777619);
  17  |   }
  18  |   return h >>> 0;
  19  | }
  20  | 
  21  | function mulberry32(seed: number): () => number {
  22  |   let a = seed;
  23  |   return () => {
  24  |     a = (a + 0x6d2b79f5) | 0;
  25  |     let t = Math.imul(a ^ (a >>> 15), 1 | a);
  26  |     t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
  27  |     return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
  28  |   };
  29  | }
  30  | 
  31  | /**
  32  |  * Up to `count` items of `array`, sampled deterministically from SAMPLE_SEED and
  33  |  * `label` (default: the current test's title), and logged — so a failing sample
  34  |  * can be re-run exactly (SAMPLE_SEED=<seed> npx playwright test ...).
  35  |  */
  36  | export function getRandomItems<T>(array: T[], count: number, label?: string): T[] {
  37  |   let name = label;
  38  |   if (!name) {
  39  |     try {
  40  |       name = test.info().title;
  41  |     } catch {
  42  |       name = 'sample';
  43  |     }
  44  |   }
  45  |   const rand = mulberry32(hash(`${SAMPLE_SEED}:${name}`));
  46  |   const shuffled = [...array];
  47  |   for (let i = shuffled.length - 1; i > 0; i--) {
  48  |     const j = Math.floor(rand() * (i + 1));
  49  |     [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
  50  |   }
  51  |   const picked = shuffled.slice(0, Math.min(count, array.length));
  52  |   const show = (x: T) => (x !== null && typeof x === 'object' ? JSON.stringify(x) : String(x));
  53  |   console.log(`[sample seed=${SAMPLE_SEED}] ${name}: ${picked.map(show).join(', ')}`);
  54  |   return picked;
  55  | }
  56  | 
  57  | /** Path of a URL (absolute or relative). */
  58  | export function toRelativePath(url: string): string {
  59  |   try {
  60  |     return new URL(url).pathname;
  61  |   } catch {
  62  |     return url;
  63  |   }
  64  | }
  65  | 
  66  | /** The path of the page's rel=canonical, whatever host the page was generated for. */
  67  | export function canonicalPath(html: string): string | null {
  68  |   const m = html.match(/<link[^>]+rel=['"]canonical['"][^>]+href=['"]([^'"]+)['"]/i)
  69  |     ?? html.match(/<link[^>]+href=['"]([^'"]+)['"][^>]+rel=['"]canonical['"]/i);
  70  |   if (!m) return null;
  71  |   try {
  72  |     return new URL(m[1]).pathname;
  73  |   } catch {
  74  |     return m[1];
  75  |   }
  76  | }
  77  | 
  78  | /**
  79  |  * A visitor token for the target's /api/graphql gate (electinfo/sites#277), signed
  80  |  * like nginx's njs/visitor_token.js: HS256 over {aud: host, tier, sid, iat, exp}.
  81  |  * Only with EI_VT_KEY (the target's signing key; the Tekton preview runs get it from
  82  |  * Vault), else null: a target without the gate needs no token.
  83  |  */
  84  | export function mintVisitorToken(tier: 'anon' | 'member' | 'premium', baseURL = process.env.BASE_URL || 'http://localhost'): string | null {
  85  |   const key = process.env.EI_VT_KEY;
  86  |   if (!key) return null;
  87  |   const b64url = (text: string) => Buffer.from(text).toString('base64url');
  88  |   const now = Math.floor(Date.now() / 1000);
  89  |   const claims = { aud: new URL(baseURL).host.toLowerCase(), tier, sid: `playwright-${SAMPLE_SEED}`, iat: now, exp: now + 3 * 3600 };
  90  |   const unsigned = `${b64url(JSON.stringify({ alg: 'HS256', typ: 'JWT' }))}.${b64url(JSON.stringify(claims))}`;
  91  |   return `${unsigned}.${createHmac('sha256', key).update(unsigned).digest('base64url')}`;
  92  | }
  93  | 
  94  | /**
  95  |  * Headers for a spec's own /api/graphql calls: a premium visitor token when the run
  96  |  * has the key, since anon tokens may only send the site's persisted documents and
  97  |  * the specs send their own. Without the key, none (targets without the gate).
  98  |  */
  99  | export function visitorHeaders(): Record<string, string> {
  100 |   const token = mintVisitorToken('premium');
  101 |   return token ? { Cookie: `ei_vt=${token}` } : {};
  102 | }
  103 | 
  104 | /** Run a GraphQL query against the target's /api/graphql proxy; fails with the query on HTTP or GraphQL errors. */
  105 | export async function gql<T = any>(
  106 |   request: APIRequestContext,
  107 |   query: string,
  108 |   variables: Record<string, unknown> = {},
  109 | ): Promise<T> {
> 110 |   const res: APIResponse = await request.post('/api/graphql', { data: { query, variables }, headers: visitorHeaders() });
      |                                          ^ Error: apiRequestContext.post: connect ECONNREFUSED 10.152.183.205:80
  111 |   const label = query.replace(/\s+/g, ' ').trim().slice(0, 120);
  112 |   expect(res.status(), `POST /api/graphql ${label} should answer 200`).toBe(200);
  113 |   const body = await res.json();
  114 |   expect(body.errors, `GraphQL errors for ${label}: ${JSON.stringify(body.errors)}`).toBeUndefined();
  115 |   return body.data as T;
  116 | }
  117 | 
```