setup-selenium-testing 0.1.1__tar.gz → 0.1.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: setup-selenium-testing
3
- Version: 0.1.1
3
+ Version: 0.1.3
4
4
  Summary: Setup Selenium for automation testing
5
5
  Home-page: https://github.com/bandophahita/setup_selenium
6
6
  License: MIT
@@ -21,17 +21,14 @@ Classifier: Programming Language :: Python :: 3 :: Only
21
21
  Provides-Extra: dev
22
22
  Provides-Extra: test
23
23
  Requires-Dist: black ; extra == "dev"
24
- Requires-Dist: coverage ; extra == "dev"
25
- Requires-Dist: flake8 ; extra == "dev"
24
+ Requires-Dist: isort ; extra == "dev"
26
25
  Requires-Dist: mypy ; extra == "dev"
27
- Requires-Dist: pylint ; extra == "dev"
28
26
  Requires-Dist: pytest ; extra == "dev" or extra == "test"
29
27
  Requires-Dist: ruff ; extra == "dev"
30
28
  Requires-Dist: selenium (>=4.7.0)
31
29
  Requires-Dist: semantic-version
32
- Requires-Dist: setuptools ; extra == "dev"
33
30
  Requires-Dist: tox ; extra == "dev" or extra == "test"
34
- Requires-Dist: typing-extensions (>=4.7.1,<5.0.0)
31
+ Requires-Dist: typing-extensions
35
32
  Project-URL: Repository, https://github.com/bandophahita/setup_selenium
36
33
  Description-Content-Type: text/markdown
37
34
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "setup-selenium-testing"
3
- version = "0.1.1"
3
+ version = "0.1.3"
4
4
  description = "Setup Selenium for automation testing"
5
5
  authors = ["Marcel Wilson <trenchrats@gmail.com>"]
6
6
  license = "MIT"
@@ -25,30 +25,23 @@ packages = [{include = "setup_selenium"}]
25
25
  python = "^3.8"
26
26
  selenium = ">=4.7.0"
27
27
  semantic-version = "*"
28
- typing-extensions = "^4.7.1"
28
+ typing-extensions = "*"
29
29
 
30
30
  # extras
31
31
  pytest = {version="*", optional = true}
32
- coverage = {version="*", optional = true}
33
- flake8 = {version="*", optional = true}
34
32
  black = {version="*", optional = true}
35
33
  mypy = {version="*", optional = true}
36
- pylint = {version="*", optional = true}
37
- setuptools = {version="*", optional = true}
38
34
  ruff = {version="*", optional = true}
39
35
  tox = {version="*", optional = true}
40
-
36
+ isort = {version = "*", optional = true}
41
37
 
42
38
  [tool.poetry.extras]
43
39
  dev = [
44
40
  "pytest",
45
- "coverage",
46
- "flake8",
47
41
  "black",
48
42
  "mypy",
49
- "pylint",
50
- "setuptools",
51
43
  "ruff",
44
+ "isort",
52
45
  "tox",
53
46
  ]
54
47
  test = [
@@ -88,72 +81,68 @@ build-backend = "poetry.core.masonry.api"
88
81
  # Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or
89
82
  # McCabe complexity (`C901`) by default.
90
83
  select = [
84
+ "A", # flake8-builtins
85
+ "ANN", # flake8-annotations # coming back to this one later to compare against mypy
86
+ "ARG", # flake8-unused-arguments
87
+ "B", # flake8-bugbear
88
+ "BLE", # flake8-blind-except
89
+ "C4", # flake8-comprehensions
90
+ "D", # pydocstyle
91
91
  "E", # pycodestyle error
92
- "W", # pycodestyle warning
92
+ "EM", # flake8-errmsg
93
+ "ERA", # eradicate
93
94
  "F", # Pyflakes
94
- "FA", # flake8-future-annotations
95
+ "FA", # flake8-future-annotations
96
+ # "FBT", # flake8-boolean-trap
97
+ "FIX", # flake8-fixme
98
+ "FLY", # flynt
95
99
  "I", # isort
96
- "D", # pydocstyle
97
- "C4", # flake8-comprehensions
98
- "TCH", # type checking
100
+ "ICN", # flake8-import-conventions
101
+ "ISC", # flake8-implicit-str-concat
102
+ "PGH", # pygrep-hooks
103
+ "PIE", # flake8-pie
104
+ "PL", # pylint
105
+ "PT", # flake8-pytest-style
106
+ "Q", # flake8-quotes
107
+ "RET", # flake8-return
108
+ "RSE", # flake8-raise
99
109
  "RUF", # ruff specific
100
- "ANN", # annotations
110
+ "SIM", # flake8-simplify
111
+ "T10", # flake8-debugger
112
+ "TCH", # flake8-type-checking
113
+ "TD", #TODOs
114
+ "TRY", # tryceratops
101
115
  "UP", # python upgrade
102
- "B", # bugbear
103
- "SIM", # flake8 simplify
104
- "A", # built-ins
105
- "RET", # flake8 return
106
- "YTT", # flake8-2020
107
- "EXE", # flake8-executable
108
- "ISC", # flake8-implicit-str-concat
109
- "PIE", # flake8-pie
110
- "Q", # flake8-quotes
111
- "RSE", # flake8-raise
112
- "FLY", # flynt
113
- "PERF", # perflint
114
- "PL", # pylint
115
- # "ERA", # eradicate
116
- # "N", # pep8 naming -- dear lord, leave this off
116
+ "W", # pycodestyle warning
117
+ "YTT", # flake8-2020
117
118
 
118
119
  # may eventually use but for now these are not helpful
119
- # "FURB", # refurb
120
- # "ARG", # unused arguments
121
- # "PT", # Pytest style
122
- # "TD", #TODOs
123
- # "FBT", # boolean trap
120
+ # "FURB", # refurb # needs --preview flag to run
124
121
  ]
125
122
  ignore = [
126
- # "PT003", # pytest fixture scope implied
127
- # "PT004", # pytest fixture setup doesn't return anything
128
- "RUF100", # blanket noqa
129
123
  "ANN101", # missing-type-self
130
124
  "ANN102", # cls
131
125
  "E501", # line too long -- black will take care of this for us
132
- "E721", # type-comparison WTF?
133
- "B028", # No explicit `stacklevel` keyword argument found
134
- "SIM102", # single if instead of nested -- only sometimes useful
135
- "SIM114", # combine if branches using logical OR -- only sometimes useful
136
126
  "SIM115", # use context handler for open -- situationally useful
137
- "SIM118", # use x in y instead of y.keys() - sometimes we want the keys
138
- "SIM300", # yoda conditions -- meh
127
+ # "SIM300", # yoda conditions -- meh
139
128
 
140
129
  "PERF203", # `try`-`except` within a loop incurs performance overhead
141
130
  "PERF401", # use list comp
142
131
 
143
132
  # NOT OPTIONAL. MUST REMAIN AS SET
144
133
  # these are all completely unnecessary
145
- "D100", # Missing docstring in public module
146
134
  "D101", # Missing docstring in public class
147
- "D102", # Missing docstring in public method
148
- "D103", # Missing docstring in public function
149
135
  "D104", # Missing docstring in public package
150
136
  "D105", # Missing docstring in magic method
151
137
  "D106", # Missing docstring in public nested class
152
138
  "D107", # Missing docstring in `__init__`
153
- "D301", # Use `r"""` if any backslashes in a docstring
139
+ "D202", # no blank lines after docstring
140
+ "D203", # one-blank-line-before-class
141
+ "D204", # blank line required after docstring
142
+ "D205", # blank line between summary and description
143
+ "D212", # Multi-line summary should start at the first line
154
144
  "D400", # First line should end with a period
155
145
  "D401", # imperative mood
156
- "D403", # first word of 1st line caps
157
146
  "D404", # First word of the docstring should not be "This"
158
147
  "D405", # Section name should be properly capitalized
159
148
  "D406", # Section name should end with a newline
@@ -161,21 +150,12 @@ ignore = [
161
150
  "D411", # Missing blank line before section
162
151
  "D412", # No blank lines allowed between a section header and its content
163
152
  "D415", # First line should end with punctuation
164
- "D200", # One-line docstring should fit on one line
165
- "D202", # no blank lines after docstring
166
- "D204", # blank line required after docstring
167
- "D205", # blank line between summary and description
168
- "D203", # one-blank-line-before-class
169
- "D210", # whitespace
170
- "D212", # Multi-line summary should start at the first line
171
- "D214", # Section overindented
172
- "A003", # Class attribute shadow builtin
153
+
173
154
  "PLC0414", # useless-import-alias
174
155
  "PLR0911", # too-many-return-statements
175
156
  "PLR0912", # too-many-branches
176
157
  "PLR0913", # too-many-arguments
177
158
  "PLR0915", # too-many-statements
178
- "PLR2004", # magic-value-comparison
179
159
 
180
160
  # 3.8 & 3.9 compatibility
181
161
  "UP007", # Use `X | Y` for type annotations
@@ -215,11 +195,25 @@ exclude = [
215
195
  ]
216
196
 
217
197
  [tool.ruff.lint]
218
- extend-safe-fixes = ["TCH003"]
198
+ extend-safe-fixes = [
199
+ "EM101",
200
+ "EM102",
201
+ "TCH001", "TCH002", "TCH003", "TCH004",
202
+ # "SIM108"
203
+ "C419",
204
+ "D200", "D205",
205
+ "PT003", "PT006", "PT018",
206
+ "RET504",
207
+ "UP007",
208
+ ]
219
209
 
220
210
  [tool.ruff.per-file-ignores]
221
211
  "__init__.py" = ["F401"]
222
-
212
+ "tests/**" = [
213
+ "D", # we don't need public-API-polished docstrings in tests.
214
+ "FBT", # using a boolean as a test object is useful!
215
+ "PLR", # likewise using specific numbers and strings in tests.
216
+ ]
223
217
 
224
218
  [tool.ruff.isort]
225
219
  combine-as-imports = true
@@ -234,19 +228,16 @@ mark-parentheses = false
234
228
  ignore-overlong-task-comments = true
235
229
 
236
230
 
237
- #[tool.ruff.flake8-annotations]
238
- #mypy-init-return = true
239
-
240
-
241
231
  [tool.ruff.flake8-annotations]
242
232
  # ignore returns types for functions that implicity or explicitly only return None
243
233
  suppress-none-returning = true
244
234
  allow-star-arg-any = true
235
+ #mypy-init-return = true
245
236
 
246
237
 
247
238
  [tool.black]
248
239
  line-length = 88
249
- target-version = ['py310']
240
+ target-version = ['py311']
250
241
 
251
242
  # 'extend-exclude' excludes files or directories in addition to the defaults
252
243
  extend-exclude = '''
@@ -1,10 +1,12 @@
1
+ """Setup selenium for testing"""
2
+
1
3
  from __future__ import annotations
2
4
 
3
5
  import errno
4
6
  import logging
5
7
  import os as os
6
8
  from enum import Enum
7
- from typing import TYPE_CHECKING, Union
9
+ from typing import TYPE_CHECKING, Optional, Union
8
10
 
9
11
  from selenium import webdriver
10
12
  from selenium.common.exceptions import NoSuchWindowException, WebDriverException
@@ -12,7 +14,7 @@ from selenium.webdriver.chrome.service import Service as ChromeService
12
14
  from selenium.webdriver.common.selenium_manager import SeleniumManager
13
15
  from selenium.webdriver.edge.service import Service as EdgeService
14
16
  from selenium.webdriver.firefox.service import Service as FirefoxService
15
- from semantic_version import Version # type: ignore
17
+ from semantic_version import Version # type: ignore[import-untyped]
16
18
  from typing_extensions import TypeAlias
17
19
 
18
20
  if TYPE_CHECKING:
@@ -22,6 +24,9 @@ if TYPE_CHECKING:
22
24
  from selenium.webdriver.common.options import ArgOptions
23
25
 
24
26
  T_WebDriver: TypeAlias = Union[Firefox, Chrome, Edge]
27
+ T_DrvOpts: TypeAlias = Union[
28
+ webdriver.FirefoxOptions, webdriver.ChromeOptions, webdriver.EdgeOptions
29
+ ]
25
30
 
26
31
  __all__ = ["SetupSelenium"]
27
32
 
@@ -38,12 +43,11 @@ logger = create_logger("sel")
38
43
 
39
44
 
40
45
  def set_logger(logr: logging.Logger) -> None:
41
- """
42
- Set the global logger with a custom logger
43
- """
46
+ """Set the global logger with a custom logger"""
44
47
  # Check if the logger is a valid logger
45
48
  if not isinstance(logr, logging.Logger):
46
- raise ValueError("The logger must be an instance of logging.Logger")
49
+ msg = "logger must be an instance of logging.Logger"
50
+ raise TypeError(msg)
47
51
 
48
52
  # Bind the logger input to the global logger
49
53
  global logger # noqa: PLW0603
@@ -132,9 +136,7 @@ class SetupSelenium:
132
136
  def make_screenshot_path(
133
137
  output_dir: str = "./logs", screenshots: str = "screenshots"
134
138
  ) -> str:
135
- """
136
- Set the output directory for where screenshots should go.
137
- """
139
+ """Set the output directory for where screenshots should go."""
138
140
  output_dir = os.path.abspath(os.path.expanduser(output_dir))
139
141
  if os.path.split(output_dir)[-1].lower() != screenshots:
140
142
  output_dir = os.path.join(output_dir, screenshots)
@@ -152,6 +154,7 @@ class SetupSelenium:
152
154
  ############################################################################
153
155
  @staticmethod
154
156
  def log_options(options: ArgOptions) -> None:
157
+ """Logs the browser option in clean format"""
155
158
  opts = "\n".join(options.arguments)
156
159
  logger.debug(f"{opts}")
157
160
 
@@ -163,7 +166,7 @@ class SetupSelenium:
163
166
  browser_path: str | None = None,
164
167
  install_browser: bool = False,
165
168
  ) -> tuple[str, str]:
166
- """install the webdriver and browser if needed."""
169
+ """Install the webdriver and browser if needed."""
167
170
  browser = Browser[browser.upper()].lower()
168
171
  driver_version = driver_version or None
169
172
 
@@ -203,19 +206,24 @@ class SetupSelenium:
203
206
  log_dir: str = "./logs",
204
207
  binary: str | None = None,
205
208
  driver_path: str | None = None,
209
+ options: Optional[T_DrvOpts] = None,
206
210
  ) -> T_WebDriver:
211
+ """Instantiates the browser driver"""
207
212
  browser = browser.lower()
208
213
  driver: T_WebDriver
209
214
  if browser == Browser.FIREFOX:
215
+ assert options is None or isinstance(options, webdriver.FirefoxOptions)
210
216
  driver = SetupSelenium.firefox(
211
217
  headless=headless,
212
218
  enable_log_driver=enable_log_driver,
213
219
  log_dir=log_dir,
214
220
  binary=binary,
215
221
  driver_path=driver_path,
222
+ options=options,
216
223
  )
217
224
 
218
225
  elif browser == Browser.CHROME:
226
+ assert options is None or isinstance(options, webdriver.ChromeOptions)
219
227
  driver = SetupSelenium.chrome(
220
228
  headless=headless,
221
229
  enable_log_performance=enable_log_performance,
@@ -224,9 +232,11 @@ class SetupSelenium:
224
232
  log_dir=log_dir,
225
233
  binary=binary,
226
234
  driver_path=driver_path,
235
+ options=options,
227
236
  )
228
237
 
229
238
  elif browser == Browser.EDGE:
239
+ assert options is None or isinstance(options, webdriver.EdgeOptions)
230
240
  driver = SetupSelenium.edge(
231
241
  headless=headless,
232
242
  enable_log_performance=enable_log_performance,
@@ -235,15 +245,18 @@ class SetupSelenium:
235
245
  log_dir=log_dir,
236
246
  binary=binary,
237
247
  driver_path=driver_path,
248
+ options=options,
238
249
  )
239
250
 
240
251
  else:
241
- raise ValueError(f"Unknown browser: {browser}")
252
+ msg = f"Unknown browser: {browser}"
253
+ raise ValueError(msg)
242
254
 
243
255
  return driver
244
256
 
245
257
  @staticmethod
246
258
  def firefox_options() -> webdriver.FirefoxOptions:
259
+ """Default options for firefox"""
247
260
  options = webdriver.FirefoxOptions()
248
261
  options.set_capability("unhandledPromptBehavior", "ignore")
249
262
 
@@ -261,8 +274,9 @@ class SetupSelenium:
261
274
  log_dir: str = "./logs",
262
275
  driver_path: str | None = None,
263
276
  binary: str | None = None,
264
- options: webdriver.FirefoxOptions = None,
277
+ options: webdriver.FirefoxOptions | None = None,
265
278
  ) -> webdriver.Firefox:
279
+ """Instantiates firefox geockodriver"""
266
280
  options = options or SetupSelenium.firefox_options()
267
281
  if binary:
268
282
  options.binary_location = binary
@@ -301,6 +315,7 @@ class SetupSelenium:
301
315
 
302
316
  @staticmethod
303
317
  def chrome_options() -> webdriver.ChromeOptions:
318
+ """Default options for chrome"""
304
319
  logger.debug("Setting up chrome options")
305
320
  # The list of options set below mostly came from this StackOverflow post
306
321
  # https://stackoverflow.com/q/48450594/2532408
@@ -332,8 +347,9 @@ class SetupSelenium:
332
347
  log_dir: str = "./logs",
333
348
  driver_path: str | None = None,
334
349
  binary: str | None = None,
335
- options: webdriver.ChromeOptions = None,
350
+ options: webdriver.ChromeOptions | None = None,
336
351
  ) -> webdriver.Chrome:
352
+ """Instantiates chromedriver"""
337
353
  options = options or SetupSelenium.chrome_options()
338
354
  if binary:
339
355
  options.binary_location = binary
@@ -374,12 +390,12 @@ class SetupSelenium:
374
390
  service = ChromeService(
375
391
  executable_path=driver_path,
376
392
  service_args=args,
377
- log_output=logpath,
393
+ log_output=logpath, # type: ignore[arg-type]
378
394
  )
379
395
  else:
380
396
  service = ChromeService(
381
397
  service_args=args,
382
- log_output=logpath,
398
+ log_output=logpath, # type: ignore[arg-type]
383
399
  )
384
400
 
385
401
  driver = webdriver.Chrome(service=service, options=options)
@@ -405,6 +421,7 @@ class SetupSelenium:
405
421
 
406
422
  @staticmethod
407
423
  def set_throttle(driver: webdriver.Chrome):
424
+ """Experimental settings to slow down browser"""
408
425
  # experimental settings to slow down browser
409
426
  # @formatter:off
410
427
  # fmt: off
@@ -433,6 +450,7 @@ class SetupSelenium:
433
450
 
434
451
  @staticmethod
435
452
  def edge_options() -> webdriver.EdgeOptions:
453
+ """Default options for edgedriver"""
436
454
  logger.debug("Setting up edge options")
437
455
  # The list of options set below mostly came from this StackOverflow post
438
456
  # https://stackoverflow.com/q/48450594/2532408
@@ -447,8 +465,6 @@ class SetupSelenium:
447
465
  # edgedriver crashes without these two in linux
448
466
  "--no-sandbox",
449
467
  "--disable-dev-shm-usage",
450
- # it's possible we no longer need to do these
451
- # "--disable-gpu", # https://stackoverflow.com/q/51959986/2532408
452
468
  )
453
469
  options = webdriver.EdgeOptions()
454
470
  for opt in opts:
@@ -464,8 +480,9 @@ class SetupSelenium:
464
480
  log_dir: str = "./logs",
465
481
  driver_path: str | None = None,
466
482
  binary: str | None = None,
467
- options: webdriver.EdgeOptions = None,
483
+ options: webdriver.EdgeOptions | None = None,
468
484
  ) -> webdriver.Edge:
485
+ """Instantiates edgedriver"""
469
486
  options = options or SetupSelenium.edge_options()
470
487
  if binary:
471
488
  options.binary_location = binary
@@ -507,12 +524,12 @@ class SetupSelenium:
507
524
  service = EdgeService(
508
525
  executable_path=driver_path,
509
526
  service_args=args,
510
- log_output=logpath,
527
+ log_output=logpath, # type: ignore[arg-type]
511
528
  )
512
529
  else:
513
530
  service = EdgeService(
514
531
  service_args=args,
515
- log_output=logpath,
532
+ log_output=logpath, # type: ignore[arg-type]
516
533
  )
517
534
  driver = webdriver.Edge(service=service, options=options)
518
535
 
@@ -540,6 +557,7 @@ class SetupSelenium:
540
557
 
541
558
  ############################################################################
542
559
  def set_window_size(self, size: str = "720") -> None:
560
+ """Helper to set the window size after driver has been instantiated."""
543
561
  if size == "max":
544
562
  self.driver.maximize_window()
545
563
  return
@@ -550,17 +568,22 @@ class SetupSelenium:
550
568
  self.driver.set_window_size(width, height)
551
569
 
552
570
  def set_main_window_handle(self, window: str | None = None) -> str:
553
- if not window:
554
- # does the main_window_handle exist and point to an available window?
555
- if not self.main_window_handle:
571
+ """
572
+ maintains the initial window handle as an attribute
573
+
574
+ Most users will never utilize this. It's part of a legacy requirement for
575
+ an old test suite
576
+ """
577
+ # does the main_window_handle exist and point to an available window?
578
+ if not window and not self.main_window_handle:
579
+ try:
580
+ window = self.driver.current_window_handle
581
+ except NoSuchWindowException:
556
582
  try:
557
- window = self.driver.current_window_handle
558
- except NoSuchWindowException:
559
- try:
560
- window = self.driver.window_handles[0]
561
- except WebDriverException:
562
- # Have we closed all the windows?
563
- raise
583
+ window = self.driver.window_handles[0]
584
+ except WebDriverException: # noqa: TRY302
585
+ # Have we closed all the windows?
586
+ raise
564
587
  if window:
565
588
  self.main_window_handle = window
566
589
  return self.main_window_handle