# 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 >> the graph serves every scalar the schema declares (seeded sample per type)
- Location: tests/ui/data-contracts.spec.ts:81:7

# Error details

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

```

# Test source

```ts
  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  | function mint(tier: 'anon' | 'member' | 'premium', baseURL: string): { token: string; exp: number } | 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 exp = now + 3 * 3600;
  90  |   const claims = { aud: new URL(baseURL).host.toLowerCase(), tier, sid: `playwright-${SAMPLE_SEED}`, iat: now, exp };
  91  |   const unsigned = `${b64url(JSON.stringify({ alg: 'HS256', typ: 'JWT' }))}.${b64url(JSON.stringify(claims))}`;
  92  |   return { token: `${unsigned}.${createHmac('sha256', key).update(unsigned).digest('base64url')}`, exp };
  93  | }
  94  | 
  95  | export function mintVisitorToken(tier: 'anon' | 'member' | 'premium', baseURL = process.env.BASE_URL || 'http://localhost'): string | null {
  96  |   return mint(tier, baseURL)?.token ?? null;
  97  | }
  98  | 
  99  | /**
  100 |  * Browse as a premium visitor: the browser context gets a premium visitor token
  101 |  * (as nginx would set it), and /api/token is answered without a new cookie, so
  102 |  * auth.js's refresh for the signed-out test identity keeps it. Donor and voter
  103 |  * records are premium (sites#277). Returns false without EI_VT_KEY (a target
  104 |  * without the gate serves the pages anyway).
  105 |  */
  106 | export async function premiumVisitor(context: BrowserContext, baseURL = process.env.BASE_URL || 'http://localhost'): Promise<boolean> {
  107 |   const minted = mint('premium', baseURL);
  108 |   if (!minted) return false;
  109 |   const { hostname, protocol } = new URL(baseURL);
  110 |   const secure = protocol === 'https:';
  111 |   await context.addCookies([
  112 |     { name: 'ei_vt', value: minted.token, domain: hostname, path: '/api/', httpOnly: true, sameSite: 'Strict', secure, expires: minted.exp },
  113 |     { name: 'ei_vt_exp', value: `${minted.exp}.premium`, domain: hostname, path: '/', sameSite: 'Strict', secure, expires: minted.exp },
  114 |   ]);
  115 |   await context.route('**/api/token', route => route.fulfill({ status: 204, headers: { 'cache-control': 'no-store' } }));
  116 |   return true;
  117 | }
  118 | 
  119 | /**
  120 |  * Headers for a spec's own /api/graphql calls: a premium visitor token when the run
  121 |  * has the key, since anon tokens may only send the site's persisted documents and
  122 |  * the specs send their own. Without the key, none (targets without the gate).
  123 |  */
  124 | export function visitorHeaders(): Record<string, string> {
  125 |   const token = mintVisitorToken('premium');
  126 |   return token ? { Cookie: `ei_vt=${token}` } : {};
  127 | }
  128 | 
  129 | /** Run a GraphQL query against the target's /api/graphql proxy; fails with the query on HTTP or GraphQL errors. */
  130 | export async function gql<T = any>(
  131 |   request: APIRequestContext,
  132 |   query: string,
  133 |   variables: Record<string, unknown> = {},
  134 | ): Promise<T> {
> 135 |   const res: APIResponse = await request.post('/api/graphql', { data: { query, variables }, headers: visitorHeaders() });
      |                                          ^ Error: apiRequestContext.post: connect ECONNREFUSED 10.1.74.202:80
  136 |   const label = query.replace(/\s+/g, ' ').trim().slice(0, 120);
  137 |   expect(res.status(), `POST /api/graphql ${label} should answer 200`).toBe(200);
  138 |   const body = await res.json();
  139 |   expect(body.errors, `GraphQL errors for ${label}: ${JSON.stringify(body.errors)}`).toBeUndefined();
  140 |   return body.data as T;
  141 | }
  142 | 
```