Prabal Tripathi

Virtual Lists Break Your Tests

A rendering optimisation that is invisible to users and devastating to a test suite.

Here's a failure that took me a while to see, because every individual piece of it was correct.

A dropdown had grown to a few thousand entries. Someone virtualised it — only render the rows currently in view, plus a small buffer. Good change. Correct change. The list went from sluggish to instant.

A test that had passed for months started failing. Nothing about the test had changed.

What virtualisation actually does

It's worth being precise, because the mental model matters.

Virtualisation doesn't hide the off-screen rows. It doesn't render them at all. They are not in the document. If your viewport fits six rows and the buffer is eight, roughly fourteen elements exist out of three thousand.

For a human, indistinguishable from a normal list — you scroll, rows appear, everything's fine.

For a test, catastrophic, because tests don't scroll. A test asks the document whether an element exists. And for 2,986 of those rows, the honest answer is now no.

Why the error message misleads you

The test failed with "element not found."

Which sounds like the data is missing. So you check the data. The data is fine. You check the search filter. Fine. You check whether the record exists. It does. Everything you check is healthy, because nothing is broken — the element is genuinely absent from the DOM while being entirely present in the application.

"Not found" is true and completely unhelpful.

The tell

What unlocked it for me was a contrast inside the same run: one test selecting the same record passed, while the assertion failed.

The passing one went through a helper that scrolls until the row renders. The failing one asserted directly.

Two tests, same record, same run, different outcome — that gap is never about the data. It's about how each one looks.

What I'd do differently

Go through the page object, always. The helper that scrolls-then-asserts already encodes this knowledge; a raw query re-implements an assumption about rendering that's now false.

And the deeper point: a test that asserts on DOM presence is asserting on a rendering strategy. That's a detail the product is allowed to change. Writing tests that survive it isn't extra work — it's writing the test against the thing you actually care about.