OverviewFilesCommitsIssuesDocsReleasesPeers

codetree / test / manual / keyprobe-floor.mjs

119 lines · javascript ref:
// 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)