How to Test Contact List: A Complete Guide

How to Test Contact List: A Complete Guide

June 03, 2026 · 18 min read · How-To Guides

How to Test Contact List: A Complete Guide

Testing a contact list is more than verifying that names appear; it is about ensuring that the core social graph of an application remains reliable, private, and usable under every condition a user might encounter. A faulty contact list can break login flows, prevent messaging, expose personal data, or cause accessibility barriers that drive users away. This guide walks you through a complete, platform‑agnostic testing strategy, from why it matters to a ready‑to‑use checklist, with concrete examples, tables, and code snippets you can apply immediately.

Why Contact List Testing Matters

Direct impact on user goals

When a user opens an app to send a message, start a call, or share a file, the first action is often to pick a recipient from the contact list. If the list fails to load, shows duplicate entries, or hides a contact because of a permission glitch, the user’s goal is blocked instantly. Studies show that a single failure in a primary task flow can increase abandonment rates by up to 20 %.

Business and compliance risks

Beyond frustration, contact list defects can lead to regulatory exposure. Mishandled personal data may violate GDPR, CCPA, or HIPAA, especially if the app inadvertently shares contacts with third‑party services or stores them in plain text. Additionally, crashes or ANRs triggered by list operations generate negative reviews and affect app store rankings.

Ripple effects on other features

Many features depend on the contact list as a data source: invitation flows, referral programs, social sharing, and emergency‑contact features. A defect in the list propagates outward, multiplying the cost of a bug. Testing the list in isolation therefore protects a wide surface area of the application.

Core Concepts: What a Contact List Entails

Data model fundamentals

At its simplest, a contact record includes fields such as firstName, lastName, phoneNumbers (array), emailAddresses (array), postalAddresses, avatar, and a unique identifier (contactId). Platforms may extend this with custom fields like notes, ringtone, or groupMembership. Understanding the schema helps you design validation tests for each field type, length limits, and allowed characters.

UI interactions

Typical interactions include:

Each interaction can expose different classes of bugs, from UI rendering issues to data persistence failures.

Platform variations

Knowing these differences lets you tailor test data (e.g., phone number formats) and tooling (Appium for native, Playwright for web) to the target platform.

Test Matrix for Contact List

Below is a comprehensive matrix that covers happy paths, error paths, edge cases, accessibility, and security. Each row can be turned into a test case ID for manual or automated execution.

Test IDCategoryDescriptionExpected ResultPlatform Notes
CL‑001Happy pathLaunch app, navigate to contact list, verify list loads within 2 sList displays contacts, no empty placeholdersAll
CL‑002Happy pathScroll to bottom of list with 500 contactsNo jank, all contacts rendered, scroll position retainedPerformance‑sensitive
CL‑003Error pathDeny contacts permission, open listApp shows permission rationale UI, no crashiOS/Android
CL‑004Error pathRevoke permission while list is visibleList gracefully hides data, shows empty state messageiOS/Android
CL‑005Edge caseContact with phone number exceeding 20 digitsInput rejected, validation error shownAll
CL‑006Edge caseContact name containing Unicode emojisName displays correctly, searchable by emojiiOS/Android/Web
CL‑007Edge caseVery long name (200 chars)UI truncates with ellipsis, full name visible on detail screenAll
CL‑008Edge caseContact with no phone or email (only name)Contact appears, actions like call/message disabledAll
CL‑009SearchSearch for “John” returns all contacts with John in any fieldResults sorted alphabetically, no missing matchesAll
CL‑010SearchSearch with special characters (@#$%) returns no results, no crashEmpty state shown, search field clearedAll
CL‑011CreateAdd new contact with mandatory fields onlyContact saved, appears in list, editableAll
CL‑012CreateAttempt to save contact with duplicate phone numberSystem warns of duplicate, prevents save or offers mergeAndroid (native)
CL‑013EditChange avatar image, verify persistence after app restartNew avatar displayed, original retained in backup if applicableiOS/Android
CL‑014DeleteSwipe‑to‑delete contact, confirm deletionContact removed from list, undo toast appearsiOS/Android
CL‑015DeleteDelete contact while it is open in detail viewDetail view closes, list updates, no dangling referenceAll
CL‑016Permission change mid‑useStart with permission granted, revoke while typing in searchSearch stops, UI shows permission prompt, no exceptioniOS/Android
Android
CL‑017Large data setLoad 10 000 contacts via syncList loads within 5 s, memory usage < 150 MB, scroll smoothPerformance test
CL‑018Network lossDisable Wi‑Fi/cellular after list loaded, attemptAll
CL‑019Locale changeSwitch device language to right‑to‑left (Arabic)Layout mirrors, text aligns correctly, no clippingiOS/Android/Web
CL‑020AccessibilityNavigate list using TalkBack/VoiceOverEach item announces name, phone, and action hintsiOS/Android
CL‑021SecurityAttempt to read contacts via background service without foreground permissionSystem blocks read, logs security violationAndroid
CL‑022SecurityShare contact via intent, verify only selected fields transmittedOnly name and phone number sent, no extra dataAndroid
CL‑023Data integritySimulate sync conflict (two edits offline)Conflict resolution UI appears, user can choose versioniOS/Android
CL‑024LocalizationContact fields with date of birth in various formatsDates displayed according to locale, editable correctlyAll
CL‑025StressRapidly add/delete 100 contacts in successionNo crashes, list stays responsive, DB integrity intactAll

*Use this matrix as a baseline; add platform‑specific rows (e.g., such as testing CNContactFetchRequest batch size on iOS or AsyncTaskLoader behavior on Android.*

Manual Testing Approach

Exploratory testing checklist

Even the most exhaustive automated suite benefits from human intuition. When you explore the contact list manually, focus on the following areas:

  1. Permission flow – Grant, deny, and toggle permissions while the list is visible, during search, and while editing a contact. Observe whether the UI gracefully degrades or crashes.
  2. Data entry boundaries – Try maximum lengths, illegal characters, leading/trailing spaces, and varied scripts (Cyrillic, Arabic, Han). Check for truncation, overflow, or incorrect validation.
  3. Interaction chaining – Start from the list, open a contact detail, edit a field, return to the list, then immediately search for the edited value. Verify that the UI reflects the change without a full reload.
  4. Gesture variations – On touch devices, test long‑press, swipe‑left/right, double‑tap, and pinch‑to‑zoom where applicable. Ensure that unintended gestures do not trigger actions.
  5. Interruption simulation – Receive an incoming call, switch to another app, or lock the screen while the list.Confirm that the list returns. After resuming, the list should be in the same state, with no stale selections.
  6. Accessibility audit – Enable screen‑reader magnification, invert colors, and increase font size. Verify that all touch targets meet the 48 dp minimum and that labels are descriptive.
  7. Performance observation – Scroll quickly through a large list, flip between tabs, and note any frame drops or memory warnings in device logs.

Tools and techniques

Example session

*Charter*: “Validate that the contact list handles a contact with a phone number containing extensions (e.g., +1‑555‑123‑4567 ext. 89) correctly across create, edit, and search flows.”

  1. Launch the app, grant contacts permission.
  2. Tap Add Contact, fill first name “Test”, phone +1-555-123-4567 ext. 89.
  3. Save, return to list – verify the contact appears with the extension visible.
  4. Open the contact detail, change the extension to ext. 90, save.
  5. Return to list, search for “ext. 90” – the contact should appear.
  6. Revoke contacts permission, repeat step 4 – app should show permission rationale and not crash.
  7. Re‑grant permission, verify the edited contact still shows the updated extension.

This session catches validation, persistence, search indexing, and permission‑handling bugs in one go.

Automated Testing Approaches

Unit tests for the contact model

Validate the core data layer independent of UI.


// JUnit 5 example for Android Contact entity
@Test
void phoneNumber_withExtension_isStoredCorrectly() {
    Contact c = new Contact();
    c.setFirstName("Ada");
    c.setPhoneNumber("+1-555-123-4567 ext. 89");
    assertEquals("+1-555-123-4567 ext. 89", c.getPhoneNumber());
    // Ensure getter returns trimmed value if required by business rule
    assertFalse(c.getPhoneNumber().endsWith(" "));
}

Similar tests in Swift (XCTest) or JavaScript (Jest) cover edge cases like nil fields, maximum length, and Unicode normalization.

UI tests with Appium (native) and Playwright (web)

Automate the flows that users actually perform. Below is a compact Appium Java script that tests the add‑contact happy path and a permission denial scenario.


// Appium Java – Add contact
@Test
public void testAddContact() throws Exception {
    // Assume driver is initialized with desiredCapabilities for Android
    MobileElement addBtn = driver.findElementById("fab_add_contact");
    addBtn.click();

    MobileElement firstName = driver.findElementById("input_first_name");
    firstName.sendKeys("Lena");

    MobileElement phone = driver.findElementById("input_phone");
    phone.sendKeys("+49 151 23456789");

    MobileElement saveBtn = driver.findElementById("btn_save");
    saveBtn.click();

    // Verify contact appears in list
    MobileElement listItem = driver.findElementByAndroidUIAutomator(
            "new UiSelector().textContains(\"Lena\")");
    assertTrue(listItem.isDisplayed());
}

// Permission denial test
@Test
public void testContactListWithoutPermission() throws Exception {
    // Revoke permission via ADB before launching
    DriverHelper.revokePermission("android.permission.READ_CONTACTS");
    driver.launchApp();

    MobileElement contactTab = driver.findElementByAccessibilityId("Contacts");
    contactTab.click();

    MobileElement emptyState = driver.findElementById("txt_empty_state");
    assertEquals("Grant contact access to see your contacts", emptyState.getText());
}

For web contacts (e.g., a CRM that loads contacts from an API), Playwright offers a concise alternative:


// Playwright test – search and select contact
test('search returns matching contact', async ({ page }) => {
  await page.goto('https://app.example.com/contacts');
  await page.fill('#search-box', 'Maria Garcia');
  await page.waitForSelector('tbody tr:has-text("Maria Garcia")');
  const row = page.locator('tbody tr:has-text("Maria Garcia")');
  await expect(row).toBeVisible();
  await row.click();
  await expect(page.locator('detail-panel')).toContainText('Maria Garcia');
});

Both frameworks allow you to parameterize data sets (CSV, JSON) to run the same script with many variations (long names, special characters, empty fields).

API tests for contact sync

If your app synchronizes contacts with a backend, test the contract directly.


# curl example – create contact via REST API
curl -X POST https://api.example.com/v1/contacts \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "firstName": "Yuki",
        "lastName": "Tanaka",
        "phoneNumbers": ["+81 90-1234-5678"],
        "emails": ["yuki.tanaka@example.com"]
      }' | jq .

Assert that the response 201 Created contains the same fields and that a subsequent GET /contacts returns the newly created record. Add negative tests for missing required fields, invalid phone formats, and oversized payloads.

Data‑driven and property‑based testing

Use tools like jqwik (Java) or hypothesis (Python) to generate random yet valid contact objects and assert invariants such as:

These techniques uncover edge cases that manual example‑based tests might miss, like a name consisting solely of combining characters.

Edge Cases That Only Appear in Production

Network interruptions mid‑operation

A user may start editing a contact, then lose connectivity before hitting save. The app should either queue the change for later sync or revert to the last known good state, never leaving a half‑written record in the local DB. Test by toggling airplane mode during a save flow and observing the UI and DB state after reconnection.

Permission changes while the UI is active

On Android, a user can disable READ_CONTACTS from Settings while your activity is in the foreground. The system will not automatically pause your UI; you must handle SecurityException when accessing the ContentResolver. Verify that your code catches the exception, shows a permission rationale, and does not crash.

Contact merging from multiple sources

Modern smartphones aggregate contacts from Google, Exchange, SIM, and local accounts. When two sources provide conflicting data (different phone numbers for the same name), the OS may present a unified view or keep them separate. Your app must decide whether to show the merged view, preserve source labels, or allow the user to choose. Test by adding a contact via Google sync, then editing the same contact locally, and confirming the final representation matches your merge policy.

Locale and formatting issues

Phone number formats vary wildly: +1 (555) 123-4567 in the US, 020 7946 0958 in the UK, +61 2 9876 5432 in Australia. If your app displays numbers using a hard‑coded mask, you will see misplaced parentheses or missing spaces in certain locales. Test by setting the device language to a region with a distinct format and confirming that the displayed number adheres to the local pattern (use PhoneNumberUtil from libphonenumber for verification).

Large contact list performance

Devices with limited RAM may struggle when the app loads thousands of contacts into a RecyclerView or ListView without pagination. Monitor GPU overdraw, frame time, and memory churn using Android Studio Profiler or Instruments. Implement lazy loading with Paging 3 or UITableViewDiffableDataSource and assert that 95th‑percentile scroll latency stays under 16 ms.

Background sync conflicts

Suppose the user edits a contact offline while a background sync pushes a conflicting update from the server. The app must surface a conflict resolution dialog rather than silently overwriting one version. Simulate this by disabling network, editing a contact, re‑enabling network, and injecting a mock server update with a newer timestamp. Verify the UI presents a choice and that the selected version persists.

Each of these scenarios is unlikely to surface in a clean test lab but can cause data loss, crashes, or privacy violations in the wild.

Accessibility and WCAG Considerations

Screen‑reader labels

Every list item must have an accessible name that conveys the essential info. For a contact, a good label is "{firstName} {lastName}, {phoneType}: {phoneNumber}". Avoid relying solely on visual cues; test with TalkBack (Android) and VoiceOver (iOS) to confirm that double‑tapping an item opens the detail view.

Touch target size

Minimum recommended size is 48 dp × 48 dp (≈9 mm). Use the Android ViewTreeObserver.OnGlobalLayoutListener or iOS UIView frame measurements to assert that each row’s hit‑test area meets this threshold. A common mistake is making the avatar image the only tappable part; ensure the entire row is clickable.

Color contrast

Text and icons must contrast at least 4.5:1 against the background (WCAG AA). Run automated contrast checks (e.g., axe-core for web, Accessibility Scanner for Android) on various themes, including dark mode.

Keyboard navigation

For web or desktop‑style contact lists, ensure that Tab moves focus between rows, Enter activates the selected row, and arrow keys allow scrolling without a mouse. Verify that focus rings are visible and that custom JavaScript does not trap focus.

Reduced motion

Users who prefer reduced motion may experience discomfort with animated list reorders. Respect the prefers-reduced-motion media query or Android’s isAccessibilityFeatureEnabled(AccessibilityManager.FEATURE_DYNAMIC_ANIMATIONS) and disable or simplify animations accordingly.

By integrating these checks into your CI pipeline (e.g., running axe on web builds, using AccessibilityTestFragment for Android), you catch regressions before they reach users.

Security and Privacy Testing

Permission handling

Confirm that the app requests READ_CONTACTS and WRITE_CONTACTS only when needed, and that it explains why (runtime rationale). On iOS, verify that the usage description in Info.plist is clear and that the system prompt appears exactly once per session unless the user changes Settings.

Data leakage

Ensure that contacts are never logged inadvertently. Search logcat or console output for strings containing phone numbers or email addresses after performing a contact‑list operation. Use tools like Logcat filters (grep -i "contact") to spot accidental leaks.

Sharing via intents (Android)

When the user taps “Share contact,” the app creates an Intent with ACTION_SEND and a vCard MIME type. Verify that the intent extras contain only the fields the user opted to share (e.g., name and phone numbers) and that no extra metadata like _id or rawContactId leaks. You can capture the intent with adb shell am start -S and inspect the extras.

Encryption at rest

If the app caches contacts locally (e.g., in a Room database or SharedPreferences), confirm that the data is encrypted using a strong algorithm (AES‑256‑GCM) and that the key is stored in the Android Keystore or iOS Keychain. Attempt to pull the data via adb backup or a jailbroken filesystem and verify that it is unintelligible without the key.

API security

For endpoints that return contact data, enforce OAuth2 scopes and rate limiting. Test that an unauthorized request receives 401 or 403, and that a token with insufficient scope cannot read the phoneNumbers field. Use tools like OWASP ZAP to perform active scans on the contact‑related endpoints.

Third‑party contact plugins

If you rely on a native plugin (e.g., react-native-contacts), review its source for over‑permission requests. Some plugins request both READ_CONTACTS and WRITE_CONTACTS even when the app only reads. Audit the plugin’s manifest and update to a fork that minimizes permissions.

Persona‑Driven Autonomous Exploration with SUSA

How SUSA simulates different user personas

SUSA’s autonomous agent does not rely on pre‑written scripts. Instead, it loads a behavior profile for each persona—curious, impatient, novice, adversarial, elderly, accessibility‑focused, power user, and more. Each profile defines tap frequency, scroll velocity, likelihood to use voice input, tolerance for error messages, and propensity to explore settings menus. When pointed at an app’s contact list (via APK or URL), SUSA spawns virtual users that exercise the UI in ways a manual tester might not think to try.

For example, the impatient persona rapidly taps the search bar after every character, testing debounce logic and ensuring that the UI does not crash when the query changes faster than the data source can respond. The accessibility persona enables TalkBack and navigates solely via swipe gestures, exposing missing content descriptions or focus‑order problems. The adversarial persona attempts to input SQL‑like strings (' OR 1=1 --) into name fields to probe for injection vulnerabilities in local storage or sync APIs.

Finding bugs scripts miss

Because SUSA explores state space stochastically, it can reach combinations that are rare in scripted suites. In one internal run on a messaging app, the elderlycuriously opens every context menu and then backs out) triggered a hidden bug where long‑pressing a contact’s avatar opened a modal that, upon dismissal, left the underlying RecyclerView in an inconsistent state, causing a IndexOutOfBoundsException on the next scroll. No existing automated test covered the long‑press‑avatar flow followed by a back navigation, so the bug escaped detection until SUSA reported it.

The agent also logs each action with timestamps, enabling you to replay a failing session step‑by‑step in a debugger or to add the exact sequence to your regression suite.

Example of a persona‑driven flow

Consider the novice persona, which prefers big buttons and avoids gestures like swipe‑to‑delete. SUSA will:

  1. Launch the app and wait for the main screen.
  2. Tap the “Contacts” tab (identified by accessibility label).
  3. Scroll slowly (200 ms per item) until it sees a contact with a phone icon.
  4. Tap the contact’s name to open the detail view.
  5. Locate the “Edit” button (large, bottom‑aligned) and tap it.
  6. Change the first name field to a string of 150 ‘A’ characters.
  7. Tap the “Save” button (wide, high‑contrast).
  8. Return to the list and verify that the contact appears with the truncated name (if the app imposes a limit) or that the full name is shown (if the limit is higher).

If the app crashes at step 6 due to a buffer overflow in the native contact‑store bridge, SUSA captures the stack trace and flags the issue as high severity.

By running SUSA nightly on a device farm, you gain continuous feedback on how real‑world users—beyond the idealized tester—interact with your contact list.

Cross‑Session Learning and Regression Script Generation

How SUSA builds knowledge over runs

Susa stores a graph of visited screens and transitions after each execution. Nodes represent UI states (e.g., “Contact list – showing 23 items”), edges represent actions (tap, swipe, type). When a run encounters a dead end (e.g., a button that leads to a toast with no further navigation), the agent marks that edge as low‑value and avoids repeating it in future sessions unless the app changes. Conversely, paths that consistently lead to new states or discover anomalies are reinforced. Over time, the agent focuses its exploration on under‑tested areas, increasing the likelihood of finding regressions introduced by recent code changes.

Generating Appium (Android) + Playwright (Web) scripts

After a successful run, Susa can export the traversed paths as executable test scripts. For Android, it produces an Appium Java test that uses the same element locators (by accessibility id, text, or resource‑id) that the agent observed. For web, it outputs a Playwright TypeScript script that replicates the exact sequence of clicks, fills, and navigations.

Example snippet of an generated Appium test (truncated for brevity):


@Test
public void generatedContactListFlow() throws Exception {
    // Launch app
    driver.launchApp();

    // Tap Contacts tab (by accessibility label)
    new WebDriverWait(driver, 10)
        .until(ExpectedConditions.elementToBeClickable(
            MobileBy.AccessibilityId("Contacts")))
        .click();

    // Scroll to find contact "John Doe"
    UiScrollable scrollable = new UiScrollable(new UiSelector()
        .scrollable(true));
    scrollable.scrollIntoView(
        new UiSelector().textStartsWith("John Doe"));

    // Tap contact
    driver.findElementByAndroidUiAutomator(
        "new UiSelector().text(\"John Doe\")")
        .click();

    // Tap Edit button (resource id)
    driver.findElementById("btn_edit_contact")
        .click();

    // Edit first name field
    MobileElement firstName = driver.findElementById("input_first_name");
    firstName.clear();
    firstName.sendKeys("Jonathan");

    // Tap Save
    driver.findElementById("btn_save_contact")
        .click();

    // Verify name appears in list
    driver.findElementByAndroidUiAutomator(
        "new UiSelector().textContains(\"Jonathan\")");
}

For web, the generated Playwright script mirrors the same flow using CSS selectors or ARIA labels.

Using generated scripts in CI

Commit the exported scripts to your repository under a folder like tests/susa-generated/. Add a step in your CI pipeline that runs these scripts on every pull request against a staging build. Because the scripts are derived from actual exploratory behavior, they act as a living regression suite that evolves with the app. If a subsequent change breaks a path that Susa previously explored, the corresponding generated test will fail, alerting you before the defect reaches production.

You can also configure Susa to prune scripts that become obsolete (e.g., when a screen is removed) and to regenerate them on a schedule, ensuring the suite stays in sync with the UI.

Checklist for Contact List Testing

Pre‑release checklist

Production‑only monitoring checklist

-

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