208 lines
7.1 KiB
JavaScript
208 lines
7.1 KiB
JavaScript
import { chromium } from 'playwright';
|
||
import fs from 'fs';
|
||
import path from 'path';
|
||
import { fileURLToPath } from 'url';
|
||
|
||
// 获取当前文件的目录路径(ES模块中没有 __dirname)
|
||
const __filename = fileURLToPath(import.meta.url);
|
||
const __dirname = path.dirname(__filename);
|
||
|
||
async function getAXTreeForUrl(url) {
|
||
// 启动浏览器并打开页面
|
||
const browser = await chromium.launch({ headless: true });
|
||
const page = await browser.newPage();
|
||
await page.goto(url, { waitUntil: 'networkidle' });
|
||
|
||
// 等待页面完全加载,避免 "Loading..." 状态
|
||
await page.waitForTimeout(10000);
|
||
|
||
console.log('页面已加载: ' + url);
|
||
|
||
// 提取完整的 AXTree(设置 interestingOnly: false 可获取全部节点)
|
||
const axTree = await page.accessibility.snapshot({ interestingOnly: false });
|
||
|
||
// 全局计数器和编号到"伪选择器"映射的对象
|
||
let idCounter = 1;
|
||
const idToSelector = {};
|
||
|
||
// 在文件开头添加一个全局变量来存储父子关系
|
||
const nodeParents = new Map();
|
||
let axTreeOutput = '';
|
||
|
||
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);
|
||
const line = `${indent}[${currentId}] ${node.role} '${node.name || ''}'${props.length > 0 ? ' (' + props.join(', ') + ')' : ''}`;
|
||
axTreeOutput += line + '\n';
|
||
console.log(line);
|
||
|
||
if (node.children && node.children.length > 0) {
|
||
for (const child of node.children) {
|
||
traverse(child, depth + 1, node);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 输出 AXTree 的整体结构
|
||
axTreeOutput += '## AXTree:\n';
|
||
console.log('## AXTree:');
|
||
|
||
// 打印根节点信息(这里用 Root+role 来模拟输出)
|
||
let rootProps = [];
|
||
if (axTree.focusable) rootProps.push('focusable=True');
|
||
if (axTree.focused) rootProps.push('focused');
|
||
const rootLine = `Root${axTree.role ? axTree.role : 'WebArea'} '${axTree.name || ''}'${rootProps.length > 0 ? ', ' + rootProps.join(', ') : ''}`;
|
||
axTreeOutput += rootLine + '\n';
|
||
console.log(rootLine);
|
||
|
||
if (axTree.children && axTree.children.length > 0) {
|
||
for (const child of axTree.children) {
|
||
traverse(child, 1, axTree);
|
||
}
|
||
}
|
||
|
||
await browser.close();
|
||
|
||
return { axTreeOutput, idToSelector };
|
||
}
|
||
|
||
async function processAXTreeFiles() {
|
||
const axtreesDir = path.join(__dirname, 'axtrees');
|
||
|
||
// 确保目录存在
|
||
if (!fs.existsSync(axtreesDir)) {
|
||
fs.mkdirSync(axtreesDir, { recursive: true });
|
||
}
|
||
|
||
// 读取所有 txt 文件
|
||
const files = fs.readdirSync(axtreesDir).filter(file => file.endsWith('.txt'));
|
||
|
||
for (const file of files) {
|
||
const filePath = path.join(axtreesDir, file);
|
||
const content = fs.readFileSync(filePath, 'utf8');
|
||
|
||
// 检查是否包含 "Loading ..."
|
||
if (content.includes('Loading ...')) {
|
||
console.log(`文件 ${file} 包含 "Loading ...",需要重新获取`);
|
||
|
||
// 从文件名中提取 ID
|
||
const id = path.basename(file, '.txt');
|
||
|
||
// 查找对应的 JSON 文件
|
||
const jsonPath = path.join(__dirname, 'storage', 'request_queues', 'default', `${id}.json`);
|
||
|
||
if (fs.existsSync(jsonPath)) {
|
||
try {
|
||
const jsonContent = fs.readFileSync(jsonPath, 'utf8');
|
||
const jsonData = JSON.parse(jsonContent);
|
||
const jsonObj = JSON.parse(jsonData.json);
|
||
const url = jsonObj.url;
|
||
|
||
console.log(`为 ID ${id} 找到 URL: ${url}`);
|
||
|
||
// 获取新的 AXTree
|
||
const { axTreeOutput, idToSelector } = await getAXTreeForUrl(url);
|
||
|
||
// 检查新获取的 AXTree 是否包含 "Loading ..."
|
||
if (!axTreeOutput.includes('Loading ...')) {
|
||
// 保存 AXTree 到原文件
|
||
fs.writeFileSync(filePath, axTreeOutput);
|
||
|
||
// 保存 idToSelector 到新文件
|
||
const selectorFilePath = path.join(axtreesDir, `${id}_idToSelector.json`);
|
||
fs.writeFileSync(selectorFilePath, JSON.stringify(idToSelector, null, 2));
|
||
|
||
console.log(`已更新 ${file} 和创建 ${id}_idToSelector.json`);
|
||
} else {
|
||
console.log(`警告: 新获取的 AXTree 仍然包含 "Loading ..."`);
|
||
}
|
||
} catch (error) {
|
||
console.error(`处理 ${file} 时出错:`, error);
|
||
}
|
||
} else {
|
||
console.log(`找不到对应的 JSON 文件: ${jsonPath}`);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 主函数
|
||
(async () => {
|
||
try {
|
||
await processAXTreeFiles();
|
||
console.log('所有 AXTree 文件处理完成');
|
||
} catch (error) {
|
||
console.error('处理过程中出错:', error);
|
||
}
|
||
})();
|