seleniumbase 4.44.2__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.
- seleniumbase/__version__.py +1 -1
- seleniumbase/behave/behave_sb.py +14 -0
- seleniumbase/console_scripts/run.py +1 -0
- seleniumbase/console_scripts/sb_install.py +142 -11
- seleniumbase/console_scripts/sb_mkdir.py +76 -0
- seleniumbase/console_scripts/sb_mkrec.py +25 -0
- seleniumbase/console_scripts/sb_recorder.py +40 -3
- seleniumbase/core/browser_launcher.py +279 -117
- seleniumbase/core/detect_b_ver.py +8 -6
- seleniumbase/core/log_helper.py +11 -16
- seleniumbase/core/mysql.py +1 -1
- seleniumbase/core/report_helper.py +3 -7
- seleniumbase/core/sb_cdp.py +275 -78
- seleniumbase/core/sb_driver.py +36 -5
- seleniumbase/core/session_helper.py +2 -4
- seleniumbase/drivers/chromium_drivers/__init__.py +0 -0
- seleniumbase/fixtures/base_case.py +251 -201
- seleniumbase/fixtures/constants.py +1 -0
- seleniumbase/fixtures/js_utils.py +52 -14
- seleniumbase/fixtures/page_actions.py +18 -7
- seleniumbase/fixtures/page_utils.py +4 -2
- seleniumbase/fixtures/shared_utils.py +2 -4
- seleniumbase/masterqa/master_qa.py +16 -2
- seleniumbase/plugins/base_plugin.py +8 -0
- seleniumbase/plugins/driver_manager.py +15 -5
- seleniumbase/plugins/pytest_plugin.py +43 -57
- seleniumbase/plugins/sb_manager.py +23 -19
- seleniumbase/plugins/selenium_plugin.py +20 -13
- seleniumbase/undetected/__init__.py +11 -10
- seleniumbase/undetected/cdp.py +1 -12
- seleniumbase/undetected/cdp_driver/browser.py +330 -128
- seleniumbase/undetected/cdp_driver/cdp_util.py +48 -14
- seleniumbase/undetected/cdp_driver/config.py +78 -11
- seleniumbase/undetected/cdp_driver/connection.py +15 -43
- seleniumbase/undetected/cdp_driver/element.py +37 -22
- seleniumbase/undetected/cdp_driver/tab.py +414 -39
- {seleniumbase-4.44.2.dist-info → seleniumbase-4.45.10.dist-info}/METADATA +140 -152
- {seleniumbase-4.44.2.dist-info → seleniumbase-4.45.10.dist-info}/RECORD +42 -41
- {seleniumbase-4.44.2.dist-info → seleniumbase-4.45.10.dist-info}/licenses/LICENSE +1 -1
- {seleniumbase-4.44.2.dist-info → seleniumbase-4.45.10.dist-info}/WHEEL +0 -0
- {seleniumbase-4.44.2.dist-info → seleniumbase-4.45.10.dist-info}/entry_points.txt +0 -0
- {seleniumbase-4.44.2.dist-info → seleniumbase-4.45.10.dist-info}/top_level.txt +0 -0
seleniumbase/core/sb_cdp.py
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
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
|
|
@@ -14,6 +16,7 @@ from seleniumbase.fixtures import js_utils
|
|
|
14
16
|
from seleniumbase.fixtures import page_utils
|
|
15
17
|
from seleniumbase.fixtures import shared_utils
|
|
16
18
|
from seleniumbase.undetected.cdp_driver import cdp_util
|
|
19
|
+
from seleniumbase.undetected.cdp_driver import tab as cdp_tab
|
|
17
20
|
|
|
18
21
|
|
|
19
22
|
class CDPMethods():
|
|
@@ -112,7 +115,18 @@ class CDPMethods():
|
|
|
112
115
|
driver = self.driver
|
|
113
116
|
if hasattr(driver, "cdp_base"):
|
|
114
117
|
driver = driver.cdp_base
|
|
115
|
-
|
|
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)
|
|
116
130
|
url_protocol = url.split(":")[0]
|
|
117
131
|
safe_url = True
|
|
118
132
|
if url_protocol not in ["about", "data", "chrome"]:
|
|
@@ -124,7 +138,14 @@ class CDPMethods():
|
|
|
124
138
|
else:
|
|
125
139
|
time.sleep(0.012)
|
|
126
140
|
self.__slow_mode_pause_if_set()
|
|
127
|
-
|
|
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
|
|
128
149
|
|
|
129
150
|
def open(self, url, **kwargs):
|
|
130
151
|
self.get(url, **kwargs)
|
|
@@ -143,6 +164,48 @@ class CDPMethods():
|
|
|
143
164
|
def get_event_loop(self):
|
|
144
165
|
return self.loop
|
|
145
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
|
+
|
|
146
209
|
def add_handler(self, event, handler):
|
|
147
210
|
self.page.add_handler(event, handler)
|
|
148
211
|
|
|
@@ -479,7 +542,7 @@ class CDPMethods():
|
|
|
479
542
|
text = text[:-1]
|
|
480
543
|
for key in text:
|
|
481
544
|
element.send_keys(key)
|
|
482
|
-
time.sleep(0.
|
|
545
|
+
time.sleep(float(0.042 + (random.random() / 110.0)))
|
|
483
546
|
if submit:
|
|
484
547
|
element.send_keys("\r\n")
|
|
485
548
|
time.sleep(0.044)
|
|
@@ -707,10 +770,15 @@ class CDPMethods():
|
|
|
707
770
|
if tag_name:
|
|
708
771
|
tag_name = tag_name.lower().strip()
|
|
709
772
|
if (
|
|
710
|
-
tag_name in [
|
|
773
|
+
tag_name in [
|
|
774
|
+
"a", "button", "canvas", "div", "input", "li", "span", "label"
|
|
775
|
+
]
|
|
711
776
|
and "contains(" not in selector
|
|
712
777
|
):
|
|
713
|
-
|
|
778
|
+
try:
|
|
779
|
+
element.mouse_click() # Simulated click (NOT PyAutoGUI)
|
|
780
|
+
except Exception:
|
|
781
|
+
element.click() # Standard CDP click
|
|
714
782
|
else:
|
|
715
783
|
element.click() # Standard CDP click
|
|
716
784
|
self.__slow_mode_pause_if_set()
|
|
@@ -938,7 +1006,7 @@ class CDPMethods():
|
|
|
938
1006
|
text = text.replace("\n", "\r")
|
|
939
1007
|
for key in text:
|
|
940
1008
|
element.send_keys(key)
|
|
941
|
-
time.sleep(0.
|
|
1009
|
+
time.sleep(float(0.042 + (random.random() / 110.0)))
|
|
942
1010
|
if submit:
|
|
943
1011
|
element.send_keys("\r\n")
|
|
944
1012
|
time.sleep(0.044)
|
|
@@ -1120,10 +1188,74 @@ class CDPMethods():
|
|
|
1120
1188
|
def switch_to_newest_window(self):
|
|
1121
1189
|
self.switch_to_tab(-1)
|
|
1122
1190
|
|
|
1123
|
-
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
|
|
1124
1193
|
if not isinstance(url, str):
|
|
1125
1194
|
url = "about:blank"
|
|
1126
|
-
|
|
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)
|
|
1127
1259
|
if switch_to:
|
|
1128
1260
|
self.switch_to_newest_tab()
|
|
1129
1261
|
|
|
@@ -1185,7 +1317,14 @@ class CDPMethods():
|
|
|
1185
1317
|
self.page.evaluate("window.location.origin")
|
|
1186
1318
|
)
|
|
1187
1319
|
|
|
1188
|
-
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()
|
|
1189
1328
|
try:
|
|
1190
1329
|
source = self.loop.run_until_complete(
|
|
1191
1330
|
self.page.evaluate("document.documentElement.outerHTML")
|
|
@@ -1527,13 +1666,15 @@ class CDPMethods():
|
|
|
1527
1666
|
css_selector = self.__convert_to_css_if_xpath(selector)
|
|
1528
1667
|
css_selector = re.escape(css_selector) # Add "\\" to special chars
|
|
1529
1668
|
css_selector = js_utils.escape_quotes_if_needed(css_selector)
|
|
1530
|
-
js_code =
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
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
|
+
)
|
|
1537
1678
|
)
|
|
1538
1679
|
with suppress(Exception):
|
|
1539
1680
|
self.loop.run_until_complete(self.page.evaluate(js_code))
|
|
@@ -1692,7 +1833,7 @@ class CDPMethods():
|
|
|
1692
1833
|
self.__make_sure_pyautogui_lock_is_writable()
|
|
1693
1834
|
for key in keys:
|
|
1694
1835
|
pyautogui.press(key)
|
|
1695
|
-
time.sleep(0.
|
|
1836
|
+
time.sleep(float(0.042 + (random.random() / 110.0)))
|
|
1696
1837
|
self.__slow_mode_pause_if_set()
|
|
1697
1838
|
self.loop.run_until_complete(self.page.sleep(0.025))
|
|
1698
1839
|
|
|
@@ -1796,6 +1937,16 @@ class CDPMethods():
|
|
|
1796
1937
|
def click_with_offset(self, selector, x, y, center=False):
|
|
1797
1938
|
element = self.find_element(selector)
|
|
1798
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
|
+
)
|
|
1799
1950
|
element.click_with_offset(x=x, y=y, center=center)
|
|
1800
1951
|
self.__slow_mode_pause_if_set()
|
|
1801
1952
|
self.loop.run_until_complete(self.page.wait())
|
|
@@ -1805,8 +1956,12 @@ class CDPMethods():
|
|
|
1805
1956
|
time.sleep(0.2)
|
|
1806
1957
|
source = self.get_page_source()
|
|
1807
1958
|
if (
|
|
1808
|
-
|
|
1809
|
-
|
|
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
|
|
1810
1965
|
or 'id="challenge-widget-' in source
|
|
1811
1966
|
or "challenges.cloudf" in source
|
|
1812
1967
|
or "cf-turnstile-" in source
|
|
@@ -1814,17 +1969,28 @@ class CDPMethods():
|
|
|
1814
1969
|
return True
|
|
1815
1970
|
return False
|
|
1816
1971
|
|
|
1817
|
-
def _on_a_g_recaptcha_page(self,
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
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()
|
|
1821
1976
|
if (
|
|
1822
|
-
|
|
1823
|
-
|
|
1977
|
+
(
|
|
1978
|
+
'id="recaptcha-token"' in source
|
|
1979
|
+
or 'title="reCAPTCHA"' in source
|
|
1980
|
+
)
|
|
1981
|
+
and self.is_element_visible('iframe[title="reCAPTCHA"]')
|
|
1824
1982
|
):
|
|
1983
|
+
try:
|
|
1984
|
+
self.loop.run_until_complete(self.page.wait(0.1))
|
|
1985
|
+
except Exception:
|
|
1986
|
+
time.sleep(0.1)
|
|
1825
1987
|
return True
|
|
1826
|
-
elif "/recaptcha/api.js" in source:
|
|
1988
|
+
elif "com/recaptcha/api.js" in source:
|
|
1827
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)
|
|
1828
1994
|
return True
|
|
1829
1995
|
return False
|
|
1830
1996
|
|
|
@@ -1834,8 +2000,10 @@ class CDPMethods():
|
|
|
1834
2000
|
selector = 'iframe[title="reCAPTCHA"]'
|
|
1835
2001
|
else:
|
|
1836
2002
|
return
|
|
2003
|
+
time.sleep(0.25)
|
|
2004
|
+
self.loop.run_until_complete(self.page.wait())
|
|
2005
|
+
time.sleep(0.25)
|
|
1837
2006
|
with suppress(Exception):
|
|
1838
|
-
time.sleep(0.08)
|
|
1839
2007
|
element_rect = self.get_gui_element_rect(selector, timeout=1)
|
|
1840
2008
|
e_x = element_rect["x"]
|
|
1841
2009
|
e_y = element_rect["y"]
|
|
@@ -1861,51 +2029,41 @@ class CDPMethods():
|
|
|
1861
2029
|
def solve_captcha(self):
|
|
1862
2030
|
self.__click_captcha(use_cdp=True)
|
|
1863
2031
|
|
|
2032
|
+
def click_captcha(self):
|
|
2033
|
+
"""Same as solve_captcha()"""
|
|
2034
|
+
self.__click_captcha(use_cdp=True)
|
|
2035
|
+
|
|
1864
2036
|
def gui_click_captcha(self):
|
|
2037
|
+
"""Use PyAutoGUI to click the CAPTCHA"""
|
|
1865
2038
|
self.__click_captcha(use_cdp=False)
|
|
1866
2039
|
|
|
1867
2040
|
def __click_captcha(self, use_cdp=False):
|
|
1868
2041
|
"""Uses PyAutoGUI unless use_cdp == True"""
|
|
1869
|
-
self.sleep(0.
|
|
2042
|
+
self.sleep(0.075)
|
|
2043
|
+
self.loop.run_until_complete(self.page.wait())
|
|
2044
|
+
self.sleep(0.025)
|
|
1870
2045
|
source = self.get_page_source()
|
|
1871
|
-
if self.
|
|
2046
|
+
if self._on_a_cf_turnstile_page(source):
|
|
2047
|
+
pass
|
|
2048
|
+
elif self._on_a_g_recaptcha_page(source):
|
|
1872
2049
|
self.__gui_click_recaptcha(use_cdp)
|
|
1873
2050
|
return
|
|
1874
|
-
|
|
2051
|
+
else:
|
|
1875
2052
|
return
|
|
1876
2053
|
selector = None
|
|
1877
|
-
if (
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
):
|
|
2054
|
+
if self.is_element_present('[class="cf-turnstile"]'):
|
|
2055
|
+
selector = '[class="cf-turnstile"]'
|
|
2056
|
+
elif self.is_element_present("#challenge-form div > div"):
|
|
1881
2057
|
selector = "#challenge-form div > div"
|
|
1882
|
-
elif (
|
|
1883
|
-
self.is_element_present('[name*="cf-turnstile-"]')
|
|
1884
|
-
and self.is_element_present(
|
|
1885
|
-
'[style="display: grid;"] div div'
|
|
1886
|
-
)
|
|
1887
|
-
):
|
|
2058
|
+
elif self.is_element_present('[style="display: grid;"] div div'):
|
|
1888
2059
|
selector = '[style="display: grid;"] div div'
|
|
1889
|
-
elif (
|
|
1890
|
-
self.is_element_present('[name*="cf-turnstile-"]')
|
|
1891
|
-
and self.is_element_present("[class*=spacer] + div div")
|
|
1892
|
-
):
|
|
2060
|
+
elif self.is_element_present("[class*=spacer] + div div"):
|
|
1893
2061
|
selector = '[class*=spacer] + div div'
|
|
1894
|
-
elif (
|
|
1895
|
-
self.is_element_present('[name*="cf-turnstile-"]')
|
|
1896
|
-
and self.is_element_present(".spacer div:not([class])")
|
|
1897
|
-
):
|
|
2062
|
+
elif self.is_element_present(".spacer div:not([class])"):
|
|
1898
2063
|
selector = ".spacer div:not([class])"
|
|
1899
|
-
elif (
|
|
1900
|
-
self.is_element_present('script[src*="challenges.c"]')
|
|
1901
|
-
and self.is_element_present(
|
|
1902
|
-
'[data-testid*="challenge-"] div'
|
|
1903
|
-
)
|
|
1904
|
-
):
|
|
2064
|
+
elif self.is_element_present('[data-testid*="challenge-"] div'):
|
|
1905
2065
|
selector = '[data-testid*="challenge-"] div'
|
|
1906
|
-
elif self.is_element_present(
|
|
1907
|
-
"div#turnstile-widget div:not([class])"
|
|
1908
|
-
):
|
|
2066
|
+
elif self.is_element_present("div#turnstile-widget div:not([class])"):
|
|
1909
2067
|
selector = "div#turnstile-widget div:not([class])"
|
|
1910
2068
|
elif self.is_element_present("ngx-turnstile div:not([class])"):
|
|
1911
2069
|
selector = "ngx-turnstile div:not([class])"
|
|
@@ -1913,22 +2071,12 @@ class CDPMethods():
|
|
|
1913
2071
|
'form div:not([class]):has(input[name*="cf-turn"])'
|
|
1914
2072
|
):
|
|
1915
2073
|
selector = 'form div:not([class]):has(input[name*="cf-turn"])'
|
|
1916
|
-
elif (
|
|
1917
|
-
self.is_element_present('[src*="/turnstile/"]')
|
|
1918
|
-
and self.is_element_present("form div:not(:has(*))")
|
|
1919
|
-
):
|
|
2074
|
+
elif self.is_element_present("form div:not(:has(*))"):
|
|
1920
2075
|
selector = "form div:not(:has(*))"
|
|
1921
|
-
elif (
|
|
1922
|
-
self.is_element_present('[src*="/turnstile/"]')
|
|
1923
|
-
and self.is_element_present(
|
|
1924
|
-
"body > div#check > div:not([class])"
|
|
1925
|
-
)
|
|
1926
|
-
):
|
|
2076
|
+
elif self.is_element_present("body > div#check > div:not([class])"):
|
|
1927
2077
|
selector = "body > div#check > div:not([class])"
|
|
1928
2078
|
elif self.is_element_present(".cf-turnstile-wrapper"):
|
|
1929
2079
|
selector = ".cf-turnstile-wrapper"
|
|
1930
|
-
elif self.is_element_present('[class="cf-turnstile"]'):
|
|
1931
|
-
selector = '[class="cf-turnstile"]'
|
|
1932
2080
|
elif self.is_element_present(
|
|
1933
2081
|
'[id*="turnstile"] div:not([class])'
|
|
1934
2082
|
):
|
|
@@ -1941,6 +2089,10 @@ class CDPMethods():
|
|
|
1941
2089
|
'[data-callback="onCaptchaSuccess"]'
|
|
1942
2090
|
):
|
|
1943
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])"
|
|
1944
2096
|
else:
|
|
1945
2097
|
return
|
|
1946
2098
|
if not selector:
|
|
@@ -2028,7 +2180,7 @@ class CDPMethods():
|
|
|
2028
2180
|
self.loop.run_until_complete(self.page.evaluate(script))
|
|
2029
2181
|
self.loop.run_until_complete(self.page.wait())
|
|
2030
2182
|
with suppress(Exception):
|
|
2031
|
-
time.sleep(0.
|
|
2183
|
+
time.sleep(0.05)
|
|
2032
2184
|
element_rect = self.get_gui_element_rect(selector, timeout=1)
|
|
2033
2185
|
e_x = element_rect["x"]
|
|
2034
2186
|
e_y = element_rect["y"]
|
|
@@ -2039,15 +2191,17 @@ class CDPMethods():
|
|
|
2039
2191
|
x = e_x + x_offset
|
|
2040
2192
|
y = e_y + y_offset
|
|
2041
2193
|
sb_config._saved_cf_x_y = (x, y)
|
|
2042
|
-
time.sleep(0.
|
|
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
|
|
2043
2197
|
if use_cdp:
|
|
2044
2198
|
self.sleep(0.03)
|
|
2045
2199
|
gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK)
|
|
2046
2200
|
with gui_lock: # Prevent issues with multiple processes
|
|
2047
2201
|
self.bring_active_window_to_front()
|
|
2048
|
-
time.sleep(0.
|
|
2202
|
+
time.sleep(0.05)
|
|
2049
2203
|
self.click_with_offset(selector, x_offset, y_offset)
|
|
2050
|
-
time.sleep(0.
|
|
2204
|
+
time.sleep(0.05)
|
|
2051
2205
|
else:
|
|
2052
2206
|
self.gui_click_x_y(x, y)
|
|
2053
2207
|
|
|
@@ -2071,13 +2225,17 @@ class CDPMethods():
|
|
|
2071
2225
|
if uc_lock:
|
|
2072
2226
|
gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK)
|
|
2073
2227
|
with gui_lock: # Prevent issues with multiple processes
|
|
2228
|
+
if "--debug" in sys.argv:
|
|
2229
|
+
print(" <DEBUG> pyautogui.moveTo(%s, %s)" % (x1, y1))
|
|
2074
2230
|
pyautogui.moveTo(x1, y1, 0.25, pyautogui.easeOutQuad)
|
|
2075
2231
|
self.__add_light_pause()
|
|
2076
2232
|
if "--debug" in sys.argv:
|
|
2077
|
-
print(" <DEBUG> pyautogui.
|
|
2233
|
+
print(" <DEBUG> pyautogui.dragTo(%s, %s)" % (x2, y2))
|
|
2078
2234
|
pyautogui.dragTo(x2, y2, button="left", duration=timeframe)
|
|
2079
2235
|
else:
|
|
2080
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))
|
|
2081
2239
|
pyautogui.moveTo(x1, y1, 0.25, pyautogui.easeOutQuad)
|
|
2082
2240
|
self.__add_light_pause()
|
|
2083
2241
|
if "--debug" in sys.argv:
|
|
@@ -2156,16 +2314,16 @@ class CDPMethods():
|
|
|
2156
2314
|
if uc_lock:
|
|
2157
2315
|
gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK)
|
|
2158
2316
|
with gui_lock: # Prevent issues with multiple processes
|
|
2159
|
-
pyautogui.moveTo(x, y, timeframe, pyautogui.easeOutQuad)
|
|
2160
|
-
time.sleep(0.056)
|
|
2161
2317
|
if "--debug" in sys.argv:
|
|
2162
2318
|
print(" <DEBUG> pyautogui.moveTo(%s, %s)" % (x, y))
|
|
2319
|
+
pyautogui.moveTo(x, y, timeframe, pyautogui.easeOutQuad)
|
|
2320
|
+
time.sleep(0.056)
|
|
2163
2321
|
else:
|
|
2164
2322
|
# Called from a method where the gui_lock is already active
|
|
2165
|
-
pyautogui.moveTo(x, y, timeframe, pyautogui.easeOutQuad)
|
|
2166
|
-
time.sleep(0.056)
|
|
2167
2323
|
if "--debug" in sys.argv:
|
|
2168
2324
|
print(" <DEBUG> pyautogui.moveTo(%s, %s)" % (x, y))
|
|
2325
|
+
pyautogui.moveTo(x, y, timeframe, pyautogui.easeOutQuad)
|
|
2326
|
+
time.sleep(0.056)
|
|
2169
2327
|
|
|
2170
2328
|
def gui_hover_x_y(self, x, y, timeframe=0.25):
|
|
2171
2329
|
gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK)
|
|
@@ -2230,7 +2388,35 @@ class CDPMethods():
|
|
|
2230
2388
|
self.__slow_mode_pause_if_set()
|
|
2231
2389
|
self.loop.run_until_complete(self.page.wait())
|
|
2232
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
|
+
|
|
2233
2416
|
def gui_hover_and_click(self, hover_selector, click_selector):
|
|
2417
|
+
if getattr(sb_config, "_cdp_mobile_mode", None):
|
|
2418
|
+
self.select(click_selector).click()
|
|
2419
|
+
return
|
|
2234
2420
|
gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK)
|
|
2235
2421
|
with gui_lock:
|
|
2236
2422
|
self.__make_sure_pyautogui_lock_is_writable()
|
|
@@ -2870,4 +3056,15 @@ class Chrome(CDPMethods):
|
|
|
2870
3056
|
driver = cdp_util.start_sync(**kwargs)
|
|
2871
3057
|
loop = asyncio.new_event_loop()
|
|
2872
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
|
|
2873
3070
|
super().__init__(loop, page, driver)
|
seleniumbase/core/sb_driver.py
CHANGED
|
@@ -12,6 +12,10 @@ from seleniumbase.fixtures import shared_utils
|
|
|
12
12
|
class DriverMethods(WebDriver):
|
|
13
13
|
def __init__(self, driver):
|
|
14
14
|
self.driver = driver
|
|
15
|
+
if hasattr(driver, "session_id"):
|
|
16
|
+
self.session_id = driver.session_id
|
|
17
|
+
if hasattr(driver, "command_executor"):
|
|
18
|
+
self.command_executor = driver.command_executor
|
|
15
19
|
|
|
16
20
|
def __is_cdp_swap_needed(self):
|
|
17
21
|
"""If the driver is disconnected, use a CDP method when available."""
|
|
@@ -37,6 +41,36 @@ class DriverMethods(WebDriver):
|
|
|
37
41
|
value, by = page_utils.swap_selector_and_by_if_reversed(value, by)
|
|
38
42
|
return self.driver.default_find_elements(by=by, value=value)
|
|
39
43
|
|
|
44
|
+
def add_cookie(self, *args, **kwargs):
|
|
45
|
+
page_actions._reconnect_if_disconnected(self.driver)
|
|
46
|
+
self.driver.default_add_cookie(*args, **kwargs)
|
|
47
|
+
|
|
48
|
+
def get_cookie(self, *args, **kwargs):
|
|
49
|
+
page_actions._reconnect_if_disconnected(self.driver)
|
|
50
|
+
self.driver.default_get_cookie(*args, **kwargs)
|
|
51
|
+
|
|
52
|
+
def delete_cookie(self, *args, **kwargs):
|
|
53
|
+
page_actions._reconnect_if_disconnected(self.driver)
|
|
54
|
+
self.driver.default_delete_cookie(*args, **kwargs)
|
|
55
|
+
|
|
56
|
+
def back(self):
|
|
57
|
+
if self.__is_cdp_swap_needed():
|
|
58
|
+
self.driver.cdp.go_back()
|
|
59
|
+
return
|
|
60
|
+
self.driver.default_back()
|
|
61
|
+
|
|
62
|
+
def forward(self):
|
|
63
|
+
if self.__is_cdp_swap_needed():
|
|
64
|
+
self.driver.cdp.go_forward()
|
|
65
|
+
return
|
|
66
|
+
self.driver.default_forward()
|
|
67
|
+
|
|
68
|
+
def refresh(self, *args, **kwargs):
|
|
69
|
+
if self.__is_cdp_swap_needed():
|
|
70
|
+
self.driver.cdp.refresh(*args, **kwargs)
|
|
71
|
+
return
|
|
72
|
+
self.driver.default_refresh()
|
|
73
|
+
|
|
40
74
|
def locator(self, selector, by=None):
|
|
41
75
|
if not by:
|
|
42
76
|
by = "css selector"
|
|
@@ -288,11 +322,8 @@ class DriverMethods(WebDriver):
|
|
|
288
322
|
selector = kwargs["selector"]
|
|
289
323
|
else:
|
|
290
324
|
selector = args[0]
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
return
|
|
294
|
-
else:
|
|
295
|
-
self.driver.connect()
|
|
325
|
+
self.driver.cdp.highlight(selector)
|
|
326
|
+
return
|
|
296
327
|
if "scroll" in kwargs:
|
|
297
328
|
kwargs.pop("scroll")
|
|
298
329
|
w_args = kwargs.copy()
|
|
@@ -3,10 +3,8 @@ from seleniumbase import config as sb_config
|
|
|
3
3
|
|
|
4
4
|
def end_reused_class_session_as_needed():
|
|
5
5
|
if (
|
|
6
|
-
|
|
7
|
-
and sb_config
|
|
8
|
-
and hasattr(sb_config, "shared_driver")
|
|
9
|
-
and sb_config.shared_driver
|
|
6
|
+
getattr(sb_config, "reuse_class_session", None)
|
|
7
|
+
and getattr(sb_config, "shared_driver", None)
|
|
10
8
|
):
|
|
11
9
|
if (
|
|
12
10
|
hasattr(sb_config.shared_driver, "service")
|
|
File without changes
|