跳转到正文
返回门户

LFDOM ​

全局变量:LFDOM · 启用方式:// @require dom · 附带:自动包含 network

DOM 与页面操作库:就绪等待、HTML 挂载、toast、Playwright 风格定位器(自动等待/重试)、语义快照(看 → 操作)、卡片聚合、表单填充、虚拟列表收集、截图与网络等待。

仅在页面环境可用:已保存/已安装应用与 liveDebug(USER_SCRIPT 隔离世界)、run --tab 临时脚本(默认 MAIN 世界)。后台应用分支(// @match background / run --target background)与不带 --tab 的 run(编辑器沙箱)没有目标网页:声明了 @require dom 时库仍会加载,但操作的是不可见、零尺寸的沙箱文档——页面选择器匹配不到元素,click / fill / hover / waitFor(visible)等依赖可见性的操作会超时并附带 [no web page in the <env> environment …] 提示,waitForResponse / waitForRequest 直接以 LF_NETWORK_NO_TAB 拒绝。请用 LFRuntime.executionEnvironment 分流,不要在这些环境交付 UI。@require dom 会在运行时自动注入 network 供 waitForResponse / waitForRequest 使用,但源码里直接调用 LFNetwork.* 仍须声明 // @require network。

方法 ​

ts
LFDOM.ready(callback): void
LFDOM.ready(options, callback): void
await LFDOM.ready(options?): Promise<void>

文档脱离 loading(DOMContentLoaded)后执行回调;DOM 已解析时同步执行。不幂等:每次调用都会各执行一次回调。options.waitFor(CSS 选择器,或返回真值即就绪的谓词函数,可为 async)用于再等 SPA 主体渲染,options.timeout 默认 30000ms;超时不会 reject,只在 console 打印警告后照常执行回调。不传回调时返回 Promise

ts
LFDOM.whenReady(options?): Promise<void>

Promise 形式的就绪等待,等价于 await LFDOM.ready(options);options 同上,另有 pierce(默认 false)、interval(默认 50ms 起轮询)、maxInterval(退避上限 500ms)

ts
LFDOM.mountHTML(html, parent?): Element | Element[] | Node | null

把 HTML 字符串挂载为真实节点(parent 默认 document.body):单个顶层元素返回该元素,多个并列元素返回数组,纯文本返回 Text 节点,空串返回 null

ts
LFDOM.createFloatingRoot(options?): HTMLDivElement

创建固定定位的浮层根节点;同一应用同 id 复用,id 已被其他应用或页面占用时改用 <id>--lf-<应用id>

ts
LFDOM.toast(message, duration?): HTMLDivElement

弹出自动消失的提示条

ts
LFDOM.locator(selector, options?): Locator

创建定位器:查询 + 动作(click/fill/hover…)自动等待重试;selector 也可以是 snapshot 给出的 @N 引用

ts
LFDOM.activeElement(): Locator

当前聚焦元素的定位器

ts
await LFDOM.clickAt(x, y, options?): Promise<Element>

按视口坐标点击

ts
LFDOM.mountAnchoredHTML(options): AnchoredMountState

把 HTML 锚定挂载到页面元素旁,锚点出现时自动挂上;锚点被替换后自动重挂(仅限锚点的直接父容器不变,见下文 reanchorOnLost)

ts
LFDOM.unmountAnchored(key): boolean

卸载锚定挂载

ts
LFDOM.mountFloatingHTML(options): FloatingMountState

浮动跟随某个锚点元素的挂载

ts
LFDOM.unmountFloating(key): boolean

卸载浮动挂载

ts
LFDOM.mountHoverAction(options): HoverActionState

为卡片流挂载共享悬停操作,详见 mountHoverAction

ts
LFDOM.waitForElement(selector, options?): Promise<Element | true>

等待元素达到指定 state(默认 'attached',注意与 Locator.waitFor 默认 'visible' 不同),默认超时 5000ms;state 为 hidden / detached 时 resolve 为 true 而不是元素

ts
LFDOM.fillForm(map, options?): Promise<Record<string, true>>

按「CSS 选择器 / @N 引用 → 值」映射批量填表(键不支持 label 文本)

ts
LFDOM.findInVirtualList(options): Promise<Array<Item>>

滚动收集虚拟列表/瀑布流的全部条目并去重

ts
LFDOM.waitForResponse(urlPattern, options?): Promise<NetworkRecord>

等待命中 URL 模式的响应(基于 LFNetwork;响应头到达时即 resolve)

ts
LFDOM.waitForRequest(urlPattern, options?): Promise<NetworkRecord>

等待命中 URL 模式的请求

ts
LFDOM.intercept(urlPattern, handler): () => void

猴补丁当前 JS 世界的 fetch/XHR 做 mock(返回取消函数)。仅适合 run --tab(MAIN 世界)临时探测:已保存应用与 liveDebug 运行在隔离世界,看不到页面自身的请求,正式 Compile 会直接拒绝包含 LFDOM.intercept 的源码。产品代码请用 LFRuntime.network 并声明 @intercept。未取消的 handler 在 run 进程结束 / liveDebug 热重载时自动注销;最后一个 handler 注销后还原原始的 fetch/XHR

ts
LFDOM.screenshot(target?, options?): Promise<string>

把页面或元素的 DOM 序列化进 SVG foreignObject,返回 data:image/svg+xml 字符串——不是像素截图(不含外部样式表、canvas、video、跨域图片);需要真实截图请用终端 screenshot 命令

ts
LFDOM.actAsHuman(options?): LFDOM

返回一个代理对象:只有经该代理调用的顶层 click / dblclick / hover / fill / type / press 会先随机延迟;不改变 LFDOM 本身,也不影响 Locator 上的动作

ts
await LFDOM.snapshot(options?): Promise<{ text, nodes, cards, ... }>

生成带 @N 引用的语义快照文本,适合「看一眼再操作」

ts
await LFDOM.cards(options?): Promise<CardRecord[]>

把重复卡片结构(商品列表等)聚合为结构化记录

ts
await LFDOM.layoutAudit(root?, options?): Promise<{ text, issues, count }>

布局体检:重叠/溢出/对比度问题列表

ts
await LFDOM.fireReactHandler(selector, eventName?): Promise<boolean>

直接触发 React 合成事件处理器(顽固 hover 场景)

参数详解 ​

createFloatingRoot(options?) / toast(message, duration?) ​

参数类型默认值说明
options.idstringuserscript-lf-root根节点的 DOM id;同一应用相同 id 复用同一元素,该 id 已被其他应用/页面占用时改用 <id>--lf-<应用id>
options.classNamestring''每次调用都会追加到根节点的 classList(复用已有元素时同样追加)
options.zIndexnumber / string2147483000新建根节点的 z-index,留有余量给站点自身的弹层(复用已有元素时不修改)
message(toast)string—纯文本,经 textContent 赋值,不解析 HTML
duration(toast)number2400总显示毫秒数;返回元素可调 .remove() 提前关闭

新建的根节点默认样式为 position: fixed; top: 16px; right: 16px; max-width: 420px。被页面脚本移除时会自动补挂回 body;调用根节点自己的 .remove() 才会真正移除。

定位器 Locator ​

LFDOM.locator(selector, { root?, pierce? }) 创建惰性定位器:动作前自动等待元素挂载/可见/稳定/可用(默认超时 5000ms 并自动重试),默认穿透开放 Shadow DOM。selector 必须是原生 CSS 或 snapshot 的 @N 引用;:has-text() 等 Playwright 专有伪类会在构造时直接抛错——文本匹配用语义构造器:

构造器说明
getByRole(role, { name?, exact? })按可访问角色 + 可访问名匹配(button/link/textbox/checkbox 等,含隐式 HTML 角色);操作交互控件的首选方式
getByText(text, { exact? })按可见文本匹配,返回包含文本的最内层元素;默认子串匹配(大小写不敏感、空白归一化),exact: true 精确匹配
getByLabel(text)按关联 label 匹配表单控件(解析 aria-label / for= / 包裹 label),返回控件本身
getByPlaceholder(text) / getByTitle(text) / getByAltText(text) / getByTestId(id)按 placeholder / title / img alt / data-testid 匹配

链式收窄:locator(child)、filter({ hasText, hasNotText, has, hasNot })、first() / last() / nth(i)、and(other) / or(other)、all()、count()。语义查询可作用在父定位器上(如 row.getByTitle('删除')),是消除重复按钮歧义的推荐方式。中文标签常互为子串(如「删除」与「批量删除」),必要时传 { exact: true } 或先用父容器限定范围。

动作/读取说明
click({ modifiers?, position?, force? }) / dblclick / tripleClick / hover / tap / dragTo(target)完整的浏览器事件序列(pointer→mouse→click);modifiers: ['Control'] 支持 Ctrl+Click 多选等
fill(value) / type(text) / clear()支持 input/textarea 与 contenteditable,触发 input/change 让 React/Vue 受控组件正常更新;目标 readonly/disabled 时直接抛错,{ force: true } 强写
press(key)带真实默认行为的按键(字符插入、Backspace、方向键移动光标、Control+a 全选、Enter 提交表单、Tab 焦点移动);组合键用 Modifier+Key 语法
selectOption(value)仅支持原生 select;接受字符串(按 value 或可见文本)、{ value } / { label } / { index } 或数组;自定义下拉需像用户一样点触发器再点选项
check() / uncheck() / setChecked(bool) / setFiles(files)复选框/单选/文件输入;隐藏 input + 样式 label 的自定义控件会自动转投可见 label;setFiles 返回回执 { ok, count, names, input } 而不是元素
focus() / blur() / scrollIntoViewIfNeeded()焦点与滚动
waitFor({ state, timeout })state 可为 attached / visible(默认)/ hidden / detached / stable
textContent() / innerText() / innerHTML() / inputValue() / getAttribute(name) / boundingBox()读取内容与几何信息
isVisible() / isHidden() / isEnabled() / isDisabled() / isChecked() / isEditable()状态判断
elementHandle() / elementHandles() / elementHandleOrNull()取原生元素(前者自动等待挂载,后两者立即返回)
evaluate(fn) / evaluateAll(fn)对匹配元素执行自定义函数
allInVirtualList(options?)在当前 Locator 作用域内滚动收集虚拟列表,等价于 findInVirtualList({ ...options, locator: this });参数同下文 findInVirtualList,请指定 itemSelector。返回 Promise<Array<{ key, text, html, href, dataset }>>
fireReactHandler(eventName?, options?) / dispatchReact(eventName?, options?)两者等价,返回 Promise<boolean>。eventName 为 React 事件名,默认 'onMouseEnter';支持 options.modifiers(如 ['Control']),内部以 force: true 跳过可操作性检查。先在当前世界调用 handler,未命中时转 MAIN world;无 handler 返回 false,元素不存在默认等待 5000ms 后超时。用于 hover() 无效时兜底

mountAnchored / mountFloating(options) ​

把按钮/面板挂到页面已有元素旁。两者共用大部分选项,内部走 LFRuntime.mountHot,liveDebug 重注入时复用元素并调用 onUpdate 而不是重复插入:

选项类型默认值说明
keystring必填挂载槽标识
anchorstring / Element / LFLocator / () => Element必填锚点:CSS 选择器、元素、语义定位器或返回元素的函数(anchorSelector 为别名)
position(anchored)'beforebegin' / 'afterbegin' / 'beforeend' / 'afterend''afterend'相对锚点的插入位置(参与原生布局);其他取值静默回落为 'afterend'
placement(floating)'left' / 'right' / 'top' / 'bottom' / 'overlay''right'浮层相对锚点的方位(position:fixed 跟随,不进入锚点父级子节点序列)
offset(floating)number8与锚点的间距(替代 margin)
host(floating)Elementdocument.body浮层元素的挂载父节点;浮层强制 position: fixed、margin: 0,未设置 z-index 时为 2147483646
html / factory / vue—三选一HTML 片段 / 工厂函数 / Vue 组件配置 { createApp, h?, component, props?, setup? }(响应式 props 必须同时传 h)
onMount / onUpdate / onDisposeFunction—挂载 / 热更新 / 卸载回调
persistentbooleantrueliveDebug 卸载时保留元素;需要严格清理时传 false 或调 unmountAnchored(key)
reanchorOnLostbooleantrue锚点被框架替换后自动重新解析(仅显式传 false 关闭;string/Locator/function 锚点可用)。已知限制:只在锚点的直接父容器保持不变时生效——框架连同父容器或更高层祖先一起重渲时,锚定元素会随旧子树消失且不再重挂,浮层也不会跟随新锚点
anchorTimeoutnumber30000等待锚点出现的超时毫秒数
piercebooleanfalse锚点查询是否穿透 Shadow DOM(与定位器默认穿透不同,锚点在 shadow 内时需显式传 true)
suppressInsertionWarning(anchored)booleanfalse关闭 liveDebug 下 beforebegin / afterend 插入框架容器的提示

挂载是异步的:返回的 state 提供 state.ready(Promise),操作挂载出的元素前先 await state.ready 或使用 onMount 回调。选型:锚点父级是稳定容器时优先 mountAnchored;必须挨着 Vue/React 管理的按钮显示时用 mountFloating,避免破坏框架的子节点 diff。

mountHoverAction(options) ​

卡片流悬停操作统一使用此方法。单个持久 fixed root 通过 document 级 pointer 委托跟随当前卡片,处理延迟隐藏及 scroll / resize 重定位。

选项说明
key必填;bundle 内唯一、跨热更新保持不变的槽位标识
cardSelector必填;卡片的 CSS 选择器
html / factory(state) => Element二选一,生成共享操作元素;也可改用 vue
vue替代 html / factory 的 Vue 配置 { createApp, h?, component, props?, setup? }
placement默认 'top-right-inside';另支持 'top-left-inside' / 'bottom-right-inside' / 'bottom-left-inside'
offset相对卡片的位置偏移
hideDelay隐藏延迟
getIdentity(card)显示时捕获稳定业务标识,直接传给 onActivate,无需重新读取 hover 状态
onActivate({ event, card, identity, element, state })点击回调
onShow / onHide / onMount / onDispose显示 / 隐藏 / 挂载 / 卸载回调

返回 HoverActionState,包含:

字段 / 方法说明
root / element共享的 fixed root / 操作元素
activeCard / identity当前活动卡片 / 业务标识
visible是否显示
show(card)在指定卡片上显示
hide()隐藏
reposition()重新定位
  • onActivate 在 root 捕获阶段处理真实 click,onMount 中内部按钮的 stopPropagation() / preventDefault() 不影响回调,通常无需转发 click。
  • Vue 按钮必须使用 vue 配置:复用单个实例并自动卸载,不要另写 HoverMount / createFloatingRoot / createApp 状态机。
  • 不要在 mouseover / mouseout 中反复调用 mountFloating / unmountFloating,避免按钮在 mousedown 与 click 之间被移除。

示例

js
// @require dom

LFDOM.mountHoverAction({
  key: "product-hover-action",
  cardSelector: "[data-product-id]",
  html: '<button type="button">查看商品编号</button>',
  getIdentity: (card) => card.dataset.productId,
  onActivate: ({ identity }) => LFDOM.toast(`商品编号:${identity}`),
});

snapshot(options?) / cards(options?) ​

选项类型默认值说明
interactiveOnlybooleanfalse只保留可交互角色(button/link/textbox 等);标题等有名字的非交互节点也会被丢弃
viewportOnlybooleanfalse只保留与视口相交的节点
contentOnlybooleanfalse跳过 footer/banner/navigation 等地标区域
groupCardsbooleanfalse把重复卡片折叠为单行并填充返回值的 cards[]
cardMinRepeatnumber3识别为卡片组的最小重复次数(最小取 2)
cardSelector(cards)string—启发式失效时的确定性兜底:直接指定卡片元素选择器
cardRootElement同 root只在该子树内识别卡片
maxNodes / maxCardsnumber4000 / 200输出上限
maxCardActions / maxCardDescendantsnumber12 / 150单张卡片收集的动作数上限 / 识别卡片时扫描的后代数上限
rootElementdocument.documentElement限定遍历子树;必须是 Element(Document / ShadowRoot 会被忽略并回落默认值)

snapshot 返回 { text, nodes, cards, count, refCount }:text 是带 @N 引用的缩进可访问性树;nodes[] 每项含 { ref, role, name, depth, url, region, states }。@N 引用可传给任何接受选择器的地方——LFDOM.locator('@42'),或顶层快捷方法如 await LFDOM.click('@42')——但只对最近一次 snapshot 有效:每次 snapshot 都会重建引用表,页面变化后须重新 snapshot,失效引用会直接抛错。cards 返回卡片数组,每项 { index, depth, region, ref, title, url, image, text, actions: [{ ref, role, name }] },适合商品列表/信息流的结构化遍历。

其他常用方法 ​

方法参数说明
waitForElement(selector, { timeout?, state? })state 默认 'attached',timeout 默认 5000ms;attached/visible/stable 时返回元素,hidden / detached 时返回 true
fillForm(map, { root?, timeout? })map 的键是 CSS 选择器或 @N 引用(不支持直接写 label 文本,按 label 定位请先用 getByLabel 再逐个操作),值按控件类型自动选择 fill/selectOption/setChecked;任一字段失败即抛错,已填字段不回滚;root 可限定到 iframe 文档或 ShadowRoot
findInVirtualList({ itemSelector, scrollContainer?, maxItems?, idleMs?, dedupeKey?, timeout? })滚动收集虚拟列表条目并去重,返回 { key, text, html, href, dataset }[]。itemSelector 实际必填(省略时为 '*',会匹配到整页元素);scrollContainer 可为选择器字符串、Element 或 Locator,省略或传 'window' 时整页滚动;maxItems 默认 100,idleMs(连续无新条目即停止)默认 300、最小 50;dedupeKey 默认依次取 data-id、href、文本,也可传 (element) => string
waitForResponse(urlPattern, { timeout?, networkOptions? }) / waitForRequest(...)urlPattern 支持字符串(子串)/正则/谓词函数;timeout 默认 5000ms。JS 监听在调用时同步注册,但若 LFNetwork 尚未在监听,会在后台异步 start(),调试器附加完成前发出的请求会被漏掉——首次使用前先 await LFNetwork.start()(需 // @require network),再「先建 wait、后触发」:const p = LFDOM.waitForResponse(/api/); await action(); const rec = await p;。waitForResponse 在响应头到达时即 resolve(rec.finished 通常仍为 false),要读响应体请等 finished 后再 LFNetwork.getResponseBody(rec.requestId)。由它隐式启动的监听不会自动停止;已在监听时 networkOptions 被忽略
intercept(urlPattern, handler)仅 MAIN 世界临时探测(见上文)。urlPattern 支持字符串(子串)/正则/谓词;handler 收到 { type: 'fetch', url, input, init } 或 { type: 'xhr', method, url, body },返回 { body, status?, headers? } 即 mock(status 默认 200),返回假值放行;XHR mock 只设置 readyState / status / responseText
screenshot(target?, options?)target 为 CSS 选择器或 Element(不接受 Locator),省略时为整个文档;options 只影响等待元素出现;返回 data:image/svg+xml;charset=utf-8,...
actAsHuman({ delay? })返回代理对象;经代理调用的顶层 click/dblclick/hover/fill/type/press(selector, …) 先随机等待 delay~2×delay 毫秒(delay 默认 80)
clickAt(x, y, { modifiers? })按视口坐标命中最深元素并派发完整事件序列(穿透开放 Shadow DOM);该点无元素时抛错;仅用于 canvas 热区等真坐标场景
layoutAudit(root?, { maxIssues?, contrast?, overlap? })root 可为选择器/Element/Locator,省略或匹配不到时回落 body;maxIssues 默认 50(上限 200),contrast / overlap 默认 true。返回 { text, issues: [{ type, element, detail }], count },element 是元素的描述字符串而不是 Element;issue 类型含横向溢出、零尺寸交互元素、被遮挡、兄弟重叠、文本截断、低对比度等
fireReactHandler(selector, eventName?)直接调用 React fiber 上的合成事件处理器(默认 onMouseEnter):元素存在但无 handler 时返回 false,元素不存在时等待超时(默认 5000ms)后抛错;hover 无法展开浮层时的兜底
frameLocator(selector)进入同源 iframe:LFDOM.frameLocator('#if').getByRole(...);可链式穿多层 iframe,支持 nth(i) / first() / last();.frame() 返回 Promise<Document>

顶层快捷方法 ​

以下方法等价于 LFDOM.locator(selector).xxx(...),selector 同样接受 CSS 或 @N 引用,自动等待规则与默认 5000ms 超时相同:

方法等价于
click(selector, options?) / dblclick / hoverlocator(selector).click(options) 等
fill(selector, value, options?) / type(selector, text, options?) / press(selector, key, options?) / clear(selector, options?)对应的 Locator 输入动作
text(selector) / value(selector) / attr(selector, name)innerText() / inputValue() / getAttribute(name)
exists(selector) / visible(selector) / count(selector)立即判断是否存在(不等待)/ isVisible() / count()
waitFor(selector, options?) / waitForHidden(selector, options?)locator(selector).waitFor(options)(默认 state 'visible')/ state 固定为 'hidden'
getByRole / getByText / getByLabel / getByPlaceholder / getByTitle / getByAltText / getByTestId全页范围的语义构造器(见「定位器 Locator」)
waitForUrl(pattern, options?)轮询直到 location.href 命中(字符串子串/正则/谓词),resolve 为当前 URL
waitForLoadState(state?)默认 'domcontentloaded';'networkidle' 只是固定等待 500ms,并不检测网络空闲
waitForFunction(fn, options?)轮询直到 fn() 返回真值
mountAnchored / mountFloating与 mountAnchoredHTML / mountFloatingHTML 相同
timeoutError(message, details?)构造与库内一致的超时错误对象

示例

js
// @require dom

await LFDOM.whenReady();

// Playwright 风格的定位器:自动等待、自动重试
await LFDOM.locator("#submit-button").click();
await LFDOM.locator("input[name=q]").fill("keyword");

// 语义快照:把页面结构变成带 @N 引用的文本(看 → 操作)
const snap = await LFDOM.snapshot({ interactiveOnly: true });
LFRuntime.print(snap.text);

// 等待元素出现
const row = await LFDOM.waitForElement(".order-row", { timeout: 8000 });