Skip to content

test(web): wait for the table to leave antd's spin blur before clicking into it - #4200

Merged
lizhimins merged 1 commit into
apache:rocketmq-studiofrom
lizhimins:fix/table-spin-blur-test-race
Sep 15, 2026
Merged

lizhimins merged 1 commit into
apache:rocketmq-studiofrom
lizhimins:fix/table-spin-blur-test-race

Conversation

@lizhimins

Copy link
Copy Markdown
Member

Two frontend tests were failing intermittently with Unable to perform pointer interaction as the element has pointer-events: none — on an INPUT (a row selection checkbox) in AlertsPage.test.tsx and on an LI (a pagination item) in ConsumerPage.test.tsx. Both are the same race, and it is in the tests rather than in the pages.

Root cause

antd's Table keeps the previously rendered rows on screen while loading is true, and wraps the body — pagination included — in a Spin. Spin tracks its own spinning state (antd/es/spin/index.js:52) and only syncs it from the spinning prop inside an effect (:54-66), while the .ant-spin-blur class is keyed on that internal state (:80). So when the Table's loading prop flips to false, the blur class survives one further commit.

.ant-spin-blur sets pointer-events: none (antd/es/spin/style/index.js:133-137), covering every row control plus the pagination items that antd renders inside the same spin container (antd/es/table/InternalTable.js:396).

findByText on a row name resolves during that window because the rows are already in the DOM, and the click that follows is rejected by userEvent's pointer-events check.

Instrumenting the failing runs confirms the mechanism and rules out the alternatives:

failing run passing run
.ant-spin-blur present 1 0
.ant-spin-spinning present 1 0
checkbox disabled false false
computed pointer-events (input / row) none / none auto / auto
bulkToggleAlertRules calls 0 0
tables / text matches in DOM 1 / 1 1 / 1

The checkbox is not disabled (getCheckboxProps only disables while isActionRunning, and no action had run), there is no stale DOM from a previous test, and the row text matches exactly once — so the only thing blocking the click is the spin blur.

Why the fix is test-side

Batching setLoading(false) into the same .then as the row updates in pages/ops/alerts.tsx was tried first and reverted. It removes our own intermediate commit but not Spin's, so it lowered the failure rate without eliminating it (1 failure in 10 runs, versus 1 in 6 before). And since both microtasks drain before the browser paints, that intermediate state is invisible to users — it is only observable from tests. There was nothing to fix on the page side.

What changed

Each of the two test files gains a small wait for the target to accept pointer events, applied only where a test clicks a table-internal control immediately after a findBy* with nothing polled in between:

  • AlertsPage.test.tsx — expectRuleRowInteractive(ruleName), used at 3 sites (two 编辑 button clicks and the row checkbox before 批量启用).
  • ConsumerPage.test.tsx — findInteractivePageItem(selector) at the two pagination clicks, and expectInteractive(element) for the row checkbox that follows a page change.

Sites that already wait on something else — e.g. await waitFor(() => expect(checkbox).toBeEnabled()) at AlertsPage.test.tsx:247 and :696 — are deliberately left untouched. That poll absorbs the extra commit, which is exactly why those tests never failed. The ConsumerPage pagination waits are preventive: that file produced one observed failure with the identical signature and contains the identical code pattern.

No production code is changed.

Validation

  • npx tsc --noEmit -p tsconfig.app.json — clean.
  • npx eslint on both files — clean, 0 errors.
  • npm run build — succeeds.
  • AlertsPage.test.tsx under 3-way concurrent load: 30/30 passed (baseline before the fix: 1 failure in 6 sequential runs, and 1 in 12 on a second measurement).
  • ConsumerPage.test.tsx under 3-way concurrent load: 15/15 passed.
  • Full frontend suite (vitest run, 120 files / 980 tests): 6 consecutive clean runs, 0 failures, 0 unhandled errors. Before this change the suite produced one failure per run, alternating between these two files.

Note that tsc --noEmit -p tsconfig.json is not a usable check in this repo — tsconfig.json is a solution file with "files": [] and only project references, so it type-checks nothing and exits 0 regardless. Use tsconfig.app.json, or npm run build, whose script is tsc -b && vite build.

Follow-up (not in this PR)

The same latent pattern — findBy* on table content followed immediately by a click on a control inside the table — may exist in other test files. If more instances surface it is worth promoting one shared helper into src/test/ rather than keeping a copy per file.

Separately, 18 call sites across 12 frontend files set data in .then() and clear a loading flag in .finally(). Those are two microtasks, so they always commit an intermediate state where the data is present and the spinner is still on. Browsers drain both before painting, so this is not user-visible and is not touched here; it is noted only because it is what makes the intermediate state reachable from a test in the first place.

@RockteMQ-AI RockteMQ-AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Summary

Fix flaky frontend tests caused by antd's Spin component retaining pointer-events: none (.ant-spin-blur) for one extra commit after loading flips to false. The fix adds a small waitFor helper in each test file to wait until the target element accepts pointer events before clicking — applied only where a test clicks a table-internal control immediately after a findBy* query.

Thorough root-cause analysis in the PR description confirms this is purely a test-side race, not a page-side bug. Changes are minimal, well-scoped, and correctly placed.

LGTM.


Automated review by github-manager-bot

…ng into it

Two frontend tests failed intermittently with `Unable to perform pointer
interaction as the element has pointer-events: none` — on an INPUT (a row
selection checkbox) in AlertsPage and on an LI (a pagination item) in
ConsumerPage. Both are the same race, and it is in the tests rather than in the
pages.

antd's Table keeps the previously rendered rows on screen while `loading` is
true and wraps the body — pagination included — in a Spin. Spin tracks its own
`spinning` state (`antd/es/spin/index.js:52`) and only syncs it from the
`spinning` prop inside an effect (`:54-66`), while the `.ant-spin-blur` class is
keyed on that internal state (`:80`). So when the Table's `loading` prop flips
to false, the blur class survives one further commit. `.ant-spin-blur` sets
`pointer-events: none` (`antd/es/spin/style/index.js:133-137`), which covers
every row control and the pagination items antd renders inside the same spin
container (`antd/es/table/InternalTable.js:396`).

`findByText` on a row name resolves during that window, because the rows are
already in the DOM, and the click that follows is then rejected by userEvent's
pointer-events check. Instrumenting the failing runs confirms it: the row
checkbox reports `disabled=false` with computed `pointer-events: none` on both
the input and its row, one `.ant-spin-blur` and one `.ant-spin-spinning` present,
and no bulk action ever invoked — passing runs report `blur=0 spinning=0
pe=auto` at the same point.

Each file gains a small wait for the target to accept pointer events, applied
only where a test clicks a table-internal control immediately after a `findBy*`
with nothing polled in between. Sites that already wait on something else (for
example `waitFor(() => expect(checkbox).toBeEnabled())`) are left alone: that
poll absorbs the extra commit, which is why those tests never failed.

Batching `setLoading(false)` into the same `.then` as the row updates was tried
first and reverted. It removes our own intermediate commit but not Spin's, so it
reduced the failure rate without eliminating it; and since both microtasks drain
before the browser paints, the intermediate state is invisible to users and only
observable from tests. There was nothing to fix on the page side.

(cherry picked from commit 2ce9f90)
@lizhimins
lizhimins force-pushed the fix/table-spin-blur-test-race branch from 2ce9f90 to c183bf2 Compare September 15, 2026 13:05
@lizhimins
lizhimins merged commit 368683f into apache:rocketmq-studio Sep 15, 2026
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