# 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: client.spec.js >> Client Portal — Smoke >> sw.js contains current 360client version
- Location: tests/client.spec.js:36:3

# Error details

```
Error: expect(received).toMatch(expected)

Expected pattern: /360client-v\d+/
Received string:  "404 page not found
"
```

# Page snapshot

```yaml
- generic [ref=e2]: 404 page not found
```

# Test source

```ts
  1   | // client.spec.js — Client Portal PWA
  2   | const { test, expect, devices, request } = require('@playwright/test');
  3   | 
  4   | const PWA_NAME = 'Client Portal';
  5   | const BASE_URL  = 'https://srv1111289.hstgr.cloud/client/';
  6   | 
  7   | test.use({ ...devices['Pixel 6'] });
  8   | 
  9   | // Detect if the Client Portal HTML is fully built or still a placeholder.
  10  | // Tests that require the full HTML are skipped when only the placeholder exists.
  11  | let _clientBuilt = null;
  12  | async function isClientBuilt() {
  13  |   if (_clientBuilt !== null) return _clientBuilt;
  14  |   const ctx = await request.newContext();
  15  |   const html = await (await ctx.get(BASE_URL)).text();
  16  |   await ctx.dispose();
  17  |   _clientBuilt = html.includes('id="screen-login"');
  18  |   return _clientBuilt;
  19  | }
  20  | 
  21  | // ── Smoke ─────────────────────────────────────────────────────────────────────
  22  | test.describe(`${PWA_NAME} — Smoke`, () => {
  23  |   test('page loads with login screen', async ({ page }) => {
  24  |     test.skip(!(await isClientBuilt()), 'Client Portal HTML not yet built — placeholder only');
  25  |     await page.goto(BASE_URL);
  26  |     const body = await page.locator('body').innerText();
  27  |     expect(body.length).toBeGreaterThan(0);
  28  |     await expect(page.locator('#screen-login')).toBeVisible({ timeout: 8000 });
  29  |   });
  30  | 
  31  |   test('manifest.json returns 200', async ({ page }) => {
  32  |     const resp = await page.goto(BASE_URL + 'manifest.json');
  33  |     expect(resp?.status()).toBe(200);
  34  |   });
  35  | 
  36  |   test('sw.js contains current 360client version', async ({ page }) => {
  37  |     const resp = await page.goto(BASE_URL + 'sw.js');
  38  |     const text = await resp?.text() || '';
> 39  |     expect(text).toMatch(/360client-v\d+/);
      |                  ^ Error: expect(received).toMatch(expected)
  40  |   });
  41  | });
  42  | 
  43  | // ── HTML Structure ─────────────────────────────────────────────────────────────
  44  | test.describe(`${PWA_NAME} — HTML Structure`, () => {
  45  |   test.beforeEach(async () => {
  46  |     test.skip(!(await isClientBuilt()), 'Client Portal HTML not yet built — placeholder only');
  47  |   });
  48  | 
  49  |   test('login screen has PIN pad, Client ID tab, Company Code tab', async ({}) => {
  50  |     const ctx = await request.newContext();
  51  |     const resp = await ctx.get(BASE_URL);
  52  |     const html = await resp.text();
  53  |     expect(html).toContain('id="screen-login"');
  54  |     expect(html).toContain('id="tab-pin"');
  55  |     expect(html).toContain('id="tab-code"');
  56  |     expect(html).toContain('id="login-id"');
  57  |   });
  58  | 
  59  |   test('screens for home, recce, recce-approval, responses, acct detail all exist', async ({}) => {
  60  |     const ctx = await request.newContext();
  61  |     const resp = await ctx.get(BASE_URL);
  62  |     const html = await resp.text();
  63  |     expect(html).toContain('id="screen-home"');
  64  |     expect(html).toContain('id="screen-recce"');
  65  |     expect(html).toContain('id="screen-recce-approval"');
  66  |     expect(html).toContain('id="approval-body"');
  67  |     expect(html).toContain('id="approval-hdr-title"');
  68  |     expect(html).toContain('id="screen-responses"');
  69  |     expect(html).toContain('id="screen-acct"');
  70  |   });
  71  | 
  72  |   test('New Account modal has zone dropdown and is_head toggle', async ({}) => {
  73  |     const ctx = await request.newContext();
  74  |     const resp = await ctx.get(BASE_URL);
  75  |     const html = await resp.text();
  76  |     expect(html).toContain('id="na-zone"');
  77  |     expect(html).toContain('id="na-ishead"');
  78  |     expect(html).toContain('Head of Client');
  79  |   });
  80  | 
  81  |   test('Edit Zone / Head Status modal exists', async ({}) => {
  82  |     const ctx = await request.newContext();
  83  |     const resp = await ctx.get(BASE_URL);
  84  |     const html = await resp.text();
  85  |     expect(html).toContain('id="modal-edit-zone"');
  86  |     expect(html).toContain('id="ez-zone"');
  87  |     expect(html).toContain('id="ez-ishead"');
  88  |   });
  89  | 
  90  |   test('Add Grant modal contains campaign option', async ({}) => {
  91  |     const ctx = await request.newContext();
  92  |     const resp = await ctx.get(BASE_URL);
  93  |     const html = await resp.text();
  94  |     expect(html).toContain('value="campaign"');
  95  |     expect(html).toContain('Campaign (Recce access)');
  96  |   });
  97  | 
  98  |   test('account detail has zone-row and Edit Zone button', async ({}) => {
  99  |     const ctx = await request.newContext();
  100 |     const resp = await ctx.get(BASE_URL);
  101 |     const html = await resp.text();
  102 |     expect(html).toContain('id="acct-zone-row"');
  103 |     expect(html).toContain('openEditZoneHead');
  104 |     expect(html).toContain('Edit Zone');
  105 |   });
  106 | });
  107 | 
  108 | // ── JS Source: zone-aware access model ────────────────────────────────────────
  109 | test.describe(`${PWA_NAME} — Zone-Aware Access`, () => {
  110 |   test.beforeEach(async () => {
  111 |     test.skip(!(await isClientBuilt()), 'Client Portal HTML not yet built — placeholder only');
  112 |   });
  113 | 
  114 |   test('source contains INST_RD header for installation schema', async ({}) => {
  115 |     const ctx = await request.newContext();
  116 |     const resp = await ctx.get(BASE_URL);
  117 |     const src = await resp.text();
  118 |     expect(src).toContain('INST_RD');
  119 |     expect(src).toContain("'Accept-Profile':'installation'");
  120 |   });
  121 | 
  122 |   test('source contains computeAccessibleCampaigns and isHead logic', async ({}) => {
  123 |     const ctx = await request.newContext();
  124 |     const resp = await ctx.get(BASE_URL);
  125 |     const src = await resp.text();
  126 |     expect(src).toContain('computeAccessibleCampaigns');
  127 |     expect(src).toContain('_accessibleCampaignIds');
  128 |     expect(src).toContain('clientSession.isHead');
  129 |     expect(src).toContain('clientSession.zone');
  130 |     expect(src).toContain('is_head');
  131 |   });
  132 | 
  133 |   test('session enrichment: separate account fetch for zone/isHead', async ({}) => {
  134 |     const ctx = await request.newContext();
  135 |     const resp = await ctx.get(BASE_URL);
  136 |     const src = await resp.text();
  137 |     expect(src).toContain('fullAcct.zone');
  138 |     expect(src).toContain('fullAcct.is_head');
  139 |     expect(src).toContain('cs.isHead===undefined');
```