# 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: sales.spec.js >> Sales — Proforma Invoice >> source contains openProformaOptions function
- Location: tests/sales.spec.js:168:3

# Error details

```
Error: expect(received).toContain(expected) // indexOf

Expected substring: "function openProformaOptions("
Received string:    "404 page not found
"
```

# Test source

```ts
  72  | 
  73  |   test('source contains campaignSlug and buildCampaignIdStr helpers', async ({}) => {
  74  |     const ctx = await request.newContext();
  75  |     const resp = await ctx.get(BASE_URL);
  76  |     const src = await resp.text();
  77  |     expect(src).toContain('campaignSlug');
  78  |     expect(src).toContain('buildCampaignIdStr');
  79  |   });
  80  | 
  81  |   test('source writes new jobs directly to /db/jobs with operational_draft status', async ({}) => {
  82  |     const ctx = await request.newContext();
  83  |     const resp = await ctx.get(BASE_URL);
  84  |     const src = await resp.text();
  85  |     expect(src).toContain("fetch(`${CFG.api}/jobs`");
  86  |     expect(src).toContain("'operational_draft'");
  87  |   });
  88  | 
  89  |   test('source writes campaign_id to sales.jobs', async ({}) => {
  90  |     const ctx = await request.newContext();
  91  |     const resp = await ctx.get(BASE_URL);
  92  |     const src = await resp.text();
  93  |     expect(src).toContain('campaign_id:   campaignId');
  94  |     expect(src).toContain('job_name:      campaignId');
  95  |   });
  96  | 
  97  |   test('source has app-layer rollback on Step 2 fail', async ({}) => {
  98  |     const ctx = await request.newContext();
  99  |     const resp = await ctx.get(BASE_URL);
  100 |     const src = await resp.text();
  101 |     expect(src).toContain('App-layer rollback');
  102 |     expect(src).toContain("method:'DELETE'");
  103 |     // Duplicate campaign_id gives clear error message
  104 |     expect(src).toContain('23505');
  105 |     expect(src).toContain('already exists');
  106 |   });
  107 | 
  108 |   test('source contains zone state and loadZoneChips', async ({}) => {
  109 |     const ctx = await request.newContext();
  110 |     const resp = await ctx.get(BASE_URL);
  111 |     const src = await resp.text();
  112 |     expect(src).toContain('_selectedZones');
  113 |     expect(src).toContain('loadZoneChips');
  114 |     expect(src).toContain('toggleZone');
  115 |     expect(src).toContain('renderZoneChips');
  116 |   });
  117 | 
  118 |   test('zone selection is required (validation in saveJob)', async ({}) => {
  119 |     const ctx = await request.newContext();
  120 |     const resp = await ctx.get(BASE_URL);
  121 |     const src = await resp.text();
  122 |     expect(src).toContain('Select at least one zone');
  123 |   });
  124 | 
  125 |   test('edit mode locks campaign identity fields', async ({}) => {
  126 |     const ctx = await request.newContext();
  127 |     const resp = await ctx.get(BASE_URL);
  128 |     const src = await resp.text();
  129 |     expect(src).toContain("'f-year','f-phase','f-brand','f-client','f-campaign'");
  130 |     expect(src).toContain('el.disabled = locked');
  131 |     expect(src).toContain('zonesGroup.style.display = \'none\'');
  132 |   });
  133 | });
  134 | 
  135 | // ── Hub PWA: campaign creation removed ───────────────────────────────────────
  136 | test.describe(`Hub PWA — Campaign Creation Removed (Step 4b)`, () => {
  137 |   const HUB_URL = 'https://srv1111289.hstgr.cloud/hub/';
  138 | 
  139 |   test('hub sw.js contains a current version', async ({ page }) => {
  140 |     const resp = await page.goto(HUB_URL + 'sw.js');
  141 |     const text = await resp?.text() || '';
  142 |     expect(text).toMatch(/360lm-hub-v\d+/);
  143 |   });
  144 | 
  145 |   test('hub source no longer contains modal-new-campaign form fields', async ({}) => {
  146 |     const ctx = await request.newContext();
  147 |     const resp = await ctx.get(HUB_URL);
  148 |     const html = await resp.text();
  149 |     // Old campaign form fields should be absent
  150 |     expect(html).not.toContain('id="nc-brand"');
  151 |     expect(html).not.toContain('id="nc-client"');
  152 |     expect(html).not.toContain('id="nc-campaign"');
  153 |     // Comment confirming removal should be present
  154 |     expect(html).toContain('Campaign creation moved to Sales PWA');
  155 |   });
  156 | 
  157 |   test('hub source has stub openNewCampaign redirecting to /sales/', async ({}) => {
  158 |     const ctx = await request.newContext();
  159 |     const resp = await ctx.get(HUB_URL);
  160 |     const src = await resp.text();
  161 |     expect(src).toContain('function openNewCampaign()');
  162 |     expect(src).toContain("window.location.href = '/sales/'");
  163 |   });
  164 | });
  165 | 
  166 | // ── Proforma Invoice ─────────────────────────────────────────────────────────
  167 | test.describe(`${PWA_NAME} — Proforma Invoice`, () => {
  168 |   test('source contains openProformaOptions function', async ({}) => {
  169 |     const ctx = await request.newContext();
  170 |     const src = await (await ctx.get(BASE_URL)).text();
  171 |     await ctx.dispose();
> 172 |     expect(src).toContain('function openProformaOptions(');
      |                 ^ Error: expect(received).toContain(expected) // indexOf
  173 |     expect(src).toContain('function generateProforma(');
  174 |     expect(src).toContain('function buildProformaHtml(');
  175 |   });
  176 | 
  177 |   test('source does not contain "coming soon" placeholder for Proforma', async ({}) => {
  178 |     const ctx = await request.newContext();
  179 |     const src = await (await ctx.get(BASE_URL)).text();
  180 |     await ctx.dispose();
  181 |     expect(src).not.toContain('Proforma Invoice — coming soon');
  182 |   });
  183 | 
  184 |   test('modal-proforma-opts exists in DOM', async ({}) => {
  185 |     const ctx = await request.newContext();
  186 |     const src = await (await ctx.get(BASE_URL)).text();
  187 |     await ctx.dispose();
  188 |     expect(src).toContain('id="modal-proforma-opts"');
  189 |     expect(src).toContain('id="proforma-advance-pct"');
  190 |   });
  191 | 
  192 |   test('clients list renders Proforma Invoice button', async ({}) => {
  193 |     const ctx = await request.newContext();
  194 |     const src = await (await ctx.get(BASE_URL)).text();
  195 |     await ctx.dispose();
  196 |     expect(src).toContain("openProformaOptions('${o.offer_id}')");
  197 |     expect(src).toContain('Proforma Invoice');
  198 |   });
  199 | });
  200 | 
```