How to Test Infinite Scroll on Android (Complete Guide)
Infinite scroll is a UI pattern that loads additional content automatically as the user reaches the end of a list. On Android, it is commonly implemented with RecyclerView, Paging 3 library, or custom
Why Infinite Scroll Matters on Android
Infinite scroll is a UI pattern that loads additional content automatically as the user reaches the end of a list. On Android, it is commonly implemented with RecyclerView, Paging 3 library, or custom AbsListView subclasses. The pattern improves perceived performance by avoiding explicit pagination controls, but it also couples UI behavior to data fetching, lifecycle events, and thread management. When any part of that chain fails, the user sees a frozen spinner, duplicated items, or a sudden jump back to the top of the list.
From a product perspective, infinite scroll drives engagement: users stay longer when new items appear without interruption. From a testing perspective, the pattern creates a moving target. Traditional scripted tests that navigate to a fixed position and assert a single element miss the dynamic nature of the list. They also ignore the myriad ways the loading mechanism can be triggered—by a quick flick, a slow drag, an accessibility gesture, or a system‑initiated scroll due to IME changes. Consequently, bugs that only manifest after dozens of loads, under memory pressure, or when the device is rotated often escape detection until they reach production.
Testing infinite scroll therefore requires a strategy that exercises the list across many scroll distances, varied timing, and different device states while observing both UI correctness and underlying data‑source behavior. The following sections lay out a complete approach, from manual exploration to automated scripts and persona‑driven autonomous testing.
Common Failure Modes in Production
Understanding what can go wrong helps focus test effort. Below are the most frequent categories of infinite‑scroll defects observed in Android apps released to the Play Store.
| Category | Symptom | Typical Root Cause |
|---|---|---|
| Data‑source exhaustion | List stops loading after N pages; shows empty state or stale footer | PagingSource returns null after a certain page, or network API returns empty array without signalling end‑of‑list |
| Duplicate entries | Same item appears twice consecutively after a scroll | Failure to deduplicate keys when merging new page with existing list; PagingDataAdapter not using stable IDs |
| Spinner leakage | Progress bar remains visible after data arrives | Loading state flag not cleared on error or success; coroutine not cancelled on configuration change |
| ANR on main thread | UI freezes for >5 seconds during scroll | Heavy work (JSON parsing, DB query) executed on UI thread inside RecyclerView.ViewHolder.bind |
| Memory overflow | App crashes with OutOfMemoryError after scrolling 30+ pages | Bitmaps or large objects held in ViewHolder without recycling; ListAdapter retains stale references |
| Accessibility breakage | TalkBack skips newly loaded items or announces “loading” indefinitely | New views not marked as importantForAccessibility; live region not updated |
| Security/privacy leak | Prefetched data includes PII that should not be cached | PagingSource loads more data than needed for prefetch; data persisted in Room without encryption |
| Rotation loss | Scroll position resets to top after device rotation | SavedStateHandle not implemented; PagingData not retained across ViewModel recreation |
| Network‑retry loop | Endless spinner appears when network fluctuates | Retry policy does not respect maxAttempts; each failure triggers a new load request |
| Edge‑gesture conflict | Swipe‑to‑refresh triggers unintended load more | Both swipeRefreshLayout and RecyclerView onTouchListener consume the same motion event |
Each of these defects can be reproduced with a combination of manual steps, automated checks, and stress‑testing techniques. The next section defines a test matrix that covers the necessary dimensions.
Test Matrix Overview
A systematic matrix ensures that happy‑path, error‑path, accessibility, and security aspects are all exercised. The table below lists test dimensions (rows) against scroll scenarios (columns). Each cell indicates the recommended verification technique.
| Test Dimension \ Scroll Scenario | Initial Load (0‑5 items) | Mid‑Scroll (10‑30 pages) | Fast Flick ( >100 dp/ms ) | Slow Drag ( <10 dp/ms ) | Accessibility Gesture (TalkBack swipe) | Rotation Mid‑Scroll | Low Memory ( <150 MB free ) | Network Flakiness ( 30% loss ) |
|---|---|---|---|---|---|---|---|---|
| Data correctness | Verify first page matches API | Ensure no duplicates, correct ordering | Same as mid‑scroll, plus check for dropped frames | Verify incremental loading triggers at correct threshold | New items announced, live region updated | Scroll position preserved after rotation | List continues loading without OOM | Retry logic respects back‑off, no infinite spinner |
| UI stability | No overlapping views, proper item decor | Footer/spinner appears only when needed | No jank >16ms per frame (measured via FrameMetrics) | Smooth scroll, no stutter | Focus moves to newly loaded item | UI does not flash or reset | UI remains responsive, no dropped frames | Spinner shows, then hides on success/error |
| State persistence | ViewModel holds initial PagingData | PagingData survives configuration change | Same as mid‑scroll | Same as mid‑scroll | TalkBack reads updated state | Scroll offset restored, PagingData not reloaded | PagingData retained, no reload triggered | Network state reflected in UI after recovery |
| Resource usage | Memory baseline ~20MB | Memory growth <5MB per 10 pages | Peak memory <80MB | Same as mid‑scroll | Accessibility overlay does not leak | No memory spike on rotation | Heap stays <120MB, GC frequency normal | No leak of HttpClient or coroutine scopes |
| Accessibility compliance | ContentDescription on each item | New items receive proper labels | Labels appear within 200ms of appearance | Labels appear promptly | TalkBack reads each new item as it appears | Focus not lost after rotation | Contrast ratios unchanged under low memory | Error messages announced via accessibility |
| Security/privacy | No prefetch beyond visible range | Prefetch window respects configured size | No extra prefetch triggered by fling | Prefetch respects drag speed | No exposure of PII in accessibility events | No caching of sensitive data after rotation | No persistence of prefetched data to disk | No transmission of auth tokens on retry loops |
| Error handling | Network error shows retry button | Error state appears, allows manual retry | Error state does not mask new data | Error state appears after slow drag | TalkBack announces error message | Error state persists across rotation | Error state shown under low memory if data fetch fails | Retry attempts capped, fallback to cached data |
How to Use the Matrix
- Select a scenario (e.g., Fast Flick) and run through each dimension, marking Pass/Fail.
- Automate the repetitive checks (data correctness, UI stability, resource usage) with instrumented tests.
- Reserve manual or exploratory testing for dimensions that rely on human perception (accessibility announcements, visual jank, focus behavior).
- Iterate: if a scenario fails a dimension, add a targeted test case that isolates the failure condition (e.g., simulate low memory with
adb shell am set-debug-app -w com.example.app persisistent).
The matrix also serves as a communication tool with product and UX teams: they can see which aspects of infinite scroll are covered and where additional design work (e.g., prefetch tuning) may be needed.
Manual Testing Approach
Manual exploration remains valuable for catching subtle UX glitches that automated assertions may overlook. Below is a step‑by‑step protocol that a tester can follow on a physical device or emulator.
Preparation
- Install the app variant you intend to test (debug or release). Ensure logging is enabled (
adb logcat -v threadtime). - Clear app data (
adb shell pm clear com.example.app) to start from a clean state. - Enable developer options: Show CPU usage, Show surface updates, and Enable strict mode to detect disk/network on main thread.
- Set up accessibility service: Turn on TalkBack, enable “Explore by touch”, and optionally enable “Speak passwords” if you need to verify that no credentials are spoken.
- Prepare network throttling: Use
adb shell netemor a tool like Charles Proxy to simulate 3G, packet loss, or latency.
Execution
| Step | Action | Observation |
|---|---|---|
| 1 | Launch the app, navigate to the infinite‑scroll screen. | Verify initial loading spinner disappears after first page renders. |
| 2 | Perform a slow drag (≈5 dp/ms) downwards. | New items should appear smoothly; spinner should show only when data fetch starts. |
| 3 | Perform a fast flick (≈150 dp/ms). | List should keep up; no blank spaces or duplicate items. |
| 4 | Rotate device to landscape, then back to portrait. | Scroll position should be preserved; no flash of stale data. |
| 5 | Enable TalkBack, swipe right to move focus through items. | Each newly loaded item should receive focus and be announced; live region should update when spinner appears/disappears. |
| 6 | Simulate low memory: adb shell am set-debug-app -w com.example.app persistant then open a memory‑heavy app (e.g., Chrome with many tabs) to pressure RAM. | Continue scrolling; app should not crash with OOM; if it does, note the exact page count. |
| 7 | Introduce 30% packet loss via network throttle. | Observe retry behavior; ensure spinner does not stay forever and that error UI appears after max retries. |
| 8 | After many scrolls (≈200 items), open the overflow menu and trigger a manual refresh (swipe‑to‑refresh). | List should reset to first page correctly; no duplicate header. |
| 9 | Leave the app in the background for 5 minutes, then return. | List should restore to same position; background prefetch should not have consumed excess battery. |
| 10 | Examine logcat for StrictMode violations, GC spikes, or NetworkOnMainThreadException. | Any occurrence flags a defect to be fixed. |
Documentation
- Record a short screen capture (using
adb shell screenrecord) for each failure mode. - Note the exact scroll distance (in dp) at which the issue appeared, which can be obtained from
adb shell dumpsys gfxinfo com.example.app. - Capture memory trend via
adb shell dumpsys meminfo com.example.appbefore and after a scrolling session.
Manual testing is time‑consuming but essential for validating the subjective feel of infinite scroll—something that automated frame‑timing checks can quantify but not fully interpret.
Automated Testing with Espresso/UIAutomator
Automated checks give confidence that regressions are caught early. The following patterns work well for infinite scroll on Android.
Espresso Basics
Espresso synchronizes with the UI thread and is ideal for verifying item correctness and simple interactions.
@RunWith(AndroidJUnit4::class)
class InfiniteScrollTest {
@get:Rule
val activityRule = ActivityScenarioRule(MainActivity::class)
@Test
fun `loads next page on scroll`() {
// 1. Verify first page contains expected IDs
onView(withId(R.id.recycler_view))
.perform(RecyclerViewActions.scrollToPosition<RecyclerView.ViewHolder>(0))
onView(withId(R.id.item_text)).check(matches(withText("Item 1")))
// 2. Scroll to near the end to trigger load more
onView(withId(R.id.recycler_view))
.perform(RecyclerViewActions.scrollToPosition<RecyclerView.ViewHolder>(19))
// 3. Wait for new items (use IdlingResource tied to PagingState)
val pagingIdle = object : IdlingResource {
override fun getName() = "PagingIdle"
override fun isIdleNow(): Boolean {
val adapter = activityRule.scenario.onActivity { it.findViewById<RecyclerView>(R.id.recycler_view).adapter }
return adapter?.itemCount ?: 0 > 20
}
override fun registerIdleTransitionCallback(callback: IdlingResource.ResourceCallback) {}
}
IdlingRegistry.getInstance().register(pagingIdle)
// 4. Verify that item 21 appears
onView(withId(R.id.item_text))
.check(matches(withText("Item 21")))
IdlingRegistry.getInstance().unregister(pagingIdle)
}
}
Key points:
- Use
RecyclerViewActions.scrollToPositionto drive the list. - Implement an
IdlingResourcethat observes thePagingDataitem count or aMutableStateFlowexposingloadState. - Assert on item text or contentDescription to catch duplicates or missing data.
UIAutomator for Scroll Dynamics
UIAutomator works across app boundaries and can expose low‑level scroll metrics, making it suitable for performance and jank checks.
public class ScrollPerformanceTest extends UiAutomatorTestCase {
private UiDevice device;
@Override
protected void setUp() throws Exception {
super.setUp();
device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
device.pressHome();
// Launch the app
Context ctx = InstrumentationRegistry.getTargetContext();
final Intent intent = ctx.getPackageManager()
.getLaunchIntentForPackage("com.example.app");
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
ctx.startActivity(intent);
device.wait(Until.hasObject(By.pkg("com.example.app").depth(0)), 5000);
}
public void testFastFlickJank() throws UiObjectNotFoundException {
// Locate RecyclerView
UiObject list = device.findObject(new UiSelector()
.resourceId("com.example.app:id/recycler_view"));
// Record frame timestamps before flick
long[] before = device.executeShellCommand(
"dumpsys gfxinfo com.example.app | grep -A 20 \"Draw\"").getBytes();
// Perform a fast flick (150 dp/ms)
list.swipeUp(30); // 30 steps approximates a quick gesture
// Wait for UI to settle
SystemClock.sleep(500);
// Capture frame timestamps after flick
long[] after = device.executeShellCommand(
"dumpsys gfxinfo com.example.app | grep -A 20 \"Draw\"").getBytes();
// Compute jank: frames >16ms
int jankCount = computeJank(after);
assertTrue("Excessive jank detected: " + jankCount + " frames", jankCount <= 2);
}
private int computeJank(long[] frameTimes) {
int over = 0;
for (long t : frameTimes) {
if (t > 16) over++;
}
return over;
}
}
This test:
- Launches the app, grabs the RecyclerView, performs a programmable swipe.
- Uses
dumpsys gfxinfoto capture frame draw times before and after the gesture. - Flags any frame exceeding 16 ms (≈60 fps) as jank.
Integrating with CI
- Add the above tests to your
androidTestsource set. - Enable
testOptions.unitTests.includeAndroidResources = trueif you need resources. - Use Gradle’s
connectedAndroidTesttask to run on emulators or device farms. - Fail the build if jank > 2 frames per flick or if any
IdlingResourcetimes out (indicating a stalled load).
Automated tests give fast feedback on functional correctness and performance regressions, but they cannot replace the exploratory power of a human tester or a persona‑driven agent that tries unconventional gestures.
Leveraging SUSA for Autonomous Exploration
SUSA (SUSATest) is an autonomous QA platform that explores an app without pre‑written scripts. It builds a behavior model from actual interactions and can simulate a variety of user personas. Applying SUSA to infinite scroll surfaces bugs that scripted tests never think to trigger.
How SUSA Approaches Infinite Scroll
- Screen Discovery – On launch, SUSA identifies the RecyclerView as a scrollable container and notes the presence of a loading indicator.
- Persona‑Driven Strategies –
- *Curious*: slowly drags, pauses, watches for new items.
- *Impatient*: performs rapid flicks, then immediately taps an item.
- *Novice*: uses accessibility gestures (TalkBack swipe) to navigate.
- *Adversarial*: sends out‑of‑order scroll events, rotates mid‑scroll, toggles airplane mode.
- *Elderly*: simulates tremor by adding jitter to swipe coordinates.
- State Tracking – SUSA records each unique screen state (identified by view hierarchy hash) and the transitions that lead to it. If a scroll results in a state already seen, it marks the edge as a *dead end* and avoids re‑exploring it unnecessarily.
- Learning Loop – After each run, the platform updates its internal graph: successful load‑more transitions are weighted higher, while paths that lead to crashes or ANRs are deprioritized for future runs but flagged for review.
Concrete Example: Finding a Duplicate‑Item Bug
Suppose the app’s PagingSource fails to deduplicate when the network returns overlapping IDs after a retry. A scripted test that scrolls exactly 20 items then asserts item 21 would miss this because the duplication only appears after a network glitch triggers a retry on page 3.
SUSA’s *Impatient* persona might:
- Scroll quickly to load page 2.
- Toggle airplane mode to simulate loss, then restore connection.
- Immediately perform another fast flick, causing the PagingSource to request page 3 while a retry for page 2 is still in flight.
When the merged list contains duplicate IDs, SUSA’s oracle (based on heuristics like “same contentDescription appearing consecutively”) raises an alert, captures a screenshot, and logs the exact sequence of events (including timestamps and network state).
Integrating SUSA into Your Workflow
- CLI usage:
susatest-agent run --apk app-debug.apk --personas curious,impatient,adversarial --duration 10m. - Output: a JSON report with discovered flows, PASS/FAIL verdicts per persona, and a list of unique crashes/ANRs.
- Regression generation:
susatest-agent export --format appium --output tests/produces Appium Java/Kotlin scripts that replicate the paths SUSA found, giving you a starting point for deterministic tests.
Because SUSA explores the state space driven by actual interaction patterns, it often reaches combinations of gestures, device states, and network conditions that a manual tester would overlook due to time constraints, and that a scripted test would never consider because the test author did not imagine them.
Limitations to Keep in Mind
- SUSA treats the app as a black box; it cannot assert business‑logic correctness without testers adding custom oracle functions (e.g., verify that item IDs are strictly increasing).
- The platform’s exploration depth is bounded by the allotted time; for very deep paging (hundreds of pages) you may need to extend the run duration or provide a “fast‑forward” hint via a custom API that SUSA can call to jump to a specific page.
- Privacy‑sensitive data (tokens, PII) may be logged in the exploratory traces; ensure you run SUSA against a debug or staging build where such data is either masked or non‑production.
When used alongside traditional Espresso/UIAutomator checks, SUSA provides a complementary safety net that catches the “unknown unknowns” of infinite scroll.
Accessibility and Security Considerations
Infinite scroll touches two non‑functional domains that are often neglected in functional test plans: accessibility and data privacy. Below are concrete checks to add to your test suite.
Accessibility Checks
| Check | Method | Expected Result |
|---|---|---|
| ContentDescription on each item | Espresso: onView(withId(R.id.item_text)).check(matches(withContentDescription(notNullValue()))) | Every visible item has a non‑empty description. |
| Live region for loading state | Verify that a view with android:accessibilityLiveRegion="polite" updates when spinner appears/disappears. | TalkBack announces “Loading more items” when spinner visible, “Finished loading” when hidden. |
| Focus order after new items | Use UIAutomator to perform a TalkBack swipe, then check getFocusedChild() on RecyclerView. | Focus moves to the first newly loaded item, not stuck on footer. |
| Touch target size | Run androidx.test.espresso.accessibility.AccessibilityChecks.enable(); run the test suite. | No view fails the minimum 48 dp touch target rule. |
| Contrast ratio | Use the Accessibility Scanner tool on a screenshot of the list after several pages. | All text meets WCAG AA (≥4.5:1) for normal text, ≥3:1 for large text. |
| Screen reader announces duplicates | If duplicate items appear, TalkBack will read the same description twice in succession. | No duplicate announcements unless the data truly repeats (which should be flagged as a bug). |
Automating these checks can be done with the built‑in Espresso accessibility rules:
@get:Rule
val accessibilityRule = AccessibilityChecks.EnableRule()
Security / Privacy Checks
Infinite scroll often prefetches additional pages to reduce latency. Prefetching can inadvertently pull sensitive data that should remain server‑side or be encrypted at rest.
| Check | Technique |
|---|---|
| Prefetch window size | Verify that the PagingConfig’s prefetchDistance is no greater than 2–3 pages for lists containing PII. |
| Disk cache inspection | After a scrolling session, run adb shell run-as com.example.app ls /data/data/com.example.app/cache/ and inspect any files for unencrypted JSON or images containing personal data. |
| Network request inspection | Use a proxy (e.g., mitmproxy) to confirm that requests for pages beyond the visible range do not include authentication tokens in query parameters or headers that could be logged. |
| Memory leak of sensitive objects | LeakCanary should not report any retained User, Message, or Payment ViewModel after scrolling and then backgrounding the app. |
| Permission usage | Ensure the app does not request READ_EXTERNAL_STORAGE or ACCESS_FINE_LOCATION solely for infinite scroll; such permissions are unnecessary for pure UI scrolling. |
A simple test to assert that no PII appears in logs:
@Test
fun `no personal data in logcat`() {
// Perform scrolling that triggers prefetch
val scenario = ActivityScenario.launch(MainActivity::class)
scenario.onActivity {
// scroll to trigger 5 pages
val rv = findViewById<RecyclerView>(R.id.recycler_view)
rv.scrollToPosition(200)
}
// Capture recent logcat
val logs = executeShellCommand("logcat -d -t 500")
assertFalse(logs.contains("email@"))
assertFalse(logs.contains("ssn"))
scenario.close()
}
By integrating accessibility and security verification into the same test suite that covers functional correctness, you reduce the chance that a performance optimization inadvertently creates a compliance risk.
Edge Cases That Appear Only in Production
Even with exhaustive test matrices, some defects surface only after the app reaches real users under specific conditions. Below are the most elusive infinite‑scroll bugs observed in post‑release monitoring, along with tactics to surface them earlier.
1. Battery‑Driven Throttling
On devices with aggressive battery‑saver policies, the system may lower CPU frequency or throttle background services while the app is in the foreground but not interacting. This can cause the paging loader to miss its deadline, resulting in a stale spinner.
Detection:
- Use
adb shell cmd power set-adaptive-battery-enabled falseto disable adaptive battery, then re‑enable it and run a long scroll session while monitoringdumpsys batterystats. - Look for
UIDLEorTHERMAL_THROTTLEDstates coinciding with increased load‑more latency.
2. Input Method Editor (IME) Interference
When a user taps an edit‑text inside a list item (e.g., to comment), the IME may cause the RecyclerView to layout again, resetting scroll position if the adapter does not handle onSaveInstanceState correctly.
Detection:
- Automate a test that focuses an EditText inside an item, types a character, then presses back, and asserts that the scroll offset is unchanged (
RecyclerView.computeVerticalScrollOffset()).
3. Multi‑Window / Split‑Screen Mode
On foldables or tablets, users may run the app side‑by‑side with another. The system may deliver onConfigurationChanged with a new screenWidthDp while the list is mid‑scroll, causing the RecyclerView to request a new layout and lose its prefetch state.
Detection:
- Use
adb shell am start -S -n com.example.app/.MainActivity --ei windowingMode 2to launch in split‑screen, then perform a scroll and rotate the device. Verify that the list continues loading without jumping.
4. Network Handoff (Wi‑Fi to Cellular)
When the device switches from Wi‑Fi to cellular mid‑scroll, the underlying socket may be torn down. If the paging library does not recreate the request correctly, the list stalls.
Detection:
- Use
adb shell cmd connectivity set-mobile-data-enabled trueandfalseto toggle while scrolling. Monitor logcat forHttpExceptionorIOExceptionand ensure the paging state transitions toLoadState.Errorthen back toLoadState.Loadafter retry.
5. Database Cursor Exhaustion (Room + Paging 3)
If the backing DataSource is a LimitOffsetDataSource over a Room query that uses LIMIT/OFFSET, high offset values can cause slow queries and eventual cursor exhaustion on low‑end devices.
Detection:
- Enable
RoomDatabase.QueryCallbackto log query execution times. Assert that average query time stays below 150 ms even at offset 5000. - Use
adb shell dumpsys cpuinfoto watch for spikes in theandroid.process.acorethread during scrolling.
6. Overdraw from Item Decorations
Custom ItemDecoration that draws dividers or backgrounds can cause excessive overdraw when many items are on screen, leading to dropped frames on low‑GPU devices.
Detection:
- Use
adb shell gfxinfo com.example.appand examine theOverdrawcolumn; values > 2× indicate trouble. - Run the test on a low‑end emulator (e.g.,
pixel_2_api_30withramSize 512) and ensure frame times stay under 16 ms.
7. Accessibility Service Conflict
Some third‑party accessibility services (e.g., font changers, screen dimmers) intercept touch events and can delay or distort scroll gestures, causing the app to miss the threshold for loading more.
Detection:
- Install a known accessibility service from Play Store, enable it, then run a scroll session. Compare the number of load‑more triggers against a baseline run without the service. A significant drop indicates a conflict.
8. Memory Pressure from Background Services
A music playback service or location tracker running in the same process can consume RAM, triggering early GC cycles during scrolling, which manifests as periodic UI freezes.
Detection:
- Use
adb shell dumpsys meminfobefore and after starting a background service, then scroll. Look for increasedGCfrequency in logcat (GC_CONCURRENTspikes).
Mitigation Strategies to Incorporate Early
- Parameterize your paging config in a debug build so you can easily inflate
prefetchDistanceorpageSizeto stress test the loading logic. - Expose a test hook (e.g., via
BuildConfig.DEBUG) that allows injecting artificial latency (SystemClock.sleep(200)) into the data source’sloadfunction to emulate slow networks or CPU throttling. - Log paging state transitions (
LoadState.Load,LoadState.Error,LoadState.NotLoading) to a file that can be pulled after a CI run; analyze the sequence for abnormal patterns (e.g., Load → Error → Load without user interaction). - Run a “monster” test on a device farm that randomizes gestures, rotates, toggles airplane mode, and enables/disables battery saver for 30 minutes, then asserts that no crash or ANR occurred and that the list still shows new items.
By intentionally reproducing these production‑only conditions in a controlled environment, you shrink the gap between lab testing and real‑world reliability.
Checklist and Takeaways
Quick‑Reference Checklist
| Area | Item | How to Verify |
|---|---|---|
| Functional | First page loads correctly | Espresso assertion on first item text |
| Load‑more triggers at correct threshold | Scroll to itemCount‑prefetchDistance, verify spinner appears | |
| No duplicate items | Collect item IDs in a Set while scrolling, assert size equals count | |
| Error state shown on network failure | Throttle network to 100% loss, verify retry UI appears | |
| Position retained after rotation | Rotate device, compare computeVerticalScrollOffset() before/after | |
| Performance | Frame time <16 ms during fast flick | UIAutomator + dumpsys gfxinfo jank count ≤2 |
| Memory growth <5 MB per 10 pages | dumpsys meminfo delta after scrolling 50 pages | |
| No main‑thread disk/network | StrictMode enabled, logcat clear of violations | |
| Accessibility | ContentDescription present on all items | AccessibilityChecks.EnableRule() |
| Live region announces loading/spinner | TalkBack feedback captured via AccessibilityEvent | |
| Focus moves to newly loaded items | UIAutomator focus check after scroll | |
| Security/Privacy | Prefetch window ≤2 pages for PII lists | Inspect PagingConfig in debug build |
| No unencrypted PII in cache | run-as cache directory scan after scroll | |
| No auth tokens leaked in network logs | Proxy inspection of requests beyond visible range | |
| Production‑Like | Battery saver enabled does not stall loader | Toggle power mode, watch load‑more latency |
| IME open/close does not reset scroll | Focus EditText, type, back, verify offset | |
| Split‑screen mode maintains state | Launch in multi‑window, scroll, rotate, verify | |
| Network handoff recovers gracefully | Toggle Wi‑Fi/mobile data while scrolling, observe retry logic | |
| Low‑end device (≤1 GB RAM) stays responsive | Run on Android Go emulator, measure frame drops | |
| Third‑party accessibility service does not block load‑more | Install service, repeat scroll, compare load‑more count |
Core Takeaways
- Treat infinite scroll as a state machine, not a static list. Each
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