Compare commits
No commits in common. "master" and "v4.36.4" have entirely different histories.
|
@ -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="Featured|HelloGitHub" /></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="Featured|HelloGitHub" /></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>
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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"]')
|
||||||
|
|
|
@ -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()
|
|
|
@ -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"]')
|
||||||
|
|
|
@ -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"]'
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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"):
|
||||||
|
|
|
@ -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()
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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"]'
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
# seleniumbase package
|
# seleniumbase package
|
||||||
__version__ = "4.37.7"
|
__version__ = "4.36.4"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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__,
|
||||||
|
|
|
@ -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 (
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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)
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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(
|
||||||
|
|
38
setup.py
38
setup.py
|
@ -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.)
|
||||||
|
|
Loading…
Reference in New Issue