crawlee/misc/test_axtree.js
2025-04-23 12:14:50 +08:00

134 lines
4.9 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const { chromium } = require('playwright');
(async () => {
// 启动浏览器并打开页面
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
// await page.goto('https://www.baidu.com');
// await page.goto('https://play.grafana.org/a/grafana-synthetic-monitoring-app/probes', { waitUntil: 'domcontentloaded' });
await page.goto('https://play.grafana.org/d/be9htelw63ke8b/metrics-rename-example?orgId=1&from=now-6h&to=now&timezone=utc', { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(10000);
console.log('page loaded');
// 提取完整的 AXTree设置 interestingOnly: false 可获取全部节点)
const axTree = await page.accessibility.snapshot({ interestingOnly: false });
// 全局计数器和编号到"伪选择器"映射的对象
let idCounter = 1;
const idToSelector = {};
// 在文件开头添加一个全局变量来存储父子关系
const nodeParents = new Map();
function traverse(node, depth = 0, parent = null) {
// 记录父节点关系
nodeParents.set(node, parent);
// 增加 InlineTextBox 到过滤条件中
if (((node.role === 'none' || node.role === 'generic' || node.role === 'InlineTextBox') &&
!node.name &&
!node.focusable &&
!node.focused &&
node.expanded === undefined) ||
node.role === 'InlineTextBox' // 无论如何都跳过 InlineTextBox
) {
// 直接处理子节点
if (node.children && node.children.length > 0) {
for (const child of node.children) {
traverse(child, depth, node);
}
}
return;
}
const currentId = idCounter++;
// 构建更详细的 selector
let selectorParts = [`role=${node.role}`];
if (node.name) {
selectorParts.push(`[name="${node.name}"]`);
}
// 添加其他可能的属性
if (node.selected) selectorParts.push('[selected=true]');
if (node.checked !== undefined) selectorParts.push(`[checked=${node.checked}]`);
if (node.pressed !== undefined) selectorParts.push(`[pressed=${node.pressed}]`);
// 如果有父节点,添加父节点信息
if (parent && parent.role !== 'WebArea') {
let parentSelector = `role=${parent.role}`;
if (parent.name) {
parentSelector += `[name="${parent.name}"]`;
}
selectorParts.unshift(`${parentSelector} >>`);
}
// 如果是列表项,添加位置信息
if (parent && parent.children) {
const siblingIndex = parent.children.findIndex(child => child === node);
if (siblingIndex !== -1) {
selectorParts.push(`:nth-match(${siblingIndex + 1})`);
}
}
idToSelector[currentId] = selectorParts.join(' ');
// 收集所有可能的属性
let props = [];
if (node.focusable) props.push('focusable');
if (node.focused) props.push('focused');
if (node.expanded !== undefined) props.push(`expanded=${node.expanded}`);
if (node.selected) props.push('selected');
if (node.checked !== undefined) props.push(`checked=${node.checked}`);
if (node.disabled) props.push('disabled');
if (node.required) props.push('required');
if (node.pressed !== undefined) props.push(`pressed=${node.pressed}`);
// 判断元素是否可点击
const clickableRoles = ['button', 'link', 'menuitem', 'tab', 'checkbox', 'radio', 'switch', 'option'];
const isClickable = clickableRoles.includes(node.role) ||
node.focusable ||
node.role === 'generic' && node.name && node.focusable;
if (isClickable) props.push('clickable');
const indent = ' '.repeat(depth * 4);
console.log(`${indent}[${currentId}] ${node.role} '${node.name || ''}'${props.length > 0 ? ' (' + props.join(', ') + ')' : ''}`);
if (node.children && node.children.length > 0) {
for (const child of node.children) {
traverse(child, depth + 1, node);
}
}
}
// 输出 AXTree 的整体结构
console.log('## AXTree:');
// 打印根节点信息(这里用 Root+role 来模拟输出)
let rootProps = [];
if (axTree.focusable) rootProps.push('focusable=True');
if (axTree.focused) rootProps.push('focused');
console.log(`Root${axTree.role ? axTree.role : 'WebArea'} '${axTree.name || ''}'${rootProps.length > 0 ? ', ' + rootProps.join(', ') : ''}`);
if (axTree.children && axTree.children.length > 0) {
for (const child of axTree.children) {
traverse(child, 1, axTree);
}
}
// 输出编号与伪选择器的映射
console.log('\n编号与 Selector 映射:');
console.log(idToSelector);
// 示例:后续可根据编号获取对应的 selector 进行操作
// 比如要点击编号为 25 的节点:
// const selectorForId25 = idToSelector[25];
// await page.click(selectorForId25);
//
// 注意:上面的 selector 为伪生成示例,实际操作中需要根据页面结构生成能唯一定位该元素的 selector。
await browser.close();
})();