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" 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"> <p align="center">
<a href="#python_installation">🚀 Start</a> | <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://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://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://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><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> <div align="left"></div>

View File

@ -465,8 +465,9 @@ sb.cdp.gui_press_keys(keys)
sb.cdp.gui_write(text) sb.cdp.gui_write(text)
sb.cdp.gui_click_x_y(x, y) sb.cdp.gui_click_x_y(x, y)
sb.cdp.gui_click_element(selector) sb.cdp.gui_click_element(selector)
sb.cdp.gui_drag_drop_points(x1, y1, x2, y2) sb.cdp.gui_drag_drop_points(x1, y1, x2, y2, timeframe=0.35)
sb.cdp.gui_drag_and_drop(drag_selector, drop_selector) 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_x_y(x, y)
sb.cdp.gui_hover_element(selector) sb.cdp.gui_hover_element(selector)
sb.cdp.gui_hover_and_click(hover_selector, click_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.sleep(1)
sb.cdp.gui_press_keys("\b" * 10 + formatted_date + "\n") sb.cdp.gui_press_keys("\b" * 10 + formatted_date + "\n")
sb.sleep(1) 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.cdp.click('button[data-att="done"]')
sb.sleep(1) sb.sleep(1)
sb.cdp.click('button[data-att="search"]') 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: with SB(uc=True, test=True, ad_block=True) as sb:
url = "https://www.glassdoor.com/Reviews/index.htm" url = "https://www.glassdoor.com/Reviews/index.htm"
sb.activate_cdp_mode(url) sb.activate_cdp_mode(url)
sb.sleep(2)
sb.uc_gui_click_captcha() sb.uc_gui_click_captcha()
sb.highlight('[data-test="global-nav-glassdoor-logo"]') sb.highlight('[data-test="global-nav-glassdoor-logo"]')
sb.highlight('[data-test="site-header-companies"]') 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.uc_gui_click_captcha()
sb.sleep(0.5) sb.sleep(0.5)
channel_name = "michaelmintz" channel_name = "michaelmintz"
sb.cdp.press_keys('input[name="query"]', channel_name) channel_title = "Michael Mintz"
sb.cdp.click('form[action*="/search"] button') sb.cdp.press_keys('input[placeholder*="Search"]', channel_name)
sb.sleep(2)
sb.cdp.click('a[title="%s"] h2' % channel_name)
sb.sleep(1.5) sb.sleep(1.5)
sb.cdp.click('a:contains("%s")' % channel_title)
sb.sleep(2)
sb.cdp.remove_elements("#lngtd-top-sticky") sb.cdp.remove_elements("#lngtd-top-sticky")
sb.sleep(1.5) sb.sleep(1.5)
name = sb.cdp.get_text("h1") name = sb.cdp.get_text("h1")
link = sb.cdp.get_attribute("#YouTubeUserTopInfoBlockTop h4 a", "href") source = sb.get_page_source()
subscribers = sb.cdp.get_text("#youtube-stats-header-subs") base = "https://www.youtube.com/c/"
video_views = sb.cdp.get_text("#youtube-stats-header-views") base2 = 'href="/youtube/c/'
rankings = sb.cdp.get_text( start = source.find(base2) + len(base2)
'#socialblade-user-content [style*="border-bottom"]' end = source.find('"', start)
).replace("\xa0", "").replace(" ", " ").replace(" ", " ") link = base + source[start:end]
print("********** SocialBlade Stats for %s: **********" % name) print("********** SocialBlade Stats for %s: **********" % name)
print(">>> (Link: %s) <<<" % link) print(">>> (Link: %s) <<<" % link)
print("* YouTube Subscribers: %s" % subscribers) print(sb.get_text('[class*="grid lg:hidden"]'))
print("* YouTube Video Views: %s" % video_views)
print("********** SocialBlade Ranks: **********") print("********** SocialBlade Ranks: **********")
for row in rankings.split("\n"): print(sb.get_text('[class*="gap-3 flex-1"]'))
if len(row.strip()) > 8:
print("--> " + row.strip())
for i in range(17): for i in range(17):
sb.cdp.scroll_down(6) sb.cdp.scroll_down(6)
sb.sleep(0.1) sb.sleep(0.1)

View File

@ -1,3 +1,4 @@
from contextlib import suppress
from seleniumbase import BaseCase from seleniumbase import BaseCase
BaseCase.main(__name__, __file__) BaseCase.main(__name__, __file__)
@ -7,6 +8,54 @@ class UCPresentationClass(BaseCase):
self.open("data:,") self.open("data:,")
self.set_window_position(4, 40) self.set_window_position(4, 40)
self._output_file_saves = False 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.create_presentation(theme="serif", transition="none")
self.add_slide("<h2>Press SPACE to begin!</h2>\n") self.add_slide("<h2>Press SPACE to begin!</h2>\n")
self.add_slide( self.add_slide(
@ -244,4 +293,9 @@ class UCPresentationClass(BaseCase):
'<img src="https://seleniumbase.io/other/hackers_at_comp.jpg"' '<img src="https://seleniumbase.io/other/hackers_at_comp.jpg"'
' width="70%">' ' 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") self.begin_presentation(filename="uc_presentation.html")

View File

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

View File

@ -15,7 +15,7 @@ mycdp>=1.1.1
pynose>=1.5.4 pynose>=1.5.4
platformdirs>=4.3.6;python_version<"3.9" platformdirs>=4.3.6;python_version<"3.9"
platformdirs>=4.3.7;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 sbvirtualdisplay>=1.4.0
MarkupSafe==2.1.5;python_version<"3.9" MarkupSafe==2.1.5;python_version<"3.9"
MarkupSafe>=3.0.2;python_version>="3.9" MarkupSafe>=3.0.2;python_version>="3.9"
@ -33,7 +33,7 @@ idna==3.10
chardet==5.2.0 chardet==5.2.0
charset-normalizer==3.4.1 charset-normalizer==3.4.1
urllib3>=1.26.20,<2;python_version<"3.10" 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 requests==2.32.3
sniffio==1.3.1 sniffio==1.3.1
h11==0.14.0 h11==0.14.0
@ -44,7 +44,7 @@ trio-websocket==0.12.2
wsproto==1.2.0 wsproto==1.2.0
websocket-client==1.8.0 websocket-client==1.8.0
selenium==4.27.1;python_version<"3.9" 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.2.0;python_version<"3.9"
cssselect==1.3.0;python_version>="3.9" cssselect==1.3.0;python_version>="3.9"
sortedcontainers==2.4.0 sortedcontainers==2.4.0
@ -66,19 +66,19 @@ pyotp==2.9.0
python-xlib==0.33;platform_system=="Linux" python-xlib==0.33;platform_system=="Linux"
markdown-it-py==3.0.0 markdown-it-py==3.0.0
mdurl==0.1.2 mdurl==0.1.2
rich==13.9.4 rich>=14.0.0,<15
# --- Testing Requirements --- # # --- Testing Requirements --- #
# ("pip install -r requirements.txt" also installs this, but "pip install -e ." won't.) # ("pip install -r requirements.txt" also installs this, but "pip install -e ." won't.)
coverage>=7.6.1;python_version<"3.9" 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>=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==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 mccabe==0.7.0
pyflakes==2.5.0;python_version<"3.9" 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.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 # 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_click_element = CDPM.gui_click_element
cdp.gui_drag_drop_points = CDPM.gui_drag_drop_points cdp.gui_drag_drop_points = CDPM.gui_drag_drop_points
cdp.gui_drag_and_drop = CDPM.gui_drag_and_drop 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_x_y = CDPM.gui_hover_x_y
cdp.gui_hover_element = CDPM.gui_hover_element cdp.gui_hover_element = CDPM.gui_hover_element
cdp.gui_hover_and_click = CDPM.gui_hover_and_click 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. """Keep a record of all file names that have been uploaded.
Upload log files related to each test after its execution. Upload log files related to each test after its execution.
Once done, use already_uploaded_files to create an index file.""" 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) already_uploaded_files.extend(files)

View File

@ -1227,15 +1227,23 @@ class CDPMethods():
if not timeout: if not timeout:
timeout = settings.SMALL_TIMEOUT timeout = settings.SMALL_TIMEOUT
selector = self.__convert_to_css_if_xpath(selector) selector = self.__convert_to_css_if_xpath(selector)
self.select(selector, timeout=timeout) element = self.select(selector, timeout=timeout)
self.__add_light_pause() self.__add_light_pause()
coordinates = self.loop.run_until_complete( coordinates = None
self.page.js_dumps( if ":contains(" in selector:
"""document.querySelector""" position = element.get_position()
"""('%s').getBoundingClientRect()""" x = position.x
% js_utils.escape_quotes_if_needed(re.escape(selector)) 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 return coordinates
def get_element_size(self, selector, timeout=None): def get_element_size(self, selector, timeout=None):
@ -1614,6 +1622,8 @@ class CDPMethods():
pyautogui.dragTo(x2, y2, button="left", duration=timeframe) pyautogui.dragTo(x2, y2, button="left", duration=timeframe)
def gui_drag_drop_points(self, x1, y1, x2, y2, timeframe=0.35): 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( gui_lock = fasteners.InterProcessLock(
constants.MultiBrowser.PYAUTOGUILOCK constants.MultiBrowser.PYAUTOGUILOCK
) )
@ -1653,6 +1663,8 @@ class CDPMethods():
self.loop.run_until_complete(self.page.wait()) self.loop.run_until_complete(self.page.wait())
def gui_drag_and_drop(self, drag_selector, drop_selector, timeframe=0.35): 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.__slow_mode_pause_if_set()
self.bring_active_window_to_front() self.bring_active_window_to_front()
x1, y1 = self.get_gui_element_center(drag_selector) x1, y1 = self.get_gui_element_center(drag_selector)
@ -1661,6 +1673,14 @@ class CDPMethods():
self.__add_light_pause() self.__add_light_pause()
self.gui_drag_drop_points(x1, y1, x2, y2, timeframe=timeframe) 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): def __gui_hover_x_y(self, x, y, timeframe=0.25, uc_lock=False):
self.__install_pyautogui_if_missing() self.__install_pyautogui_if_missing()
import pyautogui import pyautogui

View File

@ -4871,6 +4871,13 @@ class BaseCase(unittest.TestCase):
def activate_cdp_mode(self, url=None): def activate_cdp_mode(self, url=None):
if hasattr(self.driver, "_is_using_uc") and self.driver._is_using_uc: 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) self.driver.uc_open_with_cdp_mode(url)
else: else:
self.get_new_driver(undetectable=True) self.get_new_driver(undetectable=True)

View File

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

View File

@ -406,7 +406,13 @@ async def create_from_driver(driver) -> Browser:
browser = await start(conf) browser = await start(conf)
browser._process_pid = driver.browser_pid browser._process_pid = driver.browser_pid
# Stop chromedriver binary # 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.browser_pid = -1
driver.user_data_dir = None driver.user_data_dir = None
return browser return browser

View File

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