How to Test Contact List on Web (Complete Guide)

Contact lists are a core feature in many web applications—CRMs, messaging platforms, e‑commerce sites, and internal tools. When a user adds, edits, searches, or deletes a contact, they expect the oper

By · February 21, 2026 · 18 min read · How-To Guides

Why Testing a Contact List Matters

Contact lists are a core feature in many web applications—CRMs, messaging platforms, e‑commerce sites, and internal tools. When a user adds, edits, searches, or deletes a contact, they expect the operation to be instantaneous, accurate, and safe. Failures in this area manifest as:

Because the contact list touches data persistence, UI rendering, state management, and often third‑party integrations (e.g., address‑book APIs), a defect here can cascade into downstream workflows such as messaging, billing, or reporting. A systematic test strategy catches these issues before they reach production, reduces support overhead, and preserves user trust.

Comprehensive Test Matrix

Below is a matrix that groups test ideas by category, sub‑category, and typical verification points. Use it as a checklist when designing manual or automated suites. Each row represents a distinct test scenario; the “Expected Result” column describes the observable outcome that indicates a pass.

CategorySub‑categoryTest IDDescriptionExpected Result
Happy PathAdd contactHP‑01Fill all required fields (first name, last name, phone, email) and submit.New contact appears at the top/bottom of the list with correct values.
Edit contactHP‑02Open an existing contact, change the phone number, save.Updated phone number reflects in the list; other fields unchanged.
Delete contactHP‑03Select a contact and confirm deletion.Contact removed from list; no trace in UI or storage.
SearchHP‑04Type a substring that matches one contact’s name.Only matching contacts displayed; others hidden.
Filter by groupHP‑05Apply a filter for a custom group (e.g., “Work”).List shows only contacts belonging to that group.
Bulk selectHP‑06Use checkbox‑select‑all, then delete selected contacts.All selected contacts removed; unselected remain.
Error PathsRequired field missingEP‑01Submit add form with first name left blank.Inline validation error highlights the empty field; form does not submit.
Invalid email formatEP‑02Enter “user@” in email field.Error message indicates invalid email; submission blocked.
Phone number too shortEP‑03Enter “123” in phone field.Validation prevents save; tooltip shows required length.
Duplicate detectionEP‑04Attempt to add a contact with same email as existing.System warns of duplicate and blocks creation (or offers merge).
Network failure on saveEP‑05Simulate offline state while submitting.UI shows offline banner; data queued and syncs when connection restored.
Server error (500)EP‑06Mock API to return 500 on add request.Error toast displayed; no partial contact appears in list.
Edge CasesVery long stringsEC‑01Input 200‑character name, 300‑character phone.UI truncates or wraps gracefully; no overflow or layout break.
Special charactersEC‑02Name contains emojis, Unicode accents, or HTML tags (<script>).Characters rendered correctly; no XSS execution.
Leading/trailing spacesEC‑03Enter “ John Doe ” in name fields.System trims spaces on save; displayed name has no extra spaces.
Zero contacts stateEC‑04Fresh user with no contacts saved.Empty state illustration or message shown; no JavaScript errors.
Pagination / infinite scrollEC‑05Load more than 200 contacts; scroll to bottom repeatedly.New batches load without duplication; scroll position stable.
Keyboard navigationEC‑06Tab through add form, use Enter to submit, Escape to cancel.Focus moves logically; form submits on Enter, cancels on Escape.
Touch gestures (mobile viewport)EC‑07Swipe left on a contact to reveal delete button (if implemented).Delete action triggered; swipe right restores original view.
AccessibilityScreen reader labelsA‑01Navigate list with NVDA or VoiceOver.Each list item announces name, phone, email, and available actions.
Color contrastA‑02Verify contrast ratio of text vs. background meets WCAG AA (4.5:1).All text passes contrast test.
Focus orderA‑03Tab through controls; ensure logical order.Focus never jumps unexpectedly; modal traps focus when open.
ARIA live regionsA‑04After adding a contact, listen for announcement.Live region announces “Contact added”.
Touch target sizeA‑05Measure tap areas on buttons (minimum 44×44 dp).All actionable elements meet size guideline.
Security / PrivacyData exposure in networkSP‑01Inspect XHR/fetch payloads when listing contacts.No unnecessary fields (e.g., internal IDs, passwords) sent.
Authorization bypassSP‑02Attempt to access /api/contacts/42 without valid token.Server returns 401/403; UI shows error or redirects.
XSS via contact fieldsSP‑03Insert <img src=x onerror=alert(1)> into name field.Script does not execute; content escaped or sanitized.
CSRF on deleteSP‑04Perform a cross‑site delete request lacking CSRF token.Request rejected; contact remains.
GDPR right to be forgottenSP‑05Delete a contact; verify removal from backups/logs (if applicable).Contact not retrievable via any API after deletion.
PerformanceInitial load timePF‑01Measure time to render list with 50 contacts on 3G throttled.First meaningful paint < 2 seconds.
Scroll jankPF‑02Record FPS while scrolling rapidly through 500 contacts.Average FPS ≥ 55; no dropped frames causing visible stutter.
Memory leakPF‑03Repeatedly add/delete 100 contacts over 5 minutes.Memory growth stays within acceptable bounds (< 10 MB increase).
Cache utilizationPF‑04Reload page after adding a contact; check if request to server avoided.Subsequent load uses cached data or optimized delta sync.

How to Use the Matrix

Manual Testing Approach – Step‑by‑Step

Even when automation covers the bulk of regression, a disciplined manual session uncovers usability quirks, visual regressions, and context‑specific issues that scripts may miss. Follow this procedure for a thorough exploratory pass.

  1. Environment preparation
  1. Login / state setup
  1. Happy‑path walkthrough
  1. Error‑path injection
  1. Edge‑case probing
  1. Search & filter validation
  1. Bulk actions
  1. Accessibility audit (manual)
  1. Security sniffing
  1. Performance check
  1. Cleanup

Document each step’s outcome in a test‑run spreadsheet, attaching screenshots or video clips for failures. This manual baseline becomes the oracle for your automated tests.

Automated Testing Strategies for Web Contact Lists

Automation excels at repeatable validation of functional correctness, regression detection, and CI gating. Below are the layers you should implement, with concrete examples using popular frameworks.

Unit / Service Layer

If your front‑end consumes a contacts API via a service module (e.g., contactsService.js), unit test that module in isolation.


// contactsService.test.js
import { addContact, updateContact, deleteContact } from './contactsService';
import { mockApi } from './testUtils'; // a library like msw or jest‑mock‑axios

describe('contactsService', () => {
  beforeEach(() => mockApi.reset());

  test('addContact sends correct payload', async () => {
    mockApi.onPost('/api/contacts').reply(201, { id: 42, ...payload });
    const result = await addContact({ firstName: 'Ada', lastName: 'Lovelace', email: 'ada@example.com' });
    expect(result.id).toBe(42);
    const [request] = mockApi.history.post;
    expect(request.data).toMatchObject({
      firstName: 'Ada',
      lastName: 'Lovelace',
      email: 'ada@example.com',
    });
  });

  test('updateContact handles 400 validation error', async () => {
    mockApi.onPatch('/api/contacts/42').reply(400, { error: 'Invalid email' });
    await expect(updateContact(42, { email: 'bad' })).rejects.toMatchObject({ error: 'Invalid email' });
  });
});

*Use Jest or Vitest for the test runner; MSW (Mock Service Worker) to intercept network calls.*

Component / UI Tests

Render the contact‑list component in isolation with React Testing Library, Vue Test Utils, or Svelte Testing Library. Focus on interactions and DOM assertions.


// ContactList.test.jsx
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import ContactList from './ContactList';
import { mockApi } from './testUtils';

beforeEach(() => mockApi.reset());

test('displays added contact after submit', async () => {
  render(<ContactList />);

  // open add form
  await fireEvent.click(screen.getByRole('button', { name: /add contact/i }));

  // fill form
  await fireEvent.change(screen.getByLabelText(/first name/i), { target: { value: 'Ada' } });
  await fireEvent.change(screen.getByLabelText(/last name/i), { target: { value: 'Lovelace' } });
  await fireEvent.change(screen.getByLabelText(/email/i), { target: { value: 'ada@example.com' } });
  await fireEvent.change(screen.getByLabelText(/phone/i), { target: { value: '+1-555-0123' } });

  // submit
  await fireEvent.click(screen.getByRole('button', { name: /save/i }));

  // wait for list update
  await waitFor(() => {
    expect(screen.getByText('Ada Lovelace')).toBeInTheDocument();
    expect(screen.getByText('ada@example.com')).toBeInTheDocument();
    expect(screen.getByText('+1-555-0123')).toBeInTheDocument();
  });
});

*Key points*: use role‑based queries (getByRole, getByLabelText) to mirror how assistive tech perceives the UI; avoid brittle selectors like CSS classes unless they are part of a design system contract.

End‑to‑End (E2E) Tests

E2E validates the full stack—routing, state management, API contracts, and third‑party integrations. Playwright and Cypress are the most common choices for modern web apps.

#### Playwright Example (TypeScript)


// tests/contact-list.spec.ts
import { test, expect } from '@playwright/test';

test.describe('Contact List', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('/contacts');
    // ensure clean state via API call (if your app supports it)
    await page.route('**/api/contacts**', route => {
      route.fulfill({ status: 200, json: { contacts: [] } });
    });
    await page.reload();
  });

  test('adds a new contact successfully', async ({ page }) => {
    await page.click('button:has-text("Add Contact")');
    await page.fill('input[label="First Name"]', 'Ada');
    await page.fill('input[label="Last Name"]', 'Lovelace');
    await page.fill('input[label="Email"]', 'ada@example.com');
    await page.fill('input[label="Phone"]', '+1-555-0123');
    await page.click('button:has-text("Save")');

    // verify toast
    await expect(page.locator('text=Contact added')).toBeVisible({ timeout: 5000 });

    // verify list entry
    const row = page.locator('tr:has-text("Ada Lovelace")');
    await expect(row).toBeVisible();
    await expect(row.locator('td:nth-child(2)')).toHaveText('ada@example.com');
    await expect(row.locator('td:nth-child(3)')).toHaveText('+1-555-0123');
  });

  test('blocks submission with invalid email', async ({ page }) => {
    await page.click('button:has-text("Add Contact")');
    await page.fill('input[label="Email"]', 'notanemail');
    await page.click('button:has-text("Save")');
    await expect(page.locator('text=Please enter a valid email')).toBeVisible();
    await expect(page.getByRole('button', { name: /save/i })).toBeDisabled();
  });

  test('deletes a contact and confirms removal', async ({ page }) => {
    // seed a contact via API
    await page.route('**/api/contacts**', route => {
      route.fulfill({
        status: 200,
        json: { contacts: [{ id: 1, firstName: 'Ada', lastName: 'Lovelace', email: 'ada@example.com', phone: '+1-555-0123' }] },
      });
    });
    await page.reload();

    await page.click('tr:has-text("Ada Lovelace") button[aria-label="Delete"]');
    await page.click('button:has-text("Confirm")');

    await expect(page.locator('tr:has-text("Ada Lovelace")')).not.toBeInViewport();
    await expect(page.locator('text=No contacts found')).toBeVisible();
  });
});

#### Cypress Equivalent (JavaScript)


// cypress/e2e/contact_list.cy.js
describe('Contact List', () => {
  beforeEach(() => {
    cy.visit('/contacts');
    cy.intercept('GET', '**/api/contacts', { contacts: [] }).as('getContacts');
    cy.wait('@getContacts');
  });

  it('adds a contact', () => {
    cy.contains('button', 'Add Contact').click();
    cy.get('input[placeholder="First Name"]').type('Ada');
    cy.get('input[placeholder="Last Name"]').type('Lovelace');
    cy.get('input[placeholder="Email"]').type('ada@example.com');
    cy.get('input[placeholder="Phone"]').type('+1-555-0123');
    cy.contains('button', 'Save').click();

    cy.contains('Contact added').should('be.visible');
    cy.contains('tr', 'Ada Lovelace')
      .should('exist')
      .find('td')
      .eq(1).should('have.text', 'ada@example.com')
      .end()
      .eq(2).should('have.text', '+1-555-0123');
  });

  it('shows error for invalid email', () => {
    cy.contains('button', 'Add Contact').click();
    cy.get('input[placeholder="Email"]').type('bad@@');
    cy.contains('button', 'Save').click();
    cy.contains('Please enter a valid email').should('be.visible');
    cy.contains('button', 'Save').should('be.disabled');
  });

  it('deletes a contact', () => {
    cy.intercept('GET', '**/api/contacts', {
      contacts: [{ id: 9, firstName: 'Ada', lastName: 'Lovelace', email: 'ada@example.com', phone: '+1-555-0123' }],
    }).as('seed');
    cy.visit('/contacts');
    cy.wait('@seed');

    cy.contains('tr', 'Ada Lovelace').find('button[aria-label="Delete"]').click();
    cy.contains('button', 'Confirm').click();
    cy.contains('tr', 'Ada Lovelace').should('not.exist');
    cy.contains('No contacts found').should('be.visible');
  });
});

Tips for stable E2E

Tooling Landscape for Web Contact‑List Testing

Choosing the right stack influences maintenance effort, test fidelity, and developer experience. Below is a comparison of widely‑adopted tools across the three testing layers.

LayerToolLanguage / FrameworkStrengthsWeaknesses / Gotchas
Unit / ServiceJestJavaScript/TypeScriptFast, built‑in mocking, snapshot supportRequires additional setup for ES modules; mocking fetch can be verbose
Unit / ServiceVitestJavaScript/TypeScriptNative ESM support, Jest‑compatible API, lightning fastSmaller ecosystem, fewer plugins
ComponentReact Testing Library (RTL)ReactEncourages accessible queries, minimal DOM relianceNot suited for testing lifecycle hooks that depend on external stores
ComponentVue Test UtilsVue 2/3Full Vue instance access, easy mountingAPI differs between Vue 2 and 3; need version‑specific docs
ComponentSvelte Testing LibrarySvelteSimple rendering, integrates with RTL queriesLimited to Svelte; community smaller
E2EPlaywrightTypeScript/JavaScript/Python/.NET/JavaAuto‑wait, built‑in tracing, multi‑browser, API mocking, no flaky waitsHeavier binary download (~100 MB)
E2ECypressJavaScript/TypeScriptTime‑travel debugging, rich UI, extensive pluginsRuns only in Chromium/Firefox/WebKit via bundled browsers; limited cross‑origin navigation
E2ESelenium WebDriverJava/JavaScript/Python/C#/RubyIndustry standard, works with any browser via driversVerbose API, requires explicit waits, slower execution
Accessibilityaxe-coreJS/TS (integrates with Jest, Playwright, Cypress)Comprehensive WCAG checks, easy CI integrationOnly static analysis; does not replace manual screen‑reader testing
PerformanceLighthouse CINodeAudits performance, accessibility, SEO, best practicesLab data only; not a substitute for real‑user monitoring
Visual RegressionPercy / ChromaticSDKs for Storybook integrationDetects pixel‑level changes, handles dynamic contentRequires baseline management; false positives on anti‑aliasing differences
API MockingMSW (Mock Service Worker)Node / BrowserIntercepts requests at network level, works both in tests and devNeeds careful cleanup to avoid leaking mocks across test files

When to pick what

Edge Cases That Surface Only in Production

Even the most exhaustive test matrix can miss issues that arise under real‑world traffic, data variance, or infrastructure quirks. Below are categories of production‑only bugs and concrete ways to surface them during testing.

1. Data‑Driven UI Glitches

*Problem*: A contact’s name contains a rare Unicode character (e.g., 𝔘) that causes a font‑fallback bug, leading to missing glyphs or layout shift.

*Detection*:

2. Race Conditions in Optimistic UI

*Problem*: The UI optimistically shows a newly added contact, but the backend rejects it due to a server‑side validation (e.g., duplicate email). The UI then needs to roll back, but the rollback fails leaving a phantom entry.

*Detection*:

3. Network Partition & Offline Sync

*Problem*: The app queues edits while offline, but upon reconnection it sends stale payloads, overwriting newer changes made on another device.

*Detection*:

4. Third‑Party Address‑Book API Rate Limiting

*Problem*: When importing contacts from Google or Outlook, the app exceeds the provider’s rate limit, resulting in HTTP 429 responses that are not handled, causing the import to stall silently.

*Detection*:

5. Memory Leak in Virtual Scrolling

*Problem*: A virtual‑scroll list fails to unmount detached rows, causing DOM growth and eventual browser slowdown after hundreds of add/delete cycles.

*Detection*:

6. CSP Violations from User‑Generated Content

*Problem*: A contact’s note field accepts raw HTML; a malicious user injects a script that violates the Content Security Policy, which is blocked but not reported to the user, leaving them confused why the note disappears.

*Detection*:

7. Localization Layout Breakage

*Problem*: When the UI language switches to Arabic (right‑to‑left), the contact‑list columns misalign, causing action buttons to overlap with text.

*Detection*:

8. Session Expiration Mid‑Flow

*Problem*: A user begins editing a contact, the auth token expires silently, and the subsequent save request returns 401; the app redirects to login losing the unsaved edits.

*Detection*:

Mitigation Strategies

Autonomous, Persona‑Driven Exploration with SUSA

Traditional scripted tests verify known paths; they rarely stumble upon the surprising interactions that real users exhibit. SUSA (Susatest) introduces autonomous exploration driven by configurable user personas, each with distinct behavior patterns, goals, and tolerances for friction.

How It Works

  1. Model Building – SUSA crawls the application, constructing a state graph of screens, UI elements, and possible actions (taps, keystrokes, scrolls).
  2. Persona Profiling – You select or define personas (e.g., “Impatient Power User”, “Elderly Novice”, “Adversarial Tester”). Each persona has a probability distribution over actions: speed of interaction, likelihood to use keyboard shortcuts, propensity to ignore validation messages, etc.
  3. Guided Exploration – The engine walks the state graph, making decisions according to the chosen persona’s policy. It automatically handles dialogs, fills forms with generated data (respecting constraints), and can inject network faults or accessibility‑mode toggles on the fly.
  4. Issue Detection – While exploring, SUSA monitors for: JavaScript errors, uncaught promises, ANR‑equivalent long tasks, accessibility violations (via integrated axe checks), security red flags (e.g., reflected input in responses), and UX friction signals (rage clicks, repeated back‑button presses).
  5. Regression Script Generation – After a run, SUSA exports the traversed flows as executable test scripts: Appium for Android WebView equivalents, Playwright for pure web, or Cypress for those who prefer its syntax. These scripts capture the exact sequences the persona exercised, providing a reproducible baseline for CI.

Practical Example: Testing the Contact‑List with an “Adversarial” Persona

Suppose you want to see how the app behaves when a user deliberately tries to break it (e.g., pasting huge strings, rapid double‑clicks, using keyboard shortcuts in unexpected orders).


# Install the SUSA agent (Node‑based CLI)
npm i -g susatest-agent

# Run an exploratory session targeting the contact list page
susatest-agent test https://app.example.com/contacts \
  --persona adversarial \
  --output ./susareport

*What happens under the hood*

When the run finishes, you receive:

Why This Finds Bugs Scripts Miss

Test Your App Autonomously

Upload your APK or URL. SUSA explores like 11 real users — finds bugs, accessibility violations, and security issues. No scripts. New to the category? Start with what autonomous product intelligence & QA means.

Try SUSA Free