// Manual browser test (Node + Chromium over CDP), not part of `npm test`; same
// setup as browser-shared-control.mjs (fake proto-3 unit on :1339, server :4052).
// Swatches tab in real Chromium: tap Red -> unit gets grant/slot/release;
// save current -> appears under My swatches; edit -> remove it.
import { spawn } from 'node:child_process'
import { writeFileSync, mkdirSync, readFileSync } from 'node:fs'
const OUT = process.argv[2]
const sleep = (ms) => new Promise((r) => setTimeout(r, ms))
const results = []
const check = (name, ok, detail = '') => { results.push(ok); console.log((ok ? 'PASS ' : 'FAIL ') + name + (ok ? '' : ' ' + detail)) }
const prof = process.env.HOME + '/.cache/cthuloid-shots/prof-sw-' + Date.now()
mkdirSync(prof, { recursive: true })
const chrome = spawn('chromium', ['--headless=new', '--disable-gpu', '--remote-debugging-port=9351', '--user-data-dir=' + prof, '--window-size=1100,1100', 'about:blank'], { stdio: 'ignore' })
let target
for (let i = 0; i < 50 && !target; i++) { await sleep(200); try { target = (await (await fetch('http://127.0.0.1:9351/json')).json()).find((t) => t.type === 'page') } catch {} }
const ws = new WebSocket(target.webSocketDebuggerUrl)
await new Promise((r) => ws.addEventListener('open', r))
let id = 0
const pending = new Map()
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) } })
const send = (method, params = {}) => new Promise((r) => { const i = ++id; pending.set(i, r); ws.send(JSON.stringify({ id: i, method, params })) })
const evalJs = async (expr) => (await send('Runtime.evaluate', { expression: expr, returnByValue: true })).result.result.value
const click = async (expr) => {
const b = await evalJs(`(() => { const e = ${expr}; if (!e) return null; const r = e.getBoundingClientRect(); return { x: r.x + r.width / 2, y: r.y + r.height / 2 } })()`)
if (!b) return false
await send('Input.dispatchMouseEvent', { type: 'mousePressed', x: b.x, y: b.y, button: 'left', buttons: 1, clickCount: 1 })
await send('Input.dispatchMouseEvent', { type: 'mouseReleased', x: b.x, y: b.y, button: 'left', buttons: 0, clickCount: 1 })
return true
}
const lines = () => readFileSync(OUT + '/fake3-lines.txt', 'utf8').trim().split('\n').map((l) => l.split(' ').slice(1).join(' '))
await send('Page.enable'); await send('Runtime.enable')
await send('Page.navigate', { url: 'http://127.0.0.1:4052/' })
await sleep(2000)
const card = `[...document.querySelectorAll('.unit')].find(u => !u.classList.contains('off'))`
await click(`${card}.querySelector('.swatch')`)
await sleep(300)
check('swatches tab exists', await click(`[...document.querySelectorAll('.pk-tabs button')].find(b => b.dataset.tab === 'swatches')`))
await sleep(300)
const count = await evalJs(`document.querySelectorAll('.pk-swatches')[0].children.length`)
check('palette shows 18 colours', count === 18, count)
check('tap Red', await click(`[...document.querySelectorAll('.pk-swatch')].find(b => b.title === 'Red')`))
await sleep(1800) // grab, frame, 1 s quiet release
const l = lines()
const seq = l.filter((x) => /^(grant|slot|release)/.test(x))
check('unit got grant, red frame, release', seq[0] === 'grant 0 ' + (await evalJs(`document.querySelector('#my-name').value`)) &&
seq.some((x) => /^slot 0 5 \d+ 0 0 30000 0$/.test(x)) && seq[seq.length - 1] === 'release 0', JSON.stringify(seq))
check('Recent now lists red', await evalJs(`[...document.querySelectorAll('.pk-sw-title')].some(t => t.textContent === 'Recent')`))
await send('Page.captureScreenshot', { format: 'png' }).then((r) => writeFileSync(OUT + '/sw-tab.png', Buffer.from(r.result.data, 'base64')))
check('save current', await click(`[...document.querySelectorAll('.pk-sw-actions button')].find(b => b.textContent === '+ Save current')`))
await sleep(200)
const mine = () => evalJs(`(() => { const t = [...document.querySelectorAll('.pk-sw-title')].find(t => t.textContent === 'My swatches'); return t.nextElementSibling.children.length })()`)
check('My swatches has it', (await mine()) === 1)
await click(`[...document.querySelectorAll('.pk-sw-actions button')].find(b => b.textContent === 'Edit')`)
await sleep(150)
await click(`document.querySelector('.pk-swatch.removing')`)
await sleep(150)
check('Edit -> remove empties it', (await mine()) === 0)
ws.close(); chrome.kill()
console.log(results.every(Boolean) ? 'ALL PASS' : 'FAILURES')
process.exit(results.every(Boolean) ? 0 : 1)