I have a helper that presses buttons on publishing platforms. It finds candidates by label, filters
out the ones that are not visible, checks that the point it is about to click really contains the
element it means to press, and clicks.
One night it reported zero candidates for a Publish button that was sitting in the top bar of the
page, fully visible, where I could read its label in the same breath.
The filter
The visibility test was offsetParent !== null. It is the compact idiom everyone reaches for, it
costs nothing, and it correctly rejects elements that are not rendered.
It also returns null for anything inside a fixed position container. That is where this platform puts
its action bar, and it is where most platforms put theirs.
So the candidate list was empty before any of my careful identity checking ran. The clever part of
the helper never executed, because the boring part in front of it had already thrown the answer away.
The fix, and the measurement I should have run first
I replaced it with getClientRects().length > 0 and moved on. It worked, the button was pressed, the
post went out.
Two hours later I noticed I had repeated the claim about fixed positioning three or four times in my
notes without ever measuring it. So I built five elements in a real page and read both properties on
each.
| element | offsetParent | getClientRects |
|---|---|---|
| visible, static | not null | 1 |
| visible, fixed | null | 1 |
| visible, absolute | not null | 1 |
| display none | null | 0 |
| visibility hidden | not null | 1 |
Row two confirmed what I had been asserting. Row five is the one I was not looking for: an element
with visibility: hidden is invisible to a user, and my replacement test accepts it.
So the old test had a false negative on fixed bars, and my new test has a false positive on hidden
elements. I had swapped a failure I had just been bitten by for one I had not measured, and I would
have called that progress.
What I use now
Both, together: a client rect exists and computed visibility is not hidden.
I do not claim it is complete. It covers the five cases I measured. There are others I have not
tested, opacity: 0 and zero size and elements behind an overlay among them, and the honest position
is that my test is now correct on five cases rather than correct in general.
The habit that produced this
I ran the control because I caught myself repeating a fact I had never checked. That is the only
reason. The button worked, the post was published, nothing was pushing me to look.
A claim you have repeated three times feels like something you measured. It is worth writing down
which of your working beliefs have a measurement behind them and which have only survived being said
out loud, because the second kind is where the surprises live, and they turn up in the code you wrote
to fix the last surprise.
Disclosure
I build BlueTicks for Gmail, a Chrome and Firefox extension that shows WhatsApp style ticks in your
Gmail sent list, one tick sent and two blue ticks opened. It costs 4 dollars a year, and the free
tier covers 30 emails a month. The automation above exists to publish notes about distributing it,
and it has spent more of this week catching me than catching platforms. You can find it at
blueticks.io.
Replacing a test whose failure you know with one whose failure you have not measured is not a fix. It
is a change of subject.
Top comments (0)