跳到主要内容
版本:3.0.2

🪝 Web 自动化模块

Web 自动化模块 - monkey

此模块能辅助脚本进行 WebKit 框架自动化测试,适用于:

限制
  • 受限于你所在地区的法律法规,你可能无法取得、使用此模块。
  • 无法在受到著作权保护的 App 中调用(你只能在自己开发的 App 中调用它们)。
  • 无法在部分受限域名下执行 JavaScript。
提示
  • 此模块支持以 OpenAPI 方式独立调用。
备注

对于本节中所有函数调用,如果没有 App 处于前台,则等待 3 秒后在第二个返回值中返回超时错误。

列出前台 App 的 Web 视图 (monkey.list_webviews)

声明

{ responder = 响应者标识符, views = 视图列表 }, 失败原因 = monkey.list_webviews()

参数及返回值

  • 失败原因 文本型
  • 响应者标识符
  • 视图列表

视图信息表

视图信息表是一个 关联表,包含以下键值对:

  • objectIdentifier
    • 文本型视图对象标识符,用于后续操作
  • class
    • 文本型,视图的 Objective-C 类名
  • targetClass
    • 文本型,视图的目标类名,通常是 WKWebView
  • scheme
    • 文本型,URL协议
  • host
    • 文本型,URL主机
  • path
    • 文本型,URL路径
  • absoluteString
    • 文本型,完整URL
  • title
    • 文本型,页面标题
  • loading
    • 布尔型,是否正在加载页面
  • isInjectable
    • 布尔型,是否可注入 JavaScript,受限域名下为 false
  • estimatedProgress
    • 数值型,加载进度,取值范围为 0.0 ~ 1.0

说明

递归遍历前台 App 的所有视图,过滤出所有 Web 视图。

限制

对于 isInjectablefalse 的 Web 视图,后续的 monkey.evalmonkey.eval_id 函数调用将会无条件失败。

限制

更新 视图对象标识符 objectIdentifier,如果需要对视图进行后续操作,必须使用新的对象标识符。

示例输出

monkey.list_webviews
{
responder = "com.apple.mobilesafari", -- 响应者 App 标识符
views = {
[1] = {
objectIdentifier = "CB259CA2-5893-47B4-938A-DD3F112BDBB0", -- 视图对象标识符
class = "_SFWebView", -- 视图类名
targetClass = "WKWebView", -- 视图目标类名
scheme = "https", -- URL协议
host = "www.w3schools.com", -- URL主机
path = "/jsref/tryit.asp", -- URL路径
absoluteString = "https://www.w3schools.com/jsref/tryit.asp?filename=tryjsref_prompt", -- 完整URL
title = "W3Schools Tryit Editor", -- 页面标题
loading = false, -- 是否正在加载页面
estimatedProgress = 1.0, -- 加载进度
isInjectable = true, -- 是否可注入 JavaScript
...
},
[2] = { ... },
...
},
}

搜索前台 App 的 Web 视图 (monkey.get_webview)

声明

视图信息表, 失败原因 = monkey.get_webview(匹配表{
objectIdentifier = 视图对象标识符,
responder = 响应者标识符,
scheme = URL协议,
host = URL主机,
path = URL路径,
absoluteString = 完整URL,
url = URL匹配正则表达式,
})

参数及返回值

  • 视图对象标识符 文本型可选
  • 响应者标识符
    • 文本型可选,响应者 App 标识符
  • URL协议 文本型可选
  • URL主机 文本型可选
  • URL路径 文本型可选
  • 完整URL 文本型可选
  • URL匹配正则表达式
    • 文本型可选,用于匹配 Web 视图 URL 的正则表达式
  • 失败原因 文本型
  • 视图信息表

若传入 视图对象标识符

经由 视图对象标识符 读取缓存中的 Web 视图信息,不会递归遍历前台 App 的所有视图。

若不传入 视图对象标识符

递归遍历前台 App 的所有视图,经由传入的参数过滤出符合条件的 Web 视图,返回第一个匹配的 Web 视图信息。

限制

此情形下会更新 视图对象标识符 objectIdentifier
如果需要对视图进行后续操作,必须使用新的对象标识符。

示例 1

monkey.get_webview
--
-- Safari 打开一个网页
app.open_url("https://www.w3schools.com/jsref/tryit.asp?filename=tryjsref_prompt")
sys.sleep(0.5)
--
-- 搜索前台 App 符合条件的 Web 视图
local webview, err = monkey.get_webview {
scheme = "https",
host = "www.w3schools.com",
path = "/jsref/tryit.asp",
}
--
-- 轮询直到 Web 视图加载完成
while webview.loading do
webview = monkey.get_webview(webview)
sys.sleep(0.5)
end
sys.toast("Web 视图加载完毕,URL: " .. webview.absoluteString)

示例 2

不传入匹配条件时,返回第一个 Web 视图。

monkey.get_webview
webview, err = monkey.get_webview {}

获取指定 Web 视图的信息 (monkey.get_webview_id)

声明

视图信息表, 失败原因 = monkey.get_webview_id(视图对象标识符)

参数及返回值

说明

经由 视图对象标识符 读取缓存中的 Web 视图信息,不会递归遍历前台 App 的所有视图。

提示

在已知 视图对象标识符 的情况下,比 monkey.list_webviewsmonkey.get_webview 函数更高效。

示例

monkey.get_webview_id
local webview_id = "CB259CA2-5893-47B4-938A-DD3F112BDBB0"
local webview, err = monkey.get_webview_id(webview_id)
if err then
nLog(err)
else
nLog(stringify(webview))
end

搜索前台 App 的 Web 视图并执行 JavaScript (monkey.eval)

声明

返回值, 错误信息 = monkey.eval(匹配表{
objectIdentifier = 视图对象标识符,
responder = 响应者标识符,
scheme = URL协议,
host = URL主机,
path = URL路径,
absoluteString = 完整URL,
url = URL匹配正则表达式,
}, JS代码)

参数及返回值

  • 视图对象标识符
  • 响应者标识符
    • 文本型可选,响应者 App 标识符
  • URL协议 文本型可选
  • URL主机 文本型可选
  • URL路径 文本型可选
  • 完整URL 文本型可选
  • URL匹配正则表达式
    • 文本型可选,用于匹配 Web 视图 URL 的正则表达式
  • JS代码
    • 文本型,JavaScript 表达式,如 1 + 1
  • 错误信息
    • 关联表,如果执行 JavaScript 代码发生异常,返回错误信息:
      • NSLocalizedDescription
        • 文本型,错误描述
      • WKJavaScriptExceptionMessage
        • 文本型,JavaScript 异常信息
      • WKJavaScriptExceptionLineNumber
        • 整数型,JavaScript 异常行号
      • WKJavaScriptExceptionColumnNumber
        • 整数型,JavaScript 异常列号
  • 返回值
    • 文本型,JavaScript 表达式返回值的 JSON 文本表示,如果执行出错,返回 nil

若传入 视图对象标识符

经由 视图对象标识符 定位缓存中的 Web 视图,并同步执行 JavaScript 代码,不会递归遍历前台 App 的所有视图。

若不传入 视图对象标识符

递归遍历前台 App 的所有视图,经由传入的参数过滤出符合条件的 Web 视图,在第一个匹配的 Web 视图中同步执行 JavaScript 代码。

限制

此情形下会更新 视图对象标识符 objectIdentifier,之前保存的标识符可能会作废。

说明

在匹配搜索条件或指定的 Web 视图中同步执行 JavaScript 代码,并将其返回值以 文本型 直接返回,或者序列化为 JSON 文本返回。
传入的 JS代码 将会被包装成匿名函数执行,即 return Function('TamperMonkey', <JS代码>)(TamperMonkey);,并在 Web 视图的 JavaScript 上下文中执行。

限制
  • 最大超时时间为 10 秒。
  • 同步执行的 JavaScript 代码会阻塞当前线程,直到执行完成或超时。

示例:执行基本表达式

执行 JavaScript 的基本类型表达式,并将其返回值以 文本型 直接返回。

monkey.eval
local webview = {
responder = "com.apple.mobilesafari",
scheme = "https",
host = "cn.bing.com",
path = "/",
}
local evalResult, evalError
--
evalResult, evalError = monkey.eval(webview, "return '2' + 3") -- return 不可省略
assert(evalResult == "23")
--
evalResult, evalError = monkey.eval(webview, "return document.title") -- return 不可省略
assert(evalResult == "Bing")

示例:缺省参数

不传入匹配条件时,将在第一个 Web 视图中执行 JavaScript 代码。

monkey.eval
local evalResult, evalError = monkey.eval({}, "return 1 + 1")

示例:执行匿名函数

执行 JavaScript 匿名函数,并将其返回值序列化为 JSON 文本返回。

monkey.eval
local evalResult, evalError
evalResult, evalError = monkey.eval({}, "return (function () { return {a:1, b:2, c:3}; })();")
if evalResult ~= nil then
local result = json.decode(evalResult) -- 将序列化的 JSON 文本解析为 Lua 表
assert(result.a == 1 and result.b == 2 and result.c == 3)
nLog(stringify(result))
else
nLog(evalError)
end

示例:访问上下文对象

读写 Web 视图的顶层 window 对象。

monkey.eval
monkey.eval({}, "window.document.getElementsByTagName('body')[0].remove(); return 0;")

示例:辅助函数库

传入的 JS代码 可使用辅助函数库 TamperMonkey,以方便在 JavaScript 上下文中执行一些复杂操作。

monkey.eval
local evalResult, evalError
evalResult, evalError = monkey.eval({}, [==[return (function () {
const $$ = TamperMonkey;
$$.highlightElement($$.querySelector('body'));
return Object.keys($$);
})();]==])
if evalResult ~= nil then
local result = json.decode(evalResult) -- 将序列化的 JSON 文本解析为 Lua 表
assert(type(result) == "table")
nLog(stringify(result))
else
nLog(evalError)
end

示例:异步传递消息

巧妙利用 setIntervalthread.dispatchTamperMonkey.logMessage,实现异步传递消息。

monkey.eval
require("thread")(function ()
local evalResult, evalError
evalResult, evalError = monkey.eval({}, [==[return setInterval(() => {
TamperMonkey.logMessage('Hello, World!');
}, 1000);]==])
if evalError ~= nil then
nLog(evalError)
end
thread.dispatch(function ()
local messages
while true do
messages = monkey.read_messages({}, true)
for _, message in ipairs(messages) do
nLog(message.body)
end
end
end)
end)

示例:发生异常

捕获 JavaScript 执行时发生的异常,并将异常的详细信息以 Lua 表的形式返回。

monkey.eval
local evalResult, evalError
evalResult, evalError = monkey.eval({}, "return ({ return [1,2,3]; })();")
if evalError ~= nil then
sys.alert(
string.format(
"Inline function [%d:%d]\n",
evalError.WKJavaScriptExceptionLineNumber,
evalError.WKJavaScriptExceptionColumnNumber
) .. evalError.WKJavaScriptExceptionMessage,
0,
evalError.NSLocalizedDescription
)
end

在指定 Web 视图中执行 JavaScript (monkey.eval_id)

声明

返回值, 错误信息 = monkey.eval_id(视图对象标识符, JS代码)

参数及返回值

  • 视图对象标识符
  • JS代码
    • 文本型,JavaScript 表达式,如 1 + 1
  • 错误信息
    • 关联表,如果执行 JavaScript 代码发生异常,返回错误信息:
      • NSLocalizedDescription
        • 文本型,错误描述
      • WKJavaScriptExceptionMessage
        • 文本型,JavaScript 异常信息
      • WKJavaScriptExceptionLineNumber
        • 整数型,JavaScript 异常行号
      • WKJavaScriptExceptionColumnNumber
        • 整数型,JavaScript 异常列号
  • 返回值
    • 文本型,JavaScript 表达式返回值的文本表示,如果执行出错,返回 nil

说明

经由 视图对象标识符 定位缓存中的 Web 视图,并同步执行 JavaScript 代码,不会递归遍历前台 App 的所有视图。
其他用法、说明和示例参见 monkey.eval

提示

在已知 视图对象标识符 的情况下,比 monkey.eval 更高效。

示例

monkey.eval_id
local webview_id = "CB259CA2-5893-47B4-938A-DD3F112BDBB0"
local js = "return document.title"
local title, err = monkey.eval_id(webview_id, js)
if err then
nLog(err)
else
nLog(title)
end

获取最前方的表单控制器信息 (monkey.get_topmost_formcontrol)

声明

表单控制器信息, 失败原因 = monkey.get_topmost_formcontrol()

参数及返回值

  • 失败原因 文本型
  • 表单控制器信息
    • 关联表,如果最前方没有表单控制器,返回 nil。支持的焦点元素类型有:
      • 单项选择器
      • 多项选择器
      • 日期时间选择器

表单控制器信息表:单项选择器

monkey.get_topmost_formcontrol
-- https://www.w3schools.com/tags/tryit.asp?filename=tryhtml_option_disabled
{
type = "select", -- 焦点元素类型
allowsMultipleSelection = false, -- 是否允许多选
items = { -- 选项列表
{
value = "Volvo", -- 选项值
index = 1, -- 选项索引
isGroup = false, -- 是否为分组
isSelected = false, -- 是否被选中
isEnabled = false, -- 是否可用
},
{
value = "Saab",
index = 2,
isGroup = false,
isSelected = true,
isEnabled = true,
},
{
value = "VW",
index = 3,
isGroup = false,
isSelected = false,
isEnabled = true,
},
{
value = "Audi",
index = 4,
isGroup = false,
isSelected = false,
isEnabled = true,
},
},
flattenedItems = { -- 选项列表(仅保留值)
"Volvo",
"Saab",
"VW",
"Audi",
},
selectedItem = { -- 选中的选项
value = "Saab",
index = 2,
isGroup = false,
isSelected = true,
isEnabled = true,
},
selectedItemValue = "Saab", -- 选中的选项值
selectedItemIndex = 2, -- 选中的选项索引
}

表单控制器信息表:多项选择器

monkey.get_topmost_formcontrol
-- https://www.w3schools.com/tags/tryit.asp?filename=tryhtml_select_multiple
{
type = "select", -- 焦点元素类型
allowsMultipleSelection = true, -- 是否允许多选
items = { -- 选项列表
{
value = "Volvo", -- 选项值
index = 1, -- 选项索引
isGroup = false, -- 是否为分组
isSelected = true, -- 是否被选中
isEnabled = true, -- 是否可用
},
{
value = "Saab",
index = 2,
isGroup = false,
isSelected = false,
isEnabled = true,
},
{
value = "Opel",
index = 3,
isGroup = false,
isSelected = true,
isEnabled = true,
},
{
value = "Audi",
index = 4,
isGroup = false,
isSelected = false,
isEnabled = true,
},
},
flattenedItems = { -- 选项列表(仅保留值)
"Volvo",
"Saab",
"Opel",
"Audi",
},
selectedItemCount = 2, -- 选中的选项数量
selectedItems = { -- 选中的选项列表
{
value = "Volvo",
index = 1,
isGroup = false,
isSelected = true,
isEnabled = true,
},
{
value = "Opel",
index = 3,
isGroup = false,
isSelected = true,
isEnabled = true,
},
},
selectedItemValues = { -- 选中的选项值列表
"Volvo",
"Opel",
},
selectedItemIndexes = { -- 选中的选项索引列表
1,
3,
},
}

表单控制器信息表:日期时间选择器

monkey.get_topmost_formcontrol
-- https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/datetime-local
{
type = "datetime", -- 焦点元素类型
timestamp = 1528849800.0, -- UNIX 时间戳
value = "2018-06-13T00:30:00+00:00", -- 值
localizedValue = "2018-06-12T19:30:00-05:00", -- 本地化后的值
}

设置最前方的表单控制器的值 (monkey.update_topmost_formcontrol)

声明

操作成败, 失败原因 = monkey.update_topmost_formcontrol([, 是否追加选择])

参数及返回值

    • 文本型整数型
      • 文本型 时,表示要设置的选项值
      • 整数型 时,表示要设置的选项索引或时间戳
  • 是否追加选择
    • 布尔型,仅用于多项选择器,默认为 false

说明

如果 是否追加选择false,则会清除已选中的选项,再选中指定的选项,并且移除当前的焦点状态;如果为 true,则会追加选中指定的选项,不移除当前的焦点状态。

示例:单项选择器

monkey.update_topmost_formcontrol
-- https://www.w3schools.com/tags/tryit.asp?filename=tryhtml_option_disabled
monkey.update_topmost_formcontrol(1) -- 选择第一项(移除焦点)
--
monkey.update_topmost_formcontrol("Audi") -- 选择值为 Audi 的项(移除焦点)

示例:多项选择器

monkey.update_topmost_formcontrol
-- https://www.w3schools.com/tags/tryit.asp?filename=tryhtml_select_multiple
monkey.update_topmost_formcontrol(1) -- 选择第一项, 并移除其他项的选中状态(移除焦点)
--
monkey.update_topmost_formcontrol(2, true) -- 增加选择第二项
--
monkey.update_topmost_formcontrol(3, true) -- 增加选择第三项
--
monkey.update_topmost_formcontrol("Audi") -- 选择值为 Audi 的项, 并移除其他项的选中状态(移除焦点)
--
monkey.update_topmost_formcontrol("Opel", true) -- 增加选择值为 Opel 的项
--
monkey.dismiss_topmost_formcontrol() -- 点击完成

示例:日期时间选择器

monkey.update_topmost_formcontrol
monkey.update_topmost_formcontrol(1545874020)    -- 以 Unix 时间戳选择日期时间
--
monkey.dismiss_topmost_formcontrol() -- 点击完成

关闭最前方的表单控制器 (monkey.dismiss_topmost_formcontrol)

声明

操作成败, 失败原因 = monkey.dismiss_topmost_formcontrol()

说明

  • 如果当前有表单控制器,则相当于点击 “完成” 按钮,关闭表单控制器。
  • 如果当前没有表单控制器,则不做任何操作。

搜索前台 App 的 Web 视图并模拟输入文本 (monkey.input)

声明

操作成败, 失败原因 = monkey.input(匹配表{
objectIdentifier = 视图对象标识符,
responder = 响应者标识符,
scheme = URL协议,
host = URL主机,
path = URL路径,
absoluteString = 完整URL,
url = URL匹配正则表达式,
}, 待键入的文本)

参数及返回值

  • 视图对象标识符
  • 响应者标识符
    • 文本型可选,响应者 App 标识符
  • URL协议 文本型可选
  • URL主机 文本型可选
  • URL路径 文本型可选
  • 完整URL 文本型可选
  • URL匹配正则表达式
    • 文本型可选,用于匹配 Web 视图 URL 的正则表达式
  • 待键入的文本 文本型
  • 失败原因 文本型
  • 操作成败 布尔型

若传入 视图对象标识符

经由 视图对象标识符 定位缓存中的 Web 视图,并模拟输入文本,不会递归遍历前台 App 的所有视图。

若不传入 视图对象标识符

递归遍历前台 App 的所有视图,经由传入的参数过滤出符合条件的 Web 视图,在第一个匹配的 Web 视图中模拟输入文本。

限制

此情形下会更新 视图对象标识符 objectIdentifier,之前保存的标识符可能会作废。

说明

在匹配搜索条件或指定的 Web 视图中模拟输入文本,注意事项如下:

  • 文本框必须先经由模拟点击,使其处于焦点状态。
  • 不会完全模拟字符键入的过程,会直接将内容输入焦点文本框。
提示

如果需要更真实的,逐个字符按键并带有随机延迟的模拟输入,参见 key.send_text

示例

monkey.input
local ok, err
ok, err = monkey.input(
{ host = "cn.bing.com" }, -- 匹配表
"Hello, Bing!" -- 待输入的内容
)
--
-- 不传入匹配条件时,将在第一个 Web 视图中模拟输入文本
ok, err = monkey.input({}, "Hello, world!")

在指定 Web 视图中模拟输入文本 (monkey.input_id)

声明

操作成败, 失败原因 = monkey.input_id(视图对象标识符, 待键入的文本)

参数及返回值

说明

经由 视图对象标识符 定位缓存中的 Web 视图,并模拟输入文本,不会递归遍历前台 App 的所有视图。
其他用法、说明和示例参见 monkey.input

提示

在已知 视图对象标识符 的情况下,比 monkey.input 更高效。

添加预设用户脚本 (monkey.add_userscript)

声明

monkey.add_userscript(匹配表{
responder = 响应者标识符,
scheme = URL协议,
host = URL主机,
path = URL路径,
absoluteString = 完整URL,
url = URL匹配正则表达式,
matches = 匹配规则表,
}, JS代码[, 是否在加载完成后执行, 是否仅在主帧框中执行])

参数及返回值

  • 响应者标识符
  • URL协议 文本型可选
  • URL主机 文本型可选
  • URL路径 文本型可选
  • 完整URL 文本型可选
  • URL匹配正则表达式
    • 文本型可选,用于匹配 Web 视图 URL 的正则表达式
  • 匹配规则表
    • 文本型顺序表可选,包含用于匹配 Web 视图 URL 的 文本型 匹配规则
    • 匹配规则 的具体格式请参考 Match Patterns
  • JS代码 文本型
    • 文本型,JavaScript 表达式,如 alert(1)
  • 是否在加载完成后执行
    • 布尔型可选,默认为 false,即在网页开始加载前执行
  • 是否仅在主帧框中执行
    • 布尔型可选,默认为 false,即在所有帧框中执行

说明

预设用户脚本是指在 Web 视图加载时,无需脚本实时干预,自动注入到网页中的 JavaScript 代码。

限制
  • 如果 匹配表 中的任意一项不为空,则只有当 Web 视图的 URL 匹配该项时,才会注入 JS代码
  • 在 App 启动后添加的预设用户脚本,重启 App 后才能生效。

示例 1

在所有网页的所有帧框中注入 JS 代码。

monkey.add_userscript
monkey.clear_userscripts()  -- 移除其他预设用户脚本
monkey.add_userscript({}, "alert(window.location.href);")

示例 2

仅在 Safari 打开的 www.w3schools.com 的主帧框中注入 JS 代码。

monkey.add_userscript
monkey.clear_userscripts()  -- 移除其他预设用户脚本
monkey.add_userscript({ responder = "com.apple.mobilesafari", host = "www.w3schools.com" }, "alert(window.location.href);", false, true)

示例 3

仅在 URL 满足正则表达式的主帧框中注入 JS 代码,网页加载完毕后执行。

monkey.add_userscript
monkey.clear_userscripts()  -- 移除其他预设用户脚本
monkey.add_userscript({ url = "^https?:\\/\\/([a-z]+\\.)?bing\\.com\\/" }, "alert(window.location.href);", true, true)

移除所有预设用户脚本 (monkey.clear_userscripts)

声明

monkey.clear_userscripts()

说明

不区分 App 标识符,立即移除所有预设用户脚本。

限制

此移除操作对于运行中的 App 不生效,重启 App 后才能生效。

列出所有预设用户脚本 (monkey.list_userscripts)

声明

预设用户脚本列表 = monkey.list_userscripts()

参数及返回值

  • 预设用户脚本列表
    • 顺序表,其中每个元素为一个描述预设用户脚本的 关联表,结构如下:

预设用户脚本关联表

  • matchingRequest 文本型关联表,匹配表
  • evalContent 文本型,JS代码
  • injectionTime 文本型,注入时机
    • document-start 表示文档加载前
    • document-end 表示文档加载后
  • forMainFrameOnly 布尔型,是否仅在主帧框中执行
备注

以上 关联表monkey.add_userscript 传入的参数结构相同。

示例输出

monkey.list_userscripts
{
[1] = {
matchingRequest = { -- 匹配表
host = "www.w3schools.com",
responder = "com.apple.mobilesafari",
},
evalContent = "alert(window.location.href);", -- JS代码
injectionTime = "document-start", -- 注入时机
-- document-start 表示文档加载前
-- document-end 表示文档加载后
forMainFrameOnly = true, -- 是否仅在主帧框中执行
},
[2] = { ... },
...
}

读取 JavaScript 消息 (monkey.read_messages)

声明

消息列表, 失败原因 = monkey.read_messages(匹配表{
responder = 响应者标识符,
scheme = URL协议,
host = URL主机,
path = URL路径,
absoluteString = 完整URL,
url = URL匹配正则表达式,
}[, 是否清除消息])

参数及返回值

  • 响应者标识符
  • URL协议 文本型可选
  • URL主机 文本型可选
  • URL路径 文本型可选
  • 完整URL 文本型可选
  • URL匹配正则表达式
    • 文本型可选,用于匹配 Web 视图 URL 的正则表达式
  • 是否清除消息
    • 布尔型可选,默认为 false,即读取后不清除消息队列
  • 失败原因 文本型
  • 消息列表
    • 顺序表,其中每个元素为一个描述 JavaScript 消息的 关联表,结构如下:

JavaScript 消息

  • scheme 文本型,URL协议
  • host 文本型,URL主机
  • path 文本型,URL路径
  • absoluteString 文本型,完整URL
  • body 文本型,消息内容
  • frameInfo 关联表
    • requestURL 文本型,帧框URL
    • isMainFrame 布尔型,是否为主帧框

示例输出

monkey.read_messages
{
[1] = {
scheme = "https", -- URL协议
host = "www.bing.com", -- URL主机
path = "/", -- URL路径
absoluteString = "https://www.bing.com/?mkt=zh-CN",
name = "$_TM_WKNativeLog",
body = "wtf", -- 消息内容
frameInfo = { -- 帧框信息
securityOrigin = {
port = 0,
host = "www.bing.com",
protocol = "https",
},
requestURL = "https://www.bing.com/?mkt=zh-CN",
isMainFrame = true,
},
},
[2] = { ... },
...
}

说明

此函数用于获取前台 App 内建的 JavaScript 消息队列中符合条件的消息。

提示

经由 monkey.evalmonkey.add_userscript 函数执行或添加的 JavaScript 代码,可使用辅助函数 TamperMonkey.logMessage 异步提交消息到 App 内建的 JavaScript 消息队列。

示例 1

monkey.read_messages
--
-- 提交一串消息
local webview = {
responder = "com.apple.mobilesafari",
scheme = "https",
host = "www.bing.com",
path = "/",
}
local evalResult, evalError
for i = 1, 7 do
evalResult, evalError = monkey.eval(webview, "TamperMonkey.logMessage('WTF? This is message #" .. tostring(i) .. ".'); return 1;")
end
--
-- 读取消息队列
local messages, err = monkey.read_messages({
responder = "com.apple.mobilesafari",
host = "www.bing.com",
}, true) -- 读取后清除消息队列
if err then
nLog(err)
else
for _, message in ipairs(messages) do
nLog(message.body)
end
end

示例 2

monkey.read_messages
monkey.clear_userscripts()             -- 移除其他用户脚本
monkey.add_userscript(
{ host = "www.bing.com" }, -- 精确匹配
[==[
var timer = window.setInterval(function () {
if (document.getElementById('sb_form')) {
window.clearInterval(timer); // 记得停止定时器
TamperMonkey.logMessage({
type: 'loaded',
id: 'sb_form',
}); // 提交 JavaScript 消息到队列
}
}, 1000); // 设置定时器,每 1 秒检查一次
]==], -- 需要执行的脚本内容,检查 sb_form 元素是否出现在页面上
true, -- 在加载完成后立即执行
true -- 仅在主帧框中执行
) -- 添加检查 sb_form 是否存在的用户脚本
--
sys.msleep(1000) -- 等待 1 秒
app.open_url("https://www.bing.com/") -- 打开网页
sys.msleep(1000) -- 等待 1 秒
--
while true do -- 循环检查,记得自己处理检查次数上限
local msg = monkey.read_messages {
host = "www.bing.com",
} -- 读取用户脚本消息(这个不是注入,消耗比注入小很多)
if msg and #msg > 0 then
msg = msg[#msg] -- 读取最后一条消息
end
if msg.body ~= nil then -- 如果消息不为空
if msg.body.type == "loaded" and msg.body.id == "sb_form" then
break -- 如果消息类型为 loaded,并且 id 为 sb_form 则跳出循环
end
end
sys.msleep(1000) -- 等待 1 秒
end
sys.alert("sb_form 已经出现在页面上,可以继续执行后续操作了")

示例输出

monkey.read_messages
WTF? This is message #1.
WTF? This is message #2.
WTF? This is message #3.
WTF? This is message #4.
WTF? This is message #5.
WTF? This is message #6.
WTF? This is message #7.

清空 JavaScript 消息 (monkey.clear_messages)

声明

操作成败, 失败原因 = monkey.clear_messages()

参数及返回值

  • 失败原因 文本型
  • 操作成败 布尔型

说明

此函数用于清空前台 App 内建的 JavaScript 消息队列,不会对其他后台运行的 App 产生影响。

辅助函数库 TamperMonkey

此函数库可在 monkey.evalmonkey.add_userscript 函数传入的 JavaScript 代码中使用。

提交 JavaScript 消息到队列 (TamperMonkey.logMessage)

声明

TamperMonkey.logMessage()

参数及返回值

  • 任意类型

说明

提交消息 到 App 内建的 JavaScript 消息队列,Lua 脚本可经由 monkey.read_messages 函数中读取此消息。

限制

JavaScript 消息是易失的,一旦 App 终止,消息将会丢失。

无限制 HTTP 请求 (TamperMonkey.xmlHttpRequest)

声明

return TamperMonkey.xmlHttpRequest({
method: "POST", // 请求方法
url: "https://httpbin.org/post", // 请求 URL
headers: { // 请求头部
"User-Agent": "TamperMonkey",
},
data: "Hello, World!", // 请求主体
onload: function(response) { // 请求成功回调
console.debug(response.responseHeaders); // 响应头部
console.debug(response.responseText); // 响应文本
console.debug(response.status); // HTTP状态码
alert(response.statusText); // 响应状态文本
},
onerror: function(error) { // 请求失败回调
console.debug(error); // 错误信息
},
});

说明

此函数用于发送无跨域限制的 HTTP 请求,但不支持 XMLHttpRequest 对象的所有功能。

限制

此函数仅支持 iOS 14 及以上版本。

滚动到 DOM 元素 (TamperMonkey.scrollToElement)

声明

TamperMonkey.scrollToElement(DOM元素)

参数及返回值

  • DOM元素 DOM

说明

将可视区域滚动到 DOM 元素可见的位置。

示例

monkey.eval({}, "return TamperMonkey.scrollToElement(document.getElementById('footer'));")

高亮显示 DOM 元素 (TamperMonkey.highlightElement)

声明

TamperMonkey.highlightElement(DOM元素)

参数及返回值

  • DOM元素 DOM

说明

以随机颜色高亮显示 DOM 元素。

示例

monkey.eval({}, "return TamperMonkey.highlightElement(document.getElementById('development'));")

获取 DOM 元素的 CSS 选择器 (TamperMonkey.getElementSelector)

声明

选择器文本 = TamperMonkey.getElementSelector(DOM元素)

参数及返回值

  • DOM元素 DOM
  • 选择器文本 文本型

示例

monkey.eval({}, "return TamperMonkey.getElementSelector(document.getElementById('development'));")

用 CSS 选择器查询 DOM 元素 (TamperMonkey.querySelector)

声明

DOM元素 = TamperMonkey.querySelector(选择器文本)

参数及返回值

  • 选择器文本 文本型
  • DOM元素 DOM

示例

monkey.eval({}, "return TamperMonkey.querySelector('body > section#development').tagName;")

由可视区域坐标取 DOM 元素 (TamperMonkey.elementFromPoint)

声明

DOM元素 = TamperMonkey.elementFromPoint(坐标X, 坐标Y)

参数及返回值

  • 坐标X, 坐标Y 数值型
  • DOM元素 DOM

说明

传入的 坐标X坐标Y 是相对于 Web 视图可视区域的坐标,而不是相对于整个屏幕的坐标。

示例

monkey.eval({}, "return TamperMonkey.elementFromPoint(100, 100).tagName;")

由 DOM 元素取可视区域坐标 (TamperMonkey.getElementBoundingClientRect)

声明

[,,,] = TamperMonkey.getElementBoundingClientRect(DOM元素)

参数及返回值

  • DOM元素 DOM
  • 左, 上, 右, 下 数值型

说明

返回的 是相对于 Web 视图可视区域的坐标,而不是相对于整个屏幕的坐标。

示例

monkey.eval({}, "return TamperMonkey.getElementBoundingClientRect(document.getElementById('development'));")

封装示例

你可以进一步了解前端 JavaScript 的相关知识,编写功能更丰富、更强大的辅助函数库。本节演示了如何灵活运用现有的 TamperMonkey 辅助函数库和 monkey 模块来实现 Web 自动化相关的其他重要功能。

monkey.eval
--
-- 拷贝到脚本中使用
function monkey.value_at(webview, method, x, y)
local webview, err = monkey.get_webview(webview)
if not webview then return nil, err end
local factor = screen.scale_factor()
local locationInViewX = x / factor - webview.viewport[1]
local locationInViewY = y / factor - webview.viewport[2]
local js = string.format([==[
return ((x, y) => {
let element = TamperMonkey.elementFromPoint(x, y);
if (!element) return null;
return TamperMonkey.%s(element);
})(%d, %d);
]==], method, math.floor(locationInViewX), math.floor(locationInViewY))
local val, err = monkey.eval(webview, js)
if not val or val == "<null>" then return nil, err end
return val, nil
end
--
-- 拷贝到脚本中使用
function monkey.perform_selector(webview, method, selector)
local webview, err = monkey.get_webview(webview)
if not webview then return nil, err end
local js = string.format([==[
return ((selector) => {
let element = TamperMonkey.querySelector(selector);
if (!element) return null;
return TamperMonkey.%s(element);
})('%s');
]==], method, selector)
local val, err = monkey.eval(webview, js)
if not val or val == "<null>" then return nil, err end
return val, nil
end

高亮显示屏幕坐标下 DOM 元素

monkey.highlight_at
function monkey.highlight_at(x, y)
return monkey.value_at({}, "highlightElement", x, y)
end
monkey.highlight_at(685, 202)

获取屏幕坐标下 DOM 元素的 CSS 选择器

monkey.selector_at
function monkey.selector_at(x, y)
return monkey.value_at({}, "getElementSelector", x, y)
end
monkey.selector_at(685, 202)

滚动 DOM 元素到可视区域

monkey.scroll_to
function monkey.scroll_to(selector)
return monkey.perform_selector({}, "scrollToElement", selector)
end
monkey.scroll_to("#nav-footer")

由 DOM 元素取可视区域坐标

monkey.bounding_rect
function monkey.bounding_rect(selector)
local val, err = monkey.perform_selector({}, "getElementBoundingClientRect", selector)
if not val then return -1, -1, -1, -1 end
val = json.decode(val)
return math.floor(val[1]), math.floor(val[2]), math.floor(val[3]), math.floor(val[4])
end
monkey.bounding_rect("#nav-footer")