Skip to content

Commit 81b9b4b

Browse files
committed
refactor of all the framework
1 parent cf5a22e commit 81b9b4b

22 files changed

+534
-1457
lines changed

.github/workflows/main.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ jobs:
4646
run: |
4747
mkdir -p reports
4848
pytest test/ --html=reports/report.html --self-contained-html
49-
- name: Upload HTML test report
49+
- name: Upload HTML tests report
5050
uses: actions/upload-artifact@v4
5151
with:
5252
name: html-report

.gitignore

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Virtual Environment
2+
.venv/
3+
venv/
4+
ENV/
5+
6+
# Python cache
7+
__pycache__/
8+
*.py[cod]
9+
*$py.class
10+
11+
# Coverage reports
12+
.coverage
13+
coverage.xml
14+
htmlcov/
15+
.coverage.*
16+
coverage/
17+
18+
# Test reports and artifacts
19+
reports/
20+
allure-results/
21+
allure-report/
22+
screenshots/
23+
junit/
24+
pytest_report/
25+
26+
# PyTest
27+
.pytest_cache/
28+
29+
# IDEs and editors
30+
.idea/
31+
.vscode/
32+
*.swp
33+
*.swo
34+
35+
# OS generated files
36+
.DS_Store
37+
Thumbs.db
38+
39+
# Logs
40+
*.log
41+
logs/
42+
43+
# Temporary files
44+
*.tmp
45+
*.temp
46+
47+
# Selenium / WebDriver specific
48+
geckodriver.log
49+
msedgedriver.log
50+
chromedriver.log

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ Demonstrates key skills companies seek: stable Selenium automation, beautiful st
2727
Automated-testing-project/
2828
├── config/ # Credentials & locators per page
2929
├── pages/ # POM core (login, inventory, cart, checkout)
30-
├── test/ # Test suites (login, inventory, cart/E2E)
30+
├── tests/ # Test suites (login, inventory, cart/E2E)
3131
├── reports/ # Pytest-HTML reports
3232
├── allure-results/ # Raw Allure data
3333
├── allure-report/ # Generated Allure dashboard
@@ -45,7 +45,7 @@ pip install -r requirements.txt
4545

4646
### Run Tests:
4747
```bash
48-
pytest test/ -v # Full suite
48+
pytest tests/ -v # Full suite
4949
pytest --alluredir=allure-results -v # With Allure
5050
allure serve allure-results/ # Open interactive report
5151
```

config/config_for_cart_page.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@
2727
"display_name": "Sauce Labs Bolt T-Shirt"
2828
},
2929
"red_tshirt": {
30-
"add": (By.ID, "add-to-cart-test.allthethings()-t-shirt-(red)"),
31-
"remove": (By.ID, "remove-test.allthethings()-t-shirt-(red)"),
30+
"add": (By.ID, "add-to-cart-tests.allthethings()-t-shirt-(red)"),
31+
"remove": (By.ID, "remove-tests.allthethings()-t-shirt-(red)"),
3232
"display_name": "Test.allTheThings() T-Shirt (Red)"
3333
}
3434
}

config/config_for_inventory_page.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@
2727
"display_name": "Sauce Labs Bolt T-Shirt"
2828
},
2929
"red_tshirt": {
30-
"add": (By.ID, "add-to-cart-test.allthethings()-t-shirt-(red)"),
31-
"remove": (By.ID, "remove-test.allthethings()-t-shirt-(red)"),
30+
"add": (By.ID, "add-to-cart-tests.allthethings()-t-shirt-(red)"),
31+
"remove": (By.ID, "remove-tests.allthethings()-t-shirt-(red)"),
3232
"display_name": "Test.allTheThings() T-Shirt (Red)"
3333
}
3434
}

config/locators.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
2+
from selenium.webdriver.common.by import By
3+
4+
# ==================== LOGIN PAGE ====================
5+
LOGIN = {
6+
"USERNAME_INPUT": (By.ID, "user-name"),
7+
"PASSWORD_INPUT": (By.ID, "password"),
8+
"LOGIN_BUTTON": (By.ID, "login-button"),
9+
"ERROR_MESSAGE": (By.CSS_SELECTOR, "[data-tests='error']"),
10+
"SWAG_LABS_LOGO": (By.CLASS_NAME, "login_logo"),
11+
"BURGER_MENU_BUTTON": (By.ID, "react-burger-menu-btn"),
12+
"LOGOUT_LINK": (By.CSS_SELECTOR, "[data-tests='logout-sidebar-link']"),
13+
}
14+
15+
# ==================== INVENTORY PAGE ====================
16+
INVENTORY = {
17+
"TITLE": (By.CLASS_NAME, "title"),
18+
"PRIMARY_HEADER": (By.CSS_SELECTOR, "[data-tests='primary-header']"),
19+
"SHOPPING_CART_LINK": (By.CSS_SELECTOR, "[data-tests='shopping-cart-link']"),
20+
"SHOPPING_CART_BADGE": (By.CLASS_NAME, "shopping_cart_badge"),
21+
"BURGER_MENU_BUTTON": (By.ID, "react-burger-menu-btn"),
22+
"CROSS_BURGER_BUTTON": (By.ID, "react-burger-cross-btn"),
23+
"SORT_DROPDOWN": (By.CLASS_NAME, "product_sort_container"),
24+
"PRODUCT_ITEM": (By.CLASS_NAME, "inventory_item"),
25+
"PRODUCT_NAME": (By.CLASS_NAME, "inventory_item_name"),
26+
"PRODUCT_PRICE": (By.CLASS_NAME, "inventory_item_price"),
27+
"PRODUCT_DESCRIPTION": (By.CSS_SELECTOR, "[data-tests='inventory-item-desc']"),
28+
"ADD_TO_CART_PREFIX": (By.CSS_SELECTOR, "[data-tests^='add-to-cart-']"),
29+
}
30+
31+
# ==================== CART PAGE ====================
32+
CART = {
33+
"TITLE": (By.CSS_SELECTOR, "[data-tests='title']"),
34+
"CART_ITEMS": (By.CLASS_NAME, "cart_item"),
35+
"ITEM_NAME": (By.CLASS_NAME, "inventory_item_name"),
36+
"CHECKOUT_BUTTON": (By.ID, "checkout"),
37+
"CONTINUE_SHOPPING_BTN": (By.CSS_SELECTOR, "[data-tests='continue-shopping']"),
38+
"SHOPPING_CART_BADGE": (By.CLASS_NAME, "shopping_cart_badge"),
39+
"REMOVE_BTN_PREFIX": (By.CSS_SELECTOR, "[data-tests^='remove-']"),
40+
"CART_QUANTITY": (By.CSS_SELECTOR, "[data-tests='item-quantity']"),
41+
}
42+
43+
# ==================== CHECKOUT PAGE ====================
44+
CHECKOUT = {
45+
# Step One
46+
"FIRST_NAME": (By.ID, "first-name"),
47+
"LAST_NAME": (By.ID, "last-name"),
48+
"ZIP_CODE": (By.ID, "postal-code"),
49+
"CONTINUE_BUTTON": (By.ID, "continue"),
50+
"CANCEL_BUTTON": (By.CSS_SELECTOR, "[data-tests='cancel']"),
51+
"ERROR_MESSAGE": (By.CSS_SELECTOR, "[data-tests='error']"),
52+
53+
# Step Two (Overview)
54+
"PAYMENT_INFO_LABEL": (By.CSS_SELECTOR, "[data-tests='payment-info-label']"),
55+
"PAYMENT_INFO_VALUE": (By.CSS_SELECTOR, "[data-tests='payment-info-value']"),
56+
"SHIPPING_INFO_LABEL": (By.CSS_SELECTOR, "[data-tests='shipping-info-label']"),
57+
"SHIPPING_INFO_VALUE": (By.CSS_SELECTOR, "[data-tests='shipping-info-value']"),
58+
"TOTAL_INFO_LABEL": (By.CSS_SELECTOR, "[data-tests='total-info-label']"),
59+
"SUBTOTAL_LABEL": (By.CLASS_NAME, "summary_subtotal_label"),
60+
"TAX_LABEL": (By.CLASS_NAME, "summary_tax_label"),
61+
"TOTAL_LABEL": (By.CLASS_NAME, "summary_total_label"),
62+
"FINISH_BUTTON": (By.ID, "finish"),
63+
64+
# Step Three (Complete)
65+
"COMPLETE_HEADER": (By.CSS_SELECTOR, "[data-tests='complete-header']"),
66+
"COMPLETE_TEXT": (By.CSS_SELECTOR, "[data-tests='complete-text']"),
67+
"BACK_HOME_BUTTON": (By.CSS_SELECTOR, "[data-tests='back-to-products']"),
68+
}

pages/Base_Page.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
from selenium.webdriver.remote.webdriver import WebDriver
2+
from selenium.webdriver.remote.webelement import WebElement
3+
from selenium.webdriver.support.ui import WebDriverWait
4+
from selenium.webdriver.support import expected_conditions as EC
5+
from selenium.common.exceptions import TimeoutException, NoSuchElementException, ElementNotInteractableException
6+
import logging
7+
from typing import Tuple, Optional
8+
9+
class BasePage:
10+
def __init__(self, driver: WebDriver, timeout: int = 15):
11+
self.driver = driver
12+
self.timeout = timeout
13+
self.wait = WebDriverWait(self.driver, self.timeout)
14+
self.logger = logging.getLogger(self.__class__.__name__)
15+
16+
# ==============Element Finders ======================================
17+
def find_element(self, locator: Tuple[str, str]):
18+
try:
19+
return self.wait.until(EC.presence_of_element_located(locator))
20+
except TimeoutException:
21+
self.logger.error(f"Element not found within {self.timeout} seconds: {locator}")
22+
raise
23+
def find_elements(self, locator: Tuple[str, str]):
24+
try:
25+
return self.wait.until(EC.presence_of_all_elements_located(locator))
26+
except TimeoutException:
27+
self.logger.error(f"Element not found within {self.timeout} seconds: {locator}")
28+
raise
29+
30+
# =============Actions =======================================================
31+
32+
def click(self, locator: Tuple[str, str]):
33+
try:
34+
element = self.wait.until(EC.element_to_be_clickable(locator))
35+
element.click()
36+
except (TimeoutException, ElementNotInteractableException):
37+
try:
38+
element = self.find_element(locator)
39+
self.driver.execute_script("arguments[0].click();", element)
40+
self.logger.info(f"Used JavaScript click for locator: {locator}")
41+
except Exception as e:
42+
self.logger.error(f"Failed to click element {locator}: {e}")
43+
raise
44+
45+
def fill(self, locator: Tuple[str, str], text: str):
46+
try:
47+
element = self.wait.until(EC.presence_of_element_located(locator))
48+
element.clear()
49+
element.send_keys(text)
50+
except Exception as e:
51+
self.logger.error(f"Failed to fill text in {locator}: {e}")
52+
raise
53+
def get_text(self, locator: Tuple[str, str]) -> str:
54+
element = self.find_element(locator)
55+
return element.text.strip()
56+
57+
def is_visible(self, locator: Tuple[str, str], timeout: Optional[int] = None) -> bool:
58+
try:
59+
wait_time = timeout or self.timeout
60+
WebDriverWait(self.driver, wait_time).until(EC.visibility_of_element_located(locator))
61+
return True
62+
except TimeoutException:
63+
return False
64+
65+
def get_current_url(self) -> str:
66+
return self.driver.current_url
67+
68+
def go_to ( self , url: str ) :
69+
self.driver.get (url)

pages/Cart_Page.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
from selenium.webdriver.common.by import By
2+
from selenium.common.exceptions import TimeoutException , NoSuchElementException , ElementNotInteractableException , \
3+
StaleElementReferenceException
4+
from selenium.webdriver.support import expected_conditions as EC
5+
from pages.Base_Page import BasePage
6+
from config.locators import CART
7+
from config.config_for_cart_page import PRODUCTS
8+
class CartPage(BasePage):
9+
10+
def get_cart_items ( self ) -> list :
11+
try :
12+
self.wait.until (EC.presence_of_all_elements_located (CART["CART_ITEMS"]))
13+
except TimeoutException :
14+
return []
15+
16+
present_items = []
17+
for product_key in PRODUCTS :
18+
try :
19+
remove_selector = PRODUCTS[product_key]['remove']
20+
self.driver.find_element (*remove_selector)
21+
present_items.append (PRODUCTS[product_key]['display_name'])
22+
except NoSuchElementException :
23+
pass
24+
return present_items
25+
26+
def get_cart_badge_count ( self ) -> str :
27+
try :
28+
self.wait.until (EC.presence_of_element_located (CART["SHOPPING_CART_BADGE"]))
29+
return self.driver.find_element (*CART["SHOPPING_CART_BADGE"]).text
30+
except TimeoutException :
31+
return "0"
32+
33+
def is_checkout_button_visible ( self ) -> bool :
34+
return self.is_visible (CART["CHECKOUT_BUTTON"])
35+
36+
def remove_item ( self , product_key: str ) -> bool :
37+
if PRODUCTS[product_key]['display_name'] not in self.get_cart_items () :
38+
self.logger.warning (f"Product '{product_key}' not found in cart.")
39+
return False
40+
41+
remove_selector = PRODUCTS[product_key]['remove']
42+
43+
try :
44+
self.click (remove_selector)
45+
remove_button = self.find_element (remove_selector)
46+
self.wait.until (EC.staleness_of (remove_button))
47+
48+
self.logger.info (
49+
f"Product '{product_key}' removed successfully. New badge: {self.get_cart_badge_count ()}")
50+
return True
51+
except (TimeoutException , StaleElementReferenceException) as e :
52+
self.logger.error (f"Failed to remove product '{product_key}': {e}")
53+
return False
54+
55+
def start_checkout ( self ) -> bool :
56+
if not self.is_checkout_button_visible () :
57+
self.logger.warning ("Checkout button is not visible")
58+
return False
59+
60+
try :
61+
self.click (CART["CHECKOUT_BUTTON"])
62+
self.wait.until (EC.url_contains ("checkout-step-one"))
63+
return True
64+
except TimeoutException :
65+
self.logger.error ("Failed to navigate to checkout step one")
66+
return False

pages/Checkout_Page.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
from selenium.webdriver.common.by import By
2+
from selenium.common.exceptions import TimeoutException
3+
from selenium.webdriver.support import expected_conditions as EC
4+
from pages.Base_Page import BasePage
5+
from config.locators import CHECKOUT
6+
7+
8+
class CheckoutPage(BasePage):
9+
10+
def fill_info(self, first_name: str, last_name: str, postal_code: str) -> bool:
11+
try:
12+
self.wait.until(EC.url_contains("checkout-step-one"))
13+
14+
self.fill(CHECKOUT["FIRST_NAME"], first_name)
15+
self.fill(CHECKOUT["LAST_NAME"], last_name)
16+
self.fill(CHECKOUT["ZIP_CODE"], postal_code)
17+
18+
self.logger.info(f"Checkout info filled: {first_name} {last_name}")
19+
return True
20+
except Exception as e:
21+
self.logger.error(f"Failed to fill checkout info: {e}")
22+
return False
23+
24+
def continue_to_overview(self) -> bool:
25+
try:
26+
self.click(CHECKOUT["CONTINUE_BUTTON"])
27+
self.wait.until(EC.url_contains("checkout-step-two"))
28+
self.logger.info("Navigated to checkout overview (step two)")
29+
return True
30+
except TimeoutException as e:
31+
self.logger.error(f"Failed to continue to overview: {e}")
32+
return False
33+
34+
def complete_checkout(self) -> bool:
35+
try:
36+
self.click(CHECKOUT["FINISH_BUTTON"])
37+
self.wait.until(EC.url_contains("checkout-complete"))
38+
self.logger.info("Checkout completed successfully")
39+
return True
40+
except TimeoutException as e:
41+
self.logger.error(f"Failed to complete checkout: {e}")
42+
return False
43+
44+
def is_complete_header_visible(self) -> bool:
45+
return self.is_visible(CHECKOUT["COMPLETE_HEADER"])
46+
47+
def get_complete_header_text(self) -> str:
48+
return self.get_text(CHECKOUT["COMPLETE_HEADER"])

0 commit comments

Comments
 (0)