How to Test Pagination: A Complete Guide
How to Test Pagination: A Complete Guide: Pagination is a common UI pattern that lets users navigate large data sets by breaking them into discrete pages. When pagination is flawed, users can lose dat
How to Test Pagination: A Complete Guide: Pagination is a common UI pattern that lets users navigate large data sets by breaking them into discrete pages. When pagination is flawed, users can lose data, encounter endless loops, or be blocked from reaching content, which directly impacts conversion and satisfaction. This guide walks through why pagination matters, what typically breaks, a comprehensive test matrix, manual and automated techniques, accessibility and security considerations, production‑only edge cases, and how autonomous, persona‑driven exploration can surface bugs that scripted tests miss. Each section includes concrete examples, tables, and code snippets you can adapt to your stack.
How to Test Pagination: A Complete Guide: Why Pagination Matters
Pagination appears in lists, tables, infinite scrolls, and tabbed interfaces. It affects performance, usability, and data integrity. A broken pager can cause:
- Missing items – when the total count is miscalculated, some records never appear.
- Duplicate items – when offset or limit calculations overlap across pages.
- Infinite loops – when “next” links point back to the current page or to a previous page.
- Dead ends – when the last page shows a non‑functional “next” button.
- Performance degradation – when the backend returns the full dataset instead of a slice.
- Accessibility barriers – when keyboard focus is lost or screen readers announce incorrect page numbers.
- Security issues – when unsanitized page parameters enable SQL injection or path traversal.
Testing pagination early prevents these defects from reaching production, where they are far more costly to fix. The following sections break down the testing effort into a repeatable matrix and practical techniques.
How to Test Pagination: A Complete Guide: Core Concepts and Types
Before designing tests, clarify the pagination implementation you are dealing with. Common variants include:
| Type | Description | Typical Controls | Common Pitfalls |
|---|---|---|---|
| Offset‑Limit | Server receives offset and limit (or skip/take). | “Previous”, “Next”, page numbers, jump‑to input. | Off‑by‑one errors, negative offsets, limit > max allowed. |
| Cursor‑Based | Server returns an opaque token (cursor) for the next set. | “Next” button only; sometimes “Prev”. | Token expiration, token leakage, missing prev cursor. |
| Page‑Number | Client sends page index (1‑based). | Numbered links, dropdown, “First”, “Last”. | Page 0 or negative page, page > totalPages, stale total count. |
| Infinite Scroll | No explicit controls; more data loads on scroll or trigger. | Scroll event, “Load more” button. | Duplicate loads, missing sentinel, failure to stop at end. |
| Virtualized List | UI renders only visible rows; data fetched via scroll offset. | Internal scroll position, resize listener. | Incorrect item height calculation, stale cache, jump‑to failures. |
Understanding which type you have informs the test cases you need. For example, cursor‑based systems require validation of token integrity, while offset‑limit needs boundary checks on offset and limit.
How to Test Pagination: A Complete Guide: Building a Test Matrix
A structured matrix ensures you cover happy paths, error paths, edge cases, accessibility, and security. Below is a comprehensive matrix you can copy into a test‑management tool. Each row represents a test scenario; columns indicate the pagination type(s) it applies to and the expected outcome.
| ID | Scenario | Applies To | Steps | Expected Result | Notes |
|---|---|---|---|---|---|
| P1 | Navigate to first page via UI | All | Click “First” or page 1 link | Shows first set of items, correct page indicator | Verify URL/query params |
| P2 | Navigate to last page via UI | Offset‑Limit, Page‑Number | Click “Last” or highest page number | Shows final set, “Next” disabled | Check that total items match last page size |
| P3 | Click “Next” repeatedly until last | All | Repeatedly press “Next” | Each page shows new items, no duplicates, finally “Next” disabled | Count total clicks vs. expected pages |
| P4 | Click “Previous” from first page | All | Press “Previous” on page 1 | “Previous” stays disabled, no navigation | Ensure no error state |
| P5 | Jump to middle page via input | Page‑Number | Enter page number, submit | Shows correct subset, URL updates | Validate bounds (1 ≤ page ≤ totalPages) |
| P6 | Invalid page number (0, negative, non‑numeric) | Page‑Number | Enter 0, -5, “abc” | System shows error or defaults to page 1 | Should not crash or expose stack trace |
| P7 | Offset less than zero | Offset‑Limit | Manually set offset=-10 | Returns error or defaults to offset 0 | Backend validation |
| P8 | Limit larger than max allowed | Offset‑Limit | Set limit=10000 when max is 200 | Returns error or caps limit | Prevents DoS |
| P9 | Non‑numeric offset/limit | Offset‑Limit | Set offset=ten | Error response (400) | Input sanitization |
| P10 | Cursor token tampering | Cursor‑Based | Modify token characters | Returns error or empty set | Tokens should be opaque and signed |
| P11 | Missing “prev” cursor on first page | Cursor‑Based | Inspect network on page 1 | No prev token field | UI should hide “Prev” |
| P12 | Duplicate items across pages | All | Compare item IDs on page n and n+1 | No overlap | Indicates offset/limit miscalc |
| P13 | Missing items (gap) | All | Verify sequential IDs with no gaps | Every expected ID appears somewhere | Gap suggests incorrect total count |
| P14 | Infinite scroll loads duplicate data | Infinite Scroll | Scroll to bottom, wait for load, scroll again | New items only, no repeats | Check request parameters |
| P15 | Virtualized list item height mismatch | Virtualized List | Resize browser, scroll fast | No blank spikes, all items rendered | Use debugger to inspect render offsets |
| P16 | Keyboard navigation – Tab through controls | All | Tab to pager, use Arrow keys | Focus moves logically, page changes | Verify ARIA labels |
| P17 | Screen reader announces page | All | Navigate with NVDA/JAWS | Reads “Page X of Y”, “Next button disabled” when appropriate | Check live region updates |
| P18 | Color contrast of disabled controls | All | Inspect CSS contrast ratio | ≥ 4.5:1 for normal text | Use axe or similar |
| P19 | Touch target size ≥ 44 dp | All | Measure tap area on mobile | Meets guideline | Important for elderly/power‑user personas |
| P20 | Rate‑limit abuse via rapid page changes | All | Send 100 requests/sec changing page | Server responds with 429 or similar | Prevents scraping/DoS |
| P21 | SQL injection via page parameter | Offset‑Limit, Page‑Number | Insert ' OR 1=1-- into page field | No data leakage, error response | Use parameterized queries |
| P22 | Path traversal via cursor | Cursor‑Based | Insert ../ in token | Token rejected, no file access | Validate token format |
| P23 | CSP violation via injected script in page number | Page‑Number | Submit | Script not executed, sanitized output | Output encoding |
| P24 | Load‑time spike on large page size | Offset‑Limit | Set limit=5000 (if allowed) | Response time within SLA or error | Performance guardrail |
| P25 | Stale total count after backend update | All | Add/delete items, refresh pager | Page count updates correctly | May require cache invalidation |
| P26 | Locale‑specific number formatting | Page‑Number | Switch language to Arabic, test pager | Numbers rendered correctly, RTL layout | i18n considerations |
| P27 | Accessibility‑focused persona: elderly user | All | Simulate tremor (large tap tolerance) | Controls still operable, no mis‑taps | Persona‑driven test |
| P28 | Impatient persona: rapid next clicks | All | Click “Next” five times within 200 ms | No missed pages, no duplicate loads | Tests debounce/throttle |
| P29 | Curious persona: explore jump‑to extremes | Page‑Number | Jump to page 1, then to last, then to middle | Consistent state, no UI glitches | Checks state reset |
| P30 | Adversarial persona: malformed JSON in offset | Offset‑Limit | Send { "offset": {"$gt":0} } | Error response, no injection | Tests input validation depth |
You can adjust the matrix to your specific technology stack. Each scenario should have an automated test where feasible; manual exploratory testing is valuable for the persona‑driven rows (P27‑P30) and for spotting UI‑only glitches.
How to Test Pagination: A Complete Guide: Manual Testing Techniques
Manual testing remains essential for catching visual, interaction, and accessibility issues that automated scripts may overlook. Follow this procedure for each pagination component:
- Setup – Deploy a stable build with a known dataset (e.g., 250 items, page size = 25 → 10 pages). Seed the database with unique identifiers (UUIDs or sequential numbers) to simplify duplicate/gap detection.
- Baseline verification – Load the first page, confirm that the UI shows the correct subset, that the URL or internal state reflects page=1, and that “Previous” is disabled while “Next” is enabled.
- Linear navigation – Repeatedly click “Next” (or scroll for infinite scroll) until the last page appears. After each click, note:
- Page number indicator.
- Whether any item from the previous page reappears.
- Whether the total number of distinct items seen matches the expected count.
- Reverse navigation – From the last page, click “Previous” back to the first, checking for symmetry.
- Jump‑to validation – Use the page‑number input or dropdown to jump to random pages (e.g., 3, 7, 9). Verify the displayed subset matches the expected offset.
- Boundary attempts – Try to navigate past the first or last page (e.g., click “Previous” on page 1, “Next” on last page). Ensure controls are disabled and no error state appears.
- Error injection – Manually edit network requests via browser dev tools (or a proxy like Burp) to send invalid offset/limit values, negative numbers, or non‑numeric strings. Observe that the API returns a 4xx error and the UI shows a user‑friendly message, not a stack trace.
- Accessibility check – Using only the keyboard, tab to the pager, use Arrow keys to change pages, and confirm that focus never gets lost. Run a screen reader (NVDA, VoiceOver, TalkBack) and listen for correct announcements.
- Performance observation – While navigating, watch the network tab. Ensure each request asks for only the intended slice (correct offset/limit or cursor). Note any requests that fetch the full dataset.
- Persona simulation –
- Elderly: Increase touch‑target size in the OS settings, verify that taps are still registered without mis‑fires.
- Impatient: Use a macro or rapid‑fire tool to click “Next” ten times in under a second; ensure the UI does not skip pages or show blank states.
- Curious: Perform a non‑linear navigation pattern (first → last → middle → first) and verify the UI stays in sync.
- Adversarial: Attempt to inject script tags or SQL snippets via the page field; confirm the UI sanitizes or rejects the input.
Document any deviations in a bug report with steps, expected vs. actual, screenshots, and network logs. Manual testing shines when assessing visual alignment, touch feedback, and screen‑reader nuances—areas where automated locators can be brittle.
How to Test Pagination: A Complete Guide: Automated Testing Strategies
Automated tests give you fast feedback on regression and help enforce the matrix at scale. Below are patterns for UI‑level and API‑level verification, with code snippets for Playwright (web) and Appium (Android). Adjust selectors and assertions to your framework.
API‑Level Pagination Tests
Validate that the backend respects offset/limit, cursor, and page parameters, and that it returns correct metadata (total count, next/prev tokens).
import requests
import pytest
BASE_URL = "https://api.example.com/items"
PAGE_SIZE = 10
def fetch_page(params):
resp = requests.get(BASE_URL, params=params)
resp.raise_for_status()
return resp.json()
def test_offset_limit_happy_path():
first = fetch_page({"offset":0, "limit":PAGE_SIZE})
second = fetch_page({"offset":PAGE_SIZE, "limit":PAGE_SIZE})
assert len(first["items"]) == PAGE_SIZE
assert len(second["items"]) == PAGE_SIZE
# ensure no overlap
first_ids = {i["id"] for i in first["items"]}
second_ids = {i["id"] for i in second["items"]}
assert first_ids.isdisjoint(second_ids)
def test_invalid_offset():
resp = requests.get(BASE_URL, params={"offset":-5, "limit":PAGE_SIZE})
assert resp.status_code == 400
assert "offset must be >= 0" in resp.text
def test_limit_exceeds_max():
resp = requests.get(BASE_URL, params={"offset":0, "limit":5000})
# Assuming server caps at 200
assert resp.status_code == 400 or len(resp.json()["items"]) <= 200
UI‑Level Tests with Playwright (Web)
Playwright offers auto‑waiting and robust selectors. The following example tests a typical offset‑limit pager with numbered links.
// pagination.test.js
const { test, expect } = require('@playwright/test');
test.describe('Pagination component', () => {
test.beforeEach(async ({ page }) => {
await page.goto('https://app.example.com/products?page=1&size=10');
});
test('shows correct first page items', async ({ page }) => {
const items = await page.locator('.product-item').count();
expect(items).toBe(10);
const firstId = await page.locator('.product-item').first().textContent();
expect(firstId).toBe('PROD-0001');
});
test('navigates to next page without duplicates', async ({ page }) => {
await page.click('button[aria-label="Next page"]');
await page.waitForURL(/page=2/);
const secondPageIds = await page.locator('.product-item')
.evaluateAll(el => el.map(e => e.textContent().trim()));
const firstPageIds = await page.locator('.product-item')
.evaluateAll(el => el.map(e => e.textContent().trim()));
expect([...new Set([...firstPageIds, ...secondPageIds])].length).toBe(20);
});
test('disables previous button on first page', async ({ page }) => {
const prevBtn = page.locator('button[aria-label="Previous page"]');
await expect(prevBtn).toBeDisabled();
});
test('jump‑to page works and updates URL', async ({ page }) => {
await page.fill('input[name="page"]', '7');
await page.press('input[name="page"]', 'Enter');
await page.waitForURL(/page=7/);
const items = await page.locator('.product-item').count();
expect(items).toBe(10);
const firstId = await page.locator('.product-item').first().textContent();
expect(firstId).toBe('PROD-00061');
});
test('invalid page input shows error', async ({ page }) => {
await page.fill('input[name="page"]', '0');
await page.press('input[name="page"]', 'Enter');
await expect(page.locator('.error-message')).toHaveText(/Page must be ≥ 1/);
});
test('keyboard navigation changes page', async ({ page }) => {
await page.focus('button[aria-label="Next page"]');
await page.press('ArrowRight'); // assumes right arrow triggers next
await page.waitForURL(/page=2/);
const btn = await page.locator('button[aria-label="Next page"]');
await expect(btn).toBeEnabled();
});
});
Mobile Tests with Appium (Android)
For native Android lists that use RecyclerView with pagination via scroll, you can verify that new items load without duplication.
// PaginationTest.java
import io.appium.java_client.android.AndroidDriver;
import org.junit.jupiter.api.*;
import org.openqa.selenium.By;
import java.util.HashSet;
import java.util.Set;
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class PaginationTest {
private AndroidDriver driver;
@BeforeAll
public void setUp() {
// Initialize driver with desired caps
driver = new AndroidDriver(new URL("http://localhost:4723/wd/hub"), caps);
}
@AfterAll
public void tearDown() {
if (driver != null) driver.quit();
}
@Test
public void scrollLoadsNewItemsWithoutDuplicates() {
Set<String> seenIds = new HashSet<>();
int scrolls = 0;
final int maxScrolls = 20; // safety guard
while (scrolls < maxScrolls) {
// collect visible item IDs
List<MobileElement> items = driver.findElements(By.id("item_id"));
for (MobileElement el : items) {
String id = el.getText();
Assert.assertFalse("Duplicate ID found: " + id, seenIds.contains(id));
seenIds.add(id);
}
// scroll to bottom
driver.swipe(500, 1500, 500, 500, 800);
scrolls++;
}
Assert.assertTrue("Expected at least 100 unique items", seenIds.size() >= 100);
}
}
These snippets illustrate the core ideas: validate data integrity, check control states, and ensure error handling. Integrate them into your CI pipeline to run on every commit.
How to Test Pagination: A Complete Guide: Accessibility and Security Checks
Pagination intersects with accessibility (WCAG 2.1 AA) and security (OWASP). Treat them as first‑class test categories, not afterthoughts.
Accessibility Checklist
| WCAG Criterion | How to Verify for Pagination |
|---|---|
| 1.3.1 Info and Relationships | Ensure page numbers are announced as a list or group (role="list" or aria-label="Pagination"). |
| 2.1.1 Keyboard | All pager controls reachable via Tab; Arrow keys change page without needing mouse. |
| 2.4.7 Focus Visible | When a pager button receives focus, a visible outline (≥ 2 px CSS) appears. |
| 2.4.6 Headings and Labels | Each page link has a discernible text label (not just an icon). |
| 2.5.3 Label in Name | Voice control users can say “click next” and the button responds. |
| 4.1.2 Name, Role, Value | Disabled buttons have aria-disabled="true" and are announced as disabled. |
| 1.4.3 Contrast (Minimum) | Text and icons meet 4.5:1 contrast against background. |
| 1.4.10 Reflow | On zoom to 200 %, pager controls do not lose content or functionality. |
| 1.4.11 Non‑text Contrast | Active/inactive states have sufficient contrast (≥ 3:1). |
| 2.5.5 Target Size | Touch targets ≥ 44 × 44 dp (or 48 × 48 dp for WCAG 2.2). |
Automated tools like axe-core, Pa11y, or Lighthouse can catch many of these, but manual verification with screen readers and keyboard‑only navigation is essential for complex custom widgets.
Security Checklist
| OWASP / CWE | Pagination‑Specific Test |
|---|---|
| A01:2021 – Broken Access Control | Ensure users cannot bypass pagination to view unauthorized records (e.g., by manipulating offset to jump into another tenant’s data). |
| A03:2021 – Injection | Validate that page, offset, limit, or cursor parameters are properly typed and parameterized; attempt SQL, NoSQL, and command injection. |
| A05:2021 – Security Misconfiguration | Confirm that error messages do not reveal stack traces or internal DB structure when invalid pagination values are supplied. |
| A06:2021 – Vulnerable and Outdated Components | Check that any third‑party pagination library is up‑to‑date and has no known CVEs. |
| A07:2021 – Identification and Authentication Failures | If pagination exposes user‑specific data, verify that authentication is enforced on each page request. |
| A08:2021 – Software and Data Integrity Failures | Ensure that cursor tokens are signed or encrypted; tampering should be rejected. |
| A09:2021 – Security Logging and Monitoring Failures | Log malformed pagination attempts (e.g., negative offset) and alert on spikes. |
| A10:2021 – Server‑Side Request Forgery (SSRF) | If pagination triggers internal API calls based on user input, verify that the input cannot be used to reach internal services. |
Automated security scanners (OWASP ZAP, Burp Suite) can be pointed at pagination endpoints with fuzzing payloads. Incorporate these scans into your nightly security pipeline.
How to Test Pagination: A Complete Guide: Production‑Only Edge Cases
Some defects only manifest under real‑world load, data variance, or deployment specifics. Anticipate them with targeted prod‑like testing.
Data Skew and Distribution
If your data set is not uniformly distributed (e.g., power‑law distribution of item sizes), a fixed limit may cause highly variable response times. Test with:
- Hot partitions – a small subset of keys receives 80 % of requests. Ensure pagination does not cause thundering herd on a single shard.
- Null or blank fields – items with empty titles may affect UI rendering; verify that layout does not collapse.
- Variable‑size payloads – large binary blobs (images) attached to list items can cause pagination responses to exceed HTTP limits; test with realistic asset sizes.
Concurrency and Race Conditions
Under high traffic, simultaneous updates to the underlying dataset can cause:
- Lost updates – an item deleted between two page requests appears missing, creating a gap.
- Duplicate appearance – an item inserted after the first page request but before the second appears on both pages.
- Stale cursors – a cursor that points to a position that has shifted due to inserts/deletes may skip or repeat items.
Mitigate by using snapshot isolation or version‑based cursors (e.g., include a timestamp or transaction ID in the token). In test, simulate concurrent writes with tools like Locust or k6 while iterating through pages.
Caching and CDI Effects
If your API sits behind a cache (e.g., Varnish, CloudFront), pagination parameters must be part of the cache key. Otherwise:
- Cache poisoning – a request for
page=2might serve a cached response forpage=1. - Stale cache – after a backend update, the cache continues to serve old pages, causing inconsistencies.
Test by purging cache between requests and verifying that the response reflects the latest state. Use cache‑busting query strings or headers to ensure each request is unique.
Locale and Proxy
Some CDN:
query strings (?_ts=timestamp) or vary‑by on pagination parameters.
Locale and Timezone
Pagination that relies on date‑based cursors (e.g., “load newer items since X”) can be tripped by:
- Timezone offsets – a user in UTC+10 sends a timestamp that the server interprets incorrectly.
- Calendar differences – locales that use different first‑day‑of‑week affect weekly pagination.
Validate by sending requests with various Accept-Language headers and checking that the returned data set matches expectations for each locale.
Device‑Specific Rendering
On low‑end devices, large pagination controls may cause layout thrash. Test with:
- Device emulation (Chrome DevTools, BrowserStack) at varying screen widths and pixel ratios.
- Force‑layout thrash – rapidly resize the window while navigating pages to ensure no jank.
- Memory usage – ensure that holding many page objects in DOM does not cause leaks (especially in SPA frameworks).
Monitoring and Alerting
Production pagination faults often surface as increased latency or error rates. Instrument:
- Request latency per page – track p95 for
offset/limitcalls. - Error rate for invalid pagination – alert on spikes of 400 responses.
- Cache miss ratio – high miss may indicate ineffective caching strategy.
- Duplicate item detection – emit a metric when the same item ID appears in two successive responses.
Set thresholds based on historical baselines; a sudden increase often points to a pagination regression.
How to Test Pagination: A Complete Guide: Using Autonomous, Persona‑Driven Exploration (Mention SUSA)
Scripted tests excel at checking known paths, but they can miss emergent behaviors that arise when real users interact with the app in unpredictable ways. Autonomous exploration platforms—like SUSATest—address this gap by:
- Generating realistic user sessions without pre‑written scripts. The agent explores the UI, taps, scrolls, types, and handles dialogs based on a behavior model.
- Applying multiple personas (curious, impatient, novice, adversarial, elderly, accessibility, power user, etc.) each with distinct interaction patterns (e.g., impatient users double‑tap quickly; elderly users linger on controls; adversarial users try malformed inputs).
- Tracking navigation flows and marking each as PASS/FAIL based on observed crashes, ANRs, dead buttons, WCAG violations, security red flags, or UX friction.
- Creating regression scripts from the discovered flows: Appium scripts for Android and Playwright scripts for web, so you can retain the valuable scenarios found during exploration.
- Learning across sessions: the agent remembers which screens are dead ends and which paths are productive, making each run smarter and reducing redundant effort.
When applied to pagination, an autonomous agent can:
- Discover hidden jump‑to controls that are only visible after a certain scroll depth, which a scripted test might never trigger.
- Expose throttle or debounce bugs by simulating an impatient persona issuing rapid “next” gestures.
- Identify accessibility oversights (e.g., missing ARIA labels on dynamically generated page numbers) via the accessibility persona.
- Uncover security issues by having the adversarial persona inject script tags or SQL snippets into page inputs and observing whether the app sanitizes or rejects them.
- Detect production‑only glitches such as layout shifts when the virtualized list recycles items under heavy load, because the agent varies device orientation, font scaling, and network latency in its explorations.
To run SUSATest on your app:
# Install the CLI
pip install susatest-agent
# Point at an Android APK or a web URL
susatest run --apk ./my-app.apk --personas curious impatient elderly
# or
susatest run --url https://shop.example.com/products --personas accessibility adversarial
The output includes a detailed report, a list of discovered flows with PASS/FAIL status, and generated test scripts you can commit to your repository. While autonomous testing does not replace targeted unit or API tests, it complements them by surfacing edge cases that are difficult to anticipate manually.
How to Test Pagination: A Complete Guide: Checklist and Takeaways
Use this concise checklist before signing off a pagination feature. Each item can be mapped to a test case in your test‑management tool.
✅ Functional Checklist
- [ ] First page loads correct subset and shows “Previous” disabled.
- [ ] Last page loads correct subset and shows “Next” disabled.
- [ ] Linear “Next” navigation yields no duplicate or missing items across the full set.
- [ ] Linear “Previous” navigation mirrors the forward path.
- [ ] Jump‑to (page number, cursor, offset) lands on the expected subset and updates URL/state.
- [ ] Invalid inputs (negative, non‑numeric, out‑of‑range) return a user‑friendly error, not a stack trace.
- [ ] Controls are keyboard operable via keyboard (Tab, Arrow keys) change focus and page without mouse.
- [ ] Screen reader announces current page, total pages, and disabled state appropriately.
- [ ] Touch targets meet minimum size (≥ 44 dp) and have adequate spacing.
- [ ] Color contrast of enabled/disabled states meets WCAG AA.
- [ ] No infinite loops: repeated “Next” eventually disables.
- [ ] Virtualized or infinite scroll loads new data only when approaching the bottom.
- [ ] Cache key includes pagination parameters; purging yields fresh data.
- [ ] Concurrent updates do not cause lost or duplicated items under realistic load.
- [ ] Error responses do not leak internal details (SQL stack
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