DEV Community

Cover image for Modern Frontends Don’t Have One “Ready” State
Markus Gasser
Markus Gasser

Posted on

Modern Frontends Don’t Have One “Ready” State

A lot of browser tests are still written around a simple mental model:

  1. Open the page.
  2. Wait for it to load.
  3. Interact with the final UI.
  4. Assert that the expected result appears.

That model worked reasonably well when pages arrived as complete documents and JavaScript added a few interactions afterward.

Modern frontends are different.

The page can be visible before it is interactive. A component can render three times before it settles. Text can arrive before the buttons around it. A skeleton can disappear while the real content is still being measured. A route transition can keep the previous screen in the DOM for a few hundred milliseconds. A theme preference can be applied after hydration and briefly produce the wrong colors.

There is no longer one obvious moment when the page is “ready.”

That is why some test suites look healthy in CI while users still report flickering controls, broken keyboard focus, stale content, or clicks that land on elements that are about to disappear.

The intermediate UI is part of the product

Teams often treat loading states as temporary implementation details. Users do not experience them that way.

A user on a slower device may spend several seconds looking at a skeleton screen. Someone opening a server-rendered page may try to click before hydration finishes. A user with reduced-motion preferences may receive a completely different transition path. A returning user may see the wrong theme for half a second before local storage is read.

These are real product states, even if they are short-lived.

A useful starting point is to stop asking only:

Did the final screen appear?

Instead, ask:

What states did the user pass through before the final screen appeared?

That shift immediately changes what you test.

For server-rendered applications, it is worth tracking whether your suite can actually detect client/server divergence rather than merely waiting until the browser repairs it. This guide on measuring whether a frontend test suite catches hydration mismatches provides a useful way to think about coverage.

The same problem appears in streaming interfaces. Content may be inserted in chunks, replaced, or reordered as additional data arrives. An automation agent that assumes the first plausible element is the final element can act too early. The article Why AI Test Agents Break on Streaming UIs, Skeleton States, and Incremental Renders explores why this is especially difficult for AI-driven automation.

“Visible” is not the same as “stable”

A visible element can still be unsafe to interact with.

It may be:

  • moving because a font has just loaded;
  • covered by a fading transition layer;
  • attached to a component that is about to re-render;
  • a placeholder that will be replaced;
  • part of the previous route;
  • visually complete but not yet connected to event handlers.

This is one reason fixed sleeps are so seductive. A two-second pause seems to make the problem disappear.

Until CI gets slower. Or the application gets faster. Or an animation duration changes. Or a third-party request takes 2.3 seconds.

A stronger test waits for a meaningful application condition. That might be the disappearance of a loading marker, the presence of the final record count, the completion of a network request, or a stable element that remains attached across consecutive checks.

Skeleton screens deserve particular attention because they can create both false positives and false negatives. A test may mistake a skeleton row for a real record, or wait for all placeholders to disappear even though infinite scrolling intentionally keeps one visible. This practical guide to testing skeleton screens, loading shimmers, and progressive rendering covers the problem in more detail.

Animation changes the meaning of timing

CSS View Transitions and animated route changes make applications feel smoother, but they also blur the boundary between two screens.

During a transition:

  • the old screen may still be present;
  • the new screen may already be present;
  • both can match the same selector;
  • focus may move before the animation completes;
  • screenshots may capture a blended frame;
  • clicks may hit an element that is technically visible but not usable.

The wrong response is usually to disable all animation in tests. That can be useful for a narrow set of visual checks, but it also means the test environment no longer exercises the behavior users receive.

A better strategy is to separate functional checks from motion-specific checks. Most tests can wait for a final route marker or stable state. A smaller set should deliberately verify transitions, reduced-motion behavior, focus continuity, and interruption handling.

How to Test CSS View Transitions, Route Animations, and Motion-Safe UI Changes offers a good framework for doing that without filling the suite with arbitrary delays.

Persisted preferences create hidden branches

Theme switching looks simple until persistence enters the picture.

A complete test may need to cover:

  • the default theme for a first-time visitor;
  • the operating system preference;
  • a manually selected theme;
  • persistence after refresh;
  • persistence in a new tab;
  • behavior after logout;
  • synchronization across sessions;
  • contrast and icon changes;
  • the brief state before stored preferences are applied.

This is not just a visual regression problem. It is also a browser-state problem.

The comparison in Endtest vs Playwright for testing theme switching, persisted preferences, and dark mode regression risk is useful because it looks beyond the obvious “click the theme toggle” scenario.

Whichever tool you use, the important part is making state explicit. A test should know whether it is starting with clean storage, seeded storage, or a previous session. Otherwise, failures become dependent on execution order.

High-churn interfaces expose maintenance problems quickly

Fast-changing products amplify every weakness in a test suite.

A selector tied to button text breaks when the copy team runs an experiment. A screenshot assertion fails after a spacing adjustment. A locator based on a generated class disappears after a framework upgrade. A test that expects a modal becomes invalid when the flow moves to an inline panel.

This is where maintainability matters more than how quickly the first test was recorded or coded.

A Practical Look at Endtest for Fast-Changing Frontends With Frequent Copy and Selector Drift examines that exact pressure. Two related evaluations—testing dynamic SaaS interfaces with less maintenance and using Endtest for high-churn web apps—are also useful when comparing maintenance approaches.

The broader lesson is tool-independent: design tests around stable product intent rather than incidental markup.

A test should care that the user can submit an invoice, not that the third nested <div> contains a button with exactly the same text forever.

That does not mean avoiding precise assertions. It means being precise about outcomes while being deliberate about which implementation details deserve to become dependencies.

A better model for frontend readiness

For each important workflow, define readiness at three levels:

1. Render readiness

Is the expected structure present?

This catches missing components, server failures, and major rendering problems.

2. Interaction readiness

Can the user actually operate the interface?

This includes event handlers, focus behavior, overlays, enabled controls, and elements that are no longer moving or being replaced.

3. Business readiness

Has the application reached the state that matters?

For example, the order appears in the account, the preference survives refresh, or the newly created record is available from another screen.

Many flaky tests stop at render readiness and immediately perform a business action. The unstable space between those levels is where failures hide.

Test the journey, not just the screenshot at the end

Modern frontend testing is increasingly about transitions between states.

A robust suite observes:

  • what appears first;
  • what changes next;
  • what can be interacted with at each point;
  • which state is persisted;
  • which state is temporary;
  • what happens when rendering is interrupted;
  • whether accessibility preferences produce a different path.

The final screen still matters. It is just no longer the entire story.

When a team starts treating hydration, streaming, animation, skeletons, and preference restoration as first-class behavior, a surprising number of “random” failures become understandable. More importantly, the suite starts catching the same awkward moments that users notice before those moments become support tickets.

Top comments (1)

Collapse
 
topstar_ai profile image
Luis Cruz

I particularly appreciated the point about treating loading states as temporary implementation details, when in fact they are real product states that users experience. The suggestion to shift from asking "Did the final screen appear?" to "What states did the user pass through before the final screen appeared?" is a valuable one, as it highlights the importance of testing intermediate UI states. I've found that using a combination of visual regression testing and automated tests that wait for specific application conditions, such as the disappearance of a loading marker, can help catch issues like flickering controls or stale content. Have you found any effective ways to balance the trade-off between test reliability and test execution time when dealing with complex, dynamic frontends?