How to Test Pagination on Web (Complete Guide)

Pagination controls how users navigate large data sets on the web. When it fails, users cannot reach needed information, forms lose context, and infinite‑scroll loops can exhaust browser memory. In pr

April 24, 2026 · 15 min read · How-To Guides

Why Pagination Testing Matters

Pagination controls how users navigate large data sets on the web. When it fails, users cannot reach needed information, forms lose context, and infinite‑scroll loops can exhaust browser memory. In production, broken pagination shows up as missing pages, duplicate entries, or server errors that only appear under load. Because pagination touches routing, state management, API contracts, and UI rendering, a single defect can cascade into broken analytics, abandoned funnels, and compliance gaps (e.g., WCAG 2.4.7 Focus Visible). Testing it early prevents costly hotfixes and protects user trust.

Core Concepts of Pagination

Understanding the mechanics behind pagination helps you design effective tests.

Types of Pagination

  1. Numbered links – classic “1 2 3 … 10” UI.
  2. Load more / infinite scroll – a button or automatic fetch that appends rows.
  3. Cursor‑based – uses opaque tokens (often base64‑encoded) to request the next slice.
  4. Page size selector – lets users change how many items appear per page.

Typical Implementation Flow

  1. User interacts with a pagination control (click, scroll, keypress).
  2. Front‑end updates URL query params (?page=2&size=20) or internal state.
  3. A network request fetches the next slice (REST, GraphQL, or WebSocket).
  4. Response data is merged with existing list, UI updates, and focus may shift.
  5. Browser history may be updated via pushState or replaceState.

Failure Modes to Watch For

Test Matrix for Pagination

Below is a comprehensive matrix that covers happy paths, error paths, edge cases, accessibility, and security concerns. Each cell indicates a test objective; you can mark it as PASS/FAIL during execution.

CategoryTest IDDescriptionExpected Result
Happy PathHP1Navigate from page 1 to the last page using numbered links.URL updates correctly, data matches server page, no duplicate/missing items.
Happy PathHP2Click “Load more” until all items are loaded.Request count equals ceil(total/size); final list length equals total items.
Happy PathHP3Change page size via dropdown and verify first page reflects new size.Request includes new size param; UI shows correct number of items.
Error PathEP1Request a page number beyond the last page (e.g., ?page=999).Server returns empty array or 404; UI shows “No results” and disables next button.
Error PathEP2Tamper with cursor token (invalid base64).Server returns 400 Bad Request; UI shows error toast and does not crash.
Error PathEP3Simulate network failure on a page‑change request.UI displays retry button; no partial data is shown; state reverts to previous page.
Edge CaseEC1Rapid double‑click on next button (two clicks within 50 ms).Only one request sent; UI does not queue duplicate requests.
Edge CaseEC2Scroll to bottom while a load‑more request is pending (infinite scroll).No second request triggered until first resolves; UI shows loading indicator.
Edge CaseEC3Change page size while on a middle page (e.g., size=10 on page 3).New request uses same page index but new size; UI recalculates start offset.
Edge CaseEC4Browser back/forward navigation after paginated navigation.URL and UI reflect the exact page state at that history entry.
AccessibilityAC1Keyboard navigation: Tab into pagination controls, use Arrow keys to change page.Focus moves predictably; screen reader announces new page number and item count.
AccessibilityAC2Verify sufficient contrast for disabled vs. enabled pagination buttons (WCAG 2.1).Contrast ratio ≥ 4.5:1 for normal text, ≥ 3:1 for large text.
AccessibilityAC3Ensure ARIA labels or aria‑pressed reflect current page state.Assistive tech announces “Page 3 of 10, button disabled” when appropriate.
Security/PrivacySE1Inspect pagination tokens in URL or headers for predictable patterns.Tokens are opaque, high‑entropy, and not guessable; no sequential IDs exposed.
Security/PrivacySE2Attempt to enumerate users by iterating page numbers with a small page size.Rate limiting or authentication prevents mass enumeration; logs show abnormal activity.
PerformancePE1Measure time to first byte and DOM update for a page change under 3G throttling.Total latency ≤ 2 seconds; UI shows loading spinner; no layout thrash.
PerformancePE2Verify that rapid page changes do not cause memory leak (detach listeners).Heap size remains stable after 50 rapid navigations (Chrome DevTools memory tab).

Manual Testing Approach

A structured manual session catches issues that automated scripts might overlook, especially those tied to timing, visual state, or assistive tech.

Preparation

  1. Identify pagination variants – list all places where pagination appears (tables, lists, galleries, infinite feeds).
  2. Gather test data – ensure a known total count (e.g., 253 items) and ability to control page size via API or UI.
  3. Set up environment – disable caching, enable network throttling (Chrome DevTools → Network → Slow 3G), and turn on “Show layout shift regions”.

Execution Steps

  1. Load the page – verify initial request includes default page and size.
  2. Navigate via numbered links – click each link sequentially; after each click:
  1. Test “Load more” – repeatedly click until the button disappears or changes state.
  1. Alter page size – open the size dropdown, select a new value (e.g., 50).
  1. Simulate error conditions – use DevTools to block specific requests or modify responses.
  1. Keyboard and screen‑reader checks
  1. Visual regression – take screenshots of the pagination bar at each state (first, middle, last, disabled) and compare against a baseline with a tool like Percy or Storybook shots.

Documentation

Record each step in a test‑case management tool (e.g., TestRail) with columns for Test ID, Steps, Expected, Actual, Status, and Notes. Attach network logs and screenshots for failures. This traceability helps developers reproduce timing‑dependent bugs.

Automated Approaches and Tooling

Automation accelerates regression and enables continuous validation across browsers and devices.

Choosing a Framework

Below we illustrate Playwright because it offers fine‑grained request mocking and built‑in accessibility testing.

Basic Setup


# Install dependencies
npm i -D @playwright/test playwright-axe
npx playwright install

Test File: pagination.spec.js


const { test, expect } = require('@playwright/test');
const { injectAxe, checkA11y } = require('playwright-axe');

test.describe('Pagination component', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('https://example.com/products');
    await injectAxe(page);
  });

  test('navigates via numbered links', async ({ page }) => {
    // Grab total count from an API stub or UI badge
    const totalText = await page.textContent('.total-items');
    const total = parseInt(totalText.replace(/\D/g, ''), 10);
    const pageSize = 20;
    const lastPage = Math.ceil(total / pageSize);

    for (let p = 1; p <= lastPage; p++) {
      await page.click(`text=${p}`);
      await expect(page).toHaveURL(/.*[?&]page=${p}/);
      const items = await page.$$eval('.product-card', els => els.length);
      expect(items).toBe(p === lastPage ? total % pageSize || pageSize : pageSize);
    }
  });

  test('load more button works', async ({ page }) => {
    await page.click('text=Load more');
    await page.waitForResponse(resp => resp.url().includes('/api/products') && resp.status() === 200);
    const firstBatch = await page.$$eval('.product-card', els => els.length);
    // Assume API returns 20 per load
    expect(firstBatch).toBe(20);
    await page.click('text=Load more');
    await page.waitForResponse(resp => resp.url().includes('/api/products') && resp.status() === 200);
    const secondBatch = await page.$$eval('.product-card', els => els.length);
    expect(secondBatch).toBe(40);
  });

  test('handles out‑of‑range page gracefully', async ({ page }) => {
    await page.goto('https://example.com/products?page=999');
    await expect(page.locator('.no-results')).toBeVisible();
    await expect(page.locator('button[aria-label="Next page"]')).toBeDisabled();
  });

  test('keyboard navigation announces page change', async ({ page }) => {
    await page.focus('.pagination-next');
    await page.press('ArrowLeft'); // move to previous page button
    await expect(page).toHaveFocus('.pagination-prev');
    const announcement = await page.locator('[aria-live="polite"]').innerText();
    expect(announcement).toContain('Page');
  });

  test('passes basic axe accessibility checks', async ({ page }) => {
    const accessibilityScanResults = await checkA11y(page, { 
      // exclude known false positives if any
      excludedSelectors: ['.cookie-banner'] 
    });
    expect(accessibilityScanResults.violations).toEqual([]);
  });
});

#### Explanation of Key Techniques

Parallel Execution Across Browsers


// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  workers: process.env.CI ? 4 : 2,
  reporter: 'html',
  use: {
    baseURL: 'https://example.com',
    trace: 'on-first-retry',
  },
  projects: [
    { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
    { name: 'firefox',  use: { ...devices['Desktop Firefox'] } },
    { name: 'webkit',   use: { ...devices['Desktop Safari'] } },
  ],
});

Run with npx playwright test. This matrix guarantees that pagination behaves identically across rendering engines.

Leveraging Mock Servers for Edge Cases

Use MSW (Mock Service Worker) or Playwright’s route to simulate latency page.route to inject latency, error codes, or malformed payloads.


test('shows retry UI on 500 error', async ({ page }) => {
  await page.route('**/api/products', async route => {
    const response = await route.fetch();
    // Force a 500 on the second request
    if (response.url().includes('page=2')) {
      return route.fulfill({ status: 500, body: JSON.stringify({ error: 'server' }) });
    }
    return route.continue();
  });

  await page.click('text=2');
  await expect(page.locator('.error-banner')).toContainText('Something went wrong');
  await expect(page.locator('button:has-text("Retry")')).toBeEnabled();
});

Edge Cases That Only Show Up in Production

Some defects stay hidden in staging because they depend on real‑world data volume, user behavior patterns, or infrastructural quirks.

1. Skewed Data Distribution

When a dataset contains a few extremely large records (e.g., a user profile with a 10 MB avatar), the payload size varies per page. A page that happens to include several large items may exceed the browser’s memory budget, causing a slowdown or crash, while other pages are fine.

Test: Use a data generator that inserts a few “heavy” rows at known offsets (e.g., every 500th record). Run the pagination flow and monitor Chrome’s Performance tab for JS heap spikes (> 150 MB) or long frames (> 50 ms).

2. Race Conditions with Cache Invalidation

If the frontend caches pages in a Map or Redux store and the backend updates data while the user is paging, the UI may show stale items or duplicate entries after a refresh.

Test: While a pagination request is in flight, use DevTools to modify the API response (change an item’s ID). After the request resolves, manually trigger a cache‑busting reload (e.g., change a query param) and verify that the UI reflects the updated data without showing both old and new versions of the same record.

3. Infinite Scroll Threshold Miscalculation

Some implementations trigger the next load when the scroll position is within 200 px of the bottom. On high‑resolution screens or when the browser’s zoom level changes, the threshold may fire too early or too late, resulting in duplicate requests or a perceived “stall”.

Test: Set device pixel ratio to 2.0 (via Playwright’s use: { deviceScaleFactor: 2 }) and zoom to 150 %. Scroll to the bottom and count network requests; ensure exactly one request fires per visual viewport transition.

4. Server‑Side Cursor Exhaustion

Cursor‑based pagination can leak state if the server does not invalidate old cursors after a dataset mutation (e.g., a record deletion). Users holding an old cursor may skip items or see gaps.

Test: Create a cursor for page 3, then delete a record that lies before that cursor via an admin API. Use the old cursor to request the next page and assert that the returned set does not contain a gap (i.e., item indices are contiguous).

5. Interactions with Browser Extensions

Ad blockers or privacy extensions sometimes strip query parameters they deem tracking (e.g., utm_*). If your pagination relies on those parameters for state, the extension can break navigation.

Test: Install a popular extension like uBlock Origin in a headless Chrome variant, launch the app, and paginate through several pages. Confirm that URL parameters remain intact and that the UI does not fall back to the first page.

6. Pagination Combined with Lazy‑Loaded Images

When each page includes images that lazy‑load via IntersectionObserver, a rapid page change can leave observers attached to removed elements, causing memory leaks or stray network calls for images that are no longer in the DOM.

Test: Use Chrome’s DevTools → Memory → Take heap snapshot before and after 20 rapid page changes. Compare snapshot diffs; ensure no detached IntersectionObserver objects accumulate.

Document each of these edge cases in your test plan, and add a corresponding automated check where feasible (e.g., using Playwright to set device scale factor, inject latency, or manipulate cached responses).

Accessibility and Security Considerations

Pagination intersects with both accessibility guidelines and security best practices. Overlooking either can lead to compliance risks or exploitable flaws.

Accessibility Checklist (WCAG 2.2)

CriterionWhat to VerifyHow to Test
2.4.3 Focus OrderFocus moves logically when paging via keyboard.Tab into pagination controls; verify focus order matches visual order.
2.4.7 Focus VisibleKeyboard focus indicator is visible.Ensure outline or custom focus style meets 3:1 contrast against background.
1.4.3 Contrast (Minimum)Text and icons on pagination buttons meet contrast ratio.Use axe-core or manual colour contrast analyzer.
1.3.1 Info and RelationshipsPage number conveyed via ARIA label or aria‑current.Inspect DOM: .
4.1.2 Name, Role, ValueCustom pagination widgets have appropriate roles.If using divs, add role="navigation" and aria-label="Pagination"; ensure keyboard operability.
2.5.3 Label in NameVisible label matches accessible name.For a button showing “Next”, aria-label should be “Next page” or exactly “Next”.
2.2.2 Pause, Stop, HideAuto‑advancing carousels (if used as pagination) can be paused.If autoplay exists, verify a pause button is present and functional.

Run these checks automatically with playwright-axe or eslint-plugin-jsx-a11y in your CI pipeline.

Security and Privacy Review

ConcernRiskMitigation Test
Token enumerationAttackers guess next/prev cursor to scrape data.Attempt to brute‑force a cursor space (e.g., increment base64 number) and verify server responds with 429 Too Many Requests or returns empty set after a threshold.
Parameter injectionMalicious page or size values cause excessive load.Send size=1000000 and ensure server caps the value (e.g., max 200) and returns a 400 if out of bounds.
Information leakage via headersContent-Range or X-Total-Count reveals dataset size.Confirm that these headers are either omitted or only exposed to authenticated users with appropriate scope.
ClickjackingMalicious site frames your pagination and tricks users into clicking.Verify X-Frame-Options: DENY or Content‑Security‑Policy: frame-ancestors 'self' is present.
CSRF on state‑changing pagination (e.g., “delete page” actions)Unauthorized modification of pagination state.Ensure any POST/PUT/DELETE that alters pagination state requires a valid CSRF token or SameSite cookie.

Automate security checks with tools like OWASP ZAP passive scan integrated into your nightly build, or write specific Playwright tests that attempt the above payloads and assert the expected defensive responses.

Using Autonomous Persona‑Driven Exploration (SUSA Mention)

Scripted tests excel at verifying known paths, but they often miss emergent behavior that appears only when users interact with the app in unexpected ways. Autonomous QA platforms like SUSATest address this gap by simulating diverse user personas—each with its own timing, error‑prone tendencies, and exploration strategies—without requiring you to write explicit scenarios.

When you point SUSATest at a paginated web view, it will:

  1. Curious persona – clicks every page number, hovers over links, and opens context menus to discover hidden controls (e.g., a “Jump to page” input that appears only on long‑press).
  2. Impatient persona – rapidly clicks “Next” or scrolls the infinite scroll, triggering race conditions and exposing missing debounce logic.
  3. Novice persona – relies on visual cues alone, often missing keyboard‑only navigation, thus surfacing missing focus outlines or low‑contrast disabled states.
  4. Adversarial persona – deliberately sends malformed pagination parameters (negative page numbers, huge size values) and attempts to enumerate IDs via cursor tampering.
  5. Elderly / accessibility persona – uses simulated tremor and enlarged fonts, revealing touch‑target size problems and insufficient ARIA labeling.

Because SUSATest remembers which screens it has already visited and which actions led to dead ends, each subsequent run becomes smarter: it avoids re‑executing known‑good flows and concentrates on unexplored branches, such as a “Show 200 items” toggle that only appears after a certain number of scrolls. The platform then auto‑generates regression scripts (Appium for Android WebView, Playwright for pure web) that capture the exact sequences that produced a failure, giving developers a reproducible starting point.

Integrating SUSATest into a CI pipeline is as simple as:


pip install susatest-agent
susatest run --url https://your-app.com --mode web --output ./susatest-reports

The resulting report lists each persona’s findings, complete with screenshots, console logs, and network waterfalls. You can then convert high‑impact discoveries into deterministic tests (as shown earlier) to prevent regression.

Checklist for Pagination Testing

Use this concise list before marking a pagination feature as ready for release.

Run the checklist manually for exploratory verification, then automate the majority of items via your chosen framework.

Closing Takeaways

Effective pagination testing blends rigorous scripted validation with exploratory, persona‑driven discovery. Start by mapping out every pagination variant in your application, then construct a test matrix that covers happy paths, error paths, edge cases, accessibility, and security. Implement the matrix using a framework that offers strong network control and accessibility auditing—Playwright paired with axe‑core is a solid choice. Supplement those tests with manual sessions that focus on timing, visual state, and assistive‑technology interaction.

Remember that production‑only bugs often stem from data variability, user‑induced race conditions, or environmental factors like high‑DPI screens and extensions. Address those by injecting realistic load, simulating latency, and testing with varied device profiles.

Finally, consider leveraging an autonomous QA tool such as SUSATest to surface hidden regressions that scripted tests never think to pursue. The platform’s persona‑driven exploration complements your deterministic suite, turning rare, intermittent failures into actionable, fixable defects.

By following the matrix, checklist, and the combined manual/automated strategy outlined here, you’ll ship pagination that is reliable, inclusive, and resilient—no matter how large the data set grows or how diverse your audience becomes. Happy testing.

Test Your App Autonomously

Upload your APK or URL. SUSA explores like 10 real users — finds bugs, accessibility violations, and security issues. No scripts.

Try SUSA Free