Deploy to Production / pre-check (push) Successful in 58s
Deploy to Production / build (push) Successful in 4m3s
Deploy to Production / deploy-edgeone (push) Successful in 3m48s
Deploy to Production / finalize (push) Successful in 26s
Deploy to Production / notify-failure (push) Skipped
144 lines
6.5 KiB
JavaScript
144 lines
6.5 KiB
JavaScript
// 打开评论页,抓「加载失败的请求」+ 控制台错误 + Artalk 错误层文案
|
||
import { spawn } from 'node:child_process';
|
||
import { mkdtempSync, writeFileSync } from 'node:fs';
|
||
import { tmpdir } from 'node:os';
|
||
import { join } from 'node:path';
|
||
|
||
const CHROME = 'C:/Users/QunLIn/.agent-browser/browsers/chrome-154.0.8037.92/chrome.exe';
|
||
const PORT = 9391;
|
||
const URL_ = process.argv[2] || 'http://127.0.0.1:1313/comment.html';
|
||
const OUT = process.argv[3] || '';
|
||
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
||
const userDir = mkdtempSync(join(tmpdir(), 'cdp-cmterr-'));
|
||
|
||
const chrome = spawn(CHROME, [
|
||
'--headless=new', `--remote-debugging-port=${PORT}`, `--user-data-dir=${userDir}`,
|
||
'--no-first-run', '--disable-gpu', '--window-size=1280,900', 'about:blank',
|
||
], { stdio: 'ignore' });
|
||
|
||
try {
|
||
for (let i = 0; i < 40; i++) {
|
||
try { const r = await fetch(`http://127.0.0.1:${PORT}/json/version`); if (r.ok) break; } catch { /* */ }
|
||
await sleep(300);
|
||
}
|
||
const tab = await (await fetch(`http://127.0.0.1:${PORT}/json/new?${encodeURIComponent(URL_)}`, { method: 'PUT' })).json();
|
||
const ws = new WebSocket(tab.webSocketDebuggerUrl);
|
||
let id = 0; const pending = new Map(); const net = []; const consoleMsgs = [];
|
||
ws.addEventListener('message', (ev) => {
|
||
const m = JSON.parse(ev.data);
|
||
if (m.id && pending.has(m.id)) { pending.get(m.id)(m); pending.delete(m.id); return; }
|
||
if (m.method === 'Network.responseReceived') {
|
||
const r = m.params.response;
|
||
if (r.status >= 400 || r.url.includes('/api/')) {
|
||
net.push(`HTTP ${r.status} ${r.url.slice(0, 120)}`);
|
||
}
|
||
}
|
||
if (m.method === 'Network.loadingFailed') {
|
||
net.push(`FAIL ${m.params.errorText} ${(m.params.blockedReason || '')} type=${m.params.type}`);
|
||
}
|
||
if (m.method === 'Runtime.consoleAPICalled') {
|
||
consoleMsgs.push(`[${m.params.type}] ` + (m.params.args || []).map((a) => a.value ?? a.description ?? a.type).join(' '));
|
||
}
|
||
if (m.method === 'Runtime.exceptionThrown') {
|
||
consoleMsgs.push('[exception] ' + (m.params.exceptionDetails?.exception?.description || m.params.exceptionDetails?.text || ''));
|
||
}
|
||
});
|
||
await new Promise((res) => ws.addEventListener('open', res));
|
||
const send = (method, params = {}) => new Promise((res) => { const mid = ++id; pending.set(mid, res); ws.send(JSON.stringify({ id: mid, method, params })); });
|
||
const ev = async (expr) => {
|
||
const r = await send('Runtime.evaluate', { expression: expr, returnByValue: true, awaitPromise: true });
|
||
return r.result?.result?.value ?? r.result?.value ?? null;
|
||
};
|
||
|
||
await send('Runtime.enable');
|
||
await send('Network.enable');
|
||
await send('Page.enable');
|
||
await send('Page.navigate', { url: URL_ });
|
||
await sleep(9000);
|
||
|
||
console.log('=== 网络(>=400 或 /api/) ===');
|
||
console.log(net.length ? [...new Set(net)].slice(0, 20).join('\n') : '(无)');
|
||
console.log('\n=== 控制台 ===');
|
||
console.log(consoleMsgs.length ? consoleMsgs.slice(0, 12).join('\n') : '(无)');
|
||
|
||
console.log('\n=== 卡片计算样式(代码级验证) ===');
|
||
console.log(await ev(`(() => {
|
||
const c = document.querySelector('.atk-err-card');
|
||
if (!c) return 'NO CARD';
|
||
const cs = getComputedStyle(c);
|
||
const ic = c.querySelector('.atk-err-ic');
|
||
const t = c.querySelector('.atk-err-t');
|
||
const d = c.querySelector('.atk-err-d');
|
||
const r = c.querySelector('.atk-err-retry');
|
||
const rect = c.getBoundingClientRect();
|
||
const center = Math.abs((rect.x + rect.width / 2) - window.innerWidth / 2);
|
||
return JSON.stringify({
|
||
textAlign: cs.textAlign,
|
||
padding: cs.padding,
|
||
border: cs.borderTopWidth + ' ' + cs.borderTopStyle,
|
||
background: cs.backgroundColor,
|
||
hasDetails: !!c.querySelector('details'),
|
||
hasBodyWrap: !!c.querySelector('.atk-err-body'),
|
||
icon: ic ? getComputedStyle(ic).display + '/' + getComputedStyle(ic).fontSize : null,
|
||
titleAlign: t ? getComputedStyle(t).textAlign : null,
|
||
descMaxW: d ? getComputedStyle(d).maxWidth : null,
|
||
retryRadius: r ? getComputedStyle(r).borderRadius : null,
|
||
cardCentered: center < 8 ? 'yes(off=' + Math.round(center) + 'px)' : 'no(off=' + Math.round(center) + 'px)'
|
||
}, null, 1);
|
||
})()`));
|
||
|
||
console.log('\n=== Artalk 错误层 ===');
|
||
console.log(await ev(`(() => {
|
||
const el = document.querySelector('.atk-error-layer');
|
||
if (!el) return '(没有错误层)';
|
||
const h = el.getBoundingClientRect();
|
||
return JSON.stringify({ text: el.innerText.replace(/\\n/g, ' | '), h: Math.round(h.height), html: el.innerHTML.slice(0, 500) }, null, 1);
|
||
})()`));
|
||
|
||
if (OUT) {
|
||
const s = await send('Page.captureScreenshot', { format: 'png' });
|
||
writeFileSync(OUT, Buffer.from(s.result.data, 'base64'));
|
||
|
||
// 错误卡片特写(放大 2 倍,用文档坐标 + captureBeyondViewport 才准)
|
||
const rectRaw = await ev(`(() => {
|
||
const el = document.querySelector('.atk-err-card');
|
||
if (!el) return 'null';
|
||
const r = el.getBoundingClientRect();
|
||
return JSON.stringify({
|
||
x: Math.max(0, r.x + window.scrollX - 10),
|
||
y: Math.max(0, r.y + window.scrollY - 10),
|
||
width: r.width + 20,
|
||
height: r.height + 20
|
||
});
|
||
})()`);
|
||
if (rectRaw && rectRaw !== 'null') {
|
||
const clip = JSON.parse(rectRaw);
|
||
const s2 = await send('Page.captureScreenshot', {
|
||
format: 'png', captureBeyondViewport: true, clip: { ...clip, scale: 2 },
|
||
});
|
||
writeFileSync(OUT.replace(/\.png$/, '.card.png'), Buffer.from(s2.result.data, 'base64'));
|
||
}
|
||
}
|
||
|
||
// ── 点「重新加载」→ 应触发新的一轮 /api/v2 请求(验证移动节点后事件还在)
|
||
const countApi = () => net.filter((x) => x.includes('/api/v2/')).length;
|
||
const before = countApi();
|
||
const clicked = await ev(`(() => { const b = document.querySelector('.atk-err-retry'); if (b) b.click(); return !!b; })()`);
|
||
await sleep(4500);
|
||
const after = countApi();
|
||
console.log(`\n=== 点「重新加载」 ===`);
|
||
console.log(`按钮存在=${clicked} | /api/v2 请求数 ${before} → ${after} ${after > before ? '✓ 事件仍在' : '✗ 事件丢了'}`);
|
||
console.log('错误层现状:', await ev(`(() => {
|
||
const el = document.querySelector('.atk-error-layer');
|
||
if (!el) return '(已移除)';
|
||
const c = el.querySelector('.atk-err-card');
|
||
return c ? '仍是我们重写后的卡片(重试再次失败)' : 'Artalk 原生层:' + el.innerText.slice(0, 60);
|
||
})()`));
|
||
|
||
ws.close();
|
||
} catch (e) {
|
||
console.error('ERROR', e.message);
|
||
} finally {
|
||
chrome.kill();
|
||
}
|