--- name: browser-automation description: 补充浏览器自动化缺失能力 — 等待元素、文件上传、多标签页、下载拦截 tags: [browser, automation, cdp, utility] version: 1 created: 2026-06-03 --- # Browser Automation Extensions 补充内置浏览器工具缺失的高级能力。 ## 1. 等待元素 (wait_for_element) 内置工具无显式等待,用轮询实现: ```python from hermes_tools import terminal import time def wait_for_element(selector, timeout=10, poll_interval=0.5): """等待元素出现在DOM中""" start = time.time() while time.time() - start < timeout: result = terminal(f""" node -e " const el = document.querySelector('{selector}'); console.log(el ? 'found' : 'not_found'); " """) if 'found' in result.get('output', ''): return True time.sleep(poll_interval) return False # 使用示例 if wait_for_element('.loading-spinner', timeout=15): # 元素出现后继续操作 pass ``` **适用场景**: SPA动态加载、异步渲染完成后操作 ## 2. 文件上传 (upload_file) 通过CDP的DOM.setFileInputFiles实现: ```python from hermes_tools import terminal def upload_file(selector, file_path): """上传文件到input[type=file]""" # 1. 获取元素的backendNodeId node_info = terminal(f""" node -e " const el = document.querySelector('{selector}'); if (!el) {{ console.log('ERROR: element not found'); process.exit(1); }} // CDP会注入__cdpBinding if (typeof __cdpBinding !== 'undefined') {{ __cdpBinding.getNodeId(el).then(id => console.log(id)); }} else {{ console.log('CDP_NOT_AVAILABLE'); }} " """) # 2. 通过browser_cdp直接调用 # 需要先获取target_id,然后调用DOM.setFileInputFiles terminal(f""" hermes browser cdp DOM.setFileInputFiles \\ --params '{{"files": ["{file_path}"], "backendNodeId": NODE_ID}}' """) ``` **简化版** (直接用browser_cdp工具): ```python # 假设已通过browser_snapshot获取input元素的ref # 用browser_console获取backendNodeId result = browser_console(expression=""" (function() { const el = document.querySelector('input[type=file]'); return el ? el.getAttribute('data-node-id') || 'need_cdp' : null; })() """) # 然后调用browser_cdp browser_cdp( method='DOM.setFileInputFiles', params={'files': ['/path/to/file.pdf'], 'backendNodeId': 12345} ) ``` **注意**: 需要先从DOM获取backendNodeId,可通过DOM.getDocument + DOM.querySelector链式调用 ## 3. 多标签页管理 (manage_tabs) 通过CDP的Target API: ```python from hermes_tools import terminal def list_tabs(): """列出所有标签页""" result = terminal("hermes browser cdp Target.getTargets --params '{}'") # 解析JSON获取target列表 return result def switch_tab(target_id): """切换到指定标签页""" terminal(f"hermes browser cdp Target.attachToTarget --params '{{\"targetId\": \"{target_id}\"}}'") def create_tab(url): """创建新标签页""" result = terminal(f"hermes browser cdp Target.createTarget --params '{{\"url\": \"{url}\"}}'") # 返回targetId return result # 使用示例 tabs = list_tabs() # 解析tabs获取target_id switch_tab('TARGET_ID_HERE') ``` **直接用browser_cdp**: ```python # 列出所有标签 targets = browser_cdp(method='Target.getTargets', params={}) # 创建新标签 new_tab = browser_cdp(method='Target.createTarget', params={'url': 'https://example.com'}) target_id = new_tab['targetId'] # 在指定标签执行JS browser_cdp( method='Runtime.evaluate', params={'expression': 'document.title', 'returnByValue': True}, target_id=target_id ) ``` ## 4. 下载拦截 (download_config) 通过CDP配置下载行为: ```python def config_download(download_path, allow=True): """配置下载目录和行为""" browser_cdp( method='Page.setDownloadBehavior', params={ 'behavior': 'allow' if allow else 'deny', 'downloadPath': download_path } ) # 使用示例 config_download('/tmp/downloads', allow=True) # 然后点击下载链接,文件会保存到指定目录 # 可通过terminal检查文件是否存在 ``` **等待下载完成**: ```python import os import time def wait_for_download(filename, timeout=30): """等待下载完成""" download_path = '/tmp/downloads' filepath = os.path.join(download_path, filename) start = time.time() while time.time() - start < timeout: if os.path.exists(filepath) and not os.path.exists(filepath + '.crdownload'): return filepath time.sleep(0.5) return None ``` ## 5. 简单验证码识别 (captcha_ocr) 用vision_analyze识别简单验证码图片: ```python def solve_simple_captcha(image_selector): """识别简单验证码(数字/字母)""" # 1. 获取图片URL或base64 img_info = browser_console(expression=f""" (function() {{ const img = document.querySelector('{image_selector}'); return img ? img.src : null; }})() """) # 2. 用vision分析 result = vision_analyze( image_url=img_info, question="识别这个验证码图片中的字符,只返回字符本身,不要其他文字" ) # 3. 提取识别结果(需要从result解析) return result # 如 "a3Bx" # 使用示例 captcha_text = solve_simple_captcha('img.captcha') browser_type(ref='@e15', text=captcha_text) ``` **局限**: 复杂验证码(滑块、点选、reCAPTCHA)无法可靠解决 ## 6. 网络请求拦截 (network_intercept) 通过CDP拦截和修改网络请求: ```python def enable_network_intercept(): """启用网络拦截""" browser_cdp(method='Network.enable', params={}) browser_cdp( method='Network.setRequestInterception', params={'patterns': [{'urlPattern': '*'}]} ) def mock_api_response(url_pattern, response_data): """Mock API响应""" # 需要监听Network.requestIntercepted事件 # 然后用Network.continueInterceptedRequest返回mock数据 pass # 复杂场景,通常需要配合脚本 # 简化版:直接修改页面fetch/XHR browser_console(expression=""" const originalFetch = window.fetch; window.fetch = function(url, opts) { if (url.includes('/api/user')) { return Promise.resolve({ json: () => Promise.resolve({name: 'Mock User', id: 123}) }); } return originalFetch(url, opts); }; """) ``` ## 使用模式 ### 场景1: SPA表单提交后等待结果 ```python browser_click(ref='@e10') # 点击提交 wait_for_element('.success-message', timeout=10) browser_snapshot() # 获取结果 ``` ### 场景2: 多步骤流程跨标签页 ```python # 主标签操作 browser_navigate('https://app.com/dashboard') browser_click(ref='@e5') # 打开新标签的链接 # 获取新标签 targets = browser_cdp(method='Target.getTargets', params={}) new_tab_id = [t['targetId'] for t in targets['targetInfos'] if t['url'].endswith('/details')][0] # 在新标签操作 browser_cdp( method='Runtime.evaluate', params={'expression': 'document.querySelector(".detail").innerText', 'returnByValue': True}, target_id=new_tab_id ) ``` ### 场景3: 文件上传+下载 ```python config_download('/tmp/exports') upload_file('input[type=file]', '/path/to/data.csv') browser_click(ref='@e20') # 点击处理按钮 wait_for_download('result.xlsx', timeout=60) ``` ## 7. Bricks 框架兼容性 **bricks widget 的 click 事件兼容 `browser_click`。** 已验证通过(2026-08-07,pipeline.opencomputing.cn)。 bricks 通过 `widget.bind('click', handler)` → `dom_element.addEventListener('click', handler)` 绑定事件,`browser_click` 通过 CDP `Input.dispatchMouseEvent` 触发浏览器原生 click 事件合成,`addEventListener` 回调正常触发。 | 操作 | 结果 | |------|------| | browser_click Menu 项 | ✅ 触发 `regen_menuitem_event()` | | browser_click toggle 按钮 | ✅ 触发 `idset= sidebar_menu` | | browser_console 查日志 | ✅ 完整捕获 | **注意事项:** - 页面 JS 完全加载后才可点击(约 2-3s),否则点击无响应 - snapshot 中 bricks widget 的文本标签可能显示为 "generic",需 `browser_console` 查 DOM 确认 ## 注意事项 1. **CDP需要活跃连接** - 确保browser已连接(/browser connect或配置browser.cdp_url) 2. **backendNodeId获取** - 文件上传需要先通过DOM.getDocument获取节点ID 3. **多标签页状态隔离** - 每个标签页有独立的JS上下文,cookie/session共享但DOM隔离 4. **下载路径权限** - 确保download_path目录存在且有写权限 5. **验证码识别准确率** - 简单验证码~80-90%,复杂验证码<50% ## 调试技巧 ```python # 查看当前CDP连接状态 browser_cdp(method='Browser.getVersion', params={}) # 列出所有可用CDP方法 # 参考: https://chromedevtools.github.io/devtools-protocol/ # 检查网络请求 browser_cdp(method='Network.enable', params={}) # 后续请求会触发事件,可通过browser_console监听 ```