From ab158cb2744bbfc8edce4258b0af4b2eb6dad234 Mon Sep 17 00:00:00 2001 From: "a.krasnov" Date: Mon, 4 Nov 2019 21:54:47 +0300 Subject: [PATCH] wip --- config/config.py | 31 ++++++- conftest.py | 89 +++++++++++---------- src/selenium/__init__.py | 0 src/selenium/browser_factory.py | 28 +++++++ src/selenium/browsers/__init__.py | 0 src/selenium/browsers/abstract_browser.py | 38 +++++++++ src/selenium/browsers/available_browsers.py | 22 +++++ src/selenium/browsers/chrome_browser.py | 21 +++++ src/selenium/browsers/firefox_browser.py | 18 +++++ src/selenium/capabilities.py | 23 ++++++ 10 files changed, 224 insertions(+), 46 deletions(-) create mode 100644 src/selenium/__init__.py create mode 100644 src/selenium/browser_factory.py create mode 100644 src/selenium/browsers/__init__.py create mode 100644 src/selenium/browsers/abstract_browser.py create mode 100644 src/selenium/browsers/available_browsers.py create mode 100644 src/selenium/browsers/chrome_browser.py create mode 100644 src/selenium/browsers/firefox_browser.py create mode 100644 src/selenium/capabilities.py diff --git a/config/config.py b/config/config.py index 1baf943..636e0f7 100644 --- a/config/config.py +++ b/config/config.py @@ -1,2 +1,29 @@ -# may be changed in different environments -BASE_URL = "https://go.mail.ru/" +import os + +username = None +access_key = None + +try: + username = os.environ["SAUCE_USERNAME"] + access_key = os.environ["SAUCE_ACCESS_KEY"] +except KeyError: + pass + + +class Config: + """Configuration class.""" + + base_url = None + screen_on_fail = None + browser = None + all_browser = None + platform = None + browser_version = None + has_saucelabs_connect = username and access_key + saucelabs_connection_string = f"https://{username}:{access_key}@ondemand.saucelabs.com/wd/hub" + + @classmethod + def setup(cls, cfg): + """Get values from config file.""" + for key, value in cfg.items(): + setattr(cls, key, value) diff --git a/conftest.py b/conftest.py index e3ebb1e..b068bbd 100644 --- a/conftest.py +++ b/conftest.py @@ -1,51 +1,26 @@ import os import pytest import datetime -from selenium import webdriver -AVAILABLE_BROWSERS = { - "chrome": "chrome", - "firefox": "firefox" -} +from config.config import Config +from src.selenium.browser_factory import BrowserFactory +from src.selenium.browsers.available_browsers import AvailableBrowsers +from src.selenium.capabilities import Capabilities -@pytest.fixture(scope="function") +@pytest.fixture(scope="class") def driver(request, param): - browser_type = request.config.getoption("--browser") or param - save_screen = request.config.getoption("--screenonfail") or param + # use param if its parametrized run + browser_type = Config.browser or param + save_screen = Config.browser - username = None - access_key = None + capabilities = Capabilities(browser_type, version="", page_load_strategy="none") - try: - username = os.environ["SAUCE_USERNAME"] - access_key = os.environ["SAUCE_ACCESS_KEY"] - except KeyError: - pass - - has_sauce_lab = username and access_key - caps = {} - command_executor = f"https://{username}:{access_key}@ondemand.saucelabs.com/wd/hub" - - if AVAILABLE_BROWSERS["chrome"] in browser_type: - # use "none" only for Chrome, because there some trouble in geckrodriver with this strategy - caps.update({"pageLoadStrategy": "none"}) - if has_sauce_lab: - caps.update({"browserName": "chrome", "platform": "Windows 10", "version": "71.0"}) - browser = webdriver.Remote(desired_capabilities=caps, command_executor=command_executor) - else: - browser = webdriver.Chrome(desired_capabilities=caps) - - elif AVAILABLE_BROWSERS["firefox"] in browser_type: - if has_sauce_lab: - caps.update({"browserName": "firefox", "platform": "Windows 10", "version": "64.0"}) - browser = webdriver.Remote(desired_capabilities=caps, command_executor=command_executor) - else: - browser = webdriver.Firefox(desired_capabilities=caps) - - else: - raise RuntimeError(f"Unknown browser ${browser_type}") + if Config.has_saucelabs_connect: + command_executor = Config.saucelabs_connection_string + capabilities.set_capability(Capabilities.command_executor, command_executor) + browser = BrowserFactory.get_driver(capabilities) yield browser if request.node.rep_call.failed and save_screen: @@ -59,7 +34,7 @@ def driver(request, param): browser.quit() -@pytest.fixture(scope="function") +@pytest.fixture(scope="class") def param(request): # we don"t have request.param when not parametrize tests with pytest_generate_tests try: @@ -71,22 +46,48 @@ def param(request): def pytest_addoption(parser): parser.addoption("--browser", action="append", - help=f"Browser. Valid options are {AVAILABLE_BROWSERS.keys()}") + help=f"Browser. Valid options are {AvailableBrowsers.get_available_browsers()}") - parser.addoption("--allbrowsers", + parser.addoption("--all_browsers", default=False, action="store_true", help="Run tests in all available browsers") - parser.addoption("--screenonfail", + parser.addoption("--browser_version", + default="Windows", + action="store_true", + help="Browsers version for run tests") + + parser.addoption("--platform", + default="Windows", + action="store_true", + help="OS where run tests") + + parser.addoption("--screen_on_fail", default=False, action="store_true", help="Save screenshot on test fail") + parser.addoption("--base_url", + default="https://go.mail.ru/", + action="store_true", + help="base url for tests") + + +def pytest_configure(config): + Config.setup({ + "base_url": config.getoption("--base_url"), + "screen_on_fail": config.getoption("--screen_on_fail"), + "browser": config.getoption("--browser"), + "all_browsers": config.getoption("--all_browsers"), + "browser_version": config.getoption("--browser_version"), + "platform": config.getoption("--platform"), + }) + def pytest_generate_tests(metafunc): - if metafunc.config.getoption("allbrowsers") and not metafunc.config.getoption("browser"): - metafunc.parametrize("param", AVAILABLE_BROWSERS.keys()) + if Config.all_browser and not Config.browser: + metafunc.parametrize("param", AvailableBrowsers.get_available_browsers()) # https://automated-testing.info/t/pytest-krivo-otobrazhaet-kejsy-parametrizaczii-na-russkom/17908 diff --git a/src/selenium/__init__.py b/src/selenium/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/selenium/browser_factory.py b/src/selenium/browser_factory.py new file mode 100644 index 0000000..e59b23f --- /dev/null +++ b/src/selenium/browser_factory.py @@ -0,0 +1,28 @@ +from typing import Union +from src.selenium.browsers.available_browsers import AvailableBrowsers +from src.selenium.capabilities import Capabilities + + +class BrowserFactory(object): + """Factory class for WebDriver""" + + def __init__(self): + pass + + @staticmethod + def get_driver(capabilities: Capabilities) -> Union[AvailableBrowsers.get_available_browser_constructors()]: + browser_name = capabilities.get_capability(Capabilities.browser_name) + + if browser_name not in AvailableBrowsers.get_available_browsers(): + raise RuntimeError(f"Unknown browser name: {browser_name}") + + webdriver_constructor = None + browser_constructors = AvailableBrowsers.get_available_browser_constructors() + for constructor in browser_constructors: + if browser_name == constructor.get_browser_name(): + webdriver_constructor = constructor + break + + webdriver = webdriver_constructor() + webdriver.set_capability(capabilities) + return webdriver.create_driver_instance() diff --git a/src/selenium/browsers/__init__.py b/src/selenium/browsers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/selenium/browsers/abstract_browser.py b/src/selenium/browsers/abstract_browser.py new file mode 100644 index 0000000..e2b838c --- /dev/null +++ b/src/selenium/browsers/abstract_browser.py @@ -0,0 +1,38 @@ +from src.selenium.capabilities import Capabilities +from abc import ABC, abstractmethod + +import typing + + +class AbstractBrowser(ABC): + + browser_name = None + + def __init__(self): + self.capabilities: Capabilities = None + self.browser_name: str = None + + def _capabilities_to_dict(self) -> dict: + return self.capabilities.to_dictionary() + + @classmethod + def _set_browser_name(cls, name: str) -> None: + cls.browser_name = name + + @classmethod + def get_browser_name(cls) -> str: + return cls.browser_name + + def set_capability(self, caps: Capabilities = None) -> None: + if caps: + self.capabilities = caps + + def is_local_browser(self) -> bool: + return bool(self.capabilities.command_executor) + + @abstractmethod + def create_driver_instance(self) -> typing.NoReturn: + """ + Each subclass of AbstractBrowser should implement this method. By design. + """ + raise NotImplemented("init_driver not implemented") diff --git a/src/selenium/browsers/available_browsers.py b/src/selenium/browsers/available_browsers.py new file mode 100644 index 0000000..d361c2f --- /dev/null +++ b/src/selenium/browsers/available_browsers.py @@ -0,0 +1,22 @@ +from typing import List, Type, Union +from src.selenium.browsers.chrome_browser import ChromeBrowser +from src.selenium.browsers.firefox_browser import FirefoxBrowser + + +class AvailableBrowsers(object): + CHROME = 'chrome' + FIREFOX = 'firefox' + + @classmethod + def get_available_browsers(cls) -> List[str]: + available_browsers = [] + + for attribute in dir(AvailableBrowsers): + if not attribute.startswith("__") and not callable(getattr(AvailableBrowsers, attribute)): + available_browsers.append(attribute) + + return available_browsers + + @staticmethod + def get_available_browser_constructors() -> List[Type[Union[ChromeBrowser, FirefoxBrowser]]]: + return [ChromeBrowser, FirefoxBrowser] diff --git a/src/selenium/browsers/chrome_browser.py b/src/selenium/browsers/chrome_browser.py new file mode 100644 index 0000000..ffc4a2f --- /dev/null +++ b/src/selenium/browsers/chrome_browser.py @@ -0,0 +1,21 @@ +from src.selenium.browsers.abstract_browser import AbstractBrowser +from src.selenium.browsers.available_browsers import AvailableBrowsers +from src.selenium.capabilities import Capabilities +from selenium.webdriver import Chrome, Remote + + +_DEFAULT_CHROME_CAPS = {"page_load_strategy": "none", "browser_name": AvailableBrowsers.CHROME, "version": "71.0"} + + +class ChromeBrowser(AbstractBrowser): + def __init__(self): + self.browser_name = AvailableBrowsers.CHROME + self.capabilities = Capabilities(**_DEFAULT_CHROME_CAPS) + super().__init__() + + def create_driver_instance(self) -> Chrome: + if self.is_local_browser(): + return Chrome(desired_capabilities=self._capabilities_to_dict()) + + return Remote(desired_capabilities=self._capabilities_to_dict(), + command_executor=self.capabilities.command_executor) diff --git a/src/selenium/browsers/firefox_browser.py b/src/selenium/browsers/firefox_browser.py new file mode 100644 index 0000000..8498535 --- /dev/null +++ b/src/selenium/browsers/firefox_browser.py @@ -0,0 +1,18 @@ +from src.selenium.browsers.abstract_browser import AbstractBrowser +from src.selenium.browsers.available_browsers import AvailableBrowsers +from selenium.webdriver import Firefox, Remote + +_DEFAULT_FIREFOX_CAPS = {"pageLoadStrategy": "eager", "browserName": AvailableBrowsers.FIREFOX, "version": "64.0"} + + +class FirefoxBrowser(AbstractBrowser): + def __init__(self): + self._set_browser_name(AvailableBrowsers.FIREFOX) + super().__init__() + + def create_driver_instance(self) -> Firefox: + if self.is_local_browser(): + return Firefox(capabilities=self._capabilities_to_dict()) + + return Remote(desired_capabilities=self._capabilities_to_dict(), + command_executor=self.capabilities.command_executor) diff --git a/src/selenium/capabilities.py b/src/selenium/capabilities.py new file mode 100644 index 0000000..0b45d62 --- /dev/null +++ b/src/selenium/capabilities.py @@ -0,0 +1,23 @@ +class Capabilities(object): + # link between fields of Capability and capability names + browser_name = "browserName" + version = "version" + page_load_strategy = "pageLoadStrategy" + command_executor = "command_executor" + + def __init__(self, browser_name, version, page_load_strategy, command_executor=None): + self._capability = { + Capabilities.browser_name: browser_name, + Capabilities.version: version, + Capabilities.page_load_strategy: page_load_strategy, + Capabilities.command_executor: command_executor, + } + + def set_capability(self, key, value) -> None: + self._capability[key] = value + + def get_capability(self, key): + return self._capability[key] + + def to_dictionary(self) -> dict: + return self._capability