How to Test Pagination on Android (Complete Guide)

Pagination is a core interaction pattern in almost every Android application that displays lists, grids, or feeds. Whether the app loads a timeline of social posts, a catalog of products, or a set of

April 12, 2026 · 18 min read · How-To Guides

Why Pagination Testing Matters on Android

Pagination is a core interaction pattern in almost every Android application that displays lists, grids, or feeds. Whether the app loads a timeline of social posts, a catalog of products, or a set of search results, the user expects smooth, predictable navigation between pages. When pagination fails, the consequences are immediate and visible: users see blank screens, experience endless spinners, lose their place, or trigger crashes that lead to negative reviews and uninstalls.

From a quality perspective, pagination touches several layers of the stack:

Because each layer can introduce a distinct class of defect, a focused pagination test strategy is essential. The following sections walk through a comprehensive test matrix, manual and automated techniques, persona‑driven exploration with SUSA, and production‑only edge cases that often escape scripted checks.

---

Common Pagination Patterns in Android Apps

Before designing tests, it helps to recognize the most frequent implementations you will encounter.

PatternTypical APIUI ComponentState Handling
Offset‑basedGET /items?offset=20&limit=20RecyclerView with LinearLayoutManagerOffset saved in ViewModel; incremented on each load
Cursor‑basedGET /items?cursor=abc123PagingDataAdapter (Paging 3)Cursor stored in PagingSource; refreshed after rotation
Token‑based (GraphQL)query { feed(after: "token") { items … } }Custom view with DiffUtilToken retained in ViewModel; cleared on error
Infinite scroll with prefetchGET /feed?page=3RecyclerView + setItemPrefetchEnabled(true)Page number kept in LiveData; prefetch triggers next load
Sectioned paginationSeparate endpoints per sectionNested RecyclerViews or StaggeredGridLayoutManagerEach section maintains its own offset/cursor

Understanding which pattern your app uses informs the test cases you need to write. For example, offset‑based pagination is prone to off‑by‑one errors when the dataset size changes, while cursor‑based pagination can break if the server reuses cursors after a data reset.

---

Test Matrix for Pagination

The table below captures a practical matrix that covers happy paths, error paths, edge cases, accessibility, and security/privacy. Each cell lists a concise description and expected behavior, typical failure symptoms, and a suggested verification method.

CategoryTest IDDescriptionExpected ResultFailure SymptomVerification Method
Happy PathHP‑1Load first page, scroll to bottom, trigger next loadSecond page appended, no duplicates, scroll position smoothMissing items, duplicate entries, UI jankEspresso scrollTo + assertOnView
HP‑2Rotate device while loading second pageData preserved, loading indicator shown, no loss of stateData reset, spinner stuck, crashADB shell input keyevent KEYCODE_ROTATE + UI check
HP‑3Reach last page, attempt to load moreNo further request, end‑of‑list indicator shownExtra request, crash, blank footerNetwork mock + assert no new request
Error PathEP‑1Server returns 500 on page 2Error snackbar shown, retry button enabled, existing data retainedApp crashes, spinner infinite, data lostMock server 500 + check snackbar
EP‑2Network timeout (no response for 10 s)Timeout message, retry option, previous page still visibleANR, blank screen, forced closeUse adb shell netem to add delay + assert timeout UI
EP‑3Malformed JSON (missing required field)Graceful degradation, placeholder shown, log errorParse exception, crash, missing UIInject bad JSON via MockWebServer
EP‑4Server returns empty list on intermediate pageUI shows “no more items” or placeholder, does not request furtherInfinite looping requests, UI stuckMock empty response + verify no further calls
Edge CasesEC‑1Rapid successive scrolls (fling) while loadingRequests throttled, UI does not flood networkDuplicate requests, memory spike, UI freezeEspresso perform fling + network spy
EC‑2Insertion of new items at top while user is mid‑listExisting scroll offset preserved, new items appear at top without jumpView jumps, loss of position, duplicated headersUse ItemAnimator + assert scroll state
EC‑3Deletion of items from middle of listAdapter updates, indices shift correctly, no IndexOutOfBoundsCrash, stale UI, phantom itemsRemove item via DB + verify adapter
EC‑4Very large page size (e.g., limit=500)UI remains responsive, memory usage within limitsOOM, dropped frames, ANRProfile memory with Android Studio Profiler
EC‑5Locale change to RTL language while pagingLayout mirrors correctly, scroll direction unchangedMisaligned items, clipped text, TalkBack misreadsChange language + assert layout direction
AccessibilityAC‑1TalkBack user navigates via swipe‑rightEach page load announced (“loading more items”, “page 2 of 5”)No announcement, confusing hintsAccessibility Test Framework (ATF) + assert spoken feedback
AC‑2Switch Control user selects “Load more” buttonButton receives focus, action performed on activationButton skipped, focus lostUse adb shell settings put accessibility … + UIAutomator
AC‑3Font size set to largestItem layouts do not overflow, pagination controls remain tappableText cut off, overlapping controlsChange font size + UIAutomator inspector
AC‑4High contrast mode enabledContrast ratios meet WCAG AA for pagination indicatorsLow contrast, invisible indicatorsUse developer options + contrast checker
Security/PrivacySP‑1Pagination token appears in logs or network traceToken is obfuscated or short‑lived, not persisted to diskToken exposed in plaintext logs, leaked via ADB bugreportRun adb logcat + grep for token; verify not present
SP‑2User‑specific data (e.g., email) used as offset parameterNo personal data sent in URL; only opaque identifierGDPR violation, potential enumerationInspect network calls with HttpCanary
SP‑3Error responses contain stack traces or internal pathsServer returns generic error message; client does not display detailsInternal info leaked to user, possible attack surfaceMock 500 with stack trace + assert UI shows generic message
SP‑4Pagination endpoint lacks rate limiting on client sideClient backs off after 429, retries with exponential delayHammering server, possible ban, battery drainSimulate 429 + verify back‑off behavior

*How to use the matrix*: Pick the rows relevant to your implementation, translate each Test ID into a concrete test case (manual step or automated assertion), and track coverage in your test management tool. The matrix also serves as a checklist for regression runs.

---

Manual Testing Approach

Even with strong automation, manual exploratory testing remains valuable for spotting UX nuances, timing‑dependent glitches, and accessibility quirks that scripts may overlook.

Setup and Environment

  1. Device preparation – Use a physical device running Android 11+ (API 30) to catch hardware‑specific issues. Enable Developer Options → USB debugging, disable battery optimizations for the test app, and turn off “Don’t keep activities” to avoid artificial state loss.
  2. Network control – Connect the device to a Wi‑Fi network routed through a tool like Charles Proxy or Mitmproxy. This allows you to throttle latency, drop packets, or inject malformed responses on demand.
  3. Logging – Run adb logcat -v threadtime > logcat.txt in a separate terminal to capture runtime exceptions and ANR traces.
  4. UI inspection – Keep Layout Inspector (Android Studio) or UIAutomator Viewer open to verify view hierarchies, especially when checking for duplicated or missing items.
  5. Accessibility tools – Enable TalkBack, Switch Control, and Font Size scaling from Settings → Accessibility to validate each scenario manually.

Step‑by‑Step Manual Test Procedure

Below is a repeatable flow that covers the majority of the matrix. Adjust the number of pages or dataset size according to your app’s characteristics.

  1. Launch the app and navigate to the paginated screen (e.g., product list).
  2. Observe initial load – Confirm that a loading indicator appears, then the first set of items renders without placeholders. Use Layout Inspector to ensure the RecyclerView’s item count matches the expected page size.
  3. Scroll to the bottom – Perform a slow drag until the RecyclerView signals reaching the last visible item. Verify that a “loading more…” footer or spinner appears and that a network request for the next page is fired (check in Charles).
  4. Rotate the device – While the second page is loading, press Ctrl+F11 (emulator) or physically rotate the device. Confirm that the loading indicator persists, no data is lost, and after rotation the newly loaded items remain visible.
  5. Introduce network failure – In Charles, map the next‑page endpoint to return a 500 error. Observe that an error snackbar appears with a retry button, that the previously loaded data stays on screen, and that tapping retry re‑issues the request.
  6. Test empty intermediate page – Return an empty JSON array for page 3. The app should display an “no more items” hint and cease further requests. Ensure the UI does not keep showing a spinner indefinitely.
  7. Stress with rapid flings – Perform a quick fling from top to bottom repeatedly while the app is still loading. Watch for duplicate requests, memory spikes (via Android Studio Profiler), or UI jank.
  8. Change locale to RTL – Switch device language to Arabic or Hebrew. Confirm that the list mirrors correctly, that scroll direction feels natural, and that TalkBack announces the proper pagination state.
  9. Validate accessibility – With TalkBack enabled, swipe right across the list. Each page transition should be announced (e.g., “loading more items”, “page 2”). Activate the “load more” button via double‑tap and confirm the action completes.
  10. Check security exposure – Disable USB debugging, then run adb bugreport > report.zip. Extract the report and search for any pagination tokens, user IDs, or internal URLs. Ensure none appear in plain text.

If any step fails, record the exact conditions (device model, OS version, network profile, accessibility setting to reproduce the specific Test ID from the matrix) and file a bug with logs and a short screen‑capture video.

Tools for Manual Verification

---

Automated Testing Approaches

Automation provides repeatable regression safety nets. Below are the most effective strategies for Android pagination, ranging from fast unit tests to cross‑device UI automation.

Unit and Integration Tests with Espresso

Espresso shines when you can deterministically control the data layer. Use MockWebServer to enqueue responses and verify UI updates.


@RunWith(AndroidJUnit4::class)
class PaginationEspressoTest {

    private val mockWebServer = MockWebServer()

    @Before
    fun setUp() {
        mockWebServer.start()
        // Inject the base URL into your app via DI or BuildConfig
        val apiService = ServiceGenerator.createApiService(mockWebServer.url("/"))
        // Provide the service to your ViewModel (e.g., via Hilt test module)
    }

    @After
    fun tearDown() {
        mockWebServer.shutdown()
    }

    @Test
    fun loadFirstAndSecondPage() {
        // Page 1
        mockWebServer.enqueue(MockResponse()
            .setResponseCode(200)
            .setBody(loadJsonFixture("page1.json")))
        // Page 2
        mockWebServer.enqueue(MockResponse()
            .setResponseCode(200)
            .setBody(loadJsonFixture("page2.json")))

        // Launch activity
        ActivityScenario.launch(MainActivity::class.java)

        // Verify first page items
        onView(withId(R.id.recycler_view))
            .check(matches(hasDescendant(withText("Item 1"))))
            .check(matches(hasDescendant(withText("Item 20"))))

        // Scroll to bottom to trigger next load
        onView(withId(R.id.recycler_view))
            .perform(swipeDown()) // assuming linear layout manager
            .perform(scrollToPosition(19)) // last item of page 1

        // Verify loading indicator appears
        onView(withId(R.id.progress_bar))
            .check(matches(isDisplayed()))

        // Wait for second page
        onView(withId(R.id.recycler_view))
            .check(matches(hasDescendant(withText("Item 21"))))
            .check(matches(hasDescendant(withText("Item 40"))))

        // Ensure no extra request was made
        mockWebServer.takeSequence() // should contain exactly 2 requests
    }
}

Key points:

UI Automation with UIAutomator

UIAutomator works well for black‑box tests where you cannot modify the app source (e.g., testing a third‑party SDK or a release APK). It interacts with the accessibility layer, making it inherently sensitive to accessibility bugs.


public class PaginationUiAutomatorTest {

    private UiDevice device;

    @Before
    public void setUp() throws Exception {
        device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
        device.pressHome();
        // Launch the app via its package name
        Context ctx = InstrumentationRegistry.getInstrumentation().getTargetContext();
        Intent intent = ctx.getPackageManager()
                .getLaunchIntentForPackage("com.example.myapp");
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
        ctx.startActivity(intent);
        device.wait(Until.hasObject(By.pkg("com.example.myapp").depth(0)), 5000);
    }

    @Test
    public void testPaginationWithNetworkError() throws Exception {
        // Set up a network proxy that will return 500 for page 2
        // (Assume you have a helper that configures the device's Wi‑Fi proxy)
        NetworkUtils.setProxyAndThrottle("192.168.1.100", 8888, 5000); // 5 s latency

        // Wait for first page to load
        UiObject2 list = device.wait(Until.findObject(By.res("com.example.myapp", "recycler_view")), 10000);
        assertNotNull(list);

        // Scroll to bottom
        for (int i = 0; i < 5; i++) {
            list.swipe(Swipe.DOWN, 0.5f);
            SystemClock.sleep(500);
        }

        // Verify error snackbar appears
        UiObject2 snackbar = device.wait(Until.findObject(By.textContains("Failed to load")), 8000);
        assertNotNull(snackbar);

        // Tap retry
        UiObject2 retry = device.wait(Until.findObject(By.res("com.example.myapp", "btn_retry")), 5000);
        retry.click();

        // After retry, second page should appear
        device.wait(Until.findObject(By.text("Item 21")), 10000);
        assertTrue(device.findObject(By.text("Item 21")).exists());
    }
}

UIAutomator tests are slower than Espresso but give you confidence that the app behaves correctly under real system conditions (e.g., when TalkBack is on, or when the device is in split‑screen mode).

Leveraging Appium for Cross‑Device Testing

If you need to run the same test suite on multiple device configurations (different OS versions, screen sizes, or locales) without maintaining separate instrumentation code, Appium offers a convenient bridge.


# appium-capabilities.yaml
capabilities:
  - platformName: Android
    automationName: UiAutomator2
    deviceName: Pixel_4_API_30
    app: /path/to/app-debug.apk
    appPackage: com.example.myapp
    appActivity: .MainActivity
    locale: en
    language: US
    orientation: PORTRAIT
  - platformName: Android
    automationName: UiAutomator2
    deviceName: Samsung_Galaxy_S21_API_33
    app: /path/to/app-debug.apk
    appPackage: com.example.myapp
    appActivity: .MainActivity
    locale: ar
    language: AE
    orientation: LANDSCAPE

A sample Appium test in JavaScript (using WebDriverIO) that validates pagination:


describe('Pagination flow', () => {
    it('should load second page after scrolling', async () => {
        // Wait for RecyclerView
        const recycler = await $('android=new UiSelector().resourceId("com.example.myapp:id/recycler_view")');
        await recycler.waitForExist({ timeout: 10000 });

        // Grab first item text
        const firstItem = await $('android=new UiSelector().resourceId("com.example.myapp:id/item_text").instance(0)');
        const firstText = await firstItem.getText();
        expect(firstText).toMatch(/Item \d+/);

        // Scroll to bottom (perform a swipe)
        await driver.touchPerform([
            { action: 'press', options: { x: 500, y: 1500 } },
            { action: 'wait', options: { ms: 200 } },
            { action: 'moveTo', options: { x: 500, y: 500 } },
            { action: 'release' }
        ]);

        // Wait for loading indicator to disappear
        const loader = await $('android=new UiSelector().resourceId("com.example.myapp:id/progress_bar")');
        await loader.waitForExist({ reverse: true, timeout: 15000 });

        // Verify second page items appear
        const secondItem = await $('android=new UiSelector().resourceId("com.example.myapp:id/item_text").instance(20)');
        const secondText = await secondItem.getText();
        expect(secondText).toMatch(/Item \d+/);
    });
});

Run the test against the capability matrix with:


npx wdio run wdio.conf.js --spec ./test/pagination.spec.js

Appium’s strength lies in executing the same script on a fleet of real devices or emulators, which helps surface device‑specific pagination quirks (e.g., OEM‑specific RecyclerView bugs).

Leveraging SUSA for Autonomous Exploration

SUSA (the autonomous QA platform) can be pointed at your APK or a web‑view wrapper and left to explore the app without any test scripts. It builds a session‑based knowledge graph of screens, transitions, and dead ends, then applies a set of persona‑driven behavior models to exercise the UI in ways that manual testers might overlook.

To invoke SUSA on a local build:


pip install susatest-agent
susatest-agent run \
    --apk ./app-release.apk \
    --device-id emulator-5554 \
    --personas curious impatient elderly accessibility \
    --output-dir ./susausage-report

The resulting report contains a dedicated “Pagination” section with screenshots, network traces, and any detected defects. While SUSA is not a replacement for unit test framework, it complements your existing test suite by surfacing edge cases that only manifest under realistic, varied user behavior.

---

Persona‑Driven Exploration and Its Benefits

Personas encode distinct interaction patterns, motivations, and limitations. When applied to pagination testing, they expose bugs that are invisible to a single “happy‑path” script.

Persona Profiles Relevant to Pagination

PersonaCore TraitsTypical Interaction with Pagination
CuriousExplores every UI element, taps unknown icons, reads tooltipsMay long‑press items to open context menus while scrolling, triggering unexpected adapter updates
ImpatientPerforms rapid gestures, dislikes waiting, retries quicklyFrequently flings, taps “load more” before spinner disappears, causing duplicate requests
NoviceRelies on visual cues, avoids gestures they don’t understandMay miss the infinitescroll indicator, rely exclusively on explicit “Load more” button
ElderlySlower motor response, prefers larger touch targets, often uses accessibility servicesMay double‑tap inadvertently, need larger pagination controls, may trigger TalkBack announcements frequently
AccessibilityDepends on screen reader, switch control, or voice commandsRelies on spoken state changes; missing announcements break their sense of progress
Power userUses shortcuts, expects high performance, often changes device orientationMay rotate device while list is loading, expects state preservation and no UI jank
AdversarialAttempts to break the system, sends extreme values, crafts malformed requestsMay manipulate pagination parameters via accessibility service or network proxy to trigger OOM or crashes

How SUSA Simulates Personas

SUSA’s engine includes a set of behavior modules that modify the base exploration algorithm:

These modifiers are applied on‑the‑fly as SUSA traverses the app, meaning a single run can generate dozens of distinct pagination scenarios without any test author needing to anticipate them.

Real‑World Bugs Found Only via Persona Exploration

During a recent internal audit of a media‑streaming app, SUSA uncovered three pagination‑related defects that escaped both Espresso and manual test plans:

  1. Impatient‑induced duplicate network calls – When the user flings twice within 300 ms while the first page is still loading, the app’s RecyclerView.OnScrollListener fired twice, causing two simultaneous page‑2 requests. The backend returned identical data, leading to visible duplicate rows in the list. The fix added a debouncing flag in the ViewModel.
  2. Elderly‑triggered TalkBack announcement loss – With TalkBack enabled, the app only announced “loading more” when the ProgressBar changed visibility from GONE to VISIBLE. However, when the user slowly scrolled (as the Elderly persona does), the progress bar never fully disappeared before the next load began, so the announcement was skipped. The solution was to announce based on the data source’s loadState rather than UI visibility.
  3. Adversarial‑triggered OOM via huge page size – By manipulating the network proxy to return limit=50000 (instead of the default 20), the app attempted to inflate 50 k view holders in a single layout pass, exceeding the heap limit on low‑end devices. The fix added server‑side validation and a client‑side clamp (limit = Math.min(limit, 100)).

These examples illustrate why persona‑driven exploration is a valuable complement to scripted tests: it surfaces timing‑sensitive, accessibility‑sensitive, and boundary‑condition bugs that are otherwise hard to anticipate.

---

Production‑Only Edge Cases

Certain pagination flaws only manifest after the app has been released to a broad audience, where device heterogeneity, real‑world network conditions, and user behavior patterns diverge from lab environments.

Network Variability and Caching

Mitigation: Use a centralized paging repository that deduplicates requests based on the current LoadState. Validate HTTP status codes explicitly; treat 304 as a successful reload but keep the existing data set. Pin API endpoints via network security config.

Background Threading and Configuration Changes

Mitigation: Collect paging flows in lifecycleScope tied to the activity/fragment lifecycle, or use repeatOnLifecycle(Lifecycle.State.STARTED) to auto‑cancel on stop. Ensure any custom executors are shut down in the ViewModel’s onCleared.

Large Data Sets and Memory Pressure

Mitigation:

Localization and RTL Layouts

Mitigation: Run the app with adb shell setprop persist.sys.language ar && adb shell setprop persist.sys.country EG and verify every screen with Layout Inspector. Use Android’s NumberFormat.getInstance(Locale) for any numeric UI.

---

Checklist for Pagination Testing

✅ ItemDescriptionHow to Verify
HP‑1First page loads correctly, no placeholdersVisual check + RecyclerView item count
HP‑2State survives rotationRotate during load, confirm data persists
HP‑3No request beyond last pageMock final page → verify no further network call
EP‑1Graceful handling of 5xx errorsMock 500 → error UI + retry works
EP‑2Timeout handling and retrySimulate delay → timeout UI + retry
EP‑3Malformed JSON does not crashInject bad JSON → placeholder/log
EP‑4Empty intermediate page stops paginationMock empty array → “no more items” shown
EC‑1Rapid flings do not flood networkPerform quick swipes → spy on request count
EC‑2Insertions at top preserve scroll positionAdd items via DB → verify offset unchanged
EC‑3Deletions update adapter without crashRemove item → check IndexOutOfBounds absent
EC‑4Large page size stays within memory budgetSet limit high → profile memory, avoid OOM
EC‑5RTL layout mirrors correctly, TalkBack functionalSwitch language → inspect layout direction, announcements
AC‑1TalkBack announces page loadsEnable TalkBack → listen for “loading more”, “page X”
AC‑2Switch Control can activate load moreEnable Switch Control → verify focus & action
AC‑3Font scaling does not break item layoutSet largest font → UI intact
AC‑4Contrast meets WCAG AA for pagination controlsUse Accessibility Scanner or Contrast Checker
SP‑1No token leakage in logs or bugreportadb logcat + adb bugreport → grep for tokens
SP‑2No personal data in pagination URLInspect network calls → no emails/IDs in query
SP‑3

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