iflow-mcp_janspoerer-mcp_browser_use 0.1.0__py3-none-any.whl
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.
- iflow_mcp_janspoerer_mcp_browser_use-0.1.0.dist-info/METADATA +26 -0
- iflow_mcp_janspoerer_mcp_browser_use-0.1.0.dist-info/RECORD +50 -0
- iflow_mcp_janspoerer_mcp_browser_use-0.1.0.dist-info/WHEEL +5 -0
- iflow_mcp_janspoerer_mcp_browser_use-0.1.0.dist-info/entry_points.txt +2 -0
- iflow_mcp_janspoerer_mcp_browser_use-0.1.0.dist-info/licenses/LICENSE +201 -0
- iflow_mcp_janspoerer_mcp_browser_use-0.1.0.dist-info/top_level.txt +1 -0
- mcp_browser_use/__init__.py +2 -0
- mcp_browser_use/__main__.py +1347 -0
- mcp_browser_use/actions/__init__.py +1 -0
- mcp_browser_use/actions/elements.py +173 -0
- mcp_browser_use/actions/extraction.py +864 -0
- mcp_browser_use/actions/keyboard.py +43 -0
- mcp_browser_use/actions/navigation.py +73 -0
- mcp_browser_use/actions/screenshots.py +85 -0
- mcp_browser_use/browser/__init__.py +1 -0
- mcp_browser_use/browser/chrome.py +150 -0
- mcp_browser_use/browser/chrome_executable.py +204 -0
- mcp_browser_use/browser/chrome_launcher.py +330 -0
- mcp_browser_use/browser/chrome_process.py +104 -0
- mcp_browser_use/browser/devtools.py +230 -0
- mcp_browser_use/browser/driver.py +322 -0
- mcp_browser_use/browser/process.py +133 -0
- mcp_browser_use/cleaners.py +530 -0
- mcp_browser_use/config/__init__.py +30 -0
- mcp_browser_use/config/environment.py +155 -0
- mcp_browser_use/config/paths.py +97 -0
- mcp_browser_use/constants.py +68 -0
- mcp_browser_use/context.py +150 -0
- mcp_browser_use/context_pack.py +85 -0
- mcp_browser_use/decorators/__init__.py +13 -0
- mcp_browser_use/decorators/ensure.py +84 -0
- mcp_browser_use/decorators/envelope.py +83 -0
- mcp_browser_use/decorators/locking.py +172 -0
- mcp_browser_use/helpers.py +173 -0
- mcp_browser_use/helpers_context.py +261 -0
- mcp_browser_use/locking/__init__.py +1 -0
- mcp_browser_use/locking/action_lock.py +190 -0
- mcp_browser_use/locking/file_mutex.py +139 -0
- mcp_browser_use/locking/window_registry.py +178 -0
- mcp_browser_use/tools/__init__.py +59 -0
- mcp_browser_use/tools/browser_management.py +260 -0
- mcp_browser_use/tools/debugging.py +195 -0
- mcp_browser_use/tools/extraction.py +58 -0
- mcp_browser_use/tools/interaction.py +323 -0
- mcp_browser_use/tools/navigation.py +84 -0
- mcp_browser_use/tools/screenshots.py +116 -0
- mcp_browser_use/utils/__init__.py +1 -0
- mcp_browser_use/utils/diagnostics.py +85 -0
- mcp_browser_use/utils/html_utils.py +118 -0
- mcp_browser_use/utils/retry.py +57 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""High-level browser actions."""
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""Element finding and interaction."""
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
from typing import Optional
|
|
5
|
+
from selenium import webdriver
|
|
6
|
+
from selenium.webdriver.common.by import By
|
|
7
|
+
from selenium.webdriver.support.ui import WebDriverWait
|
|
8
|
+
from selenium.webdriver.support import expected_conditions as EC
|
|
9
|
+
from selenium.common.exceptions import (
|
|
10
|
+
TimeoutException,
|
|
11
|
+
StaleElementReferenceException,
|
|
12
|
+
ElementClickInterceptedException,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
from ..context import get_context
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _wait_clickable_element(el, driver, timeout: float = 10.0):
|
|
19
|
+
"""Wait for an element to be clickable (displayed and enabled)."""
|
|
20
|
+
WebDriverWait(driver, timeout).until(lambda d: el.is_displayed() and el.is_enabled())
|
|
21
|
+
return el
|
|
22
|
+
|
|
23
|
+
def get_by_selector(selector_type: str):
|
|
24
|
+
return {
|
|
25
|
+
'css': By.CSS_SELECTOR,
|
|
26
|
+
'xpath': By.XPATH,
|
|
27
|
+
'id': By.ID,
|
|
28
|
+
'name': By.NAME,
|
|
29
|
+
'tag': By.TAG_NAME,
|
|
30
|
+
'class': By.CLASS_NAME,
|
|
31
|
+
'link_text': By.LINK_TEXT,
|
|
32
|
+
'partial_link_text': By.PARTIAL_LINK_TEXT
|
|
33
|
+
}.get(selector_type.lower())
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def find_element(
|
|
37
|
+
driver: webdriver.Chrome,
|
|
38
|
+
selector: str,
|
|
39
|
+
selector_type: str = "css",
|
|
40
|
+
timeout: int = 10,
|
|
41
|
+
visible_only: bool = False,
|
|
42
|
+
iframe_selector: Optional[str] = None,
|
|
43
|
+
iframe_selector_type: str = "css",
|
|
44
|
+
shadow_root_selector: Optional[str] = None,
|
|
45
|
+
shadow_root_selector_type: str = "css",
|
|
46
|
+
stay_in_context: bool = False, # <-- added
|
|
47
|
+
):
|
|
48
|
+
"""
|
|
49
|
+
Locate an element with optional iframe and shadow DOM support.
|
|
50
|
+
|
|
51
|
+
- If stay_in_context is True and an iframe was entered, we do NOT switch back
|
|
52
|
+
to default_content. This is needed for actions (click/type) inside iframes.
|
|
53
|
+
- If stay_in_context is False (default), we restore to default_content() so
|
|
54
|
+
callers aren't left in an iframe.
|
|
55
|
+
"""
|
|
56
|
+
original_driver = driver
|
|
57
|
+
switched_iframe = False
|
|
58
|
+
try:
|
|
59
|
+
if iframe_selector:
|
|
60
|
+
by_iframe = get_by_selector(iframe_selector_type)
|
|
61
|
+
if not by_iframe:
|
|
62
|
+
raise ValueError(f"Unsupported iframe selector type: {iframe_selector_type}")
|
|
63
|
+
iframe = WebDriverWait(driver, timeout).until(
|
|
64
|
+
EC.presence_of_element_located((by_iframe, iframe_selector))
|
|
65
|
+
)
|
|
66
|
+
driver.switch_to.frame(iframe)
|
|
67
|
+
switched_iframe = True
|
|
68
|
+
|
|
69
|
+
search_context = driver
|
|
70
|
+
if shadow_root_selector:
|
|
71
|
+
by_shadow_host = get_by_selector(shadow_root_selector_type)
|
|
72
|
+
if not by_shadow_host:
|
|
73
|
+
raise ValueError(f"Unsupported shadow root selector type: {shadow_root_selector_type}")
|
|
74
|
+
shadow_host = WebDriverWait(driver, timeout).until(
|
|
75
|
+
EC.presence_of_element_located((by_shadow_host, shadow_root_selector))
|
|
76
|
+
)
|
|
77
|
+
shadow_root = shadow_host.shadow_root
|
|
78
|
+
search_context = shadow_root
|
|
79
|
+
|
|
80
|
+
by_selector = get_by_selector(selector_type)
|
|
81
|
+
if not by_selector:
|
|
82
|
+
raise ValueError(f"Unsupported selector type: {selector_type}")
|
|
83
|
+
|
|
84
|
+
wait = WebDriverWait(search_context, timeout)
|
|
85
|
+
if visible_only:
|
|
86
|
+
element = wait.until(EC.visibility_of_element_located((by_selector, selector)))
|
|
87
|
+
else:
|
|
88
|
+
element = wait.until(EC.presence_of_element_located((by_selector, selector)))
|
|
89
|
+
|
|
90
|
+
return element
|
|
91
|
+
|
|
92
|
+
except TimeoutException:
|
|
93
|
+
if switched_iframe and not stay_in_context:
|
|
94
|
+
try:
|
|
95
|
+
original_driver.switch_to.default_content()
|
|
96
|
+
except Exception:
|
|
97
|
+
pass
|
|
98
|
+
raise
|
|
99
|
+
except Exception:
|
|
100
|
+
if switched_iframe and not stay_in_context:
|
|
101
|
+
try:
|
|
102
|
+
original_driver.switch_to.default_content()
|
|
103
|
+
except Exception:
|
|
104
|
+
pass
|
|
105
|
+
raise
|
|
106
|
+
finally:
|
|
107
|
+
if switched_iframe and not stay_in_context:
|
|
108
|
+
try:
|
|
109
|
+
original_driver.switch_to.default_content()
|
|
110
|
+
except Exception:
|
|
111
|
+
pass
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def click_element(selector: str, selector_type: str = "css") -> dict:
|
|
116
|
+
"""Click an element."""
|
|
117
|
+
ctx = get_context()
|
|
118
|
+
if not ctx.driver:
|
|
119
|
+
return {"ok": False, "error": "No driver available"}
|
|
120
|
+
try:
|
|
121
|
+
el = find_element(driver=ctx.driver, selector=selector, selector_type=selector_type, timeout=10.0)
|
|
122
|
+
if not el:
|
|
123
|
+
return {"ok": False, "error": "Element not found"}
|
|
124
|
+
el.click()
|
|
125
|
+
return {"ok": True}
|
|
126
|
+
except Exception as e:
|
|
127
|
+
return {"ok": False, "error": str(e)}
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def fill_text(selector: str, text: str, selector_type: str = "css") -> dict:
|
|
131
|
+
"""Fill text into an element."""
|
|
132
|
+
ctx = get_context()
|
|
133
|
+
if not ctx.driver:
|
|
134
|
+
return {"ok": False, "error": "No driver available"}
|
|
135
|
+
try:
|
|
136
|
+
el = find_element(driver=ctx.driver, selector=selector, selector_type=selector_type, timeout=10.0)
|
|
137
|
+
if not el:
|
|
138
|
+
return {"ok": False, "error": "Element not found"}
|
|
139
|
+
el.clear()
|
|
140
|
+
el.send_keys(text)
|
|
141
|
+
return {"ok": True}
|
|
142
|
+
except Exception as e:
|
|
143
|
+
return {"ok": False, "error": str(e)}
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def debug_element(selector: str, selector_type: str = "css") -> dict:
|
|
147
|
+
"""Debug element information."""
|
|
148
|
+
ctx = get_context()
|
|
149
|
+
if not ctx.driver:
|
|
150
|
+
return {"ok": False, "error": "No driver available"}
|
|
151
|
+
try:
|
|
152
|
+
el = find_element(driver=ctx.driver, selector=selector, selector_type=selector_type, timeout=5.0)
|
|
153
|
+
if not el:
|
|
154
|
+
return {"ok": False, "error": "Element not found"}
|
|
155
|
+
return {
|
|
156
|
+
"ok": True,
|
|
157
|
+
"tag": el.tag_name,
|
|
158
|
+
"text": el.text,
|
|
159
|
+
"visible": el.is_displayed(),
|
|
160
|
+
"enabled": el.is_enabled(),
|
|
161
|
+
}
|
|
162
|
+
except Exception as e:
|
|
163
|
+
return {"ok": False, "error": str(e)}
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
__all__ = [
|
|
167
|
+
'find_element',
|
|
168
|
+
'_wait_clickable_element',
|
|
169
|
+
'get_by_selector',
|
|
170
|
+
'click_element',
|
|
171
|
+
'fill_text',
|
|
172
|
+
'debug_element',
|
|
173
|
+
]
|