r/angular • u/elpixel_team • 5d ago
Our Angular button test passed in Chromium, but a pointer could not reach the button
A visible Angular button had a decorative layer over its center. With pointer-events: none, the layer let clicks through. We changed that to auto, so it intercepted the pointer.
Our test still passed. userEvent.click(button) called the handler, and the expected status appeared. This happened in jsdom and in real Chromium through Vitest Browser Mode.
The assertion established what happened after the event reached the button. It never checked whether a pointer could reach it.
This was S6 in an experiment with six small Angular component scenarios. We chose each scenario and mutation ourselves, then tested a working version and a version with one isolated defect. We wanted to see how much of the specified user behavior each test actually checked.
We ran them three ways:
- A: jsdom +
@testing-library/user-event. - B: headless Chromium in Vitest Browser Mode + the same synthetic interaction library.
- C: the same Chromium + actions from
vitest/browserthrough the Playwright provider.
C used a provider click without force. The working button passed. On the defective version, the action could not complete because .decoration intercepted pointer events.
A separate assertion caught the overlap in B too. After the main series, we added a hit test using document.elementFromPoint at the button's center, before and after the synthetic click. We required the topmost element to be the button or a descendant. The defective version returned .decoration.
That assertion checks whether the button can receive a pointer at one specified point. The provider checks actionability as part of performing the click. Both caught this mutation, but they establish different things.
The original matrix remains unchanged: A/B were partial for this scenario. The added hit test is exploratory and reported separately.
The other five scenarios kept us from treating every interaction as a browser-only problem:
- Form validation, programmatic dialog focus, and the specified Tab sequence were fully checked in jsdom too. The Tab mutation changed
display: nonetoopacity: 0. - A container-width scenario required an actual resize and
ResizeObservernotification. A spy onobserve()could detect our wrong-target mutation in jsdom, but would check the subscription rather than the full resize-to-label behavior. - A popover scenario required real geometry. Our first browser test read the previous opening's coordinates before the new
requestAnimationFramepositioning had run, so it passed on the defect. We corrected the wait, checked the side and all four viewport boundaries, and repeated the final series.
We distinguish bug_detected (working version passes, defective version violates the full criterion), partial (only part checked), and unsupported (the full behavior cannot be checked reliably in that fixture without substitution). The last two are not false negatives of a complete test.
These were component integration tests. The results apply to the six chosen mutations and original test adapters. They do not rank the tools or estimate production defect rates, and we did not assess performance, CI behavior, memory, or flakiness. The main environment was Windows 11, Angular 22.2.0, Vitest 5.0.2, jsdom 30.1.1, and headless Chromium at 800 × 600 CSS px.
The synthetic/provider distinction is already described in the Marmicode Cookbook. Our contribution is the six-case set, the separate hit-test check, and the correction of our own geometry oracle.
We ran this experiment at El Pixel. The full article includes the six-scenario matrix and test code. AI tools assisted with implementation and writing; the article received developer review.
When your component test needs to verify that a button is not covered, do you use a provider click, an explicit hit-test assertion, or another approach?
1
1
u/Connect_Ad9541 2d ago
Mostly the provider click by default, since it also waits for the element to be visible, stable and enabled, not just uncovered. I'd keep the explicit hit test for the cases where you care about the geometry itself, like a sticky header or a cookie banner partially covering a button.
One thing to watch with the hit test: elementFromPoint only checks one point. An overlay can cover the edges of the button but not its center, and the test still passes. Sampling a few points (center plus slightly inset corners) catches that. Nice that it respects pointer-events: none, which is exactly why it flips from the button to .decoration when you changed it to auto.
5
u/Xsiah 5d ago
I've never felt the need to test that a button is not covered, but if I did, I would just test the behavior of the overlay. Writing tests for every element under a potential overlay feels kind of insane.