How to Test Wishlists on Android (Complete Guide)

Wishlists are a core conversion driver in e‑commerce, media, and productivity apps. Users add items they intend to purchase later, compare options, or save for future reference. When a wishlist fails—

February 22, 2026 · 17 min read · How-To Guides

Why Wishlist Testing Matters on Android

Wishlists are a core conversion driver in e‑commerce, media, and productivity apps. Users add items they intend to purchase later, compare options, or save for future reference. When a wishlist fails—items disappear, cannot be removed, or trigger crashes—users lose trust and may abandon the app entirely. On Android, the wishlist interacts with several platform components: local databases (Room or SharedPreferences), network sync services, notification channels, and accessibility services. A defect in any of these layers can surface only under specific device states, making wishlist testing a high‑impact area for quality assurance.

Common Wishlist Failures in Production

Production logs reveal recurring patterns that are rarely caught by scripted UI tests:

These issues often appear only after certain combinations of network latency, low memory, or specific Android versions, which explains why they escape deterministic test suites.

Test Matrix for Wishlist Functionality

Below is a comprehensive matrix that covers happy paths, error paths, edge cases, accessibility, and security. Each row includes a unique identifier, a concise description, the expected outcome, and a suggested severity level (Critical, High, Medium, Low).

IDDescriptionExpected ResultSeverity
WL‑01Add a single item to an empty wishlist from product detail page.Item appears in wishlist list; badge increments by 1.Critical
WL‑02Add the same item twice (quick double‑tap).Only one entry exists; no duplicate.High
WL‑03Remove an item via swipe‑to‑delete.Item disappears; badge decrements by 1.Critical
WL‑04Remove all items using “Clear Wishlist” button.List empties; badge shows zero.High
WL‑05Add item while offline.Item saved locally; sync indicator shows pending; when online, item uploads.Medium
WL‑06Add item when server returns 500 error.Local fallback saves item; error toast shown; retry on next online state.Medium
WL‑07Wishlist opens after device rotation.List retains same items and scroll position.Medium
WL‑08Wishlist accessed in split‑screen mode.UI adapts; no overlapping elements; actions remain functional.Low
WL‑09TalkBack navigation through wishlist items.Each item announces title, price, and actionable states (add/remove).High
WL‑10Wishlist item with long title exceeding 100 characters.Text truncates with ellipsis; full text accessible via long‑press tooltip.Low
WL‑11Wishlist data stored in Room DB is encrypted.DB file on disk shows encrypted blobs; no plain‑text IDs visible.High
WL‑12Wishlist sharing via intent copies only a public URL, not internal IDs.Shared text contains no internal identifiers.Medium
WL‑13Wishlist receives a push notification while in background.Notification opens wishlist to the correct item; no crash.Medium
WL‑14Low storage condition (<100 MB free) while adding items.Addition fails gracefully with inline error; no crash.Medium
WL‑15Adding item with inaccessible image (content‑description missing).Image still loads; but TalkBack reads fallback description.Low
WL‑16Wishlist accessed via direct deep link from external app.App opens to wishlist screen; item pre‑selected if link includes ID.Medium
WL‑17Wishlist item price updates after add (server pushes new price).Wishlist reflects new price without manual refresh.Low
WL‑18Wishlist item removed from server while locally present.Local item removed on next sync; UI shows removal.Medium
WL‑19Wishlist accessed with system font size set to largest.All text scales; no clipping or overlap.Medium
WL‑20Wishlist screen uses color contrast below WCAG AA for buttons.Contrast ratio ≥ 4.5:1 for normal text; ≥ 3:1 for large text.High

Manual Testing Approach

Setup and Environment

  1. Device matrix – Test on at least three physical devices representing different API levels (e.g., Android 10 (API 29), Android 12 (API 31), Android 13 (API 33)) and varying screen sizes.
  2. Emulator supplement – Use Android Studio’s emulator to simulate low‑memory, low‑storage, and different locale configurations quickly.
  3. Network tools – Enable Charles Proxy or Android’s built‑in adb shell tc to introduce latency (150 ms), packet loss (5 %), or bandwidth caps.
  4. Accessibility tools – Install TalkBack, Accessibility Scanner, and the Android Accessibility Test Framework (AATF) for manual checks.
  5. Logging – Set adb logcat -v threadtime to capture Wishlist‑related tags (Wishlist, SyncManager, RoomDB).

Step‑by‑Step Test Cases

Below is a manual test script that covers the matrix items WL‑01 through WL‑05. Each step includes the action, expected observation, and notes on what to verify.

  1. Launch app – Verify home screen loads without error.
  2. Navigate to product catalog – Tap a category, then a product.
  3. Add to wishlist (WL‑01) – Press the heart icon. Observe a toast “Added to wishlist” and the badge on the wishlist tab increments to 1.
  4. Open wishlist – Tap the wishlist tab. Confirm the newly added item appears at the top of the list, showing title, thumbnail, price, and a remove icon.
  5. Add same item again quickly (WL‑02) – Return to product page, tap the heart twice within 300 ms. Verify the badge remains at 1 and the list still shows a single entry.
  6. Remove via swipe (WL‑03) – In wishlist, swipe the item left. Confirm a snackbar appears with “Removed” and an undo action. The badge decrements to 0 and the list empties.
  7. Test offline add (WL‑05) – Disable Wi‑Fi and mobile data. Return to product page, add an item. Observe a local save indicator (e.g., a small clock icon). Re‑enable network; watch for a sync indicator and the item appearing in the server‑backed list after a few seconds.
  8. Verify persistence after rotation (WL‑07) – While in wishlist, rotate device to landscape. Ensure the list retains the same scroll position and item order.

Repeat similar sequences for the remaining matrix IDs, adjusting preconditions (e.g., set low storage via adb shell sm set-storage-low true).

Exploratory Checks

Automated Testing on Android

Unit and Integration Tests

Unit tests validate the WishlistViewModel and repository logic without UI. Example using JUnit5 and MockK:


// WishlistViewModelTest.kt
class WishlistViewModelTest {

    private lateinit var viewModel: WishlistViewModel
    private val mockRepo = mockk<WishlistRepository>()

    @BeforeEach
    fun setup() {
        viewModel = WishlistViewModel(mockRepo)
    }

    @Test
    fun `addItem increments liveData count`() {
        // given
        every { mockRepo.addItem(any()) } returns Unit
        // when
        viewModel.addItem(SampleItem(id = 1, name = "Test"))
        // then
        verify(exactly = 1) { mockRepo.addItem(any()) }
        assertEquals(1, viewModel.itemCount.getOrAwaitValue())
    }
}

Integration tests with AndroidJUnitRunner verify Room DAO interactions:


@RunWith(AndroidJUnit4::class)
class WishlistDaoTest {

    private lateinit var dao: WishlistDao
    private lateinit var db: WishlistDatabase

    @Before
    fun createDb() {
        val context = ApplicationProvider.getApplicationContext()
        db = Room.inMemoryDatabaseBuilder(
            context, WishlistDatabase::class.java
        ).allowMainThreadQueries().build()
        dao = db.wishlistDao()
    }

    @After
    fun closeDb() = db.close()

    @Test
    fun insertAndRetrieve() {
        val item = WishlistItem(id = 0, productId = "abc", title = "Book")
        dao.insert(item)
        val loaded = dao.getAll().first()
        assertEquals(item.title, loaded.title)
    }
}

UI Automation with Espresso

Espresso tests provide fast, deterministic validation of core flows. Below is a parameterized test covering add, remove, and offline scenarios.


@LargeTest
@RunWith(AndroidJUnit4::class)
class WishlistEspressoTest {

    @get:Rule
    val activityRule = ActivityScenarioRule(MainActivity::class.java)

    @Test
    fun addRemoveItem_flow() {
        // Navigate to product detail
        onView(withId(R.id.recycler_products))
            .perform(RecyclerViewActions.actionOnItemAtPosition(0, click()))

        // Add to wishlist
        onView(withId(R.id.fab_add_wishlist))
            .perform(click())
        onView(withText("Added to wishlist"))
            .inRoot(isToast())
            .check(matches(isDisplayed()))

        // Open wishlist tab
        onView(withId(R.id.tab_wishlist))
            .perform(click())
        onView(withId(R.id.wishlist_recycler))
            .check(matches(hasDescendant(withText("Sample Product"))))

        // Remove item
        onView(withId(R.id.wishlist_recycler))
            .perform(RecyclerViewActions.actionOnItemAtPosition(0, swipeLeft()))
        onView(withId(R.id.wishlist_recycler))
            .check(matches(not(hasDescendant(withText("Sample Product")))))
    }

    @Test
    fun offlineAdd_syncsWhenOnline() {
        // Simulate offline
        adbShell("svc wifi disable")
        adbShell("svc data disable")

        onView(withId(R.id.fab_add_wishlist))
            .perform(click())
        onView(withId(R.id.wishlist_recycler))
            .check(matches(hasDescendant(withText("Sample Product"))))

        // Go online
        adbShell("svc wifi enable")
        // Wait for sync (custom IdlingResource)
        onView(withId(R.id.sync_progress))
            .check(matches(not(isDisplayed())))
        // Verify badge updatedItem
        // Server‑side verification could be done via a mock API
    }
}

Helper method for adb commands inside tests:


private fun adbShell(command: String) {
    Runtime.getRuntime().exec("adb $command").waitFor()
}

Using Appium for Cross‑Device Validation

Appium enables testing on real device farms or emulators without recompiling the test APK. Below is a sample appium.yml configuration and a Python script that performs the same add‑remove flow.


# appium.yml
capabilities:
  - platformName: Android
    automationName: UiAutomator2
    deviceName: Pixel_4_API_33
    appPackage: com.example.myapp
    appActivity: .MainActivity
    noReset: true

# test_wishlist.py
from appium import webdriver
from appium.webdriver.common.appiumby import AppiumBy
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

def test_add_remove():
    driver = webdriver.Remote(
        command_executor='http://localhost:4723/wd/hub',
        desired_capabilities={
            "platformName": "Android",
            "deviceName": "Pixel_4_API_33",
            "appPackage": "com.example.myapp",
            "appActivity": ".MainActivity",
            "noReset": True
        }
    )
    wait = WebDriverWait(driver, 20)

    # Add item
    add_btn = wait.until(EC.element_to_be_clickable((AppiumBy.ID, "com.example.myapp:id/fab_add_wishlist")))
    add_btn.click()
    toast = wait.until(EC.presence_of_element_located((AppiumBy.XPATH, "//*[@text='Added to wishlist']")))
    assert toast.is_displayed()

    # Open wishlist
    driver.find_element(AppiumBy.ID, "com.example.myapp:id/tab_wishlist").click()
    item = wait.until(EC.presence_of_element_located((AppiumBy.ID, "com.example.myapp:id/item_title")))
    assert item.text == "Sample Product"

    # Remove item
    driver.find_element(AppiumBy.ID, "com.example.myapp:id/wishlist_recycler")\
          .swipe(start_x=800, start_y=500, end_x=200, end_y=500, duration=800)
    undo = driver.find_element(AppiumBy.ID, "com.example.myapp:id/snackbar_action")
    undo.click()  # optional undo test

    driver.quit()

Run the test with:


appium server -wp /tmp/appium.log
pytest test_wishlist.py -v

Data‑Driven and Parameterized Tests

To cover variations like different product types, prices, and locales, use a CSV feed with JUnit’s @ParameterizedTest. Example in Kotlin:


@ParameterizedTest
@CsvSource(
    "Book, 12.99, en",
    "Книга, 12.99, ru",
    "書籍, 12.99, ja"
)
fun addItem_locale(title: String, price: Double, locale: String) {
    // Set locale
    val config = ApplicationProvider.getApplicationContext().resources.configuration
    config.setLocale(Locale.forLanguageTag(locale))
    ApplicationProvider.getApplicationContext().resources.updateConfiguration(
        config,
        ApplicationProvider.getApplicationContext().resources.displayMetrics
    )
    // Perform add via UI or ViewModel and assert localized price format
}

Accessibility and WCAG Checks

Tools

Specific Wishlist Accessibility Issues

IssueImpactFix
Missing contentDescription on the heart iconTalkBack users cannot discern whether the item is already wishlisted.Add android:contentDescription="@string/add_to_wishlist" and update state dynamically.
Touch target < 48 dp for remove buttonUsers with motor impairments may miss the target.Increase padding or use android:minWidth/android:minHeight.
Low contrast on wishlist item title (gray on white)Fails WCAG AA for normal text.Adjust text color to meet ≥ 4.5:1 ratio.
No announcement when badge count changesUsers relying on audio cues miss updates.Use AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED or update a hidden android:accessibilityLiveRegion="assertive" view.
Wishlist screen not announcing screen title on navigationTalkBack users lose context.Set android:screenReaderFocusable="true" on the toolbar title or announce via AccessibilityManager.

An Espresso test using the Accessibility Test Framework:


@Test
fun wishlistScreen_hasProperLabels() {
    onView(withId(R.id.tab_wishlist)).perform(click())
    // Assert content description on heart icon
    onView(withId(R.id.fab_add_wishlist))
        .check(matches(hasContentDescription(`is`(containsString("Add to wishlist")))))
    // Assert minimum touch size
    onView(withId(R.id.ib_remove_item))
        .check(matches(isDisplayingAtLeast(48, 48)))
}

Run androidx.test.espresso.accessibility.AccessibilityChecks.enable() in your test suite to automatically scan for violations on each test.

Security and Privacy Considerations

Data Storage

Wishlist items often contain personal intent data (e.g., gifts, medical supplies). Store them using Android’s EncryptedSharedPreferences or Room with SQLCipher. Verify that the backup flag is disabled (android:allowBackup="false" in manifest) to prevent ADB backup leakage.

Network Transmission

All sync requests must use HTTPS with certificate pinning. Use NetworkSecurityConfig to enforce pinning and disable cleartext traffic.

Logging and Clipboard

Avoid logging wishlist IDs or product SKUs at verbose levels. Use ProGuard rules to strip logging calls in release builds. Never copy internal identifiers to the clipboard unless the user explicitly initiates a share action.

Test Cases for Security

Test IDDescriptionExpected
WL‑SEC‑01Attempt to read wishlist DB via adb backup after disabling backup.Backup fails or returns empty file.
WL‑SEC‑02Capture network traffic with adb shell tcpdump while adding an item.Requests go over TLS; no plain‑text JSON with IDs.
WL‑SEC‑03Try to paste a wishlist ID into another app’s input field via clipboard after a share action.Clipboard contains only the public URL, not internal IDs.
WL‑SEC‑04Run MobSF or QARK static analysis on the APK.No high‑severity findings related to insecure storage or logging.

Automated security scanning can be added to CI via ./gradlew assembleDebug followed by java -jar mobsf.jar upload APK.

Autonomous, Persona‑Driven Exploration with SUSA

SUSA is an autonomous QA agent that explores an Android app without predefined scripts. It simulates distinct user personas—each with its own behavior model—to surface issues that scripted tests miss.

How SUSA Works

  1. App ingestion – Provide an APK or a web‑app URL; SUSA installs the app on a fleet of emulated or real devices.
  2. Persona selection – Choose from built‑in profiles (e.g., *Impatient Shopper*, *Elderly User*, *Accessibility‑Focused*, *Adversarial*). Each profile defines tap timing, scroll depth, tolerance for errors, and likelihood to use voice input or accessibility services.
  3. Exploration loop – SUSA performs actions, observes state changes, and builds a graph of screens and transitions. It records crashes, ANRs, UI freezes, and accessibility violations.
  4. Learning – After each run, the agent remembers dead ends (e.g., a button that leads nowhere) and avoids repeating fruitless paths, increasing coverage over time.

Persona Profiles Relevant to Wishlists

PersonaBehavior TraitsTypical Wishlist Interactions
Curious ExplorerLong dwell time, explores every UI element, uses long‑press for context menus.May discover hidden “Save for later” gestures or unintentionally trigger duplicate adds via long‑press.
Impatient ShopperRapid taps, minimal waiting, often abandons if feedback delayed.Triggers race conditions when tapping add while network request is pending; may expose missing progress indicators.
Elderly UserLarger touch targets, slower gestures, relies on accessibility features.Highlights touch‑target size issues and TalkBack labeling gaps.
Adversarial TesterAttempts unexpected inputs, rapid rotation, network toggling, and恶意数据.Can provoke crashes from malformed deep links, expose insufficient input validation, or reveal state corruption during forced locale switches.
Power UserUses shortcuts, swipe gestures, and often utilizes system share/intents.Tests the robustness of share intent handling and clipboard data leakage.

Example Findings

During a recent engagement with a fashion‑app test, SUSA’s *Adversarial* persona:

These defects would not have been exercised by a standard happy‑path Espresso script because they depend on specific timing, unusual input, and device state combinations that only emerge under exploratory, persona‑driven interaction.

To run SUSA locally:


pip install susatest-agent
susatest explore --apk path/to/app.apk \
    --personas impatient,elderly,adversarial \
    --output ./susa-report \
    --max-depth 6

The generated report includes a PASS/FAIL matrix for each discovered flow, video recordings of failing sessions, and suggestions for fixing root causes.

Edge Cases That Only Appear in Production

Even with thorough lab testing, certain conditions manifest only after the app reaches real users. Below are the most common production‑only edge cases for wishlists, along with detection strategies.

Network Fluctuations and Background Sync

Low Storage and Memory Pressure

Locale and Right‑to‑Left Languages

Multi‑Window and Picture‑in‑Picture

Push Notifications Interference

System Font Scaling

Battery‑Saver and Doze Modes

Consolidated Checklist for Wishlist Testing

CategoryItemVerify
Happy PathAdd single itemItem appears, badge increments
Remove item via swipeItem removed, badge decrements
Clear wishlistList empty, badge zero
Offline add + syncLocal pending indicator → server reflects addition
Error PathsDuplicate add (rapid tap)No duplicate entry
Server error during addLocal fallback, retry on reconnect
Network loss during syncNo crash, sync resumes when online
UI/StateRotation preserves list & scrollNo data loss, scroll position retained
Multi‑window layout stableNo overlap, all actions functional
Locale switch (RTL)Layout mirrors correctly, touch targets intact
Font size scalingNo clipping, all text readable
AccessibilityContent descriptions on all iconsTalkBack announces purpose
Minimum touch target 48 dpPasses Accessibility Scanner
Contrast ratio ≥ 4.5:1 (AA)Verified with color contrast analyzer
Live region for badge updatesTalkBack announces count change
SecurityEncrypted local storageDB file unreadable without key
No clear‑text logging of IDsLogcat shows no wishlist IDs
HTTPS with pinning for syncNetwork traffic encrypted
Clipboard shares only public URLNo internal IDs exposed
PerformanceAdd under high latency (< 300 ms)UI shows progress spinner, no ANR
Low memory (< 150 MB free)App remains responsive, graceful error
Battery saver / DozeBackground uploads resume after exit
Production‑OnlyNetwork handoff (Wi‑Fi ↔ cellular)No lost items after switch
Storage near‑full (< 50 MB)No silent DB commit failures
Push notification deep linkSingle wishlist instance, correct state
Rapid locale change during addNo crash, UI updates correctly
PiP + split‑screen interactionWishlist stays in sync, no leaks

Run this checklist on each release candidate; automate the items that lend themselves to UI or API tests, and reserve the exploratory, persona‑driven runs for the quarterly regression cycle.

Takeaways and Best Practices

  1. Treat the wishlist as a distributed system – it spans UI, local persistence, network sync, and background workers. Test each boundary contract (UI↔ViewModel, ViewModel↔Repository, Repository↔Network, Repository↔DB) with focused unit and integration tests.
  2. Leverage personas, not just scripts – autonomous explorers like SUSA uncover timing‑sensitive bugs, input‑validation gaps, and accessibility regressions that deterministic UI tests never reach. Schedule regular persona runs (e.g., nightly) to keep the test suite honest.
  3. Make accessibility a first‑class metric – integrate Accessibility Test Framework into your CI pipeline; treat any WCAG AA violation as a blocker, just like a crash.
  4. Guard data at rest and in transit – use EncryptedSharedPreferences or SQLCipher for local storage, enforce HTTPS with certificate pinning, and never log or clipboard‑share internal identifiers. Automated security scans (MobSF, QARK) should run on every build.
  5. Simulate real‑world stress – network throttling, low‑storage locales, font scaling, and multi‑window configurations are cheap to emulate with adb commands or device‑farms. Include them in your regression matrix to catch issues before they hit the Play Store.
  6. Keep regression scripts lightweight – the auto‑generated Appium/Playwright flows from SUSA are excellent for smoke‑testing core wishlist journeys, but complement them with focused Espresso tests for edge‑case validation (e.g., rapid‑tap duplicate prevention).
  7. Monitor production signals – instrument your wishlist module with custom metrics (add success rate, sync conflict count, accessibility event drops). Anomalies in these metrics often precede user‑visible bugs and can trigger automated hot‑fix rollouts.

By combining rigorous manual checks, automated unit/UI tests, accessibility and security validation, and autonomous persona‑driven exploration, you can deliver a wishlist experience that feels reliable, inclusive, and secure across the vast Android ecosystem. Treat the wishlist not as a simple list

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