fix: 尝试修复元素无法点击的问题

This commit is contained in:
foxhui
2026-01-28 04:45:15 +08:00
Unverified
parent 27d9dfa8cd
commit edd9336bb2
2 changed files with 40 additions and 34 deletions
+14 -21
View File
@@ -86,23 +86,20 @@ async function generate(context, prompt, imgPaths, modelId, meta = {}) {
await page.keyboard.press('Enter');
await sleep(300, 500);
// 获取所有 menuitemradio 选项
const menuItems = await page.getByRole('menuitemradio').all();
// 获取所有 menuitemradio 选项的文本
const menuItemsLocator = page.getByRole('menuitemradio');
const menuItemsCount = await menuItemsLocator.count();
if (menuItems.length === 0) {
if (menuItemsCount === 0) {
logger.warn('适配器', '未找到模型选项,使用默认模型', meta);
} else {
// 获取所有选项的文本(去除前后空白)
const itemTexts = [];
for (const item of menuItems) {
const text = await item.textContent();
itemTexts.push((text || '').trim());
}
const itemTexts = await menuItemsLocator.allTextContents();
logger.debug('适配器', `可用模型选项: [${itemTexts.join('], [')}]`, meta);
logger.debug('适配器', `可用模型选项: [${itemTexts.map(t => t.trim()).join('], [')}]`, meta);
// 判断是否有 Pro 选项
const hasPro = itemTexts.some(text => text.startsWith('Pro'));
const hasPro = itemTexts.some(text => text.trim().startsWith('Pro'));
// 确定要选择的目标选项文本前缀
let targetPrefix = null;
@@ -127,18 +124,14 @@ async function generate(context, prompt, imgPaths, modelId, meta = {}) {
logger.debug('适配器', `目标模型前缀: "${targetPrefix}"`, meta);
// 查找并点击对应的选项
let found = false;
for (let i = 0; i < menuItems.length; i++) {
if (itemTexts[i].startsWith(targetPrefix)) {
await safeClick(page, menuItems[i], { bias: 'button' });
logger.info('适配器', `已选择模型: "${itemTexts[i]}"`, meta);
found = true;
break;
}
}
// 使用 locator 直接定位目标选项(避免缓存元素引用导致 detached 错误)
const targetItem = menuItemsLocator.filter({ hasText: new RegExp(`^\\s*${targetPrefix}`) }).first();
if (!found) {
if (await targetItem.count() > 0) {
const selectedText = (await targetItem.textContent() || '').trim();
await safeClick(page, targetItem, { bias: 'button' });
logger.info('适配器', `已选择模型: "${selectedText}"`, meta);
} else {
logger.warn('适配器', `未找到匹配的模型选项 (${targetPrefix}),使用默认模型`, meta);
// 按 Escape 关闭菜单
await page.keyboard.press('Escape');
+26 -13
View File
@@ -241,37 +241,50 @@ export async function safeClick(page, target, options = {}) {
? baseTimeout + Math.ceil(50000 / cursorSpeed)
: baseTimeout;
const doClick = async () => {
let el;
// 判断输入类型
logger.debug('浏览器', `[safeClick] 开始查找: ${selector}`);
// 元素定位函数(可重复调用以获取新鲜的 ElementHandle
const resolveElement = async () => {
if (typeof target === 'string') {
// CSS selector
el = await page.$(target);
const el = await page.$(target);
if (!el) throw new Error(`未找到: ${target}`);
return el;
} else if (typeof target.elementHandle === 'function') {
// Locator (来自 page.getByRole, page.getByText 等)
el = await target.elementHandle();
const el = await target.elementHandle();
if (!el) throw new Error(`Locator 未匹配到元素`);
return el;
} else {
// ElementHandle
el = target;
if (!el || !el.asElement()) throw new Error(`Element handle invalid`);
if (!target || !target.asElement()) throw new Error(`Element handle invalid`);
return target;
}
};
const doClick = async () => {
// 1. 首次获取元素(用于滚动和等待稳定)
logger.debug('浏览器', `[safeClick] 开始查找: ${selector}`);
let el = await resolveElement();
logger.debug('浏览器', `[safeClick] 已找到元素`);
// 确保元素在可视区域内
// 2. 确保元素在可视区域内
logger.debug('浏览器', `[safeClick] 滚动到可视区域...`);
await el.scrollIntoViewIfNeeded().catch(() => { });
// 如果开启了布局稳定等待,等待元素位置稳定
// 3. 如果开启了布局稳定等待,等待元素位置稳定
if (waitStable) {
logger.debug('浏览器', `[safeClick] 等待元素稳定...`);
await waitForElementStable(el);
logger.debug('浏览器', `[safeClick] 元素已稳定`);
// 4. 重新获取元素引用(防止等待期间 DOM 变化导致 detached 错误)
// 仅对 Locator 类型重新获取,ElementHandle 无法刷新
if (typeof target.elementHandle === 'function') {
logger.debug('浏览器', `[safeClick] 重新获取元素引用...`);
el = await resolveElement();
}
}
// 使用自维护 ghost-cursor 拟人鼠标轨迹 (仅当 humanizeCursor=true)
// 5. 使用自维护 ghost-cursor 拟人鼠标轨迹 (仅当 humanizeCursor=true)
if (useGhostCursor) {
const box = await el.boundingBox();
logger.debug('浏览器', `[safeClick] boundingBox: ${JSON.stringify(box)}`);
@@ -289,7 +302,7 @@ export async function safeClick(page, target, options = {}) {
return;
}
// 使用原生点击 (humanizeCursor=false 或 "camou")
// 6. 使用原生点击 (humanizeCursor=false 或 "camou")
const mode = page?._humanizeCursorMode;
logger.debug('浏览器', `[safeClick] humanizeCursor=${mode} 使用原生点击`);
// force: true 跳过可操作性检查(遮挡检测等),避免在复杂页面卡住