seleniumbase 4.24.10__py3-none-any.whl → 4.33.15__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 (79) hide show
  1. sbase/__init__.py +1 -0
  2. sbase/steps.py +7 -0
  3. seleniumbase/__init__.py +16 -7
  4. seleniumbase/__version__.py +1 -1
  5. seleniumbase/behave/behave_sb.py +97 -32
  6. seleniumbase/common/decorators.py +16 -7
  7. seleniumbase/config/proxy_list.py +3 -3
  8. seleniumbase/config/settings.py +4 -0
  9. seleniumbase/console_scripts/logo_helper.py +47 -8
  10. seleniumbase/console_scripts/run.py +345 -335
  11. seleniumbase/console_scripts/sb_behave_gui.py +5 -12
  12. seleniumbase/console_scripts/sb_caseplans.py +6 -13
  13. seleniumbase/console_scripts/sb_commander.py +5 -12
  14. seleniumbase/console_scripts/sb_install.py +62 -54
  15. seleniumbase/console_scripts/sb_mkchart.py +13 -20
  16. seleniumbase/console_scripts/sb_mkdir.py +11 -17
  17. seleniumbase/console_scripts/sb_mkfile.py +69 -43
  18. seleniumbase/console_scripts/sb_mkpres.py +13 -20
  19. seleniumbase/console_scripts/sb_mkrec.py +88 -21
  20. seleniumbase/console_scripts/sb_objectify.py +30 -30
  21. seleniumbase/console_scripts/sb_print.py +5 -12
  22. seleniumbase/console_scripts/sb_recorder.py +16 -11
  23. seleniumbase/core/browser_launcher.py +1658 -221
  24. seleniumbase/core/detect_b_ver.py +7 -8
  25. seleniumbase/core/log_helper.py +42 -27
  26. seleniumbase/core/mysql.py +1 -4
  27. seleniumbase/core/proxy_helper.py +35 -30
  28. seleniumbase/core/recorder_helper.py +24 -5
  29. seleniumbase/core/sb_cdp.py +1951 -0
  30. seleniumbase/core/sb_driver.py +162 -8
  31. seleniumbase/core/settings_parser.py +6 -0
  32. seleniumbase/core/style_sheet.py +10 -0
  33. seleniumbase/extensions/recorder.zip +0 -0
  34. seleniumbase/fixtures/base_case.py +1234 -632
  35. seleniumbase/fixtures/constants.py +10 -1
  36. seleniumbase/fixtures/js_utils.py +171 -144
  37. seleniumbase/fixtures/page_actions.py +177 -13
  38. seleniumbase/fixtures/page_utils.py +25 -53
  39. seleniumbase/fixtures/shared_utils.py +97 -11
  40. seleniumbase/js_code/active_css_js.py +1 -1
  41. seleniumbase/js_code/recorder_js.py +1 -1
  42. seleniumbase/plugins/base_plugin.py +2 -3
  43. seleniumbase/plugins/driver_manager.py +340 -65
  44. seleniumbase/plugins/pytest_plugin.py +276 -47
  45. seleniumbase/plugins/sb_manager.py +412 -99
  46. seleniumbase/plugins/selenium_plugin.py +122 -17
  47. seleniumbase/translate/translator.py +0 -7
  48. seleniumbase/undetected/__init__.py +59 -52
  49. seleniumbase/undetected/cdp.py +0 -1
  50. seleniumbase/undetected/cdp_driver/__init__.py +1 -0
  51. seleniumbase/undetected/cdp_driver/_contradict.py +110 -0
  52. seleniumbase/undetected/cdp_driver/browser.py +829 -0
  53. seleniumbase/undetected/cdp_driver/cdp_util.py +458 -0
  54. seleniumbase/undetected/cdp_driver/config.py +334 -0
  55. seleniumbase/undetected/cdp_driver/connection.py +639 -0
  56. seleniumbase/undetected/cdp_driver/element.py +1168 -0
  57. seleniumbase/undetected/cdp_driver/tab.py +1323 -0
  58. seleniumbase/undetected/dprocess.py +4 -7
  59. seleniumbase/undetected/options.py +6 -8
  60. seleniumbase/undetected/patcher.py +11 -13
  61. seleniumbase/undetected/reactor.py +0 -1
  62. seleniumbase/undetected/webelement.py +16 -3
  63. {seleniumbase-4.24.10.dist-info → seleniumbase-4.33.15.dist-info}/LICENSE +1 -1
  64. {seleniumbase-4.24.10.dist-info → seleniumbase-4.33.15.dist-info}/METADATA +299 -252
  65. {seleniumbase-4.24.10.dist-info → seleniumbase-4.33.15.dist-info}/RECORD +68 -70
  66. {seleniumbase-4.24.10.dist-info → seleniumbase-4.33.15.dist-info}/WHEEL +1 -1
  67. sbase/ReadMe.txt +0 -2
  68. seleniumbase/ReadMe.md +0 -25
  69. seleniumbase/common/ReadMe.md +0 -71
  70. seleniumbase/console_scripts/ReadMe.md +0 -731
  71. seleniumbase/drivers/ReadMe.md +0 -27
  72. seleniumbase/extensions/ReadMe.md +0 -12
  73. seleniumbase/masterqa/ReadMe.md +0 -61
  74. seleniumbase/resources/ReadMe.md +0 -31
  75. seleniumbase/resources/favicon.ico +0 -0
  76. seleniumbase/utilities/selenium_grid/ReadMe.md +0 -84
  77. seleniumbase/utilities/selenium_ide/ReadMe.md +0 -111
  78. {seleniumbase-4.24.10.dist-info → seleniumbase-4.33.15.dist-info}/entry_points.txt +0 -0
  79. {seleniumbase-4.24.10.dist-info → seleniumbase-4.33.15.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1951 @@
1
+ """Add CDP methods to extend the driver"""
2
+ import fasteners
3
+ import os
4
+ import re
5
+ import sys
6
+ import time
7
+ from contextlib import suppress
8
+ from seleniumbase import config as sb_config
9
+ from seleniumbase.config import settings
10
+ from seleniumbase.fixtures import constants
11
+ from seleniumbase.fixtures import js_utils
12
+ from seleniumbase.fixtures import page_utils
13
+ from seleniumbase.fixtures import shared_utils
14
+
15
+
16
+ class CDPMethods():
17
+ def __init__(self, loop, page, driver):
18
+ self.loop = loop
19
+ self.page = page
20
+ self.driver = driver
21
+
22
+ def __slow_mode_pause_if_set(self):
23
+ if (
24
+ (hasattr(sb_config, "demo_mode") and sb_config.demo_mode)
25
+ or "--demo" in sys.argv
26
+ ):
27
+ time.sleep(0.48)
28
+ elif (
29
+ (hasattr(sb_config, "slow_mode") and sb_config.slow_mode)
30
+ or "--slow" in sys.argv
31
+ ):
32
+ time.sleep(0.24)
33
+
34
+ def __add_light_pause(self):
35
+ time.sleep(0.007)
36
+
37
+ def __convert_to_css_if_xpath(self, selector):
38
+ if page_utils.is_xpath_selector(selector):
39
+ with suppress(Exception):
40
+ css = js_utils.convert_to_css_selector(selector, "xpath")
41
+ if css:
42
+ return css
43
+ return selector
44
+
45
+ def __add_sync_methods(self, element):
46
+ if not element:
47
+ return element
48
+ element.clear_input = lambda: self.__clear_input(element)
49
+ element.click = lambda: self.__click(element)
50
+ element.flash = lambda *args, **kwargs: self.__flash(
51
+ element, *args, **kwargs
52
+ )
53
+ element.focus = lambda: self.__focus(element)
54
+ element.highlight_overlay = lambda: self.__highlight_overlay(element)
55
+ element.mouse_click = lambda: self.__mouse_click(element)
56
+ element.mouse_drag = (
57
+ lambda destination: self.__mouse_drag(element, destination)
58
+ )
59
+ element.mouse_move = lambda: self.__mouse_move(element)
60
+ element.query_selector = (
61
+ lambda selector: self.__query_selector(element, selector)
62
+ )
63
+ element.querySelector = element.query_selector
64
+ element.query_selector_all = (
65
+ lambda selector: self.__query_selector_all(element, selector)
66
+ )
67
+ element.querySelectorAll = element.query_selector_all
68
+ element.remove_from_dom = lambda: self.__remove_from_dom(element)
69
+ element.save_screenshot = (
70
+ lambda *args, **kwargs: self.__save_screenshot(
71
+ element, *args, **kwargs)
72
+ )
73
+ element.save_to_dom = lambda: self.__save_to_dom(element)
74
+ element.scroll_into_view = lambda: self.__scroll_into_view(element)
75
+ element.select_option = lambda: self.__select_option(element)
76
+ element.send_file = (
77
+ lambda *file_paths: self.__send_file(element, *file_paths)
78
+ )
79
+ element.send_keys = lambda text: self.__send_keys(element, text)
80
+ element.set_text = lambda value: self.__set_text(element, value)
81
+ element.set_value = lambda value: self.__set_value(element, value)
82
+ element.type = lambda text: self.__type(element, text)
83
+ element.get_position = lambda: self.__get_position(element)
84
+ element.get_html = lambda: self.__get_html(element)
85
+ element.get_js_attributes = lambda: self.__get_js_attributes(element)
86
+ element.get_attribute = (
87
+ lambda attribute: self.__get_attribute(element, attribute)
88
+ )
89
+ return element
90
+
91
+ def get(self, url):
92
+ url = shared_utils.fix_url_as_needed(url)
93
+ driver = self.driver
94
+ if hasattr(driver, "cdp_base"):
95
+ driver = driver.cdp_base
96
+ self.page = self.loop.run_until_complete(driver.get(url))
97
+ url_protocol = url.split(":")[0]
98
+ safe_url = True
99
+ if url_protocol not in ["about", "data", "chrome"]:
100
+ safe_url = False
101
+ if not safe_url:
102
+ time.sleep(constants.UC.CDP_MODE_OPEN_WAIT)
103
+ if shared_utils.is_windows():
104
+ time.sleep(constants.UC.EXTRA_WINDOWS_WAIT)
105
+ else:
106
+ time.sleep(0.012)
107
+ self.__slow_mode_pause_if_set()
108
+ self.loop.run_until_complete(self.page.wait())
109
+
110
+ def open(self, url):
111
+ self.get(url)
112
+
113
+ def reload(self, ignore_cache=True, script_to_evaluate_on_load=None):
114
+ self.loop.run_until_complete(
115
+ self.page.reload(
116
+ ignore_cache=ignore_cache,
117
+ script_to_evaluate_on_load=script_to_evaluate_on_load,
118
+ )
119
+ )
120
+
121
+ def refresh(self, *args, **kwargs):
122
+ self.reload(*args, **kwargs)
123
+
124
+ def get_event_loop(self):
125
+ return self.loop
126
+
127
+ def add_handler(self, event, handler):
128
+ self.page.add_handler(event, handler)
129
+
130
+ def find_element(self, selector, best_match=False, timeout=None):
131
+ """Similar to select(), but also finds elements by text content.
132
+ When using text-based searches, if best_match=False, then will
133
+ find the first element with the text. If best_match=True, then
134
+ if multiple elements have that text, then will use the element
135
+ with the closest text-length to the text being searched for."""
136
+ if not timeout:
137
+ timeout = settings.SMALL_TIMEOUT
138
+ self.__add_light_pause()
139
+ selector = self.__convert_to_css_if_xpath(selector)
140
+ early_failure = False
141
+ if (":contains(") in selector:
142
+ selector, _ = page_utils.recalculate_selector(
143
+ selector, by="css selector", xp_ok=True
144
+ )
145
+ failure = False
146
+ try:
147
+ if early_failure:
148
+ raise Exception("Failed!")
149
+ element = self.loop.run_until_complete(
150
+ self.page.find(
151
+ selector, best_match=best_match, timeout=timeout
152
+ )
153
+ )
154
+ except Exception:
155
+ failure = True
156
+ plural = "s"
157
+ if timeout == 1:
158
+ plural = ""
159
+ message = "\n Element {%s} was not found after %s second%s!" % (
160
+ selector,
161
+ timeout,
162
+ plural,
163
+ )
164
+ if failure:
165
+ raise Exception(message)
166
+ element = self.__add_sync_methods(element)
167
+ self.__slow_mode_pause_if_set()
168
+ return element
169
+
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
224
+ self.__add_light_pause()
225
+ selector = self.__convert_to_css_if_xpath(selector)
226
+ elements = self.loop.run_until_complete(
227
+ self.page.find_all(selector, timeout=timeout)
228
+ )
229
+ updated_elements = []
230
+ for element in elements:
231
+ element = self.__add_sync_methods(element)
232
+ updated_elements.append(element)
233
+ return updated_elements
234
+
235
+ def find_elements_by_text(self, text, tag_name=None):
236
+ """Returns a list of elements by matching text.
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)"""
239
+ self.__add_light_pause()
240
+ elements = self.loop.run_until_complete(
241
+ self.page.find_elements_by_text(text=text)
242
+ )
243
+ updated_elements = []
244
+ if tag_name:
245
+ tag_name = tag_name.lower().strip()
246
+ for element in elements:
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
255
+ ):
256
+ element = self.__add_sync_methods(element)
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)
275
+ return updated_elements
276
+
277
+ def select(self, selector, timeout=None):
278
+ """Similar to find_element(), but without text-based search."""
279
+ if not timeout:
280
+ timeout = settings.SMALL_TIMEOUT
281
+ self.__add_light_pause()
282
+ selector = self.__convert_to_css_if_xpath(selector)
283
+ if (":contains(" in selector):
284
+ tag_name = selector.split(":contains(")[0].split(" ")[-1]
285
+ text = selector.split(":contains(")[1].split(")")[0][1:-1]
286
+ with suppress(Exception):
287
+ self.loop.run_until_complete(
288
+ self.page.select(tag_name, timeout=5)
289
+ )
290
+ self.loop.run_until_complete(self.page.find(text, timeout=5))
291
+ element = self.find_elements_by_text(text, tag_name=tag_name)[0]
292
+ return self.__add_sync_methods(element)
293
+ failure = False
294
+ try:
295
+ element = self.loop.run_until_complete(
296
+ self.page.select(selector, timeout=timeout)
297
+ )
298
+ except Exception:
299
+ failure = True
300
+ plural = "s"
301
+ if timeout == 1:
302
+ plural = ""
303
+ message = "\n Element {%s} was not found after %s second%s!" % (
304
+ selector,
305
+ timeout,
306
+ plural,
307
+ )
308
+ if failure:
309
+ raise Exception(message)
310
+ element = self.__add_sync_methods(element)
311
+ self.__slow_mode_pause_if_set()
312
+ return element
313
+
314
+ def select_all(self, selector, timeout=None):
315
+ if not timeout:
316
+ timeout = settings.SMALL_TIMEOUT
317
+ self.__add_light_pause()
318
+ selector = self.__convert_to_css_if_xpath(selector)
319
+ elements = self.loop.run_until_complete(
320
+ self.page.select_all(selector, timeout=timeout)
321
+ )
322
+ updated_elements = []
323
+ for element in elements:
324
+ element = self.__add_sync_methods(element)
325
+ updated_elements.append(element)
326
+ return updated_elements
327
+
328
+ def find_elements(self, selector, timeout=None):
329
+ if not timeout:
330
+ timeout = settings.SMALL_TIMEOUT
331
+ return self.select_all(selector, timeout=timeout)
332
+
333
+ def find_visible_elements(self, selector, timeout=None):
334
+ if not timeout:
335
+ timeout = settings.SMALL_TIMEOUT
336
+ visible_elements = []
337
+ elements = self.select_all(selector, timeout=timeout)
338
+ for element in elements:
339
+ with suppress(Exception):
340
+ position = element.get_position()
341
+ if (position.width != 0 or position.height != 0):
342
+ visible_elements.append(element)
343
+ return visible_elements
344
+
345
+ def click_nth_element(self, selector, number):
346
+ elements = self.select_all(selector)
347
+ if len(elements) < number:
348
+ raise Exception(
349
+ "Not enough matching {%s} elements to "
350
+ "click number %s!" % (selector, number)
351
+ )
352
+ number = number - 1
353
+ if number < 0:
354
+ number = 0
355
+ element = elements[number]
356
+ element.scroll_into_view()
357
+ element.click()
358
+
359
+ def click_nth_visible_element(self, selector, number):
360
+ """Finds all matching page elements and clicks the nth visible one.
361
+ Example: self.click_nth_visible_element('[type="checkbox"]', 5)
362
+ (Clicks the 5th visible checkbox on the page.)"""
363
+ elements = self.find_visible_elements(selector)
364
+ if len(elements) < number:
365
+ raise Exception(
366
+ "Not enough matching {%s} elements to "
367
+ "click number %s!" % (selector, number)
368
+ )
369
+ number = number - 1
370
+ if number < 0:
371
+ number = 0
372
+ element = elements[number]
373
+ element.scroll_into_view()
374
+ element.click()
375
+
376
+ def click_link(self, link_text):
377
+ self.find_elements_by_text(link_text, "a")[0].click()
378
+
379
+ def go_back(self):
380
+ self.loop.run_until_complete(self.page.back())
381
+
382
+ def go_forward(self):
383
+ self.loop.run_until_complete(self.page.forward())
384
+
385
+ def get_navigation_history(self):
386
+ return self.loop.run_until_complete(self.page.get_navigation_history())
387
+
388
+ def __clear_input(self, element):
389
+ return (
390
+ self.loop.run_until_complete(element.clear_input_async())
391
+ )
392
+
393
+ def __click(self, element):
394
+ result = (
395
+ self.loop.run_until_complete(element.click_async())
396
+ )
397
+ self.loop.run_until_complete(self.page.wait())
398
+ return result
399
+
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
408
+ return (
409
+ self.loop.run_until_complete(
410
+ element.flash_async(*args, **kwargs)
411
+ )
412
+ )
413
+
414
+ def __focus(self, element):
415
+ return (
416
+ self.loop.run_until_complete(element.focus_async())
417
+ )
418
+
419
+ def __highlight_overlay(self, element):
420
+ return (
421
+ self.loop.run_until_complete(element.highlight_overlay_async())
422
+ )
423
+
424
+ def __mouse_click(self, element):
425
+ result = (
426
+ self.loop.run_until_complete(element.mouse_click_async())
427
+ )
428
+ self.loop.run_until_complete(self.page.wait())
429
+ return result
430
+
431
+ def __mouse_drag(self, element, destination):
432
+ return (
433
+ self.loop.run_until_complete(element.mouse_drag_async(destination))
434
+ )
435
+
436
+ def __mouse_move(self, element):
437
+ return (
438
+ self.loop.run_until_complete(element.mouse_move_async())
439
+ )
440
+
441
+ def __query_selector(self, element, selector):
442
+ selector = self.__convert_to_css_if_xpath(selector)
443
+ element2 = self.loop.run_until_complete(
444
+ element.query_selector_async(selector)
445
+ )
446
+ element2 = self.__add_sync_methods(element2)
447
+ return element2
448
+
449
+ def __query_selector_all(self, element, selector):
450
+ selector = self.__convert_to_css_if_xpath(selector)
451
+ elements = self.loop.run_until_complete(
452
+ element.query_selector_all_async(selector)
453
+ )
454
+ updated_elements = []
455
+ for element in elements:
456
+ element = self.__add_sync_methods(element)
457
+ updated_elements.append(element)
458
+ self.__slow_mode_pause_if_set()
459
+ return updated_elements
460
+
461
+ def __remove_from_dom(self, element):
462
+ return (
463
+ self.loop.run_until_complete(element.remove_from_dom_async())
464
+ )
465
+
466
+ def __save_screenshot(self, element, *args, **kwargs):
467
+ return (
468
+ self.loop.run_until_complete(
469
+ element.save_screenshot_async(*args, **kwargs)
470
+ )
471
+ )
472
+
473
+ def __save_to_dom(self, element):
474
+ return (
475
+ self.loop.run_until_complete(element.save_to_dom_async())
476
+ )
477
+
478
+ def __scroll_into_view(self, element):
479
+ self.loop.run_until_complete(element.scroll_into_view_async())
480
+ self.__add_light_pause()
481
+ return None
482
+
483
+ def __select_option(self, element):
484
+ return (
485
+ self.loop.run_until_complete(element.select_option_async())
486
+ )
487
+
488
+ def __send_file(self, element, *file_paths):
489
+ return (
490
+ self.loop.run_until_complete(element.send_file_async(*file_paths))
491
+ )
492
+
493
+ def __send_keys(self, element, text):
494
+ return (
495
+ self.loop.run_until_complete(element.send_keys_async(text))
496
+ )
497
+
498
+ def __set_text(self, element, value):
499
+ return (
500
+ self.loop.run_until_complete(element.set_text_async(value))
501
+ )
502
+
503
+ def __set_value(self, element, value):
504
+ return (
505
+ self.loop.run_until_complete(element.set_value_async(value))
506
+ )
507
+
508
+ def __type(self, element, text):
509
+ with suppress(Exception):
510
+ element.clear_input()
511
+ element.send_keys(text)
512
+
513
+ def __get_position(self, element):
514
+ return (
515
+ self.loop.run_until_complete(element.get_position_async())
516
+ )
517
+
518
+ def __get_html(self, element):
519
+ return (
520
+ self.loop.run_until_complete(element.get_html_async())
521
+ )
522
+
523
+ def __get_js_attributes(self, element):
524
+ return (
525
+ self.loop.run_until_complete(element.get_js_attributes_async())
526
+ )
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
+
559
+ def tile_windows(self, windows=None, max_columns=0):
560
+ """Tile windows and return the grid of tiled windows."""
561
+ driver = self.driver
562
+ if hasattr(driver, "cdp_base"):
563
+ driver = driver.cdp_base
564
+ return self.loop.run_until_complete(
565
+ driver.tile_windows(windows, max_columns)
566
+ )
567
+
568
+ def get_all_cookies(self, *args, **kwargs):
569
+ driver = self.driver
570
+ if hasattr(driver, "cdp_base"):
571
+ driver = driver.cdp_base
572
+ return self.loop.run_until_complete(
573
+ driver.cookies.get_all(*args, **kwargs)
574
+ )
575
+
576
+ def set_all_cookies(self, *args, **kwargs):
577
+ driver = self.driver
578
+ if hasattr(driver, "cdp_base"):
579
+ driver = driver.cdp_base
580
+ return self.loop.run_until_complete(
581
+ driver.cookies.set_all(*args, **kwargs)
582
+ )
583
+
584
+ def save_cookies(self, *args, **kwargs):
585
+ driver = self.driver
586
+ if hasattr(driver, "cdp_base"):
587
+ driver = driver.cdp_base
588
+ return self.loop.run_until_complete(
589
+ driver.cookies.save(*args, **kwargs)
590
+ )
591
+
592
+ def load_cookies(self, *args, **kwargs):
593
+ driver = self.driver
594
+ if hasattr(driver, "cdp_base"):
595
+ driver = driver.cdp_base
596
+ return self.loop.run_until_complete(
597
+ driver.cookies.load(*args, **kwargs)
598
+ )
599
+
600
+ def clear_cookies(self):
601
+ driver = self.driver
602
+ if hasattr(driver, "cdp_base"):
603
+ driver = driver.cdp_base
604
+ return self.loop.run_until_complete(
605
+ driver.cookies.clear()
606
+ )
607
+
608
+ def sleep(self, seconds):
609
+ time.sleep(seconds)
610
+
611
+ def bring_active_window_to_front(self):
612
+ self.loop.run_until_complete(self.page.bring_to_front())
613
+ self.__add_light_pause()
614
+
615
+ def get_active_element(self):
616
+ return self.loop.run_until_complete(
617
+ self.page.js_dumps("document.activeElement")
618
+ )
619
+
620
+ def get_active_element_css(self):
621
+ from seleniumbase.js_code import active_css_js
622
+
623
+ js_code = active_css_js.get_active_element_css
624
+ js_code = js_code.replace("return getBestSelector", "getBestSelector")
625
+ return self.loop.run_until_complete(
626
+ self.page.evaluate(js_code)
627
+ )
628
+
629
+ def click(self, selector, timeout=None):
630
+ if not timeout:
631
+ timeout = settings.SMALL_TIMEOUT
632
+ self.__slow_mode_pause_if_set()
633
+ element = self.find_element(selector, timeout=timeout)
634
+ element.scroll_into_view()
635
+ element.click()
636
+ self.__slow_mode_pause_if_set()
637
+ self.loop.run_until_complete(self.page.wait())
638
+
639
+ def click_active_element(self):
640
+ self.loop.run_until_complete(
641
+ self.page.evaluate("document.activeElement.click()")
642
+ )
643
+ self.__slow_mode_pause_if_set()
644
+ self.loop.run_until_complete(self.page.wait())
645
+
646
+ def click_if_visible(self, selector):
647
+ if self.is_element_visible(selector):
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())
654
+
655
+ def click_visible_elements(self, selector, limit=0):
656
+ """Finds all matching page elements and clicks visible ones in order.
657
+ If a click reloads or opens a new page, the clicking will stop.
658
+ If no matching elements appear, an Exception will be raised.
659
+ If "limit" is set and > 0, will only click that many elements.
660
+ Also clicks elements that become visible from previous clicks.
661
+ Works best for actions such as clicking all checkboxes on a page.
662
+ Example: self.click_visible_elements('input[type="checkbox"]')"""
663
+ elements = self.select_all(selector)
664
+ click_count = 0
665
+ for element in elements:
666
+ if limit and limit > 0 and click_count >= limit:
667
+ return
668
+ try:
669
+ width = 0
670
+ height = 0
671
+ try:
672
+ position = element.get_position()
673
+ width = position.width
674
+ height = position.height
675
+ except Exception:
676
+ continue
677
+ if (width != 0 or height != 0):
678
+ element.scroll_into_view()
679
+ element.click()
680
+ click_count += 1
681
+ time.sleep(0.042)
682
+ self.__slow_mode_pause_if_set()
683
+ self.loop.run_until_complete(self.page.wait())
684
+ except Exception:
685
+ break
686
+
687
+ def mouse_click(self, selector, timeout=None):
688
+ """(Attempt simulating a mouse click)"""
689
+ if not timeout:
690
+ timeout = settings.SMALL_TIMEOUT
691
+ self.__slow_mode_pause_if_set()
692
+ element = self.find_element(selector, timeout=timeout)
693
+ element.scroll_into_view()
694
+ element.mouse_click()
695
+ self.__slow_mode_pause_if_set()
696
+ self.loop.run_until_complete(self.page.wait())
697
+
698
+ def nested_click(self, parent_selector, selector):
699
+ """
700
+ Find parent element and click on child element inside it.
701
+ (This can be used to click on elements inside an iframe.)
702
+ """
703
+ element = self.find_element(parent_selector)
704
+ element.query_selector(selector).mouse_click()
705
+ self.__slow_mode_pause_if_set()
706
+ self.loop.run_until_complete(self.page.wait())
707
+
708
+ def get_nested_element(self, parent_selector, selector):
709
+ """(Can be used to find an element inside an iframe)"""
710
+ element = self.find_element(parent_selector)
711
+ return element.query_selector(selector)
712
+
713
+ def select_option_by_text(self, dropdown_selector, option):
714
+ element = self.find_element(dropdown_selector)
715
+ element.scroll_into_view()
716
+ options = element.query_selector_all("option")
717
+ for found_option in options:
718
+ if found_option.text.strip() == option.strip():
719
+ found_option.select_option()
720
+ return
721
+ raise Exception(
722
+ "Unable to find text option {%s} in dropdown {%s}!"
723
+ % (dropdown_selector, option)
724
+ )
725
+
726
+ def flash(
727
+ self,
728
+ selector, # The CSS Selector to flash
729
+ duration=1, # (seconds) flash duration
730
+ color="44CC88", # RGB hex flash color
731
+ pause=0, # (seconds) If 0, the next action starts during flash
732
+ ):
733
+ """Paint a quickly-vanishing dot over an element."""
734
+ selector = self.__convert_to_css_if_xpath(selector)
735
+ element = self.find_element(selector)
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)
740
+ if pause and isinstance(pause, (int, float)):
741
+ time.sleep(pause)
742
+
743
+ def highlight(self, selector):
744
+ """Highlight an element with multi-colors."""
745
+ selector = self.__convert_to_css_if_xpath(selector)
746
+ element = self.find_element(selector)
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)
751
+ time.sleep(0.15)
752
+ element.flash(0.42, "8844CC", x_offset, y_offset)
753
+ time.sleep(0.15)
754
+ element.flash(0.38, "CC8844", x_offset, y_offset)
755
+ time.sleep(0.15)
756
+ element.flash(0.30, "44CC88", x_offset, y_offset)
757
+ time.sleep(0.30)
758
+
759
+ def focus(self, selector):
760
+ element = self.find_element(selector)
761
+ element.scroll_into_view()
762
+ element.focus()
763
+
764
+ def highlight_overlay(self, selector):
765
+ self.find_element(selector).highlight_overlay()
766
+
767
+ def remove_element(self, selector):
768
+ self.select(selector).remove_from_dom()
769
+
770
+ def remove_from_dom(self, selector):
771
+ self.select(selector).remove_from_dom()
772
+
773
+ def remove_elements(self, selector):
774
+ """Remove all elements on the page that match the selector."""
775
+ css_selector = self.__convert_to_css_if_xpath(selector)
776
+ css_selector = re.escape(css_selector) # Add "\\" to special chars
777
+ css_selector = js_utils.escape_quotes_if_needed(css_selector)
778
+ js_code = (
779
+ """var $elements = document.querySelectorAll('%s');
780
+ var index = 0, length = $elements.length;
781
+ for(; index < length; index++){
782
+ $elements[index].remove();}"""
783
+ % css_selector
784
+ )
785
+ with suppress(Exception):
786
+ self.loop.run_until_complete(self.page.evaluate(js_code))
787
+
788
+ def send_keys(self, selector, text, timeout=None):
789
+ if not timeout:
790
+ timeout = settings.SMALL_TIMEOUT
791
+ self.__slow_mode_pause_if_set()
792
+ element = self.select(selector, timeout=timeout)
793
+ element.scroll_into_view()
794
+ if text.endswith("\n") or text.endswith("\r"):
795
+ text = text[:-1] + "\r\n"
796
+ element.send_keys(text)
797
+ self.__slow_mode_pause_if_set()
798
+ self.loop.run_until_complete(self.page.wait())
799
+
800
+ def press_keys(self, selector, text, timeout=None):
801
+ """Similar to send_keys(), but presses keys at human speed."""
802
+ if not timeout:
803
+ timeout = settings.SMALL_TIMEOUT
804
+ self.__slow_mode_pause_if_set()
805
+ element = self.select(selector, timeout=timeout)
806
+ element.scroll_into_view()
807
+ submit = False
808
+ if text.endswith("\n") or text.endswith("\r"):
809
+ submit = True
810
+ text = text[:-1]
811
+ for key in text:
812
+ element.send_keys(key)
813
+ time.sleep(0.044)
814
+ if submit:
815
+ element.send_keys("\r\n")
816
+ time.sleep(0.044)
817
+ self.__slow_mode_pause_if_set()
818
+ self.loop.run_until_complete(self.page.wait())
819
+
820
+ def type(self, selector, text, timeout=None):
821
+ """Similar to send_keys(), but clears the text field first."""
822
+ if not timeout:
823
+ timeout = settings.SMALL_TIMEOUT
824
+ self.__slow_mode_pause_if_set()
825
+ element = self.select(selector, timeout=timeout)
826
+ element.scroll_into_view()
827
+ with suppress(Exception):
828
+ element.clear_input()
829
+ if text.endswith("\n") or text.endswith("\r"):
830
+ text = text[:-1] + "\r\n"
831
+ element.send_keys(text)
832
+ self.__slow_mode_pause_if_set()
833
+ self.loop.run_until_complete(self.page.wait())
834
+
835
+ def set_value(self, selector, text, timeout=None):
836
+ """Similar to send_keys(), but clears the text field first."""
837
+ if not timeout:
838
+ timeout = settings.SMALL_TIMEOUT
839
+ self.__slow_mode_pause_if_set()
840
+ selector = self.__convert_to_css_if_xpath(selector)
841
+ element = self.select(selector, timeout=timeout)
842
+ element.scroll_into_view()
843
+ press_enter = False
844
+ if text.endswith("\n"):
845
+ text = text[:-1]
846
+ press_enter = True
847
+ value = js_utils.escape_quotes_if_needed(re.escape(text))
848
+ css_selector = re.escape(selector)
849
+ css_selector = js_utils.escape_quotes_if_needed(css_selector)
850
+ set_value_script = (
851
+ """m_elm = document.querySelector('%s');"""
852
+ """m_elm.value = '%s';""" % (css_selector, value)
853
+ )
854
+ self.loop.run_until_complete(self.page.evaluate(set_value_script))
855
+ the_type = self.get_element_attribute(selector, "type")
856
+ if the_type == "range":
857
+ # Some input sliders need a mouse event to trigger listeners.
858
+ with suppress(Exception):
859
+ mouse_move_script = (
860
+ """m_elm = document.querySelector('%s');"""
861
+ """m_evt = new Event('mousemove');"""
862
+ """m_elm.dispatchEvent(m_evt);""" % css_selector
863
+ )
864
+ self.loop.run_until_complete(
865
+ self.page.evaluate(mouse_move_script)
866
+ )
867
+ elif press_enter:
868
+ self.__add_light_pause()
869
+ self.send_keys(selector, "\n")
870
+ self.__slow_mode_pause_if_set()
871
+ self.loop.run_until_complete(self.page.wait())
872
+
873
+ def evaluate(self, expression):
874
+ """Run a JavaScript expression and return the result."""
875
+ if expression.startswith("return "):
876
+ expression = expression[len("return "):]
877
+ return self.loop.run_until_complete(
878
+ self.page.evaluate(expression)
879
+ )
880
+
881
+ def js_dumps(self, obj_name):
882
+ """Similar to evaluate(), but for dictionary results."""
883
+ if obj_name.startswith("return "):
884
+ obj_name = obj_name[len("return "):]
885
+ return self.loop.run_until_complete(
886
+ self.page.js_dumps(obj_name)
887
+ )
888
+
889
+ def maximize(self):
890
+ if self.get_window()[1].window_state.value == "maximized":
891
+ return
892
+ elif self.get_window()[1].window_state.value == "minimized":
893
+ self.loop.run_until_complete(self.page.maximize())
894
+ time.sleep(0.044)
895
+ return self.loop.run_until_complete(self.page.maximize())
896
+
897
+ def minimize(self):
898
+ if self.get_window()[1].window_state.value != "minimized":
899
+ return self.loop.run_until_complete(self.page.minimize())
900
+
901
+ def medimize(self):
902
+ if self.get_window()[1].window_state.value == "minimized":
903
+ self.loop.run_until_complete(self.page.medimize())
904
+ time.sleep(0.044)
905
+ return self.loop.run_until_complete(self.page.medimize())
906
+
907
+ def set_window_rect(self, x, y, width, height):
908
+ if self.get_window()[1].window_state.value == "minimized":
909
+ self.loop.run_until_complete(
910
+ self.page.set_window_size(
911
+ left=x, top=y, width=width, height=height)
912
+ )
913
+ time.sleep(0.044)
914
+ return self.loop.run_until_complete(
915
+ self.page.set_window_size(
916
+ left=x, top=y, width=width, height=height)
917
+ )
918
+
919
+ def reset_window_size(self):
920
+ x = settings.WINDOW_START_X
921
+ y = settings.WINDOW_START_Y
922
+ width = settings.CHROME_START_WIDTH
923
+ height = settings.CHROME_START_HEIGHT
924
+ self.set_window_rect(x, y, width, height)
925
+ self.__add_light_pause()
926
+
927
+ def get_window(self):
928
+ return self.loop.run_until_complete(
929
+ self.page.get_window()
930
+ )
931
+
932
+ def get_text(self, selector):
933
+ return self.find_element(selector).text_all
934
+
935
+ def get_title(self):
936
+ return self.loop.run_until_complete(
937
+ self.page.evaluate("document.title")
938
+ )
939
+
940
+ def get_current_url(self):
941
+ return self.loop.run_until_complete(
942
+ self.page.evaluate("window.location.href")
943
+ )
944
+
945
+ def get_origin(self):
946
+ return self.loop.run_until_complete(
947
+ self.page.evaluate("window.location.origin")
948
+ )
949
+
950
+ def get_page_source(self):
951
+ try:
952
+ source = self.loop.run_until_complete(
953
+ self.page.evaluate("document.documentElement.outerHTML")
954
+ )
955
+ except Exception:
956
+ time.sleep(constants.UC.CDP_MODE_OPEN_WAIT)
957
+ source = self.loop.run_until_complete(
958
+ self.page.evaluate("document.documentElement.outerHTML")
959
+ )
960
+ return source
961
+
962
+ def get_user_agent(self):
963
+ return self.loop.run_until_complete(
964
+ self.page.evaluate("navigator.userAgent")
965
+ )
966
+
967
+ def get_cookie_string(self):
968
+ return self.loop.run_until_complete(
969
+ self.page.evaluate("document.cookie")
970
+ )
971
+
972
+ def get_locale_code(self):
973
+ return self.loop.run_until_complete(
974
+ self.page.evaluate("navigator.language || navigator.languages[0]")
975
+ )
976
+
977
+ def get_screen_rect(self):
978
+ coordinates = self.loop.run_until_complete(
979
+ self.page.js_dumps("window.screen")
980
+ )
981
+ return coordinates
982
+
983
+ def get_window_rect(self):
984
+ coordinates = {}
985
+ innerWidth = self.loop.run_until_complete(
986
+ self.page.evaluate("window.innerWidth")
987
+ )
988
+ innerHeight = self.loop.run_until_complete(
989
+ self.page.evaluate("window.innerHeight")
990
+ )
991
+ outerWidth = self.loop.run_until_complete(
992
+ self.page.evaluate("window.outerWidth")
993
+ )
994
+ outerHeight = self.loop.run_until_complete(
995
+ self.page.evaluate("window.outerHeight")
996
+ )
997
+ pageXOffset = self.loop.run_until_complete(
998
+ self.page.evaluate("window.pageXOffset")
999
+ )
1000
+ pageYOffset = self.loop.run_until_complete(
1001
+ self.page.evaluate("window.pageYOffset")
1002
+ )
1003
+ scrollX = self.loop.run_until_complete(
1004
+ self.page.evaluate("window.scrollX")
1005
+ )
1006
+ scrollY = self.loop.run_until_complete(
1007
+ self.page.evaluate("window.scrollY")
1008
+ )
1009
+ screenLeft = self.loop.run_until_complete(
1010
+ self.page.evaluate("window.screenLeft")
1011
+ )
1012
+ screenTop = self.loop.run_until_complete(
1013
+ self.page.evaluate("window.screenTop")
1014
+ )
1015
+ x = self.loop.run_until_complete(
1016
+ self.page.evaluate("window.screenX")
1017
+ )
1018
+ y = self.loop.run_until_complete(
1019
+ self.page.evaluate("window.screenY")
1020
+ )
1021
+ coordinates["innerWidth"] = innerWidth
1022
+ coordinates["innerHeight"] = innerHeight
1023
+ coordinates["outerWidth"] = outerWidth
1024
+ coordinates["outerHeight"] = outerHeight
1025
+ coordinates["width"] = outerWidth
1026
+ coordinates["height"] = outerHeight
1027
+ coordinates["pageXOffset"] = pageXOffset if pageXOffset else 0
1028
+ coordinates["pageYOffset"] = pageYOffset if pageYOffset else 0
1029
+ coordinates["scrollX"] = scrollX if scrollX else 0
1030
+ coordinates["scrollY"] = scrollY if scrollY else 0
1031
+ coordinates["screenLeft"] = screenLeft if screenLeft else 0
1032
+ coordinates["screenTop"] = screenTop if screenTop else 0
1033
+ coordinates["x"] = x if x else 0
1034
+ coordinates["y"] = y if y else 0
1035
+ return coordinates
1036
+
1037
+ def get_window_size(self):
1038
+ coordinates = {}
1039
+ outerWidth = self.loop.run_until_complete(
1040
+ self.page.evaluate("window.outerWidth")
1041
+ )
1042
+ outerHeight = self.loop.run_until_complete(
1043
+ self.page.evaluate("window.outerHeight")
1044
+ )
1045
+ coordinates["width"] = outerWidth
1046
+ coordinates["height"] = outerHeight
1047
+ return coordinates
1048
+
1049
+ def get_window_position(self):
1050
+ coordinates = {}
1051
+ x = self.loop.run_until_complete(
1052
+ self.page.evaluate("window.screenX")
1053
+ )
1054
+ y = self.loop.run_until_complete(
1055
+ self.page.evaluate("window.screenY")
1056
+ )
1057
+ coordinates["x"] = x if x else 0
1058
+ coordinates["y"] = y if y else 0
1059
+ return coordinates
1060
+
1061
+ def get_element_rect(self, selector, timeout=None):
1062
+ if not timeout:
1063
+ timeout = settings.SMALL_TIMEOUT
1064
+ selector = self.__convert_to_css_if_xpath(selector)
1065
+ self.select(selector, timeout=timeout)
1066
+ self.__add_light_pause()
1067
+ coordinates = self.loop.run_until_complete(
1068
+ self.page.js_dumps(
1069
+ """document.querySelector"""
1070
+ """('%s').getBoundingClientRect()"""
1071
+ % js_utils.escape_quotes_if_needed(re.escape(selector))
1072
+ )
1073
+ )
1074
+ return coordinates
1075
+
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)
1080
+ coordinates = {}
1081
+ coordinates["width"] = element_rect["width"]
1082
+ coordinates["height"] = element_rect["height"]
1083
+ return coordinates
1084
+
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)
1089
+ coordinates = {}
1090
+ coordinates["x"] = element_rect["x"]
1091
+ coordinates["y"] = element_rect["y"]
1092
+ return coordinates
1093
+
1094
+ def get_gui_element_rect(self, selector, timeout=None):
1095
+ """(Coordinates are relative to the screen. Not the window.)"""
1096
+ if not timeout:
1097
+ timeout = settings.SMALL_TIMEOUT
1098
+ element_rect = self.get_element_rect(selector, timeout=timeout)
1099
+ e_width = element_rect["width"]
1100
+ e_height = element_rect["height"]
1101
+ window_rect = self.get_window_rect()
1102
+ w_bottom_y = window_rect["y"] + window_rect["height"]
1103
+ viewport_height = window_rect["innerHeight"]
1104
+ x = window_rect["x"] + element_rect["x"]
1105
+ y = w_bottom_y - viewport_height + element_rect["y"]
1106
+ y_scroll_offset = window_rect["pageYOffset"]
1107
+ y = y - y_scroll_offset
1108
+ x = x + window_rect["scrollX"]
1109
+ y = y + window_rect["scrollY"]
1110
+ return ({"height": e_height, "width": e_width, "x": x, "y": y})
1111
+
1112
+ def get_gui_element_center(self, selector, timeout=None):
1113
+ """(Coordinates are relative to the screen. Not the window.)"""
1114
+ if not timeout:
1115
+ timeout = settings.SMALL_TIMEOUT
1116
+ element_rect = self.get_gui_element_rect(selector, timeout=timeout)
1117
+ e_width = element_rect["width"]
1118
+ e_height = element_rect["height"]
1119
+ e_x = element_rect["x"]
1120
+ e_y = element_rect["y"]
1121
+ return ((e_x + e_width / 2.0) + 0.5, (e_y + e_height / 2.0) + 0.5)
1122
+
1123
+ def get_document(self):
1124
+ return self.loop.run_until_complete(
1125
+ self.page.get_document()
1126
+ )
1127
+
1128
+ def get_flattened_document(self):
1129
+ return self.loop.run_until_complete(
1130
+ self.page.get_flattened_document()
1131
+ )
1132
+
1133
+ def get_element_attributes(self, selector):
1134
+ selector = self.__convert_to_css_if_xpath(selector)
1135
+ return self.loop.run_until_complete(
1136
+ self.page.js_dumps(
1137
+ """document.querySelector('%s')"""
1138
+ % js_utils.escape_quotes_if_needed(re.escape(selector))
1139
+ )
1140
+ )
1141
+
1142
+ def get_element_attribute(self, selector, attribute):
1143
+ attributes = self.get_element_attributes(selector)
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)
1154
+
1155
+ def get_element_html(self, selector):
1156
+ selector = self.__convert_to_css_if_xpath(selector)
1157
+ return self.loop.run_until_complete(
1158
+ self.page.evaluate(
1159
+ """document.querySelector('%s').outerHTML"""
1160
+ % js_utils.escape_quotes_if_needed(re.escape(selector))
1161
+ )
1162
+ )
1163
+
1164
+ def set_locale(self, locale):
1165
+ """(Settings will take effect on the next page load)"""
1166
+ self.loop.run_until_complete(self.page.set_locale(locale))
1167
+
1168
+ def set_attributes(self, selector, attribute, value):
1169
+ """This method uses JavaScript to set/update a common attribute.
1170
+ All matching selectors from querySelectorAll() are used.
1171
+ Example => (Make all links on a website redirect to Google):
1172
+ self.set_attributes("a", "href", "https://google.com")"""
1173
+ attribute = re.escape(attribute)
1174
+ attribute = js_utils.escape_quotes_if_needed(attribute)
1175
+ value = re.escape(value)
1176
+ value = js_utils.escape_quotes_if_needed(value)
1177
+ css_selector = self.__convert_to_css_if_xpath(selector)
1178
+ css_selector = re.escape(css_selector) # Add "\\" to special chars
1179
+ css_selector = js_utils.escape_quotes_if_needed(css_selector)
1180
+ js_code = """var $elements = document.querySelectorAll('%s');
1181
+ var index = 0, length = $elements.length;
1182
+ for(; index < length; index++){
1183
+ $elements[index].setAttribute('%s','%s');}""" % (
1184
+ css_selector,
1185
+ attribute,
1186
+ value,
1187
+ )
1188
+ with suppress(Exception):
1189
+ self.loop.run_until_complete(self.page.evaluate(js_code))
1190
+
1191
+ def __make_sure_pyautogui_lock_is_writable(self):
1192
+ with suppress(Exception):
1193
+ shared_utils.make_writable(constants.MultiBrowser.PYAUTOGUILOCK)
1194
+
1195
+ def __verify_pyautogui_has_a_headed_browser(self):
1196
+ """PyAutoGUI requires a headed browser so that it can
1197
+ focus on the correct element when performing actions."""
1198
+ driver = self.driver
1199
+ if hasattr(driver, "cdp_base"):
1200
+ driver = driver.cdp_base
1201
+ if driver.config.headless:
1202
+ raise Exception(
1203
+ "PyAutoGUI can't be used in headless mode!"
1204
+ )
1205
+
1206
+ def __install_pyautogui_if_missing(self):
1207
+ self.__verify_pyautogui_has_a_headed_browser()
1208
+ driver = self.driver
1209
+ if hasattr(driver, "cdp_base"):
1210
+ driver = driver.cdp_base
1211
+ pip_find_lock = fasteners.InterProcessLock(
1212
+ constants.PipInstall.FINDLOCK
1213
+ )
1214
+ with pip_find_lock: # Prevent issues with multiple processes
1215
+ with suppress(Exception):
1216
+ shared_utils.make_writable(constants.PipInstall.FINDLOCK)
1217
+ try:
1218
+ import pyautogui
1219
+ with suppress(Exception):
1220
+ use_pyautogui_ver = constants.PyAutoGUI.VER
1221
+ if pyautogui.__version__ != use_pyautogui_ver:
1222
+ del pyautogui
1223
+ shared_utils.pip_install(
1224
+ "pyautogui", version=use_pyautogui_ver
1225
+ )
1226
+ import pyautogui
1227
+ except Exception:
1228
+ print("\nPyAutoGUI required! Installing now...")
1229
+ shared_utils.pip_install(
1230
+ "pyautogui", version=constants.PyAutoGUI.VER
1231
+ )
1232
+ try:
1233
+ import pyautogui
1234
+ except Exception:
1235
+ if (
1236
+ shared_utils.is_linux()
1237
+ and (not sb_config.headed or sb_config.xvfb)
1238
+ and not driver.config.headless
1239
+ ):
1240
+ from sbvirtualdisplay import Display
1241
+ xvfb_width = 1366
1242
+ xvfb_height = 768
1243
+ if (
1244
+ hasattr(sb_config, "_xvfb_width")
1245
+ and sb_config._xvfb_width
1246
+ and isinstance(sb_config._xvfb_width, int)
1247
+ and hasattr(sb_config, "_xvfb_height")
1248
+ and sb_config._xvfb_height
1249
+ and isinstance(sb_config._xvfb_height, int)
1250
+ ):
1251
+ xvfb_width = sb_config._xvfb_width
1252
+ xvfb_height = sb_config._xvfb_height
1253
+ if xvfb_width < 1024:
1254
+ xvfb_width = 1024
1255
+ sb_config._xvfb_width = xvfb_width
1256
+ if xvfb_height < 768:
1257
+ xvfb_height = 768
1258
+ sb_config._xvfb_height = xvfb_height
1259
+ with suppress(Exception):
1260
+ xvfb_display = Display(
1261
+ visible=True,
1262
+ size=(xvfb_width, xvfb_height),
1263
+ backend="xvfb",
1264
+ use_xauth=True,
1265
+ )
1266
+ xvfb_display.start()
1267
+
1268
+ def __get_configured_pyautogui(self, pyautogui_copy):
1269
+ if (
1270
+ shared_utils.is_linux()
1271
+ and hasattr(pyautogui_copy, "_pyautogui_x11")
1272
+ and "DISPLAY" in os.environ.keys()
1273
+ ):
1274
+ if (
1275
+ hasattr(sb_config, "_pyautogui_x11_display")
1276
+ and sb_config._pyautogui_x11_display
1277
+ and hasattr(pyautogui_copy._pyautogui_x11, "_display")
1278
+ and (
1279
+ sb_config._pyautogui_x11_display
1280
+ == pyautogui_copy._pyautogui_x11._display
1281
+ )
1282
+ ):
1283
+ pass
1284
+ else:
1285
+ import Xlib.display
1286
+ pyautogui_copy._pyautogui_x11._display = (
1287
+ Xlib.display.Display(os.environ['DISPLAY'])
1288
+ )
1289
+ sb_config._pyautogui_x11_display = (
1290
+ pyautogui_copy._pyautogui_x11._display
1291
+ )
1292
+ return pyautogui_copy
1293
+
1294
+ def gui_press_key(self, key):
1295
+ self.__install_pyautogui_if_missing()
1296
+ import pyautogui
1297
+ pyautogui = self.__get_configured_pyautogui(pyautogui)
1298
+ gui_lock = fasteners.InterProcessLock(
1299
+ constants.MultiBrowser.PYAUTOGUILOCK
1300
+ )
1301
+ with gui_lock:
1302
+ self.__make_sure_pyautogui_lock_is_writable()
1303
+ pyautogui.press(key)
1304
+ time.sleep(0.044)
1305
+ self.__slow_mode_pause_if_set()
1306
+ self.loop.run_until_complete(self.page.wait())
1307
+
1308
+ def gui_press_keys(self, keys):
1309
+ self.__install_pyautogui_if_missing()
1310
+ import pyautogui
1311
+ pyautogui = self.__get_configured_pyautogui(pyautogui)
1312
+ gui_lock = fasteners.InterProcessLock(
1313
+ constants.MultiBrowser.PYAUTOGUILOCK
1314
+ )
1315
+ with gui_lock:
1316
+ self.__make_sure_pyautogui_lock_is_writable()
1317
+ for key in keys:
1318
+ pyautogui.press(key)
1319
+ time.sleep(0.044)
1320
+ self.__slow_mode_pause_if_set()
1321
+ self.loop.run_until_complete(self.page.wait())
1322
+
1323
+ def gui_write(self, text):
1324
+ self.__install_pyautogui_if_missing()
1325
+ import pyautogui
1326
+ pyautogui = self.__get_configured_pyautogui(pyautogui)
1327
+ gui_lock = fasteners.InterProcessLock(
1328
+ constants.MultiBrowser.PYAUTOGUILOCK
1329
+ )
1330
+ with gui_lock:
1331
+ self.__make_sure_pyautogui_lock_is_writable()
1332
+ pyautogui.write(text)
1333
+ self.__slow_mode_pause_if_set()
1334
+ self.loop.run_until_complete(self.page.wait())
1335
+
1336
+ def __gui_click_x_y(self, x, y, timeframe=0.25, uc_lock=False):
1337
+ self.__install_pyautogui_if_missing()
1338
+ import pyautogui
1339
+ pyautogui = self.__get_configured_pyautogui(pyautogui)
1340
+ screen_width, screen_height = pyautogui.size()
1341
+ if x < 0 or y < 0 or x > screen_width or y > screen_height:
1342
+ raise Exception(
1343
+ "PyAutoGUI cannot click on point (%s, %s)"
1344
+ " outside screen. (Width: %s, Height: %s)"
1345
+ % (x, y, screen_width, screen_height)
1346
+ )
1347
+ if uc_lock:
1348
+ gui_lock = fasteners.InterProcessLock(
1349
+ constants.MultiBrowser.PYAUTOGUILOCK
1350
+ )
1351
+ with gui_lock: # Prevent issues with multiple processes
1352
+ self.__make_sure_pyautogui_lock_is_writable()
1353
+ pyautogui.moveTo(x, y, timeframe, pyautogui.easeOutQuad)
1354
+ if timeframe >= 0.25:
1355
+ time.sleep(0.056) # Wait if moving at human-speed
1356
+ if "--debug" in sys.argv:
1357
+ print(" <DEBUG> pyautogui.click(%s, %s)" % (x, y))
1358
+ pyautogui.click(x=x, y=y)
1359
+ else:
1360
+ # Called from a method where the gui_lock is already active
1361
+ pyautogui.moveTo(x, y, timeframe, pyautogui.easeOutQuad)
1362
+ if timeframe >= 0.25:
1363
+ time.sleep(0.056) # Wait if moving at human-speed
1364
+ if "--debug" in sys.argv:
1365
+ print(" <DEBUG> pyautogui.click(%s, %s)" % (x, y))
1366
+ pyautogui.click(x=x, y=y)
1367
+
1368
+ def gui_click_x_y(self, x, y, timeframe=0.25):
1369
+ gui_lock = fasteners.InterProcessLock(
1370
+ constants.MultiBrowser.PYAUTOGUILOCK
1371
+ )
1372
+ with gui_lock: # Prevent issues with multiple processes
1373
+ self.__make_sure_pyautogui_lock_is_writable()
1374
+ self.__install_pyautogui_if_missing()
1375
+ import pyautogui
1376
+ pyautogui = self.__get_configured_pyautogui(pyautogui)
1377
+ width_ratio = 1.0
1378
+ if shared_utils.is_windows():
1379
+ window_rect = self.get_window_rect()
1380
+ width = window_rect["width"]
1381
+ height = window_rect["height"]
1382
+ win_x = window_rect["x"]
1383
+ win_y = window_rect["y"]
1384
+ scr_width = pyautogui.size().width
1385
+ self.maximize()
1386
+ self.__add_light_pause()
1387
+ win_width = self.get_window_size()["width"]
1388
+ width_ratio = round(float(scr_width) / float(win_width), 2)
1389
+ width_ratio += 0.01
1390
+ if width_ratio < 0.45 or width_ratio > 2.55:
1391
+ width_ratio = 1.01
1392
+ sb_config._saved_width_ratio = width_ratio
1393
+ self.minimize()
1394
+ self.__add_light_pause()
1395
+ self.set_window_rect(win_x, win_y, width, height)
1396
+ self.__add_light_pause()
1397
+ x = x * width_ratio
1398
+ y = y * width_ratio
1399
+ self.bring_active_window_to_front()
1400
+ self.__gui_click_x_y(x, y, timeframe=timeframe, uc_lock=False)
1401
+
1402
+ def gui_click_element(self, selector, timeframe=0.25):
1403
+ self.__slow_mode_pause_if_set()
1404
+ x, y = self.get_gui_element_center(selector)
1405
+ self.__add_light_pause()
1406
+ self.gui_click_x_y(x, y, timeframe=timeframe)
1407
+ self.__slow_mode_pause_if_set()
1408
+ self.loop.run_until_complete(self.page.wait())
1409
+
1410
+ def __gui_drag_drop(self, x1, y1, x2, y2, timeframe=0.25, uc_lock=False):
1411
+ self.__install_pyautogui_if_missing()
1412
+ import pyautogui
1413
+ pyautogui = self.__get_configured_pyautogui(pyautogui)
1414
+ screen_width, screen_height = pyautogui.size()
1415
+ if x1 < 0 or y1 < 0 or x1 > screen_width or y1 > screen_height:
1416
+ raise Exception(
1417
+ "PyAutoGUI cannot drag-drop from point (%s, %s)"
1418
+ " outside screen. (Width: %s, Height: %s)"
1419
+ % (x1, y1, screen_width, screen_height)
1420
+ )
1421
+ if x2 < 0 or y2 < 0 or x2 > screen_width or y2 > screen_height:
1422
+ raise Exception(
1423
+ "PyAutoGUI cannot drag-drop to point (%s, %s)"
1424
+ " outside screen. (Width: %s, Height: %s)"
1425
+ % (x2, y2, screen_width, screen_height)
1426
+ )
1427
+ if uc_lock:
1428
+ gui_lock = fasteners.InterProcessLock(
1429
+ constants.MultiBrowser.PYAUTOGUILOCK
1430
+ )
1431
+ with gui_lock: # Prevent issues with multiple processes
1432
+ pyautogui.moveTo(x1, y1, 0.25, pyautogui.easeOutQuad)
1433
+ self.__add_light_pause()
1434
+ if "--debug" in sys.argv:
1435
+ print(" <DEBUG> pyautogui.moveTo(%s, %s)" % (x1, y1))
1436
+ pyautogui.dragTo(x2, y2, button="left", duration=timeframe)
1437
+ else:
1438
+ # Called from a method where the gui_lock is already active
1439
+ pyautogui.moveTo(x1, y1, 0.25, pyautogui.easeOutQuad)
1440
+ self.__add_light_pause()
1441
+ if "--debug" in sys.argv:
1442
+ print(" <DEBUG> pyautogui.dragTo(%s, %s)" % (x2, y2))
1443
+ pyautogui.dragTo(x2, y2, button="left", duration=timeframe)
1444
+
1445
+ def gui_drag_drop_points(self, x1, y1, x2, y2, timeframe=0.35):
1446
+ gui_lock = fasteners.InterProcessLock(
1447
+ constants.MultiBrowser.PYAUTOGUILOCK
1448
+ )
1449
+ with gui_lock: # Prevent issues with multiple processes
1450
+ self.__install_pyautogui_if_missing()
1451
+ import pyautogui
1452
+ pyautogui = self.__get_configured_pyautogui(pyautogui)
1453
+ width_ratio = 1.0
1454
+ if shared_utils.is_windows():
1455
+ window_rect = self.get_window_rect()
1456
+ width = window_rect["width"]
1457
+ height = window_rect["height"]
1458
+ win_x = window_rect["x"]
1459
+ win_y = window_rect["y"]
1460
+ scr_width = pyautogui.size().width
1461
+ self.maximize()
1462
+ self.__add_light_pause()
1463
+ win_width = self.get_window_size()["width"]
1464
+ width_ratio = round(float(scr_width) / float(win_width), 2)
1465
+ width_ratio += 0.01
1466
+ if width_ratio < 0.45 or width_ratio > 2.55:
1467
+ width_ratio = 1.01
1468
+ sb_config._saved_width_ratio = width_ratio
1469
+ self.minimize()
1470
+ self.__add_light_pause()
1471
+ self.set_window_rect(win_x, win_y, width, height)
1472
+ self.__add_light_pause()
1473
+ x1 = x1 * width_ratio
1474
+ y1 = y1 * width_ratio
1475
+ x2 = x2 * width_ratio
1476
+ y2 = y2 * width_ratio
1477
+ self.bring_active_window_to_front()
1478
+ self.__gui_drag_drop(
1479
+ x1, y1, x2, y2, timeframe=timeframe, uc_lock=False
1480
+ )
1481
+ self.__slow_mode_pause_if_set()
1482
+ self.loop.run_until_complete(self.page.wait())
1483
+
1484
+ def gui_drag_and_drop(self, drag_selector, drop_selector, timeframe=0.35):
1485
+ self.__slow_mode_pause_if_set()
1486
+ self.bring_active_window_to_front()
1487
+ x1, y1 = self.get_gui_element_center(drag_selector)
1488
+ self.__add_light_pause()
1489
+ x2, y2 = self.get_gui_element_center(drop_selector)
1490
+ self.__add_light_pause()
1491
+ self.gui_drag_drop_points(x1, y1, x2, y2, timeframe=timeframe)
1492
+
1493
+ def __gui_hover_x_y(self, x, y, timeframe=0.25, uc_lock=False):
1494
+ self.__install_pyautogui_if_missing()
1495
+ import pyautogui
1496
+ pyautogui = self.__get_configured_pyautogui(pyautogui)
1497
+ screen_width, screen_height = pyautogui.size()
1498
+ if x < 0 or y < 0 or x > screen_width or y > screen_height:
1499
+ raise Exception(
1500
+ "PyAutoGUI cannot hover on point (%s, %s)"
1501
+ " outside screen. (Width: %s, Height: %s)"
1502
+ % (x, y, screen_width, screen_height)
1503
+ )
1504
+ if uc_lock:
1505
+ gui_lock = fasteners.InterProcessLock(
1506
+ constants.MultiBrowser.PYAUTOGUILOCK
1507
+ )
1508
+ with gui_lock: # Prevent issues with multiple processes
1509
+ pyautogui.moveTo(x, y, timeframe, pyautogui.easeOutQuad)
1510
+ time.sleep(0.056)
1511
+ if "--debug" in sys.argv:
1512
+ print(" <DEBUG> pyautogui.moveTo(%s, %s)" % (x, y))
1513
+ else:
1514
+ # Called from a method where the gui_lock is already active
1515
+ pyautogui.moveTo(x, y, timeframe, pyautogui.easeOutQuad)
1516
+ time.sleep(0.056)
1517
+ if "--debug" in sys.argv:
1518
+ print(" <DEBUG> pyautogui.moveTo(%s, %s)" % (x, y))
1519
+
1520
+ def gui_hover_x_y(self, x, y, timeframe=0.25):
1521
+ gui_lock = fasteners.InterProcessLock(
1522
+ constants.MultiBrowser.PYAUTOGUILOCK
1523
+ )
1524
+ with gui_lock: # Prevent issues with multiple processes
1525
+ self.__install_pyautogui_if_missing()
1526
+ import pyautogui
1527
+ pyautogui = self.__get_configured_pyautogui(pyautogui)
1528
+ width_ratio = 1.0
1529
+ if (
1530
+ shared_utils.is_windows()
1531
+ and (
1532
+ not hasattr(sb_config, "_saved_width_ratio")
1533
+ or not sb_config._saved_width_ratio
1534
+ )
1535
+ ):
1536
+ window_rect = self.get_window_rect()
1537
+ width = window_rect["width"]
1538
+ height = window_rect["height"]
1539
+ win_x = window_rect["x"]
1540
+ win_y = window_rect["y"]
1541
+ if (
1542
+ hasattr(sb_config, "_saved_width_ratio")
1543
+ and sb_config._saved_width_ratio
1544
+ ):
1545
+ width_ratio = sb_config._saved_width_ratio
1546
+ else:
1547
+ scr_width = pyautogui.size().width
1548
+ self.maximize()
1549
+ self.__add_light_pause()
1550
+ win_width = self.get_window_size()["width"]
1551
+ width_ratio = round(float(scr_width) / float(win_width), 2)
1552
+ width_ratio += 0.01
1553
+ if width_ratio < 0.45 or width_ratio > 2.55:
1554
+ width_ratio = 1.01
1555
+ sb_config._saved_width_ratio = width_ratio
1556
+ self.set_window_rect(win_x, win_y, width, height)
1557
+ self.__add_light_pause()
1558
+ self.bring_active_window_to_front()
1559
+ elif (
1560
+ shared_utils.is_windows()
1561
+ and hasattr(sb_config, "_saved_width_ratio")
1562
+ and sb_config._saved_width_ratio
1563
+ ):
1564
+ width_ratio = sb_config._saved_width_ratio
1565
+ self.bring_active_window_to_front()
1566
+ if shared_utils.is_windows():
1567
+ x = x * width_ratio
1568
+ y = y * width_ratio
1569
+ self.__gui_hover_x_y(x, y, timeframe=timeframe, uc_lock=False)
1570
+ return
1571
+ self.bring_active_window_to_front()
1572
+ self.__gui_hover_x_y(x, y, timeframe=timeframe, uc_lock=False)
1573
+
1574
+ def gui_hover_element(self, selector, timeframe=0.25):
1575
+ self.__slow_mode_pause_if_set()
1576
+ element_rect = self.get_gui_element_rect(selector)
1577
+ width = element_rect["width"]
1578
+ height = element_rect["height"]
1579
+ if width > 0 and height > 0:
1580
+ x, y = self.get_gui_element_center(selector)
1581
+ self.bring_active_window_to_front()
1582
+ self.__gui_hover_x_y(x, y, timeframe=timeframe)
1583
+ self.__slow_mode_pause_if_set()
1584
+ self.loop.run_until_complete(self.page.wait())
1585
+
1586
+ def gui_hover_and_click(self, hover_selector, click_selector):
1587
+ gui_lock = fasteners.InterProcessLock(
1588
+ constants.MultiBrowser.PYAUTOGUILOCK
1589
+ )
1590
+ with gui_lock:
1591
+ self.__make_sure_pyautogui_lock_is_writable()
1592
+ self.bring_active_window_to_front()
1593
+ self.gui_hover_element(hover_selector)
1594
+ time.sleep(0.15)
1595
+ self.gui_hover_element(click_selector)
1596
+ self.click(click_selector)
1597
+
1598
+ def internalize_links(self):
1599
+ """All `target="_blank"` links become `target="_self"`.
1600
+ This prevents those links from opening in a new tab."""
1601
+ self.set_attributes('[target="_blank"]', "target", "_self")
1602
+
1603
+ def is_checked(self, selector):
1604
+ """Return True if checkbox (or radio button) is checked."""
1605
+ selector = self.__convert_to_css_if_xpath(selector)
1606
+ self.find_element(selector, timeout=settings.SMALL_TIMEOUT)
1607
+ return self.get_element_attribute(selector, "checked")
1608
+
1609
+ def is_selected(self, selector):
1610
+ selector = self.__convert_to_css_if_xpath(selector)
1611
+ return self.is_checked(selector)
1612
+
1613
+ def check_if_unchecked(self, selector):
1614
+ selector = self.__convert_to_css_if_xpath(selector)
1615
+ if not self.is_checked(selector):
1616
+ self.click(selector)
1617
+
1618
+ def select_if_unselected(self, selector):
1619
+ selector = self.__convert_to_css_if_xpath(selector)
1620
+ self.check_if_unchecked(selector)
1621
+
1622
+ def uncheck_if_checked(self, selector):
1623
+ selector = self.__convert_to_css_if_xpath(selector)
1624
+ if self.is_checked(selector):
1625
+ self.click(selector)
1626
+
1627
+ def unselect_if_selected(self, selector):
1628
+ selector = self.__convert_to_css_if_xpath(selector)
1629
+ self.uncheck_if_checked(selector)
1630
+
1631
+ def is_element_present(self, selector):
1632
+ try:
1633
+ self.select(selector, timeout=0.01)
1634
+ return True
1635
+ except Exception:
1636
+ return False
1637
+
1638
+ def is_element_visible(self, selector):
1639
+ selector = self.__convert_to_css_if_xpath(selector)
1640
+ element = None
1641
+ if ":contains(" not in selector:
1642
+ try:
1643
+ element = self.select(selector, timeout=0.01)
1644
+ except Exception:
1645
+ return False
1646
+ if not element:
1647
+ return False
1648
+ try:
1649
+ position = element.get_position()
1650
+ return (position.width != 0 or position.height != 0)
1651
+ except Exception:
1652
+ return False
1653
+ else:
1654
+ with suppress(Exception):
1655
+ tag_name = selector.split(":contains(")[0].split(" ")[-1]
1656
+ text = selector.split(":contains(")[1].split(")")[0][1:-1]
1657
+ self.loop.run_until_complete(
1658
+ self.page.select(tag_name, timeout=0.1)
1659
+ )
1660
+ self.loop.run_until_complete(self.page.find(text, timeout=0.1))
1661
+ return True
1662
+ return False
1663
+
1664
+ def wait_for_element_visible(self, selector, timeout=None):
1665
+ if not timeout:
1666
+ timeout = settings.SMALL_TIMEOUT
1667
+ try:
1668
+ self.select(selector, timeout=timeout)
1669
+ except Exception:
1670
+ raise Exception("Element {%s} was not found!" % selector)
1671
+ for i in range(30):
1672
+ if self.is_element_visible(selector):
1673
+ return self.select(selector)
1674
+ time.sleep(0.1)
1675
+ raise Exception("Element {%s} was not visible!" % selector)
1676
+
1677
+ def assert_element(self, selector, timeout=None):
1678
+ """Same as assert_element_visible()"""
1679
+ if not timeout:
1680
+ timeout = settings.SMALL_TIMEOUT
1681
+ try:
1682
+ self.select(selector, timeout=timeout)
1683
+ except Exception:
1684
+ raise Exception("Element {%s} was not found!" % selector)
1685
+ for i in range(30):
1686
+ if self.is_element_visible(selector):
1687
+ return True
1688
+ time.sleep(0.1)
1689
+ raise Exception("Element {%s} was not visible!" % selector)
1690
+
1691
+ def assert_element_visible(self, selector, timeout=None):
1692
+ """Same as assert_element()"""
1693
+ if not timeout:
1694
+ timeout = settings.SMALL_TIMEOUT
1695
+ try:
1696
+ self.select(selector, timeout=timeout)
1697
+ except Exception:
1698
+ raise Exception("Element {%s} was not found!" % selector)
1699
+ for i in range(30):
1700
+ if self.is_element_visible(selector):
1701
+ return True
1702
+ time.sleep(0.1)
1703
+ raise Exception("Element {%s} was not visible!" % selector)
1704
+
1705
+ def assert_element_present(self, selector, timeout=None):
1706
+ """Assert element is present in the DOM. (Visibility NOT required)"""
1707
+ if not timeout:
1708
+ timeout = settings.SMALL_TIMEOUT
1709
+ try:
1710
+ self.select(selector, timeout=timeout)
1711
+ except Exception:
1712
+ raise Exception("Element {%s} was not found!" % selector)
1713
+ return True
1714
+
1715
+ def assert_element_absent(self, selector, timeout=None):
1716
+ """Assert element is not present in the DOM."""
1717
+ if not timeout:
1718
+ timeout = settings.SMALL_TIMEOUT
1719
+ start_ms = time.time() * 1000.0
1720
+ stop_ms = start_ms + (timeout * 1000.0)
1721
+ for i in range(int(timeout * 10)):
1722
+ if not self.is_element_present(selector):
1723
+ return True
1724
+ now_ms = time.time() * 1000.0
1725
+ if now_ms >= stop_ms:
1726
+ break
1727
+ time.sleep(0.1)
1728
+ plural = "s"
1729
+ if timeout == 1:
1730
+ plural = ""
1731
+ raise Exception(
1732
+ "Element {%s} was still present after %s second%s!"
1733
+ % (selector, timeout, plural)
1734
+ )
1735
+
1736
+ def assert_element_not_visible(self, selector, timeout=None):
1737
+ """Assert element is not visible on page. (May still be in DOM)"""
1738
+ if not timeout:
1739
+ timeout = settings.SMALL_TIMEOUT
1740
+ start_ms = time.time() * 1000.0
1741
+ stop_ms = start_ms + (timeout * 1000.0)
1742
+ for i in range(int(timeout * 10)):
1743
+ if not self.is_element_present(selector):
1744
+ return True
1745
+ elif not self.is_element_visible(selector):
1746
+ return True
1747
+ now_ms = time.time() * 1000.0
1748
+ if now_ms >= stop_ms:
1749
+ break
1750
+ time.sleep(0.1)
1751
+ plural = "s"
1752
+ if timeout == 1:
1753
+ plural = ""
1754
+ raise Exception(
1755
+ "Element {%s} was still visible after %s second%s!"
1756
+ % (selector, timeout, plural)
1757
+ )
1758
+
1759
+ def assert_element_attribute(self, selector, attribute, value=None):
1760
+ attributes = self.get_element_attributes(selector)
1761
+ if attribute not in attributes:
1762
+ raise Exception(
1763
+ "Attribute {%s} was not found in element {%s}!"
1764
+ % (attribute, selector)
1765
+ )
1766
+ if value and attributes[attribute] != value:
1767
+ raise Exception(
1768
+ "Expected value {%s} of attribute {%s} "
1769
+ "was not found in element {%s}! "
1770
+ "(Actual value was {%s})"
1771
+ % (value, attribute, selector, attributes[attribute])
1772
+ )
1773
+
1774
+ def assert_title(self, title):
1775
+ expected = title.strip()
1776
+ actual = self.get_title().strip()
1777
+ error = (
1778
+ "Expected page title [%s] does not match the actual title [%s]!"
1779
+ )
1780
+ try:
1781
+ if expected != actual:
1782
+ raise Exception(error % (expected, actual))
1783
+ except Exception:
1784
+ time.sleep(2)
1785
+ actual = self.get_title().strip()
1786
+ if expected != actual:
1787
+ raise Exception(error % (expected, actual))
1788
+
1789
+ def assert_title_contains(self, substring):
1790
+ expected = substring.strip()
1791
+ actual = self.get_title().strip()
1792
+ error = (
1793
+ "Expected title substring [%s] does not appear "
1794
+ "in the actual page title [%s]!"
1795
+ )
1796
+ try:
1797
+ if expected not in actual:
1798
+ raise Exception(error % (expected, actual))
1799
+ except Exception:
1800
+ time.sleep(2)
1801
+ actual = self.get_title().strip()
1802
+ if expected not in actual:
1803
+ raise Exception(error % (expected, actual))
1804
+
1805
+ def assert_url(self, url):
1806
+ expected = url.strip()
1807
+ actual = self.get_current_url().strip()
1808
+ error = "Expected URL [%s] does not match the actual URL [%s]!"
1809
+ try:
1810
+ if expected != actual:
1811
+ raise Exception(error % (expected, actual))
1812
+ except Exception:
1813
+ time.sleep(2)
1814
+ actual = self.get_current_url().strip()
1815
+ if expected != actual:
1816
+ raise Exception(error % (expected, actual))
1817
+
1818
+ def assert_url_contains(self, substring):
1819
+ expected = substring.strip()
1820
+ actual = self.get_current_url().strip()
1821
+ error = (
1822
+ "Expected URL substring [%s] does not appear "
1823
+ "in the full URL [%s]!"
1824
+ )
1825
+ try:
1826
+ if expected not in actual:
1827
+ raise Exception(error % (expected, actual))
1828
+ except Exception:
1829
+ time.sleep(2)
1830
+ actual = self.get_current_url().strip()
1831
+ if expected not in actual:
1832
+ raise Exception(error % (expected, actual))
1833
+
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)
1839
+ text = text.strip()
1840
+ element = None
1841
+ try:
1842
+ element = self.find_element(selector, timeout=timeout)
1843
+ except Exception:
1844
+ raise Exception("Element {%s} not found!" % selector)
1845
+ for i in range(int(timeout * 10)):
1846
+ with suppress(Exception):
1847
+ element = self.find_element(selector, timeout=0.1)
1848
+ if text in element.text_all:
1849
+ return True
1850
+ now_ms = time.time() * 1000.0
1851
+ if now_ms >= stop_ms:
1852
+ break
1853
+ time.sleep(0.1)
1854
+ raise Exception(
1855
+ "Text {%s} not found in {%s}! Actual text: {%s}"
1856
+ % (text, selector, element.text_all)
1857
+ )
1858
+
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)
1864
+ text = text.strip()
1865
+ element = None
1866
+ try:
1867
+ element = self.select(selector, timeout=timeout)
1868
+ except Exception:
1869
+ raise Exception("Element {%s} not found!" % selector)
1870
+ for i in range(int(timeout * 10)):
1871
+ with suppress(Exception):
1872
+ element = self.select(selector, timeout=0.1)
1873
+ if (
1874
+ self.is_element_visible(selector)
1875
+ and text.strip() == element.text_all.strip()
1876
+ ):
1877
+ return True
1878
+ now_ms = time.time() * 1000.0
1879
+ if now_ms >= stop_ms:
1880
+ break
1881
+ time.sleep(0.1)
1882
+ raise Exception(
1883
+ "Expected Text {%s}, is not equal to {%s} in {%s}!"
1884
+ % (text, element.text_all, selector)
1885
+ )
1886
+
1887
+ def assert_true(self, expression):
1888
+ if not expression:
1889
+ raise AssertionError("%s is not true" % expression)
1890
+
1891
+ def assert_false(self, expression):
1892
+ if expression:
1893
+ raise AssertionError("%s is not false" % expression)
1894
+
1895
+ def assert_equal(self, first, second):
1896
+ if first != second:
1897
+ raise AssertionError("%s is not equal to %s" % (first, second))
1898
+
1899
+ def assert_not_equal(self, first, second):
1900
+ if first == second:
1901
+ raise AssertionError("%s is equal to %s" % (first, second))
1902
+
1903
+ def assert_in(self, first, second):
1904
+ if first not in second:
1905
+ raise AssertionError("%s is not in %s" % (first, second))
1906
+
1907
+ def assert_not_in(self, first, second):
1908
+ if first in second:
1909
+ raise AssertionError("%s is in %s" % (first, second))
1910
+
1911
+ def scroll_into_view(self, selector):
1912
+ self.find_element(selector).scroll_into_view()
1913
+ self.loop.run_until_complete(self.page.wait())
1914
+
1915
+ def scroll_to_y(self, y):
1916
+ y = int(y)
1917
+ js_code = "window.scrollTo(0, %s);" % y
1918
+ with suppress(Exception):
1919
+ self.loop.run_until_complete(self.page.evaluate(js_code))
1920
+ self.loop.run_until_complete(self.page.wait())
1921
+
1922
+ def scroll_to_top(self):
1923
+ js_code = "window.scrollTo(0, 0);"
1924
+ with suppress(Exception):
1925
+ self.loop.run_until_complete(self.page.evaluate(js_code))
1926
+ self.loop.run_until_complete(self.page.wait())
1927
+
1928
+ def scroll_to_bottom(self):
1929
+ js_code = "window.scrollTo(0, 10000);"
1930
+ with suppress(Exception):
1931
+ self.loop.run_until_complete(self.page.evaluate(js_code))
1932
+ self.loop.run_until_complete(self.page.wait())
1933
+
1934
+ def scroll_up(self, amount=25):
1935
+ self.loop.run_until_complete(self.page.scroll_up(amount))
1936
+ self.loop.run_until_complete(self.page.wait())
1937
+
1938
+ def scroll_down(self, amount=25):
1939
+ self.loop.run_until_complete(self.page.scroll_down(amount))
1940
+ self.loop.run_until_complete(self.page.wait())
1941
+
1942
+ def save_screenshot(self, name, folder=None, selector=None):
1943
+ filename = name
1944
+ if folder:
1945
+ filename = os.path.join(folder, name)
1946
+ if not selector:
1947
+ self.loop.run_until_complete(
1948
+ self.page.save_screenshot(filename)
1949
+ )
1950
+ else:
1951
+ self.select(selector).save_screenshot(filename)