From a52f4ebcf91b6454e87d37736944c194f67e73fc Mon Sep 17 00:00:00 2001 From: Jothika Date: Sun, 14 Jun 2026 22:15:48 +0530 Subject: [PATCH] Completed addreview feature and added AddOns --- .github/release.yml | 68 ++ .github/workflows/release.yml | 41 - .github/workflows/update-readme.yml | 56 -- .gitignore | 1 + README.md | 1167 +++------------------- actions/AddOnspageaction.py | 27 + actions/BaseAction.py | 3 +- actions/ProductCompareAction.py | 2 +- actions/blogAction.py | 57 ++ actions/checkoutAction.py | 66 +- actions/search_action.py | 184 ++++ configuration/config.ini | 6 +- data_provider/DataProvider.xlsx | Bin 15379 -> 17772 bytes data_provider/SearchProduct.xlsx | Bin 0 -> 6111 bytes data_provider/wishlist_data.csv | 9 +- data_provider/~$DataProvider.xlsx | Bin 165 -> 0 bytes pages/AddOnspage.py | 42 + pages/{ComaprePage.py => ComparePage.py} | 0 pages/checkoutPage.py | 7 - pages/search_page.py | 36 + tests/test_addons.py | 43 + tests/test_addreview.py | 89 ++ tests/test_blog.py | 62 ++ tests/test_checkout.py | 37 +- tests/test_editAccInfo.py | 25 +- tests/test_search.py | 172 ++++ tests/test_subscribeNewsLetter.py | 27 +- tests/test_wishlist.py | 222 ++-- utils/csvDataProvider.py | 24 +- utils/excelReader.py | 38 +- 30 files changed, 1209 insertions(+), 1302 deletions(-) create mode 100644 .github/release.yml delete mode 100644 .github/workflows/release.yml delete mode 100644 .github/workflows/update-readme.yml create mode 100644 actions/AddOnspageaction.py create mode 100644 actions/search_action.py create mode 100644 data_provider/SearchProduct.xlsx delete mode 100644 data_provider/~$DataProvider.xlsx create mode 100644 pages/AddOnspage.py rename pages/{ComaprePage.py => ComparePage.py} (100%) create mode 100644 pages/search_page.py create mode 100644 tests/test_addons.py create mode 100644 tests/test_search.py diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 0000000..8a133b3 --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,68 @@ +name: Release Workflow - Pytest Selenium + +on: + push: + tags: + - 'v*.*.*' + +jobs: + release: + runs-on: ubuntu-latest + + steps: + - name: Checkout Code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + cache: 'pip' + + - name: Install Firefox + uses: browser-actions/setup-firefox@v1 + + - name: Verify Firefox + run: firefox --version + + - name: Install Dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Run Pytest - Firefox Headless + env: + CI: true + BROWSER: firefox + MODE: headless + run: | + pytest -v --html=reports/pytest-report.html --self-contained-html + + - name: Upload Test Report Artifact + uses: actions/upload-artifact@v4 + if: always() + with: + name: pytest-html-report + path: reports/ + + - name: Generate Changelog + uses: mikepenz/release-changelog-builder-action@v5 + with: + configuration: ".github/release.yml" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ github.ref_name }} + name: ${{ github.ref_name }} + draft: false + prerelease: false + generate_release_notes: true + files: | + reports/pytest-report.html + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index cbe2596..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: Create GitHub Release - -on: - push: - tags: - - "v*.*.*" - -jobs: - release: - name: Build and Release - runs-on: ubuntu-latest - - steps: - - name: Checkout Source Code - uses: actions/checkout@v4 - - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: "3.12" - - - name: Install Dependencies - run: | - python -m pip install --upgrade pip - pip install -r requirements.txt - - - name: Run Tests - run: | - pytest -s - - - name: Create Project Zip - run: | - zip -r Pytest_Automation_Project.zip . -x ".git/*" "__pycache__/*" "*.pyc" ".github/*" - - - name: Create GitHub Release - uses: softprops/action-gh-release@v2 - with: - files: Pytest_Automation_Project.zip - generate_release_notes: true - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/update-readme.yml b/.github/workflows/update-readme.yml deleted file mode 100644 index 595d938..0000000 --- a/.github/workflows/update-readme.yml +++ /dev/null @@ -1,56 +0,0 @@ -name: Update README -on: - push: - branches: - - main -jobs: - update-readme: - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Run Tests - run: | - pip install --upgrade pip setuptools wheel - pip install -r requirements.txt - pytest > results.txt || true - - - name: Update README - run: | - python - <<'EOF' - import re - - with open("results.txt") as f: - results = f.read() - - with open("README.md") as f: - readme = f.read() - - new_section = "## Latest Execution\n```\n" + results + "```\n" - - if "## Latest Execution" in readme: - # Replace existing section - readme = re.sub( - r"## Latest Execution\n```.*?```\n", - new_section, - readme, - flags=re.DOTALL - ) - else: - # Append new section at the end - readme = readme.rstrip() + "\n\n" + new_section - - with open("README.md", "w") as f: - f.write(readme) - EOF - - - name: Commit README - run: | - git config user.name "github-actions" - git config user.email "actions@github.com" - git add README.md - git commit -m "Auto update README with latest test results" || exit 0 - git push 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/README.md b/README.md index e4b3569..9440224 100644 --- a/README.md +++ b/README.md @@ -1,59 +1,103 @@ +![Python](https://img.shields.io/badge/Python-3776AB?style=for-the-badge&logo=python&logoColor=white) +![Pytest](https://img.shields.io/badge/Pytest-0A9EDC?style=for-the-badge&logo=pytest&logoColor=white) +![Selenium](https://img.shields.io/badge/Selenium-43B02A?style=for-the-badge&logo=selenium&logoColor=white) +![Allure](https://img.shields.io/badge/Allure-E6E6E6?style=for-the-badge&logo=allure&logoColor=black) +![Pytest HTML](https://img.shields.io/badge/Pytest_HTML-009688?style=for-the-badge) +![GitHub Actions](https://img.shields.io/badge/GitHub_Actions-2088FF?style=for-the-badge&logo=github-actions&logoColor=white) +![Jenkins](https://img.shields.io/badge/Jenkins-D24939?style=for-the-badge&logo=jenkins&logoColor=white) + # Pytest Selenium Automation Framework -A robust and scalable Selenium WebDriver automation framework built with **Python** and **Pytest**. +A scalable and maintainable UI automation framework built using **Python, Selenium WebDriver, and Pytest** for the **LambdaTest Ecommerce Playground**. This project was developed for learning and implementing modern test automation practices using industry-standard design patterns and tools. --- ## Features -- ✅ **Cross-browser support** (Chrome & Firefox) -- ✅ **Headless & Normal mode** support -- ✅ **Page Object Model (POM)** design pattern -- ✅ **Centralized configuration** via `config.ini` -- ✅ **Automatic driver management** (webdriver-manager) -- ✅ **Explicit & Implicit waits** with configurable timeouts -- ✅ **Rich logging** and reporting -- ✅ **Pytest markers** for selective test execution -- ✅ **Allure & HTML reporting** support -- ✅ **Parallel test execution** ready (`pytest-xdist`) +* Cross-browser support (Chrome & Firefox) +* Headless and Normal execution modes +* Page Object Model (POM) design pattern +* Centralized configuration management using `config.ini` +* Automatic driver management with `webdriver-manager` +* Explicit and configurable wait strategies +* Structured logging and debugging support +* Pytest markers for selective test execution +* Allure Reporting integration +* Pytest HTML Reporting +* Parallel execution support using `pytest-xdist` +* CI/CD ready for GitHub Actions and Jenkins + +--- + +## Application Under Test + +**LambdaTest Ecommerce Playground** + +https://ecommerce-playground.lambdatest.io/ + +--- + +## Tech Stack + +| Technology | Purpose | +| ------------------ | -------------------- | +| Python 3.x | Programming Language | +| Selenium WebDriver | Browser Automation | +| Pytest | Test Framework | +| WebDriver Manager | Driver Management | +| Allure Reports | Advanced Reporting | +| Pytest HTML | HTML Reporting | +| Logging | Test Execution Logs | --- ## Project Structure -```bash -D:\Pytest_Automation_Project -├── configuration/ # Config files (config.ini) -├── pages/ # Page Object classes (POM) -├── tests/ # Test cases -├── utils/ # Utility functions & logger -├── actions/ # Reusable actions (if any) -├── data_provider/ # Test data (Excel, JSON, etc.) -├── logs/ # Log files +```text +Pytest_Automation_Project +│ +├── configuration/ # Configuration files +├── pages/ # Page Object Classes +├── tests/ # Test Cases +├── utils/ # Utility Classes +├── actions/ # Reusable Actions +├── data_provider/ # Test Data +├── logs/ # Log Files +├── reports/ # Generated Reports ├── conftest.py # Fixtures & Driver Setup -├── pytest.ini # Pytest configuration & markers +├── pytest.ini # Pytest Configuration ├── requirements.txt ├── .gitignore └── README.md +``` +--- +## Installation +Clone the repository: -Installation +```bash +git clone +cd Pytest_Automation_Project +``` -Clone or navigate to the project directory: +Install dependencies: -Bashcd D:\Pytest_Automation_Project +```bash +pip install -r requirements.txt +``` -Install dependencies: +--- + +## Configuration -Bashpip install -r requirements.txt +Update the `configuration/config.ini` file: -Configuration -Edit configuration/config.ini: -ini[browser] -browser = firefox # chrome or firefox -mode = normal # normal or headless +```ini +[browser] +browser = firefox +mode = normal [application] url = https://ecommerce-playground.lambdatest.io/index.php?route=common/home @@ -63,1008 +107,121 @@ title = Your Store implicit_wait = 10 explicit_wait = 15 page_load_timeout = 30 +``` -Running Tests -Basic Commands -Bash# Run all tests +--- + +## Running Tests + +### Run All Tests + +```bash pytest +``` + +### Verbose Execution -# Run with verbose output +```bash pytest -v +``` -# Run specific test file +### Run Specific Test File + +```bash pytest tests/test_launch.py -v +``` + +### Run Using Markers -# Run with markers +```bash pytest -m smoke +pytest -m regression +pytest -m sanity pytest -m "sanity and regression" -pytest -m "not slow" -Other Useful Commands -Bash# Run in parallel (using 4 workers) +``` + +--- + +## Parallel Execution + +Run tests using multiple workers: + +```bash pytest -n 4 +``` -# Generate HTML report +--- + +## Generate Reports + +### Pytest HTML Report + +```bash pytest --html=reports/report.html --self-contained-html +``` + +### Allure Report -# Generate Allure report +```bash pytest --alluredir=allure-results allure serve allure-results +``` -Writing Tests -Example test structure: -Python@pytest.mark.smoke +--- + +## Sample Test Structure + +```python +@pytest.mark.smoke @pytest.mark.regression -def test_homepage_verification(self, driver): +def test_homepage_verification(driver): drv, wait = driver - lp = LaunchPages(drv) - # Your test logic... -Tech Stack + # Test Logic Here +``` -Python 3.x -Pytest 9.0+ -Selenium 4.44+ -Webdriver-manager -Allure Reporting -Pytest-HTML -Logging +--- +## Contributing -Requirements -See full list in requirements.txt +Contributions are welcome. -Contributing +1. Fork the repository +2. Create a feature branch +3. Follow the existing POM structure +4. Add proper logging and assertions +5. Test changes in Chrome and Firefox +6. Submit a Pull Request -Create a new branch for your feature. -Follow the existing POM structure. -Add proper logging and assertions. -Test with both Chrome and Firefox. +--- +## Future Enhancements -Author -Pytest Automation Project - Tamil Kumar, Prasanna Venkatesh K, Rishwanth, Samiha, Jothika -Automated UI Testing Framework +* API Testing Integration +* Docker Execution Support +* Cloud Execution (LambdaTest / BrowserStack) +* Data-Driven Testing with Excel and JSON +* Advanced Reporting Dashboard +* Test Analytics Integration -Happy Testing! 🚀 -text--- +--- -### How to use: +## Contributors -1. Copy the entire content above. -2. Go to your project folder. -3. Replace the existing `README.md` (currently empty) with this content. +* Tamil Kumar +* Prasanna Venkatesh K +* Rishwanth +* Samiha +* Jothika -Would you like a shorter version or one with screenshots/badges as well? +--- -## Latest Execution -``` -============================= test session starts ============================== -platform linux -- Python 3.12.3, pytest-9.0.3, pluggy-1.6.0 -rootdir: /home/runner/work/Pytest_Automation_Project/Pytest_Automation_Project -configfile: pytest.ini -testpaths: tests -plugins: rerunfailures-16.3, soft-assertions-0.1.2, xdist-3.8.0, allure-pytest-2.16.0, metadata-3.1.1, html-4.2.0, ordering-0.6, ast-transformer-1.0.3 -collected 28 items - -tests/test_Filter.py ..... [ 17%] -tests/test_cart.py .. [ 25%] -tests/test_checkout.py FF. [ 35%] -tests/test_forgetpassword.py .. [ 42%] -tests/test_launch.py . [ 46%] -tests/test_login.py .F [ 53%] -tests/test_logout.py . [ 57%] -tests/test_register.py .. [ 64%] -tests/test_shopbycategory.py FFFF [ 78%] -tests/test_wishlist.py ..F... [100%] - -=================================== FAILURES =================================== -_______________________ TestCheckout.test_login_checkout _______________________ - -self = -driver = (, ) - - def test_login_checkout(self, driver): - - drv, wait = driver - action = CheckoutAction(drv) - - drv.get(ConfigReader.get_url()) - - action.click_hp_product() -> action.add_product_to_cart() - -tests/test_checkout.py:22: -_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ - -self = - - def add_product_to_cart(self): -> self.click(self.cp.PRODUCT_PAGE_CHECKOUT_BTN1) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -E AttributeError: 'CheckoutPage' object has no attribute 'PRODUCT_PAGE_CHECKOUT_BTN1'. Did you mean: 'CART_PAGE_CHECKOUT_BTN'? - -actions/checkoutAction.py:23: AttributeError ----------------------------- Captured stderr setup ----------------------------- -2026-06-14 06:15:12 INFO conftest Config → browser=chrome | mode=headless | url=https://ecommerce-playground.lambdatest.io/index.php?route=common/home -2026-06-14 06:15:12 INFO conftest Chrome Browser Launched Successfully -2026-06-14 06:15:12 INFO conftest Launching URL: https://ecommerce-playground.lambdatest.io/index.php?route=common/home -2026-06-14 06:15:12 INFO conftest Driver started → browser=chrome | mode=headless ------------------------------- Captured log setup ------------------------------ -INFO conftest:conftest.py:62 Config → browser=chrome | mode=headless | url=https://ecommerce-playground.lambdatest.io/index.php?route=common/home -INFO conftest:conftest.py:119 Chrome Browser Launched Successfully -INFO conftest:conftest.py:175 Launching URL: https://ecommerce-playground.lambdatest.io/index.php?route=common/home -INFO conftest:conftest.py:184 Driver started → browser=chrome | mode=headless ---------------------------- Captured stderr teardown --------------------------- -2026-06-14 06:15:29 INFO conftest Quitting driver. ----------------------------- Captured log teardown ----------------------------- -INFO conftest:conftest.py:196 Quitting driver. -_____________________ TestCheckout.test_register_checkout ______________________ - -self = -driver = (, ) - - def test_register_checkout(self, driver): - - drv, wait = driver - action = CheckoutAction(drv) - -> action.open_home_and_add_product(ConfigReader.get_url()) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -E AttributeError: 'CheckoutAction' object has no attribute 'open_home_and_add_product' - -tests/test_checkout.py:56: AttributeError ----------------------------- Captured stderr setup ----------------------------- -2026-06-14 06:15:29 INFO conftest Config → browser=chrome | mode=headless | url=https://ecommerce-playground.lambdatest.io/index.php?route=common/home -2026-06-14 06:15:29 INFO conftest Chrome Browser Launched Successfully -2026-06-14 06:15:29 INFO conftest Launching URL: https://ecommerce-playground.lambdatest.io/index.php?route=common/home -2026-06-14 06:15:30 INFO conftest Driver started → browser=chrome | mode=headless ------------------------------- Captured log setup ------------------------------ -INFO conftest:conftest.py:62 Config → browser=chrome | mode=headless | url=https://ecommerce-playground.lambdatest.io/index.php?route=common/home -INFO conftest:conftest.py:119 Chrome Browser Launched Successfully -INFO conftest:conftest.py:175 Launching URL: https://ecommerce-playground.lambdatest.io/index.php?route=common/home -INFO conftest:conftest.py:184 Driver started → browser=chrome | mode=headless ---------------------------- Captured stderr teardown --------------------------- -2026-06-14 06:15:30 INFO conftest Quitting driver. ----------------------------- Captured log teardown ----------------------------- -INFO conftest:conftest.py:196 Quitting driver. -__________ TestLogin.test_invalidLogin[camera143@gmail.com-testlogin] __________ - -self = -driver = (, ) -username1 = 'camera143@gmail.com', password1 = 'testlogin' - - @pytest.mark.parametrize( - "username1,password1", - get_data( - "data_provider/DataProvider.xlsx", - "loginDataInvalid", - ), - ) - def test_invalidLogin(self, driver, username1, password1): - drv, wait = driver - - logger.info("Invalid login test started") - - hp = HomePageAction(drv) - hp.click_myAcc() - logger.info("Clicked My Account") - - lp = LoginPageAction(drv) - lp.enter_login_credentials(username1, password1) - logger.info(f"Entered invalid username: {username1}") - -> assert lp.provide_error_msg_invalid_login() is True - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -tests/test_login.py:60: -_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ -actions/loginpageaction.py:23: in provide_error_msg_invalid_login - actual = self.get_text(self.lp.invalidLoginErrorMsg) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -actions/BaseAction.py:39: in get_text - return self.wait.until(ec.visibility_of_element_located(locator)).text - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ - -self = -method = ._predicate at 0x7f510fd2ade0> -message = '' - - def until(self, method: Callable[[D], Literal[False] | T], message: str = "") -> T: - """Wait until the method returns a value that is not False. - - Calls the method provided with the driver as an argument until the - return value does not evaluate to ``False``. - - Args: - method: A callable object that takes a WebDriver instance as an - argument. - message: Optional message for TimeoutException. - - Returns: - The result of the last call to `method`. - - Raises: - TimeoutException: If 'method' does not return a truthy value within - the WebDriverWait object's timeout. - - Example: - >>> from selenium.webdriver.common.by import By - >>> from selenium.webdriver.support.ui import WebDriverWait - >>> from selenium.webdriver.support import expected_conditions as EC - >>> - >>> # Wait until an element is visible on the page - >>> wait = WebDriverWait(driver, 10) - >>> element = wait.until(EC.visibility_of_element_located((By.ID, "exampleId"))) - >>> print(element.text) - """ - screen = None - stacktrace = None - - end_time = time.monotonic() + self._timeout - while True: - try: - value = method(self._driver) - if value: - return value - except self._ignored_exceptions as exc: - screen = getattr(exc, "screen", None) - stacktrace = getattr(exc, "stacktrace", None) - if time.monotonic() > end_time: - break - time.sleep(self._poll) -> raise TimeoutException(message, screen, stacktrace) -E selenium.common.exceptions.TimeoutException: Message: -E Stacktrace: -E #0 0x55a72439c29a -E #1 0x55a723d7e449 -E #2 0x55a723dd2baf -E #3 0x55a723dd2e01 -E #4 0x55a723e1dae4 -E #5 0x55a723e1acbf -E #6 0x55a723dc6132 -E #7 0x55a723dc6f41 -E #8 0x55a724362737 -E #9 0x55a724360f39 -E #10 0x55a72434bc26 -E #11 0x55a724361ada -E #12 0x55a724333bd0 -E #13 0x55a724388c28 -E #14 0x55a724388dc5 -E #15 0x55a72439ae1e -E #16 0x7f9355a9caa4 -E #17 0x7f9355b29c6c - -../../../.local/lib/python3.12/site-packages/selenium/webdriver/support/wait.py:121: TimeoutException ----------------------------- Captured stderr setup ----------------------------- -2026-06-14 06:16:11 INFO conftest Config → browser=chrome | mode=headless | url=https://ecommerce-playground.lambdatest.io/index.php?route=common/home -2026-06-14 06:16:12 INFO conftest Chrome Browser Launched Successfully -2026-06-14 06:16:12 INFO conftest Launching URL: https://ecommerce-playground.lambdatest.io/index.php?route=common/home -2026-06-14 06:16:12 INFO conftest Driver started → browser=chrome | mode=headless ------------------------------- Captured log setup ------------------------------ -INFO conftest:conftest.py:62 Config → browser=chrome | mode=headless | url=https://ecommerce-playground.lambdatest.io/index.php?route=common/home -INFO conftest:conftest.py:119 Chrome Browser Launched Successfully -INFO conftest:conftest.py:175 Launching URL: https://ecommerce-playground.lambdatest.io/index.php?route=common/home -INFO conftest:conftest.py:184 Driver started → browser=chrome | mode=headless ------------------------------ Captured stderr call ----------------------------- -2026-06-14 06:16:12 INFO tests.test_login Invalid login test started -2026-06-14 06:16:13 INFO tests.test_login Clicked My Account -2026-06-14 06:16:13 INFO tests.test_login Entered invalid username: camera143@gmail.com ------------------------------- Captured log call ------------------------------- -INFO tests.test_login:test_login.py:50 Invalid login test started -INFO tests.test_login:test_login.py:54 Clicked My Account -INFO tests.test_login:test_login.py:58 Entered invalid username: camera143@gmail.com ---------------------------- Captured stderr teardown --------------------------- -2026-06-14 06:16:28 INFO conftest Quitting driver. ----------------------------- Captured log teardown ----------------------------- -INFO conftest:conftest.py:196 Quitting driver. -__ TestShopByCategory.test_category_navigation[Desktops & Monitors-Monitors] ___ - -self = -driver = (, ) -category = 'Desktops & Monitors', expected_title = 'Monitors' - - @pytest.mark.parametrize( - "category, expected_title", - [ - ("Desktops & Monitors", "Monitors"), - ("Web Cameras", "Web Cameras"), - ("Phone, Tablets & Ipod", "Tablets"), - ("Laptops & Notebooks", "Laptops"), - ] - ) - def test_category_navigation(self, driver, category, expected_title): - - drv, wait = driver - action = ShopByCategoryAction(drv) - action.launch_url("https://ecommerce-playground.lambdatest.io") -> action.click_shop_by_category() - -tests/test_shopbycategory.py:24: -_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ -actions/ShopbycategoryAction.py:33: in click_shop_by_category - element = self.wait.until( -_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ - -self = -method = ._predicate at 0x7f510fec58a0> -message = '' - - def until(self, method: Callable[[D], Literal[False] | T], message: str = "") -> T: - """Wait until the method returns a value that is not False. - - Calls the method provided with the driver as an argument until the - return value does not evaluate to ``False``. - - Args: - method: A callable object that takes a WebDriver instance as an - argument. - message: Optional message for TimeoutException. - - Returns: - The result of the last call to `method`. - - Raises: - TimeoutException: If 'method' does not return a truthy value within - the WebDriverWait object's timeout. - - Example: - >>> from selenium.webdriver.common.by import By - >>> from selenium.webdriver.support.ui import WebDriverWait - >>> from selenium.webdriver.support import expected_conditions as EC - >>> - >>> # Wait until an element is visible on the page - >>> wait = WebDriverWait(driver, 10) - >>> element = wait.until(EC.visibility_of_element_located((By.ID, "exampleId"))) - >>> print(element.text) - """ - screen = None - stacktrace = None - - end_time = time.monotonic() + self._timeout - while True: - try: - value = method(self._driver) - if value: - return value - except self._ignored_exceptions as exc: - screen = getattr(exc, "screen", None) - stacktrace = getattr(exc, "stacktrace", None) - if time.monotonic() > end_time: - break - time.sleep(self._poll) -> raise TimeoutException(message, screen, stacktrace) -E selenium.common.exceptions.TimeoutException: Message: - -../../../.local/lib/python3.12/site-packages/selenium/webdriver/support/wait.py:121: TimeoutException ----------------------------- Captured stderr setup ----------------------------- -2026-06-14 06:16:37 INFO conftest Config → browser=chrome | mode=headless | url=https://ecommerce-playground.lambdatest.io/index.php?route=common/home -2026-06-14 06:16:37 INFO conftest Chrome Browser Launched Successfully -2026-06-14 06:16:37 INFO conftest Launching URL: https://ecommerce-playground.lambdatest.io/index.php?route=common/home -2026-06-14 06:16:38 INFO conftest Driver started → browser=chrome | mode=headless ------------------------------- Captured log setup ------------------------------ -INFO conftest:conftest.py:62 Config → browser=chrome | mode=headless | url=https://ecommerce-playground.lambdatest.io/index.php?route=common/home -INFO conftest:conftest.py:119 Chrome Browser Launched Successfully -INFO conftest:conftest.py:175 Launching URL: https://ecommerce-playground.lambdatest.io/index.php?route=common/home -INFO conftest:conftest.py:184 Driver started → browser=chrome | mode=headless ------------------------------ Captured stderr call ----------------------------- -2026-06-14 06:16:38 INFO actions.ShopbycategoryAction Launching URL: https://ecommerce-playground.lambdatest.io -2026-06-14 06:16:38 INFO actions.ShopbycategoryAction Application launched successfully -2026-06-14 06:16:38 INFO actions.ShopbycategoryAction Clicking Shop By Category menu -2026-06-14 06:16:58 ERROR actions.ShopbycategoryAction Unable to click Shop By Category menu: Message: - ------------------------------- Captured log call ------------------------------- -INFO actions.ShopbycategoryAction:ShopbycategoryAction.py:19 Launching URL: https://ecommerce-playground.lambdatest.io -INFO actions.ShopbycategoryAction:ShopbycategoryAction.py:23 Application launched successfully -INFO actions.ShopbycategoryAction:ShopbycategoryAction.py:32 Clicking Shop By Category menu -ERROR actions.ShopbycategoryAction:ShopbycategoryAction.py:43 Unable to click Shop By Category menu: Message: ---------------------------- Captured stderr teardown --------------------------- -2026-06-14 06:16:58 INFO conftest Quitting driver. ----------------------------- Captured log teardown ----------------------------- -INFO conftest:conftest.py:196 Quitting driver. -_____ TestShopByCategory.test_category_navigation[Web Cameras-Web Cameras] _____ - -self = -driver = (, ) -category = 'Web Cameras', expected_title = 'Web Cameras' - - @pytest.mark.parametrize( - "category, expected_title", - [ - ("Desktops & Monitors", "Monitors"), - ("Web Cameras", "Web Cameras"), - ("Phone, Tablets & Ipod", "Tablets"), - ("Laptops & Notebooks", "Laptops"), - ] - ) - def test_category_navigation(self, driver, category, expected_title): - - drv, wait = driver - action = ShopByCategoryAction(drv) - action.launch_url("https://ecommerce-playground.lambdatest.io") -> action.click_shop_by_category() - -tests/test_shopbycategory.py:24: -_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ -actions/ShopbycategoryAction.py:33: in click_shop_by_category - element = self.wait.until( -_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ - -self = -method = ._predicate at 0x7f510fd2b9c0> -message = '' - - def until(self, method: Callable[[D], Literal[False] | T], message: str = "") -> T: - """Wait until the method returns a value that is not False. - - Calls the method provided with the driver as an argument until the - return value does not evaluate to ``False``. - - Args: - method: A callable object that takes a WebDriver instance as an - argument. - message: Optional message for TimeoutException. - - Returns: - The result of the last call to `method`. - - Raises: - TimeoutException: If 'method' does not return a truthy value within - the WebDriverWait object's timeout. - - Example: - >>> from selenium.webdriver.common.by import By - >>> from selenium.webdriver.support.ui import WebDriverWait - >>> from selenium.webdriver.support import expected_conditions as EC - >>> - >>> # Wait until an element is visible on the page - >>> wait = WebDriverWait(driver, 10) - >>> element = wait.until(EC.visibility_of_element_located((By.ID, "exampleId"))) - >>> print(element.text) - """ - screen = None - stacktrace = None - - end_time = time.monotonic() + self._timeout - while True: - try: - value = method(self._driver) - if value: - return value - except self._ignored_exceptions as exc: - screen = getattr(exc, "screen", None) - stacktrace = getattr(exc, "stacktrace", None) - if time.monotonic() > end_time: - break - time.sleep(self._poll) -> raise TimeoutException(message, screen, stacktrace) -E selenium.common.exceptions.TimeoutException: Message: - -../../../.local/lib/python3.12/site-packages/selenium/webdriver/support/wait.py:121: TimeoutException ----------------------------- Captured stderr setup ----------------------------- -2026-06-14 06:16:58 INFO conftest Config → browser=chrome | mode=headless | url=https://ecommerce-playground.lambdatest.io/index.php?route=common/home -2026-06-14 06:16:59 INFO conftest Chrome Browser Launched Successfully -2026-06-14 06:16:59 INFO conftest Launching URL: https://ecommerce-playground.lambdatest.io/index.php?route=common/home -2026-06-14 06:16:59 INFO conftest Driver started → browser=chrome | mode=headless ------------------------------- Captured log setup ------------------------------ -INFO conftest:conftest.py:62 Config → browser=chrome | mode=headless | url=https://ecommerce-playground.lambdatest.io/index.php?route=common/home -INFO conftest:conftest.py:119 Chrome Browser Launched Successfully -INFO conftest:conftest.py:175 Launching URL: https://ecommerce-playground.lambdatest.io/index.php?route=common/home -INFO conftest:conftest.py:184 Driver started → browser=chrome | mode=headless ------------------------------ Captured stderr call ----------------------------- -2026-06-14 06:16:59 INFO actions.ShopbycategoryAction Launching URL: https://ecommerce-playground.lambdatest.io -2026-06-14 06:17:00 INFO actions.ShopbycategoryAction Application launched successfully -2026-06-14 06:17:00 INFO actions.ShopbycategoryAction Clicking Shop By Category menu -2026-06-14 06:17:20 ERROR actions.ShopbycategoryAction Unable to click Shop By Category menu: Message: - ------------------------------- Captured log call ------------------------------- -INFO actions.ShopbycategoryAction:ShopbycategoryAction.py:19 Launching URL: https://ecommerce-playground.lambdatest.io -INFO actions.ShopbycategoryAction:ShopbycategoryAction.py:23 Application launched successfully -INFO actions.ShopbycategoryAction:ShopbycategoryAction.py:32 Clicking Shop By Category menu -ERROR actions.ShopbycategoryAction:ShopbycategoryAction.py:43 Unable to click Shop By Category menu: Message: ---------------------------- Captured stderr teardown --------------------------- -2026-06-14 06:17:20 INFO conftest Quitting driver. ----------------------------- Captured log teardown ----------------------------- -INFO conftest:conftest.py:196 Quitting driver. -__ TestShopByCategory.test_category_navigation[Phone, Tablets & Ipod-Tablets] __ - -self = -driver = (, ) -category = 'Phone, Tablets & Ipod', expected_title = 'Tablets' - - @pytest.mark.parametrize( - "category, expected_title", - [ - ("Desktops & Monitors", "Monitors"), - ("Web Cameras", "Web Cameras"), - ("Phone, Tablets & Ipod", "Tablets"), - ("Laptops & Notebooks", "Laptops"), - ] - ) - def test_category_navigation(self, driver, category, expected_title): - - drv, wait = driver - action = ShopByCategoryAction(drv) - action.launch_url("https://ecommerce-playground.lambdatest.io") -> action.click_shop_by_category() - -tests/test_shopbycategory.py:24: -_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ -actions/ShopbycategoryAction.py:33: in click_shop_by_category - element = self.wait.until( -_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ - -self = -method = ._predicate at 0x7f51152efc40> -message = '' - - def until(self, method: Callable[[D], Literal[False] | T], message: str = "") -> T: - """Wait until the method returns a value that is not False. - - Calls the method provided with the driver as an argument until the - return value does not evaluate to ``False``. - - Args: - method: A callable object that takes a WebDriver instance as an - argument. - message: Optional message for TimeoutException. - - Returns: - The result of the last call to `method`. - - Raises: - TimeoutException: If 'method' does not return a truthy value within - the WebDriverWait object's timeout. - - Example: - >>> from selenium.webdriver.common.by import By - >>> from selenium.webdriver.support.ui import WebDriverWait - >>> from selenium.webdriver.support import expected_conditions as EC - >>> - >>> # Wait until an element is visible on the page - >>> wait = WebDriverWait(driver, 10) - >>> element = wait.until(EC.visibility_of_element_located((By.ID, "exampleId"))) - >>> print(element.text) - """ - screen = None - stacktrace = None - - end_time = time.monotonic() + self._timeout - while True: - try: - value = method(self._driver) - if value: - return value - except self._ignored_exceptions as exc: - screen = getattr(exc, "screen", None) - stacktrace = getattr(exc, "stacktrace", None) - if time.monotonic() > end_time: - break - time.sleep(self._poll) -> raise TimeoutException(message, screen, stacktrace) -E selenium.common.exceptions.TimeoutException: Message: - -../../../.local/lib/python3.12/site-packages/selenium/webdriver/support/wait.py:121: TimeoutException ----------------------------- Captured stderr setup ----------------------------- -2026-06-14 06:17:20 INFO conftest Config → browser=chrome | mode=headless | url=https://ecommerce-playground.lambdatest.io/index.php?route=common/home -2026-06-14 06:17:20 INFO conftest Chrome Browser Launched Successfully -2026-06-14 06:17:20 INFO conftest Launching URL: https://ecommerce-playground.lambdatest.io/index.php?route=common/home -2026-06-14 06:17:21 INFO conftest Driver started → browser=chrome | mode=headless ------------------------------- Captured log setup ------------------------------ -INFO conftest:conftest.py:62 Config → browser=chrome | mode=headless | url=https://ecommerce-playground.lambdatest.io/index.php?route=common/home -INFO conftest:conftest.py:119 Chrome Browser Launched Successfully -INFO conftest:conftest.py:175 Launching URL: https://ecommerce-playground.lambdatest.io/index.php?route=common/home -INFO conftest:conftest.py:184 Driver started → browser=chrome | mode=headless ------------------------------ Captured stderr call ----------------------------- -2026-06-14 06:17:21 INFO actions.ShopbycategoryAction Launching URL: https://ecommerce-playground.lambdatest.io -2026-06-14 06:17:22 INFO actions.ShopbycategoryAction Application launched successfully -2026-06-14 06:17:22 INFO actions.ShopbycategoryAction Clicking Shop By Category menu -2026-06-14 06:17:42 ERROR actions.ShopbycategoryAction Unable to click Shop By Category menu: Message: - ------------------------------- Captured log call ------------------------------- -INFO actions.ShopbycategoryAction:ShopbycategoryAction.py:19 Launching URL: https://ecommerce-playground.lambdatest.io -INFO actions.ShopbycategoryAction:ShopbycategoryAction.py:23 Application launched successfully -INFO actions.ShopbycategoryAction:ShopbycategoryAction.py:32 Clicking Shop By Category menu -ERROR actions.ShopbycategoryAction:ShopbycategoryAction.py:43 Unable to click Shop By Category menu: Message: ---------------------------- Captured stderr teardown --------------------------- -2026-06-14 06:17:42 INFO conftest Quitting driver. ----------------------------- Captured log teardown ----------------------------- -INFO conftest:conftest.py:196 Quitting driver. -___ TestShopByCategory.test_category_navigation[Laptops & Notebooks-Laptops] ___ - -self = -driver = (, ) -category = 'Laptops & Notebooks', expected_title = 'Laptops' - - @pytest.mark.parametrize( - "category, expected_title", - [ - ("Desktops & Monitors", "Monitors"), - ("Web Cameras", "Web Cameras"), - ("Phone, Tablets & Ipod", "Tablets"), - ("Laptops & Notebooks", "Laptops"), - ] - ) - def test_category_navigation(self, driver, category, expected_title): - - drv, wait = driver - action = ShopByCategoryAction(drv) - action.launch_url("https://ecommerce-playground.lambdatest.io") -> action.click_shop_by_category() - -tests/test_shopbycategory.py:24: -_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ -actions/ShopbycategoryAction.py:33: in click_shop_by_category - element = self.wait.until( -_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ - -self = -method = ._predicate at 0x7f510fec5800> -message = '' - - def until(self, method: Callable[[D], Literal[False] | T], message: str = "") -> T: - """Wait until the method returns a value that is not False. - - Calls the method provided with the driver as an argument until the - return value does not evaluate to ``False``. - - Args: - method: A callable object that takes a WebDriver instance as an - argument. - message: Optional message for TimeoutException. - - Returns: - The result of the last call to `method`. - - Raises: - TimeoutException: If 'method' does not return a truthy value within - the WebDriverWait object's timeout. - - Example: - >>> from selenium.webdriver.common.by import By - >>> from selenium.webdriver.support.ui import WebDriverWait - >>> from selenium.webdriver.support import expected_conditions as EC - >>> - >>> # Wait until an element is visible on the page - >>> wait = WebDriverWait(driver, 10) - >>> element = wait.until(EC.visibility_of_element_located((By.ID, "exampleId"))) - >>> print(element.text) - """ - screen = None - stacktrace = None - - end_time = time.monotonic() + self._timeout - while True: - try: - value = method(self._driver) - if value: - return value - except self._ignored_exceptions as exc: - screen = getattr(exc, "screen", None) - stacktrace = getattr(exc, "stacktrace", None) - if time.monotonic() > end_time: - break - time.sleep(self._poll) -> raise TimeoutException(message, screen, stacktrace) -E selenium.common.exceptions.TimeoutException: Message: - -../../../.local/lib/python3.12/site-packages/selenium/webdriver/support/wait.py:121: TimeoutException ----------------------------- Captured stderr setup ----------------------------- -2026-06-14 06:17:42 INFO conftest Config → browser=chrome | mode=headless | url=https://ecommerce-playground.lambdatest.io/index.php?route=common/home -2026-06-14 06:17:42 INFO conftest Chrome Browser Launched Successfully -2026-06-14 06:17:42 INFO conftest Launching URL: https://ecommerce-playground.lambdatest.io/index.php?route=common/home -2026-06-14 06:17:43 INFO conftest Driver started → browser=chrome | mode=headless ------------------------------- Captured log setup ------------------------------ -INFO conftest:conftest.py:62 Config → browser=chrome | mode=headless | url=https://ecommerce-playground.lambdatest.io/index.php?route=common/home -INFO conftest:conftest.py:119 Chrome Browser Launched Successfully -INFO conftest:conftest.py:175 Launching URL: https://ecommerce-playground.lambdatest.io/index.php?route=common/home -INFO conftest:conftest.py:184 Driver started → browser=chrome | mode=headless ------------------------------ Captured stderr call ----------------------------- -2026-06-14 06:17:43 INFO actions.ShopbycategoryAction Launching URL: https://ecommerce-playground.lambdatest.io -2026-06-14 06:17:43 INFO actions.ShopbycategoryAction Application launched successfully -2026-06-14 06:17:43 INFO actions.ShopbycategoryAction Clicking Shop By Category menu -2026-06-14 06:18:04 ERROR actions.ShopbycategoryAction Unable to click Shop By Category menu: Message: - ------------------------------- Captured log call ------------------------------- -INFO actions.ShopbycategoryAction:ShopbycategoryAction.py:19 Launching URL: https://ecommerce-playground.lambdatest.io -INFO actions.ShopbycategoryAction:ShopbycategoryAction.py:23 Application launched successfully -INFO actions.ShopbycategoryAction:ShopbycategoryAction.py:32 Clicking Shop By Category menu -ERROR actions.ShopbycategoryAction:ShopbycategoryAction.py:43 Unable to click Shop By Category menu: Message: ---------------------------- Captured stderr teardown --------------------------- -2026-06-14 06:18:04 INFO conftest Quitting driver. ----------------------------- Captured log teardown ----------------------------- -INFO conftest:conftest.py:196 Quitting driver. -_________________________ test_add_product_via_search __________________________ - -self = - - def get_wishlist_success_message_generic(self): - """Return the wishlist success notification text, using a fallback locator if needed.""" - try: -> message = self.get_text(self.wishlist_page.SUCCESS_NOTIFICATION) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -actions/wishlist_actions.py:163: -_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ -actions/BaseAction.py:39: in get_text - return self.wait.until(ec.visibility_of_element_located(locator)).text - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ - -self = -method = ._predicate at 0x7f510fec4d60> -message = '' - - def until(self, method: Callable[[D], Literal[False] | T], message: str = "") -> T: - """Wait until the method returns a value that is not False. - - Calls the method provided with the driver as an argument until the - return value does not evaluate to ``False``. - - Args: - method: A callable object that takes a WebDriver instance as an - argument. - message: Optional message for TimeoutException. - - Returns: - The result of the last call to `method`. - - Raises: - TimeoutException: If 'method' does not return a truthy value within - the WebDriverWait object's timeout. - - Example: - >>> from selenium.webdriver.common.by import By - >>> from selenium.webdriver.support.ui import WebDriverWait - >>> from selenium.webdriver.support import expected_conditions as EC - >>> - >>> # Wait until an element is visible on the page - >>> wait = WebDriverWait(driver, 10) - >>> element = wait.until(EC.visibility_of_element_located((By.ID, "exampleId"))) - >>> print(element.text) - """ - screen = None - stacktrace = None - - end_time = time.monotonic() + self._timeout - while True: - try: - value = method(self._driver) - if value: - return value - except self._ignored_exceptions as exc: - screen = getattr(exc, "screen", None) - stacktrace = getattr(exc, "stacktrace", None) - if time.monotonic() > end_time: - break - time.sleep(self._poll) -> raise TimeoutException(message, screen, stacktrace) -E selenium.common.exceptions.TimeoutException: Message: -E Stacktrace: -E #0 0x55693447229a -E #1 0x556933e54449 -E #2 0x556933ea8baf -E #3 0x556933ea8e01 -E #4 0x556933ef3ae4 -E #5 0x556933ef0cbf -E #6 0x556933e9c132 -E #7 0x556933e9cf41 -E #8 0x556934438737 -E #9 0x556934436f39 -E #10 0x556934421c26 -E #11 0x556934437ada -E #12 0x556934409bd0 -E #13 0x55693445ec28 -E #14 0x55693445edc5 -E #15 0x556934470e1e -E #16 0x7f7d8909caa4 -E #17 0x7f7d89129c6c - -../../../.local/lib/python3.12/site-packages/selenium/webdriver/support/wait.py:121: TimeoutException - -During handling of the above exception, another exception occurred: - -setup = (, , ) - - @pytest.mark.Prasanna - def test_add_product_via_search(setup): - """Adding a product found via search shows it in the wishlist.""" - _, _, wishlist_actions = setup - - logger.info("Starting test: add product '%s' via search", PRODUCT_IPOD_SHUFFLE) - - wishlist_actions.search_for_product(PRODUCT_IPOD_SHUFFLE) - logger.info("Searched for product: %s", PRODUCT_IPOD_SHUFFLE) - - wishlist_actions.click_product_from_search_results(PRODUCT_IPOD_SHUFFLE) - logger.info("Opened product page for: %s", PRODUCT_IPOD_SHUFFLE) - - wishlist_actions.click_heart_button_on_product_page() - logger.info("Clicked wishlist (heart) button on product page") - -> _assert_success_message(wishlist_actions.get_wishlist_success_message_generic()) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -tests/test_wishlist.py:154: -_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ -actions/wishlist_actions.py:166: in get_wishlist_success_message_generic - message = self.get_text(self.wishlist_page.SUCCESS_NOTIFICATION_FALLBACK) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -actions/BaseAction.py:39: in get_text - return self.wait.until(ec.visibility_of_element_located(locator)).text - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ - -self = -method = ._predicate at 0x7f510fdd9120> -message = '' - - def until(self, method: Callable[[D], Literal[False] | T], message: str = "") -> T: - """Wait until the method returns a value that is not False. - - Calls the method provided with the driver as an argument until the - return value does not evaluate to ``False``. - - Args: - method: A callable object that takes a WebDriver instance as an - argument. - message: Optional message for TimeoutException. - - Returns: - The result of the last call to `method`. - - Raises: - TimeoutException: If 'method' does not return a truthy value within - the WebDriverWait object's timeout. - - Example: - >>> from selenium.webdriver.common.by import By - >>> from selenium.webdriver.support.ui import WebDriverWait - >>> from selenium.webdriver.support import expected_conditions as EC - >>> - >>> # Wait until an element is visible on the page - >>> wait = WebDriverWait(driver, 10) - >>> element = wait.until(EC.visibility_of_element_located((By.ID, "exampleId"))) - >>> print(element.text) - """ - screen = None - stacktrace = None - - end_time = time.monotonic() + self._timeout - while True: - try: - value = method(self._driver) - if value: - return value - except self._ignored_exceptions as exc: - screen = getattr(exc, "screen", None) - stacktrace = getattr(exc, "stacktrace", None) - if time.monotonic() > end_time: - break - time.sleep(self._poll) -> raise TimeoutException(message, screen, stacktrace) -E selenium.common.exceptions.TimeoutException: Message: -E Stacktrace: -E #0 0x55693447229a -E #1 0x556933e54449 -E #2 0x556933ea8baf -E #3 0x556933ea8e01 -E #4 0x556933ef3ae4 -E #5 0x556933ef0cbf -E #6 0x556933e9c132 -E #7 0x556933e9cf41 -E #8 0x556934438737 -E #9 0x556934436f39 -E #10 0x556934421c26 -E #11 0x556934437ada -E #12 0x556934409bd0 -E #13 0x55693445ec28 -E #14 0x55693445edc5 -E #15 0x556934470e1e -E #16 0x7f7d8909caa4 -E #17 0x7f7d89129c6c - -../../../.local/lib/python3.12/site-packages/selenium/webdriver/support/wait.py:121: TimeoutException ----------------------------- Captured stderr setup ----------------------------- -2026-06-14 06:18:28 INFO conftest Config → browser=chrome | mode=headless | url=https://ecommerce-playground.lambdatest.io/index.php?route=common/home -2026-06-14 06:18:28 INFO conftest Chrome Browser Launched Successfully -2026-06-14 06:18:28 INFO conftest Launching URL: https://ecommerce-playground.lambdatest.io/index.php?route=common/home -2026-06-14 06:18:29 INFO conftest Driver started → browser=chrome | mode=headless -2026-06-14 06:18:29 INFO tests.test_wishlist Landed on home page: https://ecommerce-playground.lambdatest.io/index.php?route=common/home -2026-06-14 06:18:29 INFO tests.test_wishlist Using credentials for user: testlogin@gmail.com -2026-06-14 06:18:29 INFO tests.test_wishlist Clicked 'My Account' link -2026-06-14 06:18:29 INFO tests.test_wishlist Submitted login credentials -2026-06-14 06:18:30 INFO tests.test_wishlist Login successful: True -2026-06-14 06:18:30 INFO actions.wishlist_actions Navigating to home page: https://ecommerce-playground.lambdatest.io/ -2026-06-14 06:18:30 INFO actions.wishlist_actions Home page loaded successfully -2026-06-14 06:18:30 INFO tests.test_wishlist Navigated back to home page: https://ecommerce-playground.lambdatest.io/ ------------------------------- Captured log setup ------------------------------ -INFO conftest:conftest.py:62 Config → browser=chrome | mode=headless | url=https://ecommerce-playground.lambdatest.io/index.php?route=common/home -INFO conftest:conftest.py:119 Chrome Browser Launched Successfully -INFO conftest:conftest.py:175 Launching URL: https://ecommerce-playground.lambdatest.io/index.php?route=common/home -INFO conftest:conftest.py:184 Driver started → browser=chrome | mode=headless -INFO tests.test_wishlist:test_wishlist.py:34 Landed on home page: https://ecommerce-playground.lambdatest.io/index.php?route=common/home -INFO tests.test_wishlist:test_wishlist.py:37 Using credentials for user: testlogin@gmail.com -INFO tests.test_wishlist:test_wishlist.py:41 Clicked 'My Account' link -INFO tests.test_wishlist:test_wishlist.py:45 Submitted login credentials -INFO tests.test_wishlist:test_wishlist.py:50 Login successful: True -INFO actions.wishlist_actions:wishlist_actions.py:37 Navigating to home page: https://ecommerce-playground.lambdatest.io/ -INFO actions.wishlist_actions:wishlist_actions.py:43 Home page loaded successfully -INFO tests.test_wishlist:test_wishlist.py:53 Navigated back to home page: https://ecommerce-playground.lambdatest.io/ ------------------------------ Captured stderr call ----------------------------- -2026-06-14 06:18:30 INFO tests.test_wishlist Starting test: add product 'iPod Shuffle' via search -2026-06-14 06:18:30 INFO actions.wishlist_actions Searched for product: iPod Shuffle -2026-06-14 06:18:30 INFO tests.test_wishlist Searched for product: iPod Shuffle -2026-06-14 06:18:31 INFO actions.wishlist_actions Opened product page for: iPod Shuffle -2026-06-14 06:18:31 INFO tests.test_wishlist Opened product page for: iPod Shuffle -2026-06-14 06:18:32 INFO actions.wishlist_actions Clicked wishlist (heart) button on product page -2026-06-14 06:18:32 INFO tests.test_wishlist Clicked wishlist (heart) button on product page -2026-06-14 06:18:47 WARNING actions.wishlist_actions Primary success notification not found, using fallback ------------------------------- Captured log call ------------------------------- -INFO tests.test_wishlist:test_wishlist.py:143 Starting test: add product 'iPod Shuffle' via search -INFO actions.wishlist_actions:wishlist_actions.py:133 Searched for product: iPod Shuffle -INFO tests.test_wishlist:test_wishlist.py:146 Searched for product: iPod Shuffle -INFO actions.wishlist_actions:wishlist_actions.py:143 Opened product page for: iPod Shuffle -INFO tests.test_wishlist:test_wishlist.py:149 Opened product page for: iPod Shuffle -INFO actions.wishlist_actions:wishlist_actions.py:154 Clicked wishlist (heart) button on product page -INFO tests.test_wishlist:test_wishlist.py:152 Clicked wishlist (heart) button on product page -WARNING actions.wishlist_actions:wishlist_actions.py:165 Primary success notification not found, using fallback ---------------------------- Captured stderr teardown --------------------------- -2026-06-14 06:19:02 INFO conftest Quitting driver. ----------------------------- Captured log teardown ----------------------------- -INFO conftest:conftest.py:196 Quitting driver. -=============================== warnings summary =============================== -tests/test_shopbycategory.py:7 - /home/runner/work/Pytest_Automation_Project/Pytest_Automation_Project/tests/test_shopbycategory.py:7: PytestUnknownMarkWarning: Unknown pytest.mark.ShopByCategory - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/stable/how-to/mark.html - @pytest.mark.ShopByCategory - --- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html -=========================== short test summary info ============================ -FAILED tests/test_checkout.py::TestCheckout::test_login_checkout - AttributeError: 'CheckoutPage' object has no attribute 'PRODUCT_PAGE_CHECKOUT_BTN1'. Did you mean: 'CART_PAGE_CHECKOUT_BTN'? -FAILED tests/test_checkout.py::TestCheckout::test_register_checkout - AttributeError: 'CheckoutAction' object has no attribute 'open_home_and_add_product' -FAILED tests/test_login.py::TestLogin::test_invalidLogin[camera143@gmail.com-testlogin] - selenium.common.exceptions.TimeoutException: Message: -Stacktrace: -#0 0x55a72439c29a -#1 0x55a723d7e449 -#2 0x55a723dd2baf -#3 0x55a723dd2e01 -#4 0x55a723e1dae4 -#5 0x55a723e1acbf -#6 0x55a723dc6132 -#7 0x55a723dc6f41 -#8 0x55a724362737 -#9 0x55a724360f39 -#10 0x55a72434bc26 -#11 0x55a724361ada -#12 0x55a724333bd0 -#13 0x55a724388c28 -#14 0x55a724388dc5 -#15 0x55a72439ae1e -#16 0x7f9355a9caa4 -#17 0x7f9355b29c6c -FAILED tests/test_shopbycategory.py::TestShopByCategory::test_category_navigation[Desktops & Monitors-Monitors] - selenium.common.exceptions.TimeoutException: Message: -FAILED tests/test_shopbycategory.py::TestShopByCategory::test_category_navigation[Web Cameras-Web Cameras] - selenium.common.exceptions.TimeoutException: Message: -FAILED tests/test_shopbycategory.py::TestShopByCategory::test_category_navigation[Phone, Tablets & Ipod-Tablets] - selenium.common.exceptions.TimeoutException: Message: -FAILED tests/test_shopbycategory.py::TestShopByCategory::test_category_navigation[Laptops & Notebooks-Laptops] - selenium.common.exceptions.TimeoutException: Message: -FAILED tests/test_wishlist.py::test_add_product_via_search - selenium.common.exceptions.TimeoutException: Message: -Stacktrace: -#0 0x55693447229a -#1 0x556933e54449 -#2 0x556933ea8baf -#3 0x556933ea8e01 -#4 0x556933ef3ae4 -#5 0x556933ef0cbf -#6 0x556933e9c132 -#7 0x556933e9cf41 -#8 0x556934438737 -#9 0x556934436f39 -#10 0x556934421c26 -#11 0x556934437ada -#12 0x556934409bd0 -#13 0x55693445ec28 -#14 0x55693445edc5 -#15 0x556934470e1e -#16 0x7f7d8909caa4 -#17 0x7f7d89129c6c -============= 8 failed, 20 passed, 1 warning in 294.61s (0:04:54) ============== -``` +## License + +This project is intended for learning and educational purposes. + +--- + +Built with Python, Selenium, and Pytest for modern UI test automation. + +Happy Testing! 🚀 diff --git a/actions/AddOnspageaction.py b/actions/AddOnspageaction.py new file mode 100644 index 0000000..55ef20d --- /dev/null +++ b/actions/AddOnspageaction.py @@ -0,0 +1,27 @@ +from pages.AddOnspage import AddOnspage +from actions.BaseAction import BaseAction +from selenium.webdriver import ActionChains +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC + + +class AddOnsaction(BaseAction): + def __init__(self,driver): + super().__init__(driver) + self.adp=AddOnspage(driver) + + def clickAddOns(self): + addons = self.wait.until(EC.visibility_of_element_located(self.adp.AddOns)) + ActionChains(self.driver).move_to_element(addons).perform() + def clickdesigns(self): + self.click(self.adp.designs) + def clickDrawerleft(self): + self.scroll_into_view(self.adp.Drawerleft) + self.click(self.adp.Drawerleft) + def leftpanel(self): + assert self.is_displayed(self.adp.topcategories) + def clickDrawerright(self): + self.scroll_into_view(self.adp.Drawerright) + self.click(self.adp.Drawerright) + def viewrightpanel(self): + assert self.is_displayed(self.adp.rightpanel) \ No newline at end of file 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/ProductCompareAction.py b/actions/ProductCompareAction.py index 0182dad..0645f87 100644 --- a/actions/ProductCompareAction.py +++ b/actions/ProductCompareAction.py @@ -1,5 +1,5 @@ from actions.BaseAction import BaseAction -from pages.ComaprePage import ComparePage +from pages.ComparePage import ComparePage class ProductCompareAction(BaseAction): diff --git a/actions/blogAction.py b/actions/blogAction.py index e69de29..7aac287 100644 --- a/actions/blogAction.py +++ b/actions/blogAction.py @@ -0,0 +1,57 @@ +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC + +from actions.BaseAction import BaseAction +from pages.blogPage import BlogPage + + +class BlogAction(BaseAction): + + def __init__(self, driver): + super().__init__(driver) + self.bp = BlogPage() + + def js_click(self, locator): + element = WebDriverWait(self.driver, 20).until(EC.presence_of_element_located(locator)) + self.driver.execute_script("arguments[0].scrollIntoView({block: 'center'});", element) + self.driver.execute_script("arguments[0].click();", element) + + def click_blog_menu(self): + self.driver.get("https://ecommerce-playground.lambdatest.io""/index.php?route=extension/maza/blog/home") + + def is_latest_article_visible(self): + return self.is_displayed(self.bp.LATEST_ARTICLE_SECTION) + + def click_business_category(self): + self.js_click(self.bp.BUSINESS_CATEGORY) + + def click_electronics_category(self): + self.js_click(self.bp.ELECTRONICS_CATEGORY) + + def click_technology_category(self): + self.js_click(self.bp.TECHNOLOGY_CATEGORY) + + def click_fashion_category(self): + self.js_click(self.bp.FASHION_CATEGORY) + + def open_first_article(self): + self.js_click(self.bp.FIRST_ARTICLE) + + WebDriverWait(self.driver, 20).until(EC.visibility_of_element_located(self.bp.ARTICLE_TITLE)) + def is_article_visible(self): + return self.is_displayed(self.bp.ARTICLE_TITLE) + + def post_comment(self, name, email, comment): + + self.scroll_into_view(self.bp.COMMENT_NAME) + self.send_keys(self.bp.COMMENT_NAME, name) + self.send_keys(self.bp.COMMENT_EMAIL, email) + self.send_keys(self.bp.COMMENT_TEXT, comment) + self.js_click(self.bp.POST_COMMENT_BUTTON) + + def is_comment_posted(self): + try: + self.wait.until(EC.visibility_of_element_located(self.bp.COMMENT_SUCCESS)) + return True + except Exception: + return False diff --git a/actions/checkoutAction.py b/actions/checkoutAction.py index 6738e3c..27e6591 100644 --- a/actions/checkoutAction.py +++ b/actions/checkoutAction.py @@ -16,10 +16,6 @@ def __init__(self, driver): self.hp=HomePage(driver) self.pp=ProductPage(driver) - # ========================= - # PRODUCT FLOW - # ========================= - def click_hp_product(self): self.click(self.hp.Hp_Product) @@ -32,9 +28,6 @@ def click_shopping_cart_from_popup(self): def click_checkout_from_cart_page(self): self.click(self.cp.CART_PAGE_CHECKOUT_BTN) - # ========================= - # LOGIN FLOW - # ========================= def click_Login_Radio(self): self.click(self.cp.LOGIN_RADIO) @@ -46,20 +39,11 @@ def login_from_checkout_page(self, email=None, password=None): self.send_keys(self.cp.LOGIN_EMAIL, email) self.send_keys(self.cp.LOGIN_PASSWORD, password) - login_btn = WebDriverWait(self.driver, 20).until( - EC.element_to_be_clickable(self.cp.LOGIN_BUTTON) - ) - + login_btn = WebDriverWait(self.driver, 20).until(EC.element_to_be_clickable(self.cp.LOGIN_BUTTON)) self.driver.execute_script("arguments[0].scrollIntoView(true);", login_btn) login_btn.click() - WebDriverWait(self.driver, 20).until( - EC.visibility_of_element_located(self.cp.FIRST_NAME_INPUT) - ) - - # ========================= - # BILLING DETAILS - # ========================= + WebDriverWait(self.driver, 20).until(EC.visibility_of_element_located(self.cp.FIRST_NAME_INPUT)) def enter_billing_details(self, data=None): @@ -82,54 +66,30 @@ def enter_billing_details(self, data=None): def click_same_billing_address(self): self.click(self.cp.SAME_BILLING_ADDRESS_LABEL) - # ========================= - # SHIPPING - # ========================= - def select_flat_rate(self): - flat_rate = WebDriverWait(self.driver, 20).until( - EC.element_to_be_clickable(self.cp.FLAT_RATE_LABEL) - ) - + flat_rate = WebDriverWait(self.driver, 20).until(EC.element_to_be_clickable(self.cp.FLAT_RATE_LABEL)) self.driver.execute_script("arguments[0].scrollIntoView(true);", flat_rate) self.driver.execute_script("arguments[0].click();", flat_rate) - # ========================= - # PAYMENT - # ========================= - def select_cash_on_delivery(self): - cod = WebDriverWait(self.driver, 20).until( - EC.element_to_be_clickable(self.cp.COD_LABEL) - ) - + cod = WebDriverWait(self.driver, 20).until(EC.element_to_be_clickable(self.cp.COD_LABEL)) self.driver.execute_script("arguments[0].scrollIntoView(true);", cod) self.driver.execute_script("arguments[0].click();", cod) def click_terms_and_conditions(self): - terms = WebDriverWait(self.driver, 20).until( - EC.element_to_be_clickable(self.cp.TERMS_LABEL) - ) - + terms = WebDriverWait(self.driver, 20).until(EC.element_to_be_clickable(self.cp.TERMS_LABEL)) self.driver.execute_script("arguments[0].scrollIntoView(true);", terms) self.driver.execute_script("arguments[0].click();", terms) def continue_checkout(self): - btn = WebDriverWait(self.driver, 20).until( - EC.element_to_be_clickable(self.cp.CONTINUE_CHECKOUT_BTN) - ) - + btn = WebDriverWait(self.driver, 20).until(EC.element_to_be_clickable(self.cp.CONTINUE_CHECKOUT_BTN)) self.driver.execute_script("arguments[0].scrollIntoView(true);", btn) self.driver.execute_script("arguments[0].click();", btn) - # ========================= - # REGISTER FLOW - # ========================= - def select_register_account(self): self.click(self.cp.REGISTER_ACCOUNT_RADIO) @@ -144,28 +104,18 @@ def enter_registration_details(self, data): def agree_to_privacy_policy(self): - privacy = WebDriverWait(self.driver, 20).until( - EC.element_to_be_clickable(self.cp.PRIVACY_LABEL) - ) - + privacy = WebDriverWait(self.driver, 20).until(EC.element_to_be_clickable(self.cp.PRIVACY_LABEL)) self.driver.execute_script("arguments[0].scrollIntoView(true);", privacy) self.driver.execute_script("arguments[0].click();", privacy) def agree_to_account_privacy_policy(self): - privacy = WebDriverWait(self.driver, 20).until( - EC.element_to_be_clickable(self.cp.ACCOUNT_PRIVACY_LABEL) - ) - + privacy = WebDriverWait(self.driver, 20).until(EC.element_to_be_clickable(self.cp.ACCOUNT_PRIVACY_LABEL)) self.driver.execute_script("arguments[0].scrollIntoView(true);", privacy) self.driver.execute_script("arguments[0].click();", privacy) - # ========================= - # VALIDATIONS - # ========================= def clickContinueCheckout(self): self.click(self.cp.CONTINUE_CHECKOUT_BTN) def is_order_placed_successfully(self): return self.is_displayed(self.cp.ORDER_CONFIRMATION_MSG) - def is_empty_cart_message_displayed(self): return self.is_displayed(self.cp.EMPTY_CART_MESSAGE) \ 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/DataProvider.xlsx b/data_provider/DataProvider.xlsx index 25f8a3a21d274722c7add8eb0e16330730aae55f..47223c967a8736ac08cd561988fbb58bbc7aa1ab 100644 GIT binary patch delta 7008 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 XKoNUTN_;W^89)gW0Kofe#sd5g?*zCC delta 4818 zcmZu#XIK+kw@&DW79bD^y%#B=ND=7>(ve;SLJ^Q&l+Z&*rIR4N2n3K4O6a{w5u_=_ z&_u8zq9{e`<(zZRJ?HzLyXVKeGi&WN&+J+2-S3*OkAdi5W@9~K5(t0{Kmh;%xB(US zNlfB_06;ByBQp;X%6!~s5DxCK{Gy`SYcZ?}HD`?0$aYR5IRh}z4)ZjMO{;F~DoPrg zGbVv=z0VYC{V2xp+n}V*ppFa@9Mjy1@LdRZtNl=*0S!*=sF->nxO=GQ56t%x4m zA0Agr-wExHs}XZ8<=Qhcak5v%!XI#m?k3w+L4LU9+^XMuhRTcblFO%+LO$s?iS?kG z-RbVPda0{x5AE(Yk&?3Do307GW-fp^#EvsI8Y{FN)9MVRj0%wN>{&mb5k3a!Is;YB zbB_@@l2lO-Q=X>VDiC9W**+7@Uu4~8W*XU~wp|}E-u)WXf;)M;PM4feUQg6h^SEc? zrf}b#oYaE+3e<^Os<(mHZ5v@{xz5AyqeDWQH4j|5Tly%%%To{HKehLO*^$uF&2#np zH%`$s??g#hiYpzmC<5O)af#{m2aalxC=K7K0)(m-G@S~8_0YE3b$ru;>g_=4=_UK7S_zb2E; zarnI6r&(Xc z{P^dM4BktbTL2m%@$eHShyuT!tLHNrJ0|c!s^*m8kX!MfFdL-X7w)3S^^|A&o{1aBO@zyeOyG@>A3`gLoBE-y+`IuVNS+#g;Jihh7Et7A zrMjaXzhQ$i85-4vr4yw0*R#x%l%^OTE_J-WxTYT1uE@nrL9X?JzO(k@>n+2wB#1%g z=e`AY9Ka-Hl$9@6{D+RQS#z8TTVe|4sY8j|o2zU)S`t zo#j4hmjS;WmX$!mV-fcY;5S*(Dwa#?l|pd=_H1Vc$ki(YI=oF#n8S~sqb)z*%oIl8 zfy?$m5=X6(voUtpyU4kvTMRPv!YMp1LOZ-^IW=APAivw8{SGu?DF4Q(Xi0AQxR+To z>9FM!6oOI#!_v5cyq{Y-rnO~D5DvYG3@ACFQ^v8|&+Z!@wTdwYx8Zr0J&mhc(ix1$ zl*Ba2lvX-@t^)UrY9*8=9#WqsT1a;&;A10^%|EAiD&}-Ue9sEoK}Py9foR)ark2^d zawD%8hWzgm@SHAnPsXO6oMd>}NM=C_We z-O88fl`i&>?oMD4e->4m0Zt1I26qO-G<}vhk9<_9!jWu-r}tjx8&=3=WHm?J7PuJQ zF5O|&IV5Wp4Xl1ySn>7BVAuN^R+}Mqug{_V3^>10!gg)K`XvEgTo42Prbh5z+(^Ud zGR|~q3M0iggM@)7jDg1x92|cbzozEyFcCyACzgS~67OYu2pHtJ0NYCC>DaFK4q{V? z)mE8(Nu*=fPGn-*mQfGHP5?MLr>Xqo13KvC0HqRi7u8a7^N+m2-*0R;;ighh#y zpu`}XF7IPQ3K$YrI0r~wM4tz1DJKMP#1AXyxH~fMx@N{Z(yzP9QVw~A!?)ud7nE}< zt%GuKgWYl?-SEk-3cOr-YfK+rpI}CO04}vPdOr+++ zAqj_B$23n42Xa(%xDYbb3@u5z=JOK-NdYV39KzIHv;Ebus5q)zYfWDNMg`^K3+D@g zUX=w#0;PAk2faQ!ly}xIYr-}EZL!J%UNJfOuECVZ{6i8I%GljkX`Jt3=Zq>KgFIX^ z0t46gG#6kd5mQsP{X?=4QXSKF$3x$y4<(mt>HFq`dNCbMj2e3)^ZTvnDa z5gQg+RB>_cQDGBMmn>3C>>}kJX?LC}dS#RM^Mf-v$10=dCDeouv4qfWM5a5ushiU4 z+;GV$?!g4MtZ$?Xr0nIk=@NF9H;1fdGQsr9T<{5xrde>6gqKiG}R|5YSy>I$};r5nO6B=onM#8T56XJa<`Y z0NJY%fdrP(t+Uj8Ssy}%zTnt9HRb3w3fUOEqK-6NH{=`Yj?iJji>9<(=kjBSFI#T< z<~2uw@ZBaGSY0A#rou8?wMJa0b!tk?+NgY6spBU}jlu~G_`xOx2D&6~slKaXs152e zthWpn1B0Ra6IY`pVtd2*{piariO7Aak_b(8M=yR^$dZ|temYbdzC}CY$L?-BALK*f zh?09TcwQ97e|kt#+q4}e48nfxAjWZT7R#JnZixRb*1u|%VTP4M>oN(64oT1h>?|nR zztYgw_c9Gf*9CVJts;%sDDIh(nCaYVR@To8iU^wIwi?KJGlk7NUPV$-t`d|qv)Yf2 zPl|sYMsJCihg;Q`g-hFf{Cywd{bXX6XVS6^4%&PEQXsG1@~2=PfBZ1R zfi)fRU0tYJH2CN8p9PyS6GVfe=mv`S50H6@DPp~$xGFmi*K#vt9ji?nDg9<6Pq zW>*P$Hhlh7CKM4xpjOp{*yASV@K1TFdG&a;-T z``uYX5?4>C@oD_-AkY+0)w1L=y;{i7K)491q}tf2XQa&Z=UY)~kV@Tc0Q7h1Xs7 zhU6Cg$fWkGp25CN{km8~(ynVB<~s-9WdL;$cUt$mwUmez*Nv?+_qeb1q^F5nD7B`_ z-VuFqzC6sMbz@9_5%}3=%5V%OhOekRX~^S6`7Vv%sP>RRqSlbyXNJ2yYiAH@#O(Hw z$ahK=SPE|0TOYqMsps}nz`0#Bg6oU34jFFXc7?3e%A9MJ13M&kFTKsDhkY~su3`|S zLbJkxX2@e}Bg0=N6YqsN+rOpkqp)96_Luj%%~GN?(`KZB2o64hSb_WBOaH8w8 zH{XkGr3^Lmq-RkzjZh-y9Rh3UU*fC!?!DYv*U3yN-J!s-=>AI21Pc_tdOReXE)$Ix z!#3^b-q}uU@e`tlJ=jKRy2Z7cw)@6Dj&WeV&nso9qufnBP${@0Cb0Q~nYM|Z9i%sf zI)+|-vS_#m#V<=0k&%qjgX-`R3W7k?ZKFXrB!1oClvZzDd)WY-?@hu_!XQcJ3h%wP zL(0_VbFZequWcEjF4NnhROv9?aZiQ-?KmVQxL_aC?a*yS(_<%J3ux7Ue$B}9$be0N+F6u%lZ^!O-OZ_TYHn$DVrFfOkI63z6cWVg+4-TiTSH^7 ziYp(H`KgWppoosGC!4TOmh5!w87B9~zLtS18Jnjh{bK zC=A&gueFxo39nehPj)m0${mMwGI4Kp)*1Lxx=g0JHW(ZV~DS? zl#i=<7aeVMEX*zyIzhB&uM*+kH$~upi~=s`i4$3mehFYP@yJ@vB08g}^6`k@Dw*ip zqD&wV?%Va=OF=kBjAy*T2}Gd>e3s#0&0W%CTB2RqEEzZ`)(I?s#e_>OWyyb9v?KGO zak=hX^?d!VuDVNyzj%)rlDw?)eYzc|2wLWCY>+H3^e2W7fi)#Cv4p%?o9@lz({#*& zPpNC!P;BL)&W9J)=HHejq?g1~vj{uaW@E^#)%ge6MwtXVer7!kM*Sjv5Q5`hx!zIV zuN9hGK?zEvu!zo^FXLum%QiT5Fn#Yb3JqFr)j~}9d?C0C^PKae=H|NI?&Yko^D%2x zY9!oeO%JPgo-oy_)Naif><6WUi0cEi(x?vrrM3hccTRcFcTGT+LZg`TSkErLaU;5t zq48V1-AuAP7J{ras0i;bV5N(VQ%@nF69Pi2ftRQx+^efUDup8aMZQvzf!PXpd>+Vv zdp~+^Y_hz2j%jye4kl;Y1bO@3geW>T=LMt=mu=;HD??V_IE3viPkpW(mxjQQa}F!k z11TocGww%9Cr7;625v4%NY5Q#5cJoyt_7|--rFB;kw&oTO~?-2PG?>-@gun8n*unnYDaxYKRvfr$JhyQ-=R1~QrnTqBp8x`D_)mt zP518y005F`(wn@R{}dAd0R9h$OlbhX#n;(5z}GKO!qLy~4=HD?M@sgG6XfzhUan4p I>>pSE1HBW6*#H0l diff --git a/data_provider/SearchProduct.xlsx b/data_provider/SearchProduct.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..4150cfca90cc36e252f6e4dade3c84117138c3bc GIT binary patch literal 6111 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 L5rT`!1_1a!0%1Ml literal 0 HcmV?d00001 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/data_provider/~$DataProvider.xlsx b/data_provider/~$DataProvider.xlsx deleted file mode 100644 index b8a00191b149abdc7656b360fbcf1a31db6d66ee..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 165 fcmZQ|^>OwOQ6LqtGPpAMFgP>#GlVcG&>R8)__7Z< diff --git a/pages/AddOnspage.py b/pages/AddOnspage.py new file mode 100644 index 0000000..61dae94 --- /dev/null +++ b/pages/AddOnspage.py @@ -0,0 +1,42 @@ +from selenium.webdriver.common.by import By +from pages.BasePage import BasePage + + +class AddOnspage(BasePage): + def __init__(self, driver): + super().__init__(driver) + + AddOns=By.XPATH,"//span[normalize-space()='AddOns']" + designs=By.XPATH,"//span[normalize-space()='Designs']" + Drawerleft=By.XPATH,"//div[@id='entry_215006']/child::a" + topcategories=By.XPATH,"//div[@id='mz-component-1626147655']/child::h5" + Drawerright=By.XPATH,"//div[@id='entry_215007']/child::a" + rightpanel=By.XPATH,"//div[@id='entry_215089']" + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pages/ComaprePage.py b/pages/ComparePage.py similarity index 100% rename from pages/ComaprePage.py rename to pages/ComparePage.py diff --git a/pages/checkoutPage.py b/pages/checkoutPage.py index 1463501..c8e9630 100644 --- a/pages/checkoutPage.py +++ b/pages/checkoutPage.py @@ -2,19 +2,16 @@ class CheckoutPage: - # -------- Product -------- HP_PRODUCT_IMAGE = (By.XPATH, "//img[@title='HP LP3065']") ADD_TO_CART_BTN = (By.XPATH, "//button[contains(@id,'button-cart')]") SHOPPING_CART_POPUP_LINK = (By.XPATH, "//a[contains(text(),'shopping cart')]") CART_PAGE_CHECKOUT_BTN = (By.XPATH, "//a[text()='Checkout']") - # -------- Login -------- LOGIN_RADIO = (By.XPATH, "//label[@for='input-account-login']") LOGIN_EMAIL = (By.ID, "input-login-email") LOGIN_PASSWORD = (By.ID, "input-login-password") LOGIN_BUTTON = (By.XPATH, "//button[contains(text(),'Login')]") - # -------- Register -------- REGISTER_ACCOUNT_RADIO = (By.XPATH, "//label[@for='input-account-register']") PRIVACY_LABEL = (By.XPATH, "//label[@for='input-agree']") @@ -25,23 +22,19 @@ class CheckoutPage: REG_PASSWORD_INPUT = (By.ID, "input-payment-password") REG_CONFIRM_PASSWORD_INPUT = (By.ID, "input-payment-confirm") - # -------- Billing -------- FIRST_NAME_INPUT = (By.ID, "input-payment-firstname") LAST_NAME_INPUT = (By.ID, "input-payment-lastname") COMPANY_INPUT = (By.ID, "input-payment-company") ADDRESS1_INPUT = (By.ID, "input-payment-address-1") CITY_INPUT = (By.ID, "input-payment-city") POSTCODE_INPUT = (By.ID, "input-payment-postcode") - SAME_BILLING_ADDRESS_LABEL = (By.XPATH, "//label[@for='input-shipping-address-same']") - # -------- Shipping / Payment -------- FLAT_RATE_LABEL = (By.XPATH, "//label[@for='input-shipping-method-flat.flat']") COD_LABEL = (By.XPATH, "//label[@for='input-payment-method-cod']") TERMS_LABEL = (By.XPATH, "//label[@for='input-agree']") CONTINUE_CHECKOUT_BTN = (By.XPATH, "//button[@id='button-save']") ACCOUNT_PRIVACY_LABEL = (By.XPATH, "//label[@for='input-account-agree']") - # -------- Assertions -------- ORDER_CONFIRMATION_MSG = (By.XPATH, "//h1[contains(text(),'Your order has been placed')]") EMPTY_CART_MESSAGE = (By.XPATH, "//div[@id='content']//p") \ 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_addons.py b/tests/test_addons.py new file mode 100644 index 0000000..8cf0237 --- /dev/null +++ b/tests/test_addons.py @@ -0,0 +1,43 @@ +from actions.AddOnspageaction import AddOnsaction +import pytest +from utils import loggerCreator +from utils.configReader import ConfigReader + + +logger = loggerCreator.get_logger(__name__) + +@pytest.mark.Jothika +class TestAddOns: + + def test_addonsdrawerleft(self,driver): + drv, wait = driver + self.adpa=AddOnsaction(drv) + + url=ConfigReader.get_url() + logger.info("Application launched successfully") + self.adpa.clickAddOns() + logger.info("Moved to Addons") + self.adpa.clickdesigns() + logger.info("Clicked on designs") + self.adpa.clickDrawerleft() + logger.info("Selected drawerleft") + self.adpa.leftpanel() + logger.info("left panel is displayed") + + def test_drawerRight(self,driver): + drv, wait = driver + self.adpa=AddOnsaction(drv) + + url=ConfigReader.get_url() + logger.info("Application launched successfully") + self.adpa.clickAddOns() + logger.info("Moved to Addons") + self.adpa.clickdesigns() + logger.info("Clicked on designs") + self.adpa.clickDrawerright() + logger.info("Clicked on drawer right") + self.adpa.viewrightpanel() + logger.info("Right panel appeared") + + + diff --git a/tests/test_addreview.py b/tests/test_addreview.py index 2c515d3..d45eb27 100644 --- a/tests/test_addreview.py +++ b/tests/test_addreview.py @@ -63,5 +63,94 @@ def test_withrating(self,driver,rating,expectedMessage): self.arpa.warningmsg(expectedMessage) logger.info("Invalid review submitted successfully") + @pytest.mark.parametrize( + "rating,name,feedback,expectedMessage", + get_data( + "data_provider/DataProvider.xlsx", + "AddReview-withoutname", + ), + ) + + def test_withoutname(self,driver,rating,name,feedback,expectedMessage): + drv, wait = driver + self.arpa=AddReviewpageaction(drv) + url=ConfigReader.get_url() + logger.info("Application launch is successful") + self.arpa.selectproduct() + logger.info("Selected the product") + self.arpa.moveto_review() + logger.info("Found review page") + self.arpa.selectrating(rating) + logger.info("Selected Rating") + self.arpa.enterName(name) + logger.info("Entered empty name") + self.arpa.enterfeedback(feedback) + logger.info("Entered feedback") + self.arpa.clicksubmit() + logger.info("Submitted review") + self.arpa.warningmsg(expectedMessage) + logger.info("Invalid review withoutname submitted successfully") + + + @pytest.mark.parametrize( + "rating,name,expectedMessage", + get_data( + "data_provider/DataProvider.xlsx", + "AddReview-withoutfeedback", + ), + ) + + def test_addingreviewwithoutfeedback(self,driver,rating,name,expectedMessage): + drv, wait = driver + self.arpa=AddReviewpageaction(drv) + url=ConfigReader.get_url() + logger.info("Application launch is successful") + self.arpa.selectproduct() + logger.info("Selected the product") + self.arpa.moveto_review() + logger.info("Found review page") + self.arpa.selectrating(rating) + logger.info("Selected Rating") + self.arpa.enterName(name) + logger.info("Entered name") + + self.arpa.clicksubmit() + logger.info("Submitted review") + self.arpa.warningmsg(expectedMessage) + logger.info("Invalid review withoutfeedback submitted successfully") + + + @pytest.mark.parametrize( + "name,feedback,expectedMessage", + get_data( + "data_provider/DataProvider.xlsx", + "AddReview-withoutrating", + ), + ) + + def test_reviewwithoutrating(self,driver,name,feedback,expectedMessage): + drv, wait = driver + self.arpa=AddReviewpageaction(drv) + url=ConfigReader.get_url() + logger.info("Application launch is successful") + self.arpa.selectproduct() + logger.info("Selected the product") + self.arpa.moveto_review() + logger.info("Found review page") + + self.arpa.enterName(name) + logger.info("Entered empty name") + self.arpa.enterfeedback(feedback) + logger.info("Entered feedback") + self.arpa.clicksubmit() + logger.info("Submitted review") + self.arpa.warningmsg(expectedMessage) + logger.info("Invalid review withoutrating submitted successfully") + + + + + + diff --git a/tests/test_blog.py b/tests/test_blog.py index e69de29..fbf2fa1 100644 --- a/tests/test_blog.py +++ b/tests/test_blog.py @@ -0,0 +1,62 @@ +import pytest + +from actions.blogAction import BlogAction +from utils.configReader import ConfigReader + + +@pytest.mark.Samiha +class TestBlog: + + def test_blog_menu(self, driver): + + drv, wait = driver + action = BlogAction(drv) + drv.get(ConfigReader.get_url()) + action.click_blog_menu() + + assert action.is_latest_article_visible() + + def test_first_article(self, driver): + + drv, wait = driver + action = BlogAction(drv) + drv.get(ConfigReader.get_url()) + action.click_blog_menu() + action.open_first_article() + + assert action.is_article_visible() + + @pytest.mark.parametrize( + "category", + [ + "business", + "electronics", + "technology", + "fashion" + ] + ) + def test_blog_comment(self, driver, category): + + drv, wait = driver + action = BlogAction(drv) + + drv.get(ConfigReader.get_url()) + action.click_blog_menu() + + if category == "business": + action.click_business_category() + elif category == "electronics": + action.click_electronics_category() + elif category == "technology": + action.click_technology_category() + else: + action.click_fashion_category() + action.open_first_article() + + action.post_comment( + name="Samiha", + email="samiha@test.com", + comment=f"Automation comment for {category}" + ) + + assert action.is_comment_posted() diff --git a/tests/test_checkout.py b/tests/test_checkout.py index b30c545..6d23631 100644 --- a/tests/test_checkout.py +++ b/tests/test_checkout.py @@ -1,18 +1,13 @@ import time - import pytest from actions.checkoutAction import CheckoutAction from utils.configReader import ConfigReader from utils.excelReader import get_registration_data - @pytest.mark.Samiha class TestCheckout: - # ========================= - # 1. LOGIN CHECKOUT - # ========================= def test_login_checkout(self, driver): drv, wait = driver @@ -25,14 +20,9 @@ def test_login_checkout(self, driver): action.click_shopping_cart_from_popup() action.click_checkout_from_cart_page() - # Login step action.click_Login_Radio() - action.login_from_checkout_page( - ConfigReader.get("credentials", "email"), - ConfigReader.get("credentials", "password") - ) - - # Billing details + action.login_from_checkout_page(ConfigReader.get("credentials", "email"),ConfigReader.get("credentials", "password")) + action.enter_billing_details({ "firstname": ConfigReader.get("billing", "first_name"), "lastname": ConfigReader.get("billing", "last_name"), @@ -41,16 +31,11 @@ def test_login_checkout(self, driver): "city": ConfigReader.get("billing", "city"), "postcode": ConfigReader.get("billing", "postcode"), }) - - # Payment + confirm + action.agree_to_privacy_policy() action.clickContinueCheckout() print(action.is_order_placed_successfully()) - - # ========================= - # 2. REGISTER CHECKOUT - # ========================= def test_register_checkout(self, driver): drv, wait = driver @@ -64,11 +49,7 @@ def test_register_checkout(self, driver): action.select_register_account() - - data = get_registration_data( - "E:\Pytest_Automation\data_provider\DataProvider.xlsx", - "Registration" - ) + data = get_registration_data("D:\Pytest_Automation_Project\data_provider\DataProvider.xlsx", "Registration") action.enter_registration_details(data) action.agree_to_privacy_policy() @@ -76,17 +57,9 @@ def test_register_checkout(self, driver): action.clickContinueCheckout() print(action.is_order_placed_successfully()) - - # ========================= - # 3. EMPTY CART CHECKOUT - # ========================= def test_empty_cart_checkout(self, driver): - drv, wait = driver action = CheckoutAction(drv) - drv.get( - "https://ecommerce-playground.lambdatest.io/index.php?route=checkout/cart" - ) - + drv.get("https://ecommerce-playground.lambdatest.io/index.php?route=checkout/cart") print(action.is_empty_cart_message_displayed()) \ No newline at end of file diff --git a/tests/test_editAccInfo.py b/tests/test_editAccInfo.py index a0a0eb6..7f67447 100644 --- a/tests/test_editAccInfo.py +++ b/tests/test_editAccInfo.py @@ -5,10 +5,14 @@ from actions.launch_actions import LaunchActions from actions.loginpageaction import LoginPageAction from utils.configReader import ConfigReader +from utils.loggerCreator import get_logger + +logger = get_logger(__name__) @pytest.mark.Rishwanth class TestEditAccountInfo: + @pytest.mark.parametrize( "username,password", get_data( @@ -17,13 +21,32 @@ class TestEditAccountInfo: ), ) def test_EditAccInfo(self, driver, username, password): + logger.info("Starting test: Edit Account Information") + drv, wait = driver + logger.info("Driver initialized successfully") + telephone = ConfigReader.get_register_data("telephone") + logger.info(f"Telephone value fetched from config: {telephone}") + hp = HomePageAction(drv) + logger.info("HomePageAction object created") + hp.click_myAcc() + logger.info("Clicked on My Account") lp = LoginPageAction(drv) + logger.info("LoginPageAction object created") + lp.enter_login_credentials(username, password) + logger.info(f"Entered login credentials for user: {username}") apa = AccountPageAction(drv) - assert apa.click_editAccount_info(telephone) is True + logger.info("AccountPageAction object created") + + result = apa.click_editAccount_info(telephone) + logger.info(f"Edit account information result: {result}") + + assert result is True + + logger.info("Test passed: Edit Account Information updated successfully") 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_subscribeNewsLetter.py b/tests/test_subscribeNewsLetter.py index 616b66e..d3627e3 100644 --- a/tests/test_subscribeNewsLetter.py +++ b/tests/test_subscribeNewsLetter.py @@ -6,9 +6,12 @@ from utils.excelReader import get_data from utils.loggerCreator import get_logger +logger = get_logger(__name__) + @pytest.mark.Rishwanth class TestSubscribeNewsLetter: + @pytest.mark.parametrize( "username,password", get_data( @@ -17,16 +20,38 @@ class TestSubscribeNewsLetter: ), ) def test_subscribe_newsLetter(self, driver, username, password): + logger.info("Starting test: Subscribe Newsletter") + drv, wait = driver + logger.info("Driver initialized successfully") + hp = HomePageAction(drv) + logger.info("HomePageAction object created") + hp.click_myAcc() + logger.info("Clicked on My Account") lp = LoginPageAction(drv) + logger.info("LoginPageAction object created") + lp.enter_login_credentials(username, password) + logger.info(f"Entered login credentials for user: {username}") apa = AccountPageAction(drv) + logger.info("AccountPageAction object created") + apa.click_subscribe_newsLetter() + logger.info("Clicked on Subscribe Newsletter option") nlpa = NewsLetterPageAction(drv) + logger.info("NewsLetterPageAction object created") + nlpa.click_yes_on_subscribe_rb() - assert apa.succes_msg_update_newsLetter() is True + logger.info("Selected Yes radio button for newsletter subscription") + + result = apa.succes_msg_update_newsLetter() + logger.info(f"Newsletter subscription update result: {result}") + + assert result is True + + logger.info("Test passed: Newsletter subscription updated successfully") 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