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..d56fdcaf2 --- /dev/null +++ b/app/application.py @@ -0,0 +1,24 @@ +from pages.base_page import Page +from pages.cart_page import CartPage +from pages.header import Header +from pages.main_page import MainPage +from pages.search_result_page import SearchResultsPage +from pages.cart_page import CartPage +from pages.sign_in_page import SignIn + + +class Application: + + def __init__(self, driver): + self.base_page = Page(driver) + self.cart_page = CartPage(driver) + self.header = Header(driver) + self.main_page = MainPage(driver) + self.search_result_page = SearchResultsPage(driver) + self.sign_in_page = SignIn(driver) + self.pages = Page(driver) + + + + + diff --git a/features/__init__.py b/features/__init__.py index e69de29bb..57982fd8c 100755 --- a/features/__init__.py +++ b/features/__init__.py @@ -0,0 +1,25 @@ +from selenium import webdriver +from selenium.webdriver.common.by import By +from selenium.webdriver.chrome.service import Service +from webdriver_manager.chrome import ChromeDriverManager +from time import sleep + +# get the path to the ChromeDriver executable +driver_path = ChromeDriverManager().install() + +# create a new Chrome browser instance +service = Service(driver_path) +driver = webdriver.Chrome(service=service) +driver.maximize_window() + +driver.get('http://www.target.com/') + +driver.find_element(By.XPATH, "//span[@class='styles__LinkText-sc-1e1g60c-3 dZfgoT h-margin-r-x3']").click() + +driver.find_element(By.XPATH, "//*[@id='listaccountNav-signIn']/a/span").click() +sleep(6) +actual_text = driver.find_element(By.XPATH, "//*[@id='__next']/div/div/div/div[1]/div/h1/span").text +#Sign in button +driver.find_element(By.XPATH, "//button[@type='submit']") + +driver.quit() \ No newline at end of file diff --git a/features/environment.py b/features/environment.py index 0930bc703..3b0f05f41 100755 --- a/features/environment.py +++ b/features/environment.py @@ -1,6 +1,9 @@ from selenium import webdriver from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager +from selenium.webdriver.support.wait import WebDriverWait + +from app.application import Application def browser_init(context): @@ -11,10 +14,14 @@ def browser_init(context): service = Service(driver_path) context.driver = webdriver.Chrome(service=service) - context.driver.maximize_window() - context.driver.maximize_window() context.driver.implicitly_wait(4) + context.wait = WebDriverWait(context.driver, timeout=15) + + context.app = Application(context.driver) + + # context.app.header #to work with login + # context.app.main_page def before_scenario(context, scenario): @@ -29,8 +36,8 @@ def before_step(context, step): def after_step(context, step): if step.status == 'failed': print('\nStep failed: ', step) + context.app.base_page.save_screenshot('step') def after_scenario(context, feature): - context.driver.delete_all_cookies() - context.driver.quit() + context.driver.quit() \ No newline at end of file diff --git a/features/steps/cart_page_steps.py b/features/steps/cart_page_steps.py new file mode 100644 index 000000000..fe4317d66 --- /dev/null +++ b/features/steps/cart_page_steps.py @@ -0,0 +1,27 @@ +from selenium.webdriver.common.by import By +from behave import when, then + +CART_SUMMARY = (By.CSS_SELECTOR, "[class*='CartSummarySpan']") +CART_ITEM_TITLE = (By.CSS_SELECTOR, "[data-test='cartItem-title']") + + +@when('Open cart page') +def open_cart(context): + context.driver.get('https://www.target.com/cart') + + +@then('Verify cart has correct product') +def verify_product_name(context): + actual_name = context.driver.find_element(*CART_ITEM_TITLE).text + assert context.product_name in actual_name, f"Expected {context.product_name} but got {actual_name}" + + +@then('Verify cart has {amount} item(s)') +def verify_cart_items(context, amount): + cart_summary = context.driver.find_element(*CART_SUMMARY).text + assert amount in cart_summary, f"Expected {amount} items but got {cart_summary}" + + +@then("Verify 'Your cart is empty' message is shown") +def verify_cart_empty_message(context): + context.app.cart_page.verify_cart_empty_message() \ No newline at end of file diff --git a/features/steps/main_page_steps.py b/features/steps/main_page_steps.py new file mode 100644 index 000000000..2dcbd5bf1 --- /dev/null +++ b/features/steps/main_page_steps.py @@ -0,0 +1,25 @@ +from selenium.webdriver.common.by import By +from behave import given, when, then +from time import sleep +from pages import sign_in_page + + +@given('Open Target main page') +def open_target(context): + context.app.main_page.open_main() + + +@when("Click Sign in") +def click_sign_in(context): + context.app.sign_in_page.click_sign_in() + + +@then('From side nav menu, click Sign in') +def side_nav_sign_in_btn(context): + context.app.sign_in_page.side_nav_sign_in_btn() + + +@then('Verify Sign in form opened') +def verify_sign_in_shown(context): + context.app.base_page.verify_sign_in_shown('Sign into your Target account') + sleep(8) diff --git a/features/steps/product_details_page.py b/features/steps/product_details_page.py new file mode 100644 index 000000000..a0b14bf9d --- /dev/null +++ b/features/steps/product_details_page.py @@ -0,0 +1,32 @@ +from selenium.webdriver.common.by import By +from behave import given, then +from time import sleep + + +COLOR_OPTIONS = (By.CSS_SELECTOR, "[class*='ButtonWrapper'] img") +SELECTED_COLOR = (By.CSS_SELECTOR, "[class*='StyledVariationSelectorImage'] [class*='StyledHeaderWrapperDiv']") + + +@given('Open target product {product_id} page') +def open_target(context, product_id): + context.driver.get(f'https://www.target.com/p/{product_id}') + sleep(8) + + +@then('Verify user can click through colors') +def click_and_verify_colors(context): + expected_colors = ['Blue Tint', 'Denim Blue', 'Marine', 'Raven'] + actual_colors = [] + + colors = context.driver.find_elements(*COLOR_OPTIONS) # [webelement1, webelement2, webelement3] + for color in colors: + color.click() + + selected_color = context.driver.find_element(*SELECTED_COLOR).text # 'Color\nBlack' + print('Current color', selected_color) + + selected_color = selected_color.split('\n')[1] # remove 'Color\n' part, keep Black' + actual_colors.append(selected_color) + print(actual_colors) + + assert expected_colors == actual_colors, f'Expected {expected_colors} did not match actual {actual_colors}' \ No newline at end of file diff --git a/features/steps/product_search.py b/features/steps/product_search.py deleted file mode 100755 index 4e142cb40..000000000 --- a/features/steps/product_search.py +++ /dev/null @@ -1,32 +0,0 @@ -from selenium.webdriver.common.by import By -from behave import given, when, then -from time import sleep - - -SEARCH_INPUT = (By.NAME, 'q') -SEARCH_SUBMIT = (By.NAME, 'btnK') - - -@given('Open Google page') -def open_google(context): - context.driver.get('https://www.google.com/') - - -@when('Input {search_word} into search field') -def input_search(context, search_word): - search = context.driver.find_element(*SEARCH_INPUT) - search.clear() - search.send_keys(search_word) - sleep(4) - - -@when('Click on search icon') -def click_search_icon(context): - context.driver.find_element(*SEARCH_SUBMIT).click() - sleep(1) - - -@then('Product results for {search_word} are shown') -def verify_found_results_text(context, search_word): - assert search_word.lower() in context.driver.current_url.lower(), \ - f'Expected query not in {context.driver.current_url.lower()}' diff --git a/features/steps/search_results_page_steps.py b/features/steps/search_results_page_steps.py new file mode 100644 index 000000000..888950791 --- /dev/null +++ b/features/steps/search_results_page_steps.py @@ -0,0 +1,37 @@ +from selenium.webdriver.common.by import By +from selenium.webdriver.support import expected_conditions as EC + +from behave import then +from time import sleep + + + +SEARCH_RESULT_HEADER = (By.XPATH, "//div[@data-test='resultsHeading']") +LISTINGS = (By.CSS_SELECTOR, "[data-test='@web/site-top-of-funnel/ProductCardWrapper']") +PRODUCT_TITLE = (By.CSS_SELECTOR, "[data-test='product-title']") +PRODUCT_IMG = (By.CSS_SELECTOR, "[class*='ProductCardImage']") + + +@then('Verify search results are shown for {expected_item}') +def verify_search_results(context, expected_item): + context.app.search_result_page.verify_search_results(expected_item) + + +@then('Verify that URL has {partial_url}') +def verify_search_page_url(context, partial_url): + context.app.search_result_page.verify_partial_url(partial_url) + + +@then('Verify that every product has a name and an image') +def verify_products_name_img(context): + # To see ALL listings (comment out if you only check top ones): + context.driver.execute_script("window.scrollBy(0,2000)", "") + sleep(4) + context.driver.execute_script("window.scrollBy(0,2000)", "") + + all_products = context.driver.find_elements(*LISTINGS) # [WebEl1, WebEl2, WebEl3, WebEl4] + + for product in all_products: + title = product.find_element(*PRODUCT_TITLE).text + assert title, 'Product title not shown' + product.find_element(*PRODUCT_IMG) \ No newline at end of file diff --git a/features/tests/__init__.py b/features/tests/__init__.py index e69de29bb..4f7f0f253 100755 --- a/features/tests/__init__.py +++ b/features/tests/__init__.py @@ -0,0 +1,24 @@ +from selenium import webdriver +from selenium.webdriver.common.by import By +from selenium.webdriver.chrome.service import Service +from webdriver_manager.chrome import ChromeDriverManager +from time import sleep + +# get the path to the ChromeDriver executable +driver_path = ChromeDriverManager().install() + +# create a new Chrome browser instance +service = Service(driver_path) +driver = webdriver.Chrome(service=service) +driver.maximize_window() + +driver.get('http://www.target.com/') + +driver.find_element(By.XPATH, "//span[@class='styles__LinkText-sc-1e1g60c-3 dZfgoT h-margin-r-x3']").click() + +driver.find_element(By.XPATH, "//*[@id='listaccountNav-signIn']/a/span").click() +sleep(6) +actual_text = driver.find_element(By.XPATH, "//*[@id='__next']/div/div/div/div[1]/div/h1/span").text +#Sign in button +driver.find_element(By.XPATH, "//button[@type='submit']") +driver.quit() \ No newline at end of file diff --git a/features/tests/cart_tests.feature b/features/tests/cart_tests.feature new file mode 100644 index 000000000..0ce2aef27 --- /dev/null +++ b/features/tests/cart_tests.feature @@ -0,0 +1,16 @@ +Feature: Cart tests + + Scenario: 'Your cart is empty' message is shown for empty cart + Given Open target main page + When Click on Cart icon + Then Verify 'Your cart is empty' message is shown + + Scenario: User can add a product to cart + Given Open target main page + When Search for Ice Tea + And Click on Add to Cart button + And Store product name + And Confirm Add to Cart button from side navigation + And Open cart page + Then Verify cart has 1 item(s) + And Verify cart has correct product diff --git a/features/tests/main_page_ui_tests.feature b/features/tests/main_page_ui_tests.feature new file mode 100644 index 000000000..9bbe4dd3d --- /dev/null +++ b/features/tests/main_page_ui_tests.feature @@ -0,0 +1,9 @@ +Feature: Tests for main page UI + + Scenario: Verify header in shown + Given Open Target main page + Then Verify header in shown + + Scenario: Verify header has correct amount links + Given Open Target main page + Then Verify header has 5 links \ 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..708c7a0e4 --- /dev/null +++ b/features/tests/product_details.feature @@ -0,0 +1,5 @@ +Feature: Tests for product page + + Scenario: User can select colors + Given Open target product A-54551690 page + Then Verify user can click through colors \ No newline at end of file diff --git a/features/tests/product_search.feature b/features/tests/product_search.feature deleted file mode 100755 index 36d6913cf..000000000 --- a/features/tests/product_search.feature +++ /dev/null @@ -1,7 +0,0 @@ -Feature: Test Scenarios for Search functionality - - Scenario: User can search for a product - Given Open Google page - When Input Car into search field - And Click on search icon - Then Product results for Car are shown \ No newline at end of file diff --git a/features/tests/sign_in_test.feature b/features/tests/sign_in_test.feature new file mode 100644 index 000000000..a740f5446 --- /dev/null +++ b/features/tests/sign_in_test.feature @@ -0,0 +1,7 @@ +Feature: Target Sign In + + Scenario: Logged out user can Sign in + Given Open Target main page + When Click Sign in + Then From side nav menu, click Sign in + And Verify Sign in form opened \ No newline at end of file diff --git a/features/tests/target_search.feature b/features/tests/target_search.feature new file mode 100644 index 000000000..c8868bb12 --- /dev/null +++ b/features/tests/target_search.feature @@ -0,0 +1,18 @@ +Feature: Search tests + + + Scenario: User can search for a tea + Given Open Target main page + When Search for icetea + Then Verify search results are shown for icetea + Then Verify that URL has icetea + + Scenario Outline: User can search for a product + Given Open Target main page + When Search for + Then Verify search results are shown for + Examples: + |item |expected_item | + |mug |mug | + |tea |tea | + |white mug |white mug | 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..a22033995 --- /dev/null +++ b/pages/base_page.py @@ -0,0 +1,83 @@ +from selenium.webdriver.common.by import By +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, 10) + + 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.find_element(*locator).click() + + def input_text(self, text, *locator): + self.find_element(*locator).send_keys(text) + + def wait_until_clickable(self, *locator): + self.wait.until( + EC.element_to_be_clickable(locator), + message=f'Element not clickable by {locator}' + ).click() + + def wait_until_visible(self, *locator): + self.wait.until( + EC.visibility_of_element_located(locator), + f'Element not visible by {locator}' + ) + + def wait_until_disappears(self, *locator): + self.wait.until( + EC.invisibility_of_element_located(locator), + f'Element still visible by {locator}' + ) + + def get_current_window(self): + current_window = self.driver.current_window_handle + print('Current:', current_window) + print('ALL windows:', self.driver.window_handles) + return current_window + + def switch_to_new_window(self): + self.wait.until(EC.new_window_is_opened) + all_windows = self.driver.window_handles # [Win1, Win2, ...] + print('ALL windows:', self.driver.window_handles) + print('Switching to... ', all_windows[1]) + self.driver.switch_to.window(all_windows[1]) + + def switch_window_by_id(self, window_id): + print('Switching to... ', window_id) + self.driver.switch_to.window(window_id) + + def verify_partial_text(self, expected_text, *locator): + actual_text = self.find_element(*locator).text + assert expected_text in actual_text, f'Expected {expected_text}, not in {actual_text}' + + def verify_partial_url(self, expected_partial_url): + self.wait.until(EC.url_contains(expected_partial_url), message=f'Url doest not contain {expected_partial_url}') + + def verify_url(self, expected_url): + self.wait.until(EC.url_matches(expected_url), message=f'Url does not contain {expected_url}') + + def save_screenshot(self, name): + self.driver.save_screenshot(f'{name}.png') + + def close(self): + self.driver.close() + + def click_sign_in(self, *locator): + self.find_element(*locator).click() + + def side_nav_sign_in_btn(self, *locator): + self.find_element(*locator).click() + + def verify_sign_in_shown(self, expected_text, *locator): + actual_text = self.find_element(*locator).text + assert expected_text == expected_text, f'Expected {expected_text} but got {actual_text}' diff --git a/pages/cart_page.py b/pages/cart_page.py new file mode 100644 index 000000000..958f9313d --- /dev/null +++ b/pages/cart_page.py @@ -0,0 +1,10 @@ +from selenium.webdriver.common.by import By + +from pages.base_page import Page + + +class CartPage(Page): + CART_EMPTY_MSG = (By.CSS_SELECTOR, "h1[class*='StyledHeading']") + + def verify_cart_empty_message(self): + self.verify_text('Your cart is empty', *self.CART_EMPTY_MSG) \ No newline at end of file diff --git a/pages/header.py b/pages/header.py new file mode 100644 index 000000000..c0f8a7637 --- /dev/null +++ b/pages/header.py @@ -0,0 +1,19 @@ +from selenium.webdriver.common.by import By +from time import sleep + +from pages.base_page import Page + + +class Header(Page): + SEARCH_INPUT = (By.ID, 'search') + SEARCH_BTN = (By.XPATH, "//button[@data-test='@web/Search/SearchButton']") + CART_ICON = (By.CSS_SELECTOR, "[data-test='@web/CartLink']") + + def search_product(self, item): + self.input_text(item, *self.SEARCH_INPUT) + self.click(*self.SEARCH_BTN) + sleep(6) + + def click_cart(self): + self.wait_until_clickable(*self.CART_ICON) + self.save_screenshot('cart.png') \ No newline at end of file diff --git a/pages/main_page.py b/pages/main_page.py new file mode 100644 index 000000000..a7187c3f9 --- /dev/null +++ b/pages/main_page.py @@ -0,0 +1,8 @@ +from pages.base_page import Page + + +class MainPage(Page): + + def open_main(self): + self.driver.get('https://www.target.com/') + diff --git a/pages/search_result_page.py b/pages/search_result_page.py new file mode 100644 index 000000000..98f491d13 --- /dev/null +++ b/pages/search_result_page.py @@ -0,0 +1,11 @@ +from selenium.webdriver.common.by import By + +from pages.base_page import Page + + +class SearchResultsPage(Page): + SEARCH_RESULT_HEADER = (By.XPATH, "//div[@data-test='resultsHeading']") + + def verify_search_results(self, expected_item): + actual_text = self.find_element(*self.SEARCH_RESULT_HEADER).text + assert expected_item in actual_text, f'Error! Text {expected_item} not in {actual_text}' \ No newline at end of file diff --git a/pages/sign_in_page.py b/pages/sign_in_page.py new file mode 100644 index 000000000..60a5ca4d4 --- /dev/null +++ b/pages/sign_in_page.py @@ -0,0 +1,19 @@ +from selenium.webdriver.common.by import By + +from pages.base_page import Page +from behave import when, then + + +class SignIn(Page): + SIGNIN_BTN = (By.CSS_SELECTOR, "a[data-test='@web/AccountLink']") + SIDE_NAV_BTN = (By.CSS_SELECTOR, "a[data-test='accountNav-signIn']") + VERIFY_SIGNIN_OPEN = (By.CSS_SELECTOR, "h1[class*='StyledHeading']") + + def click_sign_in(self): + self.click(*self.SIGNIN_BTN) + + def side_nav_sign_in_btn(self): + self.wait_until_clickable(*self.SIDE_NAV_BTN) + + def verify_sign_in_shown(self, expected_text, *locator): + self.verify_sign_in_shown(*self.VERIFY_SIGNIN_OPEN) diff --git a/pages/target_app_page_steps.py b/pages/target_app_page_steps.py new file mode 100644 index 000000000..7af10e8ea --- /dev/null +++ b/pages/target_app_page_steps.py @@ -0,0 +1,36 @@ +from behave import given, when, then + + +@given('Open Target App page') +def open_target_app(context): + context.app.target_app_page.open_target_app() + + +@given('Store original window') +def store_original_window(context): + context.original_window = context.app.target_app_page.get_current_window() + + +@when('Click Privacy Policy link') +def click_pp_link(context): + context.app.target_app_page.click_pp_link() + + +@when('Switch to new window') +def switch_window(context): + context.app.target_app_page.switch_to_new_window() + + +@then('Verify Privacy Policy page opened') +def verify_pp_opened(context): + context.app.target_app_page.verify_pp_opened() + + +@then('Close current page') +def close(context): + context.app.target_app_page.close() + + +@then('Return to original window') +def return_to_original_window(context): + context.app.target_app_page.switch_window_by_id(context.original_window) \ No newline at end of file diff --git a/sample_script.py b/sample_script.py index 23d64fc06..b4e3a368d 100755 --- a/sample_script.py +++ b/sample_script.py @@ -2,6 +2,9 @@ from selenium.webdriver.common.by import By from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager +from selenium.webdriver.support.wait import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC + from time import sleep # get the path to the ChromeDriver executable @@ -11,6 +14,11 @@ service = Service(driver_path) driver = webdriver.Chrome(service=service) driver.maximize_window() +# Implicit +# driver.implicitly_wait(5) # up to 5 sec / applied to find_element, checks for element ever 100ms + +# Explicit +driver.wait = WebDriverWait(driver, timeout=10) # up to 10 sec / checks for element ever 500ms # open the url driver.get('https://www.google.com/') @@ -20,14 +28,15 @@ search.clear() search.send_keys('Car') -# wait for 4 sec -sleep(4) +search_btn = (By.NAME, 'btnK') +# Make sure not to use * for locators when working with EC: +driver.wait.until(EC.element_to_be_clickable(search_btn), message='Search btn not clickable').click() # click search button -driver.find_element(By.NAME, 'btnK').click() +# driver.find_element(By.NAME, 'btnK').click() # verify search results assert 'car' in driver.current_url.lower(), f"Expected query not in {driver.current_url.lower()}" print('Test Passed') -driver.quit() +driver.quit() \ No newline at end of file