# 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/routing.spec.ts >> routing: unknown addresses >> an unknown leaf entity path serves the collection shell
- Location: tests/ui/routing.spec.ts:103:7

# Error details

```
Error: apiRequestContext.get: connect ECONNREFUSED 10.1.105.22:80
Call log:
  - → GET http://10.1.105.22/individuals/
    - user-agent: Playwright/1.64.1 (x64; ubuntu 22.04) node/20.9 CI/1
    - accept: */*
    - accept-encoding: gzip,deflate,br

```

# Test source

```ts
  1   | import { test, expect, APIRequestContext, APIResponse, BrowserContext } from '@playwright/test';
  2   | import { createHmac } from 'crypto';
  3   | 
  4   | /*
  5   |  * Shared helpers for the us.elect.info specs (copied with them into the runner's
  6   |  * tests/ui; testMatch only picks up *.spec.ts, so this file is never run itself).
  7   |  */
  8   | 
  9   | /**
  10  |  * Whether a premium section (/voters/, /individuals/, /blocks/) is closed to this
  11  |  * run's visitor: where Authentik is on, a signed-out visitor gets the outpost's
  12  |  * redirect to sign-in and a signed-in one without the entitlement nginx's 403
  13  |  * (sites#270, #305). The us.elect.info front door as an anonymous visitor is such
  14  |  * a target; CI pods and in-cluster preview (no Authentik) never are.
  15  |  */
  16  | export async function premiumGated(request: APIRequestContext, path: string): Promise<boolean> {
> 17  |   const res = await request.get(path, { maxRedirects: 0 });
      |                             ^ Error: apiRequestContext.get: connect ECONNREFUSED 10.1.105.22:80
  18  |   if (res.status() === 403) return true;
  19  |   const location = res.headers()['location'] ?? '';
  20  |   return res.status() === 302 && /\/application\/o\/authorize\/|outpost\.goauthentik\.io|\/signin\//.test(location);
  21  | }
  22  | 
  23  | /** Skip the test where `path` is a premium section this run's visitor cannot open. */
  24  | export async function skipIfPremiumGated(request: APIRequestContext, path: string): Promise<void> {
  25  |   test.skip(await premiumGated(request, path), `${path} is gated for this visitor (sign-in or premium notice)`);
  26  | }
  27  | 
  28  | /** The run's sampling seed: SAMPLE_SEED (the Tekton run id), else today, so a run is reproducible. */
  29  | export const SAMPLE_SEED = process.env.SAMPLE_SEED || new Date().toISOString().slice(0, 10);
  30  | 
  31  | function hash(s: string): number {
  32  |   let h = 2166136261;
  33  |   for (let i = 0; i < s.length; i++) {
  34  |     h ^= s.charCodeAt(i);
  35  |     h = Math.imul(h, 16777619);
  36  |   }
  37  |   return h >>> 0;
  38  | }
  39  | 
  40  | function mulberry32(seed: number): () => number {
  41  |   let a = seed;
  42  |   return () => {
  43  |     a = (a + 0x6d2b79f5) | 0;
  44  |     let t = Math.imul(a ^ (a >>> 15), 1 | a);
  45  |     t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
  46  |     return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
  47  |   };
  48  | }
  49  | 
  50  | /**
  51  |  * Up to `count` items of `array`, sampled deterministically from SAMPLE_SEED and
  52  |  * `label` (default: the current test's title), and logged — so a failing sample
  53  |  * can be re-run exactly (SAMPLE_SEED=<seed> npx playwright test ...).
  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 | 
```