How to Test Favorites on Web (Complete Guide)

Favorites (sometimes called bookmarks, stars, hearts, or saved items) are a core interaction pattern in modern web applications. Users rely on them to quickly return to content they care about, to bui

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

Why Testing Favorites Matters

Favorites (sometimes called bookmarks, stars, hearts, or saved items) are a core interaction pattern in modern web applications. Users rely on them to quickly return to content they care about, to build personal collections, or to signal intent for later conversion. When the favorite mechanism fails, the impact is immediate and measurable:

From a technical standpoint, favorites touch several layers of the stack: UI event handling, client‑side storage, API contracts, and sometimes backend synchronization. A defect in any layer can surface as a silent data loss, a misleading UI state, or a security exposure. Because the feature is often implemented with a mix of frameworks, libraries, and custom code, regression risk is high. Testing favorites therefore provides a low‑cost, high‑return safety net that protects both user experience and business metrics.

Core Concepts: What Is a Favorite Feature?

Understanding the internal mechanics of favorites helps you design tests that hit the right seams.

Data Model Basics

At its simplest, a favorite is a tuple (userId, itemId, timestamp) stored somewhere. The timestamp can be used for sorting recency or for conflict resolution when syncing across devices. Some applications enrich the record with metadata such as a folder/category, a note, or a priority flag.

UI Patterns

Common visual affordances include:

PatternTypical IconInteractionTypical Use
Heart❤️ / ♡Tap/click to toggle filled/outlinedSocial media, media streaming
Star★ / ☆Tap/click to toggle filled/outlinedProduct listings, email flagging
Bookmark🔖 / 📌Tap/click to add/removeNews, documentation sites
Add to List“+” or “Add” buttonOpens a modal to select a listWishlist, playlist, reading list

The UI may also show a badge count, a tooltip, or an animation to reinforce state change.

Persistence Mechanisms

MechanismScopeLifetimeSync AbilityTypical Use
localStorageOrigin‑specificUntil cleared manuallyNo (unless custom sync)Simple SPA favorites
sessionStorageOrigin‑specificTab sessionNoTemporary UI state
IndexedDBOrigin‑specificPersistent, larger storageNo (custom sync)Complex objects, offline‑first
CookieOrigin‑specificExpires or sessionSent with each request (can be synced)Lightweight server‑side sync
Backend DB (e.g., PostgreSQL, Mongo)Server‑sidePersistentYes (real‑time)Multi‑device, collaborative apps
Service Worker CacheOrigin‑specificUntil clearedNo (but can be used for offline fallback)Progressive web apps

Choosing a persistence method influences test coverage: you must verify that data survives page reloads, browser restarts, and, when applicable, device changes.

Sync and Offline Considerations

If the application supports cross‑device sync, the favorite toggle typically fires an optimistic UI update, then sends a PATCH/POST to an endpoint like /api/favorites. The server responds with the updated state, which the client reconciles. Offline‑first apps may queue the request in IndexedDB and replay it when connectivity returns. Tests must therefore cover:

Test Matrix for Favorites

A structured matrix ensures you hit happy paths, error paths, edge cases, accessibility, and security angles. Below is a consolidated table you can copy into a test‑management tool or a spreadsheet.

Test IDCategoryDescriptionPreconditionsStepsExpected ResultPriority
FV‑001Happy PathAdd a favorite via heart iconUser logged in, item displayed1. Click heart icon (outlined) 2. Verify icon fills 3. Reload pageIcon remains filled, favorite persisted in storage/backendP1
FV‑002Happy PathRemove a favoriteUser logged in, item already favorited1. Click heart icon (filled) 2. Verify icon outlines 3. Reload pageIcon remains outlined, favorite removedP1
FV‑003Happy PathBulk select and favoriteList view with checkboxes1. Select multiple items 2. Click “Favorite selected” button 3. Verify badge counts updateAll selected items show filled heart, backend receives batch createP2
FV‑004Error PathNetwork loss during toggleUser online, mock network throttling to offline1. Toggle favorite 2. Observe UI state 3. Restore networkUI shows optimistic state, request queued, on restore UI updates to server stateP1
FV‑005Error PathServer returns 500 on toggleUser online, intercept endpoint with 5001. Toggle favorite 2. Verify error handlingUI reverts to previous state, error toast shown, retry option availableP1
FV‑006Edge CaseDuplicate favorite submissionUser already favorited item, rapid double‑click1. Double‑click heart quickly 2. Verify only one create request sentNo duplicate entry in backend, UI stays filledP2
FV‑007Edge CaseStorage quota exceededlocalStorage near limit (e.g., 4.9MB of 5MB)1. Add many favorites until quota hit 2. Attempt another addAdd fails gracefully, user notified, no data corruptionP2
FV‑008Edge CasePrivate/incognito modeBrowser in incognito, no extensions1. Favorite an item 2. Close incognito window 3. Reopen incognitoFavorite not persisted (expected for sessionStorage) or persisted per mechanismP2
FV‑009AccessibilityKeyboard navigationPage loaded, focusable elements1. Tab to heart icon 2. Press Enter/Space 3. Verify activationIcon toggles, ARIA state updates, screen reader announces changeP1
FV‑010AccessibilityScreen reader labelPage loaded with screen reader (NVDA/JAWS)1. Navigate to favorite button 2. Listen to announcementButton announces “Add to favorites” or “Remove from favorites” based on stateP1
FV‑011SecurityCSRF protectionAuthenticated user, CSRF token required1. Attempt to toggle favorite via forged request missing tokenRequest rejected (403), UI does not changeP1
FV‑012PrivacyGDPR right to be forgottenUser requests data deletion1. Trigger delete‑account flow 2. Verify favorites table clearedNo favorite records remain for that user in backend or client storageP1
FV‑013PerformanceLarge list renderingUser has 10 000 favorites1. Open favorites page 2. Measure time to first paintPage loads within 2 s, virtual scrolling used if neededP2
FV‑014LocalizationRTL languageUI set to Arabic (rtl)1. Observe favorite icon placementIcon mirrors correctly, padding respects directionP2
FV‑015ThemeDark mode toggleSystem prefers dark, site supports theme switch1. Toggle to dark 2. Verify icon contrast meets WCAG AAIcons retain sufficient contrast, no color‑blind confusionP2

*Priorities*: P1 = blocker for release, P2 = important but can defer if time‑boxed.

Comparison of Manual vs. Automated Tooling

AspectManual TestingAutomated (Code‑Based)Autonomous Persona‑Driven (SUSA)
Setup timeLow (just a browser)Medium (test framework, selectors)Low‑medium (install agent, point at URL)
Coverage depthHigh for exploratory, low for repeatabilityHigh for repeatable scripts, low for unforeseen flowsMedium‑high: explores many paths, learns over runs
MaintenanceNone (ad‑hoc)High (selector updates, flaky test fixes)Low (agent adapts to UI changes)
Speed per runMinutes to hours (depends on tester)Seconds to minutes (parallelizable)Minutes (depends on exploration budget)
Ability to catch UX frictionExcellent (human perception)Limited (needs explicit assertions)Good (personas simulate varied behavior)
Ideal use caseEarly‑stage, ad‑hoc bug huntsRegression suites, CI gatingContinuous learning, pre‑release exploratory, regression seed generation

Manual Testing Approach

Even when automation is in place, a disciplined manual session catches nuances that scripts miss. Follow this step‑by‑step routine for each release candidate.

4.1 Setup and Environment

  1. Browser matrix – Test the latest stable versions of Chrome, Firefox, Safari, and Edge on both Windows and macOS. Include a mobile viewport via device emulation (iOS Safari, Android Chrome).
  2. User accounts – Prepare at least three test accounts: a fresh account with no favorites, an account with a moderate set (≈50 items), and a power‑user account with >5 000 favorites.
  3. Network conditioning – Use Chrome DevTools throttling (Slow 3G, offline) or a tool like tc to simulate latency and packet loss.
  4. Storage inspection – Keep the Application tab open to monitor localStorage/IndexedDB changes in real time.
  5. Logging – Enable verbose network logs and console errors; capture them with a screen recorder or a tool like BrowserStack Automate for later review.

4.2 Step‑by‑Step Test Execution

  1. Login – Verify that the authentication flow does not interfere with favorite endpoints (e.g., no stray OPTIONS calls).
  2. Navigate to a content list – Choose a page where favorites are exposed (product grid, article feed, video carousel).
  3. Baseline check – Confirm that the UI state of each itemId‑filled” or “Id‑outlined”.
  4. Happy‑path toggle – Perform a single click on a few items, observe immediate UI change, then reload the page to verify persistence.
  5. Bulk operations – If the UI offers “Select all” or “Select multiple”, test adding and removing favorites in batches. Verify that backend receives a single batch request (or a series, depending on design) and that UI updates atomically.
  6. Error injection – Using DevTools, toggle the network to offline, attempt a favorite toggle, then restore connectivity. Ensure the UI does not get stuck in an intermediate state and that a retry mechanism (if present) triggers.
  7. Accessibility spot‑check – Navigate using only the keyboard (Tab, Shift+Tab, Enter/Space). Verify focus order, visible focus ring, and that screen readers announce state changes (use NVDA or VoiceOver).
  8. Theme and locale switch – Change the OS or browser language to a right‑to‑left language (e.g., Arabic) and toggle dark mode. Confirm that the favorite icon mirrors correctly and contrast ratios stay ≥4.5:1.
  9. Clean‑up – Log out, clear site data, and repeat the steps to guarantee no cross‑account leakage.

4.3 Exploratory Testing Tips

4.4 Logging and Bug Reporting

When a defect is found, capture:

Upload this package to your bug tracker with the test‑matrix ID (e.g., FV‑004) to enable quick triage.

Automated Testing Strategies

Automation gives you confidence that regressions are caught early and that the favorite flow works across configurations. Below are patterns you can adopt, ranging from unit tests to full‑blown persona‑driven exploration with SUSA.

5.1 Unit and Integration Tests for Favorite Logic

Isolate the pure JavaScript module that handles toggle state, storage writes, and API calls. Example using Jest:


// favoriteService.js
export class FavoriteService {
  constructor(storageApi, apiClient) {
    this.storage = storageApi;
    this.api = apiClient;
  }

  async toggle(itemId) {
    const currentlyFav = await this.storage.get(itemId);
    if (currentlyFav) {
      await this.storage.remove(itemId);
      await this.api.delete(`/favorites/${itemId}`);
    } else {
      await this.storage.set(itemId, { added: Date.now() });
      await this.api.post('/favorites', { itemId });
    }
    return !currentlyFav;
  }
}

// favoriteService.test.js
import { FavoriteService } from './favoriteService';
import { mockStorage, mockApi } from './mocks';

test('toggle adds favorite when not present', async () => {
  const storage = mockStorage(); // implements get/set/remove
  const api = mockApi();         // implements post/delete
  const fav = new FavoriteService(storage, api);
  storage.get.mockResolvedValue(null); // not favorited yet

  await fav.toggle('item-123');

  expect(storage.set).toHaveBeenCalledWith('item-123', expect.objectContaining({ added: expect.any(Number) }));
  expect(api.post).toHaveBeenCalledWith('/favorites', { itemId: 'item-123' });
});

*Why this matters*: Unit tests guarantee that the state machine logic is correct regardless of UI rendering bugs. They run in milliseconds and can be part of every commit.

5.2 End‑to‑End Tests with Playwright

Playwright offers cross‑browser, auto‑waiting, and traceability. Below is a robust test suite covering the matrix entries FV‑001, FV‑002, FV‑004, and FV‑009.


// tests/favorite.spec.js
const { test, expect } = require('@playwright/test');

test.describe('Favorite functionality', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('https://example-shop.com/products');
    await page.fill('#email', 'tester@example.com');
    await page.fill('#password', 'SecurePass!123');
    await page.click('#login-button');
    await page.waitForURL('**/products**');
  });

  test('add and remove favorite persists after reload', async ({ page }) => {
    const heart = page.locator('.product-card').first().locator('button[aria-label="Add to favorites"]');
    await expect(heart).toHaveAttribute('aria-pressed', 'false');
    await heart.click();
    await expect(heart).toHaveAttribute('aria-pressed', 'true');

    await page.reload();
    await expect(heart).toHaveAttribute('aria-pressed', 'true');

    await heart.click();
    await expect(heart).toHaveAttribute('aria-pressed', 'false');
    await page.reload();
    await expect(heart).toHaveAttribute('aria-pressed', 'false');
  });

  test('handles offline toggle gracefully', async ({ page }) => {
    await page.context().route('**/favorites/**', route => route.abort()); // simulate net loss
    const heart = page.locator('.product-card').first().locator('button[aria-label="Add to favorites"]');
    await heart.click();
    await expect(heart).toHaveAttribute('aria-pressed', 'true'); // optimistic UI

    await page.context().unroute('**/favorites/**'); // restore
    // Wait for retry (if implemented) or for a toast indicating failure
    const toast = page.locator('.toast');
    await expect(toast).toContainText('Failed to save favorite', { timeout: 5000 });
    // UI should revert
    await expect(heart).toHaveAttribute('aria-pressed', 'false');
  });

  test('keyboard accessibility', async ({ page }) => {
    const heart = page.locator('.product-card').first().locator('button[aria-label="Add to favorites"]');
    await heart.focus();
    await expect(heart).toBeFocused();
    await page.keyboard.press('Enter');
    await expect(heart).toHaveAttribute('aria-pressed', 'true');
    // Screen‑reader announcement can be asserted via aria-live region if present
    const live = page.locator('[aria-live="polite"]');
    await expect(live).toContainText('Added to favorites');
  });
});

*Key points*:

5.3 Visual Regression for UI State

Tools like Percy or Chromatic can snapshot the favorite button in both states. A simple configuration:


// .chromatic.json
{
  "storybook": false,
  "url": "https://example-shop.com/products",
  "viewport": [
    { "width": 1280, "height": 800 },
    { "width": 375,  "height": 667 }
  ],
  "selectors": [".product-card button[aria-label='Add to favorites']"]
}

Run Chromatic on each PR; any shift in icon color, size, or surrounding layout triggers a review.

5.4 Performance and Load Testing for Bulk Favorites

When a user imports a large collection (e.g., migrating from another service), the favorite endpoint may be hammered. Use k6 to simulate bursts:


// k6/favorite_load.js
import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  stages: [
    { duration: '2m', target: 50 },   // ramp up to 50 VUs
    { duration: '5m', target: 50 },   // sustain
    { duration: '2m', target: 0 },    // ramp down
  ],
};

const API_TOKEN = __ENV.API_TOKEN;
const ITEM_IDS = Array.from({ length: 1000 }, (_, i) => `item-${i + 10000}`);

export default function () {
  const id = ITEM_IDS[Math.floor(Math.random() * ITEM_IDS.length)];
  const payload = JSON.stringify({ itemId: id });
  const params = {
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${API_TOKEN}`,
    },
  };
  const res = http.post('https://api.example.com/favorites', payload, params);
  check(res, {
    'status is 200': (r) => r.status === 200,
    'response time < 500ms': (r) => r.timings.duration < 500,
  });
  sleep(0.5);
}

Run with k6 run favorite_load.js. Monitor backend latency and error rates; adjust throttling or batching as needed.

5.5 Using SUSA for Autonomous Persona‑Driven Exploration

SUSA can be pointed at a staging URL and let’s say a QA environment to surface favorite bugs that scripted tests never consider. Example CLI usage:


# Install the agent (once)
pip install susatest-agent

# Run a 30‑minute exploratory session with a mix of personas
susatest explore \
  --url https://staging.example-shop.com \
  --apk ./android-app.apk \   # optional if you also have a native companion
  --personas curious impatient elderly accessibility \
  --duration 30m \
  --output ./susa-report.json

The agent will:

Because SUSA builds a memory of explored screens and dead ends, subsequent runs become smarter, gradually covering edge cases like storage quota exhaustion or localization flips that manual testers might overlook.

5.6 CI Integration

Add the Playwright suite to your CI (GitHub Actions example):


name: Web Tests
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Install Node
        uses: actions/setup-node@v3
        with:
          node-version: '20'
      - run: npm ci
      - name: Install Playwright browsers
        run: npx playwright install --with-deps
      - run: npx playwright test

For SUSA, you can add a nightly workflow:


name: SUSA Exploration
on:
  schedule:
    - cron: '0 2 * * *'   # 02:00 UTC daily
jobs:
  explore:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Install SUSA agent
        run: pip install susatest-agent
      - run: susatest explore --url https://staging.example-shop.com --personas curious impatient --duration 15m --output ./susa-report.json
      - name: Upload report
        uses: actions/upload-artifact@v3
        with:
          name: susa-report
          path: susa-report.json

Edge Cases That Appear Only in Production

Even the most exhaustive test matrix can miss issues that only manifest under real‑world traffic, browser quirks, or infrastructure constraints. Below are production‑only failure modes you should monitor and, where possible, reproduce in staging.

6.1 Network Flakiness and Offline Behavior

Reproduction: Use a tool like toxiproxy to inject random latency and packet loss, or configure a local proxy to drop 1 % of response bytes.

6.2 Race Conditions with Concurrent Updates

If a user opens the same product in two tabs and toggles the favorite simultaneously, you can end up with:

Solution: Server‑side use of optimistic locking with a version column or a unique constraint on (userId, itemId). The client should send the current version or rely on the server to idempotently handle the request.

Test: Open two Chrome instances, log in as same user, navigate to same product, and rapidly click the heart in both windows. Verify final state matches expectation (only one favorite record).

6.3 Third‑Party Cookie/Storage Restrictions

Safari’s Intelligent Tracking Prevention (ITP) and Chrome’s upcoming “Privacy Sandbox” may purge localStorage after 7 days of no interaction, or partition storage per top‑level site. If your favorite data lives solely in localStorage, users may see their collection disappear after a week of inactivity.

Mitigation: Store favorites in IndexedDB (which is not subject to ITP’s timer) or sync to backend on every change.

6.4 Browser Extension Interference

Ad blockers sometimes mistakenly treat requests to /favorites as tracking and block them. Likewise, password managers may inject extra form fields that alter DOM hierarchy and break selector‑based tests.

Check: Run your test suite with popular extensions enabled (uBlock Origin, Privacy Badger, LastPass). If a block occurs, adjust the endpoint naming or ensure the request is not flagged by common filter lists (e.g., avoid URLs containing “track” or “ad”).

6.5 Localization and RTL Layout

When the UI switches to a right‑to‑left language, the favorite icon may appear on the opposite side of the item card, causing mis‑aligned tap targets or overflow. Also, some languages have longer strings (“إضافة إلى المفضلات”) that can push the icon out of view.

Test: Set the browser locale to ar-SA and verify that:

6.6 Dark Mode and Theme Changes

CSS custom properties may not update correctly when the user toggles dark mode via the OS while the page is already open. A favorite icon that relies on fixed colors could lose contrast.

Approach: Listen to the matchMedia('(prefers-color-scheme: dark)') change event and recompute styles, or use CSS variables that automatically follow the system setting. Validate with the DevTools “Rendering” panel → “Emulate CSS media feature prefers‑color‑scheme”.

6.7 Session Restore After Browser Crash

If the browser crashes while a favorite toggle is in flight (optimistic UI shown, request pending), the next session should either:

Test: Use chrome://restart or kill the browser process via task manager while the network is throttled, then relaunch and check the persisted state.

Accessibility and Inclusive Testing

Favorites must be usable by people with diverse abilities. Beyond the basic keyboard and screen‑reader checks, consider the following dimensions.

7.1 Keyboard Navigation

7.2 Screen Reader Announcements

7.3 Color Contrast and Focus Visible

7.4 ARIA Roles and Properties

If you implement a custom “favorite list” panel, treat it as a region with aria-labelledby pointing to a heading. Each list item can be a button with aria-pressed.

Example markup:


<section aria-labelledby="fav-heading" role="region">
  <h2 id="fav-heading">My Favorites</h2>
  <button
    class="fav-item"
    aria-pressed="true"
    aria-label="Remove ‘Product X’ from favorites"
  >
    <svg class="icon" aria-hidden="true">…</svg>
    Product X
  </button>
</section>

7.5 Testing with Persona Profiles (SUSA Mention)

SUSA includes built‑in persona models that simulate varying motor abilities, vision impairments, and cognitive load. For example, the “elderly” persona enlarges tap targets and introduces a delayed reaction time, while the “accessibility” persona enables high‑contrast mode and screen‑reader navigation.

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