diff --git a/examples/cdp_mode/playwright/raw_stealth_sync.py b/examples/cdp_mode/playwright/raw_stealth_sync.py new file mode 100644 index 00000000000..7cb59a5ad8b --- /dev/null +++ b/examples/cdp_mode/playwright/raw_stealth_sync.py @@ -0,0 +1,22 @@ +# Three stealth tests in one script! +from playwright.sync_api import expect, sync_playwright +from seleniumbase import sb_cdp + +sb = sb_cdp.Chrome(incognito=True) +endpoint_url = sb.get_endpoint_url() + +with sync_playwright() as p: + browser = p.chromium.connect_over_cdp(endpoint_url) + page = browser.contexts[0].pages[0] + page.goto("https://browserscan.net/bot-detection") + expect(page.locator('strong:has-text("Normal")')).to_be_visible() + page.goto("https://bot.sannysoft.com/") + expect(page.locator(".passed").first).to_be_visible() + expect(page.locator(".failed")).not_to_be_visible() + page.goto("https://www.planetminecraft.com/account") + page.locator('[name="email"]').press_sequentially("Name123", delay=105) + page.locator('[name="password"]').press_sequentially("Pass", delay=105) + sb.solve_captcha() + expect(page.locator('input[name="login"]')).to_be_visible() + expect(page.locator("input[disabled]")).not_to_be_visible() + page.wait_for_timeout(1000) diff --git a/examples/cdp_mode/raw_ad_blocking.py b/examples/cdp_mode/raw_ad_blocking.py index ab1016a9d6b..b76f31efca9 100644 --- a/examples/cdp_mode/raw_ad_blocking.py +++ b/examples/cdp_mode/raw_ad_blocking.py @@ -23,7 +23,7 @@ async def block_urls(tab): ] )) -with decorators.print_runtime("raw_ad_blocking.py"): +with decorators.print_runtime(): sb = sb_cdp.Chrome() loop = sb.get_event_loop() loop.run_until_complete(block_urls(sb.get_active_tab())) diff --git a/examples/cdp_mode/raw_basic_async.py b/examples/cdp_mode/raw_basic_async.py index 876a00846b7..cf28e8173b2 100644 --- a/examples/cdp_mode/raw_basic_async.py +++ b/examples/cdp_mode/raw_basic_async.py @@ -21,5 +21,5 @@ async def main(): driver.quit() if __name__ == "__main__": - with decorators.print_runtime("raw_basic_async.py"): + with decorators.print_runtime(): asyncio.run(main()) diff --git a/examples/cdp_mode/raw_cdp_cloudflare.py b/examples/cdp_mode/raw_cdp_cloudflare.py new file mode 100644 index 00000000000..332d90ba20b --- /dev/null +++ b/examples/cdp_mode/raw_cdp_cloudflare.py @@ -0,0 +1,17 @@ +from seleniumbase import sb_cdp + +sb = sb_cdp.Chrome() +sb.goto("https://dash.cloudflare.com/sign-up") +sb.sleep(3) +sb.solve_captcha() +sb.sleep(3) +cookie_value = None +for cookie in sb.get_all_cookies(): + if cookie.name == "cf_clearance": + cookie_value = cookie.value + break +if cookie_value: + print("cf_clearance cookie: %s" % cookie_value) +else: + print("Didn't find the cf_clearance cookie!") +sb.quit() diff --git a/examples/cdp_mode/raw_cdp_login.py b/examples/cdp_mode/raw_cdp_login.py index 1743ec2f7b5..e4ca27ac4ac 100644 --- a/examples/cdp_mode/raw_cdp_login.py +++ b/examples/cdp_mode/raw_cdp_login.py @@ -21,5 +21,5 @@ def main(): if __name__ == "__main__": - with decorators.print_runtime("raw_cdp_login.py"): + with decorators.print_runtime(): main() diff --git a/examples/cdp_mode/raw_cdp_swag.py b/examples/cdp_mode/raw_cdp_swag.py index 9bf4f07fad1..4ca6cd3e3b7 100644 --- a/examples/cdp_mode/raw_cdp_swag.py +++ b/examples/cdp_mode/raw_cdp_swag.py @@ -1,18 +1,26 @@ +from seleniumbase import decorators from seleniumbase import sb_cdp -sb = sb_cdp.Chrome() -sb.goto("https://www.saucedemo.com") -sb.type("#user-name", "standard_user") -sb.type("#password", "secret_sauce\n") -sb.assert_element("div.inventory_list") -sb.click('button[name*="backpack"]') -sb.click("#shopping_cart_container a") -sb.assert_text("Backpack", "div.cart_item") -sb.click("button#checkout") -sb.type("input#first-name", "SeleniumBase") -sb.type("input#last-name", "Automation") -sb.type("input#postal-code", "77123") -sb.click("input#continue") -sb.click("button#finish") -sb.assert_text("Thank you for your order!") -sb.quit() + +def main(): + sb = sb_cdp.Chrome(fast=True) + sb.goto("https://www.saucedemo.com") + sb.type("#user-name", "standard_user") + sb.type("#password", "secret_sauce\n") + sb.assert_element("div.inventory_list") + sb.click('button[name*="backpack"]') + sb.click("#shopping_cart_container a") + sb.assert_text("Backpack", "div.cart_item") + sb.click("button#checkout") + sb.type("input#first-name", "SeleniumBase") + sb.type("input#last-name", "Automation") + sb.type("input#postal-code", "77123") + sb.click("input#continue") + sb.click("button#finish") + sb.assert_text("Thank you for your order!") + sb.quit() + + +if __name__ == "__main__": + with decorators.print_runtime(): + main() diff --git a/examples/cdp_mode/raw_cf.py b/examples/cdp_mode/raw_cf.py index beb23acf2ee..cab063bd9f7 100644 --- a/examples/cdp_mode/raw_cf.py +++ b/examples/cdp_mode/raw_cf.py @@ -5,20 +5,17 @@ with SB(uc=True, test=True, guest=True) as sb: url = "https://seleniumbase.io/apps/turnstile" sb.activate_cdp_mode(url) - sb.sleep(3) sb.uc_gui_handle_captcha() # PyAutoGUI Tabs + Spacebar - sb.sleep(3) + sb.sleep(2) with SB(uc=True, test=True, guest=True) as sb: url = "https://seleniumbase.io/apps/turnstile" sb.activate_cdp_mode(url) - sb.sleep(4) sb.uc_gui_click_captcha() # PyAutoGUI mouse click - sb.sleep(3) + sb.sleep(2) with SB(uc=True, test=True, guest=True) as sb: url = "https://seleniumbase.io/apps/turnstile" sb.activate_cdp_mode(url) - sb.sleep(4) sb.solve_captcha() # CDP Input.dispatchMouseEvent - sb.sleep(3) + sb.sleep(2) diff --git a/examples/cdp_mode/raw_cf_clearance.py b/examples/cdp_mode/raw_cf_clearance.py index 6cf2eed7760..1224e87b17b 100644 --- a/examples/cdp_mode/raw_cf_clearance.py +++ b/examples/cdp_mode/raw_cf_clearance.py @@ -2,8 +2,7 @@ def get_cf_clearance_cookie(sb): - all_cookies = sb.get_all_cookies() - for cookie in all_cookies: + for cookie in sb.get_all_cookies(): if cookie.name == "cf_clearance": return cookie return None diff --git a/examples/cdp_mode/raw_mobile_async.py b/examples/cdp_mode/raw_mobile_async.py index 1a087e59cc8..91058a805a2 100644 --- a/examples/cdp_mode/raw_mobile_async.py +++ b/examples/cdp_mode/raw_mobile_async.py @@ -25,5 +25,5 @@ async def main(): driver.stop() if __name__ == "__main__": - with decorators.print_runtime("raw_mobile_async.py"): + with decorators.print_runtime(): asyncio.run(main()) diff --git a/examples/cdp_mode/raw_multi_async.py b/examples/cdp_mode/raw_multi_async.py index 7cf5e0b8b01..b15aeb34498 100644 --- a/examples/cdp_mode/raw_multi_async.py +++ b/examples/cdp_mode/raw_multi_async.py @@ -23,7 +23,7 @@ def run_main(url): if __name__ == "__main__": urls = ["https://seleniumbase.io/demo_page" for i in range(5)] - with decorators.print_runtime("raw_multi_async.py"): + with decorators.print_runtime(): with ThreadPoolExecutor(max_workers=len(urls)) as executor: for url in urls: executor.submit(run_main, url) diff --git a/examples/cdp_mode/raw_multi_c_async.py b/examples/cdp_mode/raw_multi_c_async.py index 6257f8a6873..efbd46ff7b7 100644 --- a/examples/cdp_mode/raw_multi_c_async.py +++ b/examples/cdp_mode/raw_multi_c_async.py @@ -25,7 +25,7 @@ def run_main(url): if __name__ == "__main__": urls = ["https://seleniumbase.io/apps/turnstile" for i in range(4)] - with decorators.print_runtime("raw_multi_c_async.py"): + with decorators.print_runtime(): with ThreadPoolExecutor(max_workers=len(urls)) as executor: for url in urls: executor.submit(run_main, url) diff --git a/examples/cdp_mode/raw_multi_captcha.py b/examples/cdp_mode/raw_multi_captcha.py index c1c39ba6c5e..029e44e1430 100644 --- a/examples/cdp_mode/raw_multi_captcha.py +++ b/examples/cdp_mode/raw_multi_captcha.py @@ -17,7 +17,7 @@ def main(url): if __name__ == "__main__": urls = ["https://www.bing.com/turing/captcha/challenge" for i in range(4)] - with decorators.print_runtime("raw_multi_captcha.py"): + with decorators.print_runtime(): with ThreadPoolExecutor(max_workers=len(urls)) as executor: for url in urls: executor.submit(main, url) diff --git a/examples/cdp_mode/raw_multi_cdp.py b/examples/cdp_mode/raw_multi_cdp.py index 20c464d2781..14d19ee9fb4 100644 --- a/examples/cdp_mode/raw_multi_cdp.py +++ b/examples/cdp_mode/raw_multi_cdp.py @@ -18,7 +18,7 @@ def main(url): if __name__ == "__main__": urls = ["https://seleniumbase.io/demo_page" for i in range(5)] - with decorators.print_runtime("raw_multi_cdp.py"): + with decorators.print_runtime(): with ThreadPoolExecutor(max_workers=len(urls)) as executor: for url in urls: executor.submit(main, url) diff --git a/examples/cdp_mode/raw_tiktok.py b/examples/cdp_mode/raw_tiktok.py index 75043de866c..1054d056480 100644 --- a/examples/cdp_mode/raw_tiktok.py +++ b/examples/cdp_mode/raw_tiktok.py @@ -9,7 +9,7 @@ print("*** " + sb.get_text('h2[data-e2e="user-bio"]')) sb.click_if_visible('button:contains("Refresh")') sb.sleep(0.3) - for i in range(33): + for i in range(30): sb.scroll_by_y(33) sb.sleep(0.03) items = sb.find_elements("picture img") diff --git a/mcp_servers/pyproject.toml b/mcp_servers/pyproject.toml index d61f6d10687..9898a99f4f0 100644 --- a/mcp_servers/pyproject.toml +++ b/mcp_servers/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "seleniumbase-mcp" -version = "1.5.1dev0" +version = "1.5.2dev0" description = "MCP server exposing SeleniumBase CDP Mode as tools for MCP clients." readme = "README.md" requires-python = ">=3.10" diff --git a/mkdocs_build/requirements.txt b/mkdocs_build/requirements.txt index 69a5c5897e4..e67d29a8e53 100644 --- a/mkdocs_build/requirements.txt +++ b/mkdocs_build/requirements.txt @@ -1,11 +1,11 @@ # mkdocs dependencies for generating the seleniumbase.io website regex>=2026.9.29 -pipdeptree>=4.2.7 -platformdirs>=4.13.0 -nab>=0.0.19 +pipdeptree>=4.2.8 +platformdirs>=4.13.3 +nab>=0.0.21 backrefs>=8.1 -pymdown-extensions>=12.2 +pymdown-extensions>=12.3 python-dateutil>=2.8.2 click>=8.5.0 Markdown==3.11.1 diff --git a/requirements.txt b/requirements.txt index f050889cd77..5fd1fe376e4 100755 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,7 @@ exceptiongroup>=1.3.1 websockets~=16.1.1;python_version=="3.10" websockets>=16.1.1;python_version>="3.11" filelock==4.1.0;python_version=="3.10" -filelock>=4.1.1;python_version>="3.11" +filelock>=4.2.0;python_version>="3.11" fasteners>=0.20 mycdp>=1.4.0 pynose>=1.5.5 @@ -43,7 +43,8 @@ cssselect>=1.6.0,<2;python_version>="3.11" sortedcontainers==2.4.0 execnet==2.1.2 iniconfig~=2.3.1 -pluggy==1.6.0 +pluggy==1.6.0;python_version<"3.11" +pluggy>=1.6.0,<1.8;python_version>="3.11" pytest==8.4.2;python_version<"3.11" pytest==9.1.1;python_version>="3.11" pytest-html==4.0.2 diff --git a/seleniumbase/__version__.py b/seleniumbase/__version__.py index 6502a6dd862..6b4275e1cc1 100755 --- a/seleniumbase/__version__.py +++ b/seleniumbase/__version__.py @@ -1,2 +1,2 @@ # seleniumbase package -__version__ = "4.56.1" +__version__ = "4.56.2" diff --git a/seleniumbase/common/decorators.py b/seleniumbase/common/decorators.py index 8d0b6916996..6c444b66752 100644 --- a/seleniumbase/common/decorators.py +++ b/seleniumbase/common/decorators.py @@ -1,6 +1,7 @@ import colorama import logging import math +import os import sys import time import warnings @@ -34,7 +35,22 @@ def my_method(): # code ... # code ... """ if not description: - description = "Code Block" + frame = sys._getframe() + current_file = "decorators.py" + if "__file__" in globals(): + current_file = os.path.basename(__file__) + while frame: + filename = frame.f_code.co_filename + base_name = os.path.basename(filename) + if base_name not in ("contextlib.py", current_file): + break + frame = frame.f_back + if frame: + filename = os.path.basename(frame.f_code.co_filename) + lineno = frame.f_lineno + description = f"{filename}:{lineno}" + else: + description = "Code Block" description = str(description) if limit: limit = float("%.2f" % limit) diff --git a/server.json b/server.json index 96a36265d01..6e785b30418 100644 --- a/server.json +++ b/server.json @@ -3,7 +3,7 @@ "name": "io.github.seleniumbase/seleniumbase", "title": "SeleniumBase MCP", "description": "Stealthy browser automation, testing, and web-scraping via CDP Mode.", - "version": "4.56.1", + "version": "4.56.2", "repository": { "url": "https://github.com/seleniumbase/SeleniumBase", "source": "github" @@ -13,7 +13,7 @@ { "registryType": "pypi", "identifier": "seleniumbase", - "version": "4.56.1", + "version": "4.56.2", "transport": { "type": "stdio" }, diff --git a/setup.py b/setup.py index e8cafc15ead..c80f4a5ba74 100755 --- a/setup.py +++ b/setup.py @@ -174,7 +174,7 @@ 'websockets~=16.1.1;python_version=="3.10"', 'websockets>=16.1.1;python_version>="3.11"', 'filelock==4.1.0;python_version=="3.10"', - 'filelock>=4.1.1;python_version>="3.11"', + 'filelock>=4.2.0;python_version>="3.11"', 'fasteners>=0.20', 'mycdp>=1.4.0', 'pynose>=1.5.5', @@ -209,7 +209,8 @@ 'sortedcontainers==2.4.0', 'execnet==2.1.2', 'iniconfig~=2.3.1', - 'pluggy==1.6.0', + 'pluggy==1.6.0;python_version<"3.11"', + 'pluggy>=1.6.0,<1.8;python_version>="3.11"', 'pytest==8.4.2;python_version<"3.11"', 'pytest==9.1.1;python_version>="3.11"', 'pytest-html==4.0.2', # Newer ones had issues @@ -293,9 +294,10 @@ # pip install -e .[playwright] # (For the Playwright integration.) "playwright": [ - 'playwright>=1.63.0', + 'playwright>=1.64.0', 'greenlet>=3.5.6', - 'pyee>=13.0.1', + 'pyee>=13.0.1;python_version<"3.12"', + 'pyee>=14.0.0;python_version>="3.12"', ], # pip install -e .[pyautogui] # (Already a required dependency on Linux now.) @@ -305,7 +307,7 @@ # pip install -e .[uv] # (For the MCP integration and more.) "uv": [ - "uv>=0.13.0" + "uv>=0.13.1" ], }, packages=[