seleniumbase 4.41.3__py3-none-any.whl → 4.45.10__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.
- sbase/steps.py +9 -0
- seleniumbase/__version__.py +1 -1
- seleniumbase/behave/behave_helper.py +2 -0
- seleniumbase/behave/behave_sb.py +21 -8
- seleniumbase/common/decorators.py +3 -1
- seleniumbase/console_scripts/run.py +1 -0
- seleniumbase/console_scripts/sb_caseplans.py +3 -4
- seleniumbase/console_scripts/sb_install.py +142 -11
- seleniumbase/console_scripts/sb_mkchart.py +1 -2
- seleniumbase/console_scripts/sb_mkdir.py +99 -29
- seleniumbase/console_scripts/sb_mkfile.py +1 -2
- seleniumbase/console_scripts/sb_mkpres.py +1 -2
- seleniumbase/console_scripts/sb_mkrec.py +26 -2
- seleniumbase/console_scripts/sb_objectify.py +4 -5
- seleniumbase/console_scripts/sb_print.py +1 -1
- seleniumbase/console_scripts/sb_recorder.py +40 -3
- seleniumbase/core/browser_launcher.py +474 -151
- seleniumbase/core/detect_b_ver.py +258 -16
- seleniumbase/core/log_helper.py +15 -21
- seleniumbase/core/mysql.py +1 -1
- seleniumbase/core/recorder_helper.py +3 -0
- seleniumbase/core/report_helper.py +9 -12
- seleniumbase/core/sb_cdp.py +734 -215
- seleniumbase/core/sb_driver.py +46 -5
- seleniumbase/core/session_helper.py +2 -4
- seleniumbase/core/tour_helper.py +1 -2
- seleniumbase/drivers/atlas_drivers/__init__.py +0 -0
- seleniumbase/drivers/brave_drivers/__init__.py +0 -0
- seleniumbase/drivers/chromium_drivers/__init__.py +0 -0
- seleniumbase/drivers/comet_drivers/__init__.py +0 -0
- seleniumbase/drivers/opera_drivers/__init__.py +0 -0
- seleniumbase/fixtures/base_case.py +448 -251
- seleniumbase/fixtures/constants.py +36 -9
- seleniumbase/fixtures/js_utils.py +77 -18
- seleniumbase/fixtures/page_actions.py +41 -13
- seleniumbase/fixtures/page_utils.py +19 -12
- seleniumbase/fixtures/shared_utils.py +64 -6
- seleniumbase/masterqa/master_qa.py +16 -2
- seleniumbase/plugins/base_plugin.py +8 -0
- seleniumbase/plugins/basic_test_info.py +2 -3
- seleniumbase/plugins/driver_manager.py +131 -5
- seleniumbase/plugins/page_source.py +2 -3
- seleniumbase/plugins/pytest_plugin.py +244 -79
- seleniumbase/plugins/sb_manager.py +143 -20
- seleniumbase/plugins/selenium_plugin.py +144 -12
- seleniumbase/translate/translator.py +2 -3
- seleniumbase/undetected/__init__.py +17 -13
- seleniumbase/undetected/cdp.py +1 -12
- seleniumbase/undetected/cdp_driver/browser.py +330 -129
- seleniumbase/undetected/cdp_driver/cdp_util.py +328 -61
- seleniumbase/undetected/cdp_driver/config.py +110 -14
- seleniumbase/undetected/cdp_driver/connection.py +18 -48
- seleniumbase/undetected/cdp_driver/element.py +105 -33
- seleniumbase/undetected/cdp_driver/tab.py +414 -39
- seleniumbase/utilities/selenium_grid/download_selenium_server.py +1 -1
- seleniumbase/utilities/selenium_grid/grid_hub.py +1 -2
- seleniumbase/utilities/selenium_grid/grid_node.py +2 -3
- seleniumbase/utilities/selenium_ide/convert_ide.py +2 -3
- {seleniumbase-4.41.3.dist-info → seleniumbase-4.45.10.dist-info}/METADATA +193 -166
- {seleniumbase-4.41.3.dist-info → seleniumbase-4.45.10.dist-info}/RECORD +64 -59
- {seleniumbase-4.41.3.dist-info → seleniumbase-4.45.10.dist-info}/licenses/LICENSE +1 -1
- {seleniumbase-4.41.3.dist-info → seleniumbase-4.45.10.dist-info}/WHEEL +0 -0
- {seleniumbase-4.41.3.dist-info → seleniumbase-4.45.10.dist-info}/entry_points.txt +0 -0
- {seleniumbase-4.41.3.dist-info → seleniumbase-4.45.10.dist-info}/top_level.txt +0 -0
seleniumbase/core/sb_cdp.py
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
"""Add CDP methods to extend the driver"""
|
|
2
2
|
import asyncio
|
|
3
3
|
import fasteners
|
|
4
|
+
import mycdp
|
|
4
5
|
import os
|
|
6
|
+
import random
|
|
5
7
|
import re
|
|
6
8
|
import sys
|
|
7
9
|
import time
|
|
8
10
|
from contextlib import suppress
|
|
11
|
+
from filelock import FileLock
|
|
9
12
|
from seleniumbase import config as sb_config
|
|
10
13
|
from seleniumbase.config import settings
|
|
11
14
|
from seleniumbase.fixtures import constants
|
|
@@ -13,6 +16,7 @@ from seleniumbase.fixtures import js_utils
|
|
|
13
16
|
from seleniumbase.fixtures import page_utils
|
|
14
17
|
from seleniumbase.fixtures import shared_utils
|
|
15
18
|
from seleniumbase.undetected.cdp_driver import cdp_util
|
|
19
|
+
from seleniumbase.undetected.cdp_driver import tab as cdp_tab
|
|
16
20
|
|
|
17
21
|
|
|
18
22
|
class CDPMethods():
|
|
@@ -63,6 +67,11 @@ class CDPMethods():
|
|
|
63
67
|
)
|
|
64
68
|
element.highlight_overlay = lambda: self.__highlight_overlay(element)
|
|
65
69
|
element.mouse_click = lambda: self.__mouse_click(element)
|
|
70
|
+
element.click_with_offset = (
|
|
71
|
+
lambda *args, **kwargs: self.__mouse_click_with_offset_async(
|
|
72
|
+
element, *args, **kwargs
|
|
73
|
+
)
|
|
74
|
+
)
|
|
66
75
|
element.mouse_drag = (
|
|
67
76
|
lambda destination: self.__mouse_drag(element, destination)
|
|
68
77
|
)
|
|
@@ -106,7 +115,18 @@ class CDPMethods():
|
|
|
106
115
|
driver = self.driver
|
|
107
116
|
if hasattr(driver, "cdp_base"):
|
|
108
117
|
driver = driver.cdp_base
|
|
109
|
-
|
|
118
|
+
load_timeout = 60.0
|
|
119
|
+
wait_timeout = 30.0
|
|
120
|
+
if hasattr(sb_config, "_cdp_proxy") and sb_config._cdp_proxy:
|
|
121
|
+
load_timeout = 90.0
|
|
122
|
+
wait_timeout = 45.0
|
|
123
|
+
try:
|
|
124
|
+
task = self.page.get(url, **kwargs)
|
|
125
|
+
self.loop.run_until_complete(
|
|
126
|
+
asyncio.wait_for(task, timeout=load_timeout)
|
|
127
|
+
)
|
|
128
|
+
except asyncio.TimeoutError:
|
|
129
|
+
print("Timeout loading %s" % url)
|
|
110
130
|
url_protocol = url.split(":")[0]
|
|
111
131
|
safe_url = True
|
|
112
132
|
if url_protocol not in ["about", "data", "chrome"]:
|
|
@@ -118,7 +138,14 @@ class CDPMethods():
|
|
|
118
138
|
else:
|
|
119
139
|
time.sleep(0.012)
|
|
120
140
|
self.__slow_mode_pause_if_set()
|
|
121
|
-
|
|
141
|
+
try:
|
|
142
|
+
self.loop.run_until_complete(
|
|
143
|
+
asyncio.wait_for(self.page.wait(), timeout=wait_timeout)
|
|
144
|
+
)
|
|
145
|
+
except asyncio.TimeoutError:
|
|
146
|
+
pass
|
|
147
|
+
except Exception:
|
|
148
|
+
pass
|
|
122
149
|
|
|
123
150
|
def open(self, url, **kwargs):
|
|
124
151
|
self.get(url, **kwargs)
|
|
@@ -137,6 +164,48 @@ class CDPMethods():
|
|
|
137
164
|
def get_event_loop(self):
|
|
138
165
|
return self.loop
|
|
139
166
|
|
|
167
|
+
def get_rd_host(self):
|
|
168
|
+
"""Returns the remote-debugging host (likely 127.0.0.1)"""
|
|
169
|
+
driver = self.driver
|
|
170
|
+
if hasattr(driver, "cdp_base"):
|
|
171
|
+
driver = driver.cdp_base
|
|
172
|
+
return driver.config.host
|
|
173
|
+
|
|
174
|
+
def get_rd_port(self):
|
|
175
|
+
"""Returns the remote-debugging port (commonly 9222)"""
|
|
176
|
+
driver = self.driver
|
|
177
|
+
if hasattr(driver, "cdp_base"):
|
|
178
|
+
driver = driver.cdp_base
|
|
179
|
+
return driver.config.port
|
|
180
|
+
|
|
181
|
+
def get_rd_url(self):
|
|
182
|
+
"""Returns the remote-debugging URL, which is used for
|
|
183
|
+
allowing the Playwright integration to launch stealthy,
|
|
184
|
+
and also applies nest-asyncio for nested event loops so
|
|
185
|
+
that SeleniumBase methods can be called from Playwright
|
|
186
|
+
without encountering event loop error messages such as:
|
|
187
|
+
Cannot run the event loop while another loop is running.
|
|
188
|
+
Also sets an environment variable to hide this warning:
|
|
189
|
+
Deprecation: "url.parse() behavior is not standardized".
|
|
190
|
+
(github.com/microsoft/playwright-python/issues/3016)"""
|
|
191
|
+
import nest_asyncio
|
|
192
|
+
nest_asyncio.apply()
|
|
193
|
+
os.environ["NODE_NO_WARNINGS"] = "1"
|
|
194
|
+
driver = self.driver
|
|
195
|
+
if hasattr(driver, "cdp_base"):
|
|
196
|
+
driver = driver.cdp_base
|
|
197
|
+
host = driver.config.host
|
|
198
|
+
port = driver.config.port
|
|
199
|
+
return f"http://{host}:{port}"
|
|
200
|
+
|
|
201
|
+
def get_endpoint_url(self):
|
|
202
|
+
"""Same as get_rd_url(), which returns the remote-debugging URL."""
|
|
203
|
+
return self.get_rd_url()
|
|
204
|
+
|
|
205
|
+
def get_port(self):
|
|
206
|
+
"""Same as get_rd_port(), which returns the remote-debugging port."""
|
|
207
|
+
return self.get_rd_port()
|
|
208
|
+
|
|
140
209
|
def add_handler(self, event, handler):
|
|
141
210
|
self.page.add_handler(event, handler)
|
|
142
211
|
|
|
@@ -186,46 +255,23 @@ class CDPMethods():
|
|
|
186
255
|
element with the given tag. (Eg: a, button, div, script, span)"""
|
|
187
256
|
if not timeout:
|
|
188
257
|
timeout = settings.SMALL_TIMEOUT
|
|
189
|
-
self.__add_light_pause()
|
|
190
|
-
time_now = time.time()
|
|
191
|
-
self.assert_text(text, timeout=timeout)
|
|
192
|
-
spent = int(time.time() - time_now)
|
|
193
|
-
remaining = 1 + timeout - spent
|
|
194
|
-
if tag_name:
|
|
195
|
-
self.assert_element(tag_name, timeout=remaining)
|
|
196
|
-
elements = self.loop.run_until_complete(
|
|
197
|
-
self.page.find_elements_by_text(text=text)
|
|
198
|
-
)
|
|
199
258
|
if tag_name:
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
element
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
and text.strip() in element.parent.text
|
|
217
|
-
):
|
|
218
|
-
element = self.__add_sync_methods(element.parent)
|
|
219
|
-
return self.__add_sync_methods(element)
|
|
220
|
-
elif (
|
|
221
|
-
element
|
|
222
|
-
and element.parent
|
|
223
|
-
and element.parent.parent
|
|
224
|
-
and tag_name in element.parent.parent.tag_name.lower()
|
|
225
|
-
and text.strip() in element.parent.parent.text
|
|
226
|
-
):
|
|
227
|
-
element = self.__add_sync_methods(element.parent.parent)
|
|
228
|
-
return self.__add_sync_methods(element)
|
|
259
|
+
try:
|
|
260
|
+
return self.find_element(
|
|
261
|
+
'%s:contains("%s")' % (tag_name, text), timeout=timeout
|
|
262
|
+
)
|
|
263
|
+
except Exception:
|
|
264
|
+
pass # The exception will be raised later
|
|
265
|
+
else:
|
|
266
|
+
self.__add_light_pause()
|
|
267
|
+
self.assert_text(text, timeout=timeout)
|
|
268
|
+
elements = self.loop.run_until_complete(
|
|
269
|
+
self.page.find_elements_by_text(text=text)
|
|
270
|
+
)
|
|
271
|
+
for element in elements:
|
|
272
|
+
if element:
|
|
273
|
+
element = self.__add_sync_methods(element)
|
|
274
|
+
return self.__add_sync_methods(element)
|
|
229
275
|
plural = "s"
|
|
230
276
|
if timeout == 1:
|
|
231
277
|
plural = ""
|
|
@@ -300,28 +346,7 @@ class CDPMethods():
|
|
|
300
346
|
self.__add_light_pause()
|
|
301
347
|
selector = self.__convert_to_css_if_xpath(selector)
|
|
302
348
|
if (":contains(" in selector):
|
|
303
|
-
|
|
304
|
-
text = selector.split(":contains(")[1].split(")")[0][1:-1]
|
|
305
|
-
with suppress(Exception):
|
|
306
|
-
new_timeout = timeout
|
|
307
|
-
if new_timeout < 1:
|
|
308
|
-
new_timeout = 1
|
|
309
|
-
self.loop.run_until_complete(
|
|
310
|
-
self.page.select(tag_name, timeout=new_timeout)
|
|
311
|
-
)
|
|
312
|
-
self.loop.run_until_complete(
|
|
313
|
-
self.page.find(text, timeout=new_timeout)
|
|
314
|
-
)
|
|
315
|
-
elements = self.find_elements_by_text(text, tag_name=tag_name)
|
|
316
|
-
if not elements:
|
|
317
|
-
plural = "s"
|
|
318
|
-
if timeout == 1:
|
|
319
|
-
plural = ""
|
|
320
|
-
msg = "\n Element {%s} was not found after %s second%s!"
|
|
321
|
-
message = msg % (selector, timeout, plural)
|
|
322
|
-
raise Exception(message)
|
|
323
|
-
element = self.__add_sync_methods(elements[0])
|
|
324
|
-
return element
|
|
349
|
+
return self.find_element(selector, timeout=timeout)
|
|
325
350
|
failure = False
|
|
326
351
|
try:
|
|
327
352
|
element = self.loop.run_until_complete(
|
|
@@ -490,6 +515,15 @@ class CDPMethods():
|
|
|
490
515
|
self.loop.run_until_complete(self.page.wait())
|
|
491
516
|
return result
|
|
492
517
|
|
|
518
|
+
def __mouse_click_with_offset_async(self, element, *args, **kwargs):
|
|
519
|
+
result = (
|
|
520
|
+
self.loop.run_until_complete(
|
|
521
|
+
element.mouse_click_with_offset_async(*args, **kwargs)
|
|
522
|
+
)
|
|
523
|
+
)
|
|
524
|
+
self.loop.run_until_complete(self.page.wait())
|
|
525
|
+
return result
|
|
526
|
+
|
|
493
527
|
def __mouse_drag(self, element, destination):
|
|
494
528
|
return (
|
|
495
529
|
self.loop.run_until_complete(element.mouse_drag_async(destination))
|
|
@@ -508,7 +542,7 @@ class CDPMethods():
|
|
|
508
542
|
text = text[:-1]
|
|
509
543
|
for key in text:
|
|
510
544
|
element.send_keys(key)
|
|
511
|
-
time.sleep(0.
|
|
545
|
+
time.sleep(float(0.042 + (random.random() / 110.0)))
|
|
512
546
|
if submit:
|
|
513
547
|
element.send_keys("\r\n")
|
|
514
548
|
time.sleep(0.044)
|
|
@@ -732,7 +766,21 @@ class CDPMethods():
|
|
|
732
766
|
self.__slow_mode_pause_if_set()
|
|
733
767
|
element = self.find_element(selector, timeout=timeout)
|
|
734
768
|
element.scroll_into_view()
|
|
735
|
-
element.
|
|
769
|
+
tag_name = element.tag_name
|
|
770
|
+
if tag_name:
|
|
771
|
+
tag_name = tag_name.lower().strip()
|
|
772
|
+
if (
|
|
773
|
+
tag_name in [
|
|
774
|
+
"a", "button", "canvas", "div", "input", "li", "span", "label"
|
|
775
|
+
]
|
|
776
|
+
and "contains(" not in selector
|
|
777
|
+
):
|
|
778
|
+
try:
|
|
779
|
+
element.mouse_click() # Simulated click (NOT PyAutoGUI)
|
|
780
|
+
except Exception:
|
|
781
|
+
element.click() # Standard CDP click
|
|
782
|
+
else:
|
|
783
|
+
element.click() # Standard CDP click
|
|
736
784
|
self.__slow_mode_pause_if_set()
|
|
737
785
|
self.loop.run_until_complete(self.page.wait())
|
|
738
786
|
|
|
@@ -778,7 +826,7 @@ class CDPMethods():
|
|
|
778
826
|
element.scroll_into_view()
|
|
779
827
|
element.click()
|
|
780
828
|
click_count += 1
|
|
781
|
-
time.sleep(0.
|
|
829
|
+
time.sleep(0.044)
|
|
782
830
|
self.__slow_mode_pause_if_set()
|
|
783
831
|
self.loop.run_until_complete(self.page.wait())
|
|
784
832
|
except Exception:
|
|
@@ -823,6 +871,37 @@ class CDPMethods():
|
|
|
823
871
|
% (dropdown_selector, option)
|
|
824
872
|
)
|
|
825
873
|
|
|
874
|
+
def select_option_by_index(self, dropdown_selector, option):
|
|
875
|
+
element = self.find_element(dropdown_selector)
|
|
876
|
+
element.scroll_into_view()
|
|
877
|
+
options = element.query_selector_all("option")
|
|
878
|
+
count = 0
|
|
879
|
+
for found_option in options:
|
|
880
|
+
if count == int(option):
|
|
881
|
+
found_option.select_option()
|
|
882
|
+
return
|
|
883
|
+
count += 1
|
|
884
|
+
raise Exception(
|
|
885
|
+
"Unable to find index option {%s} in dropdown {%s}!"
|
|
886
|
+
% (dropdown_selector, option)
|
|
887
|
+
)
|
|
888
|
+
|
|
889
|
+
def select_option_by_value(self, dropdown_selector, option):
|
|
890
|
+
element = self.find_element(dropdown_selector)
|
|
891
|
+
element.scroll_into_view()
|
|
892
|
+
options = element.query_selector_all("option")
|
|
893
|
+
for found_option in options:
|
|
894
|
+
if (
|
|
895
|
+
"value" in found_option.attrs
|
|
896
|
+
and str(found_option.attrs["value"]) == str(option)
|
|
897
|
+
):
|
|
898
|
+
found_option.select_option()
|
|
899
|
+
return
|
|
900
|
+
raise Exception(
|
|
901
|
+
"Unable to find value option {%s} in dropdown {%s}!"
|
|
902
|
+
% (dropdown_selector, option)
|
|
903
|
+
)
|
|
904
|
+
|
|
826
905
|
def flash(
|
|
827
906
|
self,
|
|
828
907
|
selector, # The CSS Selector to flash
|
|
@@ -898,6 +977,12 @@ class CDPMethods():
|
|
|
898
977
|
element.scroll_into_view()
|
|
899
978
|
if text.endswith("\n") or text.endswith("\r"):
|
|
900
979
|
text = text[:-1] + "\r\n"
|
|
980
|
+
elif (
|
|
981
|
+
element.tag_name == "textarea"
|
|
982
|
+
and "\n" in text
|
|
983
|
+
and "\r" not in text
|
|
984
|
+
):
|
|
985
|
+
text = text.replace("\n", "\r")
|
|
901
986
|
element.send_keys(text)
|
|
902
987
|
self.__slow_mode_pause_if_set()
|
|
903
988
|
self.loop.run_until_complete(self.page.sleep(0.025))
|
|
@@ -913,9 +998,15 @@ class CDPMethods():
|
|
|
913
998
|
if text.endswith("\n") or text.endswith("\r"):
|
|
914
999
|
submit = True
|
|
915
1000
|
text = text[:-1]
|
|
1001
|
+
elif (
|
|
1002
|
+
element.tag_name == "textarea"
|
|
1003
|
+
and "\n" in text
|
|
1004
|
+
and "\r" not in text
|
|
1005
|
+
):
|
|
1006
|
+
text = text.replace("\n", "\r")
|
|
916
1007
|
for key in text:
|
|
917
1008
|
element.send_keys(key)
|
|
918
|
-
time.sleep(0.
|
|
1009
|
+
time.sleep(float(0.042 + (random.random() / 110.0)))
|
|
919
1010
|
if submit:
|
|
920
1011
|
element.send_keys("\r\n")
|
|
921
1012
|
time.sleep(0.044)
|
|
@@ -933,6 +1024,12 @@ class CDPMethods():
|
|
|
933
1024
|
element.clear_input()
|
|
934
1025
|
if text.endswith("\n") or text.endswith("\r"):
|
|
935
1026
|
text = text[:-1] + "\r\n"
|
|
1027
|
+
elif (
|
|
1028
|
+
element.tag_name == "textarea"
|
|
1029
|
+
and "\n" in text
|
|
1030
|
+
and "\r" not in text
|
|
1031
|
+
):
|
|
1032
|
+
text = text.replace("\n", "\r")
|
|
936
1033
|
element.send_keys(text)
|
|
937
1034
|
self.__slow_mode_pause_if_set()
|
|
938
1035
|
self.loop.run_until_complete(self.page.sleep(0.025))
|
|
@@ -1010,6 +1107,9 @@ class CDPMethods():
|
|
|
1010
1107
|
).strip()
|
|
1011
1108
|
return self.loop.run_until_complete(self.page.evaluate(expression))
|
|
1012
1109
|
|
|
1110
|
+
def execute_script(self, expression):
|
|
1111
|
+
return self.evaluate(expression)
|
|
1112
|
+
|
|
1013
1113
|
def js_dumps(self, obj_name):
|
|
1014
1114
|
"""Similar to evaluate(), but for dictionary results."""
|
|
1015
1115
|
if obj_name.startswith("return "):
|
|
@@ -1017,12 +1117,19 @@ class CDPMethods():
|
|
|
1017
1117
|
return self.loop.run_until_complete(self.page.js_dumps(obj_name))
|
|
1018
1118
|
|
|
1019
1119
|
def maximize(self):
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
self.
|
|
1024
|
-
|
|
1025
|
-
|
|
1120
|
+
try:
|
|
1121
|
+
if self.get_window()[1].window_state.value == "maximized":
|
|
1122
|
+
return
|
|
1123
|
+
elif self.get_window()[1].window_state.value == "minimized":
|
|
1124
|
+
self.loop.run_until_complete(self.page.maximize())
|
|
1125
|
+
time.sleep(0.044)
|
|
1126
|
+
return self.loop.run_until_complete(self.page.maximize())
|
|
1127
|
+
except Exception:
|
|
1128
|
+
with suppress(Exception):
|
|
1129
|
+
width = self.evaluate("screen.availWidth;")
|
|
1130
|
+
height = self.evaluate("screen.availHeight;")
|
|
1131
|
+
self.__set_window_rect(0, 0, width, height)
|
|
1132
|
+
return
|
|
1026
1133
|
|
|
1027
1134
|
def minimize(self):
|
|
1028
1135
|
if self.get_window()[1].window_state.value != "minimized":
|
|
@@ -1034,24 +1141,42 @@ class CDPMethods():
|
|
|
1034
1141
|
time.sleep(0.044)
|
|
1035
1142
|
return self.loop.run_until_complete(self.page.medimize())
|
|
1036
1143
|
|
|
1037
|
-
def
|
|
1038
|
-
if
|
|
1039
|
-
|
|
1144
|
+
def __set_window_rect(self, x, y, width, height, uc_lock=False):
|
|
1145
|
+
if uc_lock:
|
|
1146
|
+
gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK)
|
|
1147
|
+
with gui_lock:
|
|
1148
|
+
self.__make_sure_pyautogui_lock_is_writable()
|
|
1149
|
+
if self.get_window()[1].window_state.value == "minimized":
|
|
1150
|
+
self.loop.run_until_complete(
|
|
1151
|
+
self.page.set_window_size(
|
|
1152
|
+
left=x, top=y, width=width, height=height)
|
|
1153
|
+
)
|
|
1154
|
+
time.sleep(0.044)
|
|
1155
|
+
return self.loop.run_until_complete(
|
|
1156
|
+
self.page.set_window_size(
|
|
1157
|
+
left=x, top=y, width=width, height=height)
|
|
1158
|
+
)
|
|
1159
|
+
else:
|
|
1160
|
+
if self.get_window()[1].window_state.value == "minimized":
|
|
1161
|
+
self.loop.run_until_complete(
|
|
1162
|
+
self.page.set_window_size(
|
|
1163
|
+
left=x, top=y, width=width, height=height)
|
|
1164
|
+
)
|
|
1165
|
+
time.sleep(0.044)
|
|
1166
|
+
return self.loop.run_until_complete(
|
|
1040
1167
|
self.page.set_window_size(
|
|
1041
1168
|
left=x, top=y, width=width, height=height)
|
|
1042
1169
|
)
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
left=x, top=y, width=width, height=height)
|
|
1047
|
-
)
|
|
1170
|
+
|
|
1171
|
+
def set_window_rect(self, x, y, width, height):
|
|
1172
|
+
return self.__set_window_rect(x, y, width, height, uc_lock=True)
|
|
1048
1173
|
|
|
1049
1174
|
def reset_window_size(self):
|
|
1050
1175
|
x = settings.WINDOW_START_X
|
|
1051
1176
|
y = settings.WINDOW_START_Y
|
|
1052
1177
|
width = settings.CHROME_START_WIDTH
|
|
1053
1178
|
height = settings.CHROME_START_HEIGHT
|
|
1054
|
-
self.
|
|
1179
|
+
self.__set_window_rect(x, y, width, height, uc_lock=True)
|
|
1055
1180
|
self.__add_light_pause()
|
|
1056
1181
|
|
|
1057
1182
|
def open_new_window(self, url=None, switch_to=True):
|
|
@@ -1063,10 +1188,74 @@ class CDPMethods():
|
|
|
1063
1188
|
def switch_to_newest_window(self):
|
|
1064
1189
|
self.switch_to_tab(-1)
|
|
1065
1190
|
|
|
1066
|
-
def open_new_tab(self, url=None, switch_to=True):
|
|
1191
|
+
def open_new_tab(self, url=None, switch_to=True, **kwargs):
|
|
1192
|
+
driver = self.driver
|
|
1067
1193
|
if not isinstance(url, str):
|
|
1068
1194
|
url = "about:blank"
|
|
1069
|
-
|
|
1195
|
+
if hasattr(driver, "cdp_base"):
|
|
1196
|
+
try:
|
|
1197
|
+
self.loop.run_until_complete(
|
|
1198
|
+
self.page.get(url, new_tab=True, **kwargs)
|
|
1199
|
+
)
|
|
1200
|
+
except Exception:
|
|
1201
|
+
original_targets = self.loop.run_until_complete(
|
|
1202
|
+
self.page.send(mycdp.target.get_targets())
|
|
1203
|
+
)
|
|
1204
|
+
tab_url = driver.cdp_base.tabs[0].websocket_url
|
|
1205
|
+
if not self.driver.is_connected():
|
|
1206
|
+
self.driver.connect()
|
|
1207
|
+
self.driver.open_new_tab()
|
|
1208
|
+
targets = self.loop.run_until_complete(
|
|
1209
|
+
self.page.send(mycdp.target.get_targets())
|
|
1210
|
+
)
|
|
1211
|
+
new_targets = []
|
|
1212
|
+
for target in targets:
|
|
1213
|
+
if target not in original_targets:
|
|
1214
|
+
new_targets.append(target)
|
|
1215
|
+
if new_targets:
|
|
1216
|
+
found_target = new_targets[0]
|
|
1217
|
+
t_str = str(new_targets[0])
|
|
1218
|
+
target_id = (
|
|
1219
|
+
t_str.split("target_id=TargetID('")[-1].split("')")[0]
|
|
1220
|
+
)
|
|
1221
|
+
pre_tab_url = tab_url.split("/page/")[0] + "/page/"
|
|
1222
|
+
new_tab_url = pre_tab_url + target_id
|
|
1223
|
+
new_tab = cdp_tab.Tab(
|
|
1224
|
+
new_tab_url, found_target, driver.cdp_base
|
|
1225
|
+
)
|
|
1226
|
+
driver.cdp_base.targets.append(new_tab)
|
|
1227
|
+
driver.cdp_base.tabs.append(new_tab)
|
|
1228
|
+
self.driver.disconnect()
|
|
1229
|
+
self.switch_to_newest_tab()
|
|
1230
|
+
self.open(url)
|
|
1231
|
+
return
|
|
1232
|
+
elif getattr(sb_config, "guest_mode", None):
|
|
1233
|
+
print(" open_new_tab() failed! (Known Guest Mode issue)")
|
|
1234
|
+
if switch_to:
|
|
1235
|
+
self.switch_to_newest_tab()
|
|
1236
|
+
return
|
|
1237
|
+
|
|
1238
|
+
target_id = self.loop.run_until_complete(
|
|
1239
|
+
self.page.send(mycdp.target.create_target(url))
|
|
1240
|
+
)
|
|
1241
|
+
if not target_id and getattr(sb_config, "guest_mode", None):
|
|
1242
|
+
print(" open_new_tab() failed! (Known Guest Mode issue)")
|
|
1243
|
+
found_target = None
|
|
1244
|
+
targets = self.loop.run_until_complete(
|
|
1245
|
+
self.page.send(mycdp.target.get_targets())
|
|
1246
|
+
)
|
|
1247
|
+
if target_id:
|
|
1248
|
+
for target in targets:
|
|
1249
|
+
if str(target_id) in str(target):
|
|
1250
|
+
found_target = target
|
|
1251
|
+
break
|
|
1252
|
+
if found_target:
|
|
1253
|
+
tab_url = driver.tabs[0].websocket_url
|
|
1254
|
+
pre_tab_url = tab_url.split("/page/")[0] + "/page/"
|
|
1255
|
+
new_tab_url = pre_tab_url + target_id
|
|
1256
|
+
new_tab = cdp_tab.Tab(new_tab_url, found_target, driver)
|
|
1257
|
+
driver.targets.append(new_tab)
|
|
1258
|
+
driver.tabs.append(new_tab)
|
|
1070
1259
|
if switch_to:
|
|
1071
1260
|
self.switch_to_newest_tab()
|
|
1072
1261
|
|
|
@@ -1128,7 +1317,14 @@ class CDPMethods():
|
|
|
1128
1317
|
self.page.evaluate("window.location.origin")
|
|
1129
1318
|
)
|
|
1130
1319
|
|
|
1131
|
-
def
|
|
1320
|
+
def get_html(self, include_shadow_dom=True):
|
|
1321
|
+
return self.get_page_source(
|
|
1322
|
+
include_shadow_dom=include_shadow_dom
|
|
1323
|
+
)
|
|
1324
|
+
|
|
1325
|
+
def get_page_source(self, include_shadow_dom=True):
|
|
1326
|
+
if include_shadow_dom:
|
|
1327
|
+
return self.find_element("html").get_html()
|
|
1132
1328
|
try:
|
|
1133
1329
|
source = self.loop.run_until_complete(
|
|
1134
1330
|
self.page.evaluate("document.documentElement.outerHTML")
|
|
@@ -1303,6 +1499,17 @@ class CDPMethods():
|
|
|
1303
1499
|
x = window_rect["x"] + element_rect["x"]
|
|
1304
1500
|
y = w_bottom_y - viewport_height + element_rect["y"]
|
|
1305
1501
|
y_scroll_offset = window_rect["pageYOffset"]
|
|
1502
|
+
if (
|
|
1503
|
+
hasattr(sb_config, "_cdp_browser")
|
|
1504
|
+
and sb_config._cdp_browser == "opera"
|
|
1505
|
+
):
|
|
1506
|
+
# Handle special case where Opera side panel shifts coordinates
|
|
1507
|
+
x_offset = window_rect["outerWidth"] - window_rect["innerWidth"]
|
|
1508
|
+
if x_offset > 56:
|
|
1509
|
+
x_offset = 56
|
|
1510
|
+
elif x_offset < 22:
|
|
1511
|
+
x_offset = 0
|
|
1512
|
+
x = x + x_offset
|
|
1306
1513
|
y = y - y_scroll_offset
|
|
1307
1514
|
x = x + window_rect["scrollX"]
|
|
1308
1515
|
y = y + window_rect["scrollY"]
|
|
@@ -1364,6 +1571,75 @@ class CDPMethods():
|
|
|
1364
1571
|
)
|
|
1365
1572
|
)
|
|
1366
1573
|
|
|
1574
|
+
def get_mfa_code(self, totp_key=None):
|
|
1575
|
+
"""Returns a time-based one-time password based on the Google
|
|
1576
|
+
Authenticator algorithm for multi-factor authentication."""
|
|
1577
|
+
return shared_utils.get_mfa_code(totp_key)
|
|
1578
|
+
|
|
1579
|
+
def enter_mfa_code(self, selector, totp_key=None, timeout=None):
|
|
1580
|
+
if not timeout:
|
|
1581
|
+
timeout = settings.SMALL_TIMEOUT
|
|
1582
|
+
mfa_code = self.get_mfa_code(totp_key)
|
|
1583
|
+
self.type(selector, mfa_code + "\n", timeout=timeout)
|
|
1584
|
+
|
|
1585
|
+
def activate_messenger(self):
|
|
1586
|
+
js_utils.activate_messenger(self)
|
|
1587
|
+
self.__add_light_pause()
|
|
1588
|
+
|
|
1589
|
+
def set_messenger_theme(
|
|
1590
|
+
self, theme="default", location="default", max_messages="default"
|
|
1591
|
+
):
|
|
1592
|
+
"""Sets a theme for posting messages.
|
|
1593
|
+
Themes: ["flat", "future", "block", "air", "ice"]
|
|
1594
|
+
Locations: ["top_left", "top_center", "top_right",
|
|
1595
|
+
"bottom_left", "bottom_center", "bottom_right"]
|
|
1596
|
+
max_messages: The limit of concurrent messages to display."""
|
|
1597
|
+
if not theme:
|
|
1598
|
+
theme = "default" # "flat"
|
|
1599
|
+
if not location:
|
|
1600
|
+
location = "default" # "bottom_right"
|
|
1601
|
+
if not max_messages:
|
|
1602
|
+
max_messages = "default" # "8"
|
|
1603
|
+
else:
|
|
1604
|
+
max_messages = str(max_messages) # Value must be in string format
|
|
1605
|
+
js_utils.set_messenger_theme(
|
|
1606
|
+
self,
|
|
1607
|
+
theme=theme,
|
|
1608
|
+
location=location,
|
|
1609
|
+
max_messages=max_messages,
|
|
1610
|
+
)
|
|
1611
|
+
self.__add_light_pause()
|
|
1612
|
+
|
|
1613
|
+
def post_message(self, message, duration=None, pause=True, style="info"):
|
|
1614
|
+
"""Post a message on the screen with Messenger.
|
|
1615
|
+
Arguments:
|
|
1616
|
+
message: The message to display.
|
|
1617
|
+
duration: The time until the message vanishes. (Default: 2.55s)
|
|
1618
|
+
pause: If True, the program waits until the message completes.
|
|
1619
|
+
style: "info", "success", or "error"."""
|
|
1620
|
+
driver = self.driver
|
|
1621
|
+
if hasattr(driver, "cdp_base"):
|
|
1622
|
+
driver = driver.cdp_base
|
|
1623
|
+
if style not in ["info", "success", "error"]:
|
|
1624
|
+
style = "info"
|
|
1625
|
+
if not duration:
|
|
1626
|
+
duration = settings.DEFAULT_MESSAGE_DURATION
|
|
1627
|
+
if (
|
|
1628
|
+
(
|
|
1629
|
+
driver.config.headless
|
|
1630
|
+
or (hasattr(sb_config, "xvfb") and sb_config.xvfb)
|
|
1631
|
+
)
|
|
1632
|
+
and float(duration) > 0.75
|
|
1633
|
+
):
|
|
1634
|
+
duration = 0.75
|
|
1635
|
+
try:
|
|
1636
|
+
js_utils.post_message(self, message, duration, style=style)
|
|
1637
|
+
except Exception:
|
|
1638
|
+
print(" * %s message: %s" % (style.upper(), message))
|
|
1639
|
+
if pause:
|
|
1640
|
+
duration = float(duration) + 0.15
|
|
1641
|
+
time.sleep(float(duration))
|
|
1642
|
+
|
|
1367
1643
|
def set_locale(self, locale):
|
|
1368
1644
|
"""(Settings will take effect on the next page load)"""
|
|
1369
1645
|
self.loop.run_until_complete(self.page.set_locale(locale))
|
|
@@ -1390,17 +1666,39 @@ class CDPMethods():
|
|
|
1390
1666
|
css_selector = self.__convert_to_css_if_xpath(selector)
|
|
1391
1667
|
css_selector = re.escape(css_selector) # Add "\\" to special chars
|
|
1392
1668
|
css_selector = js_utils.escape_quotes_if_needed(css_selector)
|
|
1393
|
-
js_code =
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1669
|
+
js_code = (
|
|
1670
|
+
"""var $elements = document.querySelectorAll('%s');
|
|
1671
|
+
var index = 0, length = $elements.length;
|
|
1672
|
+
for(; index < length; index++){
|
|
1673
|
+
$elements[index].setAttribute('%s','%s');}""" % (
|
|
1674
|
+
css_selector,
|
|
1675
|
+
attribute,
|
|
1676
|
+
value,
|
|
1677
|
+
)
|
|
1400
1678
|
)
|
|
1401
1679
|
with suppress(Exception):
|
|
1402
1680
|
self.loop.run_until_complete(self.page.evaluate(js_code))
|
|
1403
1681
|
|
|
1682
|
+
def is_attribute_present(self, selector, attribute, value=None):
|
|
1683
|
+
try:
|
|
1684
|
+
element = self.find_element(selector, timeout=0.1)
|
|
1685
|
+
found_value = element.get_attribute(attribute)
|
|
1686
|
+
if found_value is None:
|
|
1687
|
+
return False
|
|
1688
|
+
if value is not None:
|
|
1689
|
+
if found_value == value:
|
|
1690
|
+
return True
|
|
1691
|
+
else:
|
|
1692
|
+
return False
|
|
1693
|
+
else:
|
|
1694
|
+
return True
|
|
1695
|
+
except Exception:
|
|
1696
|
+
return False
|
|
1697
|
+
|
|
1698
|
+
def is_online(self):
|
|
1699
|
+
js_code = "navigator.onLine;"
|
|
1700
|
+
return self.loop.run_until_complete(self.page.evaluate(js_code))
|
|
1701
|
+
|
|
1404
1702
|
def __make_sure_pyautogui_lock_is_writable(self):
|
|
1405
1703
|
with suppress(Exception):
|
|
1406
1704
|
shared_utils.make_writable(constants.MultiBrowser.PYAUTOGUILOCK)
|
|
@@ -1431,23 +1729,24 @@ class CDPMethods():
|
|
|
1431
1729
|
import pyautogui
|
|
1432
1730
|
with suppress(Exception):
|
|
1433
1731
|
use_pyautogui_ver = constants.PyAutoGUI.VER
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
)
|
|
1732
|
+
u_pv = shared_utils.make_version_tuple(use_pyautogui_ver)
|
|
1733
|
+
pv = shared_utils.make_version_tuple(pyautogui.__version__)
|
|
1734
|
+
if pv < u_pv:
|
|
1735
|
+
del pyautogui # To get newer ver
|
|
1736
|
+
shared_utils.pip_install("pyautogui", version="Latest")
|
|
1439
1737
|
import pyautogui
|
|
1440
1738
|
except Exception:
|
|
1441
1739
|
print("\nPyAutoGUI required! Installing now...")
|
|
1442
|
-
shared_utils.pip_install(
|
|
1443
|
-
"pyautogui", version=constants.PyAutoGUI.VER
|
|
1444
|
-
)
|
|
1740
|
+
shared_utils.pip_install("pyautogui", version="Latest")
|
|
1445
1741
|
try:
|
|
1446
1742
|
import pyautogui
|
|
1447
1743
|
except Exception:
|
|
1448
1744
|
if (
|
|
1449
1745
|
shared_utils.is_linux()
|
|
1450
|
-
and (
|
|
1746
|
+
and (
|
|
1747
|
+
not sb_config.headed
|
|
1748
|
+
or (hasattr(sb_config, "xvfb") and sb_config.xvfb)
|
|
1749
|
+
)
|
|
1451
1750
|
and not driver.config.headless
|
|
1452
1751
|
and (
|
|
1453
1752
|
not hasattr(sb_config, "_virtual_display")
|
|
@@ -1517,9 +1816,7 @@ class CDPMethods():
|
|
|
1517
1816
|
self.__install_pyautogui_if_missing()
|
|
1518
1817
|
import pyautogui
|
|
1519
1818
|
pyautogui = self.__get_configured_pyautogui(pyautogui)
|
|
1520
|
-
gui_lock =
|
|
1521
|
-
constants.MultiBrowser.PYAUTOGUILOCK
|
|
1522
|
-
)
|
|
1819
|
+
gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK)
|
|
1523
1820
|
with gui_lock:
|
|
1524
1821
|
self.__make_sure_pyautogui_lock_is_writable()
|
|
1525
1822
|
pyautogui.press(key)
|
|
@@ -1531,14 +1828,12 @@ class CDPMethods():
|
|
|
1531
1828
|
self.__install_pyautogui_if_missing()
|
|
1532
1829
|
import pyautogui
|
|
1533
1830
|
pyautogui = self.__get_configured_pyautogui(pyautogui)
|
|
1534
|
-
gui_lock =
|
|
1535
|
-
constants.MultiBrowser.PYAUTOGUILOCK
|
|
1536
|
-
)
|
|
1831
|
+
gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK)
|
|
1537
1832
|
with gui_lock:
|
|
1538
1833
|
self.__make_sure_pyautogui_lock_is_writable()
|
|
1539
1834
|
for key in keys:
|
|
1540
1835
|
pyautogui.press(key)
|
|
1541
|
-
time.sleep(0.
|
|
1836
|
+
time.sleep(float(0.042 + (random.random() / 110.0)))
|
|
1542
1837
|
self.__slow_mode_pause_if_set()
|
|
1543
1838
|
self.loop.run_until_complete(self.page.sleep(0.025))
|
|
1544
1839
|
|
|
@@ -1546,9 +1841,7 @@ class CDPMethods():
|
|
|
1546
1841
|
self.__install_pyautogui_if_missing()
|
|
1547
1842
|
import pyautogui
|
|
1548
1843
|
pyautogui = self.__get_configured_pyautogui(pyautogui)
|
|
1549
|
-
gui_lock =
|
|
1550
|
-
constants.MultiBrowser.PYAUTOGUILOCK
|
|
1551
|
-
)
|
|
1844
|
+
gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK)
|
|
1552
1845
|
with gui_lock:
|
|
1553
1846
|
self.__make_sure_pyautogui_lock_is_writable()
|
|
1554
1847
|
pyautogui.write(text)
|
|
@@ -1567,9 +1860,7 @@ class CDPMethods():
|
|
|
1567
1860
|
% (x, y, screen_width, screen_height)
|
|
1568
1861
|
)
|
|
1569
1862
|
if uc_lock:
|
|
1570
|
-
gui_lock =
|
|
1571
|
-
constants.MultiBrowser.PYAUTOGUILOCK
|
|
1572
|
-
)
|
|
1863
|
+
gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK)
|
|
1573
1864
|
with gui_lock: # Prevent issues with multiple processes
|
|
1574
1865
|
self.__make_sure_pyautogui_lock_is_writable()
|
|
1575
1866
|
pyautogui.moveTo(x, y, timeframe, pyautogui.easeOutQuad)
|
|
@@ -1588,9 +1879,7 @@ class CDPMethods():
|
|
|
1588
1879
|
pyautogui.click(x=x, y=y)
|
|
1589
1880
|
|
|
1590
1881
|
def gui_click_x_y(self, x, y, timeframe=0.25):
|
|
1591
|
-
gui_lock =
|
|
1592
|
-
constants.MultiBrowser.PYAUTOGUILOCK
|
|
1593
|
-
)
|
|
1882
|
+
gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK)
|
|
1594
1883
|
with gui_lock: # Prevent issues with multiple processes
|
|
1595
1884
|
self.__make_sure_pyautogui_lock_is_writable()
|
|
1596
1885
|
self.__install_pyautogui_if_missing()
|
|
@@ -1604,20 +1893,20 @@ class CDPMethods():
|
|
|
1604
1893
|
win_x = window_rect["x"]
|
|
1605
1894
|
win_y = window_rect["y"]
|
|
1606
1895
|
scr_width = pyautogui.size().width
|
|
1607
|
-
self.
|
|
1608
|
-
self.__add_light_pause()
|
|
1609
|
-
win_width = self.get_window_size()["width"]
|
|
1896
|
+
win_width = self.evaluate("screen.availWidth;")
|
|
1610
1897
|
width_ratio = round(float(scr_width) / float(win_width), 2)
|
|
1611
1898
|
width_ratio += 0.01
|
|
1612
1899
|
if width_ratio < 0.45 or width_ratio > 2.55:
|
|
1613
1900
|
width_ratio = 1.01
|
|
1614
1901
|
sb_config._saved_width_ratio = width_ratio
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1902
|
+
with suppress(Exception):
|
|
1903
|
+
self.get_window() # If this fails, skip the rest
|
|
1904
|
+
self.minimize()
|
|
1905
|
+
self.__add_light_pause()
|
|
1906
|
+
self.__set_window_rect(win_x, win_y, width, height)
|
|
1907
|
+
self.__add_light_pause()
|
|
1908
|
+
x = x * (width_ratio + 0.03)
|
|
1909
|
+
y = y * (width_ratio - 0.03)
|
|
1621
1910
|
self.bring_active_window_to_front()
|
|
1622
1911
|
self.__gui_click_x_y(x, y, timeframe=timeframe, uc_lock=False)
|
|
1623
1912
|
|
|
@@ -1629,78 +1918,181 @@ class CDPMethods():
|
|
|
1629
1918
|
self.__slow_mode_pause_if_set()
|
|
1630
1919
|
self.loop.run_until_complete(self.page.wait())
|
|
1631
1920
|
|
|
1632
|
-
def
|
|
1633
|
-
|
|
1921
|
+
def gui_click_with_offset(
|
|
1922
|
+
self, selector, x, y, timeframe=0.25, center=False
|
|
1923
|
+
):
|
|
1924
|
+
"""Click an element at an {X,Y}-offset location.
|
|
1925
|
+
{0,0} is the top-left corner of the element.
|
|
1926
|
+
If center==True, {0,0} becomes the center of the element.
|
|
1927
|
+
The timeframe is the time spent moving the mouse."""
|
|
1928
|
+
if center:
|
|
1929
|
+
px, py = self.get_gui_element_center(selector)
|
|
1930
|
+
self.gui_click_x_y(px + x, py + y, timeframe=timeframe)
|
|
1931
|
+
else:
|
|
1932
|
+
element_rect = self.get_gui_element_rect(selector)
|
|
1933
|
+
px = element_rect["x"]
|
|
1934
|
+
py = element_rect["y"]
|
|
1935
|
+
self.gui_click_x_y(px + x, py + y, timeframe=timeframe)
|
|
1936
|
+
|
|
1937
|
+
def click_with_offset(self, selector, x, y, center=False):
|
|
1938
|
+
element = self.find_element(selector)
|
|
1939
|
+
element.scroll_into_view()
|
|
1940
|
+
if "--debug" in sys.argv:
|
|
1941
|
+
displayed_selector = "`%s`" % selector
|
|
1942
|
+
if '"' not in selector:
|
|
1943
|
+
displayed_selector = '"%s"' % selector
|
|
1944
|
+
elif "'" not in selector:
|
|
1945
|
+
displayed_selector = "'%s'" % selector
|
|
1946
|
+
print(
|
|
1947
|
+
" <DEBUG> sb.click_with_offset(%s, %s, %s, center=%s)"
|
|
1948
|
+
% (displayed_selector, x, y, center)
|
|
1949
|
+
)
|
|
1950
|
+
element.click_with_offset(x=x, y=y, center=center)
|
|
1951
|
+
self.__slow_mode_pause_if_set()
|
|
1952
|
+
self.loop.run_until_complete(self.page.wait())
|
|
1953
|
+
|
|
1954
|
+
def _on_a_cf_turnstile_page(self, source=None):
|
|
1955
|
+
if not source or len(source) < 400:
|
|
1956
|
+
time.sleep(0.2)
|
|
1957
|
+
source = self.get_page_source()
|
|
1634
1958
|
if (
|
|
1635
|
-
|
|
1636
|
-
|
|
1959
|
+
(
|
|
1960
|
+
'data-callback="onCaptchaSuccess"' in source
|
|
1961
|
+
and 'title="reCAPTCHA"' not in source
|
|
1962
|
+
and 'id="recaptcha-token"' not in source
|
|
1963
|
+
)
|
|
1964
|
+
or "/challenge-platform/h/b/" in source
|
|
1637
1965
|
or 'id="challenge-widget-' in source
|
|
1966
|
+
or "challenges.cloudf" in source
|
|
1638
1967
|
or "cf-turnstile-" in source
|
|
1639
1968
|
):
|
|
1640
1969
|
return True
|
|
1641
1970
|
return False
|
|
1642
1971
|
|
|
1972
|
+
def _on_a_g_recaptcha_page(self, *args, **kwargs):
|
|
1973
|
+
time.sleep(0.4) # reCAPTCHA may need a moment to appear
|
|
1974
|
+
self.loop.run_until_complete(self.page.wait())
|
|
1975
|
+
source = self.get_page_source()
|
|
1976
|
+
if (
|
|
1977
|
+
(
|
|
1978
|
+
'id="recaptcha-token"' in source
|
|
1979
|
+
or 'title="reCAPTCHA"' in source
|
|
1980
|
+
)
|
|
1981
|
+
and self.is_element_visible('iframe[title="reCAPTCHA"]')
|
|
1982
|
+
):
|
|
1983
|
+
try:
|
|
1984
|
+
self.loop.run_until_complete(self.page.wait(0.1))
|
|
1985
|
+
except Exception:
|
|
1986
|
+
time.sleep(0.1)
|
|
1987
|
+
return True
|
|
1988
|
+
elif "com/recaptcha/api.js" in source:
|
|
1989
|
+
time.sleep(1.6) # Still loading
|
|
1990
|
+
try:
|
|
1991
|
+
self.loop.run_until_complete(self.page.wait(0.1))
|
|
1992
|
+
except Exception:
|
|
1993
|
+
time.sleep(0.1)
|
|
1994
|
+
return True
|
|
1995
|
+
return False
|
|
1996
|
+
|
|
1997
|
+
def __gui_click_recaptcha(self, use_cdp=False):
|
|
1998
|
+
selector = None
|
|
1999
|
+
if self.is_element_visible('iframe[title="reCAPTCHA"]'):
|
|
2000
|
+
selector = 'iframe[title="reCAPTCHA"]'
|
|
2001
|
+
else:
|
|
2002
|
+
return
|
|
2003
|
+
time.sleep(0.25)
|
|
2004
|
+
self.loop.run_until_complete(self.page.wait())
|
|
2005
|
+
time.sleep(0.25)
|
|
2006
|
+
with suppress(Exception):
|
|
2007
|
+
element_rect = self.get_gui_element_rect(selector, timeout=1)
|
|
2008
|
+
e_x = element_rect["x"]
|
|
2009
|
+
e_y = element_rect["y"]
|
|
2010
|
+
x_offset = 26
|
|
2011
|
+
y_offset = 35
|
|
2012
|
+
if shared_utils.is_windows():
|
|
2013
|
+
x_offset = 29
|
|
2014
|
+
x = e_x + x_offset
|
|
2015
|
+
y = e_y + y_offset
|
|
2016
|
+
sb_config._saved_cf_x_y = (x, y)
|
|
2017
|
+
time.sleep(0.08)
|
|
2018
|
+
if use_cdp:
|
|
2019
|
+
self.sleep(0.03)
|
|
2020
|
+
gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK)
|
|
2021
|
+
with gui_lock: # Prevent issues with multiple processes
|
|
2022
|
+
self.bring_active_window_to_front()
|
|
2023
|
+
time.sleep(0.056)
|
|
2024
|
+
self.click_with_offset(selector, x_offset, y_offset)
|
|
2025
|
+
time.sleep(0.056)
|
|
2026
|
+
else:
|
|
2027
|
+
self.gui_click_x_y(x, y)
|
|
2028
|
+
|
|
2029
|
+
def solve_captcha(self):
|
|
2030
|
+
self.__click_captcha(use_cdp=True)
|
|
2031
|
+
|
|
2032
|
+
def click_captcha(self):
|
|
2033
|
+
"""Same as solve_captcha()"""
|
|
2034
|
+
self.__click_captcha(use_cdp=True)
|
|
2035
|
+
|
|
1643
2036
|
def gui_click_captcha(self):
|
|
1644
|
-
|
|
2037
|
+
"""Use PyAutoGUI to click the CAPTCHA"""
|
|
2038
|
+
self.__click_captcha(use_cdp=False)
|
|
2039
|
+
|
|
2040
|
+
def __click_captcha(self, use_cdp=False):
|
|
2041
|
+
"""Uses PyAutoGUI unless use_cdp == True"""
|
|
2042
|
+
self.sleep(0.075)
|
|
2043
|
+
self.loop.run_until_complete(self.page.wait())
|
|
2044
|
+
self.sleep(0.025)
|
|
2045
|
+
source = self.get_page_source()
|
|
2046
|
+
if self._on_a_cf_turnstile_page(source):
|
|
2047
|
+
pass
|
|
2048
|
+
elif self._on_a_g_recaptcha_page(source):
|
|
2049
|
+
self.__gui_click_recaptcha(use_cdp)
|
|
2050
|
+
return
|
|
2051
|
+
else:
|
|
1645
2052
|
return
|
|
1646
2053
|
selector = None
|
|
1647
|
-
if (
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
):
|
|
2054
|
+
if self.is_element_present('[class="cf-turnstile"]'):
|
|
2055
|
+
selector = '[class="cf-turnstile"]'
|
|
2056
|
+
elif self.is_element_present("#challenge-form div > div"):
|
|
1651
2057
|
selector = "#challenge-form div > div"
|
|
1652
|
-
elif (
|
|
1653
|
-
self.is_element_present('[name*="cf-turnstile-"]')
|
|
1654
|
-
and self.is_element_present(
|
|
1655
|
-
'[style="display: grid;"] div div'
|
|
1656
|
-
)
|
|
1657
|
-
):
|
|
2058
|
+
elif self.is_element_present('[style="display: grid;"] div div'):
|
|
1658
2059
|
selector = '[style="display: grid;"] div div'
|
|
1659
|
-
elif (
|
|
1660
|
-
self.is_element_present('[name*="cf-turnstile-"]')
|
|
1661
|
-
and self.is_element_present("[class*=spacer] + div div")
|
|
1662
|
-
):
|
|
2060
|
+
elif self.is_element_present("[class*=spacer] + div div"):
|
|
1663
2061
|
selector = '[class*=spacer] + div div'
|
|
1664
|
-
elif (
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
):
|
|
1668
|
-
selector = "div.spacer div"
|
|
1669
|
-
elif (
|
|
1670
|
-
self.is_element_present('script[src*="challenges.c"]')
|
|
1671
|
-
and self.is_element_present(
|
|
1672
|
-
'[data-testid*="challenge-"] div'
|
|
1673
|
-
)
|
|
1674
|
-
):
|
|
2062
|
+
elif self.is_element_present(".spacer div:not([class])"):
|
|
2063
|
+
selector = ".spacer div:not([class])"
|
|
2064
|
+
elif self.is_element_present('[data-testid*="challenge-"] div'):
|
|
1675
2065
|
selector = '[data-testid*="challenge-"] div'
|
|
1676
|
-
elif self.is_element_present(
|
|
1677
|
-
"div#turnstile-widget div:not([class])"
|
|
1678
|
-
):
|
|
2066
|
+
elif self.is_element_present("div#turnstile-widget div:not([class])"):
|
|
1679
2067
|
selector = "div#turnstile-widget div:not([class])"
|
|
2068
|
+
elif self.is_element_present("ngx-turnstile div:not([class])"):
|
|
2069
|
+
selector = "ngx-turnstile div:not([class])"
|
|
1680
2070
|
elif self.is_element_present(
|
|
1681
2071
|
'form div:not([class]):has(input[name*="cf-turn"])'
|
|
1682
2072
|
):
|
|
1683
2073
|
selector = 'form div:not([class]):has(input[name*="cf-turn"])'
|
|
1684
|
-
elif (
|
|
1685
|
-
self.is_element_present('[src*="/turnstile/"]')
|
|
1686
|
-
and self.is_element_present("form div:not(:has(*))")
|
|
1687
|
-
):
|
|
2074
|
+
elif self.is_element_present("form div:not(:has(*))"):
|
|
1688
2075
|
selector = "form div:not(:has(*))"
|
|
1689
|
-
elif (
|
|
1690
|
-
self.is_element_present('[src*="/turnstile/"]')
|
|
1691
|
-
and self.is_element_present(
|
|
1692
|
-
"body > div#check > div:not([class])"
|
|
1693
|
-
)
|
|
1694
|
-
):
|
|
2076
|
+
elif self.is_element_present("body > div#check > div:not([class])"):
|
|
1695
2077
|
selector = "body > div#check > div:not([class])"
|
|
1696
2078
|
elif self.is_element_present(".cf-turnstile-wrapper"):
|
|
1697
2079
|
selector = ".cf-turnstile-wrapper"
|
|
1698
|
-
elif self.is_element_present(
|
|
1699
|
-
|
|
2080
|
+
elif self.is_element_present(
|
|
2081
|
+
'[id*="turnstile"] div:not([class])'
|
|
2082
|
+
):
|
|
2083
|
+
selector = '[id*="turnstile"] div:not([class])'
|
|
2084
|
+
elif self.is_element_present(
|
|
2085
|
+
'[class*="turnstile"] div:not([class])'
|
|
2086
|
+
):
|
|
2087
|
+
selector = '[class*="turnstile"] div:not([class])'
|
|
1700
2088
|
elif self.is_element_present(
|
|
1701
2089
|
'[data-callback="onCaptchaSuccess"]'
|
|
1702
2090
|
):
|
|
1703
2091
|
selector = '[data-callback="onCaptchaSuccess"]'
|
|
2092
|
+
elif self.is_element_present(
|
|
2093
|
+
"div:not([class]) > div:not([class])"
|
|
2094
|
+
):
|
|
2095
|
+
selector = "div:not([class]) > div:not([class])"
|
|
1704
2096
|
else:
|
|
1705
2097
|
return
|
|
1706
2098
|
if not selector:
|
|
@@ -1748,9 +2140,11 @@ class CDPMethods():
|
|
|
1748
2140
|
self.loop.run_until_complete(self.page.evaluate(script))
|
|
1749
2141
|
self.loop.run_until_complete(self.page.wait())
|
|
1750
2142
|
elif (
|
|
1751
|
-
self.is_element_present(
|
|
1752
|
-
|
|
1753
|
-
|
|
2143
|
+
self.is_element_present(
|
|
2144
|
+
'form [id*="turnstile"] div:not([class])'
|
|
2145
|
+
)
|
|
2146
|
+
or self.is_element_present(
|
|
2147
|
+
'form [class*="turnstile"] div:not([class])'
|
|
1754
2148
|
)
|
|
1755
2149
|
):
|
|
1756
2150
|
script = (
|
|
@@ -1758,22 +2152,58 @@ class CDPMethods():
|
|
|
1758
2152
|
'form [id*="turnstile"]');
|
|
1759
2153
|
var index = 0, length = $elements.length;
|
|
1760
2154
|
for(; index < length; index++){
|
|
2155
|
+
$elements[index].setAttribute('align', 'left');}
|
|
2156
|
+
var $elements = document.querySelectorAll(
|
|
2157
|
+
'form [class*="turnstile"]');
|
|
2158
|
+
var index = 0, length = $elements.length;
|
|
2159
|
+
for(; index < length; index++){
|
|
1761
2160
|
$elements[index].setAttribute('align', 'left');}"""
|
|
1762
2161
|
)
|
|
1763
2162
|
with suppress(Exception):
|
|
1764
2163
|
self.loop.run_until_complete(self.page.evaluate(script))
|
|
1765
2164
|
self.loop.run_until_complete(self.page.wait())
|
|
2165
|
+
elif (
|
|
2166
|
+
self.is_element_present(
|
|
2167
|
+
'[style*="text-align: center;"] div:not([class])'
|
|
2168
|
+
)
|
|
2169
|
+
):
|
|
2170
|
+
script = (
|
|
2171
|
+
"""var $elements = document.querySelectorAll(
|
|
2172
|
+
'[style*="text-align: center;"]');
|
|
2173
|
+
var index = 0, length = $elements.length;
|
|
2174
|
+
for(; index < length; index++){
|
|
2175
|
+
the_style = $elements[index].getAttribute('style');
|
|
2176
|
+
new_style = the_style.replaceAll('center', 'left');
|
|
2177
|
+
$elements[index].setAttribute('style', new_style);}"""
|
|
2178
|
+
)
|
|
2179
|
+
with suppress(Exception):
|
|
2180
|
+
self.loop.run_until_complete(self.page.evaluate(script))
|
|
2181
|
+
self.loop.run_until_complete(self.page.wait())
|
|
1766
2182
|
with suppress(Exception):
|
|
2183
|
+
time.sleep(0.05)
|
|
1767
2184
|
element_rect = self.get_gui_element_rect(selector, timeout=1)
|
|
1768
2185
|
e_x = element_rect["x"]
|
|
1769
2186
|
e_y = element_rect["y"]
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
2187
|
+
x_offset = 32
|
|
2188
|
+
y_offset = 32
|
|
2189
|
+
if shared_utils.is_windows():
|
|
2190
|
+
y_offset = 28
|
|
2191
|
+
x = e_x + x_offset
|
|
2192
|
+
y = e_y + y_offset
|
|
1775
2193
|
sb_config._saved_cf_x_y = (x, y)
|
|
1776
|
-
|
|
2194
|
+
time.sleep(0.05)
|
|
2195
|
+
if hasattr(sb_config, "_cdp_proxy") and sb_config._cdp_proxy:
|
|
2196
|
+
time.sleep(0.22) # CAPTCHA may load slower with proxy
|
|
2197
|
+
if use_cdp:
|
|
2198
|
+
self.sleep(0.03)
|
|
2199
|
+
gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK)
|
|
2200
|
+
with gui_lock: # Prevent issues with multiple processes
|
|
2201
|
+
self.bring_active_window_to_front()
|
|
2202
|
+
time.sleep(0.05)
|
|
2203
|
+
self.click_with_offset(selector, x_offset, y_offset)
|
|
2204
|
+
time.sleep(0.05)
|
|
2205
|
+
else:
|
|
2206
|
+
self.gui_click_x_y(x, y)
|
|
1777
2207
|
|
|
1778
2208
|
def __gui_drag_drop(self, x1, y1, x2, y2, timeframe=0.25, uc_lock=False):
|
|
1779
2209
|
self.__install_pyautogui_if_missing()
|
|
@@ -1793,17 +2223,19 @@ class CDPMethods():
|
|
|
1793
2223
|
% (x2, y2, screen_width, screen_height)
|
|
1794
2224
|
)
|
|
1795
2225
|
if uc_lock:
|
|
1796
|
-
gui_lock =
|
|
1797
|
-
constants.MultiBrowser.PYAUTOGUILOCK
|
|
1798
|
-
)
|
|
2226
|
+
gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK)
|
|
1799
2227
|
with gui_lock: # Prevent issues with multiple processes
|
|
2228
|
+
if "--debug" in sys.argv:
|
|
2229
|
+
print(" <DEBUG> pyautogui.moveTo(%s, %s)" % (x1, y1))
|
|
1800
2230
|
pyautogui.moveTo(x1, y1, 0.25, pyautogui.easeOutQuad)
|
|
1801
2231
|
self.__add_light_pause()
|
|
1802
2232
|
if "--debug" in sys.argv:
|
|
1803
|
-
print(" <DEBUG> pyautogui.
|
|
2233
|
+
print(" <DEBUG> pyautogui.dragTo(%s, %s)" % (x2, y2))
|
|
1804
2234
|
pyautogui.dragTo(x2, y2, button="left", duration=timeframe)
|
|
1805
2235
|
else:
|
|
1806
2236
|
# Called from a method where the gui_lock is already active
|
|
2237
|
+
if "--debug" in sys.argv:
|
|
2238
|
+
print(" <DEBUG> pyautogui.moveTo(%s, %s)" % (x1, y1))
|
|
1807
2239
|
pyautogui.moveTo(x1, y1, 0.25, pyautogui.easeOutQuad)
|
|
1808
2240
|
self.__add_light_pause()
|
|
1809
2241
|
if "--debug" in sys.argv:
|
|
@@ -1813,9 +2245,7 @@ class CDPMethods():
|
|
|
1813
2245
|
def gui_drag_drop_points(self, x1, y1, x2, y2, timeframe=0.35):
|
|
1814
2246
|
"""Use PyAutoGUI to drag-and-drop from one point to another.
|
|
1815
2247
|
Can simulate click-and-hold when using the same point twice."""
|
|
1816
|
-
gui_lock =
|
|
1817
|
-
constants.MultiBrowser.PYAUTOGUILOCK
|
|
1818
|
-
)
|
|
2248
|
+
gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK)
|
|
1819
2249
|
with gui_lock: # Prevent issues with multiple processes
|
|
1820
2250
|
self.__install_pyautogui_if_missing()
|
|
1821
2251
|
import pyautogui
|
|
@@ -1828,18 +2258,18 @@ class CDPMethods():
|
|
|
1828
2258
|
win_x = window_rect["x"]
|
|
1829
2259
|
win_y = window_rect["y"]
|
|
1830
2260
|
scr_width = pyautogui.size().width
|
|
1831
|
-
self.
|
|
1832
|
-
self.__add_light_pause()
|
|
1833
|
-
win_width = self.get_window_size()["width"]
|
|
2261
|
+
win_width = self.evaluate("screen.availWidth;")
|
|
1834
2262
|
width_ratio = round(float(scr_width) / float(win_width), 2)
|
|
1835
2263
|
width_ratio += 0.01
|
|
1836
2264
|
if width_ratio < 0.45 or width_ratio > 2.55:
|
|
1837
2265
|
width_ratio = 1.01
|
|
1838
2266
|
sb_config._saved_width_ratio = width_ratio
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
2267
|
+
with suppress(Exception):
|
|
2268
|
+
self.get_window() # If this fails, skip the rest
|
|
2269
|
+
self.minimize()
|
|
2270
|
+
self.__add_light_pause()
|
|
2271
|
+
self.__set_window_rect(win_x, win_y, width, height)
|
|
2272
|
+
self.__add_light_pause()
|
|
1843
2273
|
x1 = x1 * width_ratio
|
|
1844
2274
|
y1 = y1 * (width_ratio - 0.02)
|
|
1845
2275
|
x2 = x2 * width_ratio
|
|
@@ -1882,25 +2312,21 @@ class CDPMethods():
|
|
|
1882
2312
|
% (x, y, screen_width, screen_height)
|
|
1883
2313
|
)
|
|
1884
2314
|
if uc_lock:
|
|
1885
|
-
gui_lock =
|
|
1886
|
-
constants.MultiBrowser.PYAUTOGUILOCK
|
|
1887
|
-
)
|
|
2315
|
+
gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK)
|
|
1888
2316
|
with gui_lock: # Prevent issues with multiple processes
|
|
1889
|
-
pyautogui.moveTo(x, y, timeframe, pyautogui.easeOutQuad)
|
|
1890
|
-
time.sleep(0.056)
|
|
1891
2317
|
if "--debug" in sys.argv:
|
|
1892
2318
|
print(" <DEBUG> pyautogui.moveTo(%s, %s)" % (x, y))
|
|
2319
|
+
pyautogui.moveTo(x, y, timeframe, pyautogui.easeOutQuad)
|
|
2320
|
+
time.sleep(0.056)
|
|
1893
2321
|
else:
|
|
1894
2322
|
# Called from a method where the gui_lock is already active
|
|
1895
|
-
pyautogui.moveTo(x, y, timeframe, pyautogui.easeOutQuad)
|
|
1896
|
-
time.sleep(0.056)
|
|
1897
2323
|
if "--debug" in sys.argv:
|
|
1898
2324
|
print(" <DEBUG> pyautogui.moveTo(%s, %s)" % (x, y))
|
|
2325
|
+
pyautogui.moveTo(x, y, timeframe, pyautogui.easeOutQuad)
|
|
2326
|
+
time.sleep(0.056)
|
|
1899
2327
|
|
|
1900
2328
|
def gui_hover_x_y(self, x, y, timeframe=0.25):
|
|
1901
|
-
gui_lock =
|
|
1902
|
-
constants.MultiBrowser.PYAUTOGUILOCK
|
|
1903
|
-
)
|
|
2329
|
+
gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK)
|
|
1904
2330
|
with gui_lock: # Prevent issues with multiple processes
|
|
1905
2331
|
self.__install_pyautogui_if_missing()
|
|
1906
2332
|
import pyautogui
|
|
@@ -1925,15 +2351,14 @@ class CDPMethods():
|
|
|
1925
2351
|
width_ratio = sb_config._saved_width_ratio
|
|
1926
2352
|
else:
|
|
1927
2353
|
scr_width = pyautogui.size().width
|
|
1928
|
-
self.
|
|
1929
|
-
self.__add_light_pause()
|
|
1930
|
-
win_width = self.get_window_size()["width"]
|
|
2354
|
+
win_width = self.evaluate("screen.availWidth;")
|
|
1931
2355
|
width_ratio = round(float(scr_width) / float(win_width), 2)
|
|
1932
2356
|
width_ratio += 0.01
|
|
1933
2357
|
if width_ratio < 0.45 or width_ratio > 2.55:
|
|
1934
2358
|
width_ratio = 1.01
|
|
1935
2359
|
sb_config._saved_width_ratio = width_ratio
|
|
1936
|
-
|
|
2360
|
+
with suppress(Exception):
|
|
2361
|
+
self.__set_window_rect(win_x, win_y, width, height)
|
|
1937
2362
|
self.__add_light_pause()
|
|
1938
2363
|
self.bring_active_window_to_front()
|
|
1939
2364
|
elif (
|
|
@@ -1959,14 +2384,40 @@ class CDPMethods():
|
|
|
1959
2384
|
if width > 0 and height > 0:
|
|
1960
2385
|
x, y = self.get_gui_element_center(selector)
|
|
1961
2386
|
self.bring_active_window_to_front()
|
|
1962
|
-
self.__gui_hover_x_y(x, y, timeframe=timeframe)
|
|
2387
|
+
self.__gui_hover_x_y(x, y, timeframe=timeframe, uc_lock=False)
|
|
1963
2388
|
self.__slow_mode_pause_if_set()
|
|
1964
2389
|
self.loop.run_until_complete(self.page.wait())
|
|
1965
2390
|
|
|
2391
|
+
def hover_element(self, selector, timeframe=0.25):
|
|
2392
|
+
element = self.select(selector)
|
|
2393
|
+
gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK)
|
|
2394
|
+
with gui_lock:
|
|
2395
|
+
self.bring_active_window_to_front()
|
|
2396
|
+
self.sleep(0.02)
|
|
2397
|
+
element.mouse_move()
|
|
2398
|
+
self.sleep(timeframe)
|
|
2399
|
+
|
|
2400
|
+
def hover_and_click(self, hover_selector, click_selector):
|
|
2401
|
+
if getattr(sb_config, "_cdp_mobile_mode", None):
|
|
2402
|
+
self.select(click_selector).click()
|
|
2403
|
+
return
|
|
2404
|
+
hover_element = self.select(hover_selector)
|
|
2405
|
+
gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK)
|
|
2406
|
+
with gui_lock:
|
|
2407
|
+
self.bring_active_window_to_front()
|
|
2408
|
+
self.sleep(0.02)
|
|
2409
|
+
hover_element.mouse_move()
|
|
2410
|
+
self.sleep(0.25)
|
|
2411
|
+
try:
|
|
2412
|
+
self.click(click_selector, timeout=0.5)
|
|
2413
|
+
except Exception:
|
|
2414
|
+
self.select(click_selector, timeout=2).click()
|
|
2415
|
+
|
|
1966
2416
|
def gui_hover_and_click(self, hover_selector, click_selector):
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
2417
|
+
if getattr(sb_config, "_cdp_mobile_mode", None):
|
|
2418
|
+
self.select(click_selector).click()
|
|
2419
|
+
return
|
|
2420
|
+
gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK)
|
|
1970
2421
|
with gui_lock:
|
|
1971
2422
|
self.__make_sure_pyautogui_lock_is_writable()
|
|
1972
2423
|
self.bring_active_window_to_front()
|
|
@@ -2126,6 +2577,10 @@ class CDPMethods():
|
|
|
2126
2577
|
time.sleep(0.1)
|
|
2127
2578
|
raise Exception("Element {%s} was not visible!" % selector)
|
|
2128
2579
|
|
|
2580
|
+
def wait_for_element(self, selector, **kwargs):
|
|
2581
|
+
"""Same as wait_for_element_visible()"""
|
|
2582
|
+
return self.wait_for_element_visible(selector, **kwargs)
|
|
2583
|
+
|
|
2129
2584
|
def wait_for_element_not_visible(self, selector, timeout=None):
|
|
2130
2585
|
"""Wait for element to not be visible on page. (May still be in DOM)"""
|
|
2131
2586
|
if not timeout:
|
|
@@ -2503,6 +2958,13 @@ class CDPMethods():
|
|
|
2503
2958
|
self.loop.run_until_complete(self.page.evaluate(js_code))
|
|
2504
2959
|
self.loop.run_until_complete(self.page.wait())
|
|
2505
2960
|
|
|
2961
|
+
def scroll_by_y(self, y):
|
|
2962
|
+
y = int(y)
|
|
2963
|
+
js_code = "window.scrollBy(0, %s);" % y
|
|
2964
|
+
with suppress(Exception):
|
|
2965
|
+
self.loop.run_until_complete(self.page.evaluate(js_code))
|
|
2966
|
+
self.loop.run_until_complete(self.page.wait())
|
|
2967
|
+
|
|
2506
2968
|
def scroll_to_top(self):
|
|
2507
2969
|
js_code = "window.scrollTo(0, 0);"
|
|
2508
2970
|
with suppress(Exception):
|
|
@@ -2516,13 +2978,56 @@ class CDPMethods():
|
|
|
2516
2978
|
self.loop.run_until_complete(self.page.wait())
|
|
2517
2979
|
|
|
2518
2980
|
def scroll_up(self, amount=25):
|
|
2519
|
-
|
|
2981
|
+
"""Scrolls up as a percentage of the page."""
|
|
2982
|
+
try:
|
|
2983
|
+
self.loop.run_until_complete(self.page.scroll_up(amount))
|
|
2984
|
+
except Exception:
|
|
2985
|
+
amount = self.get_window_size()["height"] * amount / 100
|
|
2986
|
+
self.execute_script("window.scrollBy(0, -%s);" % amount)
|
|
2520
2987
|
self.loop.run_until_complete(self.page.wait())
|
|
2521
2988
|
|
|
2522
2989
|
def scroll_down(self, amount=25):
|
|
2523
|
-
|
|
2990
|
+
"""Scrolls down as a percentage of the page."""
|
|
2991
|
+
try:
|
|
2992
|
+
self.loop.run_until_complete(self.page.scroll_down(amount))
|
|
2993
|
+
except Exception:
|
|
2994
|
+
amount = self.get_window_size()["height"] * amount / 100
|
|
2995
|
+
self.execute_script("window.scrollBy(0, %s);" % amount)
|
|
2524
2996
|
self.loop.run_until_complete(self.page.wait())
|
|
2525
2997
|
|
|
2998
|
+
def save_page_source(self, name, folder=None):
|
|
2999
|
+
from seleniumbase.core import log_helper
|
|
3000
|
+
if not name.endswith(".html"):
|
|
3001
|
+
name = name + ".html"
|
|
3002
|
+
if folder:
|
|
3003
|
+
abs_path = os.path.abspath(".")
|
|
3004
|
+
file_path = os.path.join(abs_path, folder)
|
|
3005
|
+
if not os.path.exists(file_path):
|
|
3006
|
+
os.makedirs(file_path)
|
|
3007
|
+
html_file_path = os.path.join(file_path, name)
|
|
3008
|
+
else:
|
|
3009
|
+
html_file_path = name
|
|
3010
|
+
page_source = self.get_page_source()
|
|
3011
|
+
last_page = self.get_current_url()
|
|
3012
|
+
meta_charset = '<meta charset="utf-8">'
|
|
3013
|
+
rendered_source = ""
|
|
3014
|
+
if "://" in last_page:
|
|
3015
|
+
base_href_html = log_helper.get_base_href_html(last_page)
|
|
3016
|
+
if ' charset="' not in page_source:
|
|
3017
|
+
rendered_source = "%s\n%s\n%s" % (
|
|
3018
|
+
base_href_html, meta_charset, page_source
|
|
3019
|
+
)
|
|
3020
|
+
else:
|
|
3021
|
+
rendered_source = "%s\n%s" % (base_href_html, page_source)
|
|
3022
|
+
else:
|
|
3023
|
+
rendered_source = page_source
|
|
3024
|
+
html_file = open(html_file_path, mode="w+", encoding="utf-8")
|
|
3025
|
+
html_file.write(rendered_source)
|
|
3026
|
+
html_file.close()
|
|
3027
|
+
|
|
3028
|
+
def save_as_html(self, *args, **kwargs):
|
|
3029
|
+
self.save_page_source(*args, **kwargs)
|
|
3030
|
+
|
|
2526
3031
|
def save_screenshot(self, name, folder=None, selector=None):
|
|
2527
3032
|
filename = name
|
|
2528
3033
|
if folder:
|
|
@@ -2540,12 +3045,26 @@ class CDPMethods():
|
|
|
2540
3045
|
filename = os.path.join(folder, name)
|
|
2541
3046
|
self.loop.run_until_complete(self.page.print_to_pdf(filename))
|
|
2542
3047
|
|
|
3048
|
+
def save_as_pdf(self, *args, **kwargs):
|
|
3049
|
+
self.print_to_pdf(*args, **kwargs)
|
|
3050
|
+
|
|
2543
3051
|
|
|
2544
3052
|
class Chrome(CDPMethods):
|
|
2545
3053
|
def __init__(self, url=None, **kwargs):
|
|
2546
3054
|
if not url:
|
|
2547
3055
|
url = "about:blank"
|
|
2548
|
-
loop = asyncio.new_event_loop()
|
|
2549
3056
|
driver = cdp_util.start_sync(**kwargs)
|
|
3057
|
+
loop = asyncio.new_event_loop()
|
|
2550
3058
|
page = loop.run_until_complete(driver.get(url))
|
|
3059
|
+
wait_timeout = 30.0
|
|
3060
|
+
if hasattr(sb_config, "_cdp_proxy") and sb_config._cdp_proxy:
|
|
3061
|
+
wait_timeout = 45.0
|
|
3062
|
+
try:
|
|
3063
|
+
loop.run_until_complete(
|
|
3064
|
+
asyncio.wait_for(page.wait(), timeout=wait_timeout)
|
|
3065
|
+
)
|
|
3066
|
+
except asyncio.TimeoutError:
|
|
3067
|
+
pass
|
|
3068
|
+
except Exception:
|
|
3069
|
+
pass
|
|
2551
3070
|
super().__init__(loop, page, driver)
|