pyxecm 3.0.1__py3-none-any.whl → 3.1.1__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/avts.py +4 -4
- pyxecm/coreshare.py +14 -15
- pyxecm/helper/data.py +2 -1
- pyxecm/helper/web.py +11 -11
- pyxecm/helper/xml.py +41 -10
- pyxecm/otac.py +1 -1
- pyxecm/otawp.py +19 -19
- pyxecm/otca.py +878 -70
- pyxecm/otcs.py +1716 -349
- pyxecm/otds.py +332 -153
- pyxecm/otkd.py +4 -4
- pyxecm/otmm.py +1 -1
- pyxecm/otpd.py +246 -30
- {pyxecm-3.0.1.dist-info → pyxecm-3.1.1.dist-info}/METADATA +2 -1
- pyxecm-3.1.1.dist-info/RECORD +82 -0
- pyxecm_api/app.py +45 -35
- pyxecm_api/auth/functions.py +2 -2
- pyxecm_api/auth/router.py +2 -3
- pyxecm_api/common/functions.py +67 -12
- pyxecm_api/settings.py +0 -8
- pyxecm_api/terminal/router.py +1 -1
- pyxecm_api/v1_csai/router.py +33 -18
- pyxecm_customizer/browser_automation.py +161 -79
- pyxecm_customizer/customizer.py +43 -25
- pyxecm_customizer/guidewire.py +422 -8
- pyxecm_customizer/k8s.py +23 -27
- pyxecm_customizer/knowledge_graph.py +498 -20
- pyxecm_customizer/m365.py +45 -44
- pyxecm_customizer/payload.py +1723 -1188
- pyxecm_customizer/payload_list.py +3 -0
- pyxecm_customizer/salesforce.py +122 -79
- pyxecm_customizer/servicenow.py +27 -7
- pyxecm_customizer/settings.py +3 -1
- pyxecm_customizer/successfactors.py +2 -2
- pyxecm_customizer/translate.py +1 -1
- pyxecm-3.0.1.dist-info/RECORD +0 -96
- pyxecm_api/agents/__init__.py +0 -7
- pyxecm_api/agents/app.py +0 -13
- pyxecm_api/agents/functions.py +0 -119
- pyxecm_api/agents/models.py +0 -10
- pyxecm_api/agents/otcm_knowledgegraph/__init__.py +0 -1
- pyxecm_api/agents/otcm_knowledgegraph/functions.py +0 -85
- pyxecm_api/agents/otcm_knowledgegraph/models.py +0 -61
- pyxecm_api/agents/otcm_knowledgegraph/router.py +0 -74
- pyxecm_api/agents/otcm_user_agent/__init__.py +0 -1
- pyxecm_api/agents/otcm_user_agent/models.py +0 -20
- pyxecm_api/agents/otcm_user_agent/router.py +0 -65
- pyxecm_api/agents/otcm_workspace_agent/__init__.py +0 -1
- pyxecm_api/agents/otcm_workspace_agent/models.py +0 -40
- pyxecm_api/agents/otcm_workspace_agent/router.py +0 -200
- {pyxecm-3.0.1.dist-info → pyxecm-3.1.1.dist-info}/WHEEL +0 -0
- {pyxecm-3.0.1.dist-info → pyxecm-3.1.1.dist-info}/entry_points.txt +0 -0
|
@@ -90,14 +90,20 @@ except ModuleNotFoundError:
|
|
|
90
90
|
# "wait until" strategy.
|
|
91
91
|
DEFAULT_WAIT_UNTIL_STRATEGY = "networkidle"
|
|
92
92
|
|
|
93
|
-
REQUEST_TIMEOUT = 30
|
|
94
|
-
REQUEST_RETRY_DELAY = 2
|
|
93
|
+
REQUEST_TIMEOUT = 30.0
|
|
94
|
+
REQUEST_RETRY_DELAY = 2.0
|
|
95
95
|
REQUEST_MAX_RETRIES = 3
|
|
96
96
|
|
|
97
97
|
|
|
98
98
|
class BrowserAutomation:
|
|
99
99
|
"""Class to automate settings via a browser interface."""
|
|
100
100
|
|
|
101
|
+
page: Page = None
|
|
102
|
+
browser: Browser = None
|
|
103
|
+
context: BrowserContext = None
|
|
104
|
+
playwright = None
|
|
105
|
+
proxy = None
|
|
106
|
+
|
|
101
107
|
logger: logging.Logger = default_logger
|
|
102
108
|
|
|
103
109
|
def __init__(
|
|
@@ -182,11 +188,10 @@ class BrowserAutomation:
|
|
|
182
188
|
self.screenshot_names,
|
|
183
189
|
"screenshots",
|
|
184
190
|
)
|
|
185
|
-
self.logger.debug("Creating
|
|
191
|
+
self.logger.debug("Creating screenshot directory... -> %s", self.screenshot_directory)
|
|
186
192
|
if self.take_screenshots and not os.path.exists(self.screenshot_directory):
|
|
187
193
|
os.makedirs(self.screenshot_directory)
|
|
188
194
|
|
|
189
|
-
self.proxy = None
|
|
190
195
|
if os.getenv("HTTP_PROXY"):
|
|
191
196
|
self.proxy = {
|
|
192
197
|
"server": os.getenv("HTTP_PROXY"),
|
|
@@ -194,21 +199,22 @@ class BrowserAutomation:
|
|
|
194
199
|
self.logger.info("Using HTTP proxy -> %s", os.getenv("HTTP_PROXY"))
|
|
195
200
|
|
|
196
201
|
browser = browser or os.getenv("BROWSER", "webkit")
|
|
197
|
-
self.logger.info("Using
|
|
202
|
+
self.logger.info("Using browser -> '%s'...", browser)
|
|
198
203
|
|
|
199
204
|
if not self.setup_playwright(browser=browser):
|
|
200
|
-
|
|
201
|
-
|
|
205
|
+
msg = "Failed to initialize Playwright browser automation!"
|
|
206
|
+
self.logger.error(msg)
|
|
207
|
+
raise RuntimeError(msg)
|
|
202
208
|
|
|
203
|
-
self.logger.info("Creating
|
|
209
|
+
self.logger.info("Creating browser context...")
|
|
204
210
|
self.context: BrowserContext = self.browser.new_context(
|
|
205
211
|
accept_downloads=True,
|
|
206
212
|
)
|
|
207
213
|
|
|
208
|
-
self.logger.info("Creating
|
|
214
|
+
self.logger.info("Creating page...")
|
|
209
215
|
self.page: Page = self.context.new_page()
|
|
210
216
|
self.main_page = self.page
|
|
211
|
-
self.logger.info("Browser
|
|
217
|
+
self.logger.info("Browser automation initialized.")
|
|
212
218
|
|
|
213
219
|
# end method definition
|
|
214
220
|
|
|
@@ -217,7 +223,12 @@ class BrowserAutomation:
|
|
|
217
223
|
|
|
218
224
|
Args:
|
|
219
225
|
browser (str):
|
|
220
|
-
Name of the browser engine.
|
|
226
|
+
Name of the browser engine. Supported:
|
|
227
|
+
* chromium
|
|
228
|
+
* chrome
|
|
229
|
+
* msedge
|
|
230
|
+
* webkit
|
|
231
|
+
* firefox
|
|
221
232
|
|
|
222
233
|
Returns:
|
|
223
234
|
bool:
|
|
@@ -232,6 +243,9 @@ class BrowserAutomation:
|
|
|
232
243
|
self.logger.error("Failed to start Playwright!")
|
|
233
244
|
return False
|
|
234
245
|
|
|
246
|
+
result = True
|
|
247
|
+
|
|
248
|
+
# Install and launch the selected browser in Playwright:
|
|
235
249
|
match browser:
|
|
236
250
|
case "chromium":
|
|
237
251
|
try:
|
|
@@ -239,10 +253,11 @@ class BrowserAutomation:
|
|
|
239
253
|
headless=self.headless, slow_mo=100 if not self.headless else None, proxy=self.proxy
|
|
240
254
|
)
|
|
241
255
|
except Exception:
|
|
242
|
-
self.install_browser(browser=browser)
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
256
|
+
result = self.install_browser(browser=browser)
|
|
257
|
+
if result:
|
|
258
|
+
self.browser: Browser = self.playwright.chromium.launch(
|
|
259
|
+
headless=self.headless, slow_mo=100 if not self.headless else None, proxy=self.proxy
|
|
260
|
+
)
|
|
246
261
|
|
|
247
262
|
case "chrome":
|
|
248
263
|
try:
|
|
@@ -253,13 +268,14 @@ class BrowserAutomation:
|
|
|
253
268
|
proxy=self.proxy,
|
|
254
269
|
)
|
|
255
270
|
except Exception:
|
|
256
|
-
self.install_browser(browser=browser)
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
271
|
+
result = self.install_browser(browser=browser)
|
|
272
|
+
if result:
|
|
273
|
+
self.browser: Browser = self.playwright.chromium.launch(
|
|
274
|
+
channel="chrome",
|
|
275
|
+
headless=self.headless,
|
|
276
|
+
slow_mo=100 if not self.headless else None,
|
|
277
|
+
proxy=self.proxy,
|
|
278
|
+
)
|
|
263
279
|
|
|
264
280
|
case "msedge":
|
|
265
281
|
try:
|
|
@@ -270,13 +286,14 @@ class BrowserAutomation:
|
|
|
270
286
|
proxy=self.proxy,
|
|
271
287
|
)
|
|
272
288
|
except Exception:
|
|
273
|
-
self.install_browser(browser=browser)
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
289
|
+
result = self.install_browser(browser=browser)
|
|
290
|
+
if result:
|
|
291
|
+
self.browser: Browser = self.playwright.chromium.launch(
|
|
292
|
+
channel="msedge",
|
|
293
|
+
headless=self.headless,
|
|
294
|
+
slow_mo=100 if not self.headless else None,
|
|
295
|
+
proxy=self.proxy,
|
|
296
|
+
)
|
|
280
297
|
|
|
281
298
|
case "webkit":
|
|
282
299
|
try:
|
|
@@ -284,10 +301,11 @@ class BrowserAutomation:
|
|
|
284
301
|
headless=self.headless, slow_mo=100 if not self.headless else None, proxy=self.proxy
|
|
285
302
|
)
|
|
286
303
|
except Exception:
|
|
287
|
-
self.install_browser(browser=browser)
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
304
|
+
result = self.install_browser(browser=browser)
|
|
305
|
+
if result:
|
|
306
|
+
self.browser: Browser = self.playwright.webkit.launch(
|
|
307
|
+
headless=self.headless, slow_mo=100 if not self.headless else None, proxy=self.proxy
|
|
308
|
+
)
|
|
291
309
|
|
|
292
310
|
case "firefox":
|
|
293
311
|
try:
|
|
@@ -295,16 +313,30 @@ class BrowserAutomation:
|
|
|
295
313
|
headless=self.headless, slow_mo=100 if not self.headless else None, proxy=self.proxy
|
|
296
314
|
)
|
|
297
315
|
except Exception:
|
|
298
|
-
self.install_browser(browser=browser)
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
316
|
+
result = self.install_browser(browser=browser)
|
|
317
|
+
if result:
|
|
318
|
+
self.browser: Browser = self.playwright.firefox.launch(
|
|
319
|
+
headless=self.headless, slow_mo=100 if not self.headless else None, proxy=self.proxy
|
|
320
|
+
)
|
|
321
|
+
case _:
|
|
322
|
+
self.logger.error("Unknown browser -> '%s'. Cannot install and launch it.", browser)
|
|
323
|
+
result = False
|
|
324
|
+
|
|
325
|
+
return result
|
|
303
326
|
|
|
304
327
|
# end method definition
|
|
305
328
|
|
|
306
329
|
def install_browser(self, browser: str) -> bool:
|
|
307
|
-
"""
|
|
330
|
+
"""Install a browser with a provided name in Playwright.
|
|
331
|
+
|
|
332
|
+
Args:
|
|
333
|
+
browser (str):
|
|
334
|
+
Name of the browser to be installed.
|
|
335
|
+
|
|
336
|
+
Returns:
|
|
337
|
+
bool: True = installation successful, False = installation failed.
|
|
338
|
+
|
|
339
|
+
"""
|
|
308
340
|
|
|
309
341
|
self.logger.info("Installing Browser -> '%s'...", browser)
|
|
310
342
|
process = subprocess.Popen(
|
|
@@ -314,12 +346,13 @@ class BrowserAutomation:
|
|
|
314
346
|
shell=False,
|
|
315
347
|
)
|
|
316
348
|
output, error = process.communicate()
|
|
317
|
-
if process.returncode == 0:
|
|
318
|
-
self.logger.info("
|
|
349
|
+
if process.returncode == 0: # 0 = success
|
|
350
|
+
self.logger.info("Successfuly completed installation of browser -> '%s'.", browser)
|
|
319
351
|
self.logger.debug(output.decode())
|
|
320
352
|
else:
|
|
321
|
-
self.logger.error("Installation failed
|
|
353
|
+
self.logger.error("Installation of browser -> '%s' failed! Error -> %s", browser, error.decode())
|
|
322
354
|
self.logger.error(output.decode())
|
|
355
|
+
return False
|
|
323
356
|
|
|
324
357
|
return True
|
|
325
358
|
|
|
@@ -351,19 +384,21 @@ class BrowserAutomation:
|
|
|
351
384
|
|
|
352
385
|
# end method definition
|
|
353
386
|
|
|
354
|
-
def take_screenshot(self) -> bool:
|
|
387
|
+
def take_screenshot(self, suffix: str = "") -> bool:
|
|
355
388
|
"""Take a screenshot of the current browser window and save it as PNG file.
|
|
356
389
|
|
|
390
|
+
Args:
|
|
391
|
+
suffix (str, optional):
|
|
392
|
+
Optional suffix to append to the screenshot filename.
|
|
393
|
+
|
|
357
394
|
Returns:
|
|
358
395
|
bool:
|
|
359
396
|
True if successful, False otherwise
|
|
360
397
|
|
|
361
398
|
"""
|
|
362
399
|
|
|
363
|
-
screenshot_file = "{}/{}-{:02d}.png".format(
|
|
364
|
-
self.screenshot_directory,
|
|
365
|
-
self.screenshot_names,
|
|
366
|
-
self.screenshot_counter,
|
|
400
|
+
screenshot_file = "{}/{}-{:02d}{}.png".format(
|
|
401
|
+
self.screenshot_directory, self.screenshot_names, self.screenshot_counter, suffix
|
|
367
402
|
)
|
|
368
403
|
self.logger.debug("Save browser screenshot to -> %s", screenshot_file)
|
|
369
404
|
|
|
@@ -634,6 +669,7 @@ class BrowserAutomation:
|
|
|
634
669
|
wait_state: str = "visible",
|
|
635
670
|
exact_match: bool | None = None,
|
|
636
671
|
regex: bool = False,
|
|
672
|
+
occurrence: int = 1,
|
|
637
673
|
iframe: str | None = None,
|
|
638
674
|
repeat_reload: int | None = None,
|
|
639
675
|
repeat_reload_delay: int = 60,
|
|
@@ -653,10 +689,19 @@ class BrowserAutomation:
|
|
|
653
689
|
wait_state (str, optional):
|
|
654
690
|
Defines if we wait for attached (element is part of DOM) or
|
|
655
691
|
if we wait for elem to be visible (attached, displayed, and has non-zero size).
|
|
692
|
+
Possible values are:
|
|
693
|
+
* "attached" - the element is present in the DOM.
|
|
694
|
+
* "detached" - the element is not present in the DOM.
|
|
695
|
+
* "visible" - the element is visible (attached, displayed, and has non-zero size).
|
|
696
|
+
* "hidden" - the element is hidden (attached, but not displayed).
|
|
697
|
+
Default is "visible".
|
|
656
698
|
exact_match (bool | None, optional):
|
|
657
699
|
If an exact matching is required. Default is None (not set).
|
|
658
700
|
regex (bool, optional):
|
|
659
701
|
Should the name be interpreted as a regular expression?
|
|
702
|
+
occurrence (int, optional):
|
|
703
|
+
If multiple elements match the selector, this defines which one to return.
|
|
704
|
+
Default is 1 (the first one).
|
|
660
705
|
iframe (str | None):
|
|
661
706
|
Is the element in an iFrame? Then provide the name of the iframe with this parameter.
|
|
662
707
|
repeat_reload (int | None):
|
|
@@ -674,17 +719,22 @@ class BrowserAutomation:
|
|
|
674
719
|
|
|
675
720
|
"""
|
|
676
721
|
|
|
677
|
-
failure_message = "Cannot find page element with selector -> '{}' ({}){}{}".format(
|
|
722
|
+
failure_message = "Cannot find {} page element with selector -> '{}' ({}){}{}{}{}".format(
|
|
723
|
+
"occurence #{} of".format(occurrence) if occurrence > 1 else "any",
|
|
678
724
|
selector,
|
|
679
725
|
selector_type,
|
|
680
726
|
" and role type -> '{}'".format(role_type) if role_type else "",
|
|
681
727
|
" in iframe -> '{}'".format(iframe) if iframe else "",
|
|
728
|
+
", occurrence -> {}".format(occurrence) if occurrence > 1 else "",
|
|
729
|
+
", waiting for state -> '{}'".format(wait_state),
|
|
682
730
|
)
|
|
683
|
-
success_message = "Found page element with selector -> '{}' ('{}'){}{}".format(
|
|
731
|
+
success_message = "Found {} page element with selector -> '{}' ('{}'){}{}{}".format(
|
|
732
|
+
"occurence #{} of".format(occurrence) if occurrence > 1 else "a",
|
|
684
733
|
selector,
|
|
685
734
|
selector_type,
|
|
686
735
|
" and role type -> '{}'".format(role_type) if role_type else "",
|
|
687
736
|
" in iframe -> '{}'".format(iframe) if iframe else "",
|
|
737
|
+
", occurrence -> {}".format(occurrence) if occurrence > 1 else "",
|
|
688
738
|
)
|
|
689
739
|
|
|
690
740
|
def do_find() -> Locator | None:
|
|
@@ -709,8 +759,13 @@ class BrowserAutomation:
|
|
|
709
759
|
# are not yet loaded:
|
|
710
760
|
|
|
711
761
|
try:
|
|
762
|
+
index = occurrence - 1 # convert to 0-based index
|
|
763
|
+
if index < 0: # basic validation
|
|
764
|
+
self.logger.error("Occurrence must be >= 1")
|
|
765
|
+
return None
|
|
712
766
|
self.logger.debug(
|
|
713
|
-
"Wait for locator to find
|
|
767
|
+
"Wait for locator to find %selement with selector -> '%s' (%s%s%s) and state -> '%s'%s...",
|
|
768
|
+
"occurrence #{} of ".format(occurrence) if occurrence > 1 else "",
|
|
714
769
|
selector,
|
|
715
770
|
"selector type -> '{}'".format(selector_type),
|
|
716
771
|
", role type -> '{}'".format(role_type) if role_type else "",
|
|
@@ -718,7 +773,9 @@ class BrowserAutomation:
|
|
|
718
773
|
wait_state,
|
|
719
774
|
" in iframe -> '{}'".format(iframe) if iframe else "",
|
|
720
775
|
)
|
|
721
|
-
|
|
776
|
+
|
|
777
|
+
locator = locator.first if occurrence == 1 else locator.nth(index)
|
|
778
|
+
# Wait for the element to be in the desired state:
|
|
722
779
|
locator.wait_for(state=wait_state)
|
|
723
780
|
except PlaywrightError as pe:
|
|
724
781
|
if show_error and repeat_reload is None:
|
|
@@ -763,6 +820,7 @@ class BrowserAutomation:
|
|
|
763
820
|
selector: str,
|
|
764
821
|
selector_type: str = "id",
|
|
765
822
|
role_type: str | None = None,
|
|
823
|
+
occurrence: int = 1,
|
|
766
824
|
scroll_to_element: bool = True,
|
|
767
825
|
desired_checkbox_state: bool | None = None,
|
|
768
826
|
is_navigation_trigger: bool = False,
|
|
@@ -770,6 +828,7 @@ class BrowserAutomation:
|
|
|
770
828
|
is_page_close_trigger: bool = False,
|
|
771
829
|
wait_until: str | None = None,
|
|
772
830
|
wait_time: float = 0.0,
|
|
831
|
+
wait_state: str = "visible",
|
|
773
832
|
exact_match: bool | None = None,
|
|
774
833
|
regex: bool = False,
|
|
775
834
|
hover_only: bool = False,
|
|
@@ -793,6 +852,9 @@ class BrowserAutomation:
|
|
|
793
852
|
role_type (str | None, optional):
|
|
794
853
|
ARIA role when using selector_type="role", e.g., "button", "textbox".
|
|
795
854
|
If irrelevant then None should be passed for role_type.
|
|
855
|
+
occurrence (int, optional):
|
|
856
|
+
If multiple elements match the selector, this defines which one to return.
|
|
857
|
+
Default is 1 (the first one).
|
|
796
858
|
scroll_to_element (bool, optional):
|
|
797
859
|
Scroll the element into view.
|
|
798
860
|
desired_checkbox_state (bool | None, optional):
|
|
@@ -814,8 +876,17 @@ class BrowserAutomation:
|
|
|
814
876
|
This seems to be the safest one for OpenText Content Server.
|
|
815
877
|
* "domcontentloaded" - waits for the DOMContentLoaded event (HTML is parsed,
|
|
816
878
|
but subresources may still load).
|
|
817
|
-
wait_time (float):
|
|
818
|
-
Time in seconds to wait for elements to appear.
|
|
879
|
+
wait_time (float, optional):
|
|
880
|
+
Time in seconds to wait for elements to appear. Default is 0.0 (no wait).
|
|
881
|
+
wait_state (str, optional):
|
|
882
|
+
Defines if we wait for attached (element is part of DOM) or
|
|
883
|
+
if we wait for elem to be visible (attached, displayed, and has non-zero size).
|
|
884
|
+
Possible values are:
|
|
885
|
+
* "attached" - the element is present in the DOM.
|
|
886
|
+
* "detached" - the element is not present in the DOM.
|
|
887
|
+
* "visible" - the element is visible (attached, displayed, and has non-zero size).
|
|
888
|
+
* "hidden" - the element is hidden (attached, but not displayed).
|
|
889
|
+
Default is "visible".
|
|
819
890
|
exact_match (bool | None, optional):
|
|
820
891
|
If an exact matching is required. Default is None (not set).
|
|
821
892
|
regex (bool, optional):
|
|
@@ -852,6 +923,14 @@ class BrowserAutomation:
|
|
|
852
923
|
|
|
853
924
|
"""
|
|
854
925
|
|
|
926
|
+
if not selector:
|
|
927
|
+
failure_message = "Missing element selector! Cannot find page element!"
|
|
928
|
+
if show_error:
|
|
929
|
+
self.logger.error(failure_message)
|
|
930
|
+
else:
|
|
931
|
+
self.logger.warning(failure_message)
|
|
932
|
+
return False
|
|
933
|
+
|
|
855
934
|
success = True # Final return value
|
|
856
935
|
|
|
857
936
|
# If no specific wait until strategy is provided in the
|
|
@@ -865,20 +944,17 @@ class BrowserAutomation:
|
|
|
865
944
|
self.logger.info("Wait for %d milliseconds before clicking...", wait_time * 1000)
|
|
866
945
|
self.page.wait_for_timeout(wait_time * 1000)
|
|
867
946
|
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
if show_error:
|
|
871
|
-
self.logger.error(failure_message)
|
|
872
|
-
else:
|
|
873
|
-
self.logger.warning(failure_message)
|
|
874
|
-
return False
|
|
947
|
+
if self.take_screenshots:
|
|
948
|
+
self.take_screenshot(suffix="_wait_before_click")
|
|
875
949
|
|
|
876
950
|
elem = self.find_elem(
|
|
877
951
|
selector=selector,
|
|
878
952
|
selector_type=selector_type,
|
|
879
953
|
role_type=role_type,
|
|
954
|
+
wait_state=wait_state,
|
|
880
955
|
exact_match=exact_match,
|
|
881
956
|
regex=regex,
|
|
957
|
+
occurrence=occurrence,
|
|
882
958
|
iframe=iframe,
|
|
883
959
|
repeat_reload=repeat_reload,
|
|
884
960
|
repeat_reload_delay=repeat_reload_delay,
|
|
@@ -977,6 +1053,7 @@ class BrowserAutomation:
|
|
|
977
1053
|
value: str | bool,
|
|
978
1054
|
selector_type: str = "id",
|
|
979
1055
|
role_type: str | None = None,
|
|
1056
|
+
occurrence: int = 1,
|
|
980
1057
|
is_sensitive: bool = False,
|
|
981
1058
|
press_enter: bool = False,
|
|
982
1059
|
exact_match: bool | None = None,
|
|
@@ -998,6 +1075,9 @@ class BrowserAutomation:
|
|
|
998
1075
|
role_type (str | None, optional):
|
|
999
1076
|
ARIA role when using selector_type="role", e.g., "button", "textbox".
|
|
1000
1077
|
If irrelevant then None should be passed for role_type.
|
|
1078
|
+
occurrence (int, optional):
|
|
1079
|
+
If multiple elements match the selector, this defines which one to return.
|
|
1080
|
+
Default is 1 (the first one).
|
|
1001
1081
|
is_sensitive (bool, optional):
|
|
1002
1082
|
True for suppressing sensitive information in logging.
|
|
1003
1083
|
press_enter (bool, optional):
|
|
@@ -1028,6 +1108,7 @@ class BrowserAutomation:
|
|
|
1028
1108
|
role_type=role_type,
|
|
1029
1109
|
exact_match=exact_match,
|
|
1030
1110
|
regex=regex,
|
|
1111
|
+
occurrence=occurrence,
|
|
1031
1112
|
iframe=iframe,
|
|
1032
1113
|
show_error=True,
|
|
1033
1114
|
)
|
|
@@ -1172,7 +1253,7 @@ class BrowserAutomation:
|
|
|
1172
1253
|
self.logger.error("Download failed; error -> %s", str(e))
|
|
1173
1254
|
return None
|
|
1174
1255
|
|
|
1175
|
-
self.logger.info("
|
|
1256
|
+
self.logger.info("Downloaded file to -> %s", save_path)
|
|
1176
1257
|
|
|
1177
1258
|
return save_path
|
|
1178
1259
|
|
|
@@ -1223,13 +1304,13 @@ class BrowserAutomation:
|
|
|
1223
1304
|
Is the element in an iFrame? Then provide the name of the iframe with this parameter.
|
|
1224
1305
|
min_count (int):
|
|
1225
1306
|
Minimum number of required matches (# elements on page).
|
|
1226
|
-
wait_time (float):
|
|
1227
|
-
Time in seconds to wait for elements to appear.
|
|
1307
|
+
wait_time (float, optional):
|
|
1308
|
+
Time in seconds to wait for elements to appear. Default is 0.0 (no wait).
|
|
1228
1309
|
wait_state (str, optional):
|
|
1229
1310
|
Defines if we wait for attached (element is part of DOM) or
|
|
1230
1311
|
if we wait for elem to be visible (attached, displayed, and has non-zero size).
|
|
1231
|
-
show_error (bool):
|
|
1232
|
-
Whether to log warnings/errors.
|
|
1312
|
+
show_error (bool, optional):
|
|
1313
|
+
Whether to log warnings/errors. Default is True.
|
|
1233
1314
|
|
|
1234
1315
|
Returns:
|
|
1235
1316
|
bool | None:
|
|
@@ -1256,14 +1337,13 @@ class BrowserAutomation:
|
|
|
1256
1337
|
iframe=iframe,
|
|
1257
1338
|
)
|
|
1258
1339
|
if not locator:
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
)
|
|
1340
|
+
self.logger.error(
|
|
1341
|
+
"Failed to check if elements -> '%s' (%s) exist! Locator is undefined.", selector, selector_type
|
|
1342
|
+
)
|
|
1263
1343
|
return (None, 0)
|
|
1264
1344
|
|
|
1265
1345
|
self.logger.info(
|
|
1266
|
-
"Check if at least %d element%s found by selector -> %s (%s%s)%s%s%s...",
|
|
1346
|
+
"Check if at least %d element%s found by selector -> '%s' (%s%s)%s%s%s...",
|
|
1267
1347
|
min_count,
|
|
1268
1348
|
"s are" if min_count > 1 else " is",
|
|
1269
1349
|
selector,
|
|
@@ -1319,8 +1399,9 @@ class BrowserAutomation:
|
|
|
1319
1399
|
return (None, 0)
|
|
1320
1400
|
|
|
1321
1401
|
self.logger.info(
|
|
1322
|
-
"Found %s
|
|
1402
|
+
"Found %d element%s matching selector -> '%s' (%s%s).",
|
|
1323
1403
|
count,
|
|
1404
|
+
"s" if count > 1 else "",
|
|
1324
1405
|
selector,
|
|
1325
1406
|
"selector type -> '{}'".format(selector_type),
|
|
1326
1407
|
", role type -> '{}'".format(role_type) if role_type else "",
|
|
@@ -1336,7 +1417,7 @@ class BrowserAutomation:
|
|
|
1336
1417
|
|
|
1337
1418
|
matching_elems = []
|
|
1338
1419
|
|
|
1339
|
-
# Iterate over all elements found by the locator and
|
|
1420
|
+
# Iterate over all elements found by the locator and check if
|
|
1340
1421
|
# they comply with the additional value conditions (if provided).
|
|
1341
1422
|
# We collect all matching elements in a list:
|
|
1342
1423
|
for i in range(count):
|
|
@@ -1374,8 +1455,9 @@ class BrowserAutomation:
|
|
|
1374
1455
|
else:
|
|
1375
1456
|
success = True
|
|
1376
1457
|
self.logger.info(
|
|
1377
|
-
"Found %d matching
|
|
1458
|
+
"Found %d matching element%s.%s",
|
|
1378
1459
|
matching_elements_count,
|
|
1460
|
+
"s" if matching_elements_count > 1 else "",
|
|
1379
1461
|
" This is {} the minimum {} element{} probed for.".format(
|
|
1380
1462
|
"exactly" if matching_elements_count == min_count else "more than",
|
|
1381
1463
|
min_count,
|
|
@@ -1467,7 +1549,7 @@ class BrowserAutomation:
|
|
|
1467
1549
|
return False
|
|
1468
1550
|
|
|
1469
1551
|
if "Verify" in title:
|
|
1470
|
-
self.logger.error("Site is asking for a
|
|
1552
|
+
self.logger.error("Site is asking for a verification token. You may need to whitelist your IP!")
|
|
1471
1553
|
return False
|
|
1472
1554
|
if "Login" in title:
|
|
1473
1555
|
self.logger.error("Authentication failed. You may have given the wrong password!")
|
|
@@ -1492,9 +1574,9 @@ class BrowserAutomation:
|
|
|
1492
1574
|
|
|
1493
1575
|
"""
|
|
1494
1576
|
|
|
1495
|
-
self.logger.debug("Setting default timeout to ->
|
|
1577
|
+
self.logger.debug("Setting default timeout to -> %.2f seconds...", wait_time)
|
|
1496
1578
|
self.page.set_default_timeout(wait_time * 1000)
|
|
1497
|
-
self.logger.debug("Setting navigation timeout to ->
|
|
1579
|
+
self.logger.debug("Setting navigation timeout to -> %.2f seconds...", wait_time)
|
|
1498
1580
|
self.page.set_default_navigation_timeout(wait_time * 1000)
|
|
1499
1581
|
|
|
1500
1582
|
# end method definition
|
|
@@ -1502,11 +1584,11 @@ class BrowserAutomation:
|
|
|
1502
1584
|
def end_session(self) -> None:
|
|
1503
1585
|
"""End the browser session and close the browser."""
|
|
1504
1586
|
|
|
1505
|
-
self.logger.info("Close
|
|
1587
|
+
self.logger.info("Close browser page...")
|
|
1506
1588
|
self.page.close()
|
|
1507
|
-
self.logger.info("Close
|
|
1589
|
+
self.logger.info("Close browser context...")
|
|
1508
1590
|
self.context.close()
|
|
1509
|
-
self.logger.info("Close
|
|
1591
|
+
self.logger.info("Close browser...")
|
|
1510
1592
|
self.browser.close()
|
|
1511
1593
|
self.logged_in = False
|
|
1512
1594
|
self.logger.info("Stop Playwright instance...")
|