Compare commits

..

No commits in common. "master" and "v4.36.4" have entirely different histories.

28 changed files with 131 additions and 544 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/discord/727927627830001734?color=7289DA&label=Discord&logo=discord&logoColor=white"/></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/badge/join-discord-infomational" alt="Join the SeleniumBase chat on Discord"/></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/discord/727927627830001734?color=7289DA&label=Discord&logo=discord&logoColor=white"/></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://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

@ -286,7 +286,6 @@ with SB(uc=True, test=True, ad_block=True) as sb:
sb.activate_cdp_mode(url) sb.activate_cdp_mode(url)
sb.sleep(2.5) sb.sleep(2.5)
sb.cdp.click_if_visible('[data-automation-id*="close-mark"]') sb.cdp.click_if_visible('[data-automation-id*="close-mark"]')
sb.sleep(0.3)
sb.cdp.mouse_click('input[aria-label="Search"]') sb.cdp.mouse_click('input[aria-label="Search"]')
sb.sleep(1.2) sb.sleep(1.2)
search = "Settlers of Catan Board Game" search = "Settlers of Catan Board Game"
@ -301,7 +300,7 @@ with SB(uc=True, test=True, ad_block=True) as sb:
for item in items: for item in items:
if required_text in item.text: if required_text in item.text:
description = item.querySelector( description = item.querySelector(
'[data-automation-id="product-title"]' '[data-automation-id="product-price"] + span'
) )
if description and description.text not in unique_item_text: if description and description.text not in unique_item_text:
unique_item_text.append(description.text) unique_item_text.append(description.text)
@ -420,7 +419,7 @@ sb.cdp.js_dumps(obj_name)
sb.cdp.maximize() sb.cdp.maximize()
sb.cdp.minimize() sb.cdp.minimize()
sb.cdp.medimize() sb.cdp.medimize()
sb.cdp.set_window_rect(x, y, width, height) sb.cdp.set_window_rect()
sb.cdp.reset_window_size() sb.cdp.reset_window_size()
sb.cdp.open_new_window(url=None, switch_to=True) sb.cdp.open_new_window(url=None, switch_to=True)
sb.cdp.switch_to_window(window) sb.cdp.switch_to_window(window)
@ -466,9 +465,8 @@ 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, timeframe=0.35) sb.cdp.gui_drag_drop_points(x1, y1, x2, y2)
sb.cdp.gui_drag_and_drop(drag_selector, drop_selector, timeframe=0.35) sb.cdp.gui_drag_and_drop(drag_selector, drop_selector)
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,15 +16,6 @@ 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

@ -1,16 +0,0 @@
"""Geolocation example using CDP Mode without WebDriver"""
from seleniumbase import decorators
from seleniumbase import sb_cdp
@decorators.print_runtime("Geolocation CDP Example")
def main():
url = "https://www.openstreetmap.org/"
sb = sb_cdp.Chrome(url, geoloc=(48.87645, 2.26340))
sb.click("span.geolocate")
sb.assert_url_contains("48.876450/2.263400")
sb.sleep(5)
if __name__ == "__main__":
main()

View File

@ -3,7 +3,6 @@ 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

@ -2,20 +2,12 @@ from seleniumbase import SB
with SB(uc=True, incognito=True, test=True) as sb: with SB(uc=True, incognito=True, test=True) as sb:
sb.activate_cdp_mode("https://pixelscan.net/") sb.activate_cdp_mode("https://pixelscan.net/")
sb.sleep(2) sb.sleep(3)
sb.click('button[class*="startButton"]') sb.remove_elements(".bg-bannerBg") # Remove the top banner
sb.sleep(6) sb.remove_elements("pxlscn-ad1") # Remove the ad banner
sb.remove_elements(".bg-bannerBg") # Remove top banner
sb.remove_elements("pxlscn-ad1") # Remove an ad banner
sb.remove_elements("pxlscn-ad2") # Remove an ad banner
sb.remove_elements("jdiv") # Remove chat widgets sb.remove_elements("jdiv") # Remove chat widgets
sb.sleep(14)
not_masking_text = "You are not masking your fingerprint" not_masking_text = "You are not masking your fingerprint"
sb.assert_text( sb.assert_text(not_masking_text, "pxlscn-fingerprint-masking")
not_masking_text,
"pxlscn-fingerprint-masking",
timeout=20,
)
no_automation_detected = "No automation framework detected" no_automation_detected = "No automation framework detected"
sb.assert_text(no_automation_detected, "pxlscn-bot-detection") sb.assert_text(no_automation_detected, "pxlscn-bot-detection")
consistent_selector = 'div.bg-consistentBg [alt="Good"]' consistent_selector = 'div.bg-consistentBg [alt="Good"]'

View File

@ -8,25 +8,28 @@ 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"
channel_title = "Michael Mintz" sb.cdp.press_keys('input[name="query"]', channel_name)
sb.cdp.press_keys('input[placeholder*="Search"]', channel_name) sb.cdp.click('form[action*="/search"] button')
sb.sleep(1.5)
sb.cdp.click('a:contains("%s")' % channel_title)
sb.sleep(2) sb.sleep(2)
sb.cdp.click('a[title="%s"] h2' % channel_name)
sb.sleep(1.5)
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")
source = sb.get_page_source() link = sb.cdp.get_attribute("#YouTubeUserTopInfoBlockTop h4 a", "href")
base = "https://www.youtube.com/c/" subscribers = sb.cdp.get_text("#youtube-stats-header-subs")
base2 = 'href="/youtube/c/' video_views = sb.cdp.get_text("#youtube-stats-header-views")
start = source.find(base2) + len(base2) rankings = sb.cdp.get_text(
end = source.find('"', start) '#socialblade-user-content [style*="border-bottom"]'
link = base + source[start:end] ).replace("\xa0", "").replace(" ", " ").replace(" ", " ")
print("********** SocialBlade Stats for %s: **********" % name) print("********** SocialBlade Stats for %s: **********" % name)
print(">>> (Link: %s) <<<" % link) print(">>> (Link: %s) <<<" % link)
print(sb.get_text('[class*="grid lg:hidden"]')) print("* YouTube Subscribers: %s" % subscribers)
print("* YouTube Video Views: %s" % video_views)
print("********** SocialBlade Ranks: **********") print("********** SocialBlade Ranks: **********")
print(sb.get_text('[class*="gap-3 flex-1"]')) for row in rankings.split("\n"):
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

@ -5,7 +5,6 @@ with SB(uc=True, test=True, locale="en", ad_block=True) as sb:
url = "https://architectureofcities.com/roman-theaters" url = "https://architectureofcities.com/roman-theaters"
sb.activate_cdp_mode(url) sb.activate_cdp_mode(url)
sb.cdp.click_if_visible("#cn-close-notice") sb.cdp.click_if_visible("#cn-close-notice")
sb.cdp.click_if_visible('span:contains("Continue")')
sb.sleep(1) sb.sleep(1)
print("*** " + sb.cdp.get_text("h1") + " ***") print("*** " + sb.cdp.get_text("h1") + " ***")
for item in sb.cdp.find_elements("h3"): for item in sb.cdp.find_elements("h3"):

View File

@ -1,34 +0,0 @@
"""Timezone example using CDP Mode without WebDriver"""
import mycdp
from seleniumbase import decorators
from seleniumbase import sb_cdp
async def request_paused_handler(event, tab):
r = event.request
is_image = ".png" in r.url or ".jpg" in r.url or ".gif" in r.url
if not is_image: # Let the data through
tab.feed_cdp(mycdp.fetch.continue_request(request_id=event.request_id))
else: # Block the data (images)
TIMED_OUT = mycdp.network.ErrorReason.TIMED_OUT
tab.feed_cdp(mycdp.fetch.fail_request(event.request_id, TIMED_OUT))
@decorators.print_runtime("Timezone CDP Example")
def main():
url = "https://www.randymajors.org/what-time-zone-am-i-in"
sb = sb_cdp.Chrome(
url,
ad_block=True,
lang="bn",
tzone="Asia/Kolkata",
geoloc=(26.855323, 80.937710)
)
sb.add_handler(mycdp.fetch.RequestPaused, request_paused_handler)
sb.remove_elements("#right-sidebar")
sb.remove_elements('[id*="Footer"]')
sb.sleep(6)
if __name__ == "__main__":
main()

View File

@ -5,7 +5,6 @@ with SB(uc=True, test=True, ad_block=True) as sb:
sb.activate_cdp_mode(url) sb.activate_cdp_mode(url)
sb.sleep(2.5) sb.sleep(2.5)
sb.cdp.click_if_visible('[data-automation-id*="close-mark"]') sb.cdp.click_if_visible('[data-automation-id*="close-mark"]')
sb.sleep(0.3)
sb.cdp.mouse_click('input[aria-label="Search"]') sb.cdp.mouse_click('input[aria-label="Search"]')
sb.sleep(1.2) sb.sleep(1.2)
search = "Settlers of Catan Board Game" search = "Settlers of Catan Board Game"
@ -20,7 +19,7 @@ with SB(uc=True, test=True, ad_block=True) as sb:
for item in items: for item in items:
if required_text in item.text: if required_text in item.text:
description = item.querySelector( description = item.querySelector(
'[data-automation-id="product-title"]' '[data-automation-id="product-price"] + span'
) )
if description and description.text not in unique_item_text: if description and description.text not in unique_item_text:
unique_item_text.append(description.text) unique_item_text.append(description.text)

View File

@ -521,7 +521,6 @@ class UCPresentationClass(BaseCase):
sb.activate_cdp_mode(url) sb.activate_cdp_mode(url)
sb.sleep(2.5) sb.sleep(2.5)
sb.cdp.click_if_visible('[data-automation-id*="close-mark"]') sb.cdp.click_if_visible('[data-automation-id*="close-mark"]')
sb.sleep(0.3)
sb.cdp.mouse_click('input[aria-label="Search"]') sb.cdp.mouse_click('input[aria-label="Search"]')
sb.sleep(1.2) sb.sleep(1.2)
search = "Settlers of Catan Board Game" search = "Settlers of Catan Board Game"
@ -536,7 +535,7 @@ class UCPresentationClass(BaseCase):
for item in items: for item in items:
if required_text in item.text: if required_text in item.text:
description = item.querySelector( description = item.querySelector(
'[data-automation-id="product-title"]' '[data-automation-id="product-price"] + span'
) )
if ( if (
description description

View File

@ -1,4 +1,3 @@
from contextlib import suppress
from seleniumbase import BaseCase from seleniumbase import BaseCase
BaseCase.main(__name__, __file__) BaseCase.main(__name__, __file__)
@ -8,54 +7,6 @@ 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(
@ -293,9 +244,4 @@ 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

@ -6,7 +6,7 @@ try:
url = "seleniumbase.io/apps/turnstile" url = "seleniumbase.io/apps/turnstile"
driver.uc_open_with_reconnect(url, 2) driver.uc_open_with_reconnect(url, 2)
driver.uc_gui_handle_captcha() driver.uc_gui_handle_captcha()
driver.sleep(2) driver.sleep(3)
pprint(driver.get_log("performance")) pprint(driver.get_log("performance"))
finally: finally:
driver.quit() driver.quit()

View File

@ -1,18 +1,12 @@
from seleniumbase import SB from seleniumbase import SB
with SB(uc=True, incognito=True, test=True) as sb: with SB(uc=True, incognito=True, test=True) as sb:
sb.driver.uc_open_with_reconnect("https://pixelscan.net/", 2) sb.driver.uc_open_with_reconnect("https://pixelscan.net/", 7)
sb.uc_click('button[class*="startButton"]', reconnect_time=20) sb.remove_elements(".bg-bannerBg") # Remove the top banner
sb.remove_elements(".bg-bannerBg") # Remove top banner sb.remove_elements("pxlscn-ad1") # Remove the ad banner
sb.remove_elements("pxlscn-ad1") # Remove an ad banner
sb.remove_elements("pxlscn-ad2") # Remove an ad banner
sb.remove_elements("jdiv") # Remove chat widgets sb.remove_elements("jdiv") # Remove chat widgets
no_automation_detected = "No automation framework detected" no_automation_detected = "No automation framework detected"
sb.assert_text( sb.assert_text(no_automation_detected, "pxlscn-bot-detection")
no_automation_detected,
"pxlscn-bot-detection",
timeout=20,
)
not_masking_text = "You are not masking your fingerprint" not_masking_text = "You are not masking your fingerprint"
sb.assert_text(not_masking_text, "pxlscn-fingerprint-masking") sb.assert_text(not_masking_text, "pxlscn-fingerprint-masking")
consistent_selector = 'div.bg-consistentBg [alt="Good"]' consistent_selector = 'div.bg-consistentBg [alt="Good"]'

View File

@ -3,9 +3,9 @@
regex>=2024.11.6 regex>=2024.11.6
pymdown-extensions>=10.14.3 pymdown-extensions>=10.14.3
pipdeptree>=2.26.1 pipdeptree>=2.26.0
python-dateutil>=2.8.2 python-dateutil>=2.8.2
Markdown==3.8 Markdown==3.7
click==8.1.8 click==8.1.8
ghp-import==2.1.0 ghp-import==2.1.0
watchdog==6.0.0 watchdog==6.0.0
@ -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.12 mkdocs-material==9.6.9
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

@ -1,7 +1,7 @@
pip>=25.0.1 pip>=25.0.1
packaging>=25.0 packaging>=24.2
setuptools~=70.2;python_version<"3.10" setuptools~=70.2;python_version<"3.10"
setuptools>=79.0.1;python_version>="3.10" setuptools>=78.1.0;python_version>="3.10"
wheel>=0.45.1 wheel>=0.45.1
attrs>=25.3.0 attrs>=25.3.0
certifi>=2025.1.31 certifi>=2025.1.31
@ -11,11 +11,11 @@ websockets>=15.0.1;python_version>="3.9"
filelock~=3.16.1;python_version<"3.9" filelock~=3.16.1;python_version<"3.9"
filelock>=3.18.0;python_version>="3.9" filelock>=3.18.0;python_version>="3.9"
fasteners>=0.19 fasteners>=0.19
mycdp>=1.2.0 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.2 typing-extensions>=4.13.0
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,18 +33,18 @@ 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.5.0;python_version>="3.10" urllib3>=1.26.20,<2.4.0;python_version>="3.10"
requests==2.32.3 requests==2.32.3
sniffio==1.3.1 sniffio==1.3.1
h11==0.16.0 h11==0.14.0
outcome==1.3.0.post0 outcome==1.3.0.post0
trio==0.27.0;python_version<"3.9" trio==0.27.0;python_version<"3.9"
trio==0.30.0;python_version>="3.9" trio==0.29.0;python_version>="3.9"
trio-websocket==0.12.2 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.31.0;python_version>="3.9" selenium==4.30.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
@ -60,25 +60,25 @@ pytest-rerunfailures==15.0;python_version>="3.9"
pytest-xdist==3.6.1 pytest-xdist==3.6.1
parameterized==0.9.0 parameterized==0.9.0
behave==1.2.6 behave==1.2.6
soupsieve==2.7 soupsieve==2.6
beautifulsoup4==4.13.4 beautifulsoup4==4.13.3
pyotp==2.9.0 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>=14.0.0,<15 rich==13.9.4
# --- 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.8.0;python_version>="3.9" coverage>=7.7.1;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.1.1;python_version>="3.9" pytest-cov>=6.0.0;python_version>="3.9"
flake8==5.0.4;python_version<"3.9" flake8==5.0.4;python_version<"3.9"
flake8==7.2.0;python_version>="3.9" flake8==7.1.2;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.3.2;python_version>="3.9" pyflakes==3.2.0;python_version>="3.9"
pycodestyle==2.9.1;python_version<"3.9" pycodestyle==2.9.1;python_version<"3.9"
pycodestyle==2.13.0;python_version>="3.9" pycodestyle==2.12.1;python_version>="3.9"

View File

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

View File

@ -682,7 +682,6 @@ 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 # noqa global already_uploaded_files
already_uploaded_files.extend(files) already_uploaded_files.extend(files)

View File

@ -1227,20 +1227,12 @@ 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)
element = self.select(selector, timeout=timeout) self.select(selector, timeout=timeout)
self.__add_light_pause() self.__add_light_pause()
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( coordinates = self.loop.run_until_complete(
self.page.js_dumps( self.page.js_dumps(
"""document.querySelector('%s').getBoundingClientRect()""" """document.querySelector"""
"""('%s').getBoundingClientRect()"""
% js_utils.escape_quotes_if_needed(re.escape(selector)) % js_utils.escape_quotes_if_needed(re.escape(selector))
) )
) )
@ -1622,8 +1614,6 @@ 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
) )
@ -1663,8 +1653,6 @@ 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)
@ -1673,14 +1661,6 @@ 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

@ -162,7 +162,6 @@ class BaseCase(unittest.TestCase):
self.__jqc_default_theme = None self.__jqc_default_theme = None
self.__jqc_default_color = None self.__jqc_default_color = None
self.__jqc_default_width = None self.__jqc_default_width = None
self.__saved_id = None
# Requires self._* instead of self.__* for external class use # Requires self._* instead of self.__* for external class use
self._language = "English" self._language = "English"
self._presentation_slides = {} self._presentation_slides = {}
@ -4872,13 +4871,6 @@ 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)
@ -15677,24 +15669,19 @@ class BaseCase(unittest.TestCase):
test_id = "%s.%s" % (file_name, scenario_name) test_id = "%s.%s" % (file_name, scenario_name)
return test_id return test_id
elif hasattr(self, "is_context_manager") and self.is_context_manager: elif hasattr(self, "is_context_manager") and self.is_context_manager:
if hasattr(self, "_manager_saved_id"):
self.__saved_id = self._manager_saved_id
if self.__saved_id:
return self.__saved_id
filename = self.__class__.__module__.split(".")[-1] + ".py" filename = self.__class__.__module__.split(".")[-1] + ".py"
methodname = self._testMethodName methodname = self._testMethodName
context_id = None context_id = None
if filename == "base_case.py" or methodname == "runTest": if filename == "base_case.py" or methodname == "runTest":
import traceback import traceback
stack_base = traceback.format_stack()[0].split(os.sep)[-1] stack_base = traceback.format_stack()[0].split(", in ")[0]
test_base = stack_base.split(", in ")[0] test_base = stack_base.split(", in ")[0].split(os.sep)[-1]
if hasattr(self, "cm_filename") and self.cm_filename: if hasattr(self, "cm_filename") and self.cm_filename:
filename = self.cm_filename filename = self.cm_filename
else: else:
filename = test_base.split('"')[0] filename = test_base.split('"')[0]
methodname = ".line_" + test_base.split(", line ")[-1] methodname = ".line_" + test_base.split(", line ")[-1]
context_id = filename.split(".")[0] + methodname context_id = filename.split(".")[0] + methodname
self.__saved_id = context_id
return context_id return context_id
test_id = "%s.%s.%s" % ( test_id = "%s.%s.%s" % (
self.__class__.__module__, self.__class__.__module__,

View File

@ -23,7 +23,7 @@ with SB(uc=True) as sb: # Many args! Eg. SB(browser="edge")
######################################### #########################################
""" """
from contextlib import contextmanager, suppress from contextlib import contextmanager
@contextmanager # Usage: -> ``with SB() as sb:`` @contextmanager # Usage: -> ``with SB() as sb:``
@ -258,7 +258,6 @@ def SB(
time_limit (float): SECONDS (Safely fail tests that exceed the time limit) time_limit (float): SECONDS (Safely fail tests that exceed the time limit)
""" """
import colorama import colorama
import gc
import os import os
import sys import sys
import time import time
@ -1232,15 +1231,6 @@ def SB(
sb.cap_file = sb_config.cap_file sb.cap_file = sb_config.cap_file
sb.cap_string = sb_config.cap_string sb.cap_string = sb_config.cap_string
sb._has_failure = False # This may change sb._has_failure = False # This may change
with suppress(Exception):
stack_base = traceback.format_stack()[0].split(os.sep)[-1]
test_base = stack_base.split(", in ")[0]
filename = test_base.split('"')[0]
methodname = ".line_" + test_base.split(", line ")[-1]
context_id = filename.split(".")[0] + methodname
sb._manager_saved_id = context_id
if hasattr(sb_config, "headless_active"): if hasattr(sb_config, "headless_active"):
sb.headless_active = sb_config.headless_active sb.headless_active = sb_config.headless_active
else: else:
@ -1367,13 +1357,6 @@ def SB(
"%s%s%s%s%s" "%s%s%s%s%s"
% (c1, left_space, end_text, right_space, cr) % (c1, left_space, end_text, right_space, cr)
) )
if hasattr(sb_config, "_cdp_aclose"):
import asyncio
with suppress(Exception):
loop = asyncio.get_event_loop()
asyncio.set_event_loop(loop)
loop.run_until_complete(sb_config._cdp_aclose())
gc.collect()
if test and test_name and not test_passed and raise_test_failure: if test and test_name and not test_passed and raise_test_failure:
raise exception raise exception
elif ( elif (

View File

@ -145,12 +145,7 @@ class Chrome(selenium.webdriver.chrome.webdriver.WebDriver):
debug_port = 9222 debug_port = 9222
special_port_free = False # If the port isn't free, don't use 9222 special_port_free = False # If the port isn't free, don't use 9222
try: try:
with requests.Session() as session: res = requests.get("http://127.0.0.1:9222", timeout=1)
res = session.get(
"http://127.0.0.1:9222",
headers={"Connection": "close"},
timeout=2,
)
if res.status_code != 200: if res.status_code != 200:
raise Exception("The port is free! It will be used!") raise Exception("The port is free! It will be used!")
except Exception: except Exception:
@ -402,8 +397,7 @@ class Chrome(selenium.webdriver.chrome.webdriver.WebDriver):
def add_cdp_listener(self, event_name, callback): def add_cdp_listener(self, event_name, callback):
if ( if (
hasattr(self, "reactor") self.reactor
and self.reactor
and self.reactor is not None and self.reactor is not None
and isinstance(self.reactor, Reactor) and isinstance(self.reactor, Reactor)
): ):
@ -412,11 +406,7 @@ class Chrome(selenium.webdriver.chrome.webdriver.WebDriver):
return False return False
def clear_cdp_listeners(self): def clear_cdp_listeners(self):
if ( if self.reactor and isinstance(self.reactor, Reactor):
hasattr(self, "reactor")
and self.reactor
and isinstance(self.reactor, Reactor)
):
self.reactor.handlers.clear() self.reactor.handlers.clear()
def window_new(self, url=None): def window_new(self, url=None):
@ -451,13 +441,7 @@ 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()
try: self.service.stop()
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:
@ -472,7 +456,9 @@ class Chrome(selenium.webdriver.chrome.webdriver.WebDriver):
with suppress(Exception): with suppress(Exception):
for window_handle in self.window_handles: for window_handle in self.window_handles:
self.switch_to.window(window_handle) self.switch_to.window(window_handle)
if self.current_url.startswith("chrome-extension://"): if self.current_url.startswith(
"chrome-extension://"
):
# https://issues.chromium.org/issues/396611138 # https://issues.chromium.org/issues/396611138
# (Remove the Linux conditional when resolved) # (Remove the Linux conditional when resolved)
# (So that close() is always called) # (So that close() is always called)
@ -480,13 +466,7 @@ 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()
try: self.service.stop()
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)
@ -502,13 +482,7 @@ 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)
try: self.service.stop()
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):
@ -533,13 +507,7 @@ 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()
try: self.service.stop()
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)
@ -567,35 +535,15 @@ class Chrome(selenium.webdriver.chrome.webdriver.WebDriver):
logger.debug(e, exc_info=True) logger.debug(e, exc_info=True)
except Exception: except Exception:
pass pass
with suppress(Exception):
self.stop_client()
with suppress(Exception):
if hasattr(self, "command_executor") and self.command_executor:
self.command_executor.close()
# Remove instance reference to allow garbage collection
Chrome._instances.discard(self)
if hasattr(self, "service") and getattr(self.service, "process", None): if hasattr(self, "service") and getattr(self.service, "process", None):
logger.debug("Stopping webdriver service") logger.debug("Stopping webdriver service")
with suppress(Exception): with suppress(Exception):
try: self.stop_client()
self.service.send_remote_shutdown_command() self.service.stop()
except TypeError:
pass
finally:
with suppress(Exception): with suppress(Exception):
self.service._terminate_process() if self.reactor and isinstance(self.reactor, Reactor):
if (
hasattr(self, "reactor")
and self.reactor
and hasattr(self.reactor, "event")
):
logger.debug("Shutting down Reactor") logger.debug("Shutting down Reactor")
with suppress(Exception):
self.reactor.event.set() self.reactor.event.set()
self.reactor.join(timeout=2)
self.reactor = None
if ( if (
hasattr(self, "keep_user_data_dir") hasattr(self, "keep_user_data_dir")
and hasattr(self, "user_data_dir") and hasattr(self, "user_data_dir")

View File

@ -53,58 +53,28 @@ class CDP:
self._session = requests.Session() self._session = requests.Session()
self._last_resp = None self._last_resp = None
self._last_json = None self._last_json = None
with requests.Session() as session: resp = self.get(self.endpoints.json)
resp = session.get( self.sessionId = resp[0]["id"]
self.server_addr + self.endpoints.json, self.wsurl = resp[0]["webSocketDebuggerUrl"]
headers={"Connection": "close"},
timeout=2,
)
self.sessionId = resp.json()[0]["id"]
self.wsurl = resp.json()[0]["webSocketDebuggerUrl"]
def tab_activate(self, id=None): def tab_activate(self, id=None):
if not id: if not id:
active_tab = self.tab_list()[0] active_tab = self.tab_list()[0]
id = active_tab.id id = active_tab.id
self.wsurl = active_tab.webSocketDebuggerUrl self.wsurl = active_tab.webSocketDebuggerUrl
with requests.Session() as session: return self.post(self.endpoints["activate"].format(id=id))
resp = session.post(
self.server_addr + self.endpoints["activate"].format(id=id),
headers={"Connection": "close"},
timeout=2,
)
return resp.json()
def tab_list(self): def tab_list(self):
with requests.Session() as session: retval = self.get(self.endpoints["list"])
resp = session.get(
self.server_addr + self.endpoints["list"],
headers={"Connection": "close"},
timeout=2,
)
retval = resp.json()
return [PageElement(o) for o in retval] return [PageElement(o) for o in retval]
def tab_new(self, url): def tab_new(self, url):
with requests.Session() as session: return self.post(self.endpoints["new"].format(url=url))
resp = session.post(
self.server_addr + self.endpoints["new"].format(url=url),
headers={"Connection": "close"},
timeout=2,
)
return resp.json()
def tab_close_last_opened(self): def tab_close_last_opened(self):
sessions = self.tab_list() sessions = self.tab_list()
opentabs = [s for s in sessions if s["type"] == "page"] opentabs = [s for s in sessions if s["type"] == "page"]
with requests.Session() as session: return self.post(self.endpoints["close"].format(id=opentabs[-1]["id"]))
endp_close = self.endpoints["close"]
resp = session.post(
self.server_addr + endp_close.format(id=opentabs[-1]["id"]),
headers={"Connection": "close"},
timeout=2,
)
return resp.json()
async def send(self, method, params): async def send(self, method, params):
pip_find_lock = fasteners.InterProcessLock( pip_find_lock = fasteners.InterProcessLock(
@ -131,12 +101,7 @@ class CDP:
from urllib.parse import unquote from urllib.parse import unquote
uri = unquote(uri, errors="strict") uri = unquote(uri, errors="strict")
with requests.Session() as session: resp = self._session.get(self.server_addr + uri)
resp = session.get(
self.server_addr + uri,
headers={"Connection": "close"},
timeout=2,
)
try: try:
self._last_resp = resp self._last_resp = resp
self._last_json = resp.json() self._last_json = resp.json()
@ -151,13 +116,7 @@ class CDP:
uri = unquote(uri, errors="strict") uri = unquote(uri, errors="strict")
if not data: if not data:
data = {} data = {}
with requests.Session() as session: resp = self._session.post(self.server_addr + uri, json=data)
resp = session.post(
self.server_addr + uri,
json=data,
headers={"Connection": "close"},
timeout=2,
)
try: try:
self._last_resp = resp self._last_resp = resp
self._last_json = resp.json() self._last_json = resp.json()

View File

@ -287,63 +287,10 @@ class Browser:
connection: tab.Tab = next( connection: tab.Tab = next(
filter(lambda item: item.type_ == "page", self.targets) filter(lambda item: item.type_ == "page", self.targets)
) )
if hasattr(sb_config, "_cdp_locale") and sb_config._cdp_locale:
await connection.send(cdp.page.navigate("about:blank"))
if (
hasattr(sb_config, "_cdp_user_agent")
and sb_config._cdp_user_agent
):
pass
elif (
hasattr(sb_config, "_cdp_platform")
and sb_config._cdp_platform
):
pass
else:
await connection.set_locale(sb_config._cdp_locale)
if hasattr(sb_config, "_cdp_timezone") and sb_config._cdp_timezone:
await connection.send(cdp.page.navigate("about:blank"))
await connection.set_timezone(sb_config._cdp_timezone)
if (
hasattr(sb_config, "_cdp_user_agent")
and sb_config._cdp_user_agent
):
await connection.send(cdp.page.navigate("about:blank"))
if hasattr(sb_config, "_cdp_locale") and sb_config._cdp_locale:
_cdp_platform = None
if (
hasattr(sb_config, "_cdp_platform")
and sb_config._cdp_platform
):
_cdp_platform = sb_config._cdp_platform
await connection.set_user_agent(
sb_config._cdp_user_agent,
sb_config._cdp_locale,
_cdp_platform,
)
else:
await connection.set_user_agent(sb_config._cdp_user_agent)
elif (
hasattr(sb_config, "_cdp_platform") and sb_config._cdp_platform
):
await connection.send(cdp.page.navigate("about:blank"))
if hasattr(sb_config, "_cdp_locale") and sb_config._cdp_locale:
_cdp_platform = sb_config._cdp_platform
await connection.set_user_agent(
accept_language=sb_config._cdp_locale,
platform=_cdp_platform,
)
else:
await connection.set_user_agent(
platform=sb_config._cdp_platform
)
if (
hasattr(sb_config, "_cdp_geolocation")
and sb_config._cdp_geolocation
):
await connection.send(cdp.page.navigate("about:blank"))
await connection.set_geolocation(sb_config._cdp_geolocation)
# Use the tab to navigate to new url # Use the tab to navigate to new url
if hasattr(sb_config, "_cdp_locale") and sb_config._cdp_locale:
await connection.send(cdp.page.navigate("about:blank"))
await connection.set_locale(sb_config._cdp_locale)
frame_id, loader_id, *_ = await connection.send( frame_id, loader_id, *_ = await connection.send(
cdp.page.navigate(url) cdp.page.navigate(url)
) )

View File

@ -10,10 +10,8 @@ import types
import typing import typing
from contextlib import suppress from contextlib import suppress
from seleniumbase import config as sb_config from seleniumbase import config as sb_config
from seleniumbase import extensions
from seleniumbase.config import settings from seleniumbase.config import settings
from seleniumbase.core import detect_b_ver from seleniumbase.core import detect_b_ver
from seleniumbase.core import download_helper
from seleniumbase.core import proxy_helper from seleniumbase.core import proxy_helper
from seleniumbase.fixtures import constants from seleniumbase.fixtures import constants
from seleniumbase.fixtures import shared_utils from seleniumbase.fixtures import shared_utils
@ -27,10 +25,7 @@ import mycdp as cdp
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
IS_LINUX = shared_utils.is_linux() IS_LINUX = shared_utils.is_linux()
DOWNLOADS_FOLDER = download_helper.get_downloads_folder()
PROXY_DIR_LOCK = proxy_helper.PROXY_DIR_LOCK PROXY_DIR_LOCK = proxy_helper.PROXY_DIR_LOCK
EXTENSIONS_DIR = os.path.dirname(os.path.realpath(extensions.__file__))
AD_BLOCK_ZIP_PATH = os.path.join(EXTENSIONS_DIR, "ad_block.zip")
T = typing.TypeVar("T") T = typing.TypeVar("T")
@ -173,19 +168,6 @@ def __add_chrome_ext_dir(extension_dir, dir_path):
return extension_dir return extension_dir
def __unzip_to_new_folder(zip_file, folder):
proxy_dir_lock = fasteners.InterProcessLock(PROXY_DIR_LOCK)
with proxy_dir_lock:
with suppress(Exception):
shared_utils.make_writable(PROXY_DIR_LOCK)
if not os.path.exists(folder):
import zipfile
zip_ref = zipfile.ZipFile(zip_file, "r")
os.makedirs(folder)
zip_ref.extractall(folder)
zip_ref.close()
def __add_chrome_proxy_extension( def __add_chrome_proxy_extension(
extension_dir, extension_dir,
proxy_string, proxy_string,
@ -249,7 +231,6 @@ async def start(
browser_executable_path: Optional[PathLike] = None, browser_executable_path: Optional[PathLike] = None,
browser_args: Optional[List[str]] = None, browser_args: Optional[List[str]] = None,
xvfb_metrics: Optional[List[str]] = None, # "Width,Height" for Linux xvfb_metrics: Optional[List[str]] = None, # "Width,Height" for Linux
ad_block: Optional[bool] = False,
sandbox: Optional[bool] = True, sandbox: Optional[bool] = True,
lang: Optional[str] = None, # Set the Language Locale Code lang: Optional[str] = None, # Set the Language Locale Code
host: Optional[str] = None, # Chrome remote-debugging-host host: Optional[str] = None, # Chrome remote-debugging-host
@ -257,10 +238,7 @@ async def start(
xvfb: Optional[int] = None, # Use a special virtual display on Linux xvfb: Optional[int] = None, # Use a special virtual display on Linux
headed: Optional[bool] = None, # Override default Xvfb mode on Linux headed: Optional[bool] = None, # Override default Xvfb mode on Linux
expert: Optional[bool] = None, # Open up closed Shadow-root elements expert: Optional[bool] = None, # Open up closed Shadow-root elements
agent: Optional[str] = None, # Set the user-agent string
proxy: Optional[str] = None, # "host:port" or "user:pass@host:port" proxy: Optional[str] = None, # "host:port" or "user:pass@host:port"
tzone: Optional[str] = None, # Eg "America/New_York", "Asia/Kolkata"
geoloc: Optional[list | tuple] = None, # Eg (48.87645, 2.26340)
extension_dir: Optional[str] = None, # Chrome extension directory extension_dir: Optional[str] = None, # Chrome extension directory
**kwargs: Optional[dict], **kwargs: Optional[dict],
) -> Browser: ) -> Browser:
@ -318,13 +296,6 @@ async def start(
proxy_user, proxy_user,
proxy_pass, proxy_pass,
) )
if ad_block:
incognito = False
guest = False
ad_block_zip = AD_BLOCK_ZIP_PATH
ad_block_dir = os.path.join(DOWNLOADS_FOLDER, "ad_block")
__unzip_to_new_folder(ad_block_zip, ad_block_dir)
extension_dir = __add_chrome_ext_dir(extension_dir, ad_block_dir)
if not config: if not config:
config = Config( config = Config(
user_data_dir, user_data_dir,
@ -358,26 +329,6 @@ async def start(
sb_config._cdp_locale = kwargs["locale_code"] sb_config._cdp_locale = kwargs["locale_code"]
else: else:
sb_config._cdp_locale = None sb_config._cdp_locale = None
if tzone:
sb_config._cdp_timezone = tzone
elif "timezone" in kwargs:
sb_config._cdp_timezone = kwargs["timezone"]
else:
sb_config._cdp_timezone = None
if geoloc:
sb_config._cdp_geolocation = geoloc
elif "geolocation" in kwargs:
sb_config._cdp_geolocation = kwargs["geolocation"]
else:
sb_config._cdp_geolocation = None
if agent:
sb_config._cdp_user_agent = agent
elif "user_agent" in kwargs:
sb_config._cdp_user_agent = kwargs["user_agent"]
else:
sb_config._cdp_user_agent = None
if "platform" in kwargs:
sb_config._cdp_platform = kwargs["platform"]
return driver return driver
@ -455,13 +406,7 @@ 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
try: driver.service.stop()
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

@ -19,7 +19,6 @@ from typing import (
) )
import websockets import websockets
from websockets.protocol import State from websockets.protocol import State
from seleniumbase import config as sb_config
from . import cdp_util as util from . import cdp_util as util
import mycdp as cdp import mycdp as cdp
import mycdp.network import mycdp.network
@ -271,7 +270,6 @@ class Connection(metaclass=CantTouchThis):
max_size=MAX_SIZE, max_size=MAX_SIZE,
) )
self.listener = Listener(self) self.listener = Listener(self)
sb_config._cdp_aclose = self.aclose
except (Exception,) as e: except (Exception,) as e:
logger.debug("Exception during opening of websocket: %s", e) logger.debug("Exception during opening of websocket: %s", e)
if self.listener: if self.listener:
@ -292,6 +290,10 @@ class Connection(metaclass=CantTouchThis):
Closes the websocket connection. Shouldn't be called manually by users. Closes the websocket connection. Shouldn't be called manually by users.
""" """
if self.websocket and self.websocket.state is not State.CLOSED: if self.websocket and self.websocket.state is not State.CLOSED:
if self.listener and self.listener.running:
self.listener.cancel()
self.enabled_domains.clear()
await asyncio.sleep(0.015)
try: try:
await self.websocket.close() await self.websocket.close()
except Exception: except Exception:
@ -299,9 +301,6 @@ class Connection(metaclass=CantTouchThis):
"\n❌ Error closing websocket connection to %s", "\n❌ Error closing websocket connection to %s",
self.websocket_url self.websocket_url
) )
if self.listener and self.listener.running:
self.listener.cancel()
self.enabled_domains.clear()
logger.debug( logger.debug(
"\n❌ Closed websocket connection to %s", self.websocket_url "\n❌ Closed websocket connection to %s", self.websocket_url
) )
@ -348,37 +347,7 @@ class Connection(metaclass=CantTouchThis):
async def set_locale(self, locale: Optional[str] = None): async def set_locale(self, locale: Optional[str] = None):
"""Sets the Language Locale code via set_user_agent_override.""" """Sets the Language Locale code via set_user_agent_override."""
await self.set_user_agent(user_agent="", accept_language=locale) await self.send(cdp.network.set_user_agent_override("", locale))
async def set_timezone(self, timezone: Optional[str] = None):
"""Sets the Timezone via set_timezone_override."""
await self.send(cdp.emulation.set_timezone_override(timezone))
async def set_user_agent(
self,
user_agent: Optional[str] = "",
accept_language: Optional[str] = None,
platform: Optional[str] = None, # navigator.platform
):
"""Sets the User Agent via set_user_agent_override."""
if not user_agent:
user_agent = ""
await self.send(cdp.network.set_user_agent_override(
user_agent=user_agent,
accept_language=accept_language,
platform=platform,
))
async def set_geolocation(self, geolocation: Optional[tuple] = None):
"""Sets the User Agent via set_geolocation_override."""
await self.send(cdp.browser.grant_permissions(
permissions=["geolocation"],
))
await self.send(cdp.emulation.set_geolocation_override(
latitude=geolocation[0],
longitude=geolocation[1],
accuracy=100,
))
def __getattr__(self, item): def __getattr__(self, item):
""":meta private:""" """:meta private:"""
@ -446,7 +415,6 @@ class Connection(metaclass=CantTouchThis):
if not _is_update: if not _is_update:
await self._register_handlers() await self._register_handlers()
await self.websocket.send(tx.message) await self.websocket.send(tx.message)
sb_config._cdp_aclose = self.aclose
try: try:
return await tx return await tx
except ProtocolException as e: except ProtocolException as e:
@ -579,7 +547,8 @@ class Listener:
except asyncio.TimeoutError: except asyncio.TimeoutError:
self.idle.set() self.idle.set()
# Pause for a moment. # Pause for a moment.
await asyncio.sleep(self.time_before_considered_idle / 10) # await asyncio.sleep(self.time_before_considered_idle / 10)
await asyncio.sleep(0.015)
continue continue
except (Exception,) as e: except (Exception,) as e:
logger.debug( logger.debug(

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.2.0'") os.system("python -m pip install 'flake8==7.1.2'")
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")
@ -148,9 +148,9 @@ setup(
python_requires=">=3.8", python_requires=">=3.8",
install_requires=[ install_requires=[
'pip>=25.0.1', 'pip>=25.0.1',
'packaging>=25.0', 'packaging>=24.2',
'setuptools~=70.2;python_version<"3.10"', # Newer ones had issues 'setuptools~=70.2;python_version<"3.10"', # Newer ones had issues
'setuptools>=79.0.1;python_version>="3.10"', 'setuptools>=78.1.0;python_version>="3.10"',
'wheel>=0.45.1', 'wheel>=0.45.1',
'attrs>=25.3.0', 'attrs>=25.3.0',
"certifi>=2025.1.31", "certifi>=2025.1.31",
@ -160,11 +160,11 @@ setup(
'filelock~=3.16.1;python_version<"3.9"', 'filelock~=3.16.1;python_version<"3.9"',
'filelock>=3.18.0;python_version>="3.9"', 'filelock>=3.18.0;python_version>="3.9"',
'fasteners>=0.19', 'fasteners>=0.19',
"mycdp>=1.2.0", "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.2', 'typing-extensions>=4.13.0',
"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,18 +182,18 @@ 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.5.0;python_version>="3.10"', 'urllib3>=1.26.20,<2.4.0;python_version>="3.10"',
'requests==2.32.3', 'requests==2.32.3',
'sniffio==1.3.1', 'sniffio==1.3.1',
'h11==0.16.0', 'h11==0.14.0',
'outcome==1.3.0.post0', 'outcome==1.3.0.post0',
'trio==0.27.0;python_version<"3.9"', 'trio==0.27.0;python_version<"3.9"',
'trio==0.30.0;python_version>="3.9"', 'trio==0.29.0;python_version>="3.9"',
'trio-websocket==0.12.2', '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.31.0;python_version>="3.9"', 'selenium==4.30.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",
@ -209,13 +209,13 @@ setup(
'pytest-xdist==3.6.1', 'pytest-xdist==3.6.1',
'parameterized==0.9.0', 'parameterized==0.9.0',
"behave==1.2.6", "behave==1.2.6",
'soupsieve==2.7', 'soupsieve==2.6',
"beautifulsoup4==4.13.4", "beautifulsoup4==4.13.3",
'pyotp==2.9.0', '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>=14.0.0,<15', 'rich==13.9.4',
], ],
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.8.0;python_version>="3.9"', 'coverage>=7.7.1;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.1.1;python_version>="3.9"', 'pytest-cov>=6.0.0;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.2.0;python_version>="3.9"', 'flake8==7.1.2;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.3.2;python_version>="3.9"', 'pyflakes==3.2.0;python_version>="3.9"',
'pycodestyle==2.9.1;python_version<"3.9"', 'pycodestyle==2.9.1;python_version<"3.9"',
'pycodestyle==2.13.0;python_version>="3.9"', 'pycodestyle==2.12.1;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".)
@ -261,7 +261,7 @@ setup(
# (An optional library for parsing PDF files.) # (An optional library for parsing PDF files.)
"pdfminer": [ "pdfminer": [
'pdfminer.six==20250324;python_version<"3.9"', 'pdfminer.six==20250324;python_version<"3.9"',
'pdfminer.six==20250416;python_version>="3.9"', 'pdfminer.six==20250327;python_version>="3.9"',
'cryptography==39.0.2;python_version<"3.9"', 'cryptography==39.0.2;python_version<"3.9"',
'cryptography==44.0.2;python_version>="3.9"', 'cryptography==44.0.2;python_version>="3.9"',
'cffi==1.17.1', 'cffi==1.17.1',
@ -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.2.1;python_version>="3.9"', 'Pillow>=11.1.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.)