Merge pull request #3536 from seleniumbase/cdp-mode-patch-34

CDP Mode - Patch 34
This commit is contained in:
Michael Mintz 2025-02-19 01:09:30 -05:00 committed by GitHub
commit c96380404d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 363 additions and 61 deletions

View File

@ -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)

View File

@ -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!")

View File

@ -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)

View File

@ -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")')

View File

@ -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"

View File

@ -1,2 +1,2 @@
# seleniumbase package
__version__ = "4.34.15"
__version__ = "4.34.16"

View File

@ -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")

View File

@ -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)

View File

@ -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):

View File

@ -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

View File

@ -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(

View File

@ -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:

View File

@ -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:

View File

@ -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"',