This article is not yet published and is not visible to search engines.
Visual Testing for Single Page Applications (SPA): More States, More Risks, More Reasons to Test

Visual Testing for Single Page Applications (SPA): More States, More Risks, More Reasons to Test

Visual Testing for Single Page Applications: More States, More Risks, More Reasons to Test

Key Takeaways

  • A SPA mechanically generates more visual states than a traditional multi-page site, and each of those states can contain a regression
  • Client-side navigation, animated transitions, and conditional rendering create visual combinations that functional tests don't cover
  • Automated visual testing is the only realistic approach to capture and verify all the states of a SPA
  • Ignoring visual testing in a SPA means accepting that most of your interface states are never verified

A Single Page Application, according to the MDN Web Docs (Mozilla Developer Network) definition, is "a web application that interacts with the user by dynamically rewriting the current page rather than loading entire new pages from the server, resulting in a more fluid user experience similar to a native application" (MDN, SPA — Single-page application).

That technical definition is elegant. The QA reality that follows from it is much less so.

Because by dynamically rewriting the page rather than loading new pages, a SPA concentrates in a single HTML document a number of visual states that far exceeds what a traditional site can produce. And each state is a place where something can display incorrectly.

Multi-page sites had an advantage nobody acknowledged

Before the SPA era, each URL corresponded to a distinct page, loaded entirely from the server. It was slow, sometimes jerky, but it had a considerable advantage from a QA perspective: each page was a discrete and stable state. You could capture it, verify it, and move on to the next.

A twenty-page site had twenty main states. You could literally list them on a whiteboard and check them off one by one during testing. It was manageable.

SPAs shattered that simplicity. A React, Vue, or Angular application of moderate complexity cannot be reduced to its routes. Each route can display dozens of different states depending on loaded data, user interactions, permissions, error conditions, and loading states.

Take a dashboard page in a SPA. It can display an initial loading state with skeletons. Then the state with data. Then the empty data state (first launch). Then the network error state. Then the state with an active filter. Then the state with an open modal. Then the state with a visible tooltip. Each of these states has a distinct visual appearance that your users will see.

Multiply that by the number of routes in your application, and you'll understand why manually testing a SPA is an illusion.

Visual challenges specific to SPAs

SPAs aren't simply websites with more states. They introduce entire categories of visual problems that don't exist in traditional multi-page sites.

Navigation without page reload

In a traditional site, navigation triggers a full page reload. The browser destroys the existing DOM, loads the new HTML, applies CSS, executes JavaScript. Each page starts from a clean state.

In a SPA, navigation modifies the existing DOM without reloading. Components are mounted and unmounted. Global state persists between views. Side effects accumulate.

This creates a subtle but real problem: the appearance of a view can depend on the navigation path taken to reach it. A component that mounts correctly when you access the route directly might display differently when you arrive after navigating from another view, because residual global state influences the rendering.

Functional tests that access each route directly via URL completely miss these regressions related to sequential navigation. Only a test that reproduces the real user journey and visually captures the result can detect them.

Transitions and animations

SPAs use transitions heavily to smooth navigation. A component doesn't disappear instantly: it animates out while the next one animates in. These transitions are a crucial part of the user experience.

And they're a prolific source of visual bugs. An interrupted transition can leave two components overlapping. A miscalibrated timing can create a content flash. A CSS animation that doesn't complete properly can leave an element in an intermediate visual state indefinitely.

Testing these transitions manually requires sustained attention and precise timing. The tester must observe the exact moment of the transition, at the right speed, on the right browser. It's humanly fragile and non-reproducible.

Automated visual testing can capture intermediate transition states by taking captures at precise moments in the animation cycle, detecting overlaps, flashes, and inconsistent states that the human eye sees but cannot systematically document.

Conditional rendering

SPAs love conditional rendering. Show this component if the user is logged in. Display this banner if the feature is in beta. Change this button if the cart has items. Hide this section if the subscription has expired.

Each condition creates a fork in the tree of possible visual states. With five independent conditions, you theoretically have thirty-two different visual combinations. With ten conditions, over a thousand.

Nobody tests a thousand combinations manually. Nobody lists them exhaustively in functional test cases. Yet they exist, and your users encounter them.

Visual testing doesn't claim to exhaustively cover these combinations. But it covers infinitely more than manual or functional tests, because each visual capture verifies the entire screen, including elements nobody thought to test explicitly.

Asynchronous loading states

In a multi-page site, loading is a binary event: the page is loaded or it isn't. In a SPA, loading is a continuous and partial process. One component can be loaded while another still awaits its data. The page is simultaneously in a "ready" and "loading" state.

These partial loading states are visually significant. Your skeletons, spinners, and placeholders are full-fledged interface elements. A poorly sized skeleton that doesn't match the final content size creates an unpleasant visual jump. A mispositioned spinner can overlap another component. A placeholder that never disappears is a visible bug.

Visual testing captures these intermediate states and compares them to their reference, detecting inconsistencies that functional tests (which typically wait for loading to complete before verifying) never see.

State management is a visual state multiplier

Modern SPA architecture relies on centralized state management (Redux, Vuex, Pinia, NgRx, Zustand). The application state is stored in a global store that determines what appears on screen.

This architectural pattern is excellent for code maintainability. It's terrible for visual testability, because it exponentially multiplies the possible state combinations.

Your store contains the authentication state. Cart state. User preferences. Cached data. Pending notifications. Feature flags. Each combination of these states potentially produces a different visual rendering.

A logged-in user with an empty cart, unread notifications, and dark mode enabled doesn't see the same thing as an anonymous visitor with light mode. And both need to be visually tested. These combinations aren't theoretical edge cases. They're real scenarios your users experience daily.

Automated visual testing allows capturing these combinations by configuring the store state before each capture. You're no longer just testing pages: you're testing complete application states, each with its verified visual rendering.

Client-side routing and its visual pitfalls

A SPA's router handles navigation on the client side. It's what enables instant view switching without reloading. But it also creates specific visual pitfalls.

The first pitfall is scroll position. In a multi-page site, each navigation resets the scroll to the top. In a SPA, scroll position is preserved between views unless the router is configured to reset it. Result: a user who navigates from a long page to a short one can end up with a blank screen, scrolled past the content. It's a visual bug that functional tests don't detect but visual testing captures immediately.

The second pitfall is deep linking. Users can arrive directly on any route of your SPA via a shared link or bookmark. The rendering must be correct even without the normal navigation context. But your SPA might need data loaded on previous routes. Without that data, components can display empty or with unexpected default values.

The third pitfall is back navigation. The browser's "back" button in a SPA doesn't reload the previous page: it restores a history state. If the state restoration is incomplete, the displayed view can be visually different from what it was originally. A visual test that compares the restored view to the original detects these inconsistencies.

A structured approach to visual testing for SPAs

Visually testing a SPA requires a structured approach that accounts for its specific complexity.

The first dimension is route coverage. Each route of your application should have at least one visual reference capture in its default state. This is the baseline level, equivalent to testing each page of a multi-page site.

The second dimension is state coverage. For each route, identify the visually distinct states: empty state, loaded state, error state, loading state. Each state deserves its own reference capture.

The third dimension is interaction coverage. Modals, dropdowns, tooltips, and side panels that open on interaction are visual states in their own right. They must be triggered and captured.

The fourth dimension is journey coverage. Some visual bugs only appear in a specific navigation context. Tests that reproduce real user journeys (login, navigate to dashboard, open an item, return to list) capture these contextual regressions.

This four-dimensional approach seems intimidating. It is, if you test manually. With an automated visual testing tool, each dimension translates into a test configuration, not hours of human labor.

SPAs deserve more visual testing, not less

There's an understandable temptation: facing the complexity of SPAs, some teams reduce their visual testing effort. "There are too many states, we can't cover everything, let's focus on functional tests."

That reaction is exactly the opposite of what's needed. The more visual states your application has, the more visual testing is necessary. It's precisely because SPAs are visually complex that manual testing isn't sufficient and visual automation becomes essential.

Functional tests verify that your application does what it should do. Visual testing verifies that it looks like what it should look like. In a SPA where appearance constantly changes based on state, this visual verification isn't a luxury. It's a necessity.

Every visually untested state is a state that can regress silently. And in a SPA, the number of untested states quickly exceeds the tested ones, unless you use a tool capable of capturing them automatically.


Frequently Asked Questions

Does visual testing work with all SPA frameworks (React, Vue, Angular, Svelte)?

Visual testing is framework-agnostic. It captures the final appearance rendered by the browser, regardless of the framework used to produce it. Whether your SPA is built with React, Vue, Angular, Svelte, or any other framework, the result is the same: HTML, CSS, and JavaScript rendered in a browser. Visual testing operates at this level, that of the final rendering, making it universally compatible.

How do you handle animations and transitions in SPA visual tests?

Animations represent a specific challenge. The best approach is to disable CSS and JavaScript animations during visual test captures to obtain stable and reproducible comparisons. Some visual testing tools offer this option natively. Alternatively, you can capture the states before and after the transition rather than during, which eliminates variability related to animation timing while verifying the start and end visual states.

How do you visually test states that depend on dynamic data (API, database)?

The standard solution is API mocking. You configure your tests to intercept network calls and return deterministic data. This allows you to reliably reproduce each visual state: data loaded, empty data, network error, slow loading. Without mocking, your visual captures would vary with each execution based on real data, making comparison impossible.

How many visual states should you test for a medium-sized SPA?

There's no magic number, but a useful rule of thumb is to cover at least three states per route: the nominal state (data loaded), the empty state (no data), and the error state. For a twenty-route SPA, that represents a minimum of sixty reference captures. Add interaction states (modals, dropdowns, panels) and you easily reach one hundred to one hundred fifty captures. That seems like a lot, but it's entirely manageable with an automated tool.

Does visual testing replace unit tests and end-to-end tests in a SPA?

No, and that's not its purpose. Unit tests verify the business logic of your components. End-to-end tests verify complete functional journeys. Visual testing verifies appearance. These three levels of testing are complementary. Visual testing fills the gap the other two leave: it detects visual regressions that go unnoticed in functional assertions. A button can be functional but invisible. A form can submit correctly but have a broken layout. Only visual testing catches these problems.

Aren't visual tests for a SPA too slow to execute?

Modern SPAs render in a few hundred milliseconds. The visual capture itself takes a few dozen additional milliseconds. Image comparison is nearly instantaneous. For a suite of one hundred captures, the total time is typically two to five minutes, including navigation time between states. That's negligible compared to the duration of a full CI/CD pipeline and infinitely faster than manual testing that would cover only a fraction of those states.


Your SPA has dozens of visual states that nobody tests? It's time to change that.

Try Delta-QA for Free →