├── README.md ├── conftest.py ├── pages ├── base_page.py ├── images_page.py ├── results_page.py └── search_page.py ├── requirements.txt └── ui_tests └── test_duck_go.py /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

Simple Python + Pytest + Selenium + Allure automation test project

5 | 6 |
7 | 12 |
13 | 14 |
15 |

To integrate PyCharm with PyTest

16 |

Preferences -> Tools -> Default test runner -> PyCharm

17 |
18 | 19 |
20 |

To run marked tests

21 |

For example: you have tests @pytest.mark.uitest that execute them you need to:
22 | pytest -m uitest

23 |
24 | 25 |
26 |

To rerun tests

27 |

For rerun failed tests:
28 | pytest --reruns 2

29 |
30 | 31 |
32 |

To run tests in paralell

33 |

pytest /tests -n 3

34 |

35 | 36 |
37 |

To generate HTML report:

38 |

pytest --html=report.html

39 |

40 | 41 |
42 |

To generate allure report

43 |

pytest --alluredir ./my-allure

44 |

allure generate ./my-allure

45 |
46 | -------------------------------------------------------------------------------- /conftest.py: -------------------------------------------------------------------------------- 1 | import allure 2 | import pytest 3 | from selenium import webdriver 4 | from selenium.webdriver import ChromeOptions 5 | from selenium.webdriver import FirefoxOptions 6 | from webdriver_manager.chrome import ChromeDriverManager 7 | from webdriver_manager.firefox import GeckoDriverManager 8 | 9 | 10 | def pytest_addoption(parser): 11 | parser.addoption("--browser", action="store", default="chrome") 12 | parser.addoption("--browser_ver", action="store", default="") 13 | parser.addoption("--headless", action="store", default=False) 14 | parser.addoption("--remote", action="store", default=False) 15 | parser.addoption("--hub", action="store", default="localhost") 16 | 17 | 18 | @pytest.hookimpl(hookwrapper=True, tryfirst=True) 19 | def pytest_runtest_makereport(item): 20 | outcome = yield 21 | rep = outcome.get_result() 22 | setattr(item, "rep_" + rep.when, rep) 23 | return rep 24 | 25 | 26 | @pytest.fixture() 27 | def config(request): 28 | browser = request.config.getoption("--browser") 29 | version = request.config.getoption("--browser_ver") 30 | hub = request.config.getoption("--hub") 31 | headless = False 32 | remote = False 33 | if request.config.getoption("--headless"): 34 | headless = True 35 | if request.config.getoption("--remote"): 36 | remote = True 37 | 38 | return {"remote": remote, 39 | "version": version, 40 | "browser": browser, 41 | "headless": headless, 42 | "hub": hub} 43 | 44 | 45 | def get_chrome_options(config): 46 | options = ChromeOptions() 47 | options.headless = config["headless"] 48 | return options 49 | 50 | 51 | def get_firefox_options(config): 52 | options = FirefoxOptions() 53 | options.headless = config["headless"] 54 | return options 55 | 56 | 57 | def create_remote_driver(config): 58 | if config["browser"] == "chrome": 59 | options = get_chrome_options(config) 60 | else: 61 | options = get_firefox_options(config) 62 | capabilities = {"version": config["version"], 63 | "acceptInsecureCerts": True, 64 | "screenResolution": "1280x1024x24"} 65 | return webdriver.Remote(command_executor="http://{}:4444/wd/hub".format(config["hub"]), 66 | options=options, 67 | desired_capabilities=capabilities) 68 | 69 | 70 | def create_local_driver(config): 71 | driver = None 72 | if config["browser"] == "chrome": 73 | driver_manager = ChromeDriverManager() 74 | options = get_chrome_options(config) 75 | driver = webdriver.Chrome(executable_path=driver_manager.install(), options=options) 76 | elif config["browser"] == "firefox": 77 | driver_manager = GeckoDriverManager() 78 | options = get_firefox_options(config) 79 | driver = webdriver.Firefox(executable_path=driver_manager.install(), options=options) 80 | return driver 81 | 82 | 83 | @pytest.fixture() 84 | def driver(request, config): 85 | driver = None 86 | if config["remote"]: 87 | driver = create_remote_driver(config) 88 | else: 89 | driver = create_local_driver(config) 90 | driver.maximize_window() 91 | 92 | def tear_down(): 93 | if request.node.rep_call.failed: 94 | allure.attach(driver.get_screenshot_as_png(), attachment_type=allure.attachment_type.PNG) 95 | driver.quit() 96 | 97 | request.addfinalizer(tear_down) 98 | yield driver 99 | -------------------------------------------------------------------------------- /pages/base_page.py: -------------------------------------------------------------------------------- 1 | from selenium.webdriver.chrome.webdriver import WebDriver 2 | from selenium.webdriver.support import expected_conditions as ex_cond 3 | from selenium.webdriver.support.ui import WebDriverWait 4 | 5 | 6 | class BasePage: 7 | _URL = "http://duckduckgo.com" 8 | 9 | def __init__(self, driver: WebDriver) -> None: 10 | self._driver = driver 11 | 12 | def navigate_to(self, url): 13 | self._driver.get(url) 14 | 15 | def get_element(self, locator: tuple, timeout=5): 16 | return WebDriverWait(self._driver, timeout).until( 17 | ex_cond.visibility_of_element_located(locator), ' : '.join(locator)) 18 | 19 | def get_elements(self, locator: tuple, timeout=5): 20 | return WebDriverWait(self._driver, timeout).until( 21 | ex_cond.visibility_of_any_elements_located(locator), ' : '.join(locator)) 22 | -------------------------------------------------------------------------------- /pages/images_page.py: -------------------------------------------------------------------------------- 1 | from selenium.webdriver.common.by import By 2 | 3 | from pages.base_page import BasePage 4 | 5 | 6 | class ImagesPage(BasePage): 7 | images = (By.XPATH, "//div[@class='tile-wrap']//img") 8 | -------------------------------------------------------------------------------- /pages/results_page.py: -------------------------------------------------------------------------------- 1 | import allure 2 | from selenium.webdriver.common.by import By 3 | 4 | from pages.base_page import BasePage 5 | from pages.images_page import ImagesPage 6 | 7 | 8 | class ResultPage(BasePage): 9 | _result_links = (By.CSS_SELECTOR, ".result__title a.result__a") 10 | _images_tab_link = (By.XPATH, "//a[@data-zci-link='images']") 11 | 12 | @allure.step 13 | def open_result_link(self, position): 14 | super().get_elements(self._result_links)[position - 1].click() 15 | 16 | @allure.step 17 | def get_result_link_text(self, position) -> str: 18 | return super().get_elements(self._result_links, 20)[position-1].text 19 | 20 | @allure.step 21 | def switch_to_images_page(self): 22 | super().get_element(self._images_tab_link).click() 23 | return ImagesPage(self._driver) 24 | -------------------------------------------------------------------------------- /pages/search_page.py: -------------------------------------------------------------------------------- 1 | import allure 2 | from selenium.webdriver.common.by import By 3 | from pages.base_page import BasePage 4 | from pages.results_page import ResultPage 5 | 6 | 7 | class SearchPage(BasePage): 8 | _search_field = (By.ID, "search_form_input_homepage") 9 | 10 | @allure.step 11 | def open(self): 12 | super().navigate_to(super()._URL) 13 | return self 14 | 15 | @allure.step("search for {text}") 16 | def search_for(self, text): 17 | super().get_element(self._search_field).send_keys(text) 18 | super().get_element(self._search_field).submit() 19 | return ResultPage(self._driver) 20 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | allure-pytest==2.5.2 2 | pytest==3.9.1 3 | pytest-rerunfailures==4.2 4 | selenium==3.14.1 5 | webdriver-manager==1.7 6 | pytest-html==1.19.0 -------------------------------------------------------------------------------- /ui_tests/test_duck_go.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from pages.images_page import ImagesPage 4 | from pages.search_page import SearchPage 5 | 6 | 7 | def send_test_data(): 8 | return ["python", "Python", "kitty"] 9 | 10 | 11 | class TestDuckGo: 12 | @pytest.mark.uitest 13 | @pytest.mark.parametrize("text", send_test_data()) 14 | def test_verify_search(self, driver, text: str): 15 | result_page = SearchPage(driver).open().search_for(text) 16 | actual_result = result_page.get_result_link_text(2) 17 | 18 | assert text.lower() in actual_result.lower() 19 | 20 | @pytest.mark.uitest 21 | @pytest.mark.skip(reason="Test") 22 | def test_verify_images(self, driver, text): 23 | result_page = SearchPage(driver).open().search_for(text) 24 | images_page = result_page.switch_to_images_page() 25 | 26 | images = images_page.get_elements(ImagesPage.images)[:5] 27 | for image in images: 28 | assert image.is_displayed() 29 | --------------------------------------------------------------------------------