WebView 控件
操作 WebView 内嵌网页的 DOM 元素
概述
WebView 控件模块用于查找和操作 Android App 中 WebView 内嵌的网页元素(H5 页面)。
技术基础:
- 基于 Android AccessibilityService
- WebView 将 DOM 结构暴露给 AccessibilityService
- 通过 AccessibilityNodeInfo 访问网页元素
- 不依赖 Chrome DevTools Protocol
适用场景:
- App 内嵌的 H5 页面
- 微信小程序(WebView 部分)
- 混合应用(Hybrid App)
快速开始
local node = require("new_node")
-- 方式 1:自动定位第一个 WebView
local link = node.webview():text("注册"):get()
if link then
link:click()
end
-- 方式 2:指定 WebView 后在其内部查找
local wv = node.app():type("WebView"):get()
if wv then
local btn = wv:query():text("提交"):get()
if btn then btn:click() end
end
-- 表单输入
local username = node.webview():id("username"):get()
if username then username:input("admin") end
local password = node.webview():id("password"):get()
if password then password:input("123456") end
local submit = node.webview():text("登录"):get()
if submit then submit:click() end
工作原理
AccessibilityService 视角下的 WebView:
┌─────────────────────────────────────────┐
│ App 界面 │
├─────────────────────────────────────────┤
│ LinearLayout │
│ ├── TextView ("标题") │
│ └── WebView ←── node.app():type("WebView") 定位
│ ├── [DOM 映射] heading "欢迎" │
│ ├── [DOM 映射] editText │
│ ├── [DOM 映射] button "登录" │
│ └── [DOM 映射] link "注册" │
└─────────────────────────────────────────┘
node.webview() 实际上是:
1. 找到第一个 type="WebView" 的控件
2. 在其子树中进行查询
3. 如果找不到 WebView,后续 get()/all() 等返回 nil/空表
关键点:
- WebView 内的 DOM 元素会被映射为 AccessibilityNodeInfo
- 不是所有 HTML 属性都能访问,只有无障碍相关的
- 没有真正的 CSS/XPath 支持(AccessibilityService 不提供)
API 总览
详细文档
- Query 查询器:详见 WebView Query
- Element 控件对象:详见 WebView Element
入口方法
| 方法 | 返回值 | 说明 |
|---|---|---|
node.webview() | Query | 在第一个 WebView 内查询 |
node.webview(index) | Query | 在第 n 个 WebView 内查询 |
element:query() | Query | 在指定控件内创建查询器 |
约束方法(与 App 控件相同)
| 方法 | 说明 |
|---|---|
.id(id) | 控件 ID |
.idContains(text) | ID 包含 |
.text(text) | 文本完全匹配 |
.textContains(text) | 文本包含 |
.textMatches(pattern) | 文本 Lua 模式匹配 |
.desc(desc) | 描述匹配 |
.type(className) | 控件类型 |
.clickable(bool) | 可点击 |
.scrollable(bool) | 可滚动 |
.enabled(bool) | 可用 |
.visible(bool) | 可见 |
.index(n) | 结果索引 |
配置方法
| 方法 | 说明 |
|---|---|
.timeout(ms) | 等待超时(默认 0,最大 60000ms) |
.interval(ms) | 轮询间隔(默认 100ms) |
.limit(n) | 限制 all() 返回数量 |
终结方法
| 方法 | 返回值 | 说明 |
|---|---|---|
.get() | Element/nil | 获取第一个匹配 |
.all() | table | 获取所有匹配(空时返回 {}) |
.exists() | boolean | 是否存在 |
.count() | number | 匹配数量 |
.waitUntilGone(ms) | boolean | 等待控件消失 |
.debug() | string | 打印查询条件(调试用) |
快捷方法
| 方法 | 返回值 | 说明 |
|---|---|---|
.tryClick() | boolean | 找到则点击 |
.tryLongClick() | boolean | 找到则长按 |
.tryInput(text) | boolean | 找到则输入 |
.tryClear() | boolean | 找到则清空 |
.tryFocus() | boolean | 找到则聚焦 |
使用示例
基础查找
-- 在第一个 WebView 内查找文本
local elem = node.webview():text("注册"):get()
if elem then
elem:click()
end
-- 在第二个 WebView 内查找
local elem = node.webview(2):text("确定"):get()
-- 先定位 WebView,再在其内部查找
local wv = node.app():type("WebView"):get()
if wv then
local btn = wv:query():text("提交"):get()
if btn then btn:click() end
end
表单操作
-- 输入用户名(通过 ID 定位)
local username = node.webview():id("username"):get()
if username then
username:input("admin")
end
-- 输入密码
local password = node.webview():id("password"):get()
if password then
password:input("123456")
end
-- 点击登录按钮
local login = node.webview():text("登录"):clickable():get()
if login then
login:click()
end
带等待的查找
-- 等待页面加载完成
local success = node.webview():text("加载完成"):timeout(10000):get()
if success then
print("页面加载完成")
end
-- 等待并检查登录结果
if node.webview():text("登录成功"):timeout(5000):exists() then
print("登录成功")
elseif node.webview():text("密码错误"):exists() then
print("登录失败")
end
遍历元素
-- 获取所有链接文本
local links = node.webview():clickable():all()
if links then
for i, link in ipairs(links) do
print(i, link.text)
end
end
关系查询
local label = node.webview():text("商品名称"):get()
if label then
-- 获取父控件
local card = label:parent()
-- 在父控件内查找价格
local price = card:query():textContains("¥"):get()
if price then
print("价格:", price.text)
end
-- 获取兄弟控件
local next = label:next()
end
Element 属性
WebView 内的元素与 App 控件共享相同的 Element 接口:
| 属性 | 类型 | 说明 |
|---|---|---|
.text | string | 文本内容 |
.desc | string | 描述 |
.id | string | 控件 ID |
.type | string | 类型(className) |
.bounds | Rect | 位置大小 |
.clickable | boolean | 可点击 |
.enabled | boolean | 可用 |
最佳实践
1. 确保 WebView 已加载
-- ❌ WebView 可能还没加载完
local btn = node.webview():text("登录"):get()
-- ✅ 等待 WebView 内容出现
local btn = node.webview():text("登录"):timeout(5000):get()
2. 使用稳定的定位方式
-- ✅ ID 最稳定
node.webview():id("submit-btn")
-- ⚠️ 文本可能随语言变化
node.webview():text("Login")
3. 组合多个约束
-- ❌ 可能匹配多个
node.webview():clickable():get()
-- ✅ 添加更多约束
node.webview():text("提交"):clickable():get()
4. 检查返回值
-- ❌ 不检查可能 nil 报错
node.webview():text("按钮"):get():click()
-- ✅ 检查返回值
local btn = node.webview():text("按钮"):get()
if btn then btn:click() end
注意事项
- WebView 必须已加载 - 确保 WebView 已打开并加载完成
- 某些 App 可能禁用 - 部分 App 会禁用 WebView 的无障碍访问
- 复杂页面性能 - 大型网页控件树遍历可能较慢
- iframe 支持有限 - 跨域 iframe 内容可能无法访问
- 没有 CSS/XPath - AccessibilityService 不提供这些能力