Single-Page Application (SPA) Testing Guide

Single-page apps (SPAs) replace server-rendered HTML with client-side routing and JavaScript-heavy UI. They need different testing strategies than traditional web apps. This guide covers the concerns

April 20, 2026 · 3 min read · Testing Guides

Single-page apps (SPAs) replace server-rendered HTML with client-side routing and JavaScript-heavy UI. They need different testing strategies than traditional web apps. This guide covers the concerns unique to SPAs and how to address them.

What makes SPAs tricky

Test tiers

Unit

React / Vue / Angular components with Jest, Vitest, Jasmine. Fast, no browser.

Integration

Component trees with React Testing Library or Vue Test Utils. Mock API, assert rendered output.

E2E

Playwright / Cypress. Real browser, real bundle, real API (or realistic mock).

Visual regression

Percy / Applitools / Chromatic. Snapshot each route / state.

Routing

Test every route

Navigation works:


test('navigates to user profile', async ({ page }) => {
  await page.goto('/');
  await page.click('text=Profile');
  expect(page.url()).toMatch(/\/profile/);
});

Browser back / forward


await page.click('text=Profile');
await page.click('text=Settings');
await page.goBack();
expect(page.url()).toMatch(/\/profile/);

Deep link

Direct URL navigation should work:


await page.goto('/user/123');
// Assert page loaded, data fetched

State management

Redux / Zustand / Pinia / Vuex

Test stores in isolation (unit tests). Integration: mount component, dispatch actions, assert state.

URL as state

Filters, pagination in URL params. Test: change filter → URL updates → reload → same state.

Data loading

Loading states

Every async boundary has loading UI. Test: mock slow API, assert spinner shown.

Error states

API returns 500 → user sees error with retry.

Empty states

No data → meaningful empty state.

Stale data

Cache then navigate away / back. Is cached data shown or refetched?

Authentication

Login / logout routes

Protected routes redirect to login. After login, redirect to intended destination.

Token refresh

Silent refresh mid-navigation works.

Session expiry mid-use

Graceful handling.

SEO

SPAs need special SEO consideration:

Test: curl https://myapp.com | grep ""</code> should show meaningful title, not generic SPA shell.</p> <h2>Accessibility</h2> <h3>Focus management</h3> <p>Route change → focus moves to h1 / main (announced to screen readers).</p> <h3>Live regions</h3> <p>Dynamic content updates announced.</p> <h3>Keyboard navigation</h3> <p>Every interactive element reachable via Tab.</p> <h2>Performance</h2> <h3>Bundle size</h3> <p>Main bundle < 200KB ideally. Code splitting for routes.</p> <h3>Core Web Vitals</h3> <ul> <li>LCP < 2.5s</li> <li>FID < 100ms (or INP for new metric)</li> <li>CLS < 0.1</li> </ul> <h3>Lazy loading</h3> <p>Non-critical chunks load after initial paint. Test: Chrome DevTools network tab, verify below-the-fold chunks deferred.</p> <h2>Service worker</h2> <p>If using:</p> <ul> <li>SW registers after load</li> <li>Cache strategies tested (cache-first, network-first)</li> <li>Update handling clear to user</li> </ul> <h2>SPA-specific bugs</h2> <h3>Memory leak on route change</h3> <p>Listeners not cleaned up. Long session → sluggishness.</p> <p><strong>Fix:</strong> useEffect cleanup, unmount handlers.</p> <h3>Global state leak between tests</h3> <p>Singleton stores persist between tests. Test isolation broken.</p> <p><strong>Fix:</strong> Reset state in beforeEach.</p> <h3>Race condition on rapid navigation</h3> <p>User navigates fast; pending API responses update wrong page.</p> <p><strong>Fix:</strong> AbortController per request, cancel on unmount.</p> <h3>Cached data wrong after login</h3> <p>Previous user's data cached; after login, new user sees it briefly.</p> <p><strong>Fix:</strong> Clear cache on login / logout.</p> <h3>URL out of sync with UI</h3> <p>User changes filter, URL does not update, or vice versa.</p> <p><strong>Fix:</strong> Single source of truth for state in URL.</p> <h2>How SUSA tests SPAs</h2> <p>SUSA drives SPAs via Playwright. Detects:</p> <ul> <li>Route changes and state changes</li> <li>Missing loading / error states</li> <li>Broken back navigation</li> <li>Console errors</li> <li>Accessibility issues (via axe-core)</li> </ul> <pre><code> susatest-agent test https://myapp.com --persona curious --steps 200 </code></pre> <h2>Common antipatterns</h2> <ol> <li><strong>Too much in main bundle</strong> — slow first paint</li> <li><strong>No SSR for content-heavy routes</strong> — SEO suffers</li> <li><strong>useEffect without dependency array</strong> — infinite loops</li> <li><strong>Refetching on every navigation</strong> — no cache</li> <li><strong>Over-caching</strong> — stale data</li> </ol> <p>SPAs are the default for web in 2026. Test well; performance and correctness compound.</p> <div class="cta-box"> <h3>Test Your App Autonomously</h3> <p>Upload your APK or URL. SUSA explores like 10 real users — finds bugs, accessibility violations, and security issues. No scripts.</p> <a href="https://susatest.com/signup" class="cta-btn">Try SUSA Free</a> </div> <div class="related"> <h3>Related Articles</h3> <div class="related-grid"> </div> </div> </article> <footer> © 2026 SUSATest. Autonomous QA that tests your app like thousands of real users before release. </footer> </body> </html>