Skip to content

test(console): wait for controls to enable through a shared helper - #3167

Merged
freeqaz merged 3 commits into
mainfrom
harden-2116
Oct 22, 2026
Merged

freeqaz merged 3 commits into
mainfrom
harden-2116

Conversation

@freeqaz

@freeqaz freeqaz commented Oct 22, 2026

Copy link
Copy Markdown
Collaborator

Problem

Browser tests often wait for some text, then read a control's state once: assert.equal(await create.isDisabled(), false). The control's enabled state comes from page state that can settle a task or more after that text appears (a read finishing, a refusal unlocking the form, a reload). Under load the one read sees the earlier state. That was the flake in "Create Agent names a provisioning Namespace's status before any start" (a full console run at load ~64 read Create Agent as still disabled right after the Embedded draft hint). #3101 fixed that one site with an inline .and(page.locator(":enabled")).waitFor(); the same one-shot shape is spread across the browser tests.

Change

  • New shared helper waitForEnabled(locator, timeoutMs = 10_000) in tests/browser/console-agents-browser-helpers.mjs. It waits for the element, then polls locator.isEnabled() (Playwright's own enabled check, so aria-disabled and disabled fieldsets count), and fails with the locator and the timeout.
  • 115 one-shot "expect enabled" reads (isEnabled() === true / isDisabled() === false) that follow a wait (waitFor(), login(), waitForInputValue(), waitForCreateFormReads(), ...) move onto it, in 22 browser test files. The 6 inline :enabled waits move onto it too, and console-create-agent-member.test.mjs drops its local copy (it waited on one element handle, so a re-render could leave it watching a detached node).
  • Reads that check a control is disabled at a given moment (while a request is held, after a refusal, before a recheck) stay one-shot. So do "stays editable" reads and reads made synchronously after a selectOption.
  • Not touched: console-agent-logs.test.mjs and console-agent-overview.test.mjs (another open change is editing them; two same-shape reads there can follow).

Verification

  • Each touched test file 20/20 at 2 CPUs on a remote test VM (each file run once per repeat, 10 repeats at a time): the 22 files on the second commit, then the files changed by the last commit (codex-oauth, create-agent-provisioning-400, runtime-credentials, namespace-not-ready) 20/20 on the final head.
  • Main under CPU starvation (four busy loops on the same 2 CPUs; namespace-not-ready, namespace-recheck, namespace-create, agent-draft-guards, runtime-credentials) passed 10/10, so the flake needs full-suite contention to show and the pins above are the proof available; the fix removes the race by construction.
  • eslint and prettier clean on the touched files. Two internal review passes; their findings (missed same-shape sites, helper deadline, a redundant wait) are applied.

Risks

Test-only. A wait for "enabled" would also pass if a control flickered disabled and back; each moved site was read for intent, and the ones that assert a moment (held requests, "stays editable", synchronous reads) were kept one-shot. A control that never enables now fails after 10 s instead of at once.

A control's enabled state can settle a task or more after the text a test
waits for, so a single isEnabled()/isDisabled() read right after that wait
can see the earlier state (finding 2116: Create Agent on a provisioning
Namespace read disabled at load ~64).

Add waitForEnabled() to the shared browser helpers and move every
"expect usable" read that follows a waitFor onto it (54 reads in 13 files),
plus the inline `.and(page.locator(":enabled")).waitFor()` waits. Reads that
check a control is disabled at a given moment stay one-shot.
…abled

Internal review found one-shot "expect enabled" reads of the same shape
that the first pass missed: a declaration or a one-shot assert sat between
the wait and the read, or the wait was login(), waitForInputValue(),
waitForCreateFormReads() or a local ready(). Move those 57 reads onto the
shared helper too, and drop the member test's local waitForEnabled copy
(it waited on one element handle). Disabled reads stay one-shot.

waitForEnabled() now waits for the element first, so a missing control
fails within the helper's own timeout instead of a 10 s isEnabled() wait.
Internal review notes: the steady-state Deploy reads after Channel Secret
saves, the Create Agent form unlock after a provisioning refusal, and the
Codex save after its sign-in refusal now wait too. waitForEnabled keeps
one deadline across its attached wait and its poll.
@freeqaz
freeqaz requested a review from a team as a code owner October 22, 2026 02:48
@clawsweeper

clawsweeper Bot commented Oct 22, 2026

Copy link
Copy Markdown
Contributor

🦞👀
ClawSweeper picked this up.

Pull request received. I will update this pull request when review starts.

@freeqaz
freeqaz merged commit 8c242a3 into main Oct 22, 2026
40 of 41 checks passed
@freeqaz
freeqaz deleted the harden-2116 branch October 22, 2026 02:56
freeqaz added a commit that referenced this pull request Oct 22, 2026
…ew tests (#3169)

The last one-shot 'expect enabled' reads after a wait, in the Agent logs and overview browser tests, now use the shared waitForEnabled helper (finding 2116 leftovers after #3167). Test-only.
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.

1 participant