Make notifications action sweep wait for cold-load shell

This commit is contained in:
master
2026-03-11 21:19:54 +02:00
parent 66e67f1a97
commit ebc70a3611
2 changed files with 60 additions and 0 deletions

View File

@@ -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.

View File

@@ -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 {