How to Automate Favorites Testing (Step-by-Step)

Automating favorites testing, a critical component of ensuring a robust and reliable user experience, involves systematically verifying the functionality that allows users to mark, save, or retrieve p

April 02, 2026 · 14 min read · How-To Guides

Automating favorites testing, a critical component of ensuring a robust and reliable user experience, involves systematically verifying the functionality that allows users to mark, save, or retrieve preferred items, content, or settings within an application. This guide provides a step-by-step approach for QA and development engineers to implement effective automation for this often-overlooked yet vital feature set. We'll explore when automation becomes indispensable, delve into framework selection, discuss strategies for writing stable and maintainable tests, examine locator techniques, address common challenges like waits and flakiness, cover data setup and teardown, integrate tests into CI/CD pipelines, and establish comprehensive reporting.

The "favorites" feature, whether it's starring an email, liking a post, bookmarking an article, or adding a product to a wishlist, is fundamental to user engagement and retention. Users rely on it for personalization and quick access. When this functionality breaks, it directly impacts user satisfaction and can lead to significant frustration. Given the diverse ways "favorites" can be implemented – from simple toggle buttons to complex lists with filtering and sorting – manual testing can become repetitive, error-prone, and time-consuming, especially with frequent releases and platform variations. Automating these tests not only accelerates feedback cycles but also significantly increases test coverage and consistency, allowing human testers to focus on exploratory testing and complex user journeys.

Understanding the Scope of Favorites Functionality

Before diving into automation, a clear understanding of what "favorites" entails in your specific application is paramount. This isn't just about clicking a star icon; it involves understanding the state changes, data persistence, and user interface updates across various scenarios.

Core Behaviors and Edge Cases

A comprehensive test plan for favorites functionality should cover the following aspects:

  1. Add to Favorites:
  1. Remove from Favorites:
  1. View Favorites List:
  1. Interaction from Favorites List:
  1. Synchronization and State Management:

Example Test Matrix for a "Favorite Product" Feature

Test Case IDFeatureScenarioStepsExpected ResultPriority
FAV-001Add FavoriteProduct Detail Page (PDP)1. Log in as User A.
2. Navigate to Product X PDP.
3. Click "Favorite" icon.
4. Navigate to "My Wishlist".
Product X is displayed in "My Wishlist" with correct details. Favorite icon on PDP is filled.High
FAV-002Remove FavoriteProduct Detail Page (PDP)1. Log in as User A.
2. Navigate to Product X PDP.
3. Click "Favorite" icon (ensure it's already favorited).
4. Navigate to "My Wishlist".
Product X is NOT displayed in "My Wishlist". Favorite icon on PDP is empty.High
FAV-003View FavoritesEmpty Wishlist1. Log in as User B (new user).
2. Navigate to "My Wishlist".
"Your Wishlist is Empty" message is displayed. No products are listed.Medium
FAV-004View FavoritesPopulated Wishlist1. Log in as User A.
2. Favorite 3 distinct products (X, Y, Z).
3. Navigate to "My Wishlist".
Products X, Y, Z are displayed in "My Wishlist". Details (name, price) are accurate.High
FAV-005PersistenceAfter Logout/Login1. Log in as User A.
2. Favorite Product X.
3. Log out.
4. Log in as User A.
5. Navigate to "My Wishlist".
Product X is still displayed in "My Wishlist".High
FAV-006Remove from ListMy Wishlist Page1. Log in as User A.
2. Favorite Product X.
3. Navigate to "My Wishlist".
4. Click "Remove" icon next to Product X.
5. Verify "My Wishlist".
Product X is removed from "My Wishlist". "Remove" icon changes to "Add to Wishlist" on Product X's PDP.High

When Automation Pays Off for Favorites Testing

While manual testing is essential for initial feature validation and exploratory testing, its efficacy diminishes rapidly as the application grows and release cycles shorten. Automating favorites testing becomes highly beneficial when:

The initial investment in setting up the automation framework and writing tests is quickly recouped by reduced manual effort, faster feedback, and improved software quality over time.

Choosing the Right Automation Framework and Tools

Selecting the appropriate automation framework is a critical decision that impacts the maintainability, scalability, and efficiency of your favorites tests. The choice often depends on the application's technology stack (web, mobile, desktop), team expertise, and existing infrastructure.

Web Application Automation

For web applications, popular choices include:

Mobile Application Automation

For mobile applications (iOS, Android):

Autonomous Testing Platforms

An emerging category that can significantly accelerate the initial bootstrapping of favorites testing, especially for mobile, is autonomous testing platforms.

Comparison Table: Frameworks for Favorites Testing

Feature/FrameworkSelenium (Web)Playwright (Web)Appium (Mobile)Cypress (Web)SUSA (Autonomous)
Primary Use CaseCross-browser E2EModern Web E2EMobile E2ESPA E2E, Dev-friendlyAutonomous exploration, Bug finding, Script generation
LanguagesPython, Java, JS, C#Python, Java, JS, C#Python, Java, JS, C#JavaScript/TypeScriptNo scripting required for exploration; generates Python/JS for regression.
Auto-WaitManual/ExplicitBuilt-in SmartManual/ExplicitBuilt-in SmartBuilt-in Smart
Parallel ExecutionYes (with grid)Excellent Built-inYes (with multiple devices/servers)Limited to spec filesYes (multiple personas/runs)
DebuggingBrowser DevToolsBrowser DevTools, Trace ViewerAppium Desktop, IDEIn-browser DevTools, Time TravelDetailed run reports, video, screenshots
Learning CurveModerateLow-ModerateHigh (setup)LowVery Low (for exploration)
Initial SetupModerateLowHighLowVery Low (upload APK/URL)
Script GenerationNoNoNoNoYes (Appium/Playwright)

For this guide, we'll primarily use Playwright with Python for web examples and Appium with Python for mobile examples, as they represent robust and widely adopted choices that can handle the complexities of favorites testing.

Writing Stable and Maintainable Tests

The goal is not just to automate, but to automate *well*. Unstable, flaky tests are worse than no tests, as they erode trust and waste engineering time.

Page Object Model (POM) Design Pattern

The Page Object Model is fundamental for creating maintainable and readable UI tests. Each significant screen or component of your application gets its own "Page Object" class, which encapsulates the UI elements (locators) and interactions (methods) on that page.

Benefits:

Example: ProductDetailPage Page Object (Playwright/Python)


# pages/product_detail_page.py
from playwright.sync_api import Page, Locator

class ProductDetailPage:
    def __init__(self, page: Page):
        self.page = page
        self.favorite_button: Locator = page.locator("button[data-testid='favorite-toggle']")
        self.product_title: Locator = page.locator("h1[data-testid='product-title']")
        self.add_to_cart_button: Locator = page.locator("button[data-testid='add-to-cart']")
        self.toast_message: Locator = page.locator("div[role='status']")

    def navigate(self, product_id: str):
        self.page.goto(f"/products/{product_id}")
        self.product_title.wait_for(state="visible") # Ensure page is loaded

    def toggle_favorite(self):
        self.favorite_button.click()

    def get_favorite_status(self) -> bool:
        # Assuming filled star means favorited
        return "favorited" in self.favorite_button.get_attribute("class")

    def get_product_title(self) -> str:
        return self.product_title.text_content()

    def get_toast_message(self) -> str:
        self.toast_message.wait_for(state="visible", timeout=5000)
        message = self.toast_message.text_content()
        self.toast_message.wait_for(state="hidden", timeout=5000) # Wait for toast to disappear
        return message

# pages/wishlist_page.py
class WishlistPage:
    def __init__(self, page: Page):
        self.page = page
        self.wishlist_items: Locator = page.locator("div[data-testid='wishlist-item']")
        self.empty_wishlist_message: Locator = page.locator("p[data-testid='empty-wishlist-message']")

    def navigate(self):
        self.page.goto("/wishlist")
        self.page.wait_for_selector("div[data-testid='wishlist-container']") # Wait for container to load

    def get_item_count(self) -> int:
        return self.wishlist_items.count()

    def is_item_present(self, product_name: str) -> bool:
        return self.page.locator(f"div[data-testid='wishlist-item'] >> text='{product_name}'").is_visible()

    def remove_item(self, product_name: str):
        self.page.locator(f"div[data-testid='wishlist-item'] >> text='{product_name}'") \
            .locator("button[data-testid='remove-from-wishlist']").click()
        # Add explicit wait for item to disappear or for a confirmation message
        self.page.wait_for_timeout(1000) # Simple wait for demonstration, prefer explicit waits

    def is_empty_message_displayed(self) -> bool:
        return self.empty_wishlist_message.is_visible()

Locator Strategy: The Key to Stable Tests

Choosing robust and resilient locators is paramount to preventing flaky tests. Fragile locators break with minor UI changes, leading to constant test maintenance.

Best Practices for Locators

  1. Prioritize data-testid or other custom attributes: These attributes are specifically added for testing purposes and are least likely to change during refactoring.
  2. 
        <button data-testid="favorite-toggle">Favorite</button>
        <div data-testid="product-card-123">...</div>
    

*Playwright:* page.locator("button[data-testid='favorite-toggle']")

*Appium (XPath):* //android.widget.Button[@content-desc='favorite-toggle'] or //XCUIElementTypeButton[@name='favorite-toggle'] (requires developer to add accessibility IDs).

  1. CSS Selectors: Powerful and generally preferred over XPath for web elements. Combine tag names, classes, IDs, and attributes.
  2. 
        #main-content .product-list > div.product-item button.add-to-favorites
    

*Playwright:* page.locator("#main-content .product-list > div.product-item button.add-to-favorites")

  1. Accessibility IDs/Names (Mobile): For mobile, accessibility IDs (Android: content-desc, iOS: accessibilityIdentifier) are excellent. They are stable and intended for programmatic access.

*Appium:* driver.find_element(AppiumBy.ACCESSIBILITY_ID, "favorite_button")

  1. Text Content: Useful for visible text, but be cautious as text can change due to localization, copy updates, or dynamic content. Use only if other options are unavailable or for specific text verification.

*Playwright:* page.locator("text='Add to Wishlist'")

*Appium:* driver.find_element(AppiumBy.XPATH, "//android.widget.TextView[@text='My Wishlist']")

  1. Avoid Fragile Locators:

Handling Waits and Flakiness

Flakiness is the arch-nemesis of automation. It often stems from timing issues – the test tries to interact with an element before it's visible, enabled, or loaded.

Types of Waits

  1. Implicit Waits (Avoid for UI elements): A global setting that tells the WebDriver to poll the DOM for a certain amount of time when trying to find an element. While convenient, it can mask real issues and make tests slower.
  1. Explicit Waits (Recommended): The most robust way to handle dynamic elements. You wait for a specific condition to be met before proceeding.

Playwright's Built-in Auto-Waiting: Playwright excels here. Most locator actions (click(), fill(), isVisible(), textContent()) automatically wait for elements to be actionable (visible, enabled, stable, receive events) before performing the action. This significantly reduces the need for manual explicit waits.


    # Playwright example - no explicit wait needed for click, it auto-waits
    self.favorite_button.click()
    # But for assertions or specific state checks, explicit waits are still useful
    expect(self.favorite_button).to_have_class("favorited")

Appium Explicit Waits (Python example with WebDriverWait):


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

    # ... inside a test method or Page Object
    wait = WebDriverWait(driver, 10) # Wait up to 10 seconds

    # Wait for the favorite button to be clickable
    favorite_button = wait.until(EC.element_to_be_clickable((AppiumBy.ACCESSIBILITY_ID, "favorite_toggle")))
    favorite_button.click()

    # Wait for a toast message to appear
    toast_message = wait.until(EC.visibility_of_element_located((AppiumBy.XPATH, "//*[contains(@text, 'Added to Wishlist')]")))
    # Then wait for it to disappear
    wait.until(EC.invisibility_of_element_located((AppiumBy.XPATH, "//*[contains(@text, 'Added to Wishlist')]")))
  1. Fluent Waits (Advanced Explicit Wait): Similar to explicit waits but allows custom polling intervals and ignoring specific exceptions during polling. Useful for highly dynamic scenarios.

Strategies to Reduce Flakiness

Data Setup and Teardown for Favorites Testing

Clean and consistent test data is crucial for reliable and repeatable tests.

Setup Strategies

  1. API/Database Direct (Preferred): The most efficient and stable way to prepare data. Before a test runs, use API calls or direct database manipulation (if permissible and secure) to:
  1. UI-Driven Setup: Perform initial actions through the UI (e.g., log in, add an item to favorites) if there's no API alternative or if the setup itself is part of a complex user journey you want to validate. This is slower and more prone to flakiness.
  1. Test Data Factories: Use libraries or custom code to generate realistic, unique test data on the fly. This prevents data collisions between parallel tests.

Teardown Strategies

  1. API/Database Direct (Preferred): After a test, clean up any data created.
  1. UI-Driven Teardown: Perform UI actions to clean up. Less efficient but sometimes necessary.

Fixtures for Data Management (Pytest Example)

Pytest fixtures are excellent for managing setup and teardown.


# conftest.py (or test_favorites.py)
import pytest
from playwright.sync_api import Page
from pages.login_page import LoginPage
from pages.product_detail_page import ProductDetailPage
from pages.wishlist_page import WishlistPage
import requests # For API setup/teardown

BASE_URL = "http://localhost:3000" # Or your actual application URL
API_URL = "http://localhost:8080/api"

@pytest.fixture(scope="session")
def api_client():
    """Fixture to provide an API client for data setup/teardown."""
    class ApiClient:
        def login_user(self, username, password):
            # Simulate API login to get a token/session
            response = requests.post(f"{API_URL}/login", json={"username": username, "password": password})
            response.raise_for_status()
            return response.json().get("token")

        def add_favorite_via_api(self, user_token, product_id):
            headers = {"Authorization": f"Bearer {user_token}"}
            response = requests.post(f"{API_URL}/favorites", headers=headers, json={"productId": product_id})
            response.raise_for_status()

        def clear_favorites_via_api(self, user_token):
            headers = {"Authorization": f"Bearer {user_token}"}
            response = requests.delete(f"{API_URL}/favorites/all", headers=headers)
            response.raise_for_status()

    return ApiClient()

@pytest.fixture(scope="function")
def logged_in_user_page(page: Page, api_client):
    """
    Fixture to provide a Playwright Page object with a logged-in user.
    Clears favorites before and after each test.
    """
    # Setup: Create a user and clear their favorites
    username = "testuser_fav"
    password = "password123"
    # Assuming user exists or is created by API
    user_token = api_client.login_user(username, password)
    api_client.clear_favorites_via_api(user_token) # Ensure a clean slate

    login_page = LoginPage(page)
    login_page.navigate()
    login_page.login(username, password)
    yield page # Yield control to the test function

    # Teardown: Clear favorites after the test
    api_client.clear_favorites_via_api(user_token)

Integrating Favorites Tests into CI/CD

Automated tests are most valuable when they run regularly and provide fast feedback. Integrating them into your CI/CD pipeline is essential.

Steps for CI/CD Integration

  1. Version Control: Store all test code in the same Git repository as the application code, or a closely linked one.
  2. Dedicated CI Environment: Set up a clean, consistent environment for running tests. This might involve Docker containers or virtual machines with all necessary dependencies (Node.js, Python, browser binaries for Playwright, Appium server for mobile).
  3. Pre-build/Pre-deploy Hooks: Configure your CI server (Jenkins, GitLab CI, GitHub Actions, Azure DevOps, CircleCI) to trigger the test suite automatically.
  1. Parallel Execution: Leverage the parallelization capabilities of

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