在 aardio 编程时利用 web.view 打开网页,
可以用 waitEle(selectorOrXPath,javascript[,timeout]), waitEle2(selectorOrXPath,javascrip[,timeout])
等方法等待指定的 HTML 元素,并自动回调指定的 JavaScript 代码以实现自动化操作。
waitEle 在当前页面等待,waitEle2 则支持跨页面等待 请参考 web.view 自动化
自动输入示例
import win.ui;
/*DSG{{*/
var winform = win.form(text="web.view 自动化输入";right=850;bottom=650)
/*}}*/
import web.view;
var wb = web.view(winform);
wb.html = /**
<input type="text" id="demo-input">
**/
winform.show();
/*
参数 @1 可指定 CSS 选择器或 XPath,
如果首字符为 / 或 ( 符号则 识别为 XPath。
参数 @2 如果指定 JavaScipt 代码,
则在指定元素被创建以后回调指定的 JavaScipt 代码。
在被回调的 JS 中 this 绑定当前找到的元素。
*/
wb.waitEle(`//input[@id="demo-input"]`, `
this.value = 'Hello, aardio + XPath!';
`);
win.loopMessage();
但是现在有很多使用前端框架(例如 React)编写的页面直接用上面的 JS 代码 this.value = "Hello" 可能是无效的,
下面我们将分析原因并解决问题。
input.value = "xxx" 失效了?在传统的网页中,直接修改 DOM 的 value 属性即可完成输入。但现代前端框架(尤其是 React)引入了 虚拟 DOM (Virtual DOM) 和 状态管理 (State) 的概念:
<input> 和 <textarea> DOM 节点上的 value 属性的 Setter 方法。_valueTracker,用于对比前后值是否发生变化。直接修改 DOM 的 value 并不会触发 State 的更新,导致提交时获取到的依然是空值或旧值,或者一失去焦点输入框就被清空。为了让框架“感知”到我们的输入,我们必须绕过拦截,并伪造真实的用户输入事件。
以下是将您收集的方法进行整理优化,并补充完善后的几种主流解决方案:
原理:由于 React 重写了实例对象上的 value 属性,我们可以直接找到原生 DOM 原型(HTMLInputElement.prototype 或 HTMLTextAreaElement.prototype)上的 value 描述符,强行调用原生的 set 方法修改底层 DOM 值,最后触发 input 事件通知框架更新状态。
/*
* 通用输入函数(推荐使用)
* @param {HTMLElement} element - input 或 textarea 元素
* @param {String} value - 要输入的值
*/
function setNativeValue(element, value) {
// 判断是 input 还是 textarea,获取对应的原型
const prototype = Object.getPrototypeOf(element);
// 不要用 HTMLElement.prototype,因为 HTMLElement 没有 value 属性
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(prototype, 'value').set;
if (nativeInputValueSetter) {
nativeInputValueSetter.call(element, value);
// 必须派发 input 事件,且 bubbles 必须为 true,让 React 在顶层能监听到
const inputEvent = new Event('input', { bubbles: true });
element.dispatchEvent(inputEvent);
}
}
// 使用示例:
const inputEl = document.querySelector('input[type="text"]');
setNativeValue(inputEl, "测试文本");
要点:
HTMLInputElement 或 HTMLTextAreaElement 的原型,而不是 window.HTMLElement.prototype。bubbles: true 极其重要,因为 React 17+ 将事件委托到了 root 节点,不冒泡的事件 React 接收不到。aardio 示例:
import win.ui;
/*DSG{{*/
var winform = win.form(text="自动化输入")
/*}}*/
import web.view;
var wb = web.view(winform);
winform.show();
var js = /***
//添加 JS 全局函数
window.setNativeValue = function (element, value) {
const prototype = Object.getPrototypeOf(element);
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(prototype, 'value').set;
if (nativeInputValueSetter) {
nativeInputValueSetter.call(element, value);
const inputEvent = new Event('input', { bubbles: true });
element.dispatchEvent(inputEvent);
}
}
***/
wb.preloadScript(js);
wb.html = /**
<input type="text" id="demo-input">
**/
wb.waitEle(`//input[@id="demo-input"]`, `
setNativeValue(this, "测试文本");
`);
win.loopMessage();
document.execCommand(🛠 仿真度最高)原理:这是浏览器提供的富文本编辑命令,它不仅会修改 DOM 值,还会由浏览器底层自动触发所有相关的原生键盘和输入事件,仿真度极高。
function simulateTyping(element, text) {
element.focus(); // 必须先聚焦
element.select(); // 选中当前所有内容(用于覆盖旧内容)
// 执行插入文本命令
document.execCommand('insertText', false, text);
// 滚动到底部(针对 textarea)
element.scrollTop = element.scrollHeight;
}
// 使用示例:
simulateTyping(document.querySelector('input'), "hello@example.com");
要点:
document.execCommand 为已废弃 (Deprecated),但在可预见的未来,由于历史包袱,主流浏览器(包括 WebView2 控件)依然支持此 API 处理普通的文本输入。aardio 示例:
import win.ui;
/*DSG{{*/
var winform = win.form(text="自动化输入")
/*}}*/
import web.view;
var wb = web.view(winform);
winform.show();
var js = /***
//添加 JS 全局函数
window.simulateTyping = function (element, text) {
element.focus();
element.select();
document.execCommand('insertText', false, text);
element.scrollTop = element.scrollHeight;
}
***/
wb.preloadScript(js);
wb.html = /**
<input type="text" id="demo-input">
**/
wb.waitEle(`//input[@id="demo-input"]`, `
simulateTyping(this, "测试文本");
`);
win.loopMessage();
_valueTracker(💥 针对性强,但有失效风险)原理:利用 React 内部实现机制的漏洞。React 为了防止重复触发事件,会在 DOM 节点上挂载一个 _valueTracker 对象。我们可以骗过这个追踪器,让它认为旧值和新值不同,从而放行事件。
function reactHackInput(element, newValue) {
const previousValue = element.value;
element.value = newValue; // 直接修改
// 欺骗 React 的追踪器,把追踪器的值重置为修改前的值
if (element._valueTracker) {
element._valueTracker.setValue(previousValue);
}
// 触发 input 或 change 事件 (现代 React 主要监听 input)
element.dispatchEvent(new Event('input', { bubbles: true }));
element.dispatchEvent(new Event('change', { bubbles: true }));
}
要点:
_valueTracker),理论上一旦 React 升级或更改内部实现代码(如重命名变量),此方法立刻失效。原理:某些表单不仅监听 input,还会监听 keydown、keyup 甚至 focus、blur 来触发校验逻辑(例如:输入完成后按钮才亮起)。
function simulateFullInputEvents(element, value) {
// 1. 聚焦触发 focus 事件
element.focus();
element.dispatchEvent(new Event('focus', { bubbles: true }));
// 2. 核心:修改值并触发 input
const nativeSetter = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(element), 'value').set;
nativeSetter.call(element, value);
element.dispatchEvent(new Event('input', { bubbles: true }));
// 3. 模拟键盘事件骗过特定的校验逻辑
element.dispatchEvent(new KeyboardEvent('keydown', { bubbles: true, key: 'Enter', code: 'Enter' }));
element.dispatchEvent(new KeyboardEvent('keyup', { bubbles: true, key: 'Enter', code: 'Enter' }));
// 4. 失焦触发校验
element.blur();
element.dispatchEvent(new Event('blur', { bubbles: true }));
}
| 方法名称 | 仿真度 | 兼容性/可靠性 | 适用场景 | 缺点 |
|---|---|---|---|---|
| 原生原型 Setter法 (方法一) | 中 | 极高 | 95%的 React/Vue 页面表单。首选方案。 | 无法对付使用了 ContentEditable 的富文本编辑器。 |
| execCommand 法 (方法二) | 极高 | 较高 | 应对极度严格的输入校验规则、富文本编辑器。 | API 官方已废弃,未来某天可能在某个版本突然失效。 |
| _valueTracker 破解 (方法三) | 低 | 较低 | 仅限特定的旧版 React 页面。 | 强依赖框架底层源码,版本升级即作废。 |
| 完整事件流模拟 (方法四) | 高 | 高 | 针对输入后提交按钮不亮、失去焦点报红的严格表单。 | 代码繁琐,需要根据业务逻辑猜测缺了哪个事件。 |
异步渲染延迟:如果需要连续自动化输入多个框,注意前端框架重新 Render 需要时间。可以在两次输入之间调用 aardio 函数 thread.delay( milliseconds ) 或者 aardio 函数 setTimeout(func,milliseconds) 停顿 50~100ms。
使用 CDP 命令自动偎入:
使用 web.view 也可以直接调用的 CDP (Chrome DevTools Protocol) 命令模拟硬件级按键输入。 请参考范例 CDP 自动化