// See README.md in this directory. Needs a running server, passed as argv[2].
// Drive the real page with real key events over the DevTools protocol.
// Asserting that a handler LOOKS right is not evidence that a key works.
import { spawn } from 'child_process'
const URL_ = process.argv[2]
const PORT = 9335
const dir = '/tmp/claude-1000/cdp-profile-' + PORT
const chrome = spawn('chromium', [
'--headless=new', '--no-sandbox', '--disable-gpu', '--window-size=1600,1000',
'--remote-debugging-port=' + PORT, '--user-data-dir=' + dir, URL_
], { stdio: 'ignore' })
const sleep = (ms) => new Promise(r => setTimeout(r, ms))
async function target () {
for (let i = 0; i < 60; i++) {
try {
const res = await fetch('http://127.0.0.1:' + PORT + '/json/list')
const list = await res.json()
const page = list.find(t => t.type === 'page' && t.webSocketDebuggerUrl)
if (page) return page.webSocketDebuggerUrl
} catch {}
await new Promise(r => setTimeout(r, 250))
}
throw new Error('no devtools target')
}
const ws = new WebSocket(await target())
await new Promise(r => { ws.onopen = r })
let id = 0
const pending = new Map()
ws.onmessage = (m) => {
const msg = JSON.parse(m.data)
if (msg.id && pending.has(msg.id)) { pending.get(msg.id)(msg); pending.delete(msg.id) }
}
function send (method, params = {}) {
const myId = ++id
ws.send(JSON.stringify({ id: myId, method, params }))
return new Promise(r => pending.set(myId, r))
}
async function evaluate (expr) {
const r = await send('Runtime.evaluate', { expression: expr, returnByValue: true, awaitPromise: true })
if (r.result && r.result.exceptionDetails) return { error: r.result.exceptionDetails.text }
return r.result && r.result.result ? r.result.result.value : undefined
}
const VK = { g: 71, f: 70, r: 82, o: 79, '[': 219, ']': 221 }
async function key (k) {
const code = k === '[' ? 'BracketLeft' : k === ']' ? 'BracketRight' : 'Key' + k.toUpperCase()
const base = { key: k, code, windowsVirtualKeyCode: VK[k], nativeVirtualKeyCode: VK[k] }
await send('Input.dispatchKeyEvent', { type: 'keyDown', text: k, ...base })
await send('Input.dispatchKeyEvent', { type: 'keyUp', ...base })
await new Promise(r => setTimeout(r, 450))
}
async function moveTo (x, y) {
await send('Input.dispatchMouseEvent', { type: 'mouseMoved', x, y, buttons: 0 })
await new Promise(r => setTimeout(r, 200))
}
await send('Runtime.enable')
await new Promise(r => setTimeout(r, 5000))
const out = []
const say = (label, value) => { out.push(label + ': ' + JSON.stringify(value)); console.log(label + ': ' + JSON.stringify(value)) }
say('level', await evaluate('document.getElementById("levelname").textContent'))
say('containers drawn', await evaluate('document.querySelectorAll(".zone").length'))
const rect = await evaluate(`(() => {
for (const g of document.querySelectorAll('#world > g')) {
const r = g.getBoundingClientRect();
const x = Math.round(r.x + r.width / 2), y = Math.round(r.y + r.height / 2);
const el = document.elementFromPoint(x, y);
if (el && String(el.getAttribute('class') || '').indexOf('n-') === 0) return { x, y };
}
return null;
})()`)
say('a card point', rect)
if (rect) await moveTo(rect.x, rect.y)
// level.js is an ES module, so `view` is not reachable from here. Everything
// below is read off the DOM, which is what the user sees anyway.
const stats = () => evaluate('document.getElementById("stats").textContent')
const curves = () => evaluate(
'[...document.querySelectorAll("path.link")].filter(p => p.getAttribute("d").indexOf(" C ") >= 0).length')
await key('g')
say('after G -> ring boxes', await evaluate('document.querySelectorAll(".zone-ring").length'))
say('after G -> url', await evaluate('location.search'))
say('after G -> stats', await stats())
await key(']')
say('after ] -> url', await evaluate('location.search'))
say('after ] -> stats', await stats())
await key('[')
await key('[')
say('after [[ -> ring boxes', await evaluate('document.querySelectorAll(".zone-ring").length'))
say('after [[ -> url', await evaluate('location.search'))
// B and R were dead on this rung: the handler began `if (!view.model) return`
// and view.model is null at system level.
await key('b')
say('after B -> stats mentions bundled', await stats())
await key('b')
say('curved links before R', await curves())
await key('r')
say('curved links after R', await curves())
await key('r')
await evaluate('document.getElementById("path").focus()')
say('focused', await evaluate('document.activeElement.id'))
await key('g')
say('G while typing -> ring boxes', await evaluate('document.querySelectorAll(".zone-ring").length'))
console.log('---')
ws.close()
chrome.kill('SIGKILL')
process.exit(0)