diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/app/application.py b/app/application.py new file mode 100644 index 000000000..fd25d6d1d --- /dev/null +++ b/app/application.py @@ -0,0 +1,12 @@ +from pages.bestsellers_page import BestsellersPage +from pages.main_page import MainPage +from pages.search_results_page import SearchResultsPage + + +class Application: + + def __init__(self, driver): + self.driver = driver + self.bestsellers_page = BestsellersPage(self.driver) + self.main_page = MainPage(self.driver) + self.search_results_page = SearchResultsPage(self.driver) diff --git a/css_selectors.py b/css_selectors.py new file mode 100644 index 000000000..bffa9a978 --- /dev/null +++ b/css_selectors.py @@ -0,0 +1,38 @@ +from selenium import webdriver +from selenium.webdriver.common.by import By + +driver = webdriver.Chrome(executable_path='/Users/svetlanalevinsohn/JobEasy/12-python-selenium-automation/chromedriver') + +# By tag & ID +driver.find_element(By.CSS_SELECTOR, "input#twotabsearchtextbox") +# By ID +driver.find_element(By.CSS_SELECTOR, "#twotabsearchtextbox") +driver.find_element(By.ID, "twotabsearchtextbox") + +# By class +driver.find_element(By.CSS_SELECTOR, ".icp-nav-flag-us") +# By multiple classes +driver.find_element(By.CSS_SELECTOR, ".icp-nav-flag-us.icp-nav-flag") +# By class + tag +driver.find_element(By.CSS_SELECTOR, "span.icp-nav-flag-us.icp-nav-flag") + +# By attribute: +driver.find_element(By.CSS_SELECTOR, "input[name='email']") +driver.find_element(By.CSS_SELECTOR, "[name='email']") +driver.find_element(By.CSS_SELECTOR, "[href='/gp/help/customer/display.html/ref=ap_signin_notification_condition_of_use?ie=UTF8&nodeId=508088']") +# By multiple attributes: +driver.find_element(By.CSS_SELECTOR, "input[name='email'][type='email'][maxlength='128']") +# By attribute + class +driver.find_element(By.CSS_SELECTOR, "input.a-button-input[type='submit']") +driver.find_element(By.CSS_SELECTOR, "input.a-button-input.class[type='submit'][]") +driver.find_element(By.CSS_SELECTOR, ".a-button-input.class[type='submit'][]") +# By partial attribute +driver.find_element(By.CSS_SELECTOR, "a[href*='ap_signin_notification_condition_of_use']") +# For classes, to get partial match: +driver.find_element(By.CSS_SELECTOR, "a[class*='button']") + +# From parent => child +driver.find_element(By.CSS_SELECTOR, "div#legalTextRow a[href*='condition']") + +# Xpath backwards (from child to parent) +driver.find_element(By.XPATH, "//*[./a[contains(@href, 'signin_notification_condition_of_use')]]") \ No newline at end of file diff --git a/features/environment.py b/features/environment.py index d9d7a6ac2..c9bba449b 100755 --- a/features/environment.py +++ b/features/environment.py @@ -1,16 +1,21 @@ from selenium import webdriver +from selenium.webdriver.support.wait import WebDriverWait + +from app.application import Application def browser_init(context): """ :param context: Behave context """ - context.driver = webdriver.Chrome() + context.driver = webdriver.Chrome(executable_path='/Users/svetlanalevinsohn/JobEasy/12-python-selenium-automation/chromedriver') # context.browser = webdriver.Safari() # context.browser = webdriver.Firefox() context.driver.maximize_window() context.driver.implicitly_wait(4) + context.driver.wait = WebDriverWait(context.driver, 10) + context.app = Application(context.driver) def before_scenario(context, scenario): diff --git a/features/steps/amazon_main_page_steps.py b/features/steps/amazon_main_page_steps.py new file mode 100644 index 000000000..6e872a020 --- /dev/null +++ b/features/steps/amazon_main_page_steps.py @@ -0,0 +1,40 @@ +from selenium.webdriver.common.by import By +from behave import given, when, then +from selenium.webdriver.support import expected_conditions as EC +from time import sleep + + +HAM_MENU = (By.ID, 'nav-hamburger-menu') +FOOTER_LINKS = (By.CSS_SELECTOR, '.navFooterDescItem a') +SIGN_IN = (By.CSS_SELECTOR, "#nav-signin-tooltip .nav-action-button") + + +@given('Open amazon page') +def open_amazon(context): + context.app.main_page.open_main() + + +@when('Search for {product}') +def search_product(context, product): + context.app.main_page.search_product(product) + + +@when('Click on button from SignIn popup') +def click_sign_in(context): + e = context.driver.wait.until(EC.element_to_be_clickable(SIGN_IN), message='Sign in not clickable') + e.click() + + +@then('Verify hamburger menu is present') +def verify_ham_menu_present(context): + context.driver.find_element(*HAM_MENU) + + +@then('Verify that footer has {expected_link_count} links') +def verify_link_count(context, expected_link_count): + expected_link_count = int(expected_link_count) + + links = context.driver.find_elements(*FOOTER_LINKS) + + assert len(links) == expected_link_count, \ + f'Expected {expected_link_count} links, but got {len(links)}' diff --git a/features/steps/bestsellers_page_steps.py b/features/steps/bestsellers_page_steps.py new file mode 100644 index 000000000..b514aa91b --- /dev/null +++ b/features/steps/bestsellers_page_steps.py @@ -0,0 +1,11 @@ +from behave import given, then + + +@given('Open Amazon Bestsellers') +def open_amazon_bestsellers(context): + context.app.bestsellers_page.open_bestsellers() + + +@then('User can click through top links and verify correct page opens') +def click_thru_top(context): + context.app.bestsellers_page.click_thru_top_links() diff --git a/features/steps/product_page_steps.py b/features/steps/product_page_steps.py new file mode 100644 index 000000000..6a2674fae --- /dev/null +++ b/features/steps/product_page_steps.py @@ -0,0 +1,44 @@ +from selenium.webdriver.common.by import By +from behave import when, given, then +from time import sleep + + +ADD_TO_CART_BTN = (By.ID, 'add-to-cart-button') +PRODUCT_NAME = (By.ID, 'productTitle') +COLOR_OPTIONS = (By.CSS_SELECTOR, "#variation_color_name li") +CURRENT_COLOR = (By.CSS_SELECTOR, "#variation_color_name .selection") + + +@given('Open Amazon product {product_id} page') +def open_amazon_product(context, product_id): + context.driver.get(f'https://www.amazon.com/dp/{product_id}/') + + +@when('Click on Add to cart button') +def click_add_to_cart(context): + context.driver.find_element(*ADD_TO_CART_BTN).click() + sleep(2) + + +@when('Store product name') +def get_product_name(context): + context.product_name = context.driver.find_element(*PRODUCT_NAME).text + print(f'Current product: {context.product_name}') + + +@then('Verify user can click through colors') +def verify_can_click_colors(context): + expected_colors = ['Black', 'Solid Black'] + actual_colors = [] + + colors = context.driver.find_elements(*COLOR_OPTIONS) + + for color in colors[:3]: + color.click() + current_color = context.driver.find_element(*CURRENT_COLOR).text + actual_colors += [current_color] + + assert expected_colors == actual_colors, \ + f'Expected colors {expected_colors} did not match actual {actual_colors}' + + diff --git a/features/steps/search_results_page_steps.py b/features/steps/search_results_page_steps.py new file mode 100644 index 000000000..a0885d878 --- /dev/null +++ b/features/steps/search_results_page_steps.py @@ -0,0 +1,8 @@ +from selenium.webdriver.common.by import By +from behave import given, when, then + + +@then('Search results for {expected_result} are shown') +def verify_search_results(context, expected_result): + actual_result = context.driver.find_element(By.XPATH, "//span[@class='a-color-state a-text-bold']").text + assert expected_result == actual_result, f'Error! Expected {expected_result}, but got {actual_result}' diff --git a/features/steps/sign_in_steps.py b/features/steps/sign_in_steps.py new file mode 100644 index 000000000..6fb2dcb8a --- /dev/null +++ b/features/steps/sign_in_steps.py @@ -0,0 +1,8 @@ +from selenium.webdriver.common.by import By +from behave import then +from selenium.webdriver.support import expected_conditions as EC + + +@then('Verify Sign in page opened') +def verify_sign_in_opened(context): + context.driver.wait.until(EC.url_contains('ap/signin'), message='Signin URL did not open') \ No newline at end of file diff --git a/features/tests/amazon_main.feature b/features/tests/amazon_main.feature new file mode 100644 index 000000000..f6988bad9 --- /dev/null +++ b/features/tests/amazon_main.feature @@ -0,0 +1,10 @@ +# Created by svetlanalevinsohn at 10/1/22 +Feature: Tests for Amazon main page + + Scenario: Hamburger menu is present + Given Open amazon page + Then Verify hamburger menu is present + + Scenario: Footer has correct amount of links + Given Open amazon page + Then Verify that footer has 38 links diff --git a/features/tests/amazon_search.feature b/features/tests/amazon_search.feature new file mode 100644 index 000000000..fc02c76ab --- /dev/null +++ b/features/tests/amazon_search.feature @@ -0,0 +1,33 @@ +# Created by svetlanalevinsohn at 9/24/22 +Feature: Tests for amazon search + + Scenario: User can search for coffee + Given Open amazon page + When Search for coffee + Then Search results for "coffee" are shown +# +# Scenario: User can search for mug +# Given Open amazon page +# When Search for mug +# Then Search results for "mug" are shown + + Scenario Outline: User can search for a product + Given Open amazon page + When Search for + Then Search results for are shown + Examples: + |product |search_result | + |coffee |"coffee" | + |mug |"mug" | + |dress |"dress" | + |Tritan Farm to Table Pitcher on amazon |"Tritan Farm to Table Pitcher on amazon" | + + Scenario: User can add a product to the cart + Given Open Amazon page + When Search for Tritan Farm to Table Pitcher on amazon + And Click on the first product + And Store product name + And Click on Add to cart button + And Open cart page + Then Verify cart has 1 item(s) + And Verify cart has correct product \ No newline at end of file diff --git a/features/tests/product_details.feature b/features/tests/product_details.feature new file mode 100644 index 000000000..b34a6b6fa --- /dev/null +++ b/features/tests/product_details.feature @@ -0,0 +1,5 @@ +Feature: Tests for product page + + Scenario: User can select colors + Given Open Amazon product B07MNHBRCJ page + Then Verify user can click through colors \ No newline at end of file diff --git a/features/tests/sign_in.feature b/features/tests/sign_in.feature new file mode 100644 index 000000000..93bbd5304 --- /dev/null +++ b/features/tests/sign_in.feature @@ -0,0 +1,7 @@ +# Created by svetlanalevinsohn at 7/16/22 +Feature: Sign in test cases + + Scenario: Sign In page can be opened from SignIn popup + Given Open Amazon page + When Click on button from SignIn popup + Then Verify Sign in page opened diff --git a/pages/__init__.py b/pages/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/pages/base_page.py b/pages/base_page.py new file mode 100644 index 000000000..ed2d2cb15 --- /dev/null +++ b/pages/base_page.py @@ -0,0 +1,52 @@ +from selenium.webdriver.support.wait import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC + + +class Page: + + def __init__(self, driver): + self.driver = driver + self.wait = WebDriverWait(self.driver, 15) + self.base_url = 'https://www.amazon.com/' + + def open_url(self, end_url=''): + url = f'{self.base_url}{end_url}' + print(f'Opening URL: {url}') + self.driver.get(url) + + def find_element(self, *locator): + return self.driver.find_element(*locator) + + def find_elements(self, *locator): + return self.driver.find_elements(*locator) + + def click(self, *locator): + self.driver.find_element(*locator).click() + + def input_text(self, text, *locator): + e = self.driver.find_element(*locator) + e.clear() + e.send_keys(text) + print(f'Inputting text: {text}') + + def wait_for_element_click(self, *locator): + e = self.wait.until(EC.element_to_be_clickable(locator)) + e.click() + + def wait_for_element_disappear(self, *locator): + self.wait.until(EC.invisibility_of_element(locator)) + + def wait_for_element_appear(self, *locator): + return self.wait.until(EC.presence_of_element_located(locator)) + + def verify_element_text(self, expected_text, *locator): + actual_text = self.driver.find_element(*locator).text + assert expected_text == actual_text, f'Expected {expected_text}, but got {actual_text}' + + def verify_partial_text(self, expected_text, *locator): + actual_text = self.driver.find_element(*locator).text + assert expected_text in actual_text, \ + f'Expected text {expected_text} is not in {actual_text}' + + def verify_url_contains_query(self, query): + self.wait.until(EC.url_contains(query)) \ No newline at end of file diff --git a/pages/bestsellers_page.py b/pages/bestsellers_page.py new file mode 100644 index 000000000..610b7873b --- /dev/null +++ b/pages/bestsellers_page.py @@ -0,0 +1,23 @@ +from selenium.webdriver.common.by import By + +from pages.base_page import Page + + +class BestsellersPage(Page): + TOP_LINKS = (By.CSS_SELECTOR, '#zg_header a') + HEADER = (By.CSS_SELECTOR, '#zg_banner_text') + + def open_bestsellers(self): + self.open_url('gp/bestsellers/') + + def click_thru_top_links(self): + top_links = self.driver.find_elements(*self.TOP_LINKS) # [WebEl1,WebEl2, WebEl3,... ] + print(top_links) + + for i in range(len(top_links)): # for x from 0 to 4 + link_to_click = self.driver.find_elements(*self.TOP_LINKS)[i] + link_text = link_to_click.text + link_to_click.click() + header_text = self.driver.find_element(*self.HEADER).text + assert link_text in header_text, f'Expected {link_text} to be in {header_text}' + diff --git a/pages/main_page.py b/pages/main_page.py new file mode 100644 index 000000000..7b94270b1 --- /dev/null +++ b/pages/main_page.py @@ -0,0 +1,15 @@ +from selenium.webdriver.common.by import By +from pages.base_page import Page + + +class MainPage(Page): + SEARCH_INPUT = (By.ID, 'twotabsearchtextbox') + SEARCH_BTN = (By.ID, 'nav-search-submit-button') + + def open_main(self): + self.open_url() + + def search_product(self, product): + self.input_text(product, *self.SEARCH_INPUT) + self.click(*self.SEARCH_BTN) + diff --git a/pages/search_results_page.py b/pages/search_results_page.py new file mode 100644 index 000000000..88c28a3a0 --- /dev/null +++ b/pages/search_results_page.py @@ -0,0 +1,10 @@ +from selenium.webdriver.common.by import By + +from pages.base_page import Page + + +class SearchResultsPage(Page): + SEARCH_RESULTS = (By.CSS_SELECTOR, '.a-color-state.a-text-bold') + + def verify_search_results(self, expected_result): + self.verify_element_text(expected_result, *self.SEARCH_RESULTS) diff --git a/sample_script.py b/sample_script.py index 5d9332048..37ce493cc 100755 --- a/sample_script.py +++ b/sample_script.py @@ -1,26 +1,32 @@ from time import sleep from selenium import webdriver from selenium.webdriver.common.by import By +from selenium.webdriver.support.wait import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC # init driver -driver = webdriver.Chrome() +driver = webdriver.Chrome(executable_path='/Users/svetlanalevinsohn/JobEasy/12-python-selenium-automation/chromedriver') driver.maximize_window() +driver.implicitly_wait(5) # applied to find_element(s) / #100 ms if element is found +driver.wait = WebDriverWait(driver, 10) # open the url driver.get('https://www.google.com/') search = driver.find_element(By.NAME, 'q') search.clear() -search.send_keys('Dress') +search.send_keys('Car') # wait for 4 sec -sleep(4) +# sleep(4) +btn = (By.NAME, 'btnK') +driver.wait.until(EC.element_to_be_clickable(btn), message='Search btn not clickable') # 500 ms # click search driver.find_element(By.NAME, 'btnK').click() # verify -assert 'dress' in driver.current_url.lower(), f"Expected query not in {driver.current_url.lower()}" +assert 'car' in driver.current_url.lower(), f"Expected query not in {driver.current_url.lower()}" print('Test Passed') driver.quit()