Files
wpywmail-client/tools/ui-diag.js
T

162 lines
7.6 KiB
JavaScript
Raw 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.
'use strict';
/**
* 针对性诊断:键盘动作(S / R / #)为什么没生效。
* 做法:无头打开页面,把 app.js 里的内部状态(state.selected / state.open)与
* 当前焦点元素、toast 文案都直接读出来 —— 比猜快得多。
*/
const { spawn } = require('node:child_process');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const URL_ = 'http://127.0.0.1:8788/';
const PORT = 9223;
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
class Cdp {
constructor(ws) { this.ws = ws; this.id = 0; this.pending = new Map(); this.errors = []; }
static async connect(url) {
const ws = new WebSocket(url);
await new Promise((res, rej) => {
ws.addEventListener('open', res, { once: true });
ws.addEventListener('error', () => rej(new Error('CDP connect failed')), { once: true });
});
const c = new Cdp(ws);
ws.addEventListener('message', (ev) => {
let m; try { m = JSON.parse(ev.data); } catch { return; }
if (m.id && c.pending.has(m.id)) {
const { resolve, reject } = c.pending.get(m.id);
c.pending.delete(m.id);
if (m.error) reject(new Error(m.error.message)); else resolve(m.result);
} else if (m.method === 'Runtime.exceptionThrown') {
const d = m.params.exceptionDetails;
c.errors.push((d.exception && d.exception.description ? d.exception.description.split('\n')[0] : d.text));
} else if (m.method === 'Runtime.consoleAPICalled' && m.params.type === 'error') {
c.errors.push('console.error: ' + m.params.args.map((a) => a.value || a.description || '').join(' '));
}
});
return c;
}
send(method, params = {}) {
const id = ++this.id;
return new Promise((resolve, reject) => {
this.pending.set(id, { resolve, reject });
this.ws.send(JSON.stringify({ id, method, params }));
setTimeout(() => { if (this.pending.has(id)) { this.pending.delete(id); reject(new Error(method + ' timeout')); } }, 20000);
});
}
async eval(expression) {
const r = await this.send('Runtime.evaluate', { expression, returnByValue: true, awaitPromise: true });
if (r.exceptionDetails) return { __err: r.exceptionDetails.text || 'eval error' };
return r.result ? r.result.value : undefined;
}
}
(async () => {
const edge = ['C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe',
'C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe'].find((p) => fs.existsSync(p));
const profile = fs.mkdtempSync(path.join(os.tmpdir(), 'wpyw-diag-'));
const child = spawn(edge, ['--headless=new', `--remote-debugging-port=${PORT}`, `--user-data-dir=${profile}`,
'--no-first-run', '--disable-extensions', '--window-size=1600,1000', URL_], { stdio: 'ignore' });
let cdp = null;
const out = [];
const log = (s) => { out.push(s); process.stdout.write(s + '\n'); };
try {
let target = null;
for (let i = 0; i < 40 && !target; i++) {
await sleep(400);
try {
const list = await (await fetch(`http://127.0.0.1:${PORT}/json/list`)).json();
target = list.find((t) => t.type === 'page' && t.webSocketDebuggerUrl);
} catch { /* 等 */ }
}
cdp = await Cdp.connect(target.webSocketDebuggerUrl);
await cdp.send('Runtime.enable');
await cdp.send('Page.navigate', { url: URL_ });
// 等界面渲染
let rows = 0;
for (let i = 0; i < 40 && rows === 0; i++) { await sleep(500); rows = await cdp.eval('document.querySelectorAll(".row").length'); }
log(`邮件行 = ${rows}`);
// state 能不能从外部读到?
const probe = await cdp.eval('typeof state');
log(`typeof state = ${probe}`);
log('');
log('── 步骤 1:点击第一行 ──────────────────────────');
await cdp.eval('document.querySelector(".row").click()');
await sleep(1500);
log(JSON.stringify(await cdp.eval(`({
selected: (typeof state !== 'undefined' ? state.selected : 'N/A'),
openUid: (typeof state !== 'undefined' && state.open ? state.open.uid : null),
activeTag: document.activeElement ? document.activeElement.tagName : null,
activeId: document.activeElement ? document.activeElement.id : null,
readerSubject: (document.querySelector('.reader__subject')||{}).textContent || '',
toast: (document.getElementById('toastText')||{}).textContent || '',
rowsNow: document.querySelectorAll('.row').length,
})`)));
log('');
log('── 步骤 2:派发 keydown "s" ────────────────────');
const uid = await cdp.eval('document.querySelector(".row")?.dataset.uid');
log(`目标 UID = ${uid}`);
await cdp.eval(`document.dispatchEvent(new KeyboardEvent('keydown', { key: 's', bubbles: true }))`);
await sleep(1800);
log(JSON.stringify(await cdp.eval(`({
flaggedInState: (() => { const m = state.messages.find(x => String(x.uid) === '${uid}'); return m ? m.flagged : 'not-found'; })(),
rowHasFlagClass: !!(document.querySelector('.row[data-uid="${uid}"]')?.classList.contains('is-flagged')),
openFlagged: (state.open ? state.open.flagged : null),
toast: (document.getElementById('toastText')||{}).textContent || '',
})`)));
log('');
log('── 步骤 3:派发 keydown "r" ────────────────────');
await cdp.eval(`document.dispatchEvent(new KeyboardEvent('keydown', { key: 'r', bubbles: true }))`);
await sleep(800);
log(JSON.stringify(await cdp.eval(`({
composeHidden: document.getElementById('compose').hidden,
composeTitle: document.getElementById('composeTitle').textContent,
openUid: (state.open ? state.open.uid : null),
})`)));
log('');
log('── 步骤 4:直接在 document 上监听,验证事件到底有没有到达 ──');
await cdp.eval(`(() => {
window.__probe = [];
document.addEventListener('keydown', (e) => window.__probe.push(e.key + '|' + (document.activeElement ? document.activeElement.tagName : '?')), true);
return true;
})()`);
await cdp.eval(`document.dispatchEvent(new KeyboardEvent('keydown', { key: 's', bubbles: true }))`);
await sleep(600);
log('捕获到的 keydown:' + JSON.stringify(await cdp.eval('window.__probe')));
// 对比:真实用户按键(CDP Input.dispatchKeyEvent 走浏览器输入管线)
log('');
log('── 步骤 5:用 CDP 真实键盘事件(Input.dispatchKeyEvent)──');
await cdp.eval('document.querySelector(".row").click()');
await sleep(1200);
await cdp.send('Input.dispatchKeyEvent', { type: 'keyDown', key: 's', code: 'KeyS', windowsVirtualKeyCode: 83, nativeVirtualKeyCode: 83, text: 's', unmodifiedText: 's' });
await cdp.send('Input.dispatchKeyEvent', { type: 'keyUp', key: 's', code: 'KeyS', windowsVirtualKeyCode: 83, nativeVirtualKeyCode: 83 });
await sleep(1800);
log(JSON.stringify(await cdp.eval(`({
rowHasFlagClass: !!(document.querySelector('.row[data-uid="${uid}"]')?.classList.contains('is-flagged')),
toast: (document.getElementById('toastText')||{}).textContent || '',
})`)));
log('');
log('运行时报错:' + JSON.stringify(cdp.errors.slice(0, 10)));
} catch (err) {
log('FATAL ' + (err && err.stack ? err.stack : err));
} finally {
if (cdp) try { cdp.ws.close(); } catch { /* 忽略 */ }
try { child.kill(); } catch { /* 忽略 */ }
await sleep(400);
try { fs.rmSync(profile, { recursive: true, force: true }); } catch { /* 忽略 */ }
fs.writeFileSync('E:\\deepseek\\artifacts\\client-ui-diag.txt', out.join('\n'), 'utf8');
}
})();