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 |
--------------------------------------------------------------------------------