TMWebDriver SOP
- 直接用web_scan/web_execute_js工具。本文件只记录特性和坑。
- 底层:
../TMWebDriver.py通过Chrome扩展接管用户浏览器(保留登录态/Cookie) - 非Selenium/Playwright,保留用户浏览器登录态
通用特性
- ⚠web_execute_js里使用
await时需**显式return**才能拿到返回值(底层async包裹,不写return则返回null) - ✅web_scan自动穿透同源iframe;跨域iframe需CDP或postMessage(见下方章节)
限制(isTrusted)
- JS事件
isTrusted=false,敏感操作(如文件上传/部分按钮)可能被拦截;这类场景首选CDP桥 - ⚠JS点击按钮打不开新tab→可能是浏览器弹窗拦截,换CDP点击试试
- 文件上传:JS无法填充
<input type=file>;首选CDP batch:getDocument→querySelector→DOM.setFileInputFiles,备选ljqCtrl物理点击 - 需转物理坐标时:
physX = (screenX + rect中心x) * dpr,physY = (screenY + chromeH + rect中心y) * dpr;其中chromeH = outerHeight - innerHeight
导航
web_scan仅读当前页不导航,切换网站用web_execute_js+location.href='url'
Google图搜
- class名混淆禁硬编码,点击结果用
[role=button]div - web_scan过滤边栏,弹出后用JS:文本
document.body.innerText,大图遍历img按naturalWidth最大取src - "访问"链接:遍历a找
textContent.includes('访问')的href - 缩略图:
img[src^="data:image"]直接提取;大图src可能截断用return img.src
Chrome下载PDF
场景:PDF链接在浏览器内预览而非下载
fetch('PDF_URL').then(r=>r.blob()).then(b=>{
const a=document.createElement('a');
a.href=URL.createObjectURL(b);
a.download='filename.pdf';
a.click();
});
注意:需同源或CORS允许,跨域先导航到目标域再执行
Chrome后台标签节流
- 后台标签中
setTimeout被Chrome intensive throttling延迟到≥1min/次,扩展脚本中避免依赖setTimeout轮询 - 某些SPA页面需CDP
Page.bringToFront切到前台才会加载数据
CDP桥(tmwd_cdp_bridge扩展) ⭐首选
扩展路径:assets/tmwd_cdp_bridge/(需安装,含debugger权限)
⚠TID约定标识:首次运行自动生成到assets/tmwd_cdp_bridge/config.js(已gitignore),扩展通过manifest引用
调用:web_execute_js script直传JSON字符串(工具层自动识别对象格式,走WS→background.js cmd路由)
// 直接传JSON字符串作为script参数,无需DOM操作
web_execute_js script='{"cmd": "cookies"}'
web_execute_js script='{"cmd": "tabs"}'
web_execute_js script='{"cmd": "cdp", "tabId": N, "method": "...", "params": {...}}'
web_execute_js script='{"cmd": "batch", "commands": [...]}'
// 返回值直接是JSON结果
通信方式:⭐JSON字符串直传(首选) | TID DOM方式(TID元素+MutationObserver,web_scan/execute_js底层依赖)
单命令:{cmd:'tabs'} | {cmd:'cookies'} | {cmd:'cdp', tabId:N, method:'...', params:{...}} | {cmd:'management', method:'list|reload|disable|enable', extId:'...'}
- management:list返回所有扩展信息;reload/disable/enable需传extId
- ⭐batch混合:
{cmd:'batch', commands:[{cmd:'cookies'},{cmd:'tabs'},{cmd:'cdp',...},...]}- 返回
{ok:true, results:[...]},一次请求多命令,CDP懒attach复用session - 子命令会自动继承外层batch的tabId(如cookies命令可正确获取当前页面URL)
$N.path引用第N个结果字段(0-indexed),如"nodeId":"$2.root.nodeId"- ⚠batch前序命令失败时,后续
$N引用会静默变成undefined;要检查results数组中每项的ok状态 - 典型文件上传:getDocument(depth:1) → querySelector(
input[type=file]) → setFileInputFiles - 思想:
- 同一链路内保持nodeId来源一致,不混用querySelector路径与performSearch路径
- 上传后前端框架可能不感知,必要时JS补发
input/change事件 - 上传前检查
input.accept;多input时用accept/父容器语义区分 - 等待元素优先用
DOM.performSearch('input[type=file]')做轻量轮询 - 瞬态input的核心是缩短发现→setFileInputFiles时间窗:优先同batch完成;再不行用DOM事件监听;猴子补丁仅作兜底思路
- ⚠tabId:CDP默认sender.tab.id(当前注入页),跨tab需显式tabId或先batch内tabs查
- 返回
- ⭐跨tab无需前台:指定tabId即可操作后台标签页
CDP点击完整生命周期(未验证,BBS#23)
- 通用点击需三事件序列:mouseMoved → mousePressed → mouseReleased(间隔50-100ms)
- 省略mouseMoved会导致MUI Tooltip/Ant Design Dropdown等hover依赖组件失效
- ⚠autofill释放是特例,只需mousePressed即可(见下方autofill章节)
- 坐标修正(页面有transform:scale/zoom时):
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:
- ⚠
Target.getTargets/Target.attachToTarget在CDP桥中返回"Not allowed"(chrome.debugger权限限制) - ⭐已验证方案:
Page.getFrameTree找iframe frameId →Page.createIsolatedWorld({frameId})获取contextId →Runtime.evaluate({expression, contextId})在iframe中执行JS - batch链式引用:
$0.frameTree.childFrames遍历找url匹配的frame,$1.executionContextId传给evaluate - postMessage中继方案仅在content script已注入iframe时有效,第三方支付iframe通常无注入
CDP文本输入(未验证,BBS#23)
insertText快但无key事件;受控组件需补dispatchinput事件- 需完整键盘模拟时用
dispatchKeyEvent逐键派发
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刷新
autofill获取与登录
检测:web_scan输出input带data-autofilled="true",value显示为受保护提示(非真实值,Chrome安全保护需点击释放)
- ⚠前置条件:必须先CDP
Page.bringToFront切tab到前台,Chrome仅在前台tab释放autofill保护值,后台tab物理点击无效 - ⭐一键释放与登录:bringToFront → mousePressed点任一字段(无需Released,一个释放全页) → 等500ms → 补input/change事件 → 点登录
验证码/页面视觉截图
- ⭐首选CDP截图:
Page.captureScreenshot(format:'png')→返回base64,无需前台/后台tab也行,全页高清 - 验证码canvas/img:JS
canvas.toDataURL()直接拿base64最干净
simphtml与TMWebDriver调试
- simphtml调试必须通过
code_run注入JS到真实浏览器(Python端无法模拟DOM) d=TMWebDriver(),d.set_session('url_pattern'),d.execute_js(code)→ 返回{'data': value}- simphtml:
str(simphtml.optimize_html_for_tokens(html))— 返回BS4 Tag需str()
连不上排查
web_scan失败时按序排查(自动检测优先,用户参与放最后):
①浏览器没开?→检查浏览器进程是否在跑(tasklist/ps),没有则启动并打开正常URL(⚠about:blank等内部页不加载扩展)
②WS后台挂了?→本机18766端口没监听即dead→手动后台from TMWebDriver import TMWebDriver; TMWebDriver()起master
③扩展没装?→读Chrome用户目录下Secure Preferences→extensions.settings中找path含tmwd_cdp_bridge的条目
找到→扩展已装,排查其他原因;没找到→走web_setup_sop
④以上都正常仍连不上→请求用户协助