test(e2e): tab-scope the toolbar-icon spec's action assertions#248
Merged
Conversation
The blocking/badge assertions read the globally-last setIcon/setBadgeText spy record, so a late repaint of a non-web tab — the install-time onboarding tab or the launch-initial blank tab, both of which resolve to the `active` posture (url undefined → noPage hero) — could mask the tab under test's paint and pin the 8s poll at `active` for its full timeout. Flaked exactly that way on CI run 29502943011 (PR #247): Expected "blocking", Received "active", concealment confirmed by the DOM guard. - Resolve the tab under test's id once (tabs.query on the fixture URL, brief bounded retry) and filter setIcon spy records by it; there is no getIcon API, so the spy stays — but scoped. - Read the badge via the real per-tab getBadgeText({tabId}) instead of deriving the tab from the last setBadgeText record; the badge spy is gone entirely. - off/exempt: bringToFront again after the settings flip — refreshActiveTabs repaints only the ACTIVE tab, so this guarantees an onActivated repaint of the test tab even if the onboarding tab held focus at flip time. - Poll the exempt badge clear: refreshTabIcon writes icon-then-badge, so the icon flipping to `exempt` doesn't yet prove the badge write landed. Verified: the mechanism reproduces deterministically (forcing the onboarding-tab activation after the blocking paint pins the old global read at `active` while the tab-scoped read stays `blocking`), and the fixed spec passes 40/40 at --repeat-each=10 --workers=2 plus 25/25 of the blocking test at --workers=6. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
De-flakes
apps/e2e/src/offline/toolbar-icon.behavior.spec.ts("blocking + count badge on a Russian feed" timed out on PR #247's CI run, which doesn't touch the toolbar path) by scoping every icon/badge assertion to the tab under test. Test-only — no product code changes.Root cause
The failing poll was pinned at
Received: "active"for its full 8s while the DOM guard had already confirmed concealment — so theblockingupdate did fire; the observation layer lost it:lastIconState()read the globally lastsetIconspy record, andactedTabBadge()derived its tab from the globally lastsetBadgeTextrecord.url: undefined→noPagehero →activeposture inresolveActionIconState.onActivated/onUpdatedrepaint lands seconds late, after the youtube tab'sblockingpaint. Nothing repaints the youtube tab again, so both.at(-1)reads stay pinned to the wrong tab until timeout.The mechanism was reproduced deterministically: forcing that late onboarding-tab activation after the
blockingpaint flips the old global read toactive(the exact CI signature) while a tab-scoped read staysblocking.Fix
tabIdFor()resolves the tab under test viachrome.tabs.queryon the fixture URL (brief bounded retry);lastIconState(sw, tabId)filters spy records by it. (setIconstill needs the spy — there's nogetIconAPI.)getBadgeText({tabId}); thesetBadgeTextspy is removed.bringToFront()again after the settings flip:refreshActiveTabs()repaints only the active tab, so this guarantees anonActivatedrepaint of the test tab even if the onboarding tab held focus at flip time.refreshTabIconwrites icon-then-badge, so the icon readingexemptdoesn't yet prove the badge write landed.apps/e2e/AGENTS.mdspec-table row updated to match (prettier renormalized the table's column padding, hence the whitespace-only line changes).Reviewer notes
tabs.queryreturns nourlforchrome-extension:///about:blanktabs (<all_urls>host permissions don't cover those schemes; Movar deliberately lacks thetabspermission) — that's whytabIdFormatches the http(s) fixture URLs, which are visible under the e2e build's granted host permissions.refreshTabIconpaints per-tab and Chrome holds per-tab icon/badge state; the global last-call read was simply the wrong observation point.Verification
--repeat-each=10 --workers=2(CI parity, 40 tests)--repeat-each=25 --workers=6(stress)active, tab-scoped staysblockingtsc --noEmit,eslint,prettier🤖 Generated with Claude Code