Compare commits

...

23 Commits

Author SHA1 Message Date
Michael Mintz b9a89f7a26
Merge pull request #3668 from seleniumbase/gui-click-and-hold
Add `sb.cdp.gui_click_and_hold(selector, timeframe)`
2025-04-10 18:58:17 -04:00
Michael Mintz 4fa959b718 Version 4.37.2 2025-04-10 18:54:10 -04:00
Michael Mintz c80b256e34 Refresh Python dependencies 2025-04-10 18:53:59 -04:00
Michael Mintz dba82c0bda Add `sb.cdp.gui_click_and_hold(selector, timeframe)` 2025-04-10 18:53:26 -04:00
Michael Mintz 990701d9ee
Merge pull request #3655 from seleniumbase/fix-cdp-mode-issues
Fix CDP Mode issues
2025-04-07 16:30:39 -04:00
Michael Mintz 79e38de0b1 Version 4.37.1 2025-04-07 16:26:45 -04:00
Michael Mintz a68f33ce0d Fix CDP Mode issues 2025-04-07 16:26:33 -04:00
Michael Mintz 004f22ffbd Update an example 2025-04-07 16:21:53 -04:00
Michael Mintz 94fd008a2a
Merge pull request #3653 from seleniumbase/selenium-update
Selenium update
2025-04-06 20:54:55 -04:00
Michael Mintz 0f3b0e9fe5 Version 4.37.0 2025-04-06 20:50:27 -04:00
Michael Mintz 74b57669ff Refresh Python dependencies 2025-04-06 20:50:04 -04:00
Michael Mintz 9c96a8ca83
Merge pull request #3649 from seleniumbase/refresh-dependencies
Refresh dependencies
2025-04-03 23:42:06 -04:00
Michael Mintz ce43535a53 Version 4.36.5 2025-04-03 23:38:07 -04:00
Michael Mintz 0da7d0fbca Refresh Python dependencies 2025-04-03 23:37:49 -04:00
Michael Mintz 9c48161ef1 Update CDP ReadMe 2025-04-03 23:30:27 -04:00
Michael Mintz 30815fe879 Refresh mkdocs dependencies 2025-04-03 23:28:47 -04:00
Michael Mintz d18bbf1c0d Update comments 2025-04-03 23:28:02 -04:00
Michael Mintz 05b8385994 Update flake8 2025-04-03 23:27:12 -04:00
Michael Mintz e5558c4c8a Update the ReadMe 2025-03-31 14:14:12 -04:00
Michael Mintz f30325e575 Update the ReadMe 2025-03-31 12:08:02 -04:00
Michael Mintz 808b252f47 Update a presentation 2025-03-31 12:07:52 -04:00
Michael Mintz ab9d898d56 Update timing 2025-03-31 12:07:34 -04:00
Michael Mintz a2817abcd8 Update an example 2025-03-29 11:45:50 -04:00
16 changed files with 181 additions and 55 deletions

View File

@ -13,7 +13,7 @@
<p align="center" class="hero__title"><b>All-in-one Browser Automation Framework:<br />Web Crawling / Testing / Scraping / Stealth</b></p>
<p align="center"><a href="https://pypi.python.org/pypi/seleniumbase" target="_blank"><img src="https://img.shields.io/pypi/v/seleniumbase.svg?color=3399EE" alt="PyPI version" /></a> <a href="https://github.com/seleniumbase/SeleniumBase/releases" target="_blank"><img src="https://img.shields.io/github/v/release/seleniumbase/SeleniumBase.svg?color=22AAEE" alt="GitHub version" /></a> <a href="https://seleniumbase.io"><img src="https://img.shields.io/badge/docs-seleniumbase.io-11BBAA.svg" alt="SeleniumBase Docs" /></a> <a href="https://github.com/seleniumbase/SeleniumBase/actions" target="_blank"><img src="https://github.com/seleniumbase/SeleniumBase/workflows/CI%20build/badge.svg" alt="SeleniumBase GitHub Actions" /></a> <a href="https://discord.gg/EdhQTn3EyE" target="_blank"><img src="https://img.shields.io/badge/join-discord-infomational" alt="Join the SeleniumBase chat on Discord"/></a></p>
<p align="center"><a href="https://pypi.python.org/pypi/seleniumbase" target="_blank"><img src="https://img.shields.io/pypi/v/seleniumbase.svg?color=3399EE" alt="PyPI version" /></a> <a href="https://github.com/seleniumbase/SeleniumBase/releases" target="_blank"><img src="https://img.shields.io/github/v/release/seleniumbase/SeleniumBase.svg?color=22AAEE" alt="GitHub version" /></a> <a href="https://seleniumbase.io"><img src="https://img.shields.io/badge/docs-seleniumbase.io-11BBAA.svg" alt="SeleniumBase Docs" /></a> <a href="https://github.com/seleniumbase/SeleniumBase/actions" target="_blank"><img src="https://github.com/seleniumbase/SeleniumBase/workflows/CI%20build/badge.svg" alt="SeleniumBase GitHub Actions" /></a> <a href="https://discord.gg/EdhQTn3EyE" target="_blank"><img src="https://img.shields.io/discord/727927627830001734?color=7289DA&label=Discord&logo=discord&logoColor=white"/></a></p>
<p align="center">
<a href="#python_installation">🚀 Start</a> |
@ -1391,6 +1391,6 @@ pytest --reruns=1 --reruns-delay=1
<div><a href="https://github.com/seleniumbase/SeleniumBase/"><img src="https://seleniumbase.github.io/cdn/img/super_logo_sb3.png" title="SeleniumBase" width="274" /></a></div>
<div><a href="https://seleniumbase.io"><img src="https://img.shields.io/badge/docs-seleniumbase.io-11BBAA.svg" alt="SeleniumBase Docs" /></a> <a href="https://github.com/seleniumbase/SeleniumBase/blob/master/LICENSE"><img src="https://img.shields.io/badge/license-MIT-22BBCC.svg" title="SeleniumBase" /></a></div>
<div><a href="https://github.com/seleniumbase/SeleniumBase"><img src="https://img.shields.io/badge/tested%20with-SeleniumBase-04C38E.svg" alt="Tested with SeleniumBase" /></a> <a href="https://github.com/seleniumbase/SeleniumBase/stargazers"><img src="https://img.shields.io/github/stars/seleniumbase/seleniumbase.svg?color=19A57B" title="Stargazers" /></a></div>
<div><a href="https://hellogithub.com/repository/c6be2d0f1969448697683d11a4ff915e" target="_blank"><img src="https://abroad.hellogithub.com/v1/widgets/recommend.svg?rid=c6be2d0f1969448697683d11a4ff915e&claim_uid=xcrm4p9j3d6JCO5&theme=small" alt="FeaturedHelloGitHub" /></a> <a href="https://discord.gg/EdhQTn3EyE" target="_blank"><img src="https://img.shields.io/badge/join-discord-infomational" alt="Join the SeleniumBase chat on Discord"/></a> <a href="https://gitter.im/seleniumbase/SeleniumBase" target="_blank"><img src="https://img.shields.io/gitter/room/seleniumbase/SeleniumBase.svg" alt="Gitter chat"/></a></div>
<div><a href="https://hellogithub.com/repository/c6be2d0f1969448697683d11a4ff915e" target="_blank"><img src="https://abroad.hellogithub.com/v1/widgets/recommend.svg?rid=c6be2d0f1969448697683d11a4ff915e&claim_uid=xcrm4p9j3d6JCO5&theme=small" alt="FeaturedHelloGitHub" /></a> <a href="https://discord.gg/EdhQTn3EyE" target="_blank"><img src="https://img.shields.io/discord/727927627830001734?color=7289DA&label=Discord&logo=discord&logoColor=white"/></a></div>
<div><a href="https://pepy.tech/projects/seleniumbase?timeRange=threeMonths&category=version&includeCIDownloads=true&granularity=daily&viewType=line&versions=*" target="_blank"><img src="https://static.pepy.tech/badge/seleniumbase" alt="SeleniumBase PyPI downloads" /></a> <img src="https://views.whatilearened.today/views/github/seleniumbase/SeleniumBase.svg" width="98px" height="20px" alt="Views" /></div>
<div align="left"></div>

View File

@ -465,8 +465,9 @@ sb.cdp.gui_press_keys(keys)
sb.cdp.gui_write(text)
sb.cdp.gui_click_x_y(x, y)
sb.cdp.gui_click_element(selector)
sb.cdp.gui_drag_drop_points(x1, y1, x2, y2)
sb.cdp.gui_drag_and_drop(drag_selector, drop_selector)
sb.cdp.gui_drag_drop_points(x1, y1, x2, y2, timeframe=0.35)
sb.cdp.gui_drag_and_drop(drag_selector, drop_selector, timeframe=0.35)
sb.cdp.gui_click_and_hold(selector, timeframe=0.35)
sb.cdp.gui_hover_x_y(x, y)
sb.cdp.gui_hover_element(selector)
sb.cdp.gui_hover_and_click(hover_selector, click_selector)

View File

@ -16,6 +16,15 @@ with SB(uc=True, test=True, locale="en") as sb:
sb.sleep(1)
sb.cdp.gui_press_keys("\b" * 10 + formatted_date + "\n")
sb.sleep(1)
days_ahead = (4 - today.weekday() + 8) % 14
following_saturday = today + datetime.timedelta(days=days_ahead)
formatted_date = following_saturday.strftime("%m/%d/%Y")
sb.cdp.gui_click_element(
'[data-att="end-date-toggler"] [aria-describedby*="date-input"]'
)
sb.sleep(1)
sb.cdp.gui_press_keys("\b" * 10 + formatted_date + "\n")
sb.sleep(1)
sb.cdp.click('button[data-att="done"]')
sb.sleep(1)
sb.cdp.click('button[data-att="search"]')

View File

@ -3,6 +3,7 @@ from seleniumbase import SB
with SB(uc=True, test=True, ad_block=True) as sb:
url = "https://www.glassdoor.com/Reviews/index.htm"
sb.activate_cdp_mode(url)
sb.sleep(2)
sb.uc_gui_click_captcha()
sb.highlight('[data-test="global-nav-glassdoor-logo"]')
sb.highlight('[data-test="site-header-companies"]')

View File

@ -8,28 +8,25 @@ with SB(uc=True, test=True, ad_block=True, pls="none") as sb:
sb.uc_gui_click_captcha()
sb.sleep(0.5)
channel_name = "michaelmintz"
sb.cdp.press_keys('input[name="query"]', channel_name)
sb.cdp.click('form[action*="/search"] button')
sb.sleep(2)
sb.cdp.click('a[title="%s"] h2' % channel_name)
channel_title = "Michael Mintz"
sb.cdp.press_keys('input[placeholder*="Search"]', channel_name)
sb.sleep(1.5)
sb.cdp.click('a:contains("%s")' % channel_title)
sb.sleep(2)
sb.cdp.remove_elements("#lngtd-top-sticky")
sb.sleep(1.5)
name = sb.cdp.get_text("h1")
link = sb.cdp.get_attribute("#YouTubeUserTopInfoBlockTop h4 a", "href")
subscribers = sb.cdp.get_text("#youtube-stats-header-subs")
video_views = sb.cdp.get_text("#youtube-stats-header-views")
rankings = sb.cdp.get_text(
'#socialblade-user-content [style*="border-bottom"]'
).replace("\xa0", "").replace(" ", " ").replace(" ", " ")
source = sb.get_page_source()
base = "https://www.youtube.com/c/"
base2 = 'href="/youtube/c/'
start = source.find(base2) + len(base2)
end = source.find('"', start)
link = base + source[start:end]
print("********** SocialBlade Stats for %s: **********" % name)
print(">>> (Link: %s) <<<" % link)
print("* YouTube Subscribers: %s" % subscribers)
print("* YouTube Video Views: %s" % video_views)
print(sb.get_text('[class*="grid lg:hidden"]'))
print("********** SocialBlade Ranks: **********")
for row in rankings.split("\n"):
if len(row.strip()) > 8:
print("--> " + row.strip())
print(sb.get_text('[class*="gap-3 flex-1"]'))
for i in range(17):
sb.cdp.scroll_down(6)
sb.sleep(0.1)

View File

@ -1,3 +1,4 @@
from contextlib import suppress
from seleniumbase import BaseCase
BaseCase.main(__name__, __file__)
@ -7,6 +8,54 @@ class UCPresentationClass(BaseCase):
self.open("data:,")
self.set_window_position(4, 40)
self._output_file_saves = False
self.create_presentation(theme="serif", transition="none")
self.add_slide("<h2>Press SPACE to continue!</h2>\n")
self.add_slide(
"<h3><b>Before we begin</b></h3><hr />"
"<p><b>(Here's the GitHub page)</b></p>",
image="https://seleniumbase.io/other/sbase_qr_code.png",
)
self.begin_presentation(filename="uc_presentation.html")
with suppress(Exception):
self.open("https://www.bostoncodecamp.com/CC38/info")
self.create_tour(theme="hopscotch")
self.add_tour_step(
"<h2>Good Afternoon and Welcome!</h2>", 'h1.wow'
)
self.add_tour_step(
"<h4>PSA: Visit our sponsors later.</h4>",
'[href*="/Sponsors"]',
)
self.add_tour_step(
"<h4>Let's check out the schedule...</h4>",
'[href*="/Schedule/SessionGrid"]'
)
self.play_tour()
with suppress(Exception):
self.open(
"https://www.bostoncodecamp.com/CC38/Schedule/SessionGrid"
)
self.highlight("h2", loops=8)
if self.is_element_visible('[data-sessionid="869465"]'):
self.highlight(
'div[data-sessionid="869465"]', loops=10, scroll=False
)
self.create_tour(theme="driverjs")
self.add_tour_step(
"<h2>Here we are</h2>", '[data-sessionid="869465"]'
)
self.play_tour()
self.click('a[onclick*="869465"]')
self.create_tour(theme="hopscotch")
self.add_tour_step(
"<h2>What to expect</h2>",
"div.sz-modal-session",
alignment="left",
)
self.play_tour()
self.create_presentation(theme="serif", transition="none")
self.add_slide("<h2>Press SPACE to begin!</h2>\n")
self.add_slide(
@ -244,4 +293,9 @@ class UCPresentationClass(BaseCase):
'<img src="https://seleniumbase.io/other/hackers_at_comp.jpg"'
' width="70%">'
)
self.add_slide(
"<h3><b>Live Demo Time!</b></h3><hr />"
"<h3>(Let's head over to GitHub...)</h3>",
image="https://seleniumbase.io/other/sbase_qr_code.png",
)
self.begin_presentation(filename="uc_presentation.html")

View File

@ -14,7 +14,7 @@ pathspec==0.12.1
Babel==2.17.0
paginate==0.5.7
mkdocs==1.6.1
mkdocs-material==9.6.9
mkdocs-material==9.6.11
mkdocs-exclude-search==0.6.6
mkdocs-simple-hooks==0.1.5
mkdocs-material-extensions==1.3.1

View File

@ -15,7 +15,7 @@ mycdp>=1.1.1
pynose>=1.5.4
platformdirs>=4.3.6;python_version<"3.9"
platformdirs>=4.3.7;python_version>="3.9"
typing-extensions>=4.13.0
typing-extensions>=4.13.2
sbvirtualdisplay>=1.4.0
MarkupSafe==2.1.5;python_version<"3.9"
MarkupSafe>=3.0.2;python_version>="3.9"
@ -33,7 +33,7 @@ idna==3.10
chardet==5.2.0
charset-normalizer==3.4.1
urllib3>=1.26.20,<2;python_version<"3.10"
urllib3>=1.26.20,<2.4.0;python_version>="3.10"
urllib3>=1.26.20,<2.5.0;python_version>="3.10"
requests==2.32.3
sniffio==1.3.1
h11==0.14.0
@ -44,7 +44,7 @@ trio-websocket==0.12.2
wsproto==1.2.0
websocket-client==1.8.0
selenium==4.27.1;python_version<"3.9"
selenium==4.30.0;python_version>="3.9"
selenium==4.31.0;python_version>="3.9"
cssselect==1.2.0;python_version<"3.9"
cssselect==1.3.0;python_version>="3.9"
sortedcontainers==2.4.0
@ -66,19 +66,19 @@ pyotp==2.9.0
python-xlib==0.33;platform_system=="Linux"
markdown-it-py==3.0.0
mdurl==0.1.2
rich==13.9.4
rich>=14.0.0,<15
# --- Testing Requirements --- #
# ("pip install -r requirements.txt" also installs this, but "pip install -e ." won't.)
coverage>=7.6.1;python_version<"3.9"
coverage>=7.7.1;python_version>="3.9"
coverage>=7.8.0;python_version>="3.9"
pytest-cov>=5.0.0;python_version<"3.9"
pytest-cov>=6.0.0;python_version>="3.9"
pytest-cov>=6.1.1;python_version>="3.9"
flake8==5.0.4;python_version<"3.9"
flake8==7.1.2;python_version>="3.9"
flake8==7.2.0;python_version>="3.9"
mccabe==0.7.0
pyflakes==2.5.0;python_version<"3.9"
pyflakes==3.2.0;python_version>="3.9"
pyflakes==3.3.2;python_version>="3.9"
pycodestyle==2.9.1;python_version<"3.9"
pycodestyle==2.12.1;python_version>="3.9"
pycodestyle==2.13.0;python_version>="3.9"

View File

@ -1,2 +1,2 @@
# seleniumbase package
__version__ = "4.36.4"
__version__ = "4.37.2"

View File

@ -682,6 +682,7 @@ def uc_open_with_cdp_mode(driver, url=None):
cdp.gui_click_element = CDPM.gui_click_element
cdp.gui_drag_drop_points = CDPM.gui_drag_drop_points
cdp.gui_drag_and_drop = CDPM.gui_drag_and_drop
cdp.gui_click_and_hold = CDPM.gui_click_and_hold
cdp.gui_hover_x_y = CDPM.gui_hover_x_y
cdp.gui_hover_element = CDPM.gui_hover_element
cdp.gui_hover_and_click = CDPM.gui_hover_and_click

View File

@ -92,5 +92,5 @@ class S3LoggingBucket(object):
"""Keep a record of all file names that have been uploaded.
Upload log files related to each test after its execution.
Once done, use already_uploaded_files to create an index file."""
global already_uploaded_files
global already_uploaded_files # noqa
already_uploaded_files.extend(files)

View File

@ -1227,15 +1227,23 @@ class CDPMethods():
if not timeout:
timeout = settings.SMALL_TIMEOUT
selector = self.__convert_to_css_if_xpath(selector)
self.select(selector, timeout=timeout)
element = self.select(selector, timeout=timeout)
self.__add_light_pause()
coordinates = self.loop.run_until_complete(
self.page.js_dumps(
"""document.querySelector"""
"""('%s').getBoundingClientRect()"""
% js_utils.escape_quotes_if_needed(re.escape(selector))
coordinates = None
if ":contains(" in selector:
position = element.get_position()
x = position.x
y = position.y
width = position.width
height = position.height
coordinates = {"x": x, "y": y, "width": width, "height": height}
else:
coordinates = self.loop.run_until_complete(
self.page.js_dumps(
"""document.querySelector('%s').getBoundingClientRect()"""
% js_utils.escape_quotes_if_needed(re.escape(selector))
)
)
)
return coordinates
def get_element_size(self, selector, timeout=None):
@ -1614,6 +1622,8 @@ class CDPMethods():
pyautogui.dragTo(x2, y2, button="left", duration=timeframe)
def gui_drag_drop_points(self, x1, y1, x2, y2, timeframe=0.35):
"""Use PyAutoGUI to drag-and-drop from one point to another.
Can simulate click-and-hold when using the same point twice."""
gui_lock = fasteners.InterProcessLock(
constants.MultiBrowser.PYAUTOGUILOCK
)
@ -1653,6 +1663,8 @@ class CDPMethods():
self.loop.run_until_complete(self.page.wait())
def gui_drag_and_drop(self, drag_selector, drop_selector, timeframe=0.35):
"""Use PyAutoGUI to drag-and-drop from one selector to another.
Can simulate click-and-hold when using the same selector twice."""
self.__slow_mode_pause_if_set()
self.bring_active_window_to_front()
x1, y1 = self.get_gui_element_center(drag_selector)
@ -1661,6 +1673,14 @@ class CDPMethods():
self.__add_light_pause()
self.gui_drag_drop_points(x1, y1, x2, y2, timeframe=timeframe)
def gui_click_and_hold(self, selector, timeframe=0.35):
"""Use PyAutoGUI to click-and-hold a selector."""
self.__slow_mode_pause_if_set()
self.bring_active_window_to_front()
x, y = self.get_gui_element_center(selector)
self.__add_light_pause()
self.gui_drag_drop_points(x, y, x, y, timeframe=timeframe)
def __gui_hover_x_y(self, x, y, timeframe=0.25, uc_lock=False):
self.__install_pyautogui_if_missing()
import pyautogui

View File

@ -4871,6 +4871,13 @@ class BaseCase(unittest.TestCase):
def activate_cdp_mode(self, url=None):
if hasattr(self.driver, "_is_using_uc") and self.driver._is_using_uc:
if self.__is_cdp_swap_needed():
return # CDP Mode is already active
if not self.is_connected():
self.driver.connect()
current_url = self.get_current_url()
if not current_url.startswith(("about", "data", "chrome")):
self.get_new_driver(undetectable=True)
self.driver.uc_open_with_cdp_mode(url)
else:
self.get_new_driver(undetectable=True)

View File

@ -441,7 +441,13 @@ class Chrome(selenium.webdriver.chrome.webdriver.WebDriver):
with suppress(Exception):
if self.service.is_connectable():
self.stop_client()
self.service.stop()
try:
self.service.send_remote_shutdown_command()
except TypeError:
pass
finally:
with suppress(Exception):
self.service._terminate_process()
if isinstance(timeout, str):
if timeout.lower() == "breakpoint":
breakpoint() # To continue:
@ -466,7 +472,13 @@ class Chrome(selenium.webdriver.chrome.webdriver.WebDriver):
self.close()
if self.service.is_connectable():
self.stop_client()
self.service.stop()
try:
self.service.send_remote_shutdown_command()
except TypeError:
pass
finally:
with suppress(Exception):
self.service._terminate_process()
self.service.start()
self.start_session()
time.sleep(0.003)
@ -482,7 +494,13 @@ class Chrome(selenium.webdriver.chrome.webdriver.WebDriver):
if self.service.is_connectable():
self.stop_client()
time.sleep(0.003)
self.service.stop()
try:
self.service.send_remote_shutdown_command()
except TypeError:
pass
finally:
with suppress(Exception):
self.service._terminate_process()
self._is_connected = False
def connect(self):
@ -507,7 +525,13 @@ class Chrome(selenium.webdriver.chrome.webdriver.WebDriver):
self.close()
if self.service.is_connectable():
self.stop_client()
self.service.stop()
try:
self.service.send_remote_shutdown_command()
except TypeError:
pass
finally:
with suppress(Exception):
self.service._terminate_process()
self.service.start()
self.start_session()
time.sleep(0.003)
@ -539,7 +563,13 @@ class Chrome(selenium.webdriver.chrome.webdriver.WebDriver):
logger.debug("Stopping webdriver service")
with suppress(Exception):
self.stop_client()
self.service.stop()
try:
self.service.send_remote_shutdown_command()
except TypeError:
pass
finally:
with suppress(Exception):
self.service._terminate_process()
with suppress(Exception):
if self.reactor and isinstance(self.reactor, Reactor):
logger.debug("Shutting down Reactor")

View File

@ -406,7 +406,13 @@ async def create_from_driver(driver) -> Browser:
browser = await start(conf)
browser._process_pid = driver.browser_pid
# Stop chromedriver binary
driver.service.stop()
try:
driver.service.send_remote_shutdown_command()
except TypeError:
pass
finally:
with suppress(Exception):
driver.service._terminate_process()
driver.browser_pid = -1
driver.user_data_dir = None
return browser

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.2'")
os.system("python -m pip install 'flake8==7.2.0'")
flake8_status = os.system("flake8 --exclude=recordings,temp")
if flake8_status != 0:
print("\nERROR! Fix flake8 issues before publishing to PyPI!\n")
@ -164,7 +164,7 @@ setup(
"pynose>=1.5.4",
'platformdirs>=4.3.6;python_version<"3.9"',
'platformdirs>=4.3.7;python_version>="3.9"',
'typing-extensions>=4.13.0',
'typing-extensions>=4.13.2',
"sbvirtualdisplay>=1.4.0",
'MarkupSafe==2.1.5;python_version<"3.9"',
'MarkupSafe>=3.0.2;python_version>="3.9"',
@ -182,7 +182,7 @@ setup(
'chardet==5.2.0',
'charset-normalizer==3.4.1',
'urllib3>=1.26.20,<2;python_version<"3.10"',
'urllib3>=1.26.20,<2.4.0;python_version>="3.10"',
'urllib3>=1.26.20,<2.5.0;python_version>="3.10"',
'requests==2.32.3',
'sniffio==1.3.1',
'h11==0.14.0',
@ -193,7 +193,7 @@ setup(
'wsproto==1.2.0',
'websocket-client==1.8.0',
'selenium==4.27.1;python_version<"3.9"',
'selenium==4.30.0;python_version>="3.9"',
'selenium==4.31.0;python_version>="3.9"',
'cssselect==1.2.0;python_version<"3.9"',
'cssselect==1.3.0;python_version>="3.9"',
"sortedcontainers==2.4.0",
@ -215,7 +215,7 @@ setup(
'python-xlib==0.33;platform_system=="Linux"',
'markdown-it-py==3.0.0',
'mdurl==0.1.2',
'rich==13.9.4',
'rich>=14.0.0,<15',
],
extras_require={
# pip install -e .[allure]
@ -230,20 +230,20 @@ setup(
# Usage: coverage run -m pytest; coverage html; coverage report
"coverage": [
'coverage>=7.6.1;python_version<"3.9"',
'coverage>=7.7.1;python_version>="3.9"',
'coverage>=7.8.0;python_version>="3.9"',
'pytest-cov>=5.0.0;python_version<"3.9"',
'pytest-cov>=6.0.0;python_version>="3.9"',
'pytest-cov>=6.1.1;python_version>="3.9"',
],
# pip install -e .[flake8]
# Usage: flake8
"flake8": [
'flake8==5.0.4;python_version<"3.9"',
'flake8==7.1.2;python_version>="3.9"',
'flake8==7.2.0;python_version>="3.9"',
"mccabe==0.7.0",
'pyflakes==2.5.0;python_version<"3.9"',
'pyflakes==3.2.0;python_version>="3.9"',
'pyflakes==3.3.2;python_version>="3.9"',
'pycodestyle==2.9.1;python_version<"3.9"',
'pycodestyle==2.12.1;python_version>="3.9"',
'pycodestyle==2.13.0;python_version>="3.9"',
],
# pip install -e .[ipdb]
# (Not needed for debugging anymore. SeleniumBase now includes "pdbp".)
@ -271,7 +271,7 @@ setup(
# (An optional library for image-processing.)
"pillow": [
'Pillow>=10.4.0;python_version<"3.9"',
'Pillow>=11.1.0;python_version>="3.9"',
'Pillow>=11.2.0;python_version>="3.9"',
],
# pip install -e .[pip-system-certs]
# (If you see [SSL: CERTIFICATE_VERIFY_FAILED], then get this.)