Merge pull request #3536 from seleniumbase/cdp-mode-patch-34
CDP Mode - Patch 34
This commit is contained in:
commit
c96380404d
|
@ -465,6 +465,10 @@ sb.cdp.uncheck_if_checked(selector)
|
|||
sb.cdp.unselect_if_selected(selector)
|
||||
sb.cdp.is_element_present(selector)
|
||||
sb.cdp.is_element_visible(selector)
|
||||
sb.cdp.is_text_visible(text, selector="body")
|
||||
sb.cdp.is_exact_text_visible(text, selector="body")
|
||||
sb.cdp.wait_for_text(text, selector="body", timeout=None)
|
||||
sb.cdp.wait_for_text_not_visible(text, selector="body", timeout=None)
|
||||
sb.cdp.wait_for_element_visible(selector, timeout=None)
|
||||
sb.cdp.assert_element(selector, timeout=None)
|
||||
sb.cdp.assert_element_visible(selector, timeout=None)
|
||||
|
@ -478,6 +482,7 @@ sb.cdp.assert_url(url)
|
|||
sb.cdp.assert_url_contains(substring)
|
||||
sb.cdp.assert_text(text, selector="html", timeout=None)
|
||||
sb.cdp.assert_exact_text(text, selector="html", timeout=None)
|
||||
sb.cdp.assert_text_not_visible(text, selector="body", timeout=None)
|
||||
sb.cdp.assert_true()
|
||||
sb.cdp.assert_false()
|
||||
sb.cdp.assert_equal(first, second)
|
||||
|
@ -506,6 +511,7 @@ element.highlight_overlay()
|
|||
element.mouse_click()
|
||||
element.mouse_drag(destination)
|
||||
element.mouse_move()
|
||||
element.press_keys(text)
|
||||
element.query_selector(selector)
|
||||
element.querySelector(selector)
|
||||
element.query_selector_all(selector)
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
from seleniumbase import SB
|
||||
|
||||
with SB(uc=True, test=True, incognito=True, locale_code="en") as sb:
|
||||
url = "https://ahrefs.com/website-authority-checker"
|
||||
input_field = 'input[placeholder="Enter domain"]'
|
||||
submit_button = 'span:contains("Check Authority")'
|
||||
sb.activate_cdp_mode(url) # The bot-check is later
|
||||
sb.type(input_field, "github.com/seleniumbase/SeleniumBase")
|
||||
sb.cdp.scroll_down(36)
|
||||
sb.click(submit_button)
|
||||
sb.uc_gui_click_captcha()
|
||||
sb.wait_for_text_not_visible("Checking", timeout=15)
|
||||
sb.click_if_visible('button[data-cky-tag="close-button"]')
|
||||
sb.highlight('p:contains("github.com/seleniumbase/SeleniumBase")')
|
||||
sb.highlight('a:contains("Top 100 backlinks")')
|
||||
sb.set_messenger_theme(location="bottom_center")
|
||||
sb.post_message("SeleniumBase wasn't detected!")
|
|
@ -0,0 +1,55 @@
|
|||
from seleniumbase import SB
|
||||
|
||||
with SB(uc=True, test=True, locale_code="en") as sb:
|
||||
url = "www.elal.com/flight-deals/en-us/flights-from-boston-to-tel-aviv"
|
||||
sb.activate_cdp_mode(url)
|
||||
sb.sleep(2)
|
||||
sb.cdp.click('button[data-att="search"]')
|
||||
sb.sleep(4)
|
||||
sb.cdp.click_if_visible("#onetrust-close-btn-container button")
|
||||
sb.sleep(0.5)
|
||||
view_other_dates = 'button[aria-label*="viewOtherDates.cta"]'
|
||||
if sb.cdp.is_element_visible(view_other_dates):
|
||||
sb.cdp.click(view_other_dates)
|
||||
sb.sleep(4.5)
|
||||
if sb.is_element_visible("flexible-search-calendar"):
|
||||
print("*** Flight Calendar for El Al (Boston to Tel Aviv): ***")
|
||||
print(sb.cdp.get_text("flexible-search-calendar"))
|
||||
prices = []
|
||||
elements = sb.cdp.find_elements("span.matric-cell__content__price")
|
||||
if elements:
|
||||
print("*** Prices List: ***")
|
||||
for element in elements:
|
||||
prices.append(element.text)
|
||||
for price in sorted(prices):
|
||||
print(price)
|
||||
print("*** Lowest Price: ***")
|
||||
lowest_price = sorted(prices)[0]
|
||||
print(lowest_price)
|
||||
sb.cdp.find_element_by_text(lowest_price).click()
|
||||
sb.sleep(1)
|
||||
search_cell = 'button[aria-label*="Search.cell.buttonTitle"]'
|
||||
sb.cdp.scroll_into_view(search_cell)
|
||||
sb.sleep(1)
|
||||
sb.cdp.click(search_cell)
|
||||
sb.sleep(5)
|
||||
else:
|
||||
elements = sb.cdp.find_elements("div.ui-bound__price__value")
|
||||
print("*** Lowest Prices: ***")
|
||||
first = True
|
||||
for element in elements:
|
||||
if "lowest price" in element.text:
|
||||
if first:
|
||||
print("Departure Flight:")
|
||||
print(element.text)
|
||||
first = False
|
||||
else:
|
||||
print("Return Flight:")
|
||||
print(element.text)
|
||||
break
|
||||
dates = sb.cdp.find_elements('div[class*="flight-date"]')
|
||||
if len(dates) == 2:
|
||||
print("*** Departure Date: ***")
|
||||
print(dates[0].text)
|
||||
print("*** Return Date: ***")
|
||||
print(dates[1].text)
|
|
@ -9,7 +9,7 @@ with SB(uc=True, test=True, incognito=True, locale_code="en") as sb:
|
|||
sb.reconnect(0.1)
|
||||
sb.uc_click(submit_button, reconnect_time=3.25)
|
||||
sb.uc_gui_click_captcha()
|
||||
sb.wait_for_text_not_visible("Checking", timeout=11.5)
|
||||
sb.wait_for_text_not_visible("Checking", timeout=15)
|
||||
sb.click_if_visible('button[data-cky-tag="close-button"]')
|
||||
sb.highlight('p:contains("github.com/seleniumbase/SeleniumBase")')
|
||||
sb.highlight('a:contains("Top 100 backlinks")')
|
||||
|
|
|
@ -7,7 +7,7 @@ attrs>=25.1.0
|
|||
certifi>=2025.1.31
|
||||
exceptiongroup>=1.2.2
|
||||
websockets~=13.1;python_version<"3.9"
|
||||
websockets>=14.2;python_version>="3.9"
|
||||
websockets>=15.0;python_version>="3.9"
|
||||
filelock~=3.16.1;python_version<"3.9"
|
||||
filelock>=3.17.0;python_version>="3.9"
|
||||
fasteners>=0.19
|
||||
|
@ -38,8 +38,8 @@ sniffio==1.3.1
|
|||
h11==0.14.0
|
||||
outcome==1.3.0.post0
|
||||
trio==0.27.0;python_version<"3.9"
|
||||
trio==0.28.0;python_version>="3.9"
|
||||
trio-websocket==0.11.1
|
||||
trio==0.29.0;python_version>="3.9"
|
||||
trio-websocket==0.12.1
|
||||
wsproto==1.2.0
|
||||
websocket-client==1.8.0
|
||||
selenium==4.27.1;python_version<"3.9"
|
||||
|
@ -74,7 +74,7 @@ coverage>=7.6.12;python_version>="3.9"
|
|||
pytest-cov>=5.0.0;python_version<"3.9"
|
||||
pytest-cov>=6.0.0;python_version>="3.9"
|
||||
flake8==5.0.4;python_version<"3.9"
|
||||
flake8==7.1.1;python_version>="3.9"
|
||||
flake8==7.1.2;python_version>="3.9"
|
||||
mccabe==0.7.0
|
||||
pyflakes==2.5.0;python_version<"3.9"
|
||||
pyflakes==3.2.0;python_version>="3.9"
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
# seleniumbase package
|
||||
__version__ = "4.34.15"
|
||||
__version__ = "4.34.16"
|
||||
|
|
|
@ -718,6 +718,10 @@ def uc_open_with_cdp_mode(driver, url=None):
|
|||
cdp.is_selected = CDPM.is_selected
|
||||
cdp.is_element_present = CDPM.is_element_present
|
||||
cdp.is_element_visible = CDPM.is_element_visible
|
||||
cdp.is_text_visible = CDPM.is_text_visible
|
||||
cdp.is_exact_text_visible = CDPM.is_exact_text_visible
|
||||
cdp.wait_for_text = CDPM.wait_for_text
|
||||
cdp.wait_for_text_not_visible = CDPM.wait_for_text_not_visible
|
||||
cdp.wait_for_element_visible = CDPM.wait_for_element_visible
|
||||
cdp.assert_element = CDPM.assert_element
|
||||
cdp.assert_element_visible = CDPM.assert_element_visible
|
||||
|
@ -731,6 +735,7 @@ def uc_open_with_cdp_mode(driver, url=None):
|
|||
cdp.assert_url_contains = CDPM.assert_url_contains
|
||||
cdp.assert_text = CDPM.assert_text
|
||||
cdp.assert_exact_text = CDPM.assert_exact_text
|
||||
cdp.assert_text_not_visible = CDPM.assert_text_not_visible
|
||||
cdp.assert_true = CDPM.assert_true
|
||||
cdp.assert_false = CDPM.assert_false
|
||||
cdp.assert_equal = CDPM.assert_equal
|
||||
|
@ -2280,6 +2285,7 @@ def _set_chrome_options(
|
|||
or proxy_string
|
||||
):
|
||||
chrome_options.add_argument("--ignore-certificate-errors")
|
||||
chrome_options.add_argument("--ignore-ssl-errors=yes")
|
||||
if not enable_ws:
|
||||
chrome_options.add_argument("--disable-web-security")
|
||||
if (
|
||||
|
@ -4231,6 +4237,7 @@ def get_local_driver(
|
|||
edge_options.add_argument("--log-level=3")
|
||||
edge_options.add_argument("--no-first-run")
|
||||
edge_options.add_argument("--ignore-certificate-errors")
|
||||
edge_options.add_argument("--ignore-ssl-errors=yes")
|
||||
if devtools and not headless:
|
||||
edge_options.add_argument("--auto-open-devtools-for-tabs")
|
||||
edge_options.add_argument("--allow-file-access-from-files")
|
||||
|
|
|
@ -62,6 +62,7 @@ class CDPMethods():
|
|||
lambda destination: self.__mouse_drag(element, destination)
|
||||
)
|
||||
element.mouse_move = lambda: self.__mouse_move(element)
|
||||
element.press_keys = lambda text: self.__press_keys(element, text)
|
||||
element.query_selector = (
|
||||
lambda selector: self.__query_selector(element, selector)
|
||||
)
|
||||
|
@ -211,7 +212,8 @@ class CDPMethods():
|
|||
element = self.__add_sync_methods(element.parent)
|
||||
return self.__add_sync_methods(element)
|
||||
elif (
|
||||
element.parent.parent
|
||||
element.parent
|
||||
and element.parent.parent
|
||||
and tag_name in element.parent.parent.tag_name.lower()
|
||||
and text.strip() in element.parent.parent.text
|
||||
):
|
||||
|
@ -272,7 +274,8 @@ class CDPMethods():
|
|||
if element not in updated_elements:
|
||||
updated_elements.append(element)
|
||||
elif (
|
||||
element.parent.parent
|
||||
element.parent
|
||||
and element.parent.parent
|
||||
and tag_name in element.parent.parent.tag_name.lower()
|
||||
and text.strip() in element.parent.parent.text
|
||||
):
|
||||
|
@ -445,6 +448,23 @@ class CDPMethods():
|
|||
self.loop.run_until_complete(element.mouse_move_async())
|
||||
)
|
||||
|
||||
def __press_keys(self, element, text):
|
||||
element.scroll_into_view()
|
||||
submit = False
|
||||
if text.endswith("\n") or text.endswith("\r"):
|
||||
submit = True
|
||||
text = text[:-1]
|
||||
for key in text:
|
||||
element.send_keys(key)
|
||||
time.sleep(0.044)
|
||||
if submit:
|
||||
element.send_keys("\r\n")
|
||||
time.sleep(0.044)
|
||||
self.__slow_mode_pause_if_set()
|
||||
return (
|
||||
self.loop.run_until_complete(self.page.wait())
|
||||
)
|
||||
|
||||
def __query_selector(self, element, selector):
|
||||
selector = self.__convert_to_css_if_xpath(selector)
|
||||
element2 = self.loop.run_until_complete(
|
||||
|
@ -1681,6 +1701,78 @@ class CDPMethods():
|
|||
return True
|
||||
return False
|
||||
|
||||
def is_text_visible(self, text, selector="body"):
|
||||
selector = self.__convert_to_css_if_xpath(selector)
|
||||
text = text.strip()
|
||||
element = None
|
||||
try:
|
||||
element = self.find_element(selector, timeout=0.1)
|
||||
except Exception:
|
||||
return False
|
||||
with suppress(Exception):
|
||||
if text in element.text_all:
|
||||
return True
|
||||
return False
|
||||
|
||||
def is_exact_text_visible(self, text, selector="body"):
|
||||
selector = self.__convert_to_css_if_xpath(selector)
|
||||
text = text.strip()
|
||||
element = None
|
||||
try:
|
||||
element = self.find_element(selector, timeout=0.1)
|
||||
except Exception:
|
||||
return False
|
||||
with suppress(Exception):
|
||||
if text == element.text_all.strip():
|
||||
return True
|
||||
return False
|
||||
|
||||
def wait_for_text(self, text, selector="body", timeout=None):
|
||||
if not timeout:
|
||||
timeout = settings.SMALL_TIMEOUT
|
||||
start_ms = time.time() * 1000.0
|
||||
stop_ms = start_ms + (timeout * 1000.0)
|
||||
text = text.strip()
|
||||
element = None
|
||||
try:
|
||||
element = self.find_element(selector, timeout=timeout)
|
||||
except Exception:
|
||||
raise Exception("Element {%s} not found!" % selector)
|
||||
for i in range(int(timeout * 10)):
|
||||
with suppress(Exception):
|
||||
element = self.find_element(selector, timeout=0.1)
|
||||
if text in element.text_all:
|
||||
return True
|
||||
now_ms = time.time() * 1000.0
|
||||
if now_ms >= stop_ms:
|
||||
break
|
||||
time.sleep(0.1)
|
||||
raise Exception(
|
||||
"Text {%s} not found in {%s}! Actual text: {%s}"
|
||||
% (text, selector, element.text_all)
|
||||
)
|
||||
|
||||
def wait_for_text_not_visible(self, text, selector="body", timeout=None):
|
||||
if not timeout:
|
||||
timeout = settings.SMALL_TIMEOUT
|
||||
text = text.strip()
|
||||
start_ms = time.time() * 1000.0
|
||||
stop_ms = start_ms + (timeout * 1000.0)
|
||||
for i in range(int(timeout * 10)):
|
||||
if not self.is_text_visible(text, selector):
|
||||
return True
|
||||
now_ms = time.time() * 1000.0
|
||||
if now_ms >= stop_ms:
|
||||
break
|
||||
time.sleep(0.1)
|
||||
plural = "s"
|
||||
if timeout == 1:
|
||||
plural = ""
|
||||
raise Exception(
|
||||
"Text {%s} in {%s} was still visible after %s second%s!"
|
||||
% (text, selector, timeout, plural)
|
||||
)
|
||||
|
||||
def wait_for_element_visible(self, selector, timeout=None):
|
||||
if not timeout:
|
||||
timeout = settings.SMALL_TIMEOUT
|
||||
|
@ -1696,17 +1788,8 @@ class CDPMethods():
|
|||
|
||||
def assert_element(self, selector, timeout=None):
|
||||
"""Same as assert_element_visible()"""
|
||||
if not timeout:
|
||||
timeout = settings.SMALL_TIMEOUT
|
||||
try:
|
||||
self.select(selector, timeout=timeout)
|
||||
except Exception:
|
||||
raise Exception("Element {%s} was not found!" % selector)
|
||||
for i in range(30):
|
||||
if self.is_element_visible(selector):
|
||||
return True
|
||||
time.sleep(0.1)
|
||||
raise Exception("Element {%s} was not visible!" % selector)
|
||||
self.assert_element_visible(selector, timeout=timeout)
|
||||
return True
|
||||
|
||||
def assert_element_visible(self, selector, timeout=None):
|
||||
"""Same as assert_element()"""
|
||||
|
@ -1852,29 +1935,9 @@ class CDPMethods():
|
|||
raise Exception(error % (expected, actual))
|
||||
|
||||
def assert_text(self, text, selector="body", timeout=None):
|
||||
if not timeout:
|
||||
timeout = settings.SMALL_TIMEOUT
|
||||
start_ms = time.time() * 1000.0
|
||||
stop_ms = start_ms + (timeout * 1000.0)
|
||||
text = text.strip()
|
||||
element = None
|
||||
try:
|
||||
element = self.find_element(selector, timeout=timeout)
|
||||
except Exception:
|
||||
raise Exception("Element {%s} not found!" % selector)
|
||||
for i in range(int(timeout * 10)):
|
||||
with suppress(Exception):
|
||||
element = self.find_element(selector, timeout=0.1)
|
||||
if text in element.text_all:
|
||||
return True
|
||||
now_ms = time.time() * 1000.0
|
||||
if now_ms >= stop_ms:
|
||||
break
|
||||
time.sleep(0.1)
|
||||
raise Exception(
|
||||
"Text {%s} not found in {%s}! Actual text: {%s}"
|
||||
% (text, selector, element.text_all)
|
||||
)
|
||||
"""Same as wait_for_text()"""
|
||||
self.wait_for_text(text, selector=selector, timeout=timeout)
|
||||
return True
|
||||
|
||||
def assert_exact_text(self, text, selector="body", timeout=None):
|
||||
if not timeout:
|
||||
|
@ -1904,6 +1967,13 @@ class CDPMethods():
|
|||
% (text, element.text_all, selector)
|
||||
)
|
||||
|
||||
def assert_text_not_visible(self, text, selector="body", timeout=None):
|
||||
"""Raises an exception if the text is still visible after timeout."""
|
||||
self.wait_for_text_not_visible(
|
||||
text, selector=selector, timeout=timeout
|
||||
)
|
||||
return True
|
||||
|
||||
def assert_true(self, expression):
|
||||
if not expression:
|
||||
raise AssertionError("%s is not true" % expression)
|
||||
|
|
|
@ -1454,6 +1454,8 @@ class BaseCase(unittest.TestCase):
|
|||
|
||||
def is_text_visible(self, text, selector="body", by="css selector"):
|
||||
"""Returns whether the text substring is visible in the element."""
|
||||
if self.__is_cdp_swap_needed():
|
||||
return self.cdp.is_text_visible(text, selector)
|
||||
self.wait_for_ready_state_complete()
|
||||
time.sleep(0.01)
|
||||
selector, by = self.__recalculate_selector(selector, by)
|
||||
|
@ -1464,6 +1466,8 @@ class BaseCase(unittest.TestCase):
|
|||
def is_exact_text_visible(self, text, selector="body", by="css selector"):
|
||||
"""Returns whether the text is exactly equal to the element text.
|
||||
(Leading and trailing whitespace is ignored in the verification.)"""
|
||||
if self.__is_cdp_swap_needed():
|
||||
return self.cdp.is_exact_text_visible(text, selector)
|
||||
self.wait_for_ready_state_complete()
|
||||
time.sleep(0.01)
|
||||
selector, by = self.__recalculate_selector(selector, by)
|
||||
|
@ -9281,7 +9285,8 @@ class BaseCase(unittest.TestCase):
|
|||
"bottom_left", "bottom_center", "bottom_right"]
|
||||
max_messages: The limit of concurrent messages to display."""
|
||||
self.__check_scope()
|
||||
self._check_browser()
|
||||
if not self.__is_cdp_swap_needed():
|
||||
self._check_browser()
|
||||
if not theme:
|
||||
theme = "default" # "flat"
|
||||
if not location:
|
||||
|
@ -9308,7 +9313,8 @@ class BaseCase(unittest.TestCase):
|
|||
You can also post messages by using =>
|
||||
self.execute_script('Messenger().post("My Message")') """
|
||||
self.__check_scope()
|
||||
self._check_browser()
|
||||
if not self.__is_cdp_swap_needed():
|
||||
self._check_browser()
|
||||
if style not in ["info", "success", "error"]:
|
||||
style = "info"
|
||||
if not duration:
|
||||
|
@ -10326,6 +10332,10 @@ class BaseCase(unittest.TestCase):
|
|||
if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:
|
||||
timeout = self.__get_new_timeout(timeout)
|
||||
selector, by = self.__recalculate_selector(selector, by)
|
||||
if self.__is_cdp_swap_needed():
|
||||
return self.cdp.wait_for_text(
|
||||
text, selector=selector, timeout=timeout
|
||||
)
|
||||
return page_actions.wait_for_text_not_visible(
|
||||
self.driver, text, selector, by, timeout
|
||||
)
|
||||
|
@ -13909,7 +13919,8 @@ class BaseCase(unittest.TestCase):
|
|||
js_utils.highlight_element_with_js(self.driver, element, loops, o_bs)
|
||||
|
||||
def __highlight_with_jquery(self, selector, loops, o_bs):
|
||||
self.wait_for_ready_state_complete()
|
||||
if not self.__is_cdp_swap_needed():
|
||||
self.wait_for_ready_state_complete()
|
||||
js_utils.highlight_with_jquery(self.driver, selector, loops, o_bs)
|
||||
|
||||
def __highlight_with_js_2(self, message, selector, o_bs):
|
||||
|
|
|
@ -585,52 +585,60 @@ def highlight_with_jquery(driver, selector, loops=4, o_bs=""):
|
|||
'0px 0px 6px 6px rgba(128, 128, 128, 0.5)');"""
|
||||
% selector
|
||||
)
|
||||
safe_execute_script(driver, script)
|
||||
with suppress(Exception):
|
||||
safe_execute_script(driver, script)
|
||||
for n in range(loops):
|
||||
script = (
|
||||
"""jQuery('%s').css('box-shadow',
|
||||
'0px 0px 6px 6px rgba(255, 0, 0, 1)');"""
|
||||
% selector
|
||||
)
|
||||
execute_script(driver, script)
|
||||
with suppress(Exception):
|
||||
execute_script(driver, script)
|
||||
time.sleep(0.0181)
|
||||
script = (
|
||||
"""jQuery('%s').css('box-shadow',
|
||||
'0px 0px 6px 6px rgba(128, 0, 128, 1)');"""
|
||||
% selector
|
||||
)
|
||||
execute_script(driver, script)
|
||||
with suppress(Exception):
|
||||
execute_script(driver, script)
|
||||
time.sleep(0.0181)
|
||||
script = (
|
||||
"""jQuery('%s').css('box-shadow',
|
||||
'0px 0px 6px 6px rgba(0, 0, 255, 1)');"""
|
||||
% selector
|
||||
)
|
||||
execute_script(driver, script)
|
||||
with suppress(Exception):
|
||||
execute_script(driver, script)
|
||||
time.sleep(0.0181)
|
||||
script = (
|
||||
"""jQuery('%s').css('box-shadow',
|
||||
'0px 0px 6px 6px rgba(0, 255, 0, 1)');"""
|
||||
% selector
|
||||
)
|
||||
execute_script(driver, script)
|
||||
with suppress(Exception):
|
||||
execute_script(driver, script)
|
||||
time.sleep(0.0181)
|
||||
script = (
|
||||
"""jQuery('%s').css('box-shadow',
|
||||
'0px 0px 6px 6px rgba(128, 128, 0, 1)');"""
|
||||
% selector
|
||||
)
|
||||
execute_script(driver, script)
|
||||
with suppress(Exception):
|
||||
execute_script(driver, script)
|
||||
time.sleep(0.0181)
|
||||
script = (
|
||||
"""jQuery('%s').css('box-shadow',
|
||||
'0px 0px 6px 6px rgba(128, 0, 128, 1)');"""
|
||||
% selector
|
||||
)
|
||||
execute_script(driver, script)
|
||||
with suppress(Exception):
|
||||
execute_script(driver, script)
|
||||
time.sleep(0.0181)
|
||||
script = """jQuery('%s').css('box-shadow', '%s');""" % (selector, o_bs)
|
||||
execute_script(driver, script)
|
||||
with suppress(Exception):
|
||||
execute_script(driver, script)
|
||||
|
||||
|
||||
def add_css_link(driver, css_link):
|
||||
|
@ -924,9 +932,20 @@ def post_message(driver, message, msg_dur=None, style="info"):
|
|||
"""hideAfter: %s, hideOnNavigate: true});"""
|
||||
% (message, style, msg_dur)
|
||||
)
|
||||
retry = False
|
||||
try:
|
||||
execute_script(driver, messenger_script)
|
||||
except TypeError as e:
|
||||
if (
|
||||
shared_utils.is_cdp_swap_needed(driver)
|
||||
and "cannot unpack non-iterable" in str(e)
|
||||
):
|
||||
pass
|
||||
else:
|
||||
retry = True
|
||||
except Exception:
|
||||
retry = True
|
||||
if retry:
|
||||
activate_messenger(driver)
|
||||
set_messenger_theme(driver)
|
||||
try:
|
||||
|
@ -1273,7 +1292,10 @@ def slow_scroll_to_element(driver, element, *args, **kwargs):
|
|||
scroll_position = execute_script(driver, "return window.scrollY;")
|
||||
element_location_y = None
|
||||
try:
|
||||
element_location_y = element.location["y"]
|
||||
if shared_utils.is_cdp_swap_needed(driver):
|
||||
element.get_position().y
|
||||
else:
|
||||
element_location_y = element.location["y"]
|
||||
except Exception:
|
||||
element.location_once_scrolled_into_view
|
||||
return
|
||||
|
|
|
@ -265,6 +265,8 @@ class Browser:
|
|||
:param new_window: Open new window
|
||||
:return: Page
|
||||
"""
|
||||
if url and ":" not in url:
|
||||
url = "https://" + url
|
||||
if new_tab or new_window:
|
||||
# Create new target using the browser session.
|
||||
target_id = await self.connection.send(
|
||||
|
|
|
@ -4,6 +4,7 @@ import asyncio
|
|||
import fasteners
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import types
|
||||
import typing
|
||||
|
@ -11,6 +12,7 @@ from contextlib import suppress
|
|||
from seleniumbase import config as sb_config
|
||||
from seleniumbase.config import settings
|
||||
from seleniumbase.core import detect_b_ver
|
||||
from seleniumbase.core import proxy_helper
|
||||
from seleniumbase.fixtures import constants
|
||||
from seleniumbase.fixtures import shared_utils
|
||||
from typing import Optional, List, Union, Callable
|
||||
|
@ -23,6 +25,7 @@ import mycdp as cdp
|
|||
|
||||
logger = logging.getLogger(__name__)
|
||||
IS_LINUX = shared_utils.is_linux()
|
||||
PROXY_DIR_LOCK = proxy_helper.PROXY_DIR_LOCK
|
||||
T = typing.TypeVar("T")
|
||||
|
||||
|
||||
|
@ -139,6 +142,85 @@ def __activate_virtual_display_as_needed(
|
|||
__activate_standard_virtual_display()
|
||||
|
||||
|
||||
def __set_proxy_filenames():
|
||||
DOWNLOADS_DIR = constants.Files.DOWNLOADS_FOLDER
|
||||
for num in range(1000):
|
||||
PROXY_DIR_PATH = os.path.join(DOWNLOADS_DIR, "proxy_ext_dir_%s" % num)
|
||||
if os.path.exists(PROXY_DIR_PATH):
|
||||
continue
|
||||
proxy_helper.PROXY_DIR_PATH = PROXY_DIR_PATH
|
||||
return
|
||||
# Exceeded upper bound. Use Defaults:
|
||||
PROXY_DIR_PATH = os.path.join(DOWNLOADS_DIR, "proxy_ext_dir")
|
||||
proxy_helper.PROXY_DIR_PATH = PROXY_DIR_PATH
|
||||
|
||||
|
||||
def __add_chrome_ext_dir(extension_dir, dir_path):
|
||||
# Add dir_path to the existing extension_dir
|
||||
option_exists = False
|
||||
if extension_dir:
|
||||
option_exists = True
|
||||
extension_dir = "%s,%s" % (
|
||||
extension_dir, os.path.realpath(dir_path)
|
||||
)
|
||||
if not option_exists:
|
||||
extension_dir = os.path.realpath(dir_path)
|
||||
return extension_dir
|
||||
|
||||
|
||||
def __add_chrome_proxy_extension(
|
||||
extension_dir,
|
||||
proxy_string,
|
||||
proxy_user,
|
||||
proxy_pass,
|
||||
proxy_bypass_list=None,
|
||||
multi_proxy=False,
|
||||
):
|
||||
"""Implementation of https://stackoverflow.com/a/35293284/7058266
|
||||
for https://stackoverflow.com/q/12848327/7058266
|
||||
(Run Selenium on a proxy server that requires authentication.)"""
|
||||
args = " ".join(sys.argv)
|
||||
bypass_list = proxy_bypass_list
|
||||
if (
|
||||
not ("-n" in sys.argv or " -n=" in args or args == "-c")
|
||||
and not multi_proxy
|
||||
):
|
||||
# Single-threaded
|
||||
proxy_dir_lock = fasteners.InterProcessLock(PROXY_DIR_LOCK)
|
||||
with proxy_dir_lock:
|
||||
proxy_helper.create_proxy_ext(
|
||||
proxy_string,
|
||||
proxy_user,
|
||||
proxy_pass,
|
||||
bypass_list,
|
||||
zip_it=False,
|
||||
)
|
||||
proxy_dir_path = proxy_helper.PROXY_DIR_PATH
|
||||
extension_dir = __add_chrome_ext_dir(
|
||||
extension_dir, proxy_dir_path
|
||||
)
|
||||
else:
|
||||
# Multi-threaded
|
||||
proxy_dir_lock = fasteners.InterProcessLock(PROXY_DIR_LOCK)
|
||||
with proxy_dir_lock:
|
||||
with suppress(Exception):
|
||||
shared_utils.make_writable(PROXY_DIR_LOCK)
|
||||
if multi_proxy:
|
||||
__set_proxy_filenames()
|
||||
if not os.path.exists(proxy_helper.PROXY_DIR_PATH):
|
||||
proxy_helper.create_proxy_ext(
|
||||
proxy_string,
|
||||
proxy_user,
|
||||
proxy_pass,
|
||||
bypass_list,
|
||||
zip_it=False,
|
||||
)
|
||||
extension_dir = __add_chrome_ext_dir(
|
||||
extension_dir, proxy_helper.PROXY_DIR_PATH
|
||||
)
|
||||
return extension_dir
|
||||
|
||||
|
||||
async def start(
|
||||
config: Optional[Config] = None,
|
||||
*,
|
||||
|
@ -156,6 +238,8 @@ async def start(
|
|||
xvfb: Optional[int] = None, # Use a special virtual display on Linux
|
||||
headed: Optional[bool] = None, # Override default Xvfb mode on Linux
|
||||
expert: Optional[bool] = None, # Open up closed Shadow-root elements
|
||||
proxy: Optional[str] = None, # "host:port" or "user:pass@host:port"
|
||||
extension_dir: Optional[str] = None, # Chrome extension directory
|
||||
**kwargs: Optional[dict],
|
||||
) -> Browser:
|
||||
"""
|
||||
|
@ -200,6 +284,18 @@ async def start(
|
|||
if IS_LINUX and not headless and not headed and not xvfb:
|
||||
xvfb = True # The default setting on Linux
|
||||
__activate_virtual_display_as_needed(headless, headed, xvfb, xvfb_metrics)
|
||||
if proxy and "@" in str(proxy):
|
||||
user_with_pass = proxy.split("@")[0]
|
||||
if ":" in user_with_pass:
|
||||
proxy_user = user_with_pass.split(":")[0]
|
||||
proxy_pass = user_with_pass.split(":")[1]
|
||||
proxy_string = proxy.split("@")[1]
|
||||
extension_dir = __add_chrome_proxy_extension(
|
||||
extension_dir,
|
||||
proxy_string,
|
||||
proxy_user,
|
||||
proxy_pass,
|
||||
)
|
||||
if not config:
|
||||
config = Config(
|
||||
user_data_dir,
|
||||
|
@ -213,13 +309,19 @@ async def start(
|
|||
host=host,
|
||||
port=port,
|
||||
expert=expert,
|
||||
proxy=proxy,
|
||||
extension_dir=extension_dir,
|
||||
**kwargs,
|
||||
)
|
||||
driver = None
|
||||
try:
|
||||
return await Browser.create(config)
|
||||
driver = await Browser.create(config)
|
||||
except Exception:
|
||||
time.sleep(0.15)
|
||||
return await Browser.create(config)
|
||||
driver = await Browser.create(config)
|
||||
if proxy and "@" in str(proxy):
|
||||
time.sleep(0.11)
|
||||
return driver
|
||||
|
||||
|
||||
async def start_async(*args, **kwargs) -> Browser:
|
||||
|
|
|
@ -40,6 +40,8 @@ class Config:
|
|||
host: str = AUTO,
|
||||
port: int = AUTO,
|
||||
expert: bool = AUTO,
|
||||
proxy: Optional[str] = None,
|
||||
extension_dir: Optional[str] = None,
|
||||
**kwargs: dict,
|
||||
):
|
||||
"""
|
||||
|
@ -91,6 +93,8 @@ class Config:
|
|||
self.host = host
|
||||
self.port = port
|
||||
self.expert = expert
|
||||
self.proxy = proxy
|
||||
self.extension_dir = extension_dir
|
||||
self._extensions = []
|
||||
# When using posix-ish operating system and running as root,
|
||||
# you must use no_sandbox=True
|
||||
|
@ -195,6 +199,12 @@ class Config:
|
|||
"--disable-web-security",
|
||||
"--disable-site-isolation-trials",
|
||||
]
|
||||
if self.proxy:
|
||||
args.append("--proxy-server=%s" % self.proxy.split("@")[-1])
|
||||
args.append("--ignore-certificate-errors")
|
||||
args.append("--ignore-ssl-errors=yes")
|
||||
if self.extension_dir:
|
||||
args.append("--load-extension=%s" % self.extension_dir)
|
||||
if self._browser_args:
|
||||
args.extend([arg for arg in self._browser_args if arg not in args])
|
||||
if self.headless:
|
||||
|
|
10
setup.py
10
setup.py
|
@ -34,7 +34,7 @@ if sys.argv[-1] == "publish":
|
|||
print("\nERROR! Publishing to PyPI requires Python>=3.9")
|
||||
sys.exit()
|
||||
print("\n*** Checking code health with flake8:\n")
|
||||
os.system("python -m pip install 'flake8==7.1.1'")
|
||||
os.system("python -m pip install 'flake8==7.1.2'")
|
||||
flake8_status = os.system("flake8 --exclude=recordings,temp")
|
||||
if flake8_status != 0:
|
||||
print("\nERROR! Fix flake8 issues before publishing to PyPI!\n")
|
||||
|
@ -156,7 +156,7 @@ setup(
|
|||
"certifi>=2025.1.31",
|
||||
"exceptiongroup>=1.2.2",
|
||||
'websockets~=13.1;python_version<"3.9"',
|
||||
'websockets>=14.2;python_version>="3.9"',
|
||||
'websockets>=15.0;python_version>="3.9"',
|
||||
'filelock~=3.16.1;python_version<"3.9"',
|
||||
'filelock>=3.17.0;python_version>="3.9"',
|
||||
'fasteners>=0.19',
|
||||
|
@ -187,8 +187,8 @@ setup(
|
|||
'h11==0.14.0',
|
||||
'outcome==1.3.0.post0',
|
||||
'trio==0.27.0;python_version<"3.9"',
|
||||
'trio==0.28.0;python_version>="3.9"',
|
||||
'trio-websocket==0.11.1',
|
||||
'trio==0.29.0;python_version>="3.9"',
|
||||
'trio-websocket==0.12.1',
|
||||
'wsproto==1.2.0',
|
||||
'websocket-client==1.8.0',
|
||||
'selenium==4.27.1;python_version<"3.9"',
|
||||
|
@ -236,7 +236,7 @@ setup(
|
|||
# Usage: flake8
|
||||
"flake8": [
|
||||
'flake8==5.0.4;python_version<"3.9"',
|
||||
'flake8==7.1.1;python_version>="3.9"',
|
||||
'flake8==7.1.2;python_version>="3.9"',
|
||||
"mccabe==0.7.0",
|
||||
'pyflakes==2.5.0;python_version<"3.9"',
|
||||
'pyflakes==3.2.0;python_version>="3.9"',
|
||||
|
|
Loading…
Reference in New Issue