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.

@@ -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.info("Save browser screenshot to -> %s", screenshot_file)
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.info("Load page -> %s", page_url)
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.info("Page title after get page -> %s", self.browser.title)
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 find_elem_and_click(self, find_elem: str, find_method: str = By.ID) -> bool:
165
- """Find an page element and click it.
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
- bool: True if successful, False otherwise
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 False
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
- "Cannot find page element -> %s by -> %s; error -> %s",
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
- logger.info("Found element -> %s by -> %s", find_elem, find_method)
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
- elem.click()
203
- except ElementClickInterceptedException as exception:
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
- logger.info("Successfully clicked element -> %s", find_elem)
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
- # We don't want to expose class "By" outside this module,
236
- # so we map the string values to the By class values:
237
- if find_method == "id":
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
- try:
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.info("Set element -> %s to value -> %s...", find_elem, elem_value)
370
+ logger.debug("Set element -> %s to value -> %s...", find_elem, elem_value)
264
371
  else:
265
- logger.info("Set element -> %s to value -> <sensitive>...", find_elem)
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() # assuming the base URL leads towards the login 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.info("Page title after login -> %s", self.browser.title)
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.info("Implicit wait for max -> %s seconds...", str(wait_time))
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):