diff --git a/.gitignore b/.gitignore index b07b86f..217f45d 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ __pycache__/ reports/ logs/ .env +~$DataProvider *.log *.html *.xml diff --git a/actions/BaseAction.py b/actions/BaseAction.py index c8eb7e3..bc4e89c 100644 --- a/actions/BaseAction.py +++ b/actions/BaseAction.py @@ -106,7 +106,6 @@ def select_by_visible_text(self, locator, text): Select(element).select_by_visible_text(text) def find_elements(self, locator): - return self.wait.until(ec.presence_of_all_elements_located(locator)) def clear(self, locator): @@ -114,4 +113,4 @@ def clear(self, locator): element.clear() if element.get_attribute("value") != "": element.send_keys(Keys.CONTROL, "a") - element.send_keys(Keys.BACKSPACE) + element.send_keys(Keys.BACKSPACE) \ No newline at end of file diff --git a/actions/search_action.py b/actions/search_action.py new file mode 100644 index 0000000..54e021d --- /dev/null +++ b/actions/search_action.py @@ -0,0 +1,184 @@ +""" +Search-specific action methods. + +Mirrors SearchAction.java — every method delegates low-level Selenium +operations to BaseAction so tests stay free of driver boilerplate. +""" + +from selenium.common.exceptions import TimeoutException +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.support.ui import WebDriverWait + +from actions.BaseAction import BaseAction +from pages.search_page import ( + SEARCH_BAR, + RESULT_CARDS, + NO_RESULT_MSG, + MANUFACTURER_LABEL, +) + +import time + + +# ─── Custom Exceptions ──────────────────────────────────────────────────────── + +class SearchResultException(Exception): + """Raised when a keyword search returns zero product cards unexpectedly.""" + + def __init__(self, keyword: str, count: int): + super().__init__( + f"Expected results for keyword '{keyword}' but got {count} products." + ) + + +class ManufacturerMismatchException(Exception): + """Raised when the on-page manufacturer label differs from the expected value.""" + + def __init__(self, expected: str, actual: str): + super().__init__( + f"Manufacturer mismatch — expected: '{expected}', actual: '{actual}'." + ) + + +# ─── SearchAction ───────────────────────────────────────────────────────────── + +class SearchAction(BaseAction): + """ + All search-flow interactions. + + Inherits BaseAction which exposes: + click(), send_keys(), get_text(), is_displayed(), + wait_for_page_load(), find_elements(), … + """ + + # ========================================================================= + # SEARCH BAR ACTIONS + # ========================================================================= + + def click_search_bar(self) -> None: + """ + Wait for page stability, then click the search bar. + Falls back to JS click on TimeoutException (mirrors Java behaviour). + """ + self.wait_for_page_load() + try: + self.click(SEARCH_BAR) + except TimeoutException: + self.js_click(SEARCH_BAR) + + def enter_keyword_and_press_enter(self, keyword: str) -> None: + """ + Clear the search bar, type *keyword*, and submit with ENTER. + Handles the empty-string case (clears the field and submits). + """ + self.wait_for_page_load() + try: + self.send_keys(SEARCH_BAR, keyword) + self.driver.find_element(*SEARCH_BAR).send_keys(Keys.ENTER) + except TimeoutException as exc: + raise TimeoutException( + f"Search bar not visible for keyword entry: '{keyword}'" + ) from exc + + # ========================================================================= + # RESULT VALIDATION ACTIONS + # ========================================================================= + + def _wait_for_results_or_error(self, timeout: int = 15) -> None: + """ + Block until product cards OR the no-results message is rendered. + Mirrors the Java dual-condition wait pattern. + """ + wait = WebDriverWait(self.driver, timeout) + wait.until( + lambda d: bool(d.find_elements(*RESULT_CARDS)) + or self.is_displayed(NO_RESULT_MSG) + ) + + def is_product_list_displayed(self) -> bool: + """ + Return True when product result cards are present after a search. + Return False when only the no-results message is visible. + """ + self._wait_for_results_or_error() + return bool(self.driver.find_elements(*RESULT_CARDS)) + + def get_product_count(self) -> int: + """ + Return the number of product cards visible after a search. + Returns 0 when the no-results message is shown. + """ + self._wait_for_results_or_error() + return len(self.driver.find_elements(*RESULT_CARDS)) + + def is_keyword_present_in_all_results(self, keyword: str) -> bool: + """ + Verify that every result card's display name contains *keyword* + (case-insensitive). + + Raises: + SearchResultException — if the result list is unexpectedly empty. + + Returns: + True — all card names contain the keyword. + False — at least one card name does not contain the keyword + (the offending name is printed to stdout). + """ + self._wait_for_results_or_error() + + cards = self.driver.find_elements(*RESULT_CARDS) + + if not cards: + raise SearchResultException(keyword, 0) + + kw = keyword.lower().strip() + for card in cards: + name = card.text.strip().lower() + if kw not in name: + print(f"[MISMATCH] product name '{name}' does not contain '{kw}'") + return False + + return True + + # ========================================================================= + # NO-RESULT MESSAGE HELPERS + # ========================================================================= + + def get_no_product_message(self) -> str: + """ + Wait for the no-results element to be visible, then return its text. + """ + self.wait_for_page_load() + return self.get_text(NO_RESULT_MSG) + + def is_no_product_message_displayed(self) -> bool: + """Return True if the no-results message is currently visible.""" + return self.is_displayed(NO_RESULT_MSG) + + # ========================================================================= + # URL HELPER + # ========================================================================= + + def get_current_url(self) -> str: + """Return the current browser URL.""" + return self.driver.current_url + + # ========================================================================= + # MANUFACTURER CHECK + # ========================================================================= + + def verify_manufacturer(self, expected_manufacturer: str) -> None: + """ + Read the manufacturer filter label and assert it matches + *expected_manufacturer* (case-insensitive). + + Raises: + ManufacturerMismatchException — when the labels differ. + """ + self.wait_for_page_load() + actual = self.get_text(MANUFACTURER_LABEL).strip() + + if actual.lower() != expected_manufacturer.strip().lower(): + raise ManufacturerMismatchException(expected_manufacturer, actual) + + print(f"[OK] Manufacturer verified: {actual}") \ No newline at end of file diff --git a/configuration/config.ini b/configuration/config.ini index 5521500..fa1adba 100644 --- a/configuration/config.ini +++ b/configuration/config.ini @@ -1,8 +1,6 @@ [browser] -browser = chrome - -mode = normal - +browser = firefox +mode = headless [application] url = https://ecommerce-playground.lambdatest.io/index.php?route=common/home diff --git a/data_provider/SearchProduct.xlsx b/data_provider/SearchProduct.xlsx new file mode 100644 index 0000000..4150cfc Binary files /dev/null and b/data_provider/SearchProduct.xlsx differ diff --git a/data_provider/wishlist_data.csv b/data_provider/wishlist_data.csv index 8b13789..3d36498 100644 --- a/data_provider/wishlist_data.csv +++ b/data_provider/wishlist_data.csv @@ -1 +1,8 @@ - +test_name,product_name,flow,operation,expected_page,expected_message +test_add_single_product_to_wishlist,iMac,top_products,add,My Wish List,Success +test_add_multiple_products_to_wishlist,Apple Cinema 30,top_collection,add,My Wish List,Success +test_add_multiple_products_to_wishlist,iPod Nano,top_collection,add,My Wish List,Success +test_add_product_via_search,iPod Shuffle,search,add,My Wish List,Success +test_remove_single_product_from_wishlist,iMac,top_products,remove,My Wish List,Success +test_remove_multiple_products_from_wishlist,Apple Cinema 30,top_collection,remove,My Wish List,Success +test_remove_multiple_products_from_wishlist,iPod Nano,top_collection,remove,My Wish List,Success \ No newline at end of file diff --git a/pages/search_page.py b/pages/search_page.py new file mode 100644 index 0000000..82bdabe --- /dev/null +++ b/pages/search_page.py @@ -0,0 +1,36 @@ +""" +Search page locators. + +No Page Object class — raw (By, value) tuples consumed directly +by BaseAction helper methods. +""" + +from selenium.webdriver.common.by import By + +# ─── Search Bar ─────────────────────────────────────────────────────────────── + +SEARCH_BAR = ( + By.XPATH, + "//div[@id='entry_217822']//input[@placeholder='Search For Products']", +) + +# ─── Result Cards ───────────────────────────────────────────────────────────── + +RESULT_CARDS = ( + By.XPATH, + "//div[@id='entry_212469']//div[contains(@class,'product-thumb')]//h4/a", +) + +# ─── No-Results Message ─────────────────────────────────────────────────────── + +NO_RESULT_MSG = ( + By.XPATH, + "//div[@id='entry_212469']//p", +) + +# ─── Manufacturer Label ─────────────────────────────────────────────────────── + +MANUFACTURER_LABEL = ( + By.XPATH, + "//div[@class='mz-filter-value both ']//label", +) \ No newline at end of file diff --git a/tests/test_search.py b/tests/test_search.py new file mode 100644 index 0000000..a11a814 --- /dev/null +++ b/tests/test_search.py @@ -0,0 +1,172 @@ +""" +tests/test_search.py +""" + +import os +import pytest + +from actions.search_action import SearchAction +from utils.excelReader import get_data +from utils.loggerCreator import get_logger + +logger = get_logger(__name__) + +# ─── Excel Path ─────────────────────────────────────────────────────────────── + +SEARCH_EXCEL_PATH = os.path.join( + os.path.dirname(os.path.dirname(os.path.abspath(__file__))), + "data_provider", "SearchProduct.xlsx", +) +SEARCH_SHEET = "SearchData" + +# ─── Load Data at Collection Time ──────────────────────────────────────────── +# get_data() returns [[keyword, manufacturer], ...] +_RAW_ROWS: list[list] = get_data(SEARCH_EXCEL_PATH, SEARCH_SHEET) + +# (keyword, manufacturer) tuples — used by keyword + manufacturer tests +_SEARCH_ROWS: list[tuple[str, str]] = [ + (str(row[0]).strip(), str(row[1]).strip()) + for row in _RAW_ROWS if row[0] +] + +# keywords only — used by keyword-name tests +_KEYWORDS: list[str] = [kw for kw, _ in _SEARCH_ROWS] + +# ─── Fixed Test Data ────────────────────────────────────────────────────────── + +NO_RESULT_KEYWORDS = ["Kiot", "ffgok"] +EMPTY_KEYWORD = "" + + +# ============================================================================= +# @Smoke @KeywordSearch +# Scenario Outline: Validate search results match the entered keyword +# +# And the user enters "" and presses Enter +# Then the application should display products based on the keyword +# And the application should display products matching the keyword in their name +# ============================================================================= + +@pytest.mark.Prasanna +@pytest.mark.smoke +@pytest.mark.keyword_search +@pytest.mark.parametrize( + "keyword", + _KEYWORDS + [EMPTY_KEYWORD], + ids=[*_KEYWORDS, "empty_keyword"], +) +def test_keyword_search_results_and_names(driver, keyword): + drv, _ = driver + search = SearchAction(drv) + + logger.info(f"[KeywordSearch] Starting search for keyword: '{keyword}'") + + search.click_search_bar() + search.enter_keyword_and_press_enter(keyword) + + # ── Empty keyword edge case ─────────────────────────────────────────────── + if keyword.strip() == "": + logger.info("[KeywordSearch] Empty keyword — asserting page responded") + product_shown = search.is_product_list_displayed() + no_result_shown = search.is_no_product_message_displayed() + assert product_shown or no_result_shown, ( + "Neither products nor a no-results message appeared for an empty search." + ) + logger.info("[KeywordSearch] Empty keyword — page responded correctly") + return + + # ── Products displayed ──────────────────────────────────────────────────── + assert search.is_product_list_displayed(), ( + f"Expected product results for keyword '{keyword}' but none were displayed." + ) + logger.info(f"[KeywordSearch] Products displayed for keyword: '{keyword}'") + + # ── Every card name contains the keyword ───────────────────────────────── + assert search.is_keyword_present_in_all_results(keyword), ( + f"One or more product cards did not contain keyword '{keyword}' in their name." + ) + logger.info(f"[KeywordSearch] All product names matched keyword: '{keyword}'") + + +# ============================================================================= +# @Smoke @NoResultSearch +# Scenario Outline: Validate no-results message for unmatched keywords +# +# And the user enters "" and presses Enter +# Then the application should display the no-results message +# ============================================================================= + +@pytest.mark.Prasanna +@pytest.mark.smoke +@pytest.mark.no_result_search +@pytest.mark.parametrize("keyword", NO_RESULT_KEYWORDS) +def test_no_result_search(driver, keyword): + drv, _ = driver + search = SearchAction(drv) + + logger.info(f"[NoResultSearch] Searching for no-result keyword: '{keyword}'") + + search.click_search_bar() + search.enter_keyword_and_press_enter(keyword) + + # ── No product cards ────────────────────────────────────────────────────── + assert not search.is_product_list_displayed(), ( + f"Expected NO products for keyword '{keyword}' but product cards appeared." + ) + logger.info(f"[NoResultSearch] Confirmed no products shown for: '{keyword}'") + + # ── No-results message visible ──────────────────────────────────────────── + assert search.is_no_product_message_displayed(), ( + f"No-results message was not displayed for keyword '{keyword}'." + ) + + message = search.get_no_product_message() + assert message.strip(), ( + f"No-results message text was blank for keyword '{keyword}'." + ) + logger.info(f"[NoResultSearch] No-results message: '{message}' for keyword: '{keyword}'") + + +# ============================================================================= +# @Regression @ManufacturerFilter +# Scenario: Validate search results show only manufacturer products +# +# And the user enters the product "" and presses Enter +# Then the application should display products based on the keyword +# And the application should list only the manufacturer products +# based on "" +# ============================================================================= + +@pytest.mark.Prasanna +@pytest.mark.regression +@pytest.mark.manufacturer_filter +@pytest.mark.parametrize( + "keyword, expected_manufacturer", + _SEARCH_ROWS, + ids=[f"{kw}-{mfr}" for kw, mfr in _SEARCH_ROWS], +) +def test_manufacturer_filter_search(driver, keyword, expected_manufacturer): + drv, _ = driver + search = SearchAction(drv) + + logger.info( + f"[ManufacturerFilter] Searching '{keyword}', " + f"expecting manufacturer: '{expected_manufacturer}'" + ) + + search.click_search_bar() + search.enter_keyword_and_press_enter(keyword) + + # ── Products displayed ──────────────────────────────────────────────────── + assert search.is_product_list_displayed(), ( + f"Expected products for keyword '{keyword}' but none were displayed." + ) + logger.info(f"[ManufacturerFilter] Products displayed for keyword: '{keyword}'") + + # ── Manufacturer label matches ──────────────────────────────────────────── + # verify_manufacturer() raises ManufacturerMismatchException on failure + search.verify_manufacturer(expected_manufacturer) + logger.info( + f"[ManufacturerFilter] Manufacturer '{expected_manufacturer}' " + f"verified for keyword: '{keyword}'" + ) \ No newline at end of file diff --git a/tests/test_wishlist.py b/tests/test_wishlist.py index c3d5392..902baa0 100644 --- a/tests/test_wishlist.py +++ b/tests/test_wishlist.py @@ -1,4 +1,4 @@ -"""Wishlist test suite: add and remove products via various flows.""" +"""Wishlist test suite: add and remove products via data-driven CSV.""" import pytest @@ -8,14 +8,11 @@ from actions.accountpageaction import AccountPageAction from utils.excelReader import get_data from utils.loggerCreator import get_logger +from utils.csvDataProvider import CsvDataProvider as CS LOGIN_DATA_PATH = "data_provider/DataProvider.xlsx" LOGIN_DATA_SHEET = "loginDataValid" - -PRODUCT_IMAC = "iMac" -PRODUCT_APPLE_CINEMA = "Apple Cinema 30" -PRODUCT_IPOD_NANO = "iPod Nano" -PRODUCT_IPOD_SHUFFLE = "iPod Shuffle" +WISHLIST_CSV_PATH = "data_provider/wishlist_data.csv" SUCCESS_KEYWORD = "Success" MODIFIED_KEYWORD = "modified" @@ -23,9 +20,17 @@ logger = get_logger(__name__) +def log_csv_data(data): + """Log CSV row data.""" + logger.info("CSV test data loaded successfully") + + for key, value in data.items(): + logger.info("%s = %s", key, value) + + @pytest.fixture def setup(driver): - """Log in and return (driver, wait, wishlist_actions) for each test.""" + """Log in and return driver, wait, and wishlist actions.""" drv, wait = driver wishlist_actions = WishListActions(drv) @@ -33,12 +38,21 @@ def setup(driver): assert "route=common/home" in drv.current_url logger.info("Landed on home page: %s", drv.current_url) - username, password = get_data(LOGIN_DATA_PATH, LOGIN_DATA_SHEET)[0] - logger.info("Using credentials for user: %s", username) + login_data = get_data(LOGIN_DATA_PATH, LOGIN_DATA_SHEET) + + logger.info( + "Loaded %d login record(s) from Excel file '%s' sheet '%s'", + len(login_data), + LOGIN_DATA_PATH, + LOGIN_DATA_SHEET, + ) + + username, password = login_data[0] + logger.info("Selected login username: %s", username) home_page_action = HomePageAction(drv) home_page_action.click_myAcc() - logger.info("Clicked 'My Account' link") + logger.info("Clicked My Account link") login_page_action = LoginPageAction(drv) login_page_action.enter_login_credentials(username, password) @@ -46,6 +60,7 @@ def setup(driver): account_page_action = AccountPageAction(drv) login_ok = account_page_action.success_login() + assert login_ok is True logger.info("Login successful: %s", login_ok) @@ -56,156 +71,239 @@ def setup(driver): def _assert_success_message(message): + """Validate wishlist add success message.""" logger.info("Wishlist success message: %s", message) + assert message assert SUCCESS_KEYWORD in message def _assert_removal_message(message): + """Validate wishlist remove success or modified message.""" logger.info("Wishlist removal message: %s", message) + assert message assert SUCCESS_KEYWORD in message or MODIFIED_KEYWORD in message def _ensure_product_in_wishlist(wishlist_actions, product_name, scroll_method): + """Add product to wishlist if it is not already present.""" if not wishlist_actions.is_product_present_in_wishlist(product_name): - logger.info("'%s' not in wishlist, adding it now", product_name) + logger.info("Product '%s' not found in wishlist. Adding now.", product_name) + scroll_method() wishlist_actions.add_product_to_wishlist_by_name(product_name) + + logger.info("Product '%s' added to wishlist", product_name) + wishlist_actions.navigate_to_wishlist_via_account() wishlist_actions.wait_for_wishlist_page() + + logger.info("Returned to wishlist page after adding product") else: - logger.info("'%s' already present in wishlist", product_name) + logger.info("Product '%s' already present in wishlist", product_name) @pytest.mark.Prasanna -def test_add_single_product_to_wishlist(setup): - """Adding a single product from the home page shows it in the wishlist.""" +@pytest.mark.parametrize( + "data", + CS.get_csv_data_by_test_name( + WISHLIST_CSV_PATH, + "test_add_single_product_to_wishlist", + ), +) +def test_add_single_product_to_wishlist(setup, data): + """Add a single product from home page to wishlist.""" _, _, wishlist_actions = setup - logger.info("Starting test: add single product '%s' to wishlist", PRODUCT_IMAC) + log_csv_data(data) + + product_name = data["product_name"] + expected_page = data["expected_page"] + + logger.info("Starting test: add single product '%s'", product_name) wishlist_actions.scroll_to_top_products() - wishlist_actions.hover_and_click_wishlist_button(PRODUCT_IMAC) - logger.info("Clicked wishlist button for '%s'", PRODUCT_IMAC) + logger.info("Scrolled to top products section") + + wishlist_actions.hover_and_click_wishlist_button(product_name) + logger.info("Clicked wishlist button for '%s'", product_name) - _assert_success_message(wishlist_actions.get_wishlist_success_message_generic()) + _assert_success_message( + wishlist_actions.get_wishlist_success_message_generic() + ) wishlist_actions.click_wishlist_link_from_popup() wishlist_actions.wait_for_wishlist_page() - logger.info("Navigated to wishlist page via popup link") + logger.info("Navigated to wishlist page from success popup") page_title = wishlist_actions.get_current_page_title() logger.info("Wishlist page title: %s", page_title) - assert "My Wish List" in page_title + + assert expected_page in page_title product_names = wishlist_actions.get_all_wishlist_product_names() - logger.info("Products currently in wishlist: %s", product_names) - assert product_names - assert any(PRODUCT_IMAC in name for name in product_names) + logger.info("Wishlist products: %s", product_names) - logger.info("Test passed: '%s' found in wishlist", PRODUCT_IMAC) + assert any(product_name in name for name in product_names) + + logger.info("Test passed: '%s' found in wishlist", product_name) @pytest.mark.Prasanna -def test_add_multiple_products_to_wishlist(setup): - """Adding multiple products from the top collection shows all of them in the wishlist.""" +@pytest.mark.parametrize( + "data", + CS.get_csv_data_by_test_name( + WISHLIST_CSV_PATH, + "test_add_multiple_products_to_wishlist", + ), +) +def test_add_multiple_products_to_wishlist(setup, data): + """Add multiple products from top collection to wishlist.""" _, _, wishlist_actions = setup - products = [PRODUCT_APPLE_CINEMA, PRODUCT_IPOD_NANO] - logger.info("Starting test: add multiple products %s to wishlist", products) + log_csv_data(data) + + product_name = data["product_name"] + + logger.info("Starting test: add product '%s' from top collection", product_name) wishlist_actions.scroll_to_top_collection() + logger.info("Scrolled to top collection section") - for product_name in products: - wishlist_actions.add_product_to_wishlist_by_name(product_name) - logger.info("Added '%s' to wishlist", product_name) - _assert_success_message(wishlist_actions.get_wishlist_success_message_generic()) + wishlist_actions.add_product_to_wishlist_by_name(product_name) + logger.info("Added '%s' to wishlist", product_name) + + _assert_success_message( + wishlist_actions.get_wishlist_success_message_generic() + ) wishlist_actions.navigate_to_wishlist_via_account() wishlist_actions.wait_for_wishlist_page() logger.info("Navigated to wishlist page via account") product_names = wishlist_actions.get_all_wishlist_product_names() - logger.info("Products currently in wishlist: %s", product_names) + logger.info("Wishlist products: %s", product_names) - for product_name in products: - assert any(product_name in name for name in product_names) + assert any(product_name in name for name in product_names) - logger.info("Test passed: all products %s found in wishlist", products) + logger.info("Test passed: '%s' found in wishlist", product_name) @pytest.mark.Prasanna -def test_add_product_via_search(setup): - """Adding a product found via search shows it in the wishlist.""" +@pytest.mark.parametrize( + "data", + CS.get_csv_data_by_test_name( + WISHLIST_CSV_PATH, + "test_add_product_via_search", + ), +) +def test_add_product_via_search(setup, data): + """Add product to wishlist using search flow.""" _, _, wishlist_actions = setup - logger.info("Starting test: add product '%s' via search", PRODUCT_IPOD_SHUFFLE) + log_csv_data(data) + + product_name = data["product_name"] - wishlist_actions.search_for_product(PRODUCT_IPOD_SHUFFLE) - logger.info("Searched for product: %s", PRODUCT_IPOD_SHUFFLE) + logger.info("Starting test: add product '%s' via search", product_name) - wishlist_actions.click_product_from_search_results(PRODUCT_IPOD_SHUFFLE) - logger.info("Opened product page for: %s", PRODUCT_IPOD_SHUFFLE) + wishlist_actions.search_for_product(product_name) + logger.info("Searched product: %s", product_name) + + wishlist_actions.click_product_from_search_results(product_name) + logger.info("Opened product from search results: %s", product_name) wishlist_actions.click_heart_button_on_product_page() - logger.info("Clicked wishlist (heart) button on product page") + logger.info("Clicked wishlist heart button on product page") - _assert_success_message(wishlist_actions.get_wishlist_success_message_generic()) + _assert_success_message( + wishlist_actions.get_wishlist_success_message_generic() + ) wishlist_actions.navigate_to_wishlist_via_account() wishlist_actions.wait_for_wishlist_page() logger.info("Navigated to wishlist page via account") product_names = wishlist_actions.get_all_wishlist_product_names() - logger.info("Products currently in wishlist: %s", product_names) - assert any(PRODUCT_IPOD_SHUFFLE in name for name in product_names) + logger.info("Wishlist products: %s", product_names) - logger.info("Test passed: '%s' found in wishlist", PRODUCT_IPOD_SHUFFLE) + assert any(product_name in name for name in product_names) + + logger.info("Test passed: '%s' found in wishlist", product_name) @pytest.mark.Prasanna -def test_remove_single_product_from_wishlist(setup): - """Removing a single product from the wishlist shows a success/modified message.""" +@pytest.mark.parametrize( + "data", + CS.get_csv_data_by_test_name( + WISHLIST_CSV_PATH, + "test_remove_single_product_from_wishlist", + ), +) +def test_remove_single_product_from_wishlist(setup, data): + """Remove a single product from wishlist.""" _, _, wishlist_actions = setup - logger.info("Starting test: remove single product '%s' from wishlist", PRODUCT_IMAC) + log_csv_data(data) + + product_name = data["product_name"] + + logger.info("Starting test: remove single product '%s'", product_name) wishlist_actions.navigate_to_wishlist_via_account() wishlist_actions.wait_for_wishlist_page() logger.info("Navigated to wishlist page") _ensure_product_in_wishlist( - wishlist_actions, PRODUCT_IMAC, wishlist_actions.scroll_to_top_products + wishlist_actions, + product_name, + wishlist_actions.scroll_to_top_products, ) - wishlist_actions.remove_product_from_wishlist(PRODUCT_IMAC) - logger.info("Clicked remove button for '%s'", PRODUCT_IMAC) + wishlist_actions.remove_product_from_wishlist(product_name) + logger.info("Clicked remove button for '%s'", product_name) - _assert_removal_message(wishlist_actions.get_removal_success_message()) + _assert_removal_message( + wishlist_actions.get_removal_success_message() + ) - logger.info("Test passed: '%s' removed from wishlist", PRODUCT_IMAC) + logger.info("Test passed: '%s' removed from wishlist", product_name) @pytest.mark.Prasanna -@pytest.mark.parametrize("product_name", [PRODUCT_APPLE_CINEMA, PRODUCT_IPOD_NANO]) -def test_remove_multiple_products_from_wishlist(setup, product_name): - """Removing each product from the wishlist shows a success/modified message.""" +@pytest.mark.parametrize( + "data", + CS.get_csv_data_by_test_name( + WISHLIST_CSV_PATH, + "test_remove_multiple_products_from_wishlist", + ), +) +def test_remove_multiple_products_from_wishlist(setup, data): + """Remove multiple products from wishlist using CSV data.""" _, _, wishlist_actions = setup - logger.info("Starting test: remove product '%s' from wishlist", product_name) + log_csv_data(data) + + product_name = data["product_name"] + + logger.info("Starting test: remove product '%s'", product_name) wishlist_actions.navigate_to_wishlist_via_account() wishlist_actions.wait_for_wishlist_page() logger.info("Navigated to wishlist page") _ensure_product_in_wishlist( - wishlist_actions, product_name, wishlist_actions.scroll_to_top_collection + wishlist_actions, + product_name, + wishlist_actions.scroll_to_top_collection, ) wishlist_actions.remove_product_from_wishlist(product_name) logger.info("Clicked remove button for '%s'", product_name) - _assert_removal_message(wishlist_actions.get_removal_success_message()) + _assert_removal_message( + wishlist_actions.get_removal_success_message() + ) logger.info("Test passed: '%s' removed from wishlist", product_name) \ No newline at end of file diff --git a/utils/csvDataProvider.py b/utils/csvDataProvider.py index 439c8b2..bf12db5 100644 --- a/utils/csvDataProvider.py +++ b/utils/csvDataProvider.py @@ -4,20 +4,10 @@ class CsvDataProvider: - @staticmethod - def get_data(csv_path, scenario_key): - if not os.path.exists(csv_path): - raise FileNotFoundError(f"[CSV ERROR] CSV file not found: {csv_path}") - - rows = [] - with open(csv_path, newline="", encoding="utf-8") as f: - reader = csv.DictReader(f) - for row in reader: - if row.get("scenario") == scenario_key: - rows.append(row) - return rows - - @staticmethod - def get_first_row(csv_path, scenario_key): - rows = CsvDataProvider.get_data(csv_path, scenario_key) - return rows[0] if rows else None \ No newline at end of file + def get_csv_data(file_path): + with open(file_path, mode="r", encoding="utf-8") as file: + return list(csv.DictReader(file)) + + def get_csv_data_by_test_name(file_path, test_name): + rows = CsvDataProvider.get_csv_data(file_path) + return [row for row in rows if row["test_name"] == test_name] \ No newline at end of file diff --git a/utils/excelReader.py b/utils/excelReader.py index 40c60af..fad374f 100644 --- a/utils/excelReader.py +++ b/utils/excelReader.py @@ -1,11 +1,21 @@ from openpyxl import Workbook import openpyxl +import os + + +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + +SEARCH_EXCEL_PATH = os.path.join( + BASE_DIR, "data_provider","searchProduct.xlsx" +) + +SEARCH_SHEET = "SearchData" def get_data(path, sheet_name): final_list = [] - Workbook = openpyxl.load_workbook(path) - sheet = Workbook[sheet_name] + wb = openpyxl.load_workbook(path) + sheet = wb[sheet_name] total_row = sheet.max_row total_columns = sheet.max_column @@ -22,17 +32,17 @@ def get_registration_data(path, sheet_name): sheet = wb[sheet_name] return { - "firstname": sheet.cell(2, 1).value, - "lastname": sheet.cell(2, 2).value, - "email": sheet.cell(2, 3).value, - "telephone": sheet.cell(2, 4).value, - "password": sheet.cell(2, 5).value, + "firstname": sheet.cell(2, 1).value, + "lastname": sheet.cell(2, 2).value, + "email": sheet.cell(2, 3).value, + "telephone": sheet.cell(2, 4).value, + "password": sheet.cell(2, 5).value, "confirm_password": sheet.cell(2, 6).value, - "company": sheet.cell(2, 7).value, - "address1": sheet.cell(2, 8).value, - "address2": sheet.cell(2, 9).value, - "city": sheet.cell(2, 10).value, - "postcode": sheet.cell(2, 11).value, - "country": sheet.cell(2, 12).value, - "region": sheet.cell(2, 13).value + "company": sheet.cell(2, 7).value, + "address1": sheet.cell(2, 8).value, + "address2": sheet.cell(2, 9).value, + "city": sheet.cell(2, 10).value, + "postcode": sheet.cell(2, 11).value, + "country": sheet.cell(2, 12).value, + "region": sheet.cell(2, 13).value, } \ No newline at end of file