refactor: rewrite GETTING_STARTED, cleanup README, add hub.pyw, remove QUICK_START.pdf

This commit is contained in:
Jiaqing Liang
2026-03-27 12:58:13 +08:00
parent ff258dd0a9
commit f6c5011eb0
10 changed files with 536 additions and 528 deletions

View File

@@ -5,6 +5,10 @@
- 非Selenium/Playwright不需调试浏览器或新数据目录
- 支撑 `web_scan`(只读DOM) / `web_execute_js`(执行JS) 等高层工具
## 通用限制
- ⚠web_execute_js中**禁止使用await**会报SyntaxError非async上下文BBS#34验证
- 需要异步操作时用`.then()`链式调用或回调
## 限制(isTrusted)
- JS dispatch的事件`isTrusted=false`,敏感操作(文件上传/部分按钮)会被浏览器拦截
- ⭐**首选绕过CDP桥**——CDP派发的Input事件是浏览器原生级别(isTrusted=true)且无需前台见下方CDP章节
@@ -61,12 +65,67 @@ document.body.appendChild(el); // 响应写回el.textContent
- ⭐batch混合`{cmd:'batch', commands:[{cmd:'cookies'},{cmd:'tabs'},{cmd:'cdp',...},...]}`
- 返回`{ok:true, results:[...]}`一次请求多命令CDP懒attach复用session
- `$N.path`引用第N个结果字段(0-indexed),如`"nodeId":"$2.root.nodeId"`
- 典型:文件上传三连 getDocument→querySelector(input[type=file])→setFileInputFiles
- ⚠batch前序命令失败时后续`$N`引用拿到undefined整条链路**静默失败不报错**需检查返回results数组中每项的ok状态未验证BBS#46
- 典型:文件上传三连 getDocument(**depth:1**性能优化200ms+→个位数ms)→querySelector(input[type=file])→setFileInputFiles未验证BBS#38
- ⚠nodeId路径一致性getDocument+querySelector路径和performSearch+getSearchResults路径的nodeId**不互通**同一batch内不可混用未验证BBS#45
- ⚠文件上传后前端框架(React/Vue)可能不感知→JS补发**两个事件**Vue3需input事件而非仅change未验证BBS#35/#39
```js
el.dispatchEvent(new Event('input', {bubbles:true}));
el.dispatchEvent(new Event('change', {bubbles:true}));
```
- Electron<12/旧WebView可能无InputEvent构造函数防御性降级未验证BBS#42
`const Ctor = typeof InputEvent !== 'undefined' ? InputEvent : Event; el.dispatchEvent(new Ctor('input', {bubbles:true}));`
- 极端情况(框架仍不响应)Runtime.evaluate直接访问React `__reactFiber` 或 Vue `__vue__` 触发状态更新未验证BBS#43
- ⚠上传前检查`input.accept`属性setFileInputFiles不校验类型但前端框架change handler会检查不匹配会静默丢弃未验证BBS#38
- ⚠多file input定位`DOM.querySelectorAll`返回nodeId数组用accept/父容器类名区分用途未验证BBS#38/#39
- 框架选择器Element UI `.el-upload__input` | Ant Design `.ant-upload input[type=file]` | Naive UI `.n-upload-trigger input[type=file]` | Dropzone `.dz-hidden-input`未验证BBS#39
- ⚠Dropzone拖拽上传90%底层仍创建隐藏`<input type=file>`先querySelectorAll('input[type=file]')全局扫未验证BBS#35/#38
- ⭐轻量元素存在检测:`DOM.performSearch({query:'input[type=file]'})`返回resultCount不触发DOM树构建轮询等待元素时避免重复getDocument未验证BBS#39
- performSearch支持三种语法CSS选择器 / XPath(`//input[@type='file']`) / 纯文本自动识别未验证BBS#41
- ⭐瞬态file input处理Ant Design等框架点击上传按钮时动态创建input上传完立即销毁未验证BBS#42/#43
- 方案A(批处理)在同一batch内完成 performSearch→getSearchResults→setFileInputFiles→**discardSearchResults**缩小input被销毁的时间窗口discardSearchResults防searchId泄漏未验证BBS#46
- 方案B(事件监听)`DOM.enable`后监听`DOM.childNodeInserted`事件捕获input创建瞬间零延迟拿到nodeId
- 前提须先对document.body的nodeId调`DOM.requestChildNodes`否则CDP不推送子树变更
- ⚠`DOM.disable`会使所有已获取nodeId失效setFileInputFiles必须在disable之前。正确时序DOM.enable→requestChildNodes→[等事件]→setFileInputFiles→DOM.disable未验证BBS#45
- 方案C(猴子补丁兜底)Runtime.evaluate注入MutationObserver标记新增file input阻止框架销毁争取时间窗口
- ⚠React/Vue用`parentNode.removeChild(node)`而非`node.remove()`需patch `Element.prototype.removeChild`过滤`input[type=file]`未验证BBS#45
- ⚠Svelte等框架可能用`replaceChild`或`textContent=''`清空父容器间接移除绕过removeChild补丁极端场景性价比低建议回退方案B未验证BBS#46
- ⚠阻止销毁会内存泄漏用完后手动清理被标记的节点未验证BBS#45
- FileList只读最终仍需CDP setFileInputFiles
- ⚠tabIdCDP默认sender.tab.id(当前注入页)跨tab需显式tabId或先batch内tabs查
- CDP可用任意方法(Input/Network/DOM/Page/Runtime/Emulation等)单条每次attach→send→detach
- ⭐跨tab无需前台指定tabId即可操作后台标签页
- ⭐绕过isTrustedCDP派发的Input事件是浏览器原生级别
## CDP点击完整生命周期未验证BBS#23
- 通用点击需**三事件序列**mouseMoved → mousePressed → mouseReleased间隔50-100ms
- 省略mouseMoved会导致MUI Tooltip/Ant Design Dropdown等hover依赖组件失效
- ⚠autofill释放是特例只需mousePressed即可见下方autofill章节
- 坐标修正页面有transform:scale/zoom时
```js
var scale = window.visualViewport ? window.visualViewport.scale : 1;
var zoom = parseFloat(getComputedStyle(document.documentElement).zoom) || 1;
var realX = x * zoom; var realY = y * zoom;
```
- iframe内元素CDP点击坐标需合成 `finalX = iframeRect.x + elRect.x`
- 跨域iframe拿不到contentDocument用CDP `Target.getTargets`找iframe targetId → `Target.attachToTarget`建独立会话
## CDP文本输入未验证BBS#23
- `Input.insertText({text:'...'})` — 直接插入不触发keydown/keyup
- `Input.dispatchKeyEvent` — 逐键派发,慢但完整模拟
- React/Vue受控组件先insertText再JS手动dispatch `input`事件input事件不检查isTrusted
- 简单输入框用insertText够用
## CDP DOM域穿透 closed Shadow DOM未验证BBS#24/#25
- `DOM.getDocument({depth:-1, pierce:true})` 穿透所有Shadow边界含closed
- `DOM.querySelector({nodeId, selector})` 定位 → `DOM.getBoxModel({nodeId})` 取坐标
- getBoxModel返回content八值[x1,y1,...x4,y4],中心用**四点平均**centerX=sum(x)/4, centerY=sum(y)/4
- ⚠不能简化为对角线平均——元素有transform:rotate/skew时四点非矩形
- querySelector**不能跨Shadow边界写组合选择器**需分步先找host再在其shadow内找子元素
- ⚠nodeId在DOM变更后失效 → 用`backendNodeId`更稳定或重新getDocument刷新
- 渲染检查:`DOM.resolveNode` → `Runtime.callFunctionOn` 检查offsetHeight>0
- 完整pipeline: getDocument(pierce) → querySelector → getBoxModel → 四点平均坐标 → Input三事件点击
## autofill获取
检测web_scan输出input带`data-autofilled="true"`value显示为受保护提示(非真实值Chrome安全保护需点击释放)
- ⭐首选CDP单次点击JS取任一autofill输入框坐标→CDP `Input.dispatchMouseEvent` mousePressed一次即可释放→JS读`.value`