From ebc70a3611885ed6d4e7b900ffe11acda5ab6676 Mon Sep 17 00:00:00 2001 From: master <> Date: Wed, 11 Mar 2026 21:19:54 +0200 Subject: [PATCH] Make notifications action sweep wait for cold-load shell --- ...ve_notifications_harness_truthful_waits.md | 45 +++++++++++++++++++ .../scripts/live-ops-policy-action-sweep.mjs | 15 +++++++ 2 files changed, 60 insertions(+) create mode 100644 docs/implplan/SPRINT_20260311_012_FE_live_notifications_harness_truthful_waits.md diff --git a/docs/implplan/SPRINT_20260311_012_FE_live_notifications_harness_truthful_waits.md b/docs/implplan/SPRINT_20260311_012_FE_live_notifications_harness_truthful_waits.md new file mode 100644 index 000000000..a57cc5cb1 --- /dev/null +++ b/docs/implplan/SPRINT_20260311_012_FE_live_notifications_harness_truthful_waits.md @@ -0,0 +1,45 @@ +# Sprint 20260311_012 - FE Live Notifications Harness Truthful Waits + +## Topic & Scope +- Remove the remaining cold-load false negatives from the live ops/policy Playwright sweep. +- Wait for the real notifications operator shell before asserting `New channel` and `New rule` availability on `/ops/operations/notifications`. +- Keep the harness truthful so page/action failures represent product defects rather than premature DOM checks. +- Working directory: `src/Web/StellaOps.Web`. +- Expected evidence: updated live sweep harness, clean rerun on the live stack, and a local QA-only commit. + +## Dependencies & Concurrency +- Depends on the authenticated live stack already being healthy at `https://stella-ops.local`. +- Safe parallelism: none while the live sweep is running because it mutates auth state and captures shared Playwright output. + +## Documentation Prerequisites +- `AGENTS.md` +- `docs/qa/feature-checks/FLOW.md` +- `docs/implplan/SPRINT_20260309_002_FE_live_frontdoor_canonical_route_sweep.md` + +## Delivery Tracker + +### FE-NOTIFY-HARNESS-012-001 - Wait for the real notifications shell before checking actions +Status: DONE +Dependency: none +Owners: QA, Developer +Task description: +- Live manual probes showed `/ops/operations/notifications` was healthy while the sweep still reported `New channel` and `New rule` as missing. The remaining gap was harness timing: the script checked for buttons before the operator notifications panel finished hydrating on cold loads. +- Add a notifications-shell wait gate to the live ops/policy action sweep so the harness only evaluates those actions once the real operator surface is present. + +Completion criteria: +- [x] The harness waits for the notifications shell before probing `New channel` and `New rule`. +- [x] Live rerun confirms the previous notifications false negatives are gone. +- [x] The scoped QA harness repair is committed locally. + +## Execution Log +| Date (UTC) | Update | Owner | +| --- | --- | --- | +| 2026-03-11 | Sprint created after direct manual probes on `/ops/operations/notifications` showed the product surface was healthy while the live ops/policy sweep still flagged `New channel` and `New rule` as missing. | QA | +| 2026-03-11 | Added a notifications-shell wait gate to `live-ops-policy-action-sweep.mjs`, reran the full live sweep, and confirmed `flow:New channel` plus `flow:New rule` now pass. The completed run ended with `failedActionCount=0` and `runtimeIssueCount=0`. | QA / Developer | + +## Decisions & Risks +- Decision: treat the notifications miss as a harness defect, not a product defect, because manual probes already proved the buttons existed and worked on the live stack. +- Risk: the live sweep writes shared Playwright artifacts under `src/Web/StellaOps.Web/output/playwright`; clear them before the next iteration so stale evidence does not bleed into later audits. + +## Next Checkpoints +- Archive on local commit, then clear Playwright output and start the next full scratch rebuild iteration. diff --git a/src/Web/StellaOps.Web/scripts/live-ops-policy-action-sweep.mjs b/src/Web/StellaOps.Web/scripts/live-ops-policy-action-sweep.mjs index a0e9e5d2b..a49f74fdf 100644 --- a/src/Web/StellaOps.Web/scripts/live-ops-policy-action-sweep.mjs +++ b/src/Web/StellaOps.Web/scripts/live-ops-policy-action-sweep.mjs @@ -161,6 +161,19 @@ async function navigate(page, route) { return url; } +async function waitForNotificationsPanel(page, timeoutMs = 12_000) { + await page.waitForFunction(() => { + if (document.querySelector('.notify-panel')) { + return true; + } + + return Array.from(document.querySelectorAll('h1, h2, [data-testid="page-title"], .page-title')) + .map((node) => (node.textContent || '').trim().toLowerCase()) + .some((text) => text.includes('notify control plane') || text === 'channels' || text === 'rules'); + }, { timeout: timeoutMs }).catch(() => {}); + await page.waitForTimeout(500); +} + async function findNavigationTarget(page, name, index = 0) { const candidates = [ { role: 'link', locator: page.getByRole('link', { name }) }, @@ -461,6 +474,7 @@ async function notificationsFormProbe(context, page) { actions.push(await runAction(page, route, 'flow:New channel', async () => { await navigate(page, route); + await waitForNotificationsPanel(page); const newChannel = page.getByRole('button', { name: 'New channel' }).first(); if ((await newChannel.count()) === 0) { return { @@ -516,6 +530,7 @@ async function notificationsFormProbe(context, page) { actions.push(await runAction(page, route, 'flow:New rule', async () => { await navigate(page, route); + await waitForNotificationsPanel(page); const newRule = page.getByRole('button', { name: 'New rule' }).first(); if ((await newRule.count()) === 0) { return {