wcp-library 1.6.7__tar.gz → 1.6.9__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.
Files changed (28) hide show
  1. {wcp_library-1.6.7 → wcp_library-1.6.9}/PKG-INFO +1 -1
  2. {wcp_library-1.6.7 → wcp_library-1.6.9}/pyproject.toml +1 -1
  3. {wcp_library-1.6.7 → wcp_library-1.6.9}/wcp_library/__init__.py +3 -3
  4. {wcp_library-1.6.7 → wcp_library-1.6.9}/wcp_library/browser_automation/browser.py +94 -85
  5. {wcp_library-1.6.7 → wcp_library-1.6.9}/wcp_library/browser_automation/interactions.py +149 -94
  6. {wcp_library-1.6.7 → wcp_library-1.6.9}/wcp_library/credentials/_credential_manager_asynchronous.py +4 -4
  7. {wcp_library-1.6.7 → wcp_library-1.6.9}/wcp_library/credentials/_credential_manager_synchronous.py +3 -3
  8. {wcp_library-1.6.7 → wcp_library-1.6.9}/wcp_library/credentials/api.py +4 -4
  9. {wcp_library-1.6.7 → wcp_library-1.6.9}/wcp_library/credentials/ftp.py +4 -4
  10. {wcp_library-1.6.7 → wcp_library-1.6.9}/wcp_library/credentials/internet.py +4 -4
  11. {wcp_library-1.6.7 → wcp_library-1.6.9}/wcp_library/credentials/oracle.py +4 -4
  12. {wcp_library-1.6.7 → wcp_library-1.6.9}/wcp_library/credentials/postgres.py +4 -4
  13. {wcp_library-1.6.7 → wcp_library-1.6.9}/README.md +0 -0
  14. {wcp_library-1.6.7 → wcp_library-1.6.9}/wcp_library/browser_automation/__init__.py +0 -0
  15. {wcp_library-1.6.7 → wcp_library-1.6.9}/wcp_library/credentials/__init__.py +0 -0
  16. {wcp_library-1.6.7 → wcp_library-1.6.9}/wcp_library/emailing.py +0 -0
  17. {wcp_library-1.6.7 → wcp_library-1.6.9}/wcp_library/ftp/__init__.py +0 -0
  18. {wcp_library-1.6.7 → wcp_library-1.6.9}/wcp_library/ftp/ftp.py +0 -0
  19. {wcp_library-1.6.7 → wcp_library-1.6.9}/wcp_library/ftp/sftp.py +0 -0
  20. {wcp_library-1.6.7 → wcp_library-1.6.9}/wcp_library/informatica.py +0 -0
  21. {wcp_library-1.6.7 → wcp_library-1.6.9}/wcp_library/logging.py +0 -0
  22. {wcp_library-1.6.7 → wcp_library-1.6.9}/wcp_library/selenium/__init__.py +0 -0
  23. {wcp_library-1.6.7 → wcp_library-1.6.9}/wcp_library/selenium/_selenium_driver.py +0 -0
  24. {wcp_library-1.6.7 → wcp_library-1.6.9}/wcp_library/selenium/selenium_helper.py +0 -0
  25. {wcp_library-1.6.7 → wcp_library-1.6.9}/wcp_library/sql/__init__.py +0 -0
  26. {wcp_library-1.6.7 → wcp_library-1.6.9}/wcp_library/sql/oracle.py +0 -0
  27. {wcp_library-1.6.7 → wcp_library-1.6.9}/wcp_library/sql/postgres.py +0 -0
  28. {wcp_library-1.6.7 → wcp_library-1.6.9}/wcp_library/time.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: wcp-library
3
- Version: 1.6.7
3
+ Version: 1.6.9
4
4
  Summary: Common utilites for internal development at WCP
5
5
  Author: Mitch-Petersen
6
6
  Author-email: mitch.petersen@wcap.ca
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "wcp-library"
3
- version = "1.6.7"
3
+ version = "1.6.9"
4
4
  description = "Common utilites for internal development at WCP"
5
5
  authors = [{name="Mitch-Petersen", email="mitch.petersen@wcap.ca"}]
6
6
  readme = "README.md"
@@ -5,7 +5,7 @@ import sys
5
5
  import time
6
6
  from functools import wraps
7
7
  from pathlib import Path
8
- from typing import Callable, Generator, Optional, Type
8
+ from typing import Callable, Generator, Optional, Type, Union
9
9
 
10
10
  # PyInstaller import
11
11
  import pip_system_certs.wrapt_requests
@@ -42,7 +42,7 @@ def divide_chunks(list_obj: list, size: int) -> Generator:
42
42
 
43
43
 
44
44
  def retry(
45
- exceptions: tuple[Type[Exception]],
45
+ exceptions: tuple,
46
46
  max_attempts: Optional[int] = MAX_ATTEMPTS,
47
47
  delay: Optional[int] = DELAY,
48
48
  backoff: Optional[int] = BACKOFF,
@@ -87,7 +87,7 @@ def retry(
87
87
 
88
88
 
89
89
  def async_retry(
90
- exceptions: tuple[Type[Exception]],
90
+ exceptions: tuple,
91
91
  max_attempts: Optional[int] = MAX_ATTEMPTS,
92
92
  delay: Optional[int] = DELAY,
93
93
  backoff: Optional[int] = BACKOFF,
@@ -21,9 +21,12 @@ Usage:
21
21
  """
22
22
 
23
23
  import logging
24
+ import time
25
+ import inspect
24
26
  from typing import Dict, Optional
25
27
 
26
28
  from selenium import webdriver
29
+ import selenium.common.exceptions as selenium_exceptions
27
30
  from selenium.webdriver.chrome.options import Options as ChromeOptions
28
31
  from selenium.webdriver.firefox.options import Options as FirefoxOptions
29
32
  from selenium.webdriver.edge.options import Options as EdgeOptions
@@ -33,39 +36,6 @@ from wcp_library.browser_automation.interactions import UIInteractions, WEIntera
33
36
 
34
37
  logger = logging.getLogger(__name__)
35
38
 
36
- # +--------------------------------------------------------------------------------------------------------------------------------------------------------+
37
- # | === Browser options and usage === |
38
- # +--------------+--------------------------------------------+-----------------------------------------------+--------------------------------------------+
39
- # | Browser | Description | JSON Configuration | Possible Permutations |
40
- # +--------------+--------------------------------------------+-----------------------------------------------+--------------------------------------------+
41
- # | All Browsers | Set browser timeouts (in ms) | {"timeouts": {"implicit": 5000, ...}} | implicit, pageLoad, script |
42
- # | All Browsers | Name of the browser (e.g., 'chrome', ...) | {"browserName": "chrome"} | chrome, firefox, edge, safari |
43
- # | All Browsers | Specific version of the browser to use. | {"browserVersion": "latest"} | latest, 91.0, 90.0 |
44
- # | All Browsers | OS platform (e.g., 'Windows 10', 'Linux') | {"platformName": "Windows 10"} | Windows 10, Linux, macOS |
45
- # | All Browsers | Strategy for page loads: normal, eager... | {"pageLoadStrategy": "normal"} | normal, eager, none |
46
- # | All Browsers | Accept self-signed or invalid certs | {"acceptInsecureCerts": true} | true, false |
47
- # | Chrome | Run browser in headless mode | {"args": ["--headless"]} | --headless |
48
- # | Chrome | Disable GPU acceleration | {"args": ["--disable-gpu"]} | --disable-gpu |
49
- # | Chrome | Set experimental options | {"prefs": {"download.default_directory":...}} | profile.default_content_settings.popups... |
50
- # | Chrome | Set path to Chrome binary | {"binary": "/path/to/chrome"} | /path/to/chrome |
51
- # | Chrome | Set Chrome extensions | {"extensions": ["/path/to/extension"]} | /path/to/extension |
52
- # | Chrome | Exclude switches | {"excludeSwitches": ["enable-automation"]} | enable-automation |
53
- # | Chrome | Use automation extension | {"useAutomationExtension": false} | true, false |
54
- # | Firefox | Set download folder list | {"prefs": {"browser.download.folderList": 2}} | 1(Download folder), 2(User set directory) |
55
- # | Firefox | Set download directory | {"prefs": {"browser.download.dir": "/tmp"}} | /tmp |
56
- # | Firefox | Run Firefox in headless mode | {"args": ["-headless"]} | -headless |
57
- # | Firefox | Set Firefox log level | {"log": {"level": "trace"}} | trace, debug, info, warn, error |
58
- # | Firefox | Set Firefox profile | {"profile": "/path/to/profile"} | /path/to/profile |
59
- # | Firefox | Set path to Firefox binary | {"binary": "/path/to/firefox"} | /path/to/firefox |
60
- # | Edge | Run Edge in headless mode | {"args": ["--headless"]} | --headless |
61
- # | Edge | Set path to Edge binary | {"binary": "/path/to/edge"} | /path/to/edge |
62
- # | Edge | Use Chromium-based Edge | {"useChromium": true} | true, false |
63
- # | Edge | Set Edge Chromium driver | {"edgeChromiumDriver": "/path/to/driver"} | /path/to/driver |
64
- # | Chrome/Edge | Set initial window size | {"args": ["--window-size=1920,1080"]} | --window-size=int,int |
65
- # | Firefox | Launch in private browsing mode | {"args": ["-private"]} | -private |
66
- # +--------------+--------------------------------------------+-----------------------------------------------+--------------------------------------------+
67
-
68
-
69
39
  class BaseSelenium(UIInteractions, WEInteractions):
70
40
  """
71
41
  Base class for Selenium-based browser automation.
@@ -77,6 +47,15 @@ class BaseSelenium(UIInteractions, WEInteractions):
77
47
  :param driver: Selenium WebDriver instance (optional, defaults to None).
78
48
  """
79
49
 
50
+ class SeleniumExceptions:
51
+ """Dynamically collect all Selenium exception classes."""
52
+
53
+ ALL = tuple(
54
+ obj
55
+ for _, obj in inspect.getmembers(selenium_exceptions)
56
+ if inspect.isclass(obj) and issubclass(obj, Exception)
57
+ )
58
+
80
59
  def __init__(self, browser_options: dict = None):
81
60
  self.browser_options = browser_options or {}
82
61
  self.driver = None
@@ -88,7 +67,12 @@ class BaseSelenium(UIInteractions, WEInteractions):
88
67
 
89
68
  def __exit__(self, exc_type, exc_val, exc_tb) -> None:
90
69
  if exc_type:
91
- logger.error(f"Exception occurred: {exc_type.__name__}: {exc_val}\nTraceback: {exc_tb}")
70
+ logger.error(
71
+ "Exception occurred: %s: %s\nTraceback: %s",
72
+ exc_type.__name__ if exc_type else None,
73
+ exc_val,
74
+ exc_tb,
75
+ )
92
76
  if self.driver:
93
77
  self.driver.quit()
94
78
 
@@ -147,6 +131,17 @@ class BaseSelenium(UIInteractions, WEInteractions):
147
131
  }
148
132
  options.add_experimental_option("prefs", prefs)
149
133
 
134
+ def refresh_page(self) -> None:
135
+ """Refresh the current page.
136
+
137
+ :raises RuntimeError: If the WebDriver is not initialized.
138
+ """
139
+
140
+ if self.driver:
141
+ self.driver.refresh()
142
+ else:
143
+ raise RuntimeError("WebDriver is not initialized.")
144
+
150
145
  def go_to(self, url: str | URL):
151
146
  """Navigate to the specified URL.
152
147
 
@@ -181,8 +176,16 @@ class BaseSelenium(UIInteractions, WEInteractions):
181
176
  return self.driver.title
182
177
  raise RuntimeError("WebDriver is not initialized.")
183
178
 
179
+ def force_wait(self, wait_time: int) -> None:
180
+ """Forces the browser to wait for the specified time.
181
+
182
+ :param wait_time: The amount of time to wait.
183
+ :return: None
184
+ """
185
+ time.sleep(wait_time)
186
+
184
187
  def switch_to_window(
185
- self, window_handle: Optional[str] = None
188
+ self, window_handle: Optional[str | list] = None
186
189
  ) -> Optional[Dict[str, list]]:
187
190
  """
188
191
  Switches the browser context to a new window.
@@ -211,61 +214,20 @@ class BaseSelenium(UIInteractions, WEInteractions):
211
214
  "new_window": new_window,
212
215
  "all_windows": all_windows,
213
216
  }
217
+ self.force_wait(1)
214
218
  return None
215
219
 
216
220
  def close_window(self, window_handle: Optional[str] = None) -> None:
217
221
  """
218
- Closes the specified browser window.
219
-
220
- If a window handle is provided, that window will be closed.
221
- Otherwise, the currently active window will be closed.
222
-
222
+ Closes a browser window. If a specific window handle is provided, the driver
223
+ will close that window. Otherwise, the current window will be closed.
223
224
  :param window_handle: The handle of the window to close. If None, the current window will be closed.
225
+ :return: None
224
226
  """
225
-
226
- self.driver.close(window_handle or self.driver.current_window_handle)
227
-
228
-
229
- class Firefox(BaseSelenium):
230
- """
231
- Class for Firefox browser automation using Selenium.
232
-
233
- This class extends the BaseSelenium class and provides functionality for creating
234
- and managing Firefox WebDriver instances.
235
- """
236
-
237
- def create_driver(self) -> webdriver.Firefox:
238
- options = FirefoxOptions()
239
- self._add_options(options)
240
- return webdriver.Firefox(options=options)
241
-
242
-
243
- class Edge(BaseSelenium):
244
- """
245
- Class for Edge browser automation using Selenium.
246
-
247
- This class extends the BaseSelenium class and provides functionality for creating
248
- and managing Edge WebDriver instances.
249
- """
250
-
251
- def create_driver(self) -> webdriver.Edge:
252
- options = EdgeOptions()
253
- self._add_options(options)
254
- return webdriver.Edge(options=options)
255
-
256
-
257
- class Chrome(BaseSelenium):
258
- """
259
- Class for Chrome browser automation using Selenium.
260
-
261
- This class extends the BaseSelenium class and provides functionality for creating
262
- and managing Chrome WebDriver instances.
263
- """
264
-
265
- def create_driver(self) -> webdriver.Chrome:
266
- options = ChromeOptions()
267
- self._add_options(options)
268
- return webdriver.Chrome(options=options)
227
+ if window_handle:
228
+ current_window = self.driver.current_window_handle
229
+ self.switch_to_window(current_window)
230
+ self.driver.close()
269
231
 
270
232
 
271
233
  class Browser(BaseSelenium):
@@ -294,6 +256,53 @@ class Browser(BaseSelenium):
294
256
 
295
257
  def __exit__(self, exc_type, exc_val, exc_tb) -> None:
296
258
  if exc_type:
297
- logger.error(f"Exception occurred: {exc_type.__name__}: {exc_val}\nTraceback: {exc_tb}")
259
+ logger.error(
260
+ "Exception occurred: %s: %s\nTraceback: %s",
261
+ exc_type.__name__ if exc_type else None,
262
+ exc_val,
263
+ exc_tb,
264
+ )
298
265
  if self.browser_instance and self.browser_instance.driver:
299
266
  self.browser_instance.driver.quit()
267
+
268
+ class Firefox(BaseSelenium):
269
+ """
270
+ Class for Firefox browser automation using Selenium.
271
+
272
+ This class extends the BaseSelenium class and provides functionality for creating
273
+ and managing Firefox WebDriver instances.
274
+ """
275
+
276
+ def create_driver(self) -> webdriver.Firefox:
277
+ """Create a Firefox WebDriver instance with specified options."""
278
+ options = FirefoxOptions()
279
+ self._add_options(options)
280
+ return webdriver.Firefox(options=options)
281
+
282
+ class Edge(BaseSelenium):
283
+ """
284
+ Class for Edge browser automation using Selenium.
285
+
286
+ This class extends the BaseSelenium class and provides functionality for creating
287
+ and managing Edge WebDriver instances.
288
+ """
289
+
290
+ def create_driver(self) -> webdriver.Edge:
291
+ """Create an Edge WebDriver instance with specified options."""
292
+ options = EdgeOptions()
293
+ self._add_options(options)
294
+ return webdriver.Edge(options=options)
295
+
296
+ class Chrome(BaseSelenium):
297
+ """
298
+ Class for Chrome browser automation using Selenium.
299
+
300
+ This class extends the BaseSelenium class and provides functionality for creating
301
+ and managing Chrome WebDriver instances.
302
+ """
303
+
304
+ def create_driver(self) -> webdriver.Chrome:
305
+ """Create a Chrome WebDriver instance with specified options."""
306
+ options = ChromeOptions()
307
+ self._add_options(options)
308
+ return webdriver.Chrome(options=options)
@@ -10,6 +10,7 @@ Each class provides methods for performing various web interactions such as navi
10
10
  taking screenshots, waiting for elements, clicking buttons, entering text, and more.
11
11
  """
12
12
 
13
+ import time
13
14
  import logging
14
15
  from datetime import datetime
15
16
  from io import StringIO
@@ -29,6 +30,12 @@ from selenium.webdriver.support.ui import Select, WebDriverWait
29
30
  EXECUTION_ERROR_SCREENSHOT_FOLDER = "P:/Python/RPA/Execution Error Screenshots"
30
31
 
31
32
 
33
+ class BrowserInteractionError(Exception):
34
+ """
35
+ Exception raised when an Seemium interaction with the browser fails.
36
+ """
37
+
38
+
32
39
  class Interactions:
33
40
  """Class for interacting with web elements using Selenium WebDriver.
34
41
 
@@ -88,7 +95,7 @@ class Interactions:
88
95
  :raises RuntimeError: If the WebDriver is not initialized.
89
96
  """
90
97
 
91
- return (
98
+ return int(
92
99
  wait_time
93
100
  or (
94
101
  getattr(self, "browser_options", {})
@@ -180,19 +187,10 @@ class UIInteractions(Interactions):
180
187
  (self._get_locator(locator), element_value)
181
188
  )
182
189
  )
183
- except TimeoutException as exc:
184
- self._take_error_screenshot()
185
- raise TimeoutException(
186
- f"Timeout exception for element with locator {locator} and value {element_value}"
187
- ) from exc
188
- except NoSuchElementException as exc:
189
- self._take_error_screenshot()
190
- raise NoSuchElementException(
191
- f"Element with locator {locator} and value {element_value} not found."
192
- ) from exc
193
- except WebDriverException as exc:
190
+
191
+ except WebDriverException:
194
192
  self._take_error_screenshot()
195
- raise WebDriverException(f"WebDriverException occurred: {exc}") from exc
193
+ raise
196
194
 
197
195
  def get_multiple_elements(
198
196
  self,
@@ -224,25 +222,52 @@ class UIInteractions(Interactions):
224
222
  (self._get_locator(locator), element_value)
225
223
  )
226
224
  )
227
- except TimeoutException as exc:
225
+ except WebDriverException:
228
226
  self._take_error_screenshot()
229
- raise TimeoutException(
230
- f"Timeout exception for element with locator {locator} and value {element_value} not found."
231
- ) from exc
232
- except NoSuchElementException as exc:
233
- self._take_error_screenshot()
234
- raise NoSuchElementException(
235
- f"Element with locator {locator} and value {element_value} not found."
236
- ) from exc
237
- except WebDriverException as exc:
238
- self._take_error_screenshot()
239
- raise WebDriverException(f"WebDriverException occurred: {exc}") from exc
227
+ raise
228
+
229
+ def get_first_element(
230
+ self,
231
+ elements: list[dict],
232
+ wait_time: Optional[float] = 0,
233
+ ) -> WebElement:
234
+ """Get the first available WebElement from a list of element dictionaries.
235
+
236
+ Each dictionary can contain:
237
+ - "element": the element value (required)
238
+ - "locator": the locator type (optional, defaults to "css")
239
+ - "expected_condition": the condition to wait for (optional, defaults to "clickable")
240
+
241
+ :param elements: List of dictionaries defining elements.
242
+ :param wait_time: The wait time in milliseconds.
243
+ :raises RuntimeError: If the WebDriver is not initialized.
244
+ """
245
+
246
+ # Normalize each dictionary to (value, locator, condition)
247
+ normalized = []
248
+ for item in elements:
249
+ element_value = item.get("element")
250
+ if not element_value:
251
+ raise ValueError(f"Missing 'element' key in: {item}")
252
+ locator = item.get("locator") or "css"
253
+ condition = item.get("expected_condition") or "clickable"
254
+ normalized.append((element_value, locator, condition))
255
+
256
+ end_time = time.time() + self._get_wait_time(wait_time)
257
+ while time.time() < end_time:
258
+ for element_value, locator, condition in normalized:
259
+ try:
260
+ return self.get_element(element_value, locator, condition)
261
+ except WebDriverException:
262
+ continue
263
+ raise TimeoutException("No element became clickable within the timeout.")
240
264
 
241
265
  def get_text(
242
266
  self,
243
267
  element_value: str,
244
268
  locator: Optional[str] = None,
245
269
  expected_condition: Optional[str] = None,
270
+ wait_time: Optional[float] = 0,
246
271
  ) -> str:
247
272
  """Get the text of the WebElement based on the locator and expected condition.
248
273
 
@@ -253,17 +278,21 @@ class UIInteractions(Interactions):
253
278
  :param expected_condition: The expected condition type.
254
279
  Options: 'clickable'(Default), 'present', 'visible',
255
280
  'selected', 'frame_available'
281
+ :param wait_time: Time to wait for the condition.
256
282
  :return: The text of the located WebElement.
257
283
  :raises RuntimeError: If the WebDriver is not initialized.
258
284
  """
259
285
 
260
- return self.get_element(element_value, locator, expected_condition).text
286
+ return self.get_element(
287
+ element_value, locator, expected_condition, wait_time
288
+ ).text
261
289
 
262
290
  def get_table(
263
291
  self,
264
292
  element_value: str,
265
293
  locator: Optional[str] = None,
266
294
  expected_condition: Optional[str] = None,
295
+ wait_time: Optional[float] = 0,
267
296
  ) -> pd.DataFrame:
268
297
  """Get the data from a table element.
269
298
 
@@ -274,11 +303,14 @@ class UIInteractions(Interactions):
274
303
  :param expected_condition: The expected condition type.
275
304
  Options: 'clickable'(Default), 'present', 'visible',
276
305
  'selected', 'frame_available'
306
+ :param wait_time: Time to wait for the condition.
277
307
  :return: The data from the table element.
278
308
  :raises RuntimeError: If the WebDriver is not initialized.
279
309
  """
280
310
 
281
- element = self.get_element(element_value, locator, expected_condition)
311
+ element = self.get_element(
312
+ element_value, locator, expected_condition, wait_time
313
+ )
282
314
  return pd.read_html(StringIO(element.get_attribute("outerHTML")))[0]
283
315
 
284
316
  def get_value(
@@ -286,6 +318,7 @@ class UIInteractions(Interactions):
286
318
  element_value: str,
287
319
  locator: Optional[str] = None,
288
320
  expected_condition: Optional[str] = None,
321
+ wait_time: Optional[float] = 0,
289
322
  ) -> str:
290
323
  """Get the value attribute of the WebElement based on the locator and expected condition.
291
324
 
@@ -296,12 +329,13 @@ class UIInteractions(Interactions):
296
329
  :param expected_condition: The expected condition type.
297
330
  Options: 'clickable'(Default), 'present', 'visible',
298
331
  'selected', 'frame_available'
332
+ :param wait_time: Time to wait for the condition.
299
333
  :return: The value of the located WebElement.
300
334
  :raises RuntimeError: If the WebDriver is not initialized.
301
335
  """
302
336
 
303
337
  return self.get_element(
304
- element_value, locator, expected_condition
338
+ element_value, locator, expected_condition, wait_time
305
339
  ).get_attribute("value")
306
340
 
307
341
  def press_button(
@@ -309,6 +343,7 @@ class UIInteractions(Interactions):
309
343
  element_value: str,
310
344
  locator: Optional[str] = None,
311
345
  expected_condition: Optional[str] = None,
346
+ wait_time: Optional[float] = 0,
312
347
  ) -> None:
313
348
  """Click on the WebElement based on the locator and expected condition.
314
349
 
@@ -319,11 +354,14 @@ class UIInteractions(Interactions):
319
354
  :param expected_condition: The expected condition type.
320
355
  Options: 'clickable'(Default), 'present', 'visible',
321
356
  'selected', 'frame_available'
357
+ :param wait_time: Time to wait for the condition.
322
358
  :return:
323
359
  :raises RuntimeError: If the WebDriver is not initialized.
324
360
  """
325
361
 
326
- element = self.get_element(element_value, locator, expected_condition)
362
+ element = self.get_element(
363
+ element_value, locator, expected_condition, wait_time
364
+ )
327
365
  element.click()
328
366
 
329
367
  def enter_text(
@@ -332,6 +370,7 @@ class UIInteractions(Interactions):
332
370
  element_value: str,
333
371
  locator: Optional[str] = None,
334
372
  expected_condition: Optional[str] = None,
373
+ wait_time: Optional[float] = 0,
335
374
  ) -> None:
336
375
  """Populate the text field with the provided text.
337
376
 
@@ -343,13 +382,19 @@ class UIInteractions(Interactions):
343
382
  :param expected_condition: The expected condition type.
344
383
  Options: 'clickable'(Default), 'present', 'visible',
345
384
  'selected', 'frame_available'
385
+ :param wait_time: Time to wait for the condition.
346
386
  :return:
347
387
  :raises RuntimeError: If the WebDriver is not initialized.
348
388
  """
349
389
 
350
- element = self.get_element(element_value, locator, expected_condition)
351
- element.clear()
352
- element.send_keys(text)
390
+ element = self.get_element(
391
+ element_value, locator, expected_condition, wait_time
392
+ )
393
+ try:
394
+ element.clear()
395
+ except WebDriverException:
396
+ pass
397
+ element.send_keys(str(text))
353
398
 
354
399
  def set_checkbox_state(
355
400
  self,
@@ -357,6 +402,7 @@ class UIInteractions(Interactions):
357
402
  element_value: str,
358
403
  locator: Optional[str] = None,
359
404
  expected_condition: Optional[str] = None,
405
+ wait_time: Optional[float] = 0,
360
406
  ) -> None:
361
407
  """Set the state of a checkbox.
362
408
 
@@ -368,11 +414,14 @@ class UIInteractions(Interactions):
368
414
  :param expected_condition: The expected condition type.
369
415
  Options: 'clickable'(Default), 'present', 'visible',
370
416
  'selected', 'frame_available'
417
+ :param wait_time: Time to wait for the condition.
371
418
  :return:
372
419
  :raises RuntimeError: If the WebDriver is not initialized.
373
420
  """
374
421
 
375
- element = self.get_element(element_value, locator, expected_condition)
422
+ element = self.get_element(
423
+ element_value, locator, expected_condition, wait_time
424
+ )
376
425
  if element.is_selected() != state:
377
426
  element.click()
378
427
 
@@ -383,23 +432,28 @@ class UIInteractions(Interactions):
383
432
  select_type: str = None,
384
433
  locator: Optional[str] = None,
385
434
  expected_condition: Optional[str] = None,
435
+ wait_time: Optional[float] = 0,
386
436
  ) -> None:
387
437
  """Select an option from a dropdown.
388
438
 
389
439
  :param option: The option to select. This can be the visible text,
390
440
  :param element_value: The value used to identify the element.
391
441
  :param select_type: The type of selection to perform.
442
+ Options: 'value'(Default), 'index', 'visible_text'
392
443
  :param locator: The locator type.
393
444
  Options: 'css'(Default), 'id', 'name', 'class', 'tag',
394
445
  'xpath', 'link_text', 'partial_link_text'
395
446
  :param expected_condition: The expected condition type.
396
447
  Options: 'clickable'(Default), 'present', 'visible',
397
448
  'selected', 'frame_available'
449
+ :param wait_time: Time to wait for the condition.
398
450
  :return:
399
451
  :raises RuntimeError: If the WebDriver is not initialized.
400
452
  """
401
453
 
402
- element = self.get_element(element_value, locator, expected_condition)
454
+ element = self.get_element(
455
+ element_value, locator, expected_condition, wait_time
456
+ )
403
457
  select = Select(element)
404
458
  if select_type == "index":
405
459
  select.select_by_index(int(option))
@@ -431,47 +485,15 @@ class UIInteractions(Interactions):
431
485
  or an exception occurs.
432
486
  :raises RuntimeError: If the WebDriver is not initialized.
433
487
  """
434
-
435
488
  try:
436
- return self.get_element(
437
- element_value, locator, expected_condition, wait_time
489
+ return WebDriverWait(self.driver, self._get_wait_time(wait_time)).until(
490
+ self._get_expected_condition(expected_condition)(
491
+ (self._get_locator(locator), element_value)
492
+ )
438
493
  )
439
- except (TimeoutException, NoSuchElementException):
494
+ except WebDriverException:
440
495
  return False
441
496
 
442
- def text_is_present(
443
- self,
444
- text: str,
445
- element_value: str,
446
- locator: Optional[str] = None,
447
- text_location: Optional[str] = None,
448
- wait_time: Optional[float] = 0,
449
- ) -> bool:
450
- """
451
- Checks whether the specified text is present within a web element.
452
-
453
- :param text: The text to verify within the element.
454
- :param element_value: The value used to identify the element.
455
- :param locator: The locator type.
456
- Options: 'css'(Default), 'id', 'name', 'class', 'tag',
457
- 'xpath', 'link_text', 'partial_link_text'
458
- :param text_location: Where in the element to look for the text.
459
- Options: 'anywhere'(Default), 'attribute', 'value'
460
- :param wait_time: Time to wait for the condition.
461
- :return: True if the text is found within the element, False otherwise.
462
- :raises RuntimeError: If the WebDriver is not initialized.
463
- """
464
-
465
- expected_condition = EC.text_to_be_present_in_element
466
- if text_location == "attribute":
467
- expected_condition = EC.text_to_be_present_in_element_attribute
468
- elif text_location == "value":
469
- expected_condition = EC.text_to_be_present_in_element_value
470
-
471
- return WebDriverWait(self.driver, self._get_wait_time(wait_time)).until(
472
- expected_condition((self._get_locator(locator), element_value), text)
473
- )
474
-
475
497
  def wait_for_element(
476
498
  self,
477
499
  element_value: str,
@@ -498,6 +520,39 @@ class UIInteractions(Interactions):
498
520
  element_value, locator, expected_condition, self._get_wait_time(wait_time)
499
521
  )
500
522
 
523
+ def text_is_present(
524
+ self,
525
+ text: str,
526
+ element_value: str,
527
+ locator: Optional[str] = None,
528
+ text_location: Optional[str] = None,
529
+ wait_time: Optional[float] = 0,
530
+ ) -> bool:
531
+ """
532
+ Checks whether the specified text is present within a web element.
533
+
534
+ :param text: The text to verify within the element.
535
+ :param element_value: The value used to identify the element.
536
+ :param locator: The locator type.
537
+ Options: 'css'(Default), 'id', 'name', 'class', 'tag',
538
+ 'xpath', 'link_text', 'partial_link_text'
539
+ :param text_location: Where in the element to look for the text.
540
+ Options: 'anywhere'(Default), 'attribute', 'value'
541
+ :param wait_time: Time to wait for the condition.
542
+ :return: True if the text is found within the element, False otherwise.
543
+ :raises RuntimeError: If the WebDriver is not initialized.
544
+ """
545
+
546
+ expected_condition = EC.text_to_be_present_in_element
547
+ if text_location == "attribute":
548
+ expected_condition = EC.text_to_be_present_in_element_attribute
549
+ elif text_location == "value":
550
+ expected_condition = EC.text_to_be_present_in_element_value
551
+
552
+ return WebDriverWait(self.driver, self._get_wait_time(wait_time)).until(
553
+ expected_condition((self._get_locator(locator), element_value), text)
554
+ )
555
+
501
556
 
502
557
  class WEInteractions(Interactions):
503
558
  """Class for interacting with web elements directly using WebElement instances."""
@@ -525,29 +580,6 @@ class WEInteractions(Interactions):
525
580
  expected_condition = EC.element_to_be_clickable
526
581
  return expected_condition
527
582
 
528
- def wait_for_element_we(
529
- self,
530
- web_element: WebElement,
531
- expected_condition: Optional[str] = None,
532
- wait_time: Optional[float] = 0,
533
- ) -> WebElement:
534
- """
535
- Wait for an element to be present directly using WebElement and expected condition.
536
-
537
- :param web_element: The WebElement to wait for.
538
- :param expected_condition: The expected condition type.
539
- Options: 'clickable'(Default), 'visible', 'selected', 'staleness'
540
- :param wait_time: Time to wait for the element.
541
- :return: The WebElement if it meets the expected condition.
542
- :raises RuntimeError: If the WebDriver is not initialized.
543
- """
544
-
545
- condition = self._get_expected_condition_we(expected_condition)
546
- WebDriverWait(self.driver, self._get_wait_time(wait_time)).until(
547
- condition(web_element)
548
- )
549
- return web_element
550
-
551
583
  def get_text_we(
552
584
  self,
553
585
  web_element: WebElement,
@@ -737,3 +769,26 @@ class WEInteractions(Interactions):
737
769
  )
738
770
  except (TimeoutException, NoSuchElementException):
739
771
  return False
772
+
773
+ def wait_for_element_we(
774
+ self,
775
+ web_element: WebElement,
776
+ expected_condition: Optional[str] = None,
777
+ wait_time: Optional[float] = 0,
778
+ ) -> WebElement:
779
+ """
780
+ Wait for an element to be present directly using WebElement and expected condition.
781
+
782
+ :param web_element: The WebElement to wait for.
783
+ :param expected_condition: The expected condition type.
784
+ Options: 'clickable'(Default), 'visible', 'selected', 'staleness'
785
+ :param wait_time: Time to wait for the element.
786
+ :return: The WebElement if it meets the expected condition.
787
+ :raises RuntimeError: If the WebDriver is not initialized.
788
+ """
789
+
790
+ condition = self._get_expected_condition_we(expected_condition)
791
+ WebDriverWait(self.driver, self._get_wait_time(wait_time)).until(
792
+ condition(web_element)
793
+ )
794
+ return web_element
@@ -10,9 +10,9 @@ logger = logging.getLogger(__name__)
10
10
 
11
11
 
12
12
  class AsyncCredentialManager(ABC):
13
- def __init__(self, passwordState_api_key: str, password_list_id: int):
13
+ def __init__(self, api_key: str, password_list_id: int):
14
14
  self.password_url = URL("https://vault.wcap.ca/api/passwords/")
15
- self.api_key = passwordState_api_key
15
+ self.api_key = api_key
16
16
  self.headers = {"APIKey": self.api_key, 'Reason': 'Python Script Access'}
17
17
  self._password_list_id = password_list_id
18
18
 
@@ -23,7 +23,7 @@ class AsyncCredentialManager(ABC):
23
23
  :return:
24
24
  """
25
25
 
26
- logger.debug("Getting credentials from PasswordState")
26
+ logger.debug("Getting credentials from Vault")
27
27
  url = (self.password_url / str(self._password_list_id)).with_query("QueryAll")
28
28
  async with aiohttp.ClientSession() as session:
29
29
  async with session.get(str(url), headers=self.headers) as response:
@@ -118,7 +118,7 @@ class AsyncCredentialManager(ABC):
118
118
 
119
119
  async def update_credential(self, credentials_dict: dict) -> bool:
120
120
  """
121
- Update username and password in PasswordState
121
+ Update username and password in Vault
122
122
 
123
123
  Credentials dictionary must the same keys as the original dictionary from the get_credentials method
124
124
 
@@ -10,9 +10,9 @@ logger = logging.getLogger(__name__)
10
10
 
11
11
 
12
12
  class CredentialManager(ABC):
13
- def __init__(self, passwordState_api_key: str, password_list_id: int):
13
+ def __init__(self, api_key: str, password_list_id: int):
14
14
  self.password_url = URL("https://vault.wcap.ca/api/passwords/")
15
- self.api_key = passwordState_api_key
15
+ self.api_key = api_key
16
16
  self.headers = {"APIKey": self.api_key, 'Reason': 'Python Script Access'}
17
17
  self._password_list_id = password_list_id
18
18
 
@@ -23,7 +23,7 @@ class CredentialManager(ABC):
23
23
  :return: Dictionary of credentials
24
24
  """
25
25
 
26
- logger.debug("Getting credentials from PasswordState")
26
+ logger.debug("Getting credentials from Vault")
27
27
  url = (self.password_url / str(self._password_list_id)).with_query("QueryAll")
28
28
  passwords = requests.get(str(url), headers=self.headers).json()
29
29
 
@@ -7,8 +7,8 @@ logger = logging.getLogger(__name__)
7
7
 
8
8
 
9
9
  class APICredentialManager(CredentialManager):
10
- def __init__(self, passwordState_api_key: str):
11
- super().__init__(passwordState_api_key, 214)
10
+ def __init__(self, api_key: str):
11
+ super().__init__(api_key, 214)
12
12
 
13
13
  def new_credentials(self, credentials_dict: dict) -> bool:
14
14
  """
@@ -41,8 +41,8 @@ class APICredentialManager(CredentialManager):
41
41
 
42
42
 
43
43
  class AsyncAPICredentialManager(AsyncCredentialManager):
44
- def __init__(self, passwordState_api_key: str):
45
- super().__init__(passwordState_api_key, 214)
44
+ def __init__(self, api_key: str):
45
+ super().__init__(api_key, 214)
46
46
 
47
47
  async def new_credentials(self, credentials_dict: dict) -> bool:
48
48
  """
@@ -7,8 +7,8 @@ logger = logging.getLogger(__name__)
7
7
 
8
8
 
9
9
  class FTPCredentialManager(CredentialManager):
10
- def __init__(self, passwordState_api_key: str):
11
- super().__init__(passwordState_api_key, 208)
10
+ def __init__(self, api_key: str):
11
+ super().__init__(api_key, 208)
12
12
 
13
13
  def new_credentials(self, credentials_dict: dict) -> bool:
14
14
  """
@@ -40,8 +40,8 @@ class FTPCredentialManager(CredentialManager):
40
40
 
41
41
 
42
42
  class AsyncFTPCredentialManager(AsyncCredentialManager):
43
- def __init__(self, passwordState_api_key: str):
44
- super().__init__(passwordState_api_key, 208)
43
+ def __init__(self, api_key: str):
44
+ super().__init__(api_key, 208)
45
45
 
46
46
  async def new_credentials(self, credentials_dict: dict) -> bool:
47
47
  """
@@ -7,8 +7,8 @@ logger = logging.getLogger(__name__)
7
7
 
8
8
 
9
9
  class InternetCredentialManager(CredentialManager):
10
- def __init__(self, passwordState_api_key: str):
11
- super().__init__(passwordState_api_key, 93)
10
+ def __init__(self, api_key: str):
11
+ super().__init__(api_key, 93)
12
12
 
13
13
  def new_credentials(self, credentials_dict: dict) -> bool:
14
14
  """
@@ -37,8 +37,8 @@ class InternetCredentialManager(CredentialManager):
37
37
 
38
38
 
39
39
  class AsyncInternetCredentialManager(AsyncCredentialManager):
40
- def __init__(self, passwordState_api_key: str):
41
- super().__init__(passwordState_api_key, 93)
40
+ def __init__(self, api_key: str):
41
+ super().__init__(api_key, 93)
42
42
 
43
43
  async def new_credentials(self, credentials_dict: dict) -> bool:
44
44
  """
@@ -9,8 +9,8 @@ logger = logging.getLogger(__name__)
9
9
 
10
10
 
11
11
  class OracleCredentialManager(CredentialManager):
12
- def __init__(self, passwordState_api_key: str):
13
- super().__init__(passwordState_api_key, 207)
12
+ def __init__(self, api_key: str):
13
+ super().__init__(api_key, 207)
14
14
 
15
15
  def new_credentials(self, credentials_dict: dict) -> bool:
16
16
  """
@@ -43,8 +43,8 @@ class OracleCredentialManager(CredentialManager):
43
43
 
44
44
 
45
45
  class AsyncOracleCredentialManager(AsyncCredentialManager):
46
- def __init__(self, passwordState_api_key: str):
47
- super().__init__(passwordState_api_key, 207)
46
+ def __init__(self, api_key: str):
47
+ super().__init__(api_key, 207)
48
48
 
49
49
  async def new_credentials(self, credentials_dict: dict) -> bool:
50
50
  """
@@ -7,8 +7,8 @@ logger = logging.getLogger(__name__)
7
7
 
8
8
 
9
9
  class PostgresCredentialManager(CredentialManager):
10
- def __init__(self, passwordState_api_key: str):
11
- super().__init__(passwordState_api_key, 210)
10
+ def __init__(self, api_key: str):
11
+ super().__init__(api_key, 210)
12
12
 
13
13
  def new_credentials(self, credentials_dict: dict) -> bool:
14
14
  """
@@ -40,8 +40,8 @@ class PostgresCredentialManager(CredentialManager):
40
40
 
41
41
 
42
42
  class AsyncPostgresCredentialManager(AsyncCredentialManager):
43
- def __init__(self, passwordState_api_key: str):
44
- super().__init__(passwordState_api_key, 210)
43
+ def __init__(self, api_key: str):
44
+ super().__init__(api_key, 210)
45
45
 
46
46
  async def new_credentials(self, credentials_dict: dict) -> bool:
47
47
  """
File without changes