seleniumbase 4.54.5__tar.gz → 4.54.7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {seleniumbase-4.54.5/seleniumbase.egg-info → seleniumbase-4.54.7}/PKG-INFO +6 -6
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/mcp_servers/server.py +253 -52
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/requirements.txt +2 -2
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/__version__.py +1 -1
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/undetected/cdp_driver/tab.py +19 -11
- {seleniumbase-4.54.5 → seleniumbase-4.54.7/seleniumbase.egg-info}/PKG-INFO +6 -6
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase.egg-info/requires.txt +5 -5
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/setup.py +5 -5
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/.gitignore +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/LICENSE +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/MANIFEST.in +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/README.md +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/install.sh +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/mcp_servers/__init__.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/pyproject.toml +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/pytest.ini +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/sbase/__init__.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/sbase/__main__.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/sbase/steps.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/__init__.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/__main__.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/behave/__init__.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/behave/behave_helper.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/behave/behave_sb.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/behave/steps.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/common/__init__.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/common/decorators.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/common/encryption.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/common/exceptions.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/common/obfuscate.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/common/unobfuscate.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/config/__init__.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/config/ad_block_list.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/config/proxy_list.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/config/settings.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/console_scripts/__init__.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/console_scripts/logo_helper.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/console_scripts/rich_helper.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/console_scripts/run.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/console_scripts/sb_behave_gui.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/console_scripts/sb_caseplans.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/console_scripts/sb_commander.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/console_scripts/sb_install.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/console_scripts/sb_mkchart.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/console_scripts/sb_mkdir.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/console_scripts/sb_mkfile.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/console_scripts/sb_mkpres.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/console_scripts/sb_mkrec.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/console_scripts/sb_objectify.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/console_scripts/sb_print.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/console_scripts/sb_recorder.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/core/__init__.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/core/application_manager.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/core/browser_launcher.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/core/capabilities_parser.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/core/colored_traceback.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/core/create_db_tables.sql +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/core/detect_b_ver.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/core/download_helper.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/core/encoded_images.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/core/jqc_helper.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/core/log_helper.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/core/mysql.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/core/nest_asyncio.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/core/proxy_helper.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/core/recorder_helper.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/core/report_helper.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/core/s3_manager.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/core/sb_cdp.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/core/sb_driver.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/core/session_helper.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/core/settings_parser.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/core/style_sheet.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/core/testcase_manager.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/core/tour_helper.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/core/visual_helper.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/drivers/__init__.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/drivers/brave_drivers/__init__.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/drivers/cft_drivers/__init__.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/drivers/chromium_drivers/__init__.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/drivers/chs_drivers/__init__.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/drivers/comet_drivers/__init__.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/drivers/opera_drivers/__init__.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/extensions/__init__.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/extensions/ad_block.zip +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/extensions/disable_csp.zip +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/extensions/recorder.zip +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/extensions/sbase_ext.zip +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/fixtures/__init__.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/fixtures/base_case.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/fixtures/constants.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/fixtures/css_to_xpath.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/fixtures/errors.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/fixtures/js_utils.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/fixtures/page_actions.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/fixtures/page_utils.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/fixtures/shared_utils.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/fixtures/unittest_helper.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/fixtures/words.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/fixtures/xpath_to_css.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/js_code/__init__.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/js_code/active_css_js.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/js_code/live_js.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/js_code/recorder_js.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/masterqa/__init__.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/masterqa/master_qa.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/plugins/__init__.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/plugins/base_plugin.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/plugins/basic_test_info.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/plugins/db_reporting_plugin.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/plugins/driver_manager.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/plugins/page_source.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/plugins/pytest_plugin.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/plugins/s3_logging_plugin.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/plugins/sb_manager.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/plugins/screen_shots.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/plugins/selenium_plugin.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/resources/__init__.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/translate/__init__.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/translate/chinese.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/translate/dutch.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/translate/french.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/translate/italian.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/translate/japanese.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/translate/korean.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/translate/master_dict.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/translate/portuguese.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/translate/russian.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/translate/spanish.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/translate/translator.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/undetected/__init__.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/undetected/cdp.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/undetected/cdp_driver/__init__.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/undetected/cdp_driver/_contradict.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/undetected/cdp_driver/browser.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/undetected/cdp_driver/cdp_util.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/undetected/cdp_driver/config.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/undetected/cdp_driver/connection.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/undetected/cdp_driver/element.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/undetected/dprocess.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/undetected/options.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/undetected/patcher.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/undetected/reactor.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/undetected/webelement.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/utilities/__init__.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/utilities/selenium_grid/__init__.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/utilities/selenium_grid/download_selenium_server.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/utilities/selenium_grid/font_color +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/utilities/selenium_grid/grid-hub +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/utilities/selenium_grid/grid-node +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/utilities/selenium_grid/grid_hub.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/utilities/selenium_grid/grid_node.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/utilities/selenium_grid/register-grid-node.bat +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/utilities/selenium_grid/register-grid-node.sh +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/utilities/selenium_grid/start-grid-hub.bat +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/utilities/selenium_grid/start-grid-hub.sh +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/utilities/selenium_ide/__init__.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase/utilities/selenium_ide/convert_ide.py +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase.egg-info/SOURCES.txt +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase.egg-info/dependency_links.txt +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase.egg-info/entry_points.txt +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/seleniumbase.egg-info/top_level.txt +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/setup.cfg +0 -0
- {seleniumbase-4.54.5 → seleniumbase-4.54.7}/virtualenv_install.sh +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: seleniumbase
|
|
3
|
-
Version: 4.54.
|
|
3
|
+
Version: 4.54.7
|
|
4
4
|
Summary: SeleniumBase is a framework for web crawling, scraping, and testing. Supports pytest. CDP Mode adds stealth. Includes many tools.
|
|
5
5
|
Home-page: https://github.com/seleniumbase/SeleniumBase
|
|
6
6
|
Author: Michael Mintz
|
|
@@ -67,7 +67,7 @@ Requires-Dist: certifi>=2026.7.22
|
|
|
67
67
|
Requires-Dist: exceptiongroup>=1.3.1
|
|
68
68
|
Requires-Dist: websockets~=16.1.1; python_version == "3.10"
|
|
69
69
|
Requires-Dist: websockets>=16.1.1; python_version >= "3.11"
|
|
70
|
-
Requires-Dist: filelock>=3.32.
|
|
70
|
+
Requires-Dist: filelock>=3.32.7
|
|
71
71
|
Requires-Dist: fasteners>=0.20
|
|
72
72
|
Requires-Dist: mycdp>=1.4.0
|
|
73
73
|
Requires-Dist: pynose>=1.5.5
|
|
@@ -88,7 +88,7 @@ Requires-Dist: tabcompleter>=1.4.1
|
|
|
88
88
|
Requires-Dist: pdbp>=1.8.2
|
|
89
89
|
Requires-Dist: idna>=3.19
|
|
90
90
|
Requires-Dist: charset-normalizer<4,>=3.5.1
|
|
91
|
-
Requires-Dist: urllib3<3,>=2.
|
|
91
|
+
Requires-Dist: urllib3<3,>=2.8.0
|
|
92
92
|
Requires-Dist: requests~=2.34.2
|
|
93
93
|
Requires-Dist: sniffio==1.3.1
|
|
94
94
|
Requires-Dist: h11==0.16.0
|
|
@@ -149,13 +149,13 @@ Requires-Dist: pip-system-certs>=4.0; platform_system == "Windows" and extra ==
|
|
|
149
149
|
Provides-Extra: proxy
|
|
150
150
|
Requires-Dist: proxy.py==2.4.3; extra == "proxy"
|
|
151
151
|
Provides-Extra: playwright
|
|
152
|
-
Requires-Dist: playwright>=1.
|
|
153
|
-
Requires-Dist: greenlet>=3.5.
|
|
152
|
+
Requires-Dist: playwright>=1.63.0; extra == "playwright"
|
|
153
|
+
Requires-Dist: greenlet>=3.5.6; extra == "playwright"
|
|
154
154
|
Requires-Dist: pyee>=13.0.1; extra == "playwright"
|
|
155
155
|
Provides-Extra: pyautogui
|
|
156
156
|
Requires-Dist: PyAutoGUI>=0.9.54; platform_system != "Linux" and extra == "pyautogui"
|
|
157
157
|
Provides-Extra: uv
|
|
158
|
-
Requires-Dist: uv>=0.12.
|
|
158
|
+
Requires-Dist: uv>=0.12.15; extra == "uv"
|
|
159
159
|
Dynamic: author
|
|
160
160
|
Dynamic: author-email
|
|
161
161
|
Dynamic: classifier
|
|
@@ -51,6 +51,17 @@ Tool-selection philosophy:
|
|
|
51
51
|
- Use 'focus_element' for element positioning and visual focus.
|
|
52
52
|
- Use 'solve_captcha' for clicking the checkbox of a CAPTCHA on the page.
|
|
53
53
|
- Use 'save_page' for saving page output as a PNG, a PDF, or an HTML file.
|
|
54
|
+
|
|
55
|
+
Tool annotations:
|
|
56
|
+
Where a tool has one consistent behavior, it declares MCP tool annotations
|
|
57
|
+
(title + read_only_hint/destructive_hint/idempotent_hint/open_world_hint)
|
|
58
|
+
so clients can make informed UX/confirmation decisions without calling it.
|
|
59
|
+
Several tools here consolidate multiple related actions behind a single
|
|
60
|
+
`action`/`mode` parameter (e.g. manage_cookies: get_all/clear/save/load).
|
|
61
|
+
Where those actions genuinely differ in kind -- some read-only, some
|
|
62
|
+
destructive, some writing to disk -- no single annotation value would be
|
|
63
|
+
honest for the whole tool, so annotations are intentionally omitted for
|
|
64
|
+
those (title is still provided). See the per-tool comments below.
|
|
54
65
|
"""
|
|
55
66
|
from __future__ import annotations
|
|
56
67
|
import atexit
|
|
@@ -59,6 +70,7 @@ from functools import wraps
|
|
|
59
70
|
from typing import Any, Literal
|
|
60
71
|
from mcp.server import MCPServer
|
|
61
72
|
from mcp.server.mcpserver.exceptions import ToolError
|
|
73
|
+
from mcp.types import ToolAnnotations
|
|
62
74
|
from seleniumbase import sb_cdp
|
|
63
75
|
|
|
64
76
|
mcp = MCPServer("seleniumbase-mcp")
|
|
@@ -99,7 +111,17 @@ def handle_sb_errors(func):
|
|
|
99
111
|
# Session lifecycle
|
|
100
112
|
# ---------------------------------------------------------------------------
|
|
101
113
|
|
|
102
|
-
@mcp.tool(
|
|
114
|
+
@mcp.tool(
|
|
115
|
+
title="Start Browser",
|
|
116
|
+
annotations=ToolAnnotations(
|
|
117
|
+
# Single, consistent behavior: launches (or no-ops if already
|
|
118
|
+
# running) a persistent browser session.
|
|
119
|
+
read_only_hint=False,
|
|
120
|
+
destructive_hint=False,
|
|
121
|
+
idempotent_hint=True, # No-ops with the same message if already up.
|
|
122
|
+
open_world_hint=True, # Launches a real browser onto the open web.
|
|
123
|
+
),
|
|
124
|
+
)
|
|
103
125
|
def start_browser(
|
|
104
126
|
url: str | None = None,
|
|
105
127
|
headless: Literal[False, True, None] = None,
|
|
@@ -245,7 +267,17 @@ def start_browser(
|
|
|
245
267
|
)
|
|
246
268
|
|
|
247
269
|
|
|
248
|
-
@mcp.tool(
|
|
270
|
+
@mcp.tool(
|
|
271
|
+
title="Close Browser",
|
|
272
|
+
annotations=ToolAnnotations(
|
|
273
|
+
# Single, consistent behavior: Ends the persistent browser session
|
|
274
|
+
# (or no-ops if already stopped).
|
|
275
|
+
read_only_hint=False,
|
|
276
|
+
idempotent_hint=True, # Once closed, repeated calls have no further
|
|
277
|
+
# effect.
|
|
278
|
+
open_world_hint=False, # Local teardown of a server-owned resource.
|
|
279
|
+
),
|
|
280
|
+
)
|
|
249
281
|
def close_browser() -> str:
|
|
250
282
|
"""Close the active browser session and release browser resources.
|
|
251
283
|
|
|
@@ -278,7 +310,15 @@ def close_browser() -> str:
|
|
|
278
310
|
# Page information
|
|
279
311
|
# ---------------------------------------------------------------------------
|
|
280
312
|
|
|
281
|
-
@mcp.tool(
|
|
313
|
+
@mcp.tool(
|
|
314
|
+
title="Get Page Info",
|
|
315
|
+
annotations=ToolAnnotations(
|
|
316
|
+
read_only_hint=True,
|
|
317
|
+
destructive_hint=False,
|
|
318
|
+
idempotent_hint=True,
|
|
319
|
+
open_world_hint=True,
|
|
320
|
+
),
|
|
321
|
+
)
|
|
282
322
|
@handle_sb_errors
|
|
283
323
|
def get_page_info() -> dict[str, Any]:
|
|
284
324
|
"""Get current browser session and page metadata.
|
|
@@ -287,7 +327,7 @@ def get_page_info() -> dict[str, Any]:
|
|
|
287
327
|
is after navigation, clicks, form submissions, redirects, reloads, or
|
|
288
328
|
tab switches.
|
|
289
329
|
|
|
290
|
-
This is a
|
|
330
|
+
This is a read-only metadata operation: It does not inspect arbitrary
|
|
291
331
|
page content, find elements, check visibility, wait for conditions, or
|
|
292
332
|
assert expected values.
|
|
293
333
|
|
|
@@ -337,7 +377,15 @@ def get_page_info() -> dict[str, Any]:
|
|
|
337
377
|
# Navigation
|
|
338
378
|
# ---------------------------------------------------------------------------
|
|
339
379
|
|
|
340
|
-
@mcp.tool(
|
|
380
|
+
@mcp.tool(
|
|
381
|
+
title="Open URL",
|
|
382
|
+
annotations=ToolAnnotations(
|
|
383
|
+
read_only_hint=False,
|
|
384
|
+
destructive_hint=False,
|
|
385
|
+
idempotent_hint=False,
|
|
386
|
+
open_world_hint=True,
|
|
387
|
+
),
|
|
388
|
+
)
|
|
341
389
|
@handle_sb_errors
|
|
342
390
|
def open_url(url: str) -> str:
|
|
343
391
|
"""Navigate the current browser tab to the URL provided.
|
|
@@ -372,15 +420,18 @@ def open_url(url: str) -> str:
|
|
|
372
420
|
return f"Navigated to {url}"
|
|
373
421
|
|
|
374
422
|
|
|
375
|
-
@mcp.tool()
|
|
423
|
+
@mcp.tool(title="Manage History")
|
|
424
|
+
# Annotations intentionally omitted: 'list' is a pure read, while
|
|
425
|
+
# 'back'/'forward'/'reload' each navigate and modify browser state -- no
|
|
426
|
+
# single read_only_hint value would be accurate for the whole tool.
|
|
376
427
|
@handle_sb_errors
|
|
377
428
|
def manage_history(
|
|
378
429
|
action: Literal["back", "forward", "reload", "list"] = "list",
|
|
379
430
|
) -> str | dict[str, Any]:
|
|
380
431
|
"""Manage or inspect the current browser tab's navigation history.
|
|
381
432
|
|
|
382
|
-
Use
|
|
383
|
-
|
|
433
|
+
Use "back", "forward", or "reload" actions for history navigation.
|
|
434
|
+
Use "list" to inspect history. (This one is read-only.)
|
|
384
435
|
Use 'open_url' for navigation to an arbitrary URL.
|
|
385
436
|
|
|
386
437
|
Args:
|
|
@@ -391,7 +442,7 @@ def manage_history(
|
|
|
391
442
|
- "list": Return the current history position and entries.
|
|
392
443
|
|
|
393
444
|
Navigation actions can trigger page loads or redirects.
|
|
394
|
-
Use get_page_info afterward to verify the resulting URL or title.
|
|
445
|
+
Use 'get_page_info' afterward to verify the resulting URL or title.
|
|
395
446
|
"""
|
|
396
447
|
sb = _get_sb()
|
|
397
448
|
|
|
@@ -433,7 +484,15 @@ def manage_history(
|
|
|
433
484
|
# Finding & reading
|
|
434
485
|
# ---------------------------------------------------------------------------
|
|
435
486
|
|
|
436
|
-
@mcp.tool(
|
|
487
|
+
@mcp.tool(
|
|
488
|
+
title="Find Elements",
|
|
489
|
+
annotations=ToolAnnotations(
|
|
490
|
+
read_only_hint=True,
|
|
491
|
+
destructive_hint=False,
|
|
492
|
+
idempotent_hint=True,
|
|
493
|
+
open_world_hint=True,
|
|
494
|
+
),
|
|
495
|
+
)
|
|
437
496
|
@handle_sb_errors
|
|
438
497
|
def find_elements(
|
|
439
498
|
selector: str,
|
|
@@ -514,17 +573,22 @@ def find_elements(
|
|
|
514
573
|
}
|
|
515
574
|
|
|
516
575
|
|
|
517
|
-
@mcp.tool(
|
|
576
|
+
@mcp.tool(
|
|
577
|
+
title="Get Content",
|
|
578
|
+
annotations=ToolAnnotations(
|
|
579
|
+
read_only_hint=True,
|
|
580
|
+
destructive_hint=False,
|
|
581
|
+
idempotent_hint=True,
|
|
582
|
+
open_world_hint=True,
|
|
583
|
+
),
|
|
584
|
+
)
|
|
518
585
|
@handle_sb_errors
|
|
519
586
|
def get_content(
|
|
520
587
|
selector: str = "body",
|
|
521
588
|
output_format: Literal["text", "html", "urls"] = "text",
|
|
522
589
|
timeout: float = 5,
|
|
523
590
|
) -> str | list[str]:
|
|
524
|
-
"""
|
|
525
|
-
|
|
526
|
-
Use this tool when you need to get actual page content or URL information
|
|
527
|
-
rather than page metadata.
|
|
591
|
+
"""Get visible text, HTML, or discovered URLs from the selected element.
|
|
528
592
|
|
|
529
593
|
Args:
|
|
530
594
|
selector: CSS selector or SeleniumBase-supported XPath selector.
|
|
@@ -567,18 +631,24 @@ def get_content(
|
|
|
567
631
|
)
|
|
568
632
|
|
|
569
633
|
|
|
570
|
-
@mcp.tool(
|
|
634
|
+
@mcp.tool(
|
|
635
|
+
title="Get Attributes",
|
|
636
|
+
annotations=ToolAnnotations(
|
|
637
|
+
read_only_hint=True,
|
|
638
|
+
destructive_hint=False,
|
|
639
|
+
idempotent_hint=True,
|
|
640
|
+
open_world_hint=True,
|
|
641
|
+
),
|
|
642
|
+
)
|
|
571
643
|
@handle_sb_errors
|
|
572
644
|
def get_attributes(
|
|
573
645
|
selector: str,
|
|
574
646
|
attribute: str | None = None,
|
|
575
647
|
timeout: float = 5,
|
|
576
648
|
) -> str | dict[str, Any] | None:
|
|
577
|
-
"""
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
or all HTML attributes of an element. Attributes could be something
|
|
581
|
-
such as href, src, value, class, id, name, type, aria-label, etc.
|
|
649
|
+
"""Get a specific HTML attribute (or all attributes) from the
|
|
650
|
+
first-matching element. Examples of possible attributes include
|
|
651
|
+
href, src, value, class, id, name, type, aria-label, etc.
|
|
582
652
|
|
|
583
653
|
Args:
|
|
584
654
|
selector: CSS selector or SeleniumBase-supported XPath selector.
|
|
@@ -597,8 +667,6 @@ def get_attributes(
|
|
|
597
667
|
- Need to check element presence/visibility ->
|
|
598
668
|
use 'check_if_condition'.
|
|
599
669
|
|
|
600
|
-
This is a read-only operation.
|
|
601
|
-
|
|
602
670
|
If there's no matching element found within the timeout,
|
|
603
671
|
then @handle_sb_errors returns details from the exception raised.
|
|
604
672
|
"""
|
|
@@ -610,7 +678,15 @@ def get_attributes(
|
|
|
610
678
|
return sb.get_element_attributes(selector, timeout=timeout)
|
|
611
679
|
|
|
612
680
|
|
|
613
|
-
@mcp.tool(
|
|
681
|
+
@mcp.tool(
|
|
682
|
+
title="Check Condition",
|
|
683
|
+
annotations=ToolAnnotations(
|
|
684
|
+
read_only_hint=True,
|
|
685
|
+
destructive_hint=False,
|
|
686
|
+
idempotent_hint=True,
|
|
687
|
+
open_world_hint=True,
|
|
688
|
+
),
|
|
689
|
+
)
|
|
614
690
|
@handle_sb_errors
|
|
615
691
|
def check_if_condition(
|
|
616
692
|
check: Literal["present", "visible"] = "visible",
|
|
@@ -659,8 +735,6 @@ def check_if_condition(
|
|
|
659
735
|
This tool does not intentionally wait for elements or text to appear.
|
|
660
736
|
It is intended for checking the current state only. If page timing or
|
|
661
737
|
asynchronous loading matters, use wait_for_condition instead.
|
|
662
|
-
|
|
663
|
-
When `text` is provided, `check` is ignored.
|
|
664
738
|
"""
|
|
665
739
|
sb = _get_sb()
|
|
666
740
|
|
|
@@ -682,7 +756,20 @@ def check_if_condition(
|
|
|
682
756
|
# Interacting with elements
|
|
683
757
|
# ---------------------------------------------------------------------------
|
|
684
758
|
|
|
685
|
-
@mcp.tool(
|
|
759
|
+
@mcp.tool(
|
|
760
|
+
title="Click Element",
|
|
761
|
+
annotations=ToolAnnotations(
|
|
762
|
+
# These settings are consistent across all click modes. The tool
|
|
763
|
+
# always performs a browser interaction, regardless of which matching
|
|
764
|
+
# element(s) are selected.
|
|
765
|
+
read_only_hint=False, # Clicking can modify browser/page state.
|
|
766
|
+
destructive_hint=False, # The generic click operation is not
|
|
767
|
+
# inherently destructive; the target may be.
|
|
768
|
+
idempotent_hint=False, # Repeating a click can produce a different
|
|
769
|
+
# result or trigger another action.
|
|
770
|
+
open_world_hint=True, # The tool operates on external webpages.
|
|
771
|
+
),
|
|
772
|
+
)
|
|
686
773
|
@handle_sb_errors
|
|
687
774
|
def click_element(
|
|
688
775
|
selector: str,
|
|
@@ -699,12 +786,12 @@ def click_element(
|
|
|
699
786
|
clicking all visible matches, conditional clicks, or clicks scoped to a
|
|
700
787
|
parent element.
|
|
701
788
|
|
|
702
|
-
Selection behavior:
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
789
|
+
Selection behavior and priority:
|
|
790
|
+
`nth` is 1-based and takes precedence over every other click mode.
|
|
791
|
+
Otherwise, `all_matches=True` clicks every currently visible match.
|
|
792
|
+
Otherwise, `only_if_visible=True` clicks only if a match is visible.
|
|
793
|
+
Otherwise, `parent_selector` scopes the click to a nested element.
|
|
794
|
+
If none of the above are set, then a regular click is performed.
|
|
708
795
|
|
|
709
796
|
Args:
|
|
710
797
|
selector: CSS selector, XPath selector, or supported SeleniumBase
|
|
@@ -779,7 +866,11 @@ def click_element(
|
|
|
779
866
|
return f"Clicked {selector}"
|
|
780
867
|
|
|
781
868
|
|
|
782
|
-
@mcp.tool()
|
|
869
|
+
@mcp.tool(title="Hover / Click / Drag")
|
|
870
|
+
# Annotations intentionally omitted: 'hover' alone is a near-harmless
|
|
871
|
+
# observation-adjacent action, while 'hover_and_click' and 'drag_and_drop'
|
|
872
|
+
# can modify or reorder page/data state (like click_element) -- no single
|
|
873
|
+
# destructive_hint value would be accurate for the whole tool.
|
|
783
874
|
@handle_sb_errors
|
|
784
875
|
def hover_action(
|
|
785
876
|
selector: str,
|
|
@@ -865,7 +956,19 @@ def hover_action(
|
|
|
865
956
|
)
|
|
866
957
|
|
|
867
958
|
|
|
868
|
-
@mcp.tool(
|
|
959
|
+
@mcp.tool(
|
|
960
|
+
title="Type Text",
|
|
961
|
+
annotations=ToolAnnotations(
|
|
962
|
+
# idempotent_hint is deliberately omitted: fill_input/set_value/
|
|
963
|
+
# clear_only converge to the same end state on repeat calls, but
|
|
964
|
+
# mode="append" compounds text on each call, so no single value
|
|
965
|
+
# would be accurate for every mode.
|
|
966
|
+
read_only_hint=False,
|
|
967
|
+
destructive_hint=False,
|
|
968
|
+
open_world_hint=True, # Since "\n" can perform the "Enter" action,
|
|
969
|
+
# which includes form submissions.
|
|
970
|
+
),
|
|
971
|
+
)
|
|
869
972
|
@handle_sb_errors
|
|
870
973
|
def type_text(
|
|
871
974
|
selector: str,
|
|
@@ -935,7 +1038,17 @@ def type_text(
|
|
|
935
1038
|
return f"type_text(mode={mode!r}) done for {selector}"
|
|
936
1039
|
|
|
937
1040
|
|
|
938
|
-
@mcp.tool(
|
|
1041
|
+
@mcp.tool(
|
|
1042
|
+
title="Select Option",
|
|
1043
|
+
annotations=ToolAnnotations(
|
|
1044
|
+
read_only_hint=False,
|
|
1045
|
+
destructive_hint=False,
|
|
1046
|
+
idempotent_hint=False, # Selecting an option can trigger arbitrary
|
|
1047
|
+
# page-side behavior/events.
|
|
1048
|
+
open_world_hint=True, # Selecting an option can change browser
|
|
1049
|
+
# state and can trigger arbitrary page-side behavior.
|
|
1050
|
+
),
|
|
1051
|
+
)
|
|
939
1052
|
@handle_sb_errors
|
|
940
1053
|
def select_option(
|
|
941
1054
|
dropdown_selector: str,
|
|
@@ -977,7 +1090,13 @@ def select_option(
|
|
|
977
1090
|
return f"Selected ({by}={value!r}) in {dropdown_selector}"
|
|
978
1091
|
|
|
979
1092
|
|
|
980
|
-
@mcp.tool(
|
|
1093
|
+
@mcp.tool(
|
|
1094
|
+
title="Focus Element",
|
|
1095
|
+
annotations=ToolAnnotations(
|
|
1096
|
+
read_only_hint=False,
|
|
1097
|
+
destructive_hint=False,
|
|
1098
|
+
),
|
|
1099
|
+
)
|
|
981
1100
|
@handle_sb_errors
|
|
982
1101
|
def focus_element(
|
|
983
1102
|
selector: str,
|
|
@@ -1030,7 +1149,17 @@ def focus_element(
|
|
|
1030
1149
|
# Waiting & assertions
|
|
1031
1150
|
# ---------------------------------------------------------------------------
|
|
1032
1151
|
|
|
1033
|
-
@mcp.tool(
|
|
1152
|
+
@mcp.tool(
|
|
1153
|
+
title="Wait For Condition",
|
|
1154
|
+
annotations=ToolAnnotations(
|
|
1155
|
+
# Consistent across every state: This tool only observes/blocks,
|
|
1156
|
+
# and it never modifies the page itself.
|
|
1157
|
+
read_only_hint=True,
|
|
1158
|
+
destructive_hint=False,
|
|
1159
|
+
idempotent_hint=True,
|
|
1160
|
+
open_world_hint=False,
|
|
1161
|
+
),
|
|
1162
|
+
)
|
|
1034
1163
|
@handle_sb_errors
|
|
1035
1164
|
def wait_for_condition(
|
|
1036
1165
|
state: Literal[
|
|
@@ -1138,7 +1267,17 @@ def wait_for_condition(
|
|
|
1138
1267
|
return f"Element {selector} reached state '{state}'."
|
|
1139
1268
|
|
|
1140
1269
|
|
|
1141
|
-
@mcp.tool(
|
|
1270
|
+
@mcp.tool(
|
|
1271
|
+
title="Assert Condition",
|
|
1272
|
+
annotations=ToolAnnotations(
|
|
1273
|
+
# Consistent across every check: Purely a verification/read
|
|
1274
|
+
# operation, and never modifies the page.
|
|
1275
|
+
read_only_hint=True,
|
|
1276
|
+
destructive_hint=False,
|
|
1277
|
+
idempotent_hint=True,
|
|
1278
|
+
open_world_hint=False,
|
|
1279
|
+
),
|
|
1280
|
+
)
|
|
1142
1281
|
@handle_sb_errors
|
|
1143
1282
|
def assert_condition(
|
|
1144
1283
|
check: Literal[
|
|
@@ -1256,7 +1395,11 @@ def assert_condition(
|
|
|
1256
1395
|
# Cookies & storage
|
|
1257
1396
|
# ---------------------------------------------------------------------------
|
|
1258
1397
|
|
|
1259
|
-
@mcp.tool()
|
|
1398
|
+
@mcp.tool(title="Manage Cookies")
|
|
1399
|
+
# Annotations intentionally omitted: 'get_all' is a pure read, 'clear' is
|
|
1400
|
+
# destructive, and 'save'/'load' are file/session I/O -- no single
|
|
1401
|
+
# read_only_hint or destructive_hint value would be accurate for the whole
|
|
1402
|
+
# tool.
|
|
1260
1403
|
@handle_sb_errors
|
|
1261
1404
|
def manage_cookies(
|
|
1262
1405
|
action: Literal["get_all", "clear", "save", "load"] = "get_all",
|
|
@@ -1328,7 +1471,10 @@ def manage_cookies(
|
|
|
1328
1471
|
)
|
|
1329
1472
|
|
|
1330
1473
|
|
|
1331
|
-
@mcp.tool()
|
|
1474
|
+
@mcp.tool(title="Manage Storage")
|
|
1475
|
+
# Annotations intentionally omitted: 'get' is a pure read while 'set'
|
|
1476
|
+
# modifies storage -- no single read_only_hint value would be accurate
|
|
1477
|
+
# for the whole tool.
|
|
1332
1478
|
@handle_sb_errors
|
|
1333
1479
|
def manage_storage(
|
|
1334
1480
|
key: str,
|
|
@@ -1404,7 +1550,20 @@ def manage_storage(
|
|
|
1404
1550
|
# Scrolling
|
|
1405
1551
|
# ---------------------------------------------------------------------------
|
|
1406
1552
|
|
|
1407
|
-
@mcp.tool(
|
|
1553
|
+
@mcp.tool(
|
|
1554
|
+
title="Scroll Page",
|
|
1555
|
+
annotations=ToolAnnotations(
|
|
1556
|
+
# Consistent across up/down/top/bottom: a harmless, local,
|
|
1557
|
+
# non-destructive scroll-position change.
|
|
1558
|
+
# idempotent_hint is deliberately omitted: 'top'/'bottom' are
|
|
1559
|
+
# idempotent (repeating lands you in the same place), but 'up'/
|
|
1560
|
+
# 'down' compound with each call, so no single value would be
|
|
1561
|
+
# accurate for every direction.
|
|
1562
|
+
read_only_hint=False,
|
|
1563
|
+
destructive_hint=False,
|
|
1564
|
+
open_world_hint=False,
|
|
1565
|
+
),
|
|
1566
|
+
)
|
|
1408
1567
|
@handle_sb_errors
|
|
1409
1568
|
def scroll_page(
|
|
1410
1569
|
direction: Literal["up", "down", "top", "bottom"] = "down",
|
|
@@ -1460,7 +1619,18 @@ def scroll_page(
|
|
|
1460
1619
|
# Windows & tabs
|
|
1461
1620
|
# ---------------------------------------------------------------------------
|
|
1462
1621
|
|
|
1463
|
-
@mcp.tool(
|
|
1622
|
+
@mcp.tool(
|
|
1623
|
+
title="Manage Window",
|
|
1624
|
+
annotations=ToolAnnotations(
|
|
1625
|
+
destructive_hint=False,
|
|
1626
|
+
idempotent_hint=True,
|
|
1627
|
+
open_world_hint=False,
|
|
1628
|
+
),
|
|
1629
|
+
)
|
|
1630
|
+
# The 'read_only_hint' annotation has been intentionally omitted:
|
|
1631
|
+
# 'get_rect' is a pure read while
|
|
1632
|
+
# 'set_rect'/'maximize'/'minimize' modify window state.
|
|
1633
|
+
# There's no single 'read_only_hint' value that's accurate for the whole tool.
|
|
1464
1634
|
@handle_sb_errors
|
|
1465
1635
|
def manage_window(
|
|
1466
1636
|
action: Literal[
|
|
@@ -1520,7 +1690,11 @@ def manage_window(
|
|
|
1520
1690
|
)
|
|
1521
1691
|
|
|
1522
1692
|
|
|
1523
|
-
@mcp.tool()
|
|
1693
|
+
@mcp.tool(title="Manage Tabs")
|
|
1694
|
+
# Annotations intentionally omitted: 'list_tabs' is a pure read while
|
|
1695
|
+
# 'open_new_tab'/'switch_to_tab'/'switch_to_newest_tab' modify state and
|
|
1696
|
+
# 'close_active_tab' is destructive -- no single read_only_hint or
|
|
1697
|
+
# destructive_hint value would be accurate for the whole tool.
|
|
1524
1698
|
@handle_sb_errors
|
|
1525
1699
|
def manage_tabs(
|
|
1526
1700
|
action: Literal[
|
|
@@ -1621,16 +1795,23 @@ def manage_tabs(
|
|
|
1621
1795
|
# Captcha solving
|
|
1622
1796
|
# ---------------------------------------------------------------------------
|
|
1623
1797
|
|
|
1624
|
-
@mcp.tool(
|
|
1798
|
+
@mcp.tool(
|
|
1799
|
+
title="Solve CAPTCHA",
|
|
1800
|
+
annotations=ToolAnnotations(
|
|
1801
|
+
read_only_hint=False,
|
|
1802
|
+
destructive_hint=False,
|
|
1803
|
+
idempotent_hint=False, # A 2nd attempt on an already-handled or
|
|
1804
|
+
# rotated CAPTCHA widget isn't guaranteed to be a no-op.
|
|
1805
|
+
open_world_hint=True, # Interacts with a 3rd-party CAPTCHA widget.
|
|
1806
|
+
),
|
|
1807
|
+
)
|
|
1625
1808
|
@handle_sb_errors
|
|
1626
1809
|
def solve_captcha() -> str:
|
|
1627
1810
|
"""Attempt a SeleniumBase CDP-based CAPTCHA interaction, such as clicking
|
|
1628
1811
|
a CAPTCHA checkbox, or performing a drag/drop action on a slider CAPTCHA.
|
|
1629
1812
|
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
the Chrome DevTools Protocol (CDP), which is usually stealthier than
|
|
1633
|
-
JavaScript because CDP actions can avoid triggering `isTrusted: false`.
|
|
1813
|
+
Supported CAPTCHAs include: Cloudflare Turnstile, reCAPTCHA, hCaptcha,
|
|
1814
|
+
DataDome Slider, and FriendlyCaptcha.
|
|
1634
1815
|
|
|
1635
1816
|
This tool automatically detects the coordinates of CAPTCHA checkboxes
|
|
1636
1817
|
for determining the correct location to perform the click. If no CAPTCHA
|
|
@@ -1649,8 +1830,13 @@ def solve_captcha() -> str:
|
|
|
1649
1830
|
or 'manage_cookies' to inspect resulting page/session state.
|
|
1650
1831
|
|
|
1651
1832
|
Returns:
|
|
1652
|
-
A message
|
|
1833
|
+
A confirmation message of the CAPTCHA interaction.
|
|
1653
1834
|
The message is the same for both successful and failed attempts.
|
|
1835
|
+
|
|
1836
|
+
Notes:
|
|
1837
|
+
Clicking with the Chrome DevTools Protocol (CDP) is generally
|
|
1838
|
+
stealthier than clicking with JavaScript because CDP actions
|
|
1839
|
+
can avoid triggering `isTrusted: false`.
|
|
1654
1840
|
"""
|
|
1655
1841
|
sb = _get_sb()
|
|
1656
1842
|
sb.solve_captcha()
|
|
@@ -1661,7 +1847,17 @@ def solve_captcha() -> str:
|
|
|
1661
1847
|
# Output & misc
|
|
1662
1848
|
# ---------------------------------------------------------------------------
|
|
1663
1849
|
|
|
1664
|
-
@mcp.tool(
|
|
1850
|
+
@mcp.tool(
|
|
1851
|
+
title="Save Page",
|
|
1852
|
+
annotations=ToolAnnotations(
|
|
1853
|
+
# Consistent across screenshot/html/pdf: All three read the current
|
|
1854
|
+
# (unmodified) page and write a new local file, which may silently
|
|
1855
|
+
# overwrite an existing file of the same name.
|
|
1856
|
+
read_only_hint=False,
|
|
1857
|
+
idempotent_hint=False,
|
|
1858
|
+
open_world_hint=False,
|
|
1859
|
+
),
|
|
1860
|
+
)
|
|
1665
1861
|
@handle_sb_errors
|
|
1666
1862
|
def save_page(
|
|
1667
1863
|
format: Literal["screenshot", "html", "pdf"] = "screenshot",
|
|
@@ -1741,7 +1937,12 @@ def save_page(
|
|
|
1741
1937
|
return f"Saved {format} as {name}"
|
|
1742
1938
|
|
|
1743
1939
|
|
|
1744
|
-
@mcp.tool()
|
|
1940
|
+
@mcp.tool(title="Run JavaScript")
|
|
1941
|
+
# Annotations intentionally omitted: arbitrary JavaScript can read, write,
|
|
1942
|
+
# navigate, or destroy data, or do nothing at all -- no annotation value
|
|
1943
|
+
# would be more informative than the MCP spec's own conservative defaults
|
|
1944
|
+
# for an unannotated tool (not read-only, potentially destructive,
|
|
1945
|
+
# non-idempotent, open-world).
|
|
1745
1946
|
@handle_sb_errors
|
|
1746
1947
|
def run_javascript(expression: str) -> Any:
|
|
1747
1948
|
"""Evaluate a JavaScript expression in the current page context.
|
|
@@ -7,7 +7,7 @@ certifi>=2026.7.22
|
|
|
7
7
|
exceptiongroup>=1.3.1
|
|
8
8
|
websockets~=16.1.1;python_version=="3.10"
|
|
9
9
|
websockets>=16.1.1;python_version>="3.11"
|
|
10
|
-
filelock>=3.32.
|
|
10
|
+
filelock>=3.32.7
|
|
11
11
|
fasteners>=0.20
|
|
12
12
|
mycdp>=1.4.0
|
|
13
13
|
pynose>=1.5.5
|
|
@@ -28,7 +28,7 @@ tabcompleter>=1.4.1
|
|
|
28
28
|
pdbp>=1.8.2
|
|
29
29
|
idna>=3.19
|
|
30
30
|
charset-normalizer>=3.5.1,<4
|
|
31
|
-
urllib3>=2.
|
|
31
|
+
urllib3>=2.8.0,<3
|
|
32
32
|
requests~=2.34.2
|
|
33
33
|
sniffio==1.3.1
|
|
34
34
|
h11==0.16.0
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
# seleniumbase package
|
|
2
|
-
__version__ = "4.54.
|
|
2
|
+
__version__ = "4.54.7"
|
|
@@ -222,14 +222,15 @@ class Tab(Connection):
|
|
|
222
222
|
except (Exception, TypeError):
|
|
223
223
|
pass
|
|
224
224
|
while not item:
|
|
225
|
-
item = await self.find_element_by_text(
|
|
226
|
-
text, best_match, return_enclosing_element
|
|
227
|
-
)
|
|
228
225
|
if loop.time() - start_time > timeout:
|
|
229
226
|
raise asyncio.TimeoutError(
|
|
230
227
|
"Time ran out while waiting for: {%s}" % text
|
|
231
228
|
)
|
|
229
|
+
await self
|
|
232
230
|
await self.sleep(0.5)
|
|
231
|
+
item = await self.find_element_by_text(
|
|
232
|
+
text, best_match, return_enclosing_element
|
|
233
|
+
)
|
|
233
234
|
return item
|
|
234
235
|
|
|
235
236
|
async def select(
|
|
@@ -273,13 +274,13 @@ class Tab(Connection):
|
|
|
273
274
|
except (Exception, TypeError):
|
|
274
275
|
pass
|
|
275
276
|
while not items:
|
|
276
|
-
await self
|
|
277
|
-
items = await self.find_elements_by_text(text)
|
|
278
277
|
if loop.time() - now > timeout:
|
|
279
278
|
raise asyncio.TimeoutError(
|
|
280
279
|
"Time ran out while waiting for: {%s}" % text
|
|
281
280
|
)
|
|
281
|
+
await self
|
|
282
282
|
await self.sleep(0.5)
|
|
283
|
+
items = await self.find_elements_by_text(text)
|
|
283
284
|
return items
|
|
284
285
|
|
|
285
286
|
async def select_all(
|
|
@@ -311,13 +312,13 @@ class Tab(Connection):
|
|
|
311
312
|
items.extend(await fr.query_selector_all(selector))
|
|
312
313
|
items.extend(await self.query_selector_all(selector))
|
|
313
314
|
while not items:
|
|
314
|
-
await self
|
|
315
|
-
items = await self.query_selector_all(selector)
|
|
316
315
|
if loop.time() - now > timeout:
|
|
317
316
|
raise asyncio.TimeoutError(
|
|
318
317
|
"Time ran out while waiting for: {%s}" % selector
|
|
319
318
|
)
|
|
319
|
+
await self
|
|
320
320
|
await self.sleep(0.5)
|
|
321
|
+
items = await self.query_selector_all(selector)
|
|
321
322
|
return items
|
|
322
323
|
|
|
323
324
|
async def get(
|
|
@@ -1098,25 +1099,32 @@ class Tab(Connection):
|
|
|
1098
1099
|
"""
|
|
1099
1100
|
loop = asyncio.get_running_loop()
|
|
1100
1101
|
now = loop.time()
|
|
1102
|
+
item = None
|
|
1101
1103
|
if selector:
|
|
1102
|
-
|
|
1103
|
-
while not item:
|
|
1104
|
+
with suppress(Exception):
|
|
1104
1105
|
item = await self.query_selector(selector)
|
|
1106
|
+
while not item:
|
|
1105
1107
|
if loop.time() - now > timeout:
|
|
1106
1108
|
raise asyncio.TimeoutError(
|
|
1107
1109
|
"Time ran out while waiting for: {%s}" % selector
|
|
1108
1110
|
)
|
|
1111
|
+
await self
|
|
1109
1112
|
await self.sleep(0.068)
|
|
1113
|
+
with suppress(Exception):
|
|
1114
|
+
item = await self.query_selector(selector)
|
|
1110
1115
|
return item
|
|
1111
1116
|
if text:
|
|
1112
|
-
|
|
1113
|
-
while not item:
|
|
1117
|
+
with suppress(Exception):
|
|
1114
1118
|
item = await self.find_element_by_text(text)
|
|
1119
|
+
while not item:
|
|
1115
1120
|
if loop.time() - now > timeout:
|
|
1116
1121
|
raise asyncio.TimeoutError(
|
|
1117
1122
|
"Time ran out while waiting for: {%s}" % text
|
|
1118
1123
|
)
|
|
1124
|
+
await self
|
|
1119
1125
|
await self.sleep(0.068)
|
|
1126
|
+
with suppress(Exception):
|
|
1127
|
+
item = await self.find_element_by_text(text)
|
|
1120
1128
|
return item
|
|
1121
1129
|
|
|
1122
1130
|
async def set_attributes(self, selector, attribute, value):
|