Skip to content

fix(playwright): stop notoast leaking into shared storage state - #3747

Open
EmilyRagan wants to merge 4 commits into
mainfrom
fix-playwright-toast-storage-state
Open

fix(playwright): stop notoast leaking into shared storage state#3747
EmilyRagan wants to merge 4 commits into
mainfrom
fix-playwright-toast-storage-state

Conversation

@EmilyRagan

@EmilyRagan EmilyRagan commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Problem

Enterprise Playwright CI (run 32272124083) failed 9 tests in notifications.s.spec.ts — every assertion waiting on [data-test=toast] timed out. The two tests that assert no toast passed vacuously.

That run's trace artifact shows the context's storageState for http://localhost:2900 contains:

notoast = true

Notifications.vue:316 reads it as showToast = false, so the gate at Notifications.vue:524 never queues a toast. Menu population is outside the gate, which is why the notifications badge still counted the alert.

Why it only happens under Enterprise: fixture.ts sets notoast=true for every spec (disableToasts: true), and — inside the ENTERPRISE === '1' branch only — re-saves the whole context to storageState.json after a mid-run Keycloak re-login, baking that flag in. Every later context is created from that file, and notifications.s.spec.ts's disableToasts: false only skips the init script, so it could not clear an inherited value.

Unrelated to the cosmos web-socket-api-refactor merge: core CI passes the same spec on the same commit.

Fix

  • The init script always writes the toast preference (setItem when disabling, removeItem when a spec opts out), so opting out defeats an inherited value.

  • saveAuthState allowlists what gets written to storageState.json / adminStorageState.json, keeping only session and scope keys: openc3Token, openc3RefreshToken, openc3OfflineToken, openc3LoginMode, keycloakUrl, keycloakRealm, keycloakClientId, openc3Scope. Everything else is dropped — UI preferences (notoast, toastPosition), notification read state (notificationStreamOffset, lastReadNotification, ackedAlerts), kc-callback-* redirect leftovers.

    Allowlist rather than denylist because the leak is the bug: any test-injected key that reaches that file silently changes behavior for the rest of the run, and the notification read-state keys would do it in the same way notoast did. A key missing from the allowlist fails every spec loudly instead, which is the better failure mode — but it does mean the list needs updating if auth-relevant keys are added.

  • Removed a dead commented-out setSetting(..., 'show-alerts', ...) block in the spec.

  • Hardened the two reload tests, which flaked once the toasts started working: in Enterprise every page load bounces through Keycloak, so page.reload() spends ~4s on redirect, token exchange and app boot before the message stream resubscribes and replays — all of it previously inside the 20s toast assertion. They now wait for the app bar after reloading, give the replay assertion 30s, and raise their own timeout past the 60s default so an overrun still names the failing assertion.

  • trace: 'retain-on-failure' in CI. With on-first-retry, a test that fails then passes leaves a trace of the passing run — the one that needs no diagnosing.

Verification

Enterprise run 32395585458, which did check out this branch: the 9 toast failures are gone (serial batch 47 passed). Two remaining issues, both now understood:

  • un-acked alerts survive a page reload flaked — the reload/replay timing above, addressed by the last commit and not yet re-run in CI.
  • table-manager.s.spec.ts:259 downloads binary, definition, report failed on a download timeout. Pre-existing and unrelated; it was already flaky before this branch.

Locally, against an Enterprise stack with a deliberately poisoned storageState.json (live operator session plus notoast and notificationStreamOffset):

  • opt-out clears the inherited flag and the default still sets it — passes with the fix, fails with the fixture reverted
  • forcing the re-login branch writes back exactly the allowlisted keys, and re-running against that file authenticates without another login, so the allowlist isn't too narrow
  • pnpm lint --max-warnings 0 clean

Follow-ups (not in this PR)

  • setQuiet in notifications.s.spec.ts builds a raw context from storageState.json and cannot log in. Global setup saves the operator session then logs out of it to log in as admin, so that file only works because the fixture refreshes it mid-run — the same write that was poisoning it.
  • Enterprise emits its own must-ack ALERTs (metrics_helper.rb:187) and Toast.vue shows 3 toasts at a time with duration: Infinity, so stuck system-health alerts could still starve a spec's toast on a loaded runner.

🤖 Generated with Claude Code

Enterprise runs re-save storageState.json after a mid-run Keycloak
re-login, which captured the fixture's own notoast=true and disabled
alert toasts for every later context - notifications.s.spec.ts failed 9
tests because its disableToasts: false only skipped the init script
instead of clearing the inherited value.

Always write the toast preference in the init script, and filter UI
preference keys out of the saved auth state.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@codecov

codecov Bot commented Aug 20, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 79.12%. Comparing base (9bf5634) to head (62ccdc2).
⚠️ Report is 59 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3747      +/-   ##
==========================================
- Coverage   80.00%   79.12%   -0.88%     
==========================================
  Files         885      894       +9     
  Lines       65382    66857    +1475     
  Branches     2543     2543              
==========================================
+ Hits        52309    52903     +594     
- Misses      12411    13293     +882     
+ Partials      662      661       -1     
Flag Coverage Δ
frontend 66.02% <ø> (-0.04%) ⬇️
python 79.29% <ø> (-2.58%) ⬇️
ruby-api 82.01% <ø> (-0.10%) ⬇️
ruby-backend 84.44% <ø> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@EmilyRagan
EmilyRagan marked this pull request as ready for review August 20, 2026 20:06
@EmilyRagan EmilyRagan self-assigned this Aug 20, 2026
jmthomas
jmthomas previously approved these changes Aug 21, 2026
)
}
await writeFile(path, JSON.stringify(state))
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this whole thing be better as a white list instead of a black list? Like we should probably only be saving just the auth info.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point

Denylisting notoast/toastPosition only covers the two keys that already
caused a failure. The app writes several other keys through the same
path (notificationStreamOffset, lastReadNotification, ackedAlerts,
per-tool settings), each able to change behavior for every later context.
Keep session + scope keys instead, so a leak has to be opted into.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@EmilyRagan

EmilyRagan commented Aug 21, 2026

Copy link
Copy Markdown
Contributor Author

Enterprise workflow with notification tests passing. One flaky, next commit pushed in an attempt to address that or at least make the problem more diagnosable in the future

In Enterprise every page load bounces through Keycloak, so page.reload()
spends ~4s on redirect, token exchange and app boot before the message
stream resubscribes and replays - all of it inside the 20s toast
assertion, which made "un-acked alerts survive a page reload" flaky.

Wait for the app bar after reloading, give the replay assertion 30s, and
raise the two reload tests past the 60s default so an overrun still
names the failing assertion. Also retain traces for any failed attempt:
on-first-retry only keeps the passing run's trace.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment thread playwright/playwright.config.ts Outdated
@EmilyRagan
EmilyRagan requested review from jmthomas and removed request for ryan-pratt August 21, 2026 19:42
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants