// Test only the new YCode component. Original geometry comes exclusively from fixed REA exports. const fs = require('node:fs'), path = require('node:path'), assert = require('node:assert/strict') const { app, BrowserWindow } = require('electron') const calibrated = process.argv.includes('--visible-swiftshader') app.disableHardwareAcceleration() if (calibrated) { app.commandLine.appendSwitch('use-angle', 'swiftshader'); app.commandLine.appendSwitch('disable-gpu-compositing') } const root = path.resolve(__dirname, '..'), output = path.join(root, calibrated ? '.tmp/chat-header-native-swiftshader-20261016' : '.tmp/chat-header-native-20261016') assert.ok(output.startsWith(path.join(root, '.tmp') + path.sep)) fs.mkdirSync(output, { recursive: true }) app.setPath('userData', path.join(output, 'user-data')) const sourceClosed = JSON.parse(fs.readFileSync(path.join(root, '.tmp/rea-chat-header-final-20261016/layout.json'), 'utf8')) const sourceOpen = JSON.parse(fs.readFileSync(path.join(root, '.tmp/rea-chat-header-static-20261016/layout.json'), 'utf8')) const sourceSearch = JSON.parse(fs.readFileSync(path.join(root, '.tmp/rea-chat-header-title-20261016/layout.json'), 'utf8')) const getSource = (evidence, selector) => evidence.layout.documents[0].nodes.filter(node => node.attributes.class?.split(' ').includes(selector.slice(1))) const widthClosed = getSource(sourceClosed, '.workbuddy-topbar')[0].bounds[2] const widthOpen = getSource(sourceOpen, '.workbuddy-topbar')[0].bounds[2] const fixture = path.join(output, 'fixture.tsx') const component = path.join(root, 'src/renderer/src/components/SourceChatHeader').replaceAll('\\', '/') fs.writeFileSync(fixture, `import React,{useState} from 'react';import{createRoot}from'react-dom/client';import Header from ${JSON.stringify(component)}; import ${JSON.stringify(path.join(root, 'src/renderer/src/styles/app.css').replaceAll('\\', '/'))}; function Fixture(){const[title,setTitle]=useState('开始新会话'),[right,setRight]=useState(false),[open,setOpen]=useState(false),[keyword,setKeyword]=useState(''),[index,setIndex]=useState(0);const count=keyword?3:0;window.__headerQA??={renames:[],shares:0,toggles:0};return
{window.__headerQA.toggles++;setRight(v=>!v)}} onTitleChange={async value=>{window.__headerQA.renames.push(value);setTitle(value)}} onShare={()=>{window.__headerQA.shares++}} search={{open,keyword,hitCount:count,activeIndex:index,onOpen:()=>setOpen(true),onClose:()=>{setOpen(false);setKeyword('');setIndex(0)},onKeywordChange:value=>{setKeyword(value);setIndex(0)},onNext:()=>setIndex(v=>(v+1)%count),onPrev:()=>setIndex(v=>(v+count-1)%count)}}/>
}createRoot(document.getElementById('root')).render();`) const selectors = ['.workbuddy-topbar', '.workbuddy-topbar-title-area', '.workbuddy-topbar-title', '.workbuddy-topbar-actions', '.workbuddy-topbar-actions > .wb-button', '.cb-chat-search', '.cb-chat-search__input', '.cb-chat-search__nav', '.cb-chat-search__close', '.workbuddy-topbar-title-input'] const properties = ['font-family', 'font-size', 'font-weight', 'line-height', 'color', 'background-color', 'padding-left', 'padding-right', 'gap', 'border-radius', 'display'] app.whenReady().then(async () => { await require('esbuild').build({ entryPoints: [fixture], bundle: true, outfile: path.join(output, 'fixture.js'), jsx: 'automatic', platform: 'browser', format: 'iife', logLevel: 'warning' }) fs.writeFileSync(path.join(output, 'index.html'), '
') const win = new BrowserWindow({ width: 1336, height: 901, useContentSize: true, frame: false, show: false, webPreferences: { contextIsolation: true, nodeIntegration: false, backgroundThrottling: false, offscreen: !calibrated } }) const errors = [] win.webContents.on('console-message', (_e, level, message) => { if (level === 3) errors.push(message) }) await win.loadFile(path.join(output, 'index.html')) win.webContents.debugger.attach('1.3') await win.webContents.debugger.sendCommand('Page.enable') if (calibrated) win.showInactive() const capture = async () => calibrated ? Buffer.from((await win.webContents.debugger.sendCommand('Page.captureScreenshot', { format: 'png', fromSurface: true, captureBeyondViewport: false })).data, 'base64') : (await win.webContents.capturePage()).toPNG() const settle = async () => { await capture(); await new Promise(resolve => setTimeout(resolve, 160)) } const evaluate = code => win.webContents.executeJavaScript(code) const point = selector => evaluate(`(()=>{const e=document.querySelector(${JSON.stringify(selector)});if(!e)throw Error('Missing '+${JSON.stringify(selector)});const r=e.getBoundingClientRect();return{x:r.x+r.width/2,y:r.y+r.height/2}})()`) const hover = async selector => win.webContents.debugger.sendCommand('Input.dispatchMouseEvent', { type: 'mouseMoved', ...await point(selector) }) const click = async selector => { const p = await point(selector); for (const type of ['mousePressed', 'mouseReleased']) await win.webContents.debugger.sendCommand('Input.dispatchMouseEvent', { type, ...p, button: 'left', clickCount: 1 }); await settle() } const key = async (key, code = key, modifiers = 0) => { for (const type of ['keyDown', 'keyUp']) await win.webContents.debugger.sendCommand('Input.dispatchKeyEvent', { type, key, code, modifiers, windowsVirtualKeyCode: key === 'Enter' ? 13 : key === 'Escape' ? 27 : 65 }) } const measure = () => evaluate(`(${function (selectors, properties) { return selectors.flatMap(selector => Array.from(document.querySelectorAll(selector), (el, index) => { const r = el.getBoundingClientRect(), s = getComputedStyle(el) return { selector, index, text: el.textContent, bounds: [r.x, r.y, r.width, r.height], styles: Object.fromEntries(properties.map(key => [key, s.getPropertyValue(key)])) } })) }.toString()})(${JSON.stringify(selectors)},${JSON.stringify(properties)})`) await settle() const closed = await measure() fs.writeFileSync(path.join(output, 'collapsed.png'), await capture()) await click('button[aria-label="对话内搜索"]') // The real REA search snapshot was taken after its probe restored a neutral pointer. await win.webContents.debugger.sendCommand('Input.dispatchMouseEvent', { type: 'mouseMoved', x: 0, y: 0 }) await settle() const searching = await measure() fs.writeFileSync(path.join(output, 'search.png'), await capture()) await win.webContents.debugger.sendCommand('Input.insertText', { text: '测试' }) await settle() assert.equal(await evaluate('document.querySelector(".cb-chat-search__counter").textContent'), '1/3') await key('Enter'); await settle() assert.equal(await evaluate('document.querySelector(".cb-chat-search__counter").textContent'), '2/3') await key('Enter', 'Enter', 8); await settle() assert.equal(await evaluate('document.querySelector(".cb-chat-search__counter").textContent'), '1/3') await key('Escape'); await settle() assert.equal(await evaluate('document.querySelector(".cb-chat-search")'), null) await hover('.workbuddy-topbar-title-area'); await settle() await click('.workbuddy-topbar-edit-button') const editing = await measure() fs.writeFileSync(path.join(output, 'edit.png'), await capture()) const sourceEdit = sourceSearch.probe.frames.find(frame => frame.step_id === 'title-editor-open').regions.find(region => region.selector === '.workbuddy-topbar-title-input') assert.deepEqual(editing.find(node => node.selector === '.workbuddy-topbar-title-input').bounds, sourceEdit.bounds) await win.webContents.debugger.sendCommand('Input.insertText', { text: '取消修改' }) await key('Escape'); await settle() assert.deepEqual(await evaluate('window.__headerQA.renames'), []) await hover('.workbuddy-topbar-title-area'); await settle() await click('.workbuddy-topbar-edit-button') await win.webContents.debugger.sendCommand('Input.insertText', { text: '原生编辑已保存' }) await key('Enter'); await settle() assert.deepEqual(await evaluate('window.__headerQA.renames'), ['原生编辑已保存']) await click('button[aria-label="分享任务"]') assert.equal(await evaluate('window.__headerQA.shares'), 1) await click('button[aria-label="打开右侧栏"]') const opened = await measure() assert.equal(await evaluate('document.querySelector("button[aria-label=打开右侧栏]")'), null) assert.equal(await evaluate('window.__headerQA.toggles'), 1) function compare(samples, evidence, cases) { for (const [selector, sourceSelector, fields] of cases) { const ownNodes = samples.filter(n => n.selector === selector), original = getSource(evidence, sourceSelector) assert.equal(ownNodes.length, original.length, selector) ownNodes.forEach((own, index) => { assert.deepEqual(own.bounds, original[index].bounds, selector + ' bounds') for (const property of fields) assert.equal(own.styles[property], original[index].styles[property], selector + ' ' + property) }) } } const geometryCases = [['.workbuddy-topbar', '.workbuddy-topbar', ['font-family', 'font-size', 'font-weight', 'line-height', 'color', 'padding-left', 'padding-right', 'gap']], ['.workbuddy-topbar-actions', '.workbuddy-topbar-actions', ['gap']]] compare(closed, sourceClosed, geometryCases) // Original title selectors include the empty workspace slot, so compare the actual title separately. const ownTitle = closed.find(n => n.selector === '.workbuddy-topbar-title' && n.text === '开始新会话') const originalTitle = getSource(sourceClosed, '.workbuddy-topbar-title').find(n => n.node_name === 'SPAN') assert.deepEqual(ownTitle.bounds, originalTitle.bounds) for (const property of ['font-family', 'font-size', 'font-weight', 'line-height', 'color']) assert.equal(ownTitle.styles[property], originalTitle.styles[property]) compare(searching, sourceSearch, [['.cb-chat-search', '.cb-chat-search', ['background-color', 'padding-left', 'padding-right', 'border-radius']], ['.cb-chat-search__input', '.cb-chat-search__input', ['font-family', 'font-size', 'line-height']], ['.cb-chat-search__nav', '.cb-chat-search__nav', []], ['.cb-chat-search__close', '.cb-chat-search__close', []]]) compare(opened, sourceOpen, geometryCases) assert.deepEqual(errors, []) const report = { status: 'PASS', evidenceIds: [sourceClosed.evidenceId, sourceOpen.evidenceId, sourceSearch.evidenceId], closed, searching, editing, opened, errors, actions: await evaluate('window.__headerQA'), rendering: { mode: calibrated ? 'visible-swiftshader' : 'offscreen-disabled-gpu', visible: win.isVisible(), captureMethod: calibrated ? 'Page.captureScreenshot(fromSurface:true,captureBeyondViewport:false)' : 'webContents.capturePage', ...(calibrated ? { gpu: await app.getGPUInfo('complete'), gpuFeatures: app.getGPUFeatureStatus() } : {}) }, limitations: ['Isolated component fixture with controlled host callbacks. Production search/rename/share integration is independently owned by the parent task.', 'Only the observed Windows 1336x901 stable states are compared. Tooltips, compact/Mac/dark, prompt-list runtime and full glyph pixel parity are not claimed.'] } fs.writeFileSync(path.join(output, 'report.json'), JSON.stringify(report, null, 2)) console.log(JSON.stringify({ status: 'PASS', output, errors, evidenceIds: report.evidenceIds, actions: report.actions })) win.webContents.debugger.detach(); win.destroy(); app.quit() }).catch(error => { console.error(error); app.exit(1) })