// cdp.js - drive the Code Count WebView2 via Chrome DevTools Protocol. // Usage: // node cdp.js eval "" evaluate JS (await-ed, JSON result) // node cdp.js click trusted click at CSS viewport coords // node cdp.js rclick trusted right-click // node cdp.js key [text] dispatch a key press (e.g. Enter, Escape) // node cdp.js type "" insert text into the focused element // node cdp.js shot capture in-page screenshot // node cdp.js dialog set auto dialog policy for this run (default: report only) // Multiple commands can be chained with ";;" between argument groups. const PORT = process.env.CC_DEVTOOLS_PORT || '9222'; let seq = 0; const pending = new Map(); let ws; let dialogPolicy = null; const dialogEvents = []; function send(method, params = {}) { return new Promise((resolve, reject) => { const id = ++seq; pending.set(id, { resolve, reject }); ws.send(JSON.stringify({ id, method, params })); }); } async function connect() { const res = await fetch(`http://127.0.0.1:${PORT}/json/list`); const targets = await res.json(); const page = targets.find(t => t.type === 'page'); if (!page) throw new Error('no page target'); ws = new WebSocket(page.webSocketDebuggerUrl); await new Promise((ok, bad) => { ws.onopen = ok; ws.onerror = bad; }); ws.onmessage = ev => { const msg = JSON.parse(ev.data); if (msg.id && pending.has(msg.id)) { const { resolve, reject } = pending.get(msg.id); pending.delete(msg.id); msg.error ? reject(new Error(msg.error.message)) : resolve(msg.result); } else if (msg.method === 'Page.javascriptDialogOpening') { dialogEvents.push(msg.params); console.log('DIALOG-OPEN', JSON.stringify({ type: msg.params.type, message: msg.params.message })); if (dialogPolicy) { send('Page.handleJavaScriptDialog', { accept: dialogPolicy === 'accept' }) .then(() => console.log('DIALOG-HANDLED', dialogPolicy)) .catch(e => console.log('DIALOG-ERR', e.message)); } } }; await send('Page.enable'); await send('Runtime.enable'); } const sleep = ms => new Promise(r => setTimeout(r, ms)); async function mouse(type, x, y, button, clickCount = 1) { await send('Input.dispatchMouseEvent', { type: 'mouseMoved', x, y, button: 'none' }); await sleep(40); await send('Input.dispatchMouseEvent', { type, x, y, button, clickCount }); } async function click(x, y, button = 'left') { await mouse('mousePressed', x, y, button); await sleep(50); await send('Input.dispatchMouseEvent', { type: 'mouseReleased', x, y, button, clickCount: 1 }); } async function main() { const groups = process.argv.slice(2).join('\u0000').split('\u0000;;\u0000').map(g => g.split('\u0000')); await connect(); for (const g of groups) { const [cmd, ...args] = g; if (cmd === 'dialog') { dialogPolicy = args[0]; console.log('dialog policy:', dialogPolicy); } else if (cmd === 'eval') { const r = await send('Runtime.evaluate', { expression: args.join(' '), awaitPromise: true, returnByValue: true }); console.log('EVAL', JSON.stringify(r.result && 'value' in r.result ? r.result.value : r.result)); if (r.exceptionDetails) console.log('EXC', JSON.stringify(r.exceptionDetails.exception?.description || r.exceptionDetails.text)); } else if (cmd === 'click') { await click(Number(args[0]), Number(args[1])); console.log('clicked', args[0], args[1]); } else if (cmd === 'rclick') { await click(Number(args[0]), Number(args[1]), 'right'); console.log('right-clicked', args[0], args[1]); } else if (cmd === 'key') { const key = args[0]; const defs = { Enter: { code: 'Enter', keyCode: 13, text: '\r' }, Escape: { code: 'Escape', keyCode: 27 }, Tab: { code: 'Tab', keyCode: 9 } }; const d = defs[key] || { code: key, keyCode: key.charCodeAt(0) }; await send('Input.dispatchKeyEvent', { type: 'keyDown', key, code: d.code, windowsVirtualKeyCode: d.keyCode, text: d.text }); await sleep(30); await send('Input.dispatchKeyEvent', { type: 'keyUp', key, code: d.code, windowsVirtualKeyCode: d.keyCode }); console.log('key', key); } else if (cmd === 'type') { await send('Input.insertText', { text: args.join(' ') }); console.log('typed', args.join(' ')); } else if (cmd === 'shot') { const shot = await send('Page.captureScreenshot', { format: 'png' }); require('fs').writeFileSync(args[0], Buffer.from(shot.data, 'base64')); console.log('saved', args[0]); } else if (cmd === 'viewport') { // viewport 模拟视口尺寸;viewport 0 恢复真实尺寸 const w = Number(args[0]); if (w) await send('Emulation.setDeviceMetricsOverride', { width: w, height: Number(args[1] || 700), deviceScaleFactor: 0, mobile: false }); else await send('Emulation.clearDeviceMetricsOverride'); console.log('viewport', args.join('x') || 'cleared'); } else if (cmd === 'wait') { await sleep(Number(args[0] || 500)); } else { throw new Error('unknown cmd: ' + cmd); } await sleep(120); } // give async dialog handlers a moment before exit await sleep(300); ws.close(); } main().catch(e => { console.error('FATAL', e.message); process.exit(1); });