seleniumbase 4.54.5__tar.gz → 4.54.6__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.
Files changed (164) hide show
  1. {seleniumbase-4.54.5/seleniumbase.egg-info → seleniumbase-4.54.6}/PKG-INFO +2 -2
  2. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/mcp_servers/server.py +238 -42
  3. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/__version__.py +1 -1
  4. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/undetected/cdp_driver/tab.py +19 -11
  5. {seleniumbase-4.54.5 → seleniumbase-4.54.6/seleniumbase.egg-info}/PKG-INFO +2 -2
  6. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase.egg-info/requires.txt +1 -1
  7. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/setup.py +1 -1
  8. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/.gitignore +0 -0
  9. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/LICENSE +0 -0
  10. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/MANIFEST.in +0 -0
  11. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/README.md +0 -0
  12. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/install.sh +0 -0
  13. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/mcp_servers/__init__.py +0 -0
  14. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/pyproject.toml +0 -0
  15. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/pytest.ini +0 -0
  16. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/requirements.txt +0 -0
  17. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/sbase/__init__.py +0 -0
  18. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/sbase/__main__.py +0 -0
  19. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/sbase/steps.py +0 -0
  20. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/__init__.py +0 -0
  21. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/__main__.py +0 -0
  22. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/behave/__init__.py +0 -0
  23. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/behave/behave_helper.py +0 -0
  24. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/behave/behave_sb.py +0 -0
  25. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/behave/steps.py +0 -0
  26. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/common/__init__.py +0 -0
  27. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/common/decorators.py +0 -0
  28. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/common/encryption.py +0 -0
  29. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/common/exceptions.py +0 -0
  30. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/common/obfuscate.py +0 -0
  31. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/common/unobfuscate.py +0 -0
  32. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/config/__init__.py +0 -0
  33. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/config/ad_block_list.py +0 -0
  34. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/config/proxy_list.py +0 -0
  35. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/config/settings.py +0 -0
  36. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/console_scripts/__init__.py +0 -0
  37. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/console_scripts/logo_helper.py +0 -0
  38. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/console_scripts/rich_helper.py +0 -0
  39. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/console_scripts/run.py +0 -0
  40. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/console_scripts/sb_behave_gui.py +0 -0
  41. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/console_scripts/sb_caseplans.py +0 -0
  42. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/console_scripts/sb_commander.py +0 -0
  43. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/console_scripts/sb_install.py +0 -0
  44. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/console_scripts/sb_mkchart.py +0 -0
  45. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/console_scripts/sb_mkdir.py +0 -0
  46. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/console_scripts/sb_mkfile.py +0 -0
  47. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/console_scripts/sb_mkpres.py +0 -0
  48. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/console_scripts/sb_mkrec.py +0 -0
  49. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/console_scripts/sb_objectify.py +0 -0
  50. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/console_scripts/sb_print.py +0 -0
  51. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/console_scripts/sb_recorder.py +0 -0
  52. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/core/__init__.py +0 -0
  53. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/core/application_manager.py +0 -0
  54. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/core/browser_launcher.py +0 -0
  55. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/core/capabilities_parser.py +0 -0
  56. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/core/colored_traceback.py +0 -0
  57. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/core/create_db_tables.sql +0 -0
  58. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/core/detect_b_ver.py +0 -0
  59. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/core/download_helper.py +0 -0
  60. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/core/encoded_images.py +0 -0
  61. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/core/jqc_helper.py +0 -0
  62. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/core/log_helper.py +0 -0
  63. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/core/mysql.py +0 -0
  64. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/core/nest_asyncio.py +0 -0
  65. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/core/proxy_helper.py +0 -0
  66. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/core/recorder_helper.py +0 -0
  67. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/core/report_helper.py +0 -0
  68. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/core/s3_manager.py +0 -0
  69. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/core/sb_cdp.py +0 -0
  70. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/core/sb_driver.py +0 -0
  71. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/core/session_helper.py +0 -0
  72. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/core/settings_parser.py +0 -0
  73. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/core/style_sheet.py +0 -0
  74. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/core/testcase_manager.py +0 -0
  75. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/core/tour_helper.py +0 -0
  76. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/core/visual_helper.py +0 -0
  77. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/drivers/__init__.py +0 -0
  78. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/drivers/brave_drivers/__init__.py +0 -0
  79. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/drivers/cft_drivers/__init__.py +0 -0
  80. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/drivers/chromium_drivers/__init__.py +0 -0
  81. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/drivers/chs_drivers/__init__.py +0 -0
  82. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/drivers/comet_drivers/__init__.py +0 -0
  83. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/drivers/opera_drivers/__init__.py +0 -0
  84. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/extensions/__init__.py +0 -0
  85. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/extensions/ad_block.zip +0 -0
  86. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/extensions/disable_csp.zip +0 -0
  87. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/extensions/recorder.zip +0 -0
  88. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/extensions/sbase_ext.zip +0 -0
  89. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/fixtures/__init__.py +0 -0
  90. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/fixtures/base_case.py +0 -0
  91. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/fixtures/constants.py +0 -0
  92. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/fixtures/css_to_xpath.py +0 -0
  93. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/fixtures/errors.py +0 -0
  94. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/fixtures/js_utils.py +0 -0
  95. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/fixtures/page_actions.py +0 -0
  96. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/fixtures/page_utils.py +0 -0
  97. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/fixtures/shared_utils.py +0 -0
  98. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/fixtures/unittest_helper.py +0 -0
  99. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/fixtures/words.py +0 -0
  100. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/fixtures/xpath_to_css.py +0 -0
  101. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/js_code/__init__.py +0 -0
  102. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/js_code/active_css_js.py +0 -0
  103. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/js_code/live_js.py +0 -0
  104. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/js_code/recorder_js.py +0 -0
  105. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/masterqa/__init__.py +0 -0
  106. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/masterqa/master_qa.py +0 -0
  107. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/plugins/__init__.py +0 -0
  108. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/plugins/base_plugin.py +0 -0
  109. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/plugins/basic_test_info.py +0 -0
  110. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/plugins/db_reporting_plugin.py +0 -0
  111. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/plugins/driver_manager.py +0 -0
  112. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/plugins/page_source.py +0 -0
  113. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/plugins/pytest_plugin.py +0 -0
  114. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/plugins/s3_logging_plugin.py +0 -0
  115. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/plugins/sb_manager.py +0 -0
  116. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/plugins/screen_shots.py +0 -0
  117. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/plugins/selenium_plugin.py +0 -0
  118. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/resources/__init__.py +0 -0
  119. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/translate/__init__.py +0 -0
  120. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/translate/chinese.py +0 -0
  121. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/translate/dutch.py +0 -0
  122. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/translate/french.py +0 -0
  123. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/translate/italian.py +0 -0
  124. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/translate/japanese.py +0 -0
  125. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/translate/korean.py +0 -0
  126. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/translate/master_dict.py +0 -0
  127. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/translate/portuguese.py +0 -0
  128. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/translate/russian.py +0 -0
  129. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/translate/spanish.py +0 -0
  130. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/translate/translator.py +0 -0
  131. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/undetected/__init__.py +0 -0
  132. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/undetected/cdp.py +0 -0
  133. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/undetected/cdp_driver/__init__.py +0 -0
  134. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/undetected/cdp_driver/_contradict.py +0 -0
  135. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/undetected/cdp_driver/browser.py +0 -0
  136. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/undetected/cdp_driver/cdp_util.py +0 -0
  137. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/undetected/cdp_driver/config.py +0 -0
  138. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/undetected/cdp_driver/connection.py +0 -0
  139. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/undetected/cdp_driver/element.py +0 -0
  140. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/undetected/dprocess.py +0 -0
  141. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/undetected/options.py +0 -0
  142. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/undetected/patcher.py +0 -0
  143. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/undetected/reactor.py +0 -0
  144. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/undetected/webelement.py +0 -0
  145. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/utilities/__init__.py +0 -0
  146. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/utilities/selenium_grid/__init__.py +0 -0
  147. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/utilities/selenium_grid/download_selenium_server.py +0 -0
  148. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/utilities/selenium_grid/font_color +0 -0
  149. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/utilities/selenium_grid/grid-hub +0 -0
  150. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/utilities/selenium_grid/grid-node +0 -0
  151. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/utilities/selenium_grid/grid_hub.py +0 -0
  152. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/utilities/selenium_grid/grid_node.py +0 -0
  153. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/utilities/selenium_grid/register-grid-node.bat +0 -0
  154. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/utilities/selenium_grid/register-grid-node.sh +0 -0
  155. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/utilities/selenium_grid/start-grid-hub.bat +0 -0
  156. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/utilities/selenium_grid/start-grid-hub.sh +0 -0
  157. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/utilities/selenium_ide/__init__.py +0 -0
  158. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase/utilities/selenium_ide/convert_ide.py +0 -0
  159. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase.egg-info/SOURCES.txt +0 -0
  160. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase.egg-info/dependency_links.txt +0 -0
  161. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase.egg-info/entry_points.txt +0 -0
  162. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/seleniumbase.egg-info/top_level.txt +0 -0
  163. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/setup.cfg +0 -0
  164. {seleniumbase-4.54.5 → seleniumbase-4.54.6}/virtualenv_install.sh +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: seleniumbase
3
- Version: 4.54.5
3
+ Version: 4.54.6
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
@@ -155,7 +155,7 @@ 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.12; extra == "uv"
158
+ Requires-Dist: uv>=0.12.14; 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 READ-ONLY metadata operation. It does not inspect arbitrary
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 'back' or 'forward' for history navigation, 'reload' to refresh
383
- while bypassing the cache, or 'list' to inspect history.
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,7 +573,15 @@ 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",
@@ -567,18 +634,24 @@ def get_content(
567
634
  )
568
635
 
569
636
 
570
- @mcp.tool()
637
+ @mcp.tool(
638
+ title="Get Attributes",
639
+ annotations=ToolAnnotations(
640
+ read_only_hint=True,
641
+ destructive_hint=False,
642
+ idempotent_hint=True,
643
+ open_world_hint=True,
644
+ ),
645
+ )
571
646
  @handle_sb_errors
572
647
  def get_attributes(
573
648
  selector: str,
574
649
  attribute: str | None = None,
575
650
  timeout: float = 5,
576
651
  ) -> str | dict[str, Any] | None:
577
- """Read HTML attributes from the first matching element.
578
-
579
- Use this tool when you need the value of a specific HTML attribute,
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.
652
+ """Get a specific HTML attribute (or all attributes) from the
653
+ first-matching element. Examples of possible attributes include
654
+ href, src, value, class, id, name, type, aria-label, etc.
582
655
 
583
656
  Args:
584
657
  selector: CSS selector or SeleniumBase-supported XPath selector.
@@ -597,7 +670,8 @@ def get_attributes(
597
670
  - Need to check element presence/visibility ->
598
671
  use 'check_if_condition'.
599
672
 
600
- This is a read-only operation.
673
+ This is a read-only operation: It finds elements to get the requested data,
674
+ but it does not make any modifications to those elements.
601
675
 
602
676
  If there's no matching element found within the timeout,
603
677
  then @handle_sb_errors returns details from the exception raised.
@@ -610,7 +684,15 @@ def get_attributes(
610
684
  return sb.get_element_attributes(selector, timeout=timeout)
611
685
 
612
686
 
613
- @mcp.tool()
687
+ @mcp.tool(
688
+ title="Check Condition",
689
+ annotations=ToolAnnotations(
690
+ read_only_hint=True,
691
+ destructive_hint=False,
692
+ idempotent_hint=True,
693
+ open_world_hint=True,
694
+ ),
695
+ )
614
696
  @handle_sb_errors
615
697
  def check_if_condition(
616
698
  check: Literal["present", "visible"] = "visible",
@@ -659,8 +741,6 @@ def check_if_condition(
659
741
  This tool does not intentionally wait for elements or text to appear.
660
742
  It is intended for checking the current state only. If page timing or
661
743
  asynchronous loading matters, use wait_for_condition instead.
662
-
663
- When `text` is provided, `check` is ignored.
664
744
  """
665
745
  sb = _get_sb()
666
746
 
@@ -682,7 +762,20 @@ def check_if_condition(
682
762
  # Interacting with elements
683
763
  # ---------------------------------------------------------------------------
684
764
 
685
- @mcp.tool()
765
+ @mcp.tool(
766
+ title="Click Element",
767
+ annotations=ToolAnnotations(
768
+ # These settings are consistent across all click modes. The tool
769
+ # always performs a browser interaction, regardless of which matching
770
+ # element(s) are selected.
771
+ read_only_hint=False, # Clicking can modify browser/page state.
772
+ destructive_hint=False, # The generic click operation is not
773
+ # inherently destructive; the target may be.
774
+ idempotent_hint=False, # Repeating a click can produce a different
775
+ # result or trigger another action.
776
+ open_world_hint=True, # The tool operates on external webpages.
777
+ ),
778
+ )
686
779
  @handle_sb_errors
687
780
  def click_element(
688
781
  selector: str,
@@ -699,12 +792,12 @@ def click_element(
699
792
  clicking all visible matches, conditional clicks, or clicks scoped to a
700
793
  parent element.
701
794
 
702
- Selection behavior:
703
- - `nth` is 1-based and takes precedence over every other click mode.
704
- - Otherwise, `all_matches=True` clicks every currently visible match.
705
- - Otherwise, `only_if_visible=True` clicks only if a match is visible.
706
- - Otherwise, `parent_selector` scopes the click to a nested element.
707
- - With none of the above, performs a normal SeleniumBase click.
795
+ Selection behavior and priority:
796
+ `nth` is 1-based and takes precedence over every other click mode.
797
+ Otherwise, `all_matches=True` clicks every currently visible match.
798
+ Otherwise, `only_if_visible=True` clicks only if a match is visible.
799
+ Otherwise, `parent_selector` scopes the click to a nested element.
800
+ If none of the above are set, then a regular click is performed.
708
801
 
709
802
  Args:
710
803
  selector: CSS selector, XPath selector, or supported SeleniumBase
@@ -779,7 +872,11 @@ def click_element(
779
872
  return f"Clicked {selector}"
780
873
 
781
874
 
782
- @mcp.tool()
875
+ @mcp.tool(title="Hover / Click / Drag")
876
+ # Annotations intentionally omitted: 'hover' alone is a near-harmless
877
+ # observation-adjacent action, while 'hover_and_click' and 'drag_and_drop'
878
+ # can modify or reorder page/data state (like click_element) -- no single
879
+ # destructive_hint value would be accurate for the whole tool.
783
880
  @handle_sb_errors
784
881
  def hover_action(
785
882
  selector: str,
@@ -865,7 +962,19 @@ def hover_action(
865
962
  )
866
963
 
867
964
 
868
- @mcp.tool()
965
+ @mcp.tool(
966
+ title="Type Text",
967
+ annotations=ToolAnnotations(
968
+ # idempotent_hint is deliberately omitted: fill_input/set_value/
969
+ # clear_only converge to the same end state on repeat calls, but
970
+ # mode="append" compounds text on each call, so no single value
971
+ # would be accurate for every mode.
972
+ read_only_hint=False,
973
+ destructive_hint=False,
974
+ open_world_hint=True, # Since "\n" can perform the "Enter" action,
975
+ # which includes form submissions.
976
+ ),
977
+ )
869
978
  @handle_sb_errors
870
979
  def type_text(
871
980
  selector: str,
@@ -935,7 +1044,17 @@ def type_text(
935
1044
  return f"type_text(mode={mode!r}) done for {selector}"
936
1045
 
937
1046
 
938
- @mcp.tool()
1047
+ @mcp.tool(
1048
+ title="Select Option",
1049
+ annotations=ToolAnnotations(
1050
+ read_only_hint=False,
1051
+ destructive_hint=False,
1052
+ idempotent_hint=False, # Selecting an option can trigger arbitrary
1053
+ # page-side behavior/events.
1054
+ open_world_hint=True, # Selecting an option can change browser
1055
+ # state and can trigger arbitrary page-side behavior.
1056
+ ),
1057
+ )
939
1058
  @handle_sb_errors
940
1059
  def select_option(
941
1060
  dropdown_selector: str,
@@ -977,7 +1096,13 @@ def select_option(
977
1096
  return f"Selected ({by}={value!r}) in {dropdown_selector}"
978
1097
 
979
1098
 
980
- @mcp.tool()
1099
+ @mcp.tool(
1100
+ title="Focus Element",
1101
+ annotations=ToolAnnotations(
1102
+ read_only_hint=False,
1103
+ destructive_hint=False,
1104
+ ),
1105
+ )
981
1106
  @handle_sb_errors
982
1107
  def focus_element(
983
1108
  selector: str,
@@ -1030,7 +1155,17 @@ def focus_element(
1030
1155
  # Waiting & assertions
1031
1156
  # ---------------------------------------------------------------------------
1032
1157
 
1033
- @mcp.tool()
1158
+ @mcp.tool(
1159
+ title="Wait For Condition",
1160
+ annotations=ToolAnnotations(
1161
+ # Consistent across every state: This tool only observes/blocks,
1162
+ # and it never modifies the page itself.
1163
+ read_only_hint=True,
1164
+ destructive_hint=False,
1165
+ idempotent_hint=True,
1166
+ open_world_hint=False,
1167
+ ),
1168
+ )
1034
1169
  @handle_sb_errors
1035
1170
  def wait_for_condition(
1036
1171
  state: Literal[
@@ -1138,7 +1273,17 @@ def wait_for_condition(
1138
1273
  return f"Element {selector} reached state '{state}'."
1139
1274
 
1140
1275
 
1141
- @mcp.tool()
1276
+ @mcp.tool(
1277
+ title="Assert Condition",
1278
+ annotations=ToolAnnotations(
1279
+ # Consistent across every check: Purely a verification/read
1280
+ # operation, and never modifies the page.
1281
+ read_only_hint=True,
1282
+ destructive_hint=False,
1283
+ idempotent_hint=True,
1284
+ open_world_hint=False,
1285
+ ),
1286
+ )
1142
1287
  @handle_sb_errors
1143
1288
  def assert_condition(
1144
1289
  check: Literal[
@@ -1256,7 +1401,11 @@ def assert_condition(
1256
1401
  # Cookies & storage
1257
1402
  # ---------------------------------------------------------------------------
1258
1403
 
1259
- @mcp.tool()
1404
+ @mcp.tool(title="Manage Cookies")
1405
+ # Annotations intentionally omitted: 'get_all' is a pure read, 'clear' is
1406
+ # destructive, and 'save'/'load' are file/session I/O -- no single
1407
+ # read_only_hint or destructive_hint value would be accurate for the whole
1408
+ # tool.
1260
1409
  @handle_sb_errors
1261
1410
  def manage_cookies(
1262
1411
  action: Literal["get_all", "clear", "save", "load"] = "get_all",
@@ -1328,7 +1477,10 @@ def manage_cookies(
1328
1477
  )
1329
1478
 
1330
1479
 
1331
- @mcp.tool()
1480
+ @mcp.tool(title="Manage Storage")
1481
+ # Annotations intentionally omitted: 'get' is a pure read while 'set'
1482
+ # modifies storage -- no single read_only_hint value would be accurate
1483
+ # for the whole tool.
1332
1484
  @handle_sb_errors
1333
1485
  def manage_storage(
1334
1486
  key: str,
@@ -1404,7 +1556,20 @@ def manage_storage(
1404
1556
  # Scrolling
1405
1557
  # ---------------------------------------------------------------------------
1406
1558
 
1407
- @mcp.tool()
1559
+ @mcp.tool(
1560
+ title="Scroll Page",
1561
+ annotations=ToolAnnotations(
1562
+ # Consistent across up/down/top/bottom: a harmless, local,
1563
+ # non-destructive scroll-position change.
1564
+ # idempotent_hint is deliberately omitted: 'top'/'bottom' are
1565
+ # idempotent (repeating lands you in the same place), but 'up'/
1566
+ # 'down' compound with each call, so no single value would be
1567
+ # accurate for every direction.
1568
+ read_only_hint=False,
1569
+ destructive_hint=False,
1570
+ open_world_hint=False,
1571
+ ),
1572
+ )
1408
1573
  @handle_sb_errors
1409
1574
  def scroll_page(
1410
1575
  direction: Literal["up", "down", "top", "bottom"] = "down",
@@ -1460,7 +1625,10 @@ def scroll_page(
1460
1625
  # Windows & tabs
1461
1626
  # ---------------------------------------------------------------------------
1462
1627
 
1463
- @mcp.tool()
1628
+ @mcp.tool(title="Manage Window")
1629
+ # Annotations intentionally omitted: 'get_rect' is a pure read while
1630
+ # 'set_rect'/'maximize'/'minimize' modify window state -- no single
1631
+ # read_only_hint value would be accurate for the whole tool.
1464
1632
  @handle_sb_errors
1465
1633
  def manage_window(
1466
1634
  action: Literal[
@@ -1520,7 +1688,11 @@ def manage_window(
1520
1688
  )
1521
1689
 
1522
1690
 
1523
- @mcp.tool()
1691
+ @mcp.tool(title="Manage Tabs")
1692
+ # Annotations intentionally omitted: 'list_tabs' is a pure read while
1693
+ # 'open_new_tab'/'switch_to_tab'/'switch_to_newest_tab' modify state and
1694
+ # 'close_active_tab' is destructive -- no single read_only_hint or
1695
+ # destructive_hint value would be accurate for the whole tool.
1524
1696
  @handle_sb_errors
1525
1697
  def manage_tabs(
1526
1698
  action: Literal[
@@ -1621,7 +1793,16 @@ def manage_tabs(
1621
1793
  # Captcha solving
1622
1794
  # ---------------------------------------------------------------------------
1623
1795
 
1624
- @mcp.tool()
1796
+ @mcp.tool(
1797
+ title="Solve CAPTCHA",
1798
+ annotations=ToolAnnotations(
1799
+ read_only_hint=False,
1800
+ destructive_hint=False,
1801
+ idempotent_hint=False, # A 2nd attempt on an already-handled or
1802
+ # rotated CAPTCHA widget isn't guaranteed to be a no-op.
1803
+ open_world_hint=True, # Interacts with a 3rd-party CAPTCHA widget.
1804
+ ),
1805
+ )
1625
1806
  @handle_sb_errors
1626
1807
  def solve_captcha() -> str:
1627
1808
  """Attempt a SeleniumBase CDP-based CAPTCHA interaction, such as clicking
@@ -1661,7 +1842,17 @@ def solve_captcha() -> str:
1661
1842
  # Output & misc
1662
1843
  # ---------------------------------------------------------------------------
1663
1844
 
1664
- @mcp.tool()
1845
+ @mcp.tool(
1846
+ title="Save Page",
1847
+ annotations=ToolAnnotations(
1848
+ # Consistent across screenshot/html/pdf: All three read the current
1849
+ # (unmodified) page and write a new local file, which may silently
1850
+ # overwrite an existing file of the same name.
1851
+ read_only_hint=False,
1852
+ idempotent_hint=False,
1853
+ open_world_hint=False,
1854
+ ),
1855
+ )
1665
1856
  @handle_sb_errors
1666
1857
  def save_page(
1667
1858
  format: Literal["screenshot", "html", "pdf"] = "screenshot",
@@ -1741,7 +1932,12 @@ def save_page(
1741
1932
  return f"Saved {format} as {name}"
1742
1933
 
1743
1934
 
1744
- @mcp.tool()
1935
+ @mcp.tool(title="Run JavaScript")
1936
+ # Annotations intentionally omitted: arbitrary JavaScript can read, write,
1937
+ # navigate, or destroy data, or do nothing at all -- no annotation value
1938
+ # would be more informative than the MCP spec's own conservative defaults
1939
+ # for an unannotated tool (not read-only, potentially destructive,
1940
+ # non-idempotent, open-world).
1745
1941
  @handle_sb_errors
1746
1942
  def run_javascript(expression: str) -> Any:
1747
1943
  """Evaluate a JavaScript expression in the current page context.
@@ -1,2 +1,2 @@
1
1
  # seleniumbase package
2
- __version__ = "4.54.5"
2
+ __version__ = "4.54.6"
@@ -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
- item = await self.query_selector(selector)
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
- item = await self.find_element_by_text(text)
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):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: seleniumbase
3
- Version: 4.54.5
3
+ Version: 4.54.6
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
@@ -155,7 +155,7 @@ 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.12; extra == "uv"
158
+ Requires-Dist: uv>=0.12.14; extra == "uv"
159
159
  Dynamic: author
160
160
  Dynamic: author-email
161
161
  Dynamic: classifier
@@ -121,4 +121,4 @@ proxy.py==2.4.3
121
121
  PyAutoGUI>=0.9.54
122
122
 
123
123
  [uv]
124
- uv>=0.12.12
124
+ uv>=0.12.14
@@ -303,7 +303,7 @@ setup(
303
303
  # Required for local MCP server debugging with:
304
304
  # mcp dev server.py
305
305
  "uv": [
306
- "uv>=0.12.12"
306
+ "uv>=0.12.14"
307
307
  ],
308
308
  },
309
309
  packages=[
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes