wcp-library 1.6.3__py3-none-any.whl → 1.6.5__py3-none-any.whl
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.
- wcp_library/__init__.py +106 -3
- wcp_library/browser_automation/__init__.py +0 -0
- wcp_library/browser_automation/browser.py +299 -0
- wcp_library/browser_automation/interactions.py +739 -0
- wcp_library/emailing.py +25 -0
- {wcp_library-1.6.3.dist-info → wcp_library-1.6.5.dist-info}/METADATA +1 -1
- {wcp_library-1.6.3.dist-info → wcp_library-1.6.5.dist-info}/RECORD +8 -5
- {wcp_library-1.6.3.dist-info → wcp_library-1.6.5.dist-info}/WHEEL +0 -0
wcp_library/__init__.py
CHANGED
@@ -1,14 +1,27 @@
|
|
1
|
+
import asyncio
|
2
|
+
import logging
|
3
|
+
import random
|
1
4
|
import sys
|
5
|
+
import time
|
6
|
+
from functools import wraps
|
2
7
|
from pathlib import Path
|
3
|
-
from typing import Generator
|
8
|
+
from typing import Callable, Generator, Optional, Type
|
4
9
|
|
5
10
|
# PyInstaller import
|
6
11
|
import pip_system_certs.wrapt_requests
|
7
12
|
import cryptography.hazmat.primitives.kdf.pbkdf2
|
8
13
|
|
14
|
+
logger = logging.getLogger(__name__)
|
15
|
+
|
16
|
+
# For retry logic
|
17
|
+
MAX_ATTEMPTS = 5
|
18
|
+
DELAY = 2
|
19
|
+
BACKOFF = 2
|
20
|
+
JITTER = 3
|
21
|
+
|
9
22
|
|
10
23
|
# Application Path
|
11
|
-
if getattr(sys,
|
24
|
+
if getattr(sys, "frozen", False):
|
12
25
|
application_path = sys.executable
|
13
26
|
application_path = Path(application_path).parent
|
14
27
|
else:
|
@@ -25,4 +38,94 @@ def divide_chunks(list_obj: list, size: int) -> Generator:
|
|
25
38
|
"""
|
26
39
|
|
27
40
|
for i in range(0, len(list_obj), size):
|
28
|
-
yield list_obj[i:i + size]
|
41
|
+
yield list_obj[i : i + size]
|
42
|
+
|
43
|
+
|
44
|
+
def retry(
|
45
|
+
exceptions: tuple[Type[Exception]],
|
46
|
+
max_attempts: Optional[int] = MAX_ATTEMPTS,
|
47
|
+
delay: Optional[int] = DELAY,
|
48
|
+
backoff: Optional[int] = BACKOFF,
|
49
|
+
jitter: Optional[int] = JITTER,
|
50
|
+
) -> Callable:
|
51
|
+
"""
|
52
|
+
Decorator to retry a synchronous function on a specified exception with exponential backoff and jitter.
|
53
|
+
|
54
|
+
:param exceptions: Tuple of exception types to catch and retry on.
|
55
|
+
:param max_attempts: Maximum number of retry attempts.
|
56
|
+
:param delay: Initial delay between retries (in seconds).
|
57
|
+
:param backoff: Multiplier to increase delay after each failure.
|
58
|
+
:param jitter: Maximum number of seconds to add randomly to each delay.
|
59
|
+
:return: The decorated function with retry logic.
|
60
|
+
"""
|
61
|
+
|
62
|
+
def decorator(func: Callable) -> Callable:
|
63
|
+
@wraps(func)
|
64
|
+
def wrapper(*args, **kwargs):
|
65
|
+
wait_time = delay
|
66
|
+
|
67
|
+
for attempt in range(0, max_attempts):
|
68
|
+
try:
|
69
|
+
return func(*args, **kwargs)
|
70
|
+
except exceptions as error:
|
71
|
+
if attempt == max_attempts:
|
72
|
+
logger.error("Retry failed after %d attempts.", max_attempts)
|
73
|
+
raise
|
74
|
+
|
75
|
+
randomized_delay = wait_time + random.uniform(0, jitter)
|
76
|
+
logger.warning(
|
77
|
+
"Attempt %d failed: %s. Retrying in %.2f seconds...",
|
78
|
+
attempt,
|
79
|
+
error,
|
80
|
+
randomized_delay,
|
81
|
+
)
|
82
|
+
time.sleep(randomized_delay)
|
83
|
+
wait_time *= backoff
|
84
|
+
return None
|
85
|
+
return wrapper
|
86
|
+
return decorator
|
87
|
+
|
88
|
+
|
89
|
+
def async_retry(
|
90
|
+
exceptions: tuple[Type[Exception]],
|
91
|
+
max_attempts: Optional[int] = MAX_ATTEMPTS,
|
92
|
+
delay: Optional[int] = DELAY,
|
93
|
+
backoff: Optional[int] = BACKOFF,
|
94
|
+
jitter: Optional[int] = JITTER,
|
95
|
+
) -> Callable:
|
96
|
+
"""
|
97
|
+
Decorator to retry an async function on a specified exception with exponential backoff and jitter.
|
98
|
+
|
99
|
+
:param exceptions: Tuple of exception types to catch and retry on.
|
100
|
+
:param max_attempts: Maximum number of retry attempts.
|
101
|
+
:param delay: Initial delay between retries (in seconds).
|
102
|
+
:param backoff: Multiplier to increase delay after each failure.
|
103
|
+
:param jitter: Maximum number of seconds to add randomly to each delay.
|
104
|
+
:return: The decorated async function with retry logic.
|
105
|
+
"""
|
106
|
+
|
107
|
+
def decorator(func: Callable) -> Callable:
|
108
|
+
@wraps(func)
|
109
|
+
async def wrapper(*args, **kwargs):
|
110
|
+
wait_time = delay
|
111
|
+
|
112
|
+
for attempt in range(0, max_attempts):
|
113
|
+
try:
|
114
|
+
return await func(*args, **kwargs)
|
115
|
+
except exceptions as error:
|
116
|
+
if attempt == max_attempts:
|
117
|
+
logger.error("Retry failed after %d attempts.", max_attempts)
|
118
|
+
raise
|
119
|
+
|
120
|
+
randomized_delay = wait_time + random.uniform(0, jitter)
|
121
|
+
logger.warning(
|
122
|
+
"Attempt %d failed: %s. Retrying in %.2f seconds...",
|
123
|
+
attempt,
|
124
|
+
error,
|
125
|
+
randomized_delay,
|
126
|
+
)
|
127
|
+
await asyncio.sleep(randomized_delay)
|
128
|
+
wait_time *= backoff
|
129
|
+
return None
|
130
|
+
return wrapper
|
131
|
+
return decorator
|
File without changes
|
@@ -0,0 +1,299 @@
|
|
1
|
+
"""
|
2
|
+
This module provides a framework for browser automation using Selenium WebDriver.
|
3
|
+
|
4
|
+
It defines a base class `BaseSelenium` that encapsulates shared functionality for browser setup,
|
5
|
+
option configuration, and lifecycle management. Subclasses for specific browsers—`Chrome`,
|
6
|
+
`Firefox`, and `Edge`—extend this base to implement browser-specific driver creation.
|
7
|
+
|
8
|
+
Additionally, the `Browser` context manager simplifies the use of these classes by managing
|
9
|
+
initialization and cleanup of browser sessions.
|
10
|
+
|
11
|
+
Classes:
|
12
|
+
BaseSelenium: Abstract base class for browser automation.
|
13
|
+
Chrome: Chrome-specific WebDriver implementation.
|
14
|
+
Firefox: Firefox-specific WebDriver implementation.
|
15
|
+
Edge: Edge-specific WebDriver implementation.
|
16
|
+
Browser: Context manager for browser session lifecycle.
|
17
|
+
|
18
|
+
Usage:
|
19
|
+
with Browser(Firefox) as browser:
|
20
|
+
browser.go_to("https://example.com")
|
21
|
+
"""
|
22
|
+
|
23
|
+
import logging
|
24
|
+
from typing import Dict, Optional
|
25
|
+
|
26
|
+
from selenium import webdriver
|
27
|
+
from selenium.webdriver.chrome.options import Options as ChromeOptions
|
28
|
+
from selenium.webdriver.firefox.options import Options as FirefoxOptions
|
29
|
+
from selenium.webdriver.edge.options import Options as EdgeOptions
|
30
|
+
from yarl import URL
|
31
|
+
|
32
|
+
from wcp_library.browser_automation.interactions import UIInteractions, WEInteractions
|
33
|
+
|
34
|
+
logger = logging.getLogger(__name__)
|
35
|
+
|
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
|
+
class BaseSelenium(UIInteractions, WEInteractions):
|
70
|
+
"""
|
71
|
+
Base class for Selenium-based browser automation.
|
72
|
+
|
73
|
+
This class provides common functionality for initializing and managing Selenium
|
74
|
+
WebDriver instances, as well as adding custom options to the WebDriver.
|
75
|
+
|
76
|
+
:param browser_options: Dictionary containing custom options for the WebDriver.
|
77
|
+
:param driver: Selenium WebDriver instance (optional, defaults to None).
|
78
|
+
"""
|
79
|
+
|
80
|
+
def __init__(self, browser_options: dict = None):
|
81
|
+
self.browser_options = browser_options or {}
|
82
|
+
self.driver = None
|
83
|
+
|
84
|
+
def __enter__(self) -> "BaseSelenium":
|
85
|
+
self.driver = self._create_driver()
|
86
|
+
super().__init__(self.driver)
|
87
|
+
return self
|
88
|
+
|
89
|
+
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
|
90
|
+
if exc_type:
|
91
|
+
logger.error(f"Exception occurred: {exc_type.__name__}: {exc_val}\nTraceback: {exc_tb}")
|
92
|
+
if self.driver:
|
93
|
+
self.driver.quit()
|
94
|
+
|
95
|
+
def _create_driver(self) -> webdriver:
|
96
|
+
"""
|
97
|
+
Abstract method to create a Selenium WebDriver instance.
|
98
|
+
|
99
|
+
This method must be implemented by subclasses to instantiate and return
|
100
|
+
a specific browser WebDriver (e.g., Chrome, Firefox, Edge).
|
101
|
+
|
102
|
+
:return: Selenium WebDriver instance for the specified browser.
|
103
|
+
:raises: NotImplementedError
|
104
|
+
"""
|
105
|
+
|
106
|
+
raise NotImplementedError("Subclasses must implement this method")
|
107
|
+
|
108
|
+
def _add_options(
|
109
|
+
self, options: ChromeOptions | FirefoxOptions | EdgeOptions
|
110
|
+
) -> None:
|
111
|
+
"""
|
112
|
+
Add custom options to the Selenium WebDriver.
|
113
|
+
|
114
|
+
This method applies custom options such as headless mode, download paths,
|
115
|
+
and command-line arguments to the WebDriver options.
|
116
|
+
|
117
|
+
:param options: ChromeOptions | FirefoxOptions | EdgeOptions
|
118
|
+
"""
|
119
|
+
|
120
|
+
if not self.browser_options:
|
121
|
+
return
|
122
|
+
|
123
|
+
# Apply standard Selenium options
|
124
|
+
for key, value in self.browser_options.items():
|
125
|
+
if hasattr(options, key) and "args" not in key:
|
126
|
+
setattr(options, key, value)
|
127
|
+
|
128
|
+
# Apply command-line arguments
|
129
|
+
args = self.browser_options.get("args", [])
|
130
|
+
for arg in args:
|
131
|
+
options.add_argument(arg)
|
132
|
+
|
133
|
+
# Handle download path
|
134
|
+
download_path = self.browser_options.get("download_path")
|
135
|
+
if download_path:
|
136
|
+
if isinstance(options, FirefoxOptions):
|
137
|
+
options.set_preference("browser.download.folderList", 2)
|
138
|
+
options.set_preference("browser.download.dir", str(download_path))
|
139
|
+
options.set_preference(
|
140
|
+
"browser.helperApps.neverAsk.saveToDisk", "application/octet-stream"
|
141
|
+
)
|
142
|
+
elif isinstance(options, (ChromeOptions, EdgeOptions)):
|
143
|
+
prefs = {
|
144
|
+
"download.default_directory": str(download_path),
|
145
|
+
"download.prompt_for_download": False,
|
146
|
+
"directory_upgrade": True,
|
147
|
+
}
|
148
|
+
options.add_experimental_option("prefs", prefs)
|
149
|
+
|
150
|
+
def go_to(self, url: str | URL):
|
151
|
+
"""Navigate to the specified URL.
|
152
|
+
|
153
|
+
:param url: The URL to navigate to.
|
154
|
+
:raises RuntimeError: If the WebDriver is not initialized.
|
155
|
+
"""
|
156
|
+
|
157
|
+
if self.driver:
|
158
|
+
self.driver.get(str(url))
|
159
|
+
else:
|
160
|
+
raise RuntimeError("WebDriver is not initialized.")
|
161
|
+
|
162
|
+
def get_url(self) -> str:
|
163
|
+
"""Get the current URL of the page.
|
164
|
+
|
165
|
+
:return: The current URL of the page.
|
166
|
+
:raises RuntimeError: If the WebDriver is not initialized.
|
167
|
+
"""
|
168
|
+
|
169
|
+
if self.driver:
|
170
|
+
return self.driver.current_url
|
171
|
+
raise RuntimeError("WebDriver is not initialized.")
|
172
|
+
|
173
|
+
def get_title(self) -> str:
|
174
|
+
"""Get the title of the current page.
|
175
|
+
|
176
|
+
:return: The title of the current page.
|
177
|
+
:raises RuntimeError: If the WebDriver is not initialized.
|
178
|
+
"""
|
179
|
+
|
180
|
+
if self.driver:
|
181
|
+
return self.driver.title
|
182
|
+
raise RuntimeError("WebDriver is not initialized.")
|
183
|
+
|
184
|
+
def switch_to_window(
|
185
|
+
self, window_handle: Optional[str] = None
|
186
|
+
) -> Optional[Dict[str, list]]:
|
187
|
+
"""
|
188
|
+
Switches the browser context to a new window.
|
189
|
+
|
190
|
+
If a specific window handle is provided, the driver will switch to that window.
|
191
|
+
Otherwise, it will attempt to switch to a newly opened window that is different
|
192
|
+
from the current one.
|
193
|
+
|
194
|
+
:param window_handle: The handle of the window to switch to. If None, the method will search for a new window handle.
|
195
|
+
:return: A dictionary containing the original window handle, the new window handle, and a list of all window handles at the time of switching.
|
196
|
+
:raises RuntimeError: If the WebDriver is not initialized.
|
197
|
+
"""
|
198
|
+
|
199
|
+
if window_handle:
|
200
|
+
self.driver.switch_to.window(window_handle)
|
201
|
+
return None
|
202
|
+
|
203
|
+
original_window = self.driver.current_window_handle
|
204
|
+
all_windows = self.driver.window_handles
|
205
|
+
|
206
|
+
for new_window in all_windows:
|
207
|
+
if new_window != original_window:
|
208
|
+
self.driver.switch_to.window(new_window)
|
209
|
+
return {
|
210
|
+
"original_window": original_window,
|
211
|
+
"new_window": new_window,
|
212
|
+
"all_windows": all_windows,
|
213
|
+
}
|
214
|
+
return None
|
215
|
+
|
216
|
+
def close_window(self, window_handle: Optional[str] = None) -> None:
|
217
|
+
"""
|
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
|
+
|
223
|
+
:param window_handle: The handle of the window to close. If None, the current window will be closed.
|
224
|
+
"""
|
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)
|
269
|
+
|
270
|
+
|
271
|
+
class Browser(BaseSelenium):
|
272
|
+
"""
|
273
|
+
Class for managing browser automation using Selenium.
|
274
|
+
|
275
|
+
This class provides functionality for initializing and managing browser instances
|
276
|
+
using the specified browser class and options.
|
277
|
+
|
278
|
+
It acts as a context manager, allowing for easy setup and teardown of browser sessions.
|
279
|
+
|
280
|
+
:param browser_class: The class of the browser to be used (e.g., Firefox, Edge, Chrome).
|
281
|
+
:param browser_options: Dictionary containing custom options for the browser.
|
282
|
+
"""
|
283
|
+
|
284
|
+
def __init__(self, browser_class: type, browser_options: dict = None):
|
285
|
+
super().__init__(browser_options)
|
286
|
+
self.browser_class = browser_class
|
287
|
+
self.browser_options = browser_options or {}
|
288
|
+
self.browser_instance = None
|
289
|
+
|
290
|
+
def __enter__(self) -> BaseSelenium:
|
291
|
+
self.browser_instance = self.browser_class(self.browser_options)
|
292
|
+
self.browser_instance.driver = self.browser_instance.create_driver()
|
293
|
+
return self.browser_instance
|
294
|
+
|
295
|
+
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
|
296
|
+
if exc_type:
|
297
|
+
logger.error(f"Exception occurred: {exc_type.__name__}: {exc_val}\nTraceback: {exc_tb}")
|
298
|
+
if self.browser_instance and self.browser_instance.driver:
|
299
|
+
self.browser_instance.driver.quit()
|
@@ -0,0 +1,739 @@
|
|
1
|
+
"""
|
2
|
+
This module provides classes and methods for interacting with web elements using Selenium WebDriver.
|
3
|
+
|
4
|
+
The module contains the following classes:
|
5
|
+
- Interactions: A base class for common web interactions.
|
6
|
+
- UIInteractions: A subclass of Interactions for interacting with web elements using locators.
|
7
|
+
- WEInteractions: A subclass of Interactions for interacting with web elements directly.
|
8
|
+
|
9
|
+
Each class provides methods for performing various web interactions such as navigating to a URL,
|
10
|
+
taking screenshots, waiting for elements, clicking buttons, entering text, and more.
|
11
|
+
"""
|
12
|
+
|
13
|
+
import logging
|
14
|
+
from datetime import datetime
|
15
|
+
from io import StringIO
|
16
|
+
from typing import List, Optional, Union
|
17
|
+
|
18
|
+
import pandas as pd
|
19
|
+
from selenium.common.exceptions import (
|
20
|
+
NoSuchElementException,
|
21
|
+
TimeoutException,
|
22
|
+
WebDriverException,
|
23
|
+
)
|
24
|
+
from selenium.webdriver.common.by import By
|
25
|
+
from selenium.webdriver.remote.webelement import WebElement
|
26
|
+
from selenium.webdriver.support import expected_conditions as EC
|
27
|
+
from selenium.webdriver.support.ui import Select, WebDriverWait
|
28
|
+
|
29
|
+
EXECUTION_ERROR_SCREENSHOT_FOLDER = "P:/Python/RPA/Execution Error Screenshots"
|
30
|
+
|
31
|
+
|
32
|
+
class Interactions:
|
33
|
+
"""Class for interacting with web elements using Selenium WebDriver.
|
34
|
+
|
35
|
+
Attributes:
|
36
|
+
driver: The Selenium WebDriver instance.
|
37
|
+
"""
|
38
|
+
|
39
|
+
def __init__(self, driver):
|
40
|
+
self.driver = driver
|
41
|
+
logging.basicConfig(level=logging.INFO)
|
42
|
+
|
43
|
+
def take_screenshot(self, file_path: str):
|
44
|
+
"""Take a screenshot of the current page and save it to the specified file path.
|
45
|
+
|
46
|
+
:param file_path: The path where the screenshot will be saved.
|
47
|
+
:raises RuntimeError: If the WebDriver is not initialized.
|
48
|
+
"""
|
49
|
+
|
50
|
+
if self.driver:
|
51
|
+
self.driver.save_screenshot(file_path)
|
52
|
+
else:
|
53
|
+
raise RuntimeError("WebDriver is not initialized.")
|
54
|
+
|
55
|
+
def _take_error_screenshot(self) -> None:
|
56
|
+
"""
|
57
|
+
Take a screenshot of the current page and save it to the P drive.
|
58
|
+
|
59
|
+
:return: None
|
60
|
+
:raises RuntimeError: If the WebDriver is not initialized.
|
61
|
+
"""
|
62
|
+
|
63
|
+
self.take_screenshot(
|
64
|
+
f"{EXECUTION_ERROR_SCREENSHOT_FOLDER}/Failure Screenshot - {datetime.now().strftime('%Y-%m-%d_%H-%M')}.png"
|
65
|
+
)
|
66
|
+
|
67
|
+
def _get_expect_condition_multiple(self, expected_condition: Optional[str]) -> EC:
|
68
|
+
"""Get the expected condition for multiple elements based on the provided string.
|
69
|
+
|
70
|
+
:param expected_condition: The expected condition type.
|
71
|
+
:return: The expected condition object for multiple elements.
|
72
|
+
:raises RuntimeError: If the WebDriver is not initialized.
|
73
|
+
"""
|
74
|
+
|
75
|
+
return (
|
76
|
+
EC.presence_of_all_elements_located
|
77
|
+
if expected_condition == "present"
|
78
|
+
else EC.visibility_of_all_elements_located
|
79
|
+
)
|
80
|
+
|
81
|
+
def _get_wait_time(self, wait_time: float) -> float:
|
82
|
+
"""If a wait time has been specified it is returned. Otherwise, the wait time comes from
|
83
|
+
the implicit timeout set when initializing the browser. That value is in milliseconds so
|
84
|
+
it is divided by 1000 (WebDriverWait expects a float number in seconds)
|
85
|
+
|
86
|
+
:param wait_time: The wait time in milliseconds.
|
87
|
+
:return: The wait time in seconds.
|
88
|
+
:raises RuntimeError: If the WebDriver is not initialized.
|
89
|
+
"""
|
90
|
+
|
91
|
+
return (
|
92
|
+
wait_time
|
93
|
+
or (
|
94
|
+
getattr(self, "browser_options", {})
|
95
|
+
.get("timeouts", {})
|
96
|
+
.get("implicit", 0)
|
97
|
+
)
|
98
|
+
/ 1000
|
99
|
+
) # Timeouts are in ms
|
100
|
+
|
101
|
+
|
102
|
+
class UIInteractions(Interactions):
|
103
|
+
"""Class for interacting with UI elements using Selenium WebDriver."""
|
104
|
+
|
105
|
+
def _get_locator(self, locator: str) -> str:
|
106
|
+
"""Get the locator type based on the provided string.
|
107
|
+
|
108
|
+
:param locator: The locator type.
|
109
|
+
:return: The locator type.
|
110
|
+
:raises RuntimeError: If the WebDriver is not initialized.
|
111
|
+
"""
|
112
|
+
|
113
|
+
match locator:
|
114
|
+
case "id":
|
115
|
+
by = By.ID
|
116
|
+
case "name":
|
117
|
+
by = By.NAME
|
118
|
+
case "class":
|
119
|
+
by = By.CLASS_NAME
|
120
|
+
case "tag":
|
121
|
+
by = By.TAG_NAME
|
122
|
+
case "xpath":
|
123
|
+
by = By.XPATH
|
124
|
+
case "link_text":
|
125
|
+
by = By.LINK_TEXT
|
126
|
+
case "partial_link_text":
|
127
|
+
by = By.PARTIAL_LINK_TEXT
|
128
|
+
case _:
|
129
|
+
by = By.CSS_SELECTOR
|
130
|
+
return by
|
131
|
+
|
132
|
+
def _get_expected_condition(self, expected_condition: Optional[str]) -> EC:
|
133
|
+
"""Get the expected condition based on the provided string.
|
134
|
+
|
135
|
+
:param expected_condition: The expected condition type.
|
136
|
+
:return: The expected condition object.
|
137
|
+
:raises RuntimeError: If the WebDriver is not initialized.
|
138
|
+
"""
|
139
|
+
|
140
|
+
match expected_condition:
|
141
|
+
case "present":
|
142
|
+
expected_condition = EC.presence_of_element_located
|
143
|
+
case "visible":
|
144
|
+
expected_condition = EC.visibility_of_element_located
|
145
|
+
case "selected":
|
146
|
+
expected_condition = EC.element_located_to_be_selected
|
147
|
+
case "frame_available":
|
148
|
+
expected_condition = EC.frame_to_be_available_and_switch_to_it
|
149
|
+
case _:
|
150
|
+
expected_condition = EC.element_to_be_clickable
|
151
|
+
return expected_condition
|
152
|
+
|
153
|
+
def get_element(
|
154
|
+
self,
|
155
|
+
element_value: str,
|
156
|
+
locator: Optional[str] = None,
|
157
|
+
expected_condition: Optional[str] = None,
|
158
|
+
wait_time: Optional[float] = 0,
|
159
|
+
) -> WebElement:
|
160
|
+
"""Get a single WebElement based on the expected condition, locator, and element_value.
|
161
|
+
|
162
|
+
:param element_value: The expected element value.
|
163
|
+
:param locator: The locator type.
|
164
|
+
Options: 'css'(Default), 'id', 'name', 'class', 'tag',
|
165
|
+
'xpath', 'link_text', 'partial_link_text'
|
166
|
+
:param expected_condition: The expected condition type.
|
167
|
+
Options: 'clickable'(Default), 'present', 'visible',
|
168
|
+
'selected', 'frame_available'
|
169
|
+
:param wait_time: Time to wait for the condition.
|
170
|
+
:return: The located WebElement.
|
171
|
+
:raises RuntimeError: If the WebDriver is not initialized.
|
172
|
+
:raises TimeoutException: If the element is not found within the wait time.
|
173
|
+
:raises NoSuchElementException: If the element is not found.
|
174
|
+
:raises WebDriverException: If a WebDriverException occurs.
|
175
|
+
"""
|
176
|
+
|
177
|
+
try:
|
178
|
+
return WebDriverWait(self.driver, self._get_wait_time(wait_time)).until(
|
179
|
+
self._get_expected_condition(expected_condition)(
|
180
|
+
(self._get_locator(locator), element_value)
|
181
|
+
)
|
182
|
+
)
|
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:
|
194
|
+
self._take_error_screenshot()
|
195
|
+
raise WebDriverException(f"WebDriverException occurred: {exc}") from exc
|
196
|
+
|
197
|
+
def get_multiple_elements(
|
198
|
+
self,
|
199
|
+
element_value: str,
|
200
|
+
locator: Optional[str] = None,
|
201
|
+
expected_condition: Optional[str] = None,
|
202
|
+
wait_time: Optional[float] = 0,
|
203
|
+
) -> List[WebElement]:
|
204
|
+
"""Get a list of WebElements based on the expected condition, locator, and element_value.
|
205
|
+
|
206
|
+
:param element_value: The expected element value.
|
207
|
+
:param locator: The locator type.
|
208
|
+
Options: 'css'(Default), 'id', 'name', 'class', 'tag',
|
209
|
+
'xpath', 'link_text', 'partial_link_text'
|
210
|
+
:param expected_condition: The expected condition type.
|
211
|
+
Options: 'clickable'(Default), 'present', 'visible',
|
212
|
+
'selected', 'frame_available'
|
213
|
+
:param wait_time: Time to wait for the condition.
|
214
|
+
:return: A list of located WebElements.
|
215
|
+
:raises RuntimeError: If the WebDriver is not initialized.
|
216
|
+
:raises TimeoutException: If the elements are not found within the wait time.
|
217
|
+
:raises NoSuchElementException: If the elements are not found.
|
218
|
+
:raises WebDriverException: If a WebDriverException occurs.
|
219
|
+
"""
|
220
|
+
|
221
|
+
try:
|
222
|
+
return WebDriverWait(self.driver, self._get_wait_time(wait_time)).until(
|
223
|
+
self._get_expect_condition_multiple(expected_condition)(
|
224
|
+
(self._get_locator(locator), element_value)
|
225
|
+
)
|
226
|
+
)
|
227
|
+
except TimeoutException as exc:
|
228
|
+
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
|
240
|
+
|
241
|
+
def get_text(
|
242
|
+
self,
|
243
|
+
element_value: str,
|
244
|
+
locator: Optional[str] = None,
|
245
|
+
expected_condition: Optional[str] = None,
|
246
|
+
) -> str:
|
247
|
+
"""Get the text of the WebElement based on the locator and expected condition.
|
248
|
+
|
249
|
+
:param element_value: The value used to identify the element.
|
250
|
+
:param locator: The locator type.
|
251
|
+
Options: 'css'(Default), 'id', 'name', 'class', 'tag',
|
252
|
+
'xpath', 'link_text', 'partial_link_text'
|
253
|
+
:param expected_condition: The expected condition type.
|
254
|
+
Options: 'clickable'(Default), 'present', 'visible',
|
255
|
+
'selected', 'frame_available'
|
256
|
+
:return: The text of the located WebElement.
|
257
|
+
:raises RuntimeError: If the WebDriver is not initialized.
|
258
|
+
"""
|
259
|
+
|
260
|
+
return self.get_element(element_value, locator, expected_condition).text
|
261
|
+
|
262
|
+
def get_table(
|
263
|
+
self,
|
264
|
+
element_value: str,
|
265
|
+
locator: Optional[str] = None,
|
266
|
+
expected_condition: Optional[str] = None,
|
267
|
+
) -> pd.DataFrame:
|
268
|
+
"""Get the data from a table element.
|
269
|
+
|
270
|
+
:param element_value: The value used to identify the element.
|
271
|
+
:param locator: The locator type.
|
272
|
+
Options: 'css'(Default), 'id', 'name', 'class', 'tag',
|
273
|
+
'xpath', 'link_text', 'partial_link_text'
|
274
|
+
:param expected_condition: The expected condition type.
|
275
|
+
Options: 'clickable'(Default), 'present', 'visible',
|
276
|
+
'selected', 'frame_available'
|
277
|
+
:return: The data from the table element.
|
278
|
+
:raises RuntimeError: If the WebDriver is not initialized.
|
279
|
+
"""
|
280
|
+
|
281
|
+
element = self.get_element(element_value, locator, expected_condition)
|
282
|
+
return pd.read_html(StringIO(element.get_attribute("outerHTML")))[0]
|
283
|
+
|
284
|
+
def get_value(
|
285
|
+
self,
|
286
|
+
element_value: str,
|
287
|
+
locator: Optional[str] = None,
|
288
|
+
expected_condition: Optional[str] = None,
|
289
|
+
) -> str:
|
290
|
+
"""Get the value attribute of the WebElement based on the locator and expected condition.
|
291
|
+
|
292
|
+
:param element_value: The value used to identify the element.
|
293
|
+
:param locator: The locator type.
|
294
|
+
Options: 'css'(Default), 'id', 'name', 'class', 'tag',
|
295
|
+
'xpath', 'link_text', 'partial_link_text'
|
296
|
+
:param expected_condition: The expected condition type.
|
297
|
+
Options: 'clickable'(Default), 'present', 'visible',
|
298
|
+
'selected', 'frame_available'
|
299
|
+
:return: The value of the located WebElement.
|
300
|
+
:raises RuntimeError: If the WebDriver is not initialized.
|
301
|
+
"""
|
302
|
+
|
303
|
+
return self.get_element(
|
304
|
+
element_value, locator, expected_condition
|
305
|
+
).get_attribute("value")
|
306
|
+
|
307
|
+
def press_button(
|
308
|
+
self,
|
309
|
+
element_value: str,
|
310
|
+
locator: Optional[str] = None,
|
311
|
+
expected_condition: Optional[str] = None,
|
312
|
+
) -> None:
|
313
|
+
"""Click on the WebElement based on the locator and expected condition.
|
314
|
+
|
315
|
+
:param element_value: The value used to identify the element.
|
316
|
+
:param locator: The locator type.
|
317
|
+
Options: 'css'(Default), 'id', 'name', 'class', 'tag',
|
318
|
+
'xpath', 'link_text', 'partial_link_text'
|
319
|
+
:param expected_condition: The expected condition type.
|
320
|
+
Options: 'clickable'(Default), 'present', 'visible',
|
321
|
+
'selected', 'frame_available'
|
322
|
+
:return:
|
323
|
+
:raises RuntimeError: If the WebDriver is not initialized.
|
324
|
+
"""
|
325
|
+
|
326
|
+
element = self.get_element(element_value, locator, expected_condition)
|
327
|
+
element.click()
|
328
|
+
|
329
|
+
def enter_text(
|
330
|
+
self,
|
331
|
+
text: str,
|
332
|
+
element_value: str,
|
333
|
+
locator: Optional[str] = None,
|
334
|
+
expected_condition: Optional[str] = None,
|
335
|
+
) -> None:
|
336
|
+
"""Populate the text field with the provided text.
|
337
|
+
|
338
|
+
:param text: The text to enter.
|
339
|
+
:param element_value: The value used to identify the element.
|
340
|
+
:param locator: The locator type.
|
341
|
+
Options: 'css'(Default), 'id', 'name', 'class', 'tag',
|
342
|
+
'xpath', 'link_text', 'partial_link_text'
|
343
|
+
:param expected_condition: The expected condition type.
|
344
|
+
Options: 'clickable'(Default), 'present', 'visible',
|
345
|
+
'selected', 'frame_available'
|
346
|
+
:return:
|
347
|
+
:raises RuntimeError: If the WebDriver is not initialized.
|
348
|
+
"""
|
349
|
+
|
350
|
+
element = self.get_element(element_value, locator, expected_condition)
|
351
|
+
element.clear()
|
352
|
+
element.send_keys(text)
|
353
|
+
|
354
|
+
def set_checkbox_state(
|
355
|
+
self,
|
356
|
+
state: bool,
|
357
|
+
element_value: str,
|
358
|
+
locator: Optional[str] = None,
|
359
|
+
expected_condition: Optional[str] = None,
|
360
|
+
) -> None:
|
361
|
+
"""Set the state of a checkbox.
|
362
|
+
|
363
|
+
:param state: The state to set.
|
364
|
+
:param element_value: The value used to identify the element.
|
365
|
+
:param locator: The locator type.
|
366
|
+
Options: 'css'(Default), 'id', 'name', 'class', 'tag',
|
367
|
+
'xpath', 'link_text', 'partial_link_text'
|
368
|
+
:param expected_condition: The expected condition type.
|
369
|
+
Options: 'clickable'(Default), 'present', 'visible',
|
370
|
+
'selected', 'frame_available'
|
371
|
+
:return:
|
372
|
+
:raises RuntimeError: If the WebDriver is not initialized.
|
373
|
+
"""
|
374
|
+
|
375
|
+
element = self.get_element(element_value, locator, expected_condition)
|
376
|
+
if element.is_selected() != state:
|
377
|
+
element.click()
|
378
|
+
|
379
|
+
def set_select_option(
|
380
|
+
self,
|
381
|
+
option: str,
|
382
|
+
element_value: str,
|
383
|
+
select_type: str = None,
|
384
|
+
locator: Optional[str] = None,
|
385
|
+
expected_condition: Optional[str] = None,
|
386
|
+
) -> None:
|
387
|
+
"""Select an option from a dropdown.
|
388
|
+
|
389
|
+
:param option: The option to select. This can be the visible text,
|
390
|
+
:param element_value: The value used to identify the element.
|
391
|
+
:param select_type: The type of selection to perform.
|
392
|
+
:param locator: The locator type.
|
393
|
+
Options: 'css'(Default), 'id', 'name', 'class', 'tag',
|
394
|
+
'xpath', 'link_text', 'partial_link_text'
|
395
|
+
:param expected_condition: The expected condition type.
|
396
|
+
Options: 'clickable'(Default), 'present', 'visible',
|
397
|
+
'selected', 'frame_available'
|
398
|
+
:return:
|
399
|
+
:raises RuntimeError: If the WebDriver is not initialized.
|
400
|
+
"""
|
401
|
+
|
402
|
+
element = self.get_element(element_value, locator, expected_condition)
|
403
|
+
select = Select(element)
|
404
|
+
if select_type == "index":
|
405
|
+
select.select_by_index(int(option))
|
406
|
+
elif select_type == "visible_text":
|
407
|
+
select.select_by_visible_text(option)
|
408
|
+
else:
|
409
|
+
select.select_by_value(option)
|
410
|
+
|
411
|
+
def web_page_contains(
|
412
|
+
self,
|
413
|
+
element_value: str,
|
414
|
+
locator: Optional[str] = None,
|
415
|
+
expected_condition: Optional[str] = None,
|
416
|
+
wait_time: Optional[float] = 0,
|
417
|
+
) -> Union[WebElement, bool]:
|
418
|
+
"""
|
419
|
+
Determine whether a web element is present on the page based on
|
420
|
+
the provided locator and expected condition.
|
421
|
+
|
422
|
+
:param element_value: The value used to identify the element.
|
423
|
+
:param locator: The locator type.
|
424
|
+
Options: 'css'(Default), 'id', 'name', 'class', 'tag',
|
425
|
+
'xpath', 'link_text', 'partial_link_text'
|
426
|
+
:param expected_condition: The expected condition type.
|
427
|
+
Options: 'clickable'(Default), 'present', 'visible',
|
428
|
+
'selected', 'frame_available'
|
429
|
+
:param wait_time: Time to wait for the condition.
|
430
|
+
:return: The located WebElement if found; otherwise, False if the element is not present
|
431
|
+
or an exception occurs.
|
432
|
+
:raises RuntimeError: If the WebDriver is not initialized.
|
433
|
+
"""
|
434
|
+
|
435
|
+
try:
|
436
|
+
return self.get_element(
|
437
|
+
element_value, locator, expected_condition, wait_time
|
438
|
+
)
|
439
|
+
except (TimeoutException, NoSuchElementException):
|
440
|
+
return False
|
441
|
+
|
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
|
+
def wait_for_element(
|
476
|
+
self,
|
477
|
+
element_value: str,
|
478
|
+
locator: Optional[str] = None,
|
479
|
+
expected_condition: Optional[str] = None,
|
480
|
+
wait_time: Optional[float] = 0,
|
481
|
+
) -> WebElement:
|
482
|
+
"""
|
483
|
+
Wait for an element to be present based on the locator and expected condition.
|
484
|
+
|
485
|
+
:param element_value: The value used to identify the element.
|
486
|
+
:param locator: The locator type.
|
487
|
+
Options: 'css'(Default), 'id', 'name', 'class', 'tag',
|
488
|
+
'xpath', 'link_text', 'partial_link_text'
|
489
|
+
:param expected_condition: The expected condition type.
|
490
|
+
Options: 'clickable'(Default), 'present', 'visible',
|
491
|
+
'selected', 'frame_available'
|
492
|
+
:param wait_time: Time to wait for the element.
|
493
|
+
:return: The located WebElement.
|
494
|
+
:raises RuntimeError: If the WebDriver is not initialized.
|
495
|
+
"""
|
496
|
+
|
497
|
+
return self.get_element(
|
498
|
+
element_value, locator, expected_condition, self._get_wait_time(wait_time)
|
499
|
+
)
|
500
|
+
|
501
|
+
|
502
|
+
class WEInteractions(Interactions):
|
503
|
+
"""Class for interacting with web elements directly using WebElement instances."""
|
504
|
+
|
505
|
+
def _get_expected_condition_we(self, expected_condition: Optional[str] = None):
|
506
|
+
"""
|
507
|
+
Return an expected condition that accepts a WebElement.
|
508
|
+
|
509
|
+
:param expected_condition: The expected condition type.
|
510
|
+
Options: 'clickable'(Default), 'visible', 'selected', 'staleness'
|
511
|
+
:return: A Selenium expected condition function.
|
512
|
+
:raises RuntimeError: If the WebDriver is not initialized.
|
513
|
+
"""
|
514
|
+
|
515
|
+
match expected_condition:
|
516
|
+
case "visible":
|
517
|
+
expected_condition = EC.visibility_of
|
518
|
+
case "invisible":
|
519
|
+
expected_condition = EC.invisibility_of_element
|
520
|
+
case "selected":
|
521
|
+
expected_condition = EC.element_to_be_selected
|
522
|
+
case "staleness":
|
523
|
+
expected_condition = EC.staleness_of
|
524
|
+
case _:
|
525
|
+
expected_condition = EC.element_to_be_clickable
|
526
|
+
return expected_condition
|
527
|
+
|
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
|
+
def get_text_we(
|
552
|
+
self,
|
553
|
+
web_element: WebElement,
|
554
|
+
expected_condition: Optional[str] = None,
|
555
|
+
wait_time: Optional[float] = 0,
|
556
|
+
) -> str:
|
557
|
+
"""
|
558
|
+
Get the text of the WebElement directly.
|
559
|
+
|
560
|
+
:param web_element: The WebElement to get text from.
|
561
|
+
:param expected_condition: The expected condition type.
|
562
|
+
Options: 'clickable'(Default), 'visible', 'selected', 'staleness'
|
563
|
+
:param wait_time: Time to wait for the element.
|
564
|
+
:return: The text of the WebElement.
|
565
|
+
:raises RuntimeError: If the WebDriver is not initialized.
|
566
|
+
"""
|
567
|
+
|
568
|
+
web_element = self.wait_for_element_we(
|
569
|
+
web_element, expected_condition, wait_time
|
570
|
+
)
|
571
|
+
return web_element.text
|
572
|
+
|
573
|
+
def get_table_we(
|
574
|
+
self,
|
575
|
+
web_element: WebElement,
|
576
|
+
expected_condition: Optional[str] = None,
|
577
|
+
wait_time: Optional[float] = 0,
|
578
|
+
) -> pd.DataFrame:
|
579
|
+
"""
|
580
|
+
Get the data from a table element directly.
|
581
|
+
|
582
|
+
:param web_element: The WebElement representing the table.
|
583
|
+
:param expected_condition: The expected condition type.
|
584
|
+
Options: 'clickable'(Default), 'visible', 'selected', 'staleness'
|
585
|
+
:param wait_time: Time to wait for the element.
|
586
|
+
:return: A DataFrame containing the table data.
|
587
|
+
:raises RuntimeError: If the WebDriver is not initialized.
|
588
|
+
"""
|
589
|
+
|
590
|
+
web_element = self.wait_for_element_we(
|
591
|
+
web_element, expected_condition, wait_time
|
592
|
+
)
|
593
|
+
return pd.read_html(StringIO(web_element.get_attribute("outerHTML")))[0]
|
594
|
+
|
595
|
+
def get_value_we(
|
596
|
+
self,
|
597
|
+
web_element: WebElement,
|
598
|
+
expected_condition: Optional[str] = None,
|
599
|
+
wait_time: Optional[float] = 0,
|
600
|
+
) -> str:
|
601
|
+
"""
|
602
|
+
Get the value attribute of the WebElement directly.
|
603
|
+
|
604
|
+
:param web_element: The WebElement to get value from.
|
605
|
+
:param expected_condition: The expected condition type.
|
606
|
+
Options: 'clickable'(Default), 'visible', 'selected', 'staleness'
|
607
|
+
:param wait_time: Time to wait for the element.
|
608
|
+
:return: The value attribute of the WebElement.
|
609
|
+
:raises RuntimeError: If the WebDriver is not initialized.
|
610
|
+
"""
|
611
|
+
|
612
|
+
web_element = self.wait_for_element_we(
|
613
|
+
web_element, expected_condition, wait_time
|
614
|
+
)
|
615
|
+
return web_element.get_attribute("value")
|
616
|
+
|
617
|
+
def press_button_we(
|
618
|
+
self,
|
619
|
+
web_element: WebElement,
|
620
|
+
expected_condition: Optional[str] = None,
|
621
|
+
wait_time: Optional[float] = 0,
|
622
|
+
) -> None:
|
623
|
+
"""Click on the WebElement directly.
|
624
|
+
|
625
|
+
:param web_element: The WebElement to click.
|
626
|
+
:param expected_condition: The expected condition type.
|
627
|
+
Options: 'clickable'(Default), 'visible', 'selected', 'staleness'
|
628
|
+
:param wait_time: Time to wait for the element.
|
629
|
+
:raises RuntimeError: If the WebDriver is not initialized.
|
630
|
+
"""
|
631
|
+
|
632
|
+
web_element = self.wait_for_element_we(
|
633
|
+
web_element, expected_condition, wait_time
|
634
|
+
)
|
635
|
+
web_element.click()
|
636
|
+
|
637
|
+
def enter_text_we(
|
638
|
+
self,
|
639
|
+
text: str,
|
640
|
+
web_element: WebElement,
|
641
|
+
expected_condition: Optional[str] = None,
|
642
|
+
wait_time: Optional[float] = 0,
|
643
|
+
) -> None:
|
644
|
+
"""Populate the text field with the provided text directly.
|
645
|
+
|
646
|
+
:param text: The text to enter.
|
647
|
+
:param web_element: The WebElement to populate.
|
648
|
+
:param expected_condition: The expected condition type.
|
649
|
+
Options: 'clickable'(Default), 'visible', 'selected', 'staleness'
|
650
|
+
:param wait_time: Time to wait for the element.
|
651
|
+
:raises RuntimeError: If the WebDriver is not initialized.
|
652
|
+
"""
|
653
|
+
|
654
|
+
web_element = self.wait_for_element_we(
|
655
|
+
web_element, expected_condition, wait_time
|
656
|
+
)
|
657
|
+
web_element.clear()
|
658
|
+
web_element.send_keys(text)
|
659
|
+
|
660
|
+
def set_checkbox_state_we(
|
661
|
+
self,
|
662
|
+
state: bool,
|
663
|
+
web_element: WebElement,
|
664
|
+
expected_condition: Optional[str] = None,
|
665
|
+
wait_time: Optional[float] = 0,
|
666
|
+
) -> None:
|
667
|
+
"""Set the state of a checkbox directly.
|
668
|
+
|
669
|
+
:param state: The state to set. True to check the checkbox, False to uncheck it.
|
670
|
+
:param web_element: The WebElement representing the checkbox.
|
671
|
+
:param expected_condition: The expected condition type.
|
672
|
+
Options: 'clickable'(Default), 'visible', 'selected', 'staleness'
|
673
|
+
:param wait_time: Time to wait for the element.
|
674
|
+
:raises RuntimeError: If the WebDriver is not initialized.
|
675
|
+
"""
|
676
|
+
|
677
|
+
web_element = self.wait_for_element_we(
|
678
|
+
web_element, expected_condition, wait_time
|
679
|
+
)
|
680
|
+
if web_element.is_selected() != state:
|
681
|
+
web_element.click()
|
682
|
+
|
683
|
+
def set_select_option_we(
|
684
|
+
self,
|
685
|
+
option: str,
|
686
|
+
web_element: WebElement,
|
687
|
+
select_type: str = None,
|
688
|
+
expected_condition: Optional[str] = None,
|
689
|
+
wait_time: Optional[float] = 0,
|
690
|
+
) -> None:
|
691
|
+
"""Select an option from a dropdown directly.
|
692
|
+
|
693
|
+
:param option: The option to select.
|
694
|
+
This can be the visible text, index, or value of the option.
|
695
|
+
Default is by value.
|
696
|
+
:param web_element: The WebElement representing the dropdown.
|
697
|
+
:param select_type: The type of selection to perform.
|
698
|
+
Options: 'value' (default), 'index', 'visible_text'
|
699
|
+
:param expected_condition: The expected condition type.
|
700
|
+
Options: 'clickable'(Default), 'visible', 'selected', 'staleness'
|
701
|
+
:param wait_time: Time to wait for the element.
|
702
|
+
:raises RuntimeError: If the WebDriver is not initialized.
|
703
|
+
"""
|
704
|
+
|
705
|
+
web_element = self.wait_for_element_we(
|
706
|
+
web_element, expected_condition, wait_time
|
707
|
+
)
|
708
|
+
select = Select(web_element)
|
709
|
+
if select_type == "index":
|
710
|
+
select.select_by_index(int(option))
|
711
|
+
elif select_type == "visible_text":
|
712
|
+
select.select_by_visible_text(option)
|
713
|
+
else:
|
714
|
+
select.select_by_value(option)
|
715
|
+
|
716
|
+
def web_page_contains_we(
|
717
|
+
self,
|
718
|
+
web_element: WebElement,
|
719
|
+
expected_condition: Optional[str] = None,
|
720
|
+
wait_time: Optional[float] = 0,
|
721
|
+
) -> WebElement | bool:
|
722
|
+
"""Check if the web page contains an element directly using WebElement
|
723
|
+
and expected condition.
|
724
|
+
|
725
|
+
:param web_element: The WebElement to check.
|
726
|
+
:param expected_condition: The expected condition type.
|
727
|
+
Options: 'clickable'(Default), 'visible', 'selected', 'staleness'
|
728
|
+
:param wait_time: Time to wait for the condition.
|
729
|
+
:return: The located WebElement if found; otherwise, False if the element is not present
|
730
|
+
or an exception occurs.
|
731
|
+
:raises RuntimeError: If the WebDriver is not initialized.
|
732
|
+
"""
|
733
|
+
|
734
|
+
try:
|
735
|
+
return self.wait_for_element_we(
|
736
|
+
web_element, expected_condition, self._get_wait_time(wait_time)
|
737
|
+
)
|
738
|
+
except (TimeoutException, NoSuchElementException):
|
739
|
+
return False
|
wcp_library/emailing.py
CHANGED
@@ -32,6 +32,31 @@ def send_email(sender: str, recipients: list, subject: str, message: str=None) -
|
|
32
32
|
server.quit()
|
33
33
|
|
34
34
|
|
35
|
+
def send_html_email(sender: str, recipients: list, subject: str, html_content: str) -> None:
|
36
|
+
"""
|
37
|
+
Function to send an HTML email
|
38
|
+
|
39
|
+
:param sender:
|
40
|
+
:param recipients:
|
41
|
+
:param subject:
|
42
|
+
:param html_content:
|
43
|
+
:return:
|
44
|
+
"""
|
45
|
+
|
46
|
+
msg = MIMEMultipart('alternative')
|
47
|
+
msg['From'] = sender
|
48
|
+
msg['To'] = ", ".join(recipients)
|
49
|
+
msg['Date'] = formatdate(localtime=True)
|
50
|
+
msg['Subject'] = subject
|
51
|
+
msg.attach(MIMEText(html_content, 'html'))
|
52
|
+
|
53
|
+
smtpServer = 'mail.wcap.ca'
|
54
|
+
server = smtplib.SMTP(smtpServer, 25)
|
55
|
+
server.ehlo()
|
56
|
+
server.sendmail(sender, recipients, msg.as_string())
|
57
|
+
server.quit()
|
58
|
+
|
59
|
+
|
35
60
|
def email_reporting(subject: str, message: str) -> None:
|
36
61
|
"""
|
37
62
|
Function to email the reporting team from the Python email
|
@@ -1,4 +1,7 @@
|
|
1
|
-
wcp_library/__init__.py,sha256=
|
1
|
+
wcp_library/__init__.py,sha256=kfv5jNFXKn_iHjGUOp_UDtqAFiogpVRcn4Tpmkho52o,4242
|
2
|
+
wcp_library/browser_automation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
|
+
wcp_library/browser_automation/browser.py,sha256=06xKu_lLifRzfj1TBlpZmKUeBoMqa7ye8kj0Aoy27uI,14551
|
4
|
+
wcp_library/browser_automation/interactions.py,sha256=YTYyII81M9qoVU_iYXoF5vHCc1PSj60hStPU-nT-qyA,28941
|
2
5
|
wcp_library/credentials/__init__.py,sha256=cfClJuFs9E3o_-5IPSKVJrkhQTiC3S2HhnZdrab9L6U,1856
|
3
6
|
wcp_library/credentials/_credential_manager_asynchronous.py,sha256=A7OJkBsNJ5fzNKoMih8GyLCIQytLViQgwccOrtdBOL8,6392
|
4
7
|
wcp_library/credentials/_credential_manager_synchronous.py,sha256=QUF3OOUgx0gu2PKFykkB69C-Kf_-9lyo2RIVA2JYvyc,5764
|
@@ -7,7 +10,7 @@ wcp_library/credentials/ftp.py,sha256=lSjOlfU68j9PsOCaoWJSdjbR1QmjTxjVmSxOpMdMG7
|
|
7
10
|
wcp_library/credentials/internet.py,sha256=z9tihFddTJcX2pt-iHFP_rkCoheLhTdFgn4ksc2sYYE,2239
|
8
11
|
wcp_library/credentials/oracle.py,sha256=OV02op95LNUT21cNDM4zDbD63C1ldiq--vYofDpU-zU,2869
|
9
12
|
wcp_library/credentials/postgres.py,sha256=B5qNPKYzuzOJZ5S4M3MCN4xUelvZ6AgyR6QJc4z0Cgo,2573
|
10
|
-
wcp_library/emailing.py,sha256=
|
13
|
+
wcp_library/emailing.py,sha256=g6g9ZHIMBq6fzUFEjhB54WnJOj0cZFVbjLsB29zAVlY,3135
|
11
14
|
wcp_library/ftp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
12
15
|
wcp_library/ftp/ftp.py,sha256=Zg8WMYfzUGnpqimLjtCuKyrtNGtv0Syupgi-rh_al00,3985
|
13
16
|
wcp_library/ftp/sftp.py,sha256=hykXGLGdxe7DYAxFdTwjPjTEOYuIpSMyK3NOiTQNUK0,4176
|
@@ -20,6 +23,6 @@ wcp_library/sql/__init__.py,sha256=f0JyVyFyUjueQ1XdVmCgmRjzkFENmFKl3BOzrvv3i1I,2
|
|
20
23
|
wcp_library/sql/oracle.py,sha256=GpqUn7srm4mGxeJAU06tHaMR37zxASSdMZJ3h8KTC-U,19240
|
21
24
|
wcp_library/sql/postgres.py,sha256=cLOurfmad3WqJcE7FiSxUyzo_0patpfMcTfQAoiteuU,18525
|
22
25
|
wcp_library/time.py,sha256=ktSzhK7SZnGPNXobFexnhFGQAcriLCJQKxnO0fed8fQ,1740
|
23
|
-
wcp_library-1.6.
|
24
|
-
wcp_library-1.6.
|
25
|
-
wcp_library-1.6.
|
26
|
+
wcp_library-1.6.5.dist-info/METADATA,sha256=CzgY0bCG4iFsZJhG4Y6Ff2mIaEgJX8QXmBqFqGtO988,1393
|
27
|
+
wcp_library-1.6.5.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
28
|
+
wcp_library-1.6.5.dist-info/RECORD,,
|
File without changes
|