pyxecm 1.4__py3-none-any.whl → 1.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.
Potentially problematic release.
This version of pyxecm might be problematic. Click here for more details.
- pyxecm/__init__.py +3 -0
- pyxecm/coreshare.py +2636 -0
- pyxecm/customizer/__init__.py +4 -0
- pyxecm/customizer/browser_automation.py +164 -54
- pyxecm/customizer/customizer.py +451 -235
- pyxecm/customizer/k8s.py +6 -6
- pyxecm/customizer/m365.py +1136 -221
- pyxecm/customizer/payload.py +13163 -5844
- pyxecm/customizer/pht.py +503 -0
- pyxecm/customizer/salesforce.py +694 -114
- pyxecm/customizer/sap.py +4 -4
- pyxecm/customizer/servicenow.py +1221 -0
- pyxecm/customizer/successfactors.py +1056 -0
- pyxecm/helper/__init__.py +2 -0
- pyxecm/helper/assoc.py +24 -1
- pyxecm/helper/data.py +1527 -0
- pyxecm/helper/web.py +170 -46
- pyxecm/helper/xml.py +170 -34
- pyxecm/otac.py +309 -23
- pyxecm/otcs.py +2779 -698
- pyxecm/otds.py +347 -108
- pyxecm/otmm.py +808 -0
- pyxecm/otpd.py +13 -10
- {pyxecm-1.4.dist-info → pyxecm-1.5.dist-info}/METADATA +3 -1
- pyxecm-1.5.dist-info/RECORD +30 -0
- {pyxecm-1.4.dist-info → pyxecm-1.5.dist-info}/WHEEL +1 -1
- pyxecm-1.4.dist-info/RECORD +0 -24
- {pyxecm-1.4.dist-info → pyxecm-1.5.dist-info}/LICENSE +0 -0
- {pyxecm-1.4.dist-info → pyxecm-1.5.dist-info}/top_level.txt +0 -0
pyxecm/customizer/__init__.py
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
"""PYXECM classes for Customizer"""
|
|
2
2
|
|
|
3
|
+
from .browser_automation import BrowserAutomation
|
|
4
|
+
|
|
3
5
|
from .customizer import Customizer
|
|
4
6
|
from .k8s import K8s
|
|
5
7
|
from .m365 import M365
|
|
6
8
|
from .payload import Payload
|
|
7
9
|
from .sap import SAP
|
|
8
10
|
from .salesforce import Salesforce
|
|
11
|
+
from .successfactors import SuccessFactors
|
|
12
|
+
from .servicenow import ServiceNow
|
|
@@ -9,6 +9,7 @@ Methods:
|
|
|
9
9
|
__init__ : class initializer. Start the browser session.
|
|
10
10
|
set_chrome_options: Sets chrome options for Selenium. Chrome options for headless browser is enabled
|
|
11
11
|
get_page: Load a page into the browser based on a given URL.
|
|
12
|
+
find_elem: Find an page element
|
|
12
13
|
find_elem_and_click: Find an page element and click it
|
|
13
14
|
find_elem_and_set: Find an page element and fill it with a new text.
|
|
14
15
|
find_element_and_download: Clicks a page element to initiate a download.
|
|
@@ -31,12 +32,17 @@ try:
|
|
|
31
32
|
from selenium.webdriver.chrome.options import Options
|
|
32
33
|
from selenium import webdriver
|
|
33
34
|
from selenium.webdriver.common.by import By
|
|
35
|
+
from selenium.webdriver.common.action_chains import ActionChains
|
|
36
|
+
from selenium.webdriver.remote.webelement import WebElement
|
|
34
37
|
from selenium.common.exceptions import (
|
|
35
38
|
WebDriverException,
|
|
36
39
|
NoSuchElementException,
|
|
37
40
|
ElementNotInteractableException,
|
|
38
41
|
ElementClickInterceptedException,
|
|
42
|
+
TimeoutException,
|
|
43
|
+
MoveTargetOutOfBoundsException,
|
|
39
44
|
)
|
|
45
|
+
|
|
40
46
|
except ModuleNotFoundError as module_exception:
|
|
41
47
|
logger.warning("Module selenium is not installed")
|
|
42
48
|
|
|
@@ -48,11 +54,14 @@ except ModuleNotFoundError as module_exception:
|
|
|
48
54
|
|
|
49
55
|
ID: str = ""
|
|
50
56
|
|
|
57
|
+
class WebElement:
|
|
58
|
+
"""Dummy class to avoid errors if selenium module cannot be imported"""
|
|
59
|
+
|
|
51
60
|
|
|
52
61
|
try:
|
|
53
62
|
import chromedriver_autoinstaller
|
|
54
63
|
except ModuleNotFoundError as module_exception:
|
|
55
|
-
logger.warning("Module chromedriver_autoinstaller is not installed")
|
|
64
|
+
logger.warning("Module chromedriver_autoinstaller is not installed!")
|
|
56
65
|
|
|
57
66
|
|
|
58
67
|
class BrowserAutomation:
|
|
@@ -60,9 +69,9 @@ class BrowserAutomation:
|
|
|
60
69
|
|
|
61
70
|
def __init__(
|
|
62
71
|
self,
|
|
63
|
-
base_url: str,
|
|
64
|
-
user_name: str,
|
|
65
|
-
user_password: str,
|
|
72
|
+
base_url: str = "",
|
|
73
|
+
user_name: str = "",
|
|
74
|
+
user_password: str = "",
|
|
66
75
|
download_directory: str = "/tmp",
|
|
67
76
|
take_screenshots: bool = False,
|
|
68
77
|
automation_name: str = "screen",
|
|
@@ -81,7 +90,7 @@ class BrowserAutomation:
|
|
|
81
90
|
automation_name
|
|
82
91
|
)
|
|
83
92
|
|
|
84
|
-
if self.take_screenshots:
|
|
93
|
+
if self.take_screenshots and not os.path.exists(self.screenshot_directory):
|
|
85
94
|
os.makedirs(self.screenshot_directory)
|
|
86
95
|
chromedriver_autoinstaller.install()
|
|
87
96
|
self.browser = webdriver.Chrome(options=self.set_chrome_options())
|
|
@@ -128,7 +137,7 @@ class BrowserAutomation:
|
|
|
128
137
|
screenshot_file = "{}/{}-{}.png".format(
|
|
129
138
|
self.screenshot_directory, self.screenshot_names, self.screen_counter
|
|
130
139
|
)
|
|
131
|
-
logger.
|
|
140
|
+
logger.debug("Save browser screenshot to -> %s", screenshot_file)
|
|
132
141
|
result = self.browser.get_screenshot_as_file(screenshot_file)
|
|
133
142
|
self.screen_counter += 1
|
|
134
143
|
|
|
@@ -146,13 +155,13 @@ class BrowserAutomation:
|
|
|
146
155
|
page_url = self.base_url + url
|
|
147
156
|
|
|
148
157
|
try:
|
|
149
|
-
logger.
|
|
158
|
+
logger.debug("Load page -> %s", page_url)
|
|
150
159
|
self.browser.get(page_url)
|
|
151
160
|
except WebDriverException as exception:
|
|
152
161
|
logger.error("Cannot load page -> %s; error -> %s", page_url, exception)
|
|
153
162
|
return False
|
|
154
163
|
|
|
155
|
-
logger.
|
|
164
|
+
logger.debug("Page title after get page -> %s", self.browser.title)
|
|
156
165
|
|
|
157
166
|
if self.take_screenshots:
|
|
158
167
|
self.take_screenshot()
|
|
@@ -161,14 +170,62 @@ class BrowserAutomation:
|
|
|
161
170
|
|
|
162
171
|
# end method definition
|
|
163
172
|
|
|
164
|
-
def
|
|
165
|
-
"""
|
|
173
|
+
def get_title(self) -> str:
|
|
174
|
+
"""Get the browser title. This is handy to validate a certain page is loaded after get_page()
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
str: Title of the browser window
|
|
178
|
+
"""
|
|
179
|
+
|
|
180
|
+
if not self.browser:
|
|
181
|
+
logger.error("Browser not initialized!")
|
|
182
|
+
return None
|
|
183
|
+
|
|
184
|
+
return self.browser.title
|
|
185
|
+
|
|
186
|
+
# end method definition
|
|
187
|
+
|
|
188
|
+
def scroll_to_element(self, element: WebElement):
|
|
189
|
+
"""Scroll an element into view to make it clickable
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
element (WebElement): Web element that has been identified before
|
|
193
|
+
"""
|
|
194
|
+
|
|
195
|
+
if not element:
|
|
196
|
+
logger.error("Undefined element!")
|
|
197
|
+
return
|
|
198
|
+
|
|
199
|
+
try:
|
|
200
|
+
actions = ActionChains(self.browser)
|
|
201
|
+
actions.move_to_element(element).perform()
|
|
202
|
+
except NoSuchElementException:
|
|
203
|
+
logger.error("Element not found in the DOM")
|
|
204
|
+
except TimeoutException:
|
|
205
|
+
logger.error("Timed out waiting for the element to be present or visible")
|
|
206
|
+
except ElementNotInteractableException:
|
|
207
|
+
logger.error("Element is not interactable!")
|
|
208
|
+
except MoveTargetOutOfBoundsException:
|
|
209
|
+
logger.error("Element is out of bounds!")
|
|
210
|
+
except WebDriverException as e:
|
|
211
|
+
logger.error("WebDriverException occurred -> %s", str(e))
|
|
212
|
+
|
|
213
|
+
# end method definition
|
|
214
|
+
|
|
215
|
+
def find_elem(
|
|
216
|
+
self,
|
|
217
|
+
find_elem: str,
|
|
218
|
+
find_method: str = By.ID,
|
|
219
|
+
show_error: bool = True,
|
|
220
|
+
) -> WebElement:
|
|
221
|
+
"""Find an page element.
|
|
166
222
|
|
|
167
223
|
Args:
|
|
168
224
|
find_elem (str): name of the page element
|
|
169
|
-
find_method (str): either By.ID, By.NAME, By.CLASS_NAME, BY.XPATH
|
|
225
|
+
find_method (str, optional): either By.ID, By.NAME, By.CLASS_NAME, BY.XPATH
|
|
226
|
+
show_error (bool, optional): show an error if the element is not found or not clickable
|
|
170
227
|
Returns:
|
|
171
|
-
|
|
228
|
+
WebElement: web element or None in case an error occured.
|
|
172
229
|
"""
|
|
173
230
|
|
|
174
231
|
# We don't want to expose class "By" outside this module,
|
|
@@ -183,30 +240,99 @@ class BrowserAutomation:
|
|
|
183
240
|
find_method = By.XPATH
|
|
184
241
|
else:
|
|
185
242
|
logger.error("Unsupported find method!")
|
|
186
|
-
return
|
|
243
|
+
return None
|
|
187
244
|
|
|
188
245
|
try:
|
|
189
246
|
elem = self.browser.find_element(by=find_method, value=find_elem)
|
|
190
247
|
except NoSuchElementException as exception:
|
|
248
|
+
if show_error:
|
|
249
|
+
logger.error(
|
|
250
|
+
"Cannot find page element -> %s by -> %s; error -> %s",
|
|
251
|
+
find_elem,
|
|
252
|
+
find_method,
|
|
253
|
+
exception,
|
|
254
|
+
)
|
|
255
|
+
return None
|
|
256
|
+
else:
|
|
257
|
+
logger.warning(
|
|
258
|
+
"Cannot find page element -> %s by -> %s",
|
|
259
|
+
find_elem,
|
|
260
|
+
find_method,
|
|
261
|
+
)
|
|
262
|
+
return None
|
|
263
|
+
except TimeoutException as exception:
|
|
191
264
|
logger.error(
|
|
192
|
-
"
|
|
193
|
-
find_elem,
|
|
194
|
-
find_method,
|
|
265
|
+
"Timed out waiting for the element to be present or visible; error -> %s",
|
|
195
266
|
exception,
|
|
196
267
|
)
|
|
268
|
+
return None
|
|
269
|
+
except ElementNotInteractableException as exception:
|
|
270
|
+
logger.error("Element is not interactable!; error -> %s", exception)
|
|
271
|
+
return None
|
|
272
|
+
except MoveTargetOutOfBoundsException:
|
|
273
|
+
logger.error("Element is out of bounds!")
|
|
274
|
+
return None
|
|
275
|
+
except WebDriverException as e:
|
|
276
|
+
logger.error("WebDriverException occurred -> %s", str(e))
|
|
277
|
+
return None
|
|
278
|
+
|
|
279
|
+
logger.debug("Found page element -> %s by -> %s", find_elem, find_method)
|
|
280
|
+
|
|
281
|
+
return elem
|
|
282
|
+
|
|
283
|
+
# end method definition
|
|
284
|
+
|
|
285
|
+
def find_elem_and_click(
|
|
286
|
+
self,
|
|
287
|
+
find_elem: str,
|
|
288
|
+
find_method: str = By.ID,
|
|
289
|
+
scroll_to_element: bool = True,
|
|
290
|
+
show_error: bool = True,
|
|
291
|
+
) -> bool:
|
|
292
|
+
"""Find an page element and click it.
|
|
293
|
+
|
|
294
|
+
Args:
|
|
295
|
+
find_elem (str): name of the page element
|
|
296
|
+
find_method (str, optional): either By.ID, By.NAME, By.CLASS_NAME, BY.XPATH
|
|
297
|
+
scroll_to_element (bool, optional): scroll the element into view
|
|
298
|
+
show_error (bool, optional): show an error if the element is not found or not clickable
|
|
299
|
+
Returns:
|
|
300
|
+
bool: True if successful, False otherwise
|
|
301
|
+
"""
|
|
302
|
+
|
|
303
|
+
if not find_elem:
|
|
304
|
+
if show_error:
|
|
305
|
+
logger.error("Missing element name! Cannot find HTML element!")
|
|
306
|
+
else:
|
|
307
|
+
logger.warning("Missing element name! Cannot find HTML element!")
|
|
197
308
|
return False
|
|
198
309
|
|
|
199
|
-
|
|
310
|
+
elem = self.find_elem(
|
|
311
|
+
find_elem=find_elem, find_method=find_method, show_error=show_error
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
if not elem:
|
|
315
|
+
return not show_error
|
|
200
316
|
|
|
201
317
|
try:
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
logger.error(
|
|
205
|
-
"Cannot click page element -> %s; error -> %s", find_elem, exception
|
|
206
|
-
)
|
|
207
|
-
return False
|
|
318
|
+
if scroll_to_element:
|
|
319
|
+
self.scroll_to_element(elem)
|
|
208
320
|
|
|
209
|
-
|
|
321
|
+
elem.click()
|
|
322
|
+
except (
|
|
323
|
+
ElementClickInterceptedException,
|
|
324
|
+
ElementNotInteractableException,
|
|
325
|
+
) as exception:
|
|
326
|
+
if show_error:
|
|
327
|
+
logger.error(
|
|
328
|
+
"Cannot click page element -> %s; error -> %s", find_elem, exception
|
|
329
|
+
)
|
|
330
|
+
return False
|
|
331
|
+
else:
|
|
332
|
+
logger.warning("Cannot click page element -> %s", find_elem)
|
|
333
|
+
return True
|
|
334
|
+
|
|
335
|
+
logger.debug("Successfully clicked element -> %s", find_elem)
|
|
210
336
|
|
|
211
337
|
if self.take_screenshots:
|
|
212
338
|
self.take_screenshot()
|
|
@@ -227,42 +353,23 @@ class BrowserAutomation:
|
|
|
227
353
|
Args:
|
|
228
354
|
find_elem (str): name of the page element
|
|
229
355
|
elem_value (str): new text string for the page element
|
|
230
|
-
find_method (str): either By.ID, By.NAME, By.CLASS_NAME, or By.XPATH
|
|
356
|
+
find_method (str, optional): either By.ID, By.NAME, By.CLASS_NAME, or By.XPATH
|
|
357
|
+
is_sensitive (bool, optional): True for suppressing sensitive information in logging
|
|
231
358
|
Returns:
|
|
232
359
|
bool: True if successful, False otherwise
|
|
233
360
|
"""
|
|
234
361
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
find_method = By.ID
|
|
239
|
-
elif find_method == "name":
|
|
240
|
-
find_method = By.NAME
|
|
241
|
-
elif find_method == "class_name":
|
|
242
|
-
find_method = By.CLASS_NAME
|
|
243
|
-
elif find_method == "xpath":
|
|
244
|
-
find_method = By.XPATH
|
|
245
|
-
else:
|
|
246
|
-
logger.error("Unsupported find method!")
|
|
247
|
-
return False
|
|
248
|
-
|
|
249
|
-
logger.info("Try to find element -> %s by -> %s...", find_elem, find_method)
|
|
362
|
+
elem = self.find_elem(
|
|
363
|
+
find_elem=find_elem, find_method=find_method, show_error=True
|
|
364
|
+
)
|
|
250
365
|
|
|
251
|
-
|
|
252
|
-
elem = self.browser.find_element(find_method, find_elem)
|
|
253
|
-
except NoSuchElementException as exception:
|
|
254
|
-
logger.error(
|
|
255
|
-
"Cannot find page element -> %s by -> %s; error -> %s",
|
|
256
|
-
find_elem,
|
|
257
|
-
find_method,
|
|
258
|
-
exception,
|
|
259
|
-
)
|
|
366
|
+
if not elem:
|
|
260
367
|
return False
|
|
261
368
|
|
|
262
369
|
if not is_sensitive:
|
|
263
|
-
logger.
|
|
370
|
+
logger.debug("Set element -> %s to value -> %s...", find_elem, elem_value)
|
|
264
371
|
else:
|
|
265
|
-
logger.
|
|
372
|
+
logger.debug("Set element -> %s to value -> <sensitive>...", find_elem)
|
|
266
373
|
|
|
267
374
|
try:
|
|
268
375
|
elem.clear() # clear existing text in the input field
|
|
@@ -322,13 +429,16 @@ class BrowserAutomation:
|
|
|
322
429
|
user_field: str = "otds_username",
|
|
323
430
|
password_field: str = "otds_password",
|
|
324
431
|
login_button: str = "loginbutton",
|
|
432
|
+
page: str = "",
|
|
325
433
|
) -> bool:
|
|
326
434
|
"""Login to target system via the browser"""
|
|
327
435
|
|
|
328
436
|
self.logged_in = False
|
|
329
437
|
|
|
330
438
|
if (
|
|
331
|
-
not self.get_page(
|
|
439
|
+
not self.get_page(
|
|
440
|
+
url=page
|
|
441
|
+
) # assuming the base URL leads towards the login page
|
|
332
442
|
or not self.find_elem_and_set(
|
|
333
443
|
find_elem=user_field, elem_value=self.user_name
|
|
334
444
|
)
|
|
@@ -346,7 +456,7 @@ class BrowserAutomation:
|
|
|
346
456
|
)
|
|
347
457
|
return False
|
|
348
458
|
|
|
349
|
-
logger.
|
|
459
|
+
logger.debug("Page title after login -> %s", self.browser.title)
|
|
350
460
|
|
|
351
461
|
# Some special handling for Salesforce login:
|
|
352
462
|
if "Verify" in self.browser.title:
|
|
@@ -375,7 +485,7 @@ class BrowserAutomation:
|
|
|
375
485
|
wait_time (float): time in seconds to wait
|
|
376
486
|
"""
|
|
377
487
|
|
|
378
|
-
logger.
|
|
488
|
+
logger.debug("Implicit wait for max -> %s seconds...", str(wait_time))
|
|
379
489
|
self.browser.implicitly_wait(wait_time)
|
|
380
490
|
|
|
381
491
|
def end_session(self):
|