diff --git a/pyproject.toml b/pyproject.toml index 6825231..7c647c4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -88,72 +88,68 @@ build-backend = "poetry.core.masonry.api" # Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or # McCabe complexity (`C901`) by default. select = [ + "A", # flake8-builtins + "ANN", # flake8-annotations # coming back to this one later to compare against mypy + "ARG", # flake8-unused-arguments + "B", # flake8-bugbear + "BLE", # flake8-blind-except + "C4", # flake8-comprehensions + "D", # pydocstyle "E", # pycodestyle error - "W", # pycodestyle warning + "EM", # flake8-errmsg + "ERA", # eradicate "F", # Pyflakes - "FA", # flake8-future-annotations + "FA", # flake8-future-annotations +# "FBT", # flake8-boolean-trap + "FIX", # flake8-fixme + "FLY", # flynt "I", # isort - "D", # pydocstyle - "C4", # flake8-comprehensions - "TCH", # type checking + "ICN", # flake8-import-conventions + "ISC", # flake8-implicit-str-concat + "PGH", # pygrep-hooks + "PIE", # flake8-pie + "PL", # pylint + "PT", # flake8-pytest-style + "Q", # flake8-quotes + "RET", # flake8-return + "RSE", # flake8-raise "RUF", # ruff specific - "ANN", # annotations + "SIM", # flake8-simplify + "T10", # flake8-debugger + "TCH", # flake8-type-checking + "TD", #TODOs + "TRY", # tryceratops "UP", # python upgrade - "B", # bugbear - "SIM", # flake8 simplify - "A", # built-ins - "RET", # flake8 return - "YTT", # flake8-2020 - "EXE", # flake8-executable - "ISC", # flake8-implicit-str-concat - "PIE", # flake8-pie - "Q", # flake8-quotes - "RSE", # flake8-raise - "FLY", # flynt - "PERF", # perflint - "PL", # pylint -# "ERA", # eradicate - # "N", # pep8 naming -- dear lord, leave this off + "W", # pycodestyle warning + "YTT", # flake8-2020 # may eventually use but for now these are not helpful - # "FURB", # refurb - # "ARG", # unused arguments - # "PT", # Pytest style - # "TD", #TODOs - # "FBT", # boolean trap +# "FURB", # refurb # needs --preview flag to run ] ignore = [ - # "PT003", # pytest fixture scope implied - # "PT004", # pytest fixture setup doesn't return anything - "RUF100", # blanket noqa "ANN101", # missing-type-self "ANN102", # cls "E501", # line too long -- black will take care of this for us - "E721", # type-comparison WTF? - "B028", # No explicit `stacklevel` keyword argument found - "SIM102", # single if instead of nested -- only sometimes useful - "SIM114", # combine if branches using logical OR -- only sometimes useful "SIM115", # use context handler for open -- situationally useful - "SIM118", # use x in y instead of y.keys() - sometimes we want the keys - "SIM300", # yoda conditions -- meh +# "SIM300", # yoda conditions -- meh "PERF203", # `try`-`except` within a loop incurs performance overhead "PERF401", # use list comp # NOT OPTIONAL. MUST REMAIN AS SET # these are all completely unnecessary - "D100", # Missing docstring in public module "D101", # Missing docstring in public class - "D102", # Missing docstring in public method - "D103", # Missing docstring in public function "D104", # Missing docstring in public package "D105", # Missing docstring in magic method "D106", # Missing docstring in public nested class "D107", # Missing docstring in `__init__` - "D301", # Use `r"""` if any backslashes in a docstring + "D202", # no blank lines after docstring + "D203", # one-blank-line-before-class + "D204", # blank line required after docstring + "D205", # blank line between summary and description + "D212", # Multi-line summary should start at the first line "D400", # First line should end with a period "D401", # imperative mood - "D403", # first word of 1st line caps "D404", # First word of the docstring should not be "This" "D405", # Section name should be properly capitalized "D406", # Section name should end with a newline @@ -161,21 +157,12 @@ ignore = [ "D411", # Missing blank line before section "D412", # No blank lines allowed between a section header and its content "D415", # First line should end with punctuation - "D200", # One-line docstring should fit on one line - "D202", # no blank lines after docstring - "D204", # blank line required after docstring - "D205", # blank line between summary and description - "D203", # one-blank-line-before-class - "D210", # whitespace - "D212", # Multi-line summary should start at the first line - "D214", # Section overindented - "A003", # Class attribute shadow builtin + "PLC0414", # useless-import-alias "PLR0911", # too-many-return-statements "PLR0912", # too-many-branches "PLR0913", # too-many-arguments "PLR0915", # too-many-statements - "PLR2004", # magic-value-comparison # 3.8 & 3.9 compatibility "UP007", # Use `X | Y` for type annotations @@ -215,11 +202,25 @@ exclude = [ ] [tool.ruff.lint] -extend-safe-fixes = ["TCH003"] +extend-safe-fixes = [ + "EM101", + "EM102", + "TCH001", "TCH002", "TCH003", "TCH004", + # "SIM108" + "C419", + "D200", "D205", + "PT003", "PT006", "PT018", + "RET504", + "UP007", +] [tool.ruff.per-file-ignores] "__init__.py" = ["F401"] - +"tests/**" = [ + "D", # we don't need public-API-polished docstrings in tests. + "FBT", # using a boolean as a test object is useful! + "PLR", # likewise using specific numbers and strings in tests. +] [tool.ruff.isort] combine-as-imports = true @@ -234,19 +235,16 @@ mark-parentheses = false ignore-overlong-task-comments = true -#[tool.ruff.flake8-annotations] -#mypy-init-return = true - - [tool.ruff.flake8-annotations] # ignore returns types for functions that implicity or explicitly only return None suppress-none-returning = true allow-star-arg-any = true +#mypy-init-return = true [tool.black] line-length = 88 -target-version = ['py310'] +target-version = ['py311'] # 'extend-exclude' excludes files or directories in addition to the defaults extend-exclude = ''' diff --git a/setup_selenium/selenium_module.py b/setup_selenium/selenium_module.py index 5559dc4..a1ad3e9 100644 --- a/setup_selenium/selenium_module.py +++ b/setup_selenium/selenium_module.py @@ -1,3 +1,4 @@ +"""Setup selenium for testing""" from __future__ import annotations import errno @@ -12,7 +13,7 @@ from selenium.webdriver.common.selenium_manager import SeleniumManager from selenium.webdriver.edge.service import Service as EdgeService from selenium.webdriver.firefox.service import Service as FirefoxService -from semantic_version import Version # type: ignore +from semantic_version import Version # type: ignore[import-untyped] from typing_extensions import TypeAlias if TYPE_CHECKING: @@ -38,12 +39,11 @@ def create_logger(name: str) -> logging.Logger: def set_logger(logr: logging.Logger) -> None: - """ - Set the global logger with a custom logger - """ + """Set the global logger with a custom logger""" # Check if the logger is a valid logger if not isinstance(logr, logging.Logger): - raise ValueError("The logger must be an instance of logging.Logger") + msg = "logger must be an instance of logging.Logger" + raise TypeError(msg) # Bind the logger input to the global logger global logger # noqa: PLW0603 @@ -132,9 +132,7 @@ def __init__( def make_screenshot_path( output_dir: str = "./logs", screenshots: str = "screenshots" ) -> str: - """ - Set the output directory for where screenshots should go. - """ + """Set the output directory for where screenshots should go.""" output_dir = os.path.abspath(os.path.expanduser(output_dir)) if os.path.split(output_dir)[-1].lower() != screenshots: output_dir = os.path.join(output_dir, screenshots) @@ -152,6 +150,7 @@ def make_screenshot_path( ############################################################################ @staticmethod def log_options(options: ArgOptions) -> None: + """Logs the browser option in clean format""" opts = "\n".join(options.arguments) logger.debug(f"{opts}") @@ -163,7 +162,7 @@ def install_driver( browser_path: str | None = None, install_browser: bool = False, ) -> tuple[str, str]: - """install the webdriver and browser if needed.""" + """Install the webdriver and browser if needed.""" browser = Browser[browser.upper()].lower() driver_version = driver_version or None @@ -204,6 +203,7 @@ def create_driver( binary: str | None = None, driver_path: str | None = None, ) -> T_WebDriver: + """Instantiates the browser driver""" browser = browser.lower() driver: T_WebDriver if browser == Browser.FIREFOX: @@ -238,12 +238,14 @@ def create_driver( ) else: - raise ValueError(f"Unknown browser: {browser}") + msg = f"Unknown browser: {browser}" + raise ValueError(msg) return driver @staticmethod def firefox_options() -> webdriver.FirefoxOptions: + """Default options for firefox""" options = webdriver.FirefoxOptions() options.set_capability("unhandledPromptBehavior", "ignore") @@ -263,6 +265,7 @@ def firefox( binary: str | None = None, options: webdriver.FirefoxOptions = None, ) -> webdriver.Firefox: + """Instantiates firefox geockodriver""" options = options or SetupSelenium.firefox_options() if binary: options.binary_location = binary @@ -301,6 +304,7 @@ def firefox( @staticmethod def chrome_options() -> webdriver.ChromeOptions: + """Default options for chrome""" logger.debug("Setting up chrome options") # The list of options set below mostly came from this StackOverflow post # https://stackoverflow.com/q/48450594/2532408 @@ -334,6 +338,7 @@ def chrome( binary: str | None = None, options: webdriver.ChromeOptions = None, ) -> webdriver.Chrome: + """Instantiates chromedriver""" options = options or SetupSelenium.chrome_options() if binary: options.binary_location = binary @@ -405,6 +410,7 @@ def chrome( @staticmethod def set_throttle(driver: webdriver.Chrome): + """Experimental settings to slow down browser""" # experimental settings to slow down browser # @formatter:off # fmt: off @@ -433,6 +439,7 @@ def set_throttle(driver: webdriver.Chrome): @staticmethod def edge_options() -> webdriver.EdgeOptions: + """Default options for edgedriver""" logger.debug("Setting up edge options") # The list of options set below mostly came from this StackOverflow post # https://stackoverflow.com/q/48450594/2532408 @@ -447,8 +454,6 @@ def edge_options() -> webdriver.EdgeOptions: # edgedriver crashes without these two in linux "--no-sandbox", "--disable-dev-shm-usage", - # it's possible we no longer need to do these - # "--disable-gpu", # https://stackoverflow.com/q/51959986/2532408 ) options = webdriver.EdgeOptions() for opt in opts: @@ -466,6 +471,7 @@ def edge( binary: str | None = None, options: webdriver.EdgeOptions = None, ) -> webdriver.Edge: + """Instantiates edgedriver""" options = options or SetupSelenium.edge_options() if binary: options.binary_location = binary @@ -540,6 +546,7 @@ def edge( ############################################################################ def set_window_size(self, size: str = "720") -> None: + """Helper to set the window size after driver has been instantiated.""" if size == "max": self.driver.maximize_window() return @@ -550,17 +557,22 @@ def set_window_size(self, size: str = "720") -> None: self.driver.set_window_size(width, height) def set_main_window_handle(self, window: str | None = None) -> str: - if not window: - # does the main_window_handle exist and point to an available window? - if not self.main_window_handle: + """ + maintains the initial window handle as an attribute + + Most users will never utilize this. It's part of a legacy requirement for + an old test suite + """ + # does the main_window_handle exist and point to an available window? + if not window and not self.main_window_handle: + try: + window = self.driver.current_window_handle + except NoSuchWindowException: try: - window = self.driver.current_window_handle - except NoSuchWindowException: - try: - window = self.driver.window_handles[0] - except WebDriverException: - # Have we closed all the windows? - raise + window = self.driver.window_handles[0] + except WebDriverException: # noqa: TRY302 + # Have we closed all the windows? + raise if window: self.main_window_handle = window return self.main_window_handle diff --git a/tests/conftest.py b/tests/conftest.py index 6384f04..52fc078 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -18,10 +18,6 @@ def pytest_addoption(parser: Parser) -> None: ) -# def pytest_configure(config: Config) -> None: -# config.addinivalue_line("markers", "slow: mark test as slow to run") - - def pytest_collection_modifyitems(config: Config, items: Sequence[Item]): if config.getoption("--runslow"): # --runslow given in cli: do not skip slow tests diff --git a/tests/test_setup.py b/tests/test_setup.py index b3f1f80..7b326a6 100644 --- a/tests/test_setup.py +++ b/tests/test_setup.py @@ -197,7 +197,7 @@ def test_edge_bad_binary_path(create_logger: logging.Logger) -> None: assert driver.service.is_connectable() -@pytest.fixture +@pytest.fixture() def create_logger() -> logging.Logger: """Create a logger.""" logr = logging.getLogger("testsetupsel")