seleniumbase 4.33.4__py3-none-any.whl → 4.34.2__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
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}!"