# aardio 范例: web.view XPath 自动化教学范例



```aardio
import win.ui;
/*DSG{{*/
var winform = win.form(text="web.view XPath 自动化教学范例";right=850;bottom=650)
winform.add()
/*}}*/

import web.view;
var wb = web.view(winform);

var markdown = /*
在 aardio 的 web.view 中，
利用 XPath 来实现自动化控制是非常强大且灵活的。

wb.waitEle 和 wb.waitEle2 函数原生支持 XPath，
**只要参数 1 指定的字符串以 / 或 ( 符号开头，就会被识别为 XPath 表达式**。

相较于 CSS 选择器，XPath 最大的优势在于**它可以通过元素的文本内容来定位元素**，
这在很多动态生成不规则 class 或缺少 id 属性的现代网页中是必杀技。

### 🎯 XPath 常用语法速查：

1. **`//` 和 `/` 的区别**

   - `//div`：查找文档中**任意层级**的所有 `div`。
   - `/html/body/div`：从根节点**严格按层级**查找。通常我们直接用 `//` 相对查找。
   
   例如 `//div[h3[contains(., '改进模型')]]//button`：
   找一个内部包含标题为"改进模型"的 `div`，然后再找它里面的 `button`。
   这在一些无特征但结构固定的复杂列表中非常有效。

2. **`[@属性名='值']` 限定属性**

   - `//input[@type="password"]`：找密码框。
   - `//a[@href="https://www.aardio.com"]`：找指定链接。

3.  XPath 函数

	常用 XPath 函数：contains()、normalize-space()、starts-with()、substring()
	
	函数 `contains(haystack, needle)` 
	接收参数（通常是两个字符串），执行逻辑判断，并返回一个布尔值（True 或 False）。
	参数名源自“Looking for a needle in a haystack”（在草垛中寻针，大海捞针）。
	
	**部分匹配 CSS 类名示例：**
	
	`div[contains(@class,"class-name")]`
	
	HTML 元素可能会有动态绑定的 CSS 类名，一般不要用 `=` 号做完全匹配。


	**部分匹配文本示例：**
	
	- `[contains(., "包含文本")]` 模糊匹配当前节点与任意深度子节点的文本（最精妙的用法）  
	- `[contains(text(), "包含文本")]` 模糊匹配当前节点文本（最精妙的用法） 
	
	以上参数中的 `text()` 不是函数而是一个节点测试（Node Test）。
	text 后面的 `()`表示它是一个“测试”动作，以区别于普通的元素名。
	
	类似的测试器：
	
	- node()：匹配任何类型的节点。
	- comment()：匹配注释节点。
	- processing-instruction()：匹配处理指令节点。
	
 	**用参数 `.` 替代 `text()`取文本的作用**  
 	如果 HTML 是 `<button><span>登录</span></button>`，
 	使用 `text()` 可能匹配不到外层的 button，
 	而 `.` 会将该节点内部所有子节点的文本拼在一起来匹配，**适用性更广**。
   
4. **逻辑组合**

   - 用 and 表示逻辑与关系。  
   例如 data-role="属性值" 并且包含指定字符串：  
   `//button[@data-role="属性值" and contains(., "包含字符串")]`
   
   - 用 or 表示逻辑或关系。  
   例如同时兼容中英文：  
   `//button[contains(., "Login") or contains(., "登录")]`
   
5. **轴（Axes）**

	轴（Axes） 定义了相对于“当前节点”的查找方向。
	如果把 HTML 文档看作一棵“家谱树”，XPath 的步进（Step）就像是在树上移动。
	普通的 / 或 // 只能向下查找（后代），
	而“轴”允许你横向查找（兄弟）、向上查找（祖先）或按文档顺序前后查找。
	
	- following-sibling:: (同级后续轴)
	仅查找与当前节点同一个父节点下的“兄弟”节点。
	
		示例：`//div[text()='弹窗标题']/following::button[.//span[text()='取消']]`

	- following:: (后续轴)
	查找当前节点结束标签之后的所有节点（不包括其后代），即文档流中出现在它后面的任何元素。
	也就是所有在后面的“兄弟”节点以及这些“兄弟”节点的任意深度的子节点。
	
		示例：`//div[contains(., '弹窗标题')]/following-sibling::div//button[contains(., '取消')]`

5. **绕过命名空间**
	
	XPath 默认不识别有特殊 XML 命名空间的元素，
	例如用 svg 作为标签名就找不到节点，需要改用 local-name() 函数来绕过命名空间限制。
	
	示例：`//*[local-name()='svg' and @name='Refresh']]`
*/

wb.html = /**
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>XPath 自动化教学</title>
<style>
body { font-family: "Microsoft YaHei", sans-serif; padding: 5px; line-height: 1.5; }
.test-box { border: 1px solid #0078D7; padding: 15px; margin: 15px 0; border-radius: 5px; background: #f3f9ff; }
h3 { margin-top: 0; color: #0078D7; }
code { background: #e0e0e0; padding: 2px 5px; border-radius: 3px; color: #d14; }
button { padding: 5px 15px; cursor: pointer; }
</style></head>
<body>
    <p>在 aardio 中，如果 <code>wb.waitEle</code> 的选择器参数以 <code>/</code> 开头，则被识别为 XPath。XPath 可以根据节点路径、属性、乃至<b>包含的文本内容</b>来精确定位元素。</p>

    <div class="test-box">
        <h3>测试 1：通过绝对或相对路径及属性查找</h3>
        <p>XPath 示例：<code>//input[@id="demo-input"]</code></p>
        <input type="text" id="demo-input" placeholder="等待 aardio 自动填入文本..." size="50">
    </div>

    <div class="test-box">
        <h3>测试 2：通过文本内容查找（CSS选择器做不到）</h3>
        <p>XPath 示例：<code>//button[contains(., '自动点击我')]</code> 或 <code>//button[text()='精确匹配文本']</code></p>
        <button>普通的按钮</button>
        <button>自动点击我</button>
        <button disabled id="markdown">点我显示：XPath 常用语法速查</button>
    </div>

    <div class="test-box">
        <h3>测试 3：多条件组合与层级查找</h3>
        <p>XPath 示例：<code>//div[@class="actions"]//button[@data-role="submit"]</code></p>
        <div class="actions">
            <button data-role="cancel">取消</button>
            <button data-role="submit">提交数据</button>
        </div>
    </div>

    <h3 id="msg" style="color:#d14;">操作日志：...</h3>

    <script>
        // 绑定所有按钮的点击事件，用于反馈测试结果
        document.querySelectorAll('button').forEach(btn => {
            btn.onclick = function() {
                var msg = document.getElementById('msg');
                msg.innerText = "操作日志：你点击了按钮「" + this.innerText + "」";
                this.style.backgroundColor = "#c8e6c9";
            }
        });
    </script>
</body>
</html>
**/

winform.show();

/*
参数 1 首字符是 `/` 表示 XPath ，否则表示 CSS 选择器。
waitEle 范例: https://www.aardio.com/zh-cn/docs/examples/WebUI/web.view/waitEle.html

在 XPath 中，@ 符号是轴选择器（Axis Selector），用于选取属性（Attribute）。
以下示例表示：寻找一个 input 标签，它的 id 属性值等于 "demo-input" 。
*/
wb.waitEle(`//input[@id="demo-input"]`, `
	// 这里的 this 指向查找到的 HTML 节点
    this.value = 'Hello, aardio + XPath!';
`);// waitEle 在当前页面等待节点，改用 waitEle2 方法则支持跨页面等待节点

/*
以上 XPath 去掉 @ 则表示：
寻找一个 input 标签，该标签包含一个 <span> 子标签，并且子标签的文本内容是 "demo-input"。
*/
wb.waitEle(`//input[span="demo-input"]`,  //一般用反引号 `` 包围 XPath，内部可直接写双引号
	100 //省略参数 2 或指定超时值则同步等待（阻塞代码向下执行）
); 

/*
1. 完整匹配文本：`//button[text()="精确匹配文本"]`
2. 部分匹配文本：`//button[contains(text(), '包含的字符串')]`
3. 节点或子节点包含文本：`//button[contains(., '包含的字符串')]` 

	参数 `.` 会将该节点内部所有子节点的文本拼在一起来匹配。
	
4. 去掉首尾空白压缩中间空白：//button[div[normalize-space()='清洗并规范化空白后的内容']]
*/
wb.waitEle(`//button[contains(., '自动点击我')]`, `
   this.click();
`);//参数 2 可指定异步（不阻塞代码向下执行）回调的 JavaScript 或 aardio 函数


/*
XPath 用斜杠 `/` 分隔上下级表示包含关系。
最开始的单个斜杆表示根元素，中间的单斜杆分隔父子关系。
而两个斜杠 `//` 表示模糊匹配任意深度的路径（任意深度子孙节点）。
可以用 `/..` 表示寻找父节点，例如 `//button[contains(., "提交")]/..`

下面的 `div[@class="actions"]` 先找到指定 div，
再用 `//button` 寻找它内部深层的指定按钮
*/
wb.waitEle(`//div[@class="actions"]//button[@data-role="submit"]`, `
    setTimeout(() => {this.click();document.getElementById("markdown").disabled = false}, 500);
`);

wb.waitEle(`//h3[@id="msg"][contains(text(),"常用语法速查")]`
	, function(){
		//先退出当前网页回调，用 setTimeout 异步替换网页
		winform.setTimeout( 
			function(){
				import string.markdown;
				var html = string.markdown().render(markdown);
				wb.html= "<html><head><style>" + $"~\docs\style.css" + "</style></head><body>" + html;	
			}
		);
	}
);	

win.loopMessage();
```

