seleniumbase 4.33.4__py3-none-any.whl → 4.34.2__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.
Files changed (33) hide show
  1. seleniumbase/__version__.py +1 -1
  2. seleniumbase/behave/behave_sb.py +10 -2
  3. seleniumbase/console_scripts/run.py +6 -2
  4. seleniumbase/console_scripts/sb_commander.py +5 -5
  5. seleniumbase/console_scripts/sb_install.py +235 -6
  6. seleniumbase/console_scripts/sb_mkdir.py +1 -0
  7. seleniumbase/core/browser_launcher.py +358 -105
  8. seleniumbase/core/log_helper.py +33 -12
  9. seleniumbase/core/proxy_helper.py +35 -30
  10. seleniumbase/core/sb_cdp.py +277 -74
  11. seleniumbase/core/settings_parser.py +2 -0
  12. seleniumbase/core/style_sheet.py +10 -0
  13. seleniumbase/fixtures/base_case.py +216 -127
  14. seleniumbase/fixtures/constants.py +3 -0
  15. seleniumbase/fixtures/js_utils.py +2 -0
  16. seleniumbase/fixtures/page_actions.py +7 -2
  17. seleniumbase/fixtures/shared_utils.py +25 -0
  18. seleniumbase/plugins/driver_manager.py +28 -0
  19. seleniumbase/plugins/pytest_plugin.py +110 -0
  20. seleniumbase/plugins/sb_manager.py +41 -0
  21. seleniumbase/plugins/selenium_plugin.py +9 -0
  22. seleniumbase/undetected/cdp_driver/_contradict.py +3 -3
  23. seleniumbase/undetected/cdp_driver/browser.py +8 -6
  24. seleniumbase/undetected/cdp_driver/cdp_util.py +3 -0
  25. seleniumbase/undetected/cdp_driver/config.py +0 -1
  26. seleniumbase/undetected/cdp_driver/element.py +22 -20
  27. seleniumbase/undetected/patcher.py +20 -5
  28. {seleniumbase-4.33.4.dist-info → seleniumbase-4.34.2.dist-info}/LICENSE +1 -1
  29. {seleniumbase-4.33.4.dist-info → seleniumbase-4.34.2.dist-info}/METADATA +111 -86
  30. {seleniumbase-4.33.4.dist-info → seleniumbase-4.34.2.dist-info}/RECORD +33 -33
  31. {seleniumbase-4.33.4.dist-info → seleniumbase-4.34.2.dist-info}/WHEEL +1 -1
  32. {seleniumbase-4.33.4.dist-info → seleniumbase-4.34.2.dist-info}/entry_points.txt +0 -0
  33. {seleniumbase-4.33.4.dist-info → seleniumbase-4.34.2.dist-info}/top_level.txt +0 -0
@@ -83,6 +83,9 @@ class CDPMethods():
83
83
  element.get_position = lambda: self.__get_position(element)
84
84
  element.get_html = lambda: self.__get_html(element)
85
85
  element.get_js_attributes = lambda: self.__get_js_attributes(element)
86
+ element.get_attribute = (
87
+ lambda attribute: self.__get_attribute(element, attribute)
88
+ )
86
89
  return element
87
90
 
88
91
  def get(self, url):
@@ -124,14 +127,14 @@ class CDPMethods():
124
127
  def add_handler(self, event, handler):
125
128
  self.page.add_handler(event, handler)
126
129
 
127
- def find_element(
128
- self, selector, best_match=False, timeout=settings.SMALL_TIMEOUT
129
- ):
130
+ def find_element(self, selector, best_match=False, timeout=None):
130
131
  """Similar to select(), but also finds elements by text content.
131
132
  When using text-based searches, if best_match=False, then will
132
133
  find the first element with the text. If best_match=True, then
133
134
  if multiple elements have that text, then will use the element
134
135
  with the closest text-length to the text being searched for."""
136
+ if not timeout:
137
+ timeout = settings.SMALL_TIMEOUT
135
138
  self.__add_light_pause()
136
139
  selector = self.__convert_to_css_if_xpath(selector)
137
140
  early_failure = False
@@ -164,7 +167,60 @@ class CDPMethods():
164
167
  self.__slow_mode_pause_if_set()
165
168
  return element
166
169
 
167
- def find_all(self, selector, timeout=settings.SMALL_TIMEOUT):
170
+ def find_element_by_text(self, text, tag_name=None, timeout=None):
171
+ """Returns an element by matching text.
172
+ Optionally, provide a tag_name to narrow down the search to an
173
+ element with the given tag. (Eg: a, button, div, script, span)"""
174
+ if not timeout:
175
+ timeout = settings.SMALL_TIMEOUT
176
+ self.__add_light_pause()
177
+ time_now = time.time()
178
+ self.assert_text(text, timeout=timeout)
179
+ spent = int(time.time() - time_now)
180
+ remaining = 1 + timeout - spent
181
+ if tag_name:
182
+ self.assert_element(tag_name, timeout=remaining)
183
+ elements = self.loop.run_until_complete(
184
+ self.page.find_elements_by_text(text=text)
185
+ )
186
+ if tag_name:
187
+ tag_name = tag_name.lower().strip()
188
+ for element in elements:
189
+ if element and not tag_name:
190
+ element = self.__add_sync_methods(element)
191
+ return self.__add_sync_methods(element)
192
+ elif (
193
+ element
194
+ and tag_name in element.tag_name.lower()
195
+ and text.strip() in element.text
196
+ ):
197
+ element = self.__add_sync_methods(element)
198
+ return self.__add_sync_methods(element)
199
+ elif (
200
+ element.parent
201
+ and tag_name in element.parent.tag_name.lower()
202
+ and text.strip() in element.parent.text
203
+ ):
204
+ element = self.__add_sync_methods(element.parent)
205
+ return self.__add_sync_methods(element)
206
+ elif (
207
+ element.parent.parent
208
+ and tag_name in element.parent.parent.tag_name.lower()
209
+ and text.strip() in element.parent.parent.text
210
+ ):
211
+ element = self.__add_sync_methods(element.parent.parent)
212
+ return self.__add_sync_methods(element)
213
+ plural = "s"
214
+ if timeout == 1:
215
+ plural = ""
216
+ raise Exception(
217
+ "Text {%s} with tag {%s} was not found after %s second%s!"
218
+ % (text, tag_name, timeout, plural)
219
+ )
220
+
221
+ def find_all(self, selector, timeout=None):
222
+ if not timeout:
223
+ timeout = settings.SMALL_TIMEOUT
168
224
  self.__add_light_pause()
169
225
  selector = self.__convert_to_css_if_xpath(selector)
170
226
  elements = self.loop.run_until_complete(
@@ -174,30 +230,54 @@ class CDPMethods():
174
230
  for element in elements:
175
231
  element = self.__add_sync_methods(element)
176
232
  updated_elements.append(element)
177
- self.__slow_mode_pause_if_set()
178
233
  return updated_elements
179
234
 
180
235
  def find_elements_by_text(self, text, tag_name=None):
181
236
  """Returns a list of elements by matching text.
182
- Optionally, provide a tag_name to narrow down the search
183
- to only elements with the given tag. (Eg: a, div, script, span)"""
237
+ Optionally, provide a tag_name to narrow down the search to only
238
+ elements with the given tag. (Eg: a, button, div, script, span)"""
184
239
  self.__add_light_pause()
185
240
  elements = self.loop.run_until_complete(
186
241
  self.page.find_elements_by_text(text=text)
187
242
  )
188
243
  updated_elements = []
244
+ if tag_name:
245
+ tag_name = tag_name.lower().strip()
189
246
  for element in elements:
190
- if (
191
- not tag_name
192
- or tag_name.lower().strip() in element.tag_name.lower().strip()
247
+ if element and not tag_name:
248
+ element = self.__add_sync_methods(element)
249
+ if element not in updated_elements:
250
+ updated_elements.append(element)
251
+ elif (
252
+ element
253
+ and tag_name in element.tag_name.lower()
254
+ and text.strip() in element.text
193
255
  ):
194
256
  element = self.__add_sync_methods(element)
195
- updated_elements.append(element)
196
- self.__slow_mode_pause_if_set()
257
+ if element not in updated_elements:
258
+ updated_elements.append(element)
259
+ elif (
260
+ element.parent
261
+ and tag_name in element.parent.tag_name.lower()
262
+ and text.strip() in element.parent.text
263
+ ):
264
+ element = self.__add_sync_methods(element.parent)
265
+ if element not in updated_elements:
266
+ updated_elements.append(element)
267
+ elif (
268
+ element.parent.parent
269
+ and tag_name in element.parent.parent.tag_name.lower()
270
+ and text.strip() in element.parent.parent.text
271
+ ):
272
+ element = self.__add_sync_methods(element.parent.parent)
273
+ if element not in updated_elements:
274
+ updated_elements.append(element)
197
275
  return updated_elements
198
276
 
199
- def select(self, selector, timeout=settings.SMALL_TIMEOUT):
277
+ def select(self, selector, timeout=None):
200
278
  """Similar to find_element(), but without text-based search."""
279
+ if not timeout:
280
+ timeout = settings.SMALL_TIMEOUT
201
281
  self.__add_light_pause()
202
282
  selector = self.__convert_to_css_if_xpath(selector)
203
283
  if (":contains(" in selector):
@@ -231,7 +311,9 @@ class CDPMethods():
231
311
  self.__slow_mode_pause_if_set()
232
312
  return element
233
313
 
234
- def select_all(self, selector, timeout=settings.SMALL_TIMEOUT):
314
+ def select_all(self, selector, timeout=None):
315
+ if not timeout:
316
+ timeout = settings.SMALL_TIMEOUT
235
317
  self.__add_light_pause()
236
318
  selector = self.__convert_to_css_if_xpath(selector)
237
319
  elements = self.loop.run_until_complete(
@@ -241,13 +323,16 @@ class CDPMethods():
241
323
  for element in elements:
242
324
  element = self.__add_sync_methods(element)
243
325
  updated_elements.append(element)
244
- self.__slow_mode_pause_if_set()
245
326
  return updated_elements
246
327
 
247
- def find_elements(self, selector, timeout=settings.SMALL_TIMEOUT):
328
+ def find_elements(self, selector, timeout=None):
329
+ if not timeout:
330
+ timeout = settings.SMALL_TIMEOUT
248
331
  return self.select_all(selector, timeout=timeout)
249
332
 
250
- def find_visible_elements(self, selector, timeout=settings.SMALL_TIMEOUT):
333
+ def find_visible_elements(self, selector, timeout=None):
334
+ if not timeout:
335
+ timeout = settings.SMALL_TIMEOUT
251
336
  visible_elements = []
252
337
  elements = self.select_all(selector, timeout=timeout)
253
338
  for element in elements:
@@ -268,6 +353,7 @@ class CDPMethods():
268
353
  if number < 0:
269
354
  number = 0
270
355
  element = elements[number]
356
+ element.scroll_into_view()
271
357
  element.click()
272
358
 
273
359
  def click_nth_visible_element(self, selector, number):
@@ -284,6 +370,7 @@ class CDPMethods():
284
370
  if number < 0:
285
371
  number = 0
286
372
  element = elements[number]
373
+ element.scroll_into_view()
287
374
  element.click()
288
375
 
289
376
  def click_link(self, link_text):
@@ -311,6 +398,13 @@ class CDPMethods():
311
398
  return result
312
399
 
313
400
  def __flash(self, element, *args, **kwargs):
401
+ element.scroll_into_view()
402
+ if len(args) < 3 and "x_offset" not in kwargs:
403
+ x_offset = self.__get_x_scroll_offset()
404
+ kwargs["x_offset"] = x_offset
405
+ if len(args) < 3 and "y_offset" not in kwargs:
406
+ y_offset = self.__get_y_scroll_offset()
407
+ kwargs["y_offset"] = y_offset
314
408
  return (
315
409
  self.loop.run_until_complete(
316
410
  element.flash_async(*args, **kwargs)
@@ -382,9 +476,9 @@ class CDPMethods():
382
476
  )
383
477
 
384
478
  def __scroll_into_view(self, element):
385
- return (
386
- self.loop.run_until_complete(element.scroll_into_view_async())
387
- )
479
+ self.loop.run_until_complete(element.scroll_into_view_async())
480
+ self.__add_light_pause()
481
+ return None
388
482
 
389
483
  def __select_option(self, element):
390
484
  return (
@@ -431,6 +525,37 @@ class CDPMethods():
431
525
  self.loop.run_until_complete(element.get_js_attributes_async())
432
526
  )
433
527
 
528
+ def __get_attribute(self, element, attribute):
529
+ try:
530
+ return element.get_js_attributes()[attribute]
531
+ except Exception:
532
+ if not attribute:
533
+ raise
534
+ try:
535
+ attribute_str = element.get_js_attributes()
536
+ locate = ' %s="' % attribute
537
+ if locate in attribute_str.outerHTML:
538
+ outer_html = attribute_str.outerHTML
539
+ attr_start = outer_html.find(locate) + len(locate)
540
+ attr_end = outer_html.find('"', attr_start)
541
+ value = outer_html[attr_start:attr_end]
542
+ return value
543
+ except Exception:
544
+ pass
545
+ return None
546
+
547
+ def __get_x_scroll_offset(self):
548
+ x_scroll_offset = self.loop.run_until_complete(
549
+ self.page.evaluate("window.pageXOffset")
550
+ )
551
+ return x_scroll_offset or 0
552
+
553
+ def __get_y_scroll_offset(self):
554
+ y_scroll_offset = self.loop.run_until_complete(
555
+ self.page.evaluate("window.pageYOffset")
556
+ )
557
+ return y_scroll_offset or 0
558
+
434
559
  def tile_windows(self, windows=None, max_columns=0):
435
560
  """Tile windows and return the grid of tiled windows."""
436
561
  driver = self.driver
@@ -472,12 +597,12 @@ class CDPMethods():
472
597
  driver.cookies.load(*args, **kwargs)
473
598
  )
474
599
 
475
- def clear_cookies(self, *args, **kwargs):
600
+ def clear_cookies(self):
476
601
  driver = self.driver
477
602
  if hasattr(driver, "cdp_base"):
478
603
  driver = driver.cdp_base
479
604
  return self.loop.run_until_complete(
480
- driver.cookies.clear(*args, **kwargs)
605
+ driver.cookies.clear()
481
606
  )
482
607
 
483
608
  def sleep(self, seconds):
@@ -501,10 +626,12 @@ class CDPMethods():
501
626
  self.page.evaluate(js_code)
502
627
  )
503
628
 
504
- def click(self, selector, timeout=settings.SMALL_TIMEOUT):
629
+ def click(self, selector, timeout=None):
630
+ if not timeout:
631
+ timeout = settings.SMALL_TIMEOUT
505
632
  self.__slow_mode_pause_if_set()
506
633
  element = self.find_element(selector, timeout=timeout)
507
- self.__add_light_pause()
634
+ element.scroll_into_view()
508
635
  element.click()
509
636
  self.__slow_mode_pause_if_set()
510
637
  self.loop.run_until_complete(self.page.wait())
@@ -518,9 +645,12 @@ class CDPMethods():
518
645
 
519
646
  def click_if_visible(self, selector):
520
647
  if self.is_element_visible(selector):
521
- self.find_element(selector).click()
522
- self.__slow_mode_pause_if_set()
523
- self.loop.run_until_complete(self.page.wait())
648
+ with suppress(Exception):
649
+ element = self.find_element(selector, timeout=0)
650
+ element.scroll_into_view()
651
+ element.click()
652
+ self.__slow_mode_pause_if_set()
653
+ self.loop.run_until_complete(self.page.wait())
524
654
 
525
655
  def click_visible_elements(self, selector, limit=0):
526
656
  """Finds all matching page elements and clicks visible ones in order.
@@ -545,19 +675,22 @@ class CDPMethods():
545
675
  except Exception:
546
676
  continue
547
677
  if (width != 0 or height != 0):
678
+ element.scroll_into_view()
548
679
  element.click()
549
680
  click_count += 1
550
- time.sleep(0.044)
681
+ time.sleep(0.042)
551
682
  self.__slow_mode_pause_if_set()
552
683
  self.loop.run_until_complete(self.page.wait())
553
684
  except Exception:
554
685
  break
555
686
 
556
- def mouse_click(self, selector, timeout=settings.SMALL_TIMEOUT):
687
+ def mouse_click(self, selector, timeout=None):
557
688
  """(Attempt simulating a mouse click)"""
689
+ if not timeout:
690
+ timeout = settings.SMALL_TIMEOUT
558
691
  self.__slow_mode_pause_if_set()
559
692
  element = self.find_element(selector, timeout=timeout)
560
- self.__add_light_pause()
693
+ element.scroll_into_view()
561
694
  element.mouse_click()
562
695
  self.__slow_mode_pause_if_set()
563
696
  self.loop.run_until_complete(self.page.wait())
@@ -579,6 +712,7 @@ class CDPMethods():
579
712
 
580
713
  def select_option_by_text(self, dropdown_selector, option):
581
714
  element = self.find_element(dropdown_selector)
715
+ element.scroll_into_view()
582
716
  options = element.query_selector_all("option")
583
717
  for found_option in options:
584
718
  if found_option.text.strip() == option.strip():
@@ -599,7 +733,10 @@ class CDPMethods():
599
733
  """Paint a quickly-vanishing dot over an element."""
600
734
  selector = self.__convert_to_css_if_xpath(selector)
601
735
  element = self.find_element(selector)
602
- element.flash(duration=duration, color=color)
736
+ element.scroll_into_view()
737
+ x_offset = self.__get_x_scroll_offset()
738
+ y_offset = self.__get_y_scroll_offset()
739
+ element.flash(duration, color, x_offset, y_offset)
603
740
  if pause and isinstance(pause, (int, float)):
604
741
  time.sleep(pause)
605
742
 
@@ -607,17 +744,22 @@ class CDPMethods():
607
744
  """Highlight an element with multi-colors."""
608
745
  selector = self.__convert_to_css_if_xpath(selector)
609
746
  element = self.find_element(selector)
610
- element.flash(0.46, "44CC88")
747
+ element.scroll_into_view()
748
+ x_offset = self.__get_x_scroll_offset()
749
+ y_offset = self.__get_y_scroll_offset()
750
+ element.flash(0.46, "44CC88", x_offset, y_offset)
611
751
  time.sleep(0.15)
612
- element.flash(0.42, "8844CC")
752
+ element.flash(0.42, "8844CC", x_offset, y_offset)
613
753
  time.sleep(0.15)
614
- element.flash(0.38, "CC8844")
754
+ element.flash(0.38, "CC8844", x_offset, y_offset)
615
755
  time.sleep(0.15)
616
- element.flash(0.30, "44CC88")
756
+ element.flash(0.30, "44CC88", x_offset, y_offset)
617
757
  time.sleep(0.30)
618
758
 
619
759
  def focus(self, selector):
620
- self.find_element(selector).focus()
760
+ element = self.find_element(selector)
761
+ element.scroll_into_view()
762
+ element.focus()
621
763
 
622
764
  def highlight_overlay(self, selector):
623
765
  self.find_element(selector).highlight_overlay()
@@ -643,21 +785,25 @@ class CDPMethods():
643
785
  with suppress(Exception):
644
786
  self.loop.run_until_complete(self.page.evaluate(js_code))
645
787
 
646
- def send_keys(self, selector, text, timeout=settings.SMALL_TIMEOUT):
788
+ def send_keys(self, selector, text, timeout=None):
789
+ if not timeout:
790
+ timeout = settings.SMALL_TIMEOUT
647
791
  self.__slow_mode_pause_if_set()
648
792
  element = self.select(selector, timeout=timeout)
649
- self.__add_light_pause()
793
+ element.scroll_into_view()
650
794
  if text.endswith("\n") or text.endswith("\r"):
651
795
  text = text[:-1] + "\r\n"
652
796
  element.send_keys(text)
653
797
  self.__slow_mode_pause_if_set()
654
798
  self.loop.run_until_complete(self.page.wait())
655
799
 
656
- def press_keys(self, selector, text, timeout=settings.SMALL_TIMEOUT):
800
+ def press_keys(self, selector, text, timeout=None):
657
801
  """Similar to send_keys(), but presses keys at human speed."""
802
+ if not timeout:
803
+ timeout = settings.SMALL_TIMEOUT
658
804
  self.__slow_mode_pause_if_set()
659
805
  element = self.select(selector, timeout=timeout)
660
- self.__add_light_pause()
806
+ element.scroll_into_view()
661
807
  submit = False
662
808
  if text.endswith("\n") or text.endswith("\r"):
663
809
  submit = True
@@ -671,11 +817,13 @@ class CDPMethods():
671
817
  self.__slow_mode_pause_if_set()
672
818
  self.loop.run_until_complete(self.page.wait())
673
819
 
674
- def type(self, selector, text, timeout=settings.SMALL_TIMEOUT):
820
+ def type(self, selector, text, timeout=None):
675
821
  """Similar to send_keys(), but clears the text field first."""
822
+ if not timeout:
823
+ timeout = settings.SMALL_TIMEOUT
676
824
  self.__slow_mode_pause_if_set()
677
825
  element = self.select(selector, timeout=timeout)
678
- self.__add_light_pause()
826
+ element.scroll_into_view()
679
827
  with suppress(Exception):
680
828
  element.clear_input()
681
829
  if text.endswith("\n") or text.endswith("\r"):
@@ -684,12 +832,14 @@ class CDPMethods():
684
832
  self.__slow_mode_pause_if_set()
685
833
  self.loop.run_until_complete(self.page.wait())
686
834
 
687
- def set_value(self, selector, text, timeout=settings.SMALL_TIMEOUT):
835
+ def set_value(self, selector, text, timeout=None):
688
836
  """Similar to send_keys(), but clears the text field first."""
837
+ if not timeout:
838
+ timeout = settings.SMALL_TIMEOUT
689
839
  self.__slow_mode_pause_if_set()
690
840
  selector = self.__convert_to_css_if_xpath(selector)
691
- self.select(selector, timeout=timeout)
692
- self.__add_light_pause()
841
+ element = self.select(selector, timeout=timeout)
842
+ element.scroll_into_view()
693
843
  press_enter = False
694
844
  if text.endswith("\n"):
695
845
  text = text[:-1]
@@ -908,7 +1058,9 @@ class CDPMethods():
908
1058
  coordinates["y"] = y if y else 0
909
1059
  return coordinates
910
1060
 
911
- def get_element_rect(self, selector, timeout=settings.SMALL_TIMEOUT):
1061
+ def get_element_rect(self, selector, timeout=None):
1062
+ if not timeout:
1063
+ timeout = settings.SMALL_TIMEOUT
912
1064
  selector = self.__convert_to_css_if_xpath(selector)
913
1065
  self.select(selector, timeout=timeout)
914
1066
  self.__add_light_pause()
@@ -921,23 +1073,29 @@ class CDPMethods():
921
1073
  )
922
1074
  return coordinates
923
1075
 
924
- def get_element_size(self, selector):
925
- element_rect = self.get_element_rect(selector)
1076
+ def get_element_size(self, selector, timeout=None):
1077
+ if not timeout:
1078
+ timeout = settings.SMALL_TIMEOUT
1079
+ element_rect = self.get_element_rect(selector, timeout=timeout)
926
1080
  coordinates = {}
927
1081
  coordinates["width"] = element_rect["width"]
928
1082
  coordinates["height"] = element_rect["height"]
929
1083
  return coordinates
930
1084
 
931
- def get_element_position(self, selector):
932
- element_rect = self.get_element_rect(selector)
1085
+ def get_element_position(self, selector, timeout=None):
1086
+ if not timeout:
1087
+ timeout = settings.SMALL_TIMEOUT
1088
+ element_rect = self.get_element_rect(selector, timeout=timeout)
933
1089
  coordinates = {}
934
1090
  coordinates["x"] = element_rect["x"]
935
1091
  coordinates["y"] = element_rect["y"]
936
1092
  return coordinates
937
1093
 
938
- def get_gui_element_rect(self, selector):
1094
+ def get_gui_element_rect(self, selector, timeout=None):
939
1095
  """(Coordinates are relative to the screen. Not the window.)"""
940
- element_rect = self.get_element_rect(selector)
1096
+ if not timeout:
1097
+ timeout = settings.SMALL_TIMEOUT
1098
+ element_rect = self.get_element_rect(selector, timeout=timeout)
941
1099
  e_width = element_rect["width"]
942
1100
  e_height = element_rect["height"]
943
1101
  window_rect = self.get_window_rect()
@@ -951,9 +1109,11 @@ class CDPMethods():
951
1109
  y = y + window_rect["scrollY"]
952
1110
  return ({"height": e_height, "width": e_width, "x": x, "y": y})
953
1111
 
954
- def get_gui_element_center(self, selector):
1112
+ def get_gui_element_center(self, selector, timeout=None):
955
1113
  """(Coordinates are relative to the screen. Not the window.)"""
956
- element_rect = self.get_gui_element_rect(selector)
1114
+ if not timeout:
1115
+ timeout = settings.SMALL_TIMEOUT
1116
+ element_rect = self.get_gui_element_rect(selector, timeout=timeout)
957
1117
  e_width = element_rect["width"]
958
1118
  e_height = element_rect["height"]
959
1119
  e_x = element_rect["x"]
@@ -981,7 +1141,16 @@ class CDPMethods():
981
1141
 
982
1142
  def get_element_attribute(self, selector, attribute):
983
1143
  attributes = self.get_element_attributes(selector)
984
- return attributes[attribute]
1144
+ with suppress(Exception):
1145
+ return attributes[attribute]
1146
+ locate = ' %s="' % attribute
1147
+ value = self.get_attribute(selector, attribute)
1148
+ if not value and locate not in attributes:
1149
+ raise KeyError(attribute)
1150
+ return value
1151
+
1152
+ def get_attribute(self, selector, attribute):
1153
+ return self.find_element(selector).get_attribute(attribute)
985
1154
 
986
1155
  def get_element_html(self, selector):
987
1156
  selector = self.__convert_to_css_if_xpath(selector)
@@ -1019,6 +1188,10 @@ class CDPMethods():
1019
1188
  with suppress(Exception):
1020
1189
  self.loop.run_until_complete(self.page.evaluate(js_code))
1021
1190
 
1191
+ def __make_sure_pyautogui_lock_is_writable(self):
1192
+ with suppress(Exception):
1193
+ shared_utils.make_writable(constants.MultiBrowser.PYAUTOGUILOCK)
1194
+
1022
1195
  def __verify_pyautogui_has_a_headed_browser(self):
1023
1196
  """PyAutoGUI requires a headed browser so that it can
1024
1197
  focus on the correct element when performing actions."""
@@ -1039,6 +1212,8 @@ class CDPMethods():
1039
1212
  constants.PipInstall.FINDLOCK
1040
1213
  )
1041
1214
  with pip_find_lock: # Prevent issues with multiple processes
1215
+ with suppress(Exception):
1216
+ shared_utils.make_writable(constants.PipInstall.FINDLOCK)
1042
1217
  try:
1043
1218
  import pyautogui
1044
1219
  with suppress(Exception):
@@ -1124,6 +1299,7 @@ class CDPMethods():
1124
1299
  constants.MultiBrowser.PYAUTOGUILOCK
1125
1300
  )
1126
1301
  with gui_lock:
1302
+ self.__make_sure_pyautogui_lock_is_writable()
1127
1303
  pyautogui.press(key)
1128
1304
  time.sleep(0.044)
1129
1305
  self.__slow_mode_pause_if_set()
@@ -1137,6 +1313,7 @@ class CDPMethods():
1137
1313
  constants.MultiBrowser.PYAUTOGUILOCK
1138
1314
  )
1139
1315
  with gui_lock:
1316
+ self.__make_sure_pyautogui_lock_is_writable()
1140
1317
  for key in keys:
1141
1318
  pyautogui.press(key)
1142
1319
  time.sleep(0.044)
@@ -1151,6 +1328,7 @@ class CDPMethods():
1151
1328
  constants.MultiBrowser.PYAUTOGUILOCK
1152
1329
  )
1153
1330
  with gui_lock:
1331
+ self.__make_sure_pyautogui_lock_is_writable()
1154
1332
  pyautogui.write(text)
1155
1333
  self.__slow_mode_pause_if_set()
1156
1334
  self.loop.run_until_complete(self.page.wait())
@@ -1171,6 +1349,7 @@ class CDPMethods():
1171
1349
  constants.MultiBrowser.PYAUTOGUILOCK
1172
1350
  )
1173
1351
  with gui_lock: # Prevent issues with multiple processes
1352
+ self.__make_sure_pyautogui_lock_is_writable()
1174
1353
  pyautogui.moveTo(x, y, timeframe, pyautogui.easeOutQuad)
1175
1354
  if timeframe >= 0.25:
1176
1355
  time.sleep(0.056) # Wait if moving at human-speed
@@ -1191,6 +1370,7 @@ class CDPMethods():
1191
1370
  constants.MultiBrowser.PYAUTOGUILOCK
1192
1371
  )
1193
1372
  with gui_lock: # Prevent issues with multiple processes
1373
+ self.__make_sure_pyautogui_lock_is_writable()
1194
1374
  self.__install_pyautogui_if_missing()
1195
1375
  import pyautogui
1196
1376
  pyautogui = self.__get_configured_pyautogui(pyautogui)
@@ -1408,6 +1588,7 @@ class CDPMethods():
1408
1588
  constants.MultiBrowser.PYAUTOGUILOCK
1409
1589
  )
1410
1590
  with gui_lock:
1591
+ self.__make_sure_pyautogui_lock_is_writable()
1411
1592
  self.bring_active_window_to_front()
1412
1593
  self.gui_hover_element(hover_selector)
1413
1594
  time.sleep(0.15)
@@ -1423,7 +1604,7 @@ class CDPMethods():
1423
1604
  """Return True if checkbox (or radio button) is checked."""
1424
1605
  selector = self.__convert_to_css_if_xpath(selector)
1425
1606
  self.find_element(selector, timeout=settings.SMALL_TIMEOUT)
1426
- return self.get_element_attribute(selector, "checked")
1607
+ return bool(self.get_element_attribute(selector, "checked"))
1427
1608
 
1428
1609
  def is_selected(self, selector):
1429
1610
  selector = self.__convert_to_css_if_xpath(selector)
@@ -1480,9 +1661,9 @@ class CDPMethods():
1480
1661
  return True
1481
1662
  return False
1482
1663
 
1483
- def wait_for_element_visible(
1484
- self, selector, timeout=settings.SMALL_TIMEOUT
1485
- ):
1664
+ def wait_for_element_visible(self, selector, timeout=None):
1665
+ if not timeout:
1666
+ timeout = settings.SMALL_TIMEOUT
1486
1667
  try:
1487
1668
  self.select(selector, timeout=timeout)
1488
1669
  except Exception:
@@ -1493,8 +1674,10 @@ class CDPMethods():
1493
1674
  time.sleep(0.1)
1494
1675
  raise Exception("Element {%s} was not visible!" % selector)
1495
1676
 
1496
- def assert_element(self, selector, timeout=settings.SMALL_TIMEOUT):
1677
+ def assert_element(self, selector, timeout=None):
1497
1678
  """Same as assert_element_visible()"""
1679
+ if not timeout:
1680
+ timeout = settings.SMALL_TIMEOUT
1498
1681
  try:
1499
1682
  self.select(selector, timeout=timeout)
1500
1683
  except Exception:
@@ -1505,8 +1688,10 @@ class CDPMethods():
1505
1688
  time.sleep(0.1)
1506
1689
  raise Exception("Element {%s} was not visible!" % selector)
1507
1690
 
1508
- def assert_element_visible(self, selector, timeout=settings.SMALL_TIMEOUT):
1691
+ def assert_element_visible(self, selector, timeout=None):
1509
1692
  """Same as assert_element()"""
1693
+ if not timeout:
1694
+ timeout = settings.SMALL_TIMEOUT
1510
1695
  try:
1511
1696
  self.select(selector, timeout=timeout)
1512
1697
  except Exception:
@@ -1517,16 +1702,20 @@ class CDPMethods():
1517
1702
  time.sleep(0.1)
1518
1703
  raise Exception("Element {%s} was not visible!" % selector)
1519
1704
 
1520
- def assert_element_present(self, selector, timeout=settings.SMALL_TIMEOUT):
1705
+ def assert_element_present(self, selector, timeout=None):
1521
1706
  """Assert element is present in the DOM. (Visibility NOT required)"""
1707
+ if not timeout:
1708
+ timeout = settings.SMALL_TIMEOUT
1522
1709
  try:
1523
1710
  self.select(selector, timeout=timeout)
1524
1711
  except Exception:
1525
1712
  raise Exception("Element {%s} was not found!" % selector)
1526
1713
  return True
1527
1714
 
1528
- def assert_element_absent(self, selector, timeout=settings.SMALL_TIMEOUT):
1715
+ def assert_element_absent(self, selector, timeout=None):
1529
1716
  """Assert element is not present in the DOM."""
1717
+ if not timeout:
1718
+ timeout = settings.SMALL_TIMEOUT
1530
1719
  start_ms = time.time() * 1000.0
1531
1720
  stop_ms = start_ms + (timeout * 1000.0)
1532
1721
  for i in range(int(timeout * 10)):
@@ -1544,10 +1733,10 @@ class CDPMethods():
1544
1733
  % (selector, timeout, plural)
1545
1734
  )
1546
1735
 
1547
- def assert_element_not_visible(
1548
- self, selector, timeout=settings.SMALL_TIMEOUT
1549
- ):
1736
+ def assert_element_not_visible(self, selector, timeout=None):
1550
1737
  """Assert element is not visible on page. (May still be in DOM)"""
1738
+ if not timeout:
1739
+ timeout = settings.SMALL_TIMEOUT
1551
1740
  start_ms = time.time() * 1000.0
1552
1741
  stop_ms = start_ms + (timeout * 1000.0)
1553
1742
  for i in range(int(timeout * 10)):
@@ -1642,39 +1831,53 @@ class CDPMethods():
1642
1831
  if expected not in actual:
1643
1832
  raise Exception(error % (expected, actual))
1644
1833
 
1645
- def assert_text(
1646
- self, text, selector="html", timeout=settings.SMALL_TIMEOUT
1647
- ):
1834
+ def assert_text(self, text, selector="body", timeout=None):
1835
+ if not timeout:
1836
+ timeout = settings.SMALL_TIMEOUT
1837
+ start_ms = time.time() * 1000.0
1838
+ stop_ms = start_ms + (timeout * 1000.0)
1648
1839
  text = text.strip()
1649
1840
  element = None
1650
1841
  try:
1651
1842
  element = self.find_element(selector, timeout=timeout)
1652
1843
  except Exception:
1653
1844
  raise Exception("Element {%s} not found!" % selector)
1654
- for i in range(30):
1845
+ for i in range(int(timeout * 10)):
1846
+ with suppress(Exception):
1847
+ element = self.find_element(selector, timeout=0.1)
1655
1848
  if text in element.text_all:
1656
1849
  return True
1850
+ now_ms = time.time() * 1000.0
1851
+ if now_ms >= stop_ms:
1852
+ break
1657
1853
  time.sleep(0.1)
1658
1854
  raise Exception(
1659
1855
  "Text {%s} not found in {%s}! Actual text: {%s}"
1660
1856
  % (text, selector, element.text_all)
1661
1857
  )
1662
1858
 
1663
- def assert_exact_text(
1664
- self, text, selector="html", timeout=settings.SMALL_TIMEOUT
1665
- ):
1859
+ def assert_exact_text(self, text, selector="body", timeout=None):
1860
+ if not timeout:
1861
+ timeout = settings.SMALL_TIMEOUT
1862
+ start_ms = time.time() * 1000.0
1863
+ stop_ms = start_ms + (timeout * 1000.0)
1666
1864
  text = text.strip()
1667
1865
  element = None
1668
1866
  try:
1669
1867
  element = self.select(selector, timeout=timeout)
1670
1868
  except Exception:
1671
1869
  raise Exception("Element {%s} not found!" % selector)
1672
- for i in range(30):
1870
+ for i in range(int(timeout * 10)):
1871
+ with suppress(Exception):
1872
+ element = self.select(selector, timeout=0.1)
1673
1873
  if (
1674
1874
  self.is_element_visible(selector)
1675
1875
  and text.strip() == element.text_all.strip()
1676
1876
  ):
1677
1877
  return True
1878
+ now_ms = time.time() * 1000.0
1879
+ if now_ms >= stop_ms:
1880
+ break
1678
1881
  time.sleep(0.1)
1679
1882
  raise Exception(
1680
1883
  "Expected Text {%s}, is not equal to {%s} in {%s}!"