
# web.view 自动化输入指南

在 aardio 编程时利用 web.view 打开网页，
可以用 `waitEle(selectorOrXPath,javascript[,timeout])`, `waitEle2(selectorOrXPath,javascrip[,timeout])` 
等方法等待指定的 HTML 元素，并自动回调指定的 JavaScript 代码以实现自动化操作。

> waitEle 在当前页面等待，waitEle2 则支持跨页面等待 请参考 [web.view 自动化](automation.html)

**自动输入示例**

```aardio 
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"` 可能是无效的，
下面我们将分析原因并解决问题。

## 1. 背景与痛点：为什么 `input.value = "xxx"` 失效了？

在传统的网页中，直接修改 DOM 的 `value` 属性即可完成输入。但现代前端框架（尤其是 React）引入了 **虚拟 DOM (Virtual DOM)** 和 **状态管理 (State)** 的概念：
* **拦截原生 Setter**：React 重写了 `<input>` 和 `<textarea>` DOM 节点上的 `value` 属性的 Setter 方法。
* **单向数据流**：页面的显示值由组件的内部状态（State）决定，而非 DOM 节点本身的值。
* **事件委托与追踪**：React 内部维护了一个 `_valueTracker`，用于对比前后值是否发生变化。直接修改 DOM 的 `value` 并不会触发 State 的更新，导致提交时获取到的依然是空值或旧值，或者一失去焦点输入框就被清空。

为了让框架“感知”到我们的输入，我们必须**绕过拦截，并伪造真实的用户输入事件**。


## 2. 核心解决方案分类与代码实现

以下是将您收集的方法进行整理优化，并补充完善后的几种主流解决方案：

### 方法一：调用原型链上的原生 Setter（🌟 最推荐，适用性最广）
**原理**：由于 React 重写了实例对象上的 `value` 属性，我们可以直接找到原生 DOM 原型（`HTMLInputElement.prototype` 或 `HTMLTextAreaElement.prototype`）上的 `value` 描述符，强行调用原生的 `set` 方法修改底层 DOM 值，最后触发 `input` 事件通知框架更新状态。

```javascript
/*
 * 通用输入函数（推荐使用）
 * @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 示例**：

```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 值，还会由浏览器底层自动触发所有相关的原生键盘和输入事件，仿真度极高。

```javascript
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");
```

**要点**：
* **极其适合富文本编辑器**（如 Draft.js, Quill 等），在这些组件上“方法一”通常无效。
* **注意事项**：MDN 标记 `document.execCommand` 为**已废弃 (Deprecated)**，但在可预见的未来，由于历史包袱，主流浏览器（包括 WebView2 控件）依然支持此 API 处理普通的文本输入。

**aardio 示例**：

```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();
```

### 方法三：破解 React 的 `_valueTracker`（💥 针对性强，但有失效风险）

**原理**：利用 React 内部实现机制的漏洞。React 为了防止重复触发事件，会在 DOM 节点上挂载一个 `_valueTracker` 对象。我们可以骗过这个追踪器，让它认为旧值和新值不同，从而放行事件。

```javascript
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 }));
}
```

**要点**：

* 这是非常经典的 React 15/16 破解法。
* **注意事项**：不推荐作为首选。由于依赖 React 的内部私有 API (`_valueTracker`)，理论上一旦 React 升级或更改内部实现代码（如重命名变量），此方法立刻失效。

### 方法四：完整模拟键盘事件流

**原理**：某些表单不仅监听 `input`，还会监听 `keydown`、`keyup` 甚至 `focus`、`blur` 来触发校验逻辑（例如：输入完成后按钮才亮起）。

```javascript
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 }));
}
```

---

## 3. 总结与选型建议

| 方法名称 | 仿真度 | 兼容性/可靠性 | 适用场景 | 缺点 |
| :--- | :---: | :---: | :--- | :--- |
| **原生原型 Setter法** (方法一) | 中 | **极高** | 95%的 React/Vue 页面表单。**首选方案**。 | 无法对付使用了 ContentEditable 的富文本编辑器。 |
| **execCommand 法** (方法二) | **极高** | 较高 | 应对极度严格的输入校验规则、富文本编辑器。 | API 官方已废弃，未来某天可能在某个版本突然失效。 |
| **_valueTracker 破解** (方法三) | 低 | 较低 | 仅限特定的旧版 React 页面。 | 强依赖框架底层源码，版本升级即作废。 |
| **完整事件流模拟** (方法四) | 高 | 高 | 针对输入后提交按钮不亮、失去焦点报红的严格表单。 | 代码繁琐，需要根据业务逻辑猜测缺了哪个事件。 |

## 4. 其他建议

1. **异步渲染延迟**：如果需要连续自动化输入多个框，注意前端框架重新 Render 需要时间。可以在两次输入之间调用 aardio 函数 `thread.delay( milliseconds )` 或者 aardio 函数 `setTimeout(func,milliseconds)` 停顿 50~100ms。

2. **使用 CDP 命令自动偎入**：

    使用 web.view 也可以直接调用的 CDP (Chrome DevTools Protocol) 命令模拟硬件级按键输入。
    请参考范例 [CDP 自动化](https://www.aardio.com/zh-cn/docs/examples/WebUI/web.view/DevTools/cdp.html)