Dev.to · 6 min read

The Page Looks Ready. Your Browser Test Disagrees.

The Page Looks Ready. Your Browser Test Disagrees.

A user opens a page, sees a button, and clicks it. Your automated test opens the same page, sees the same button, and fails. Sometimes the button is replaced a moment later. Sometimes a loading skeleton looks enough like the real component to confuse a selector. Sometimes the browser's address bar changes the available space, and an element moves just as the test tries to click it. The application might be broken. The test might be broken. Or both might be behaving exactly as designed, just at different moments. That's what makes modern browser testing frustrating. We build increasingly dynamic interfaces, then write tests as if pages appear all at once and stay put. A visible page isn't necessarily an interactive page Consider an app using React Suspense and server-side rendering. The server sends HTML. The browser displays it. A person looking at the screen thinks the page is ready. But JavaScript may still be loading, data may still be resolving, and React may still be attaching event handlers or replacing parts of the DOM. Your test finds the Save button and clicks it. Nothing happens. So someone adds a two-second wait. That seems to fix it, until CI gets a little slower. The better approach is to test readiness, not elapsed time. Wait for the specific control to become usable or for the application to expose the state that means the interaction can succeed. If a component passes through loading, hydrated, and interactive states, make those states observable. This guide to testing React Suspense, streaming SSR, and partial hydration lays out the important distinction between slow data, replaced DOM nodes, and lost focus. Those failures can look identical from the outside, but they need different fixes. A longer sleep is not a diagnosis. Your selector may be perfectly valid and still be wrong Now imagine a dashboard with a list of customer accounts. The test clicks the third row. It worked yesterday. Today, a new account arrives and the list reorders automatically. The third row is now a different customer. The selector didn't fail. That's worse. It worked on the wrong thing. Skeleton loaders create another version of this problem. A test sees the container, assumes the content is ready, and attempts to interact with something that hasn't been rendered yet. Virtualized lists make it even trickier because rows may not exist in the DOM until they're scrolled into view. A useful benchmark plan for dynamic lists, skeleton loaders, and selector resilience suggests measuring more than pass rate. It also looks at how often selectors need rewriting, how many special waits are required, and whether failures explain what actually happened. That seems like a much better way to compare testing frameworks than clicking the same static login form 1,000 times. For application tests, I would rather target an account by its stable identifier or accessible name than by its position on the page. And I would rather wait for a meaningful loaded state than assume the disappearance of one spinner proves everything is ready. Checkout isn't one page. It's a series of handoffs. Payment flows expose the limits of simplistic browser tests particularly well. A checkout form might live inside a third-party iframe. Then the provider redirects the user to a 3D Secure challenge. The browser returns to your site, perhaps after a reload, and the application has to recover the original order. A test that only enters card details and looks for a success message can miss the worst failures. Was the original payment intent preserved? Did the order remain associated with the correct customer? What happens if someone refreshes during the challenge or submits twice? Could you create a duplicate charge or order? The guide to testing payment iframes, redirects, and 3DS flows approaches checkout as a state-preservation problem, not just a sequence of clicks. That's the right mental model. Use a payment provider's sandbox or test environment, exercise the redirect and challenge paths, and verify the backend result. The browser reaching a thank-you page is useful evidence, but it isn't proof that the payment was processed exactly once. On mobile, even the viewport is moving Desktop tests have a fairly stable idea of how much screen space is available. Mobile browsers are different. The address bar expands and collapses. The on-screen keyboard appears. Safe-area insets protect content around notches and system UI. A fixed footer that looked fine in one screenshot can cover the only button a customer needs. This is where pixel-perfect assertions can become counterproductive. A practical guide to viewport units and safe-area testing makes a useful point: test whether content remains reachable and the layout responds correctly, not whether every coordinate remains identical after the browser chrome changes. For a mobile checkout, I'd want to know whether the Pay button stays visible or scrollable into view, whether the keyboard obscures an input, and whether fixed elements overlap important content. Those are customer problems. A three-pixel difference in an unrelated margin usually isn't. Visual tests need a review process, not just screenshots Visual regression testing sounds simple: take a screenshot, compare it with the baseline, and flag differences. Then your team updates a shared button component and gets dozens of screenshot changes. Some are intentional. Some are real regressions. Someone has to tell them apart. The more noisy the system gets, the easier it is to approve a change without looking carefully. A comparison of visual testing tools for design system teams evaluates the parts that matter after a difference is detected: noise control, baseline management, pull-request context, approvals, and ongoing maintenance. That's the interesting buying decision. Not just whether a tool can find differences, but whether people can review those differences quickly enough to keep trusting it. Test the transitions, not just the destinations If I were evaluating a browser testing setup today, I'd make a small collection of deliberately annoying scenarios before writing a large suite. A page that hydrates after the HTML appears. A list that reorders during interaction. A payment flow that leaves the site and returns. A mobile footer that has to survive changing viewport height. A component update that produces a lot of legitimate visual differences. Then I'd ask a few plain questions. Can the tests complete without arbitrary sleeps? When something fails, can we tell whether the app or the test is wrong? How much work does it take to maintain the selectors and review the screenshots? Passing the happy path matters. But real applications spend a lot of time between states: loading, updating, redirecting, returning, resizing. That's where customers find bugs. It's also where your tests need to earn their keep.

This is a summary aggregated from Dev.to. Read the complete article on the original site:

Read full article at Dev.to

More Programming & Dev News