/** Own native fixture, real production skills module + isolated FS/IPC. OS picker only is controlled. */ import fs from 'node:fs' import path from 'node:path' import assert from 'node:assert/strict' import { createRequire } from 'node:module' import { spawn } from 'node:child_process' const require = createRequire(import.meta.url) const electronModule = require('electron'), electron = typeof electronModule === 'object' && electronModule.app ? electronModule : null if (electron) { electron.app.disableHardwareAcceleration(); electron.app.commandLine.appendSwitch('use-angle', 'swiftshader'); electron.app.commandLine.appendSwitch('disable-gpu-compositing') } const repository = path.resolve('.'), base = path.join(repository, '.tmp') if (!electron) { const output = fs.mkdtempSync(path.join(base, 'skills-ui-')) assert.ok(path.relative(base, output).startsWith('skills-ui-') && !path.relative(base, output).includes('..')) const env = { ...process.env }; delete env.ELECTRON_RUN_AS_NODE; delete env.ELECTRON_RENDERER_URL const child = spawn(require('electron'), ['--no-sandbox','--disable-gpu-sandbox',path.resolve('scripts/test-skills-ui.mjs'), output], { stdio: 'inherit', windowsHide:true, env }) process.exitCode = await new Promise(resolve => child.on('exit', code => resolve(code ?? 1))) } else { void (async () => { const { app, BrowserWindow, ipcMain } = electron const output = path.resolve(process.argv.at(-1)), relative = path.relative(base, output) assert.ok(relative.startsWith('skills-ui-') && !relative.includes('..') && !path.isAbsolute(relative)) const userData = path.join(output, 'data'); fs.mkdirSync(userData); app.setPath('userData', userData) const workspace = path.join(output, 'workspace'), source = path.join(output, 'source'), checks = [], errors = [] fs.mkdirSync(workspace); fs.mkdirSync(source) fs.writeFileSync(path.join(source, 'SKILL.md'), '---\nname: Native Local Skill\ndescription: Controlled native UI fixture\n---\n# Actual skill instructions\nNATIVE_SKILL_PROOF\n') const imports = [] await app.whenReady() await require('esbuild').build({ entryPoints: [path.join(repository,'src/main/capabilities/skills.ts')], bundle: true, platform:'node',format:'cjs',external:['electron'],outfile:path.join(output,'skills.cjs'),logLevel:'warning' }) const api = require(path.join(output,'skills.cjs')) ipcMain.handle('skills-qa',async (_event,method,args)=>{ if(method==='list')return api.listSkills(...args) if(method==='read')return api.readSkill(...args) if(method==='setEnabled')return api.setSkillEnabled(...args) if(method==='remove')return api.removeSkill(...args) if(method==='export')return api.exportSkill(args[0],path.join(output,'export.zip')) if(method==='importPath'){imports.push(args);return api.importSkill(args[2]==='zip'?path.join(output,'export.zip'):source,args[1])} throw Error('Unknown QA method') }) fs.writeFileSync(path.join(output,'preload.cjs'), `const{contextBridge,ipcRenderer}=require('electron');contextBridge.exposeInMainWorld('ycode',{skills:Object.fromEntries(['list','read','setEnabled','remove','export','importPath'].map(method=>[method,(...args)=>ipcRenderer.invoke('skills-qa',method,args)]))});`) const location = file=>JSON.stringify(path.join(repository,file).replaceAll('\\','/')) fs.writeFileSync(path.join(output,'fixture.tsx'), `import React from 'react';import{createRoot}from'react-dom/client';import Center from ${location('src/renderer/src/capabilities/CapabilityCenter')};import ${location('src/renderer/src/styles/tokens.css')};import ${location('src/renderer/src/styles/app.css')};window.__skillsQa={uses:[],created:0,find:0};createRoot(document.getElementById('root')).render(
{}} onUseSkill={skill=>window.__skillsQa.uses.push(skill)} onCreateSkill={()=>window.__skillsQa.created++} onFindSkill={()=>window.__skillsQa.find++}/>
);`) await require('esbuild').build({entryPoints:[path.join(output,'fixture.tsx')],bundle:true,platform:'browser',format:'esm',jsx:'automatic',outfile:path.join(output,'fixture.js'),loader:{'.svg':'file','.png':'file','.woff':'file','.woff2':'file','.ttf':'file'},logLevel:'warning'}) fs.writeFileSync(path.join(output,'index.html'),'
') const win=new BrowserWindow({width:1052,height:901,useContentSize:true,frame:false,show:false,webPreferences:{preload:path.join(output,'preload.cjs'),contextIsolation:true,nodeIntegration:false,backgroundThrottling:false}}) win.webContents.on('console-message',(_event,level,message)=>{if(level===3)errors.push(message)}) const read=code=>win.webContents.executeJavaScript(code) const check=(name,actual,expected)=>{assert.deepEqual(actual,expected,name);checks.push(name)} const wait=async code=>{for(let index=0;index<100;index++){if(await read(code))return;await new Promise(resolve=>setTimeout(resolve,50))}throw Error('Timeout '+code)} await win.loadFile(path.join(output,'index.html'));win.webContents.debugger.attach('1.3');await win.webContents.debugger.sendCommand('Page.enable');win.showInactive() const cdp=(method,args)=>win.webContents.debugger.sendCommand(method,args) const settle=async()=>{await cdp('Page.captureScreenshot',{format:'png',fromSurface:true});await new Promise(resolve=>setTimeout(resolve,150))} const click=async selector=>{const hover=await read(`(()=>{const e=document.querySelector(${JSON.stringify(selector)});const r=e?.closest('.sm-card')?.getBoundingClientRect();return r?{x:r.x+r.width/2,y:r.y+r.height/2}:null})()`);if(hover){await cdp('Input.dispatchMouseEvent',{type:'mouseMoved',...hover});await new Promise(resolve=>setTimeout(resolve,100))}const point=await read(`(()=>{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}})()`);await cdp('Input.dispatchMouseEvent',{type:'mouseMoved',...point});await new Promise(resolve=>setTimeout(resolve,100));for(const type of ['mousePressed','mouseReleased'])await cdp('Input.dispatchMouseEvent',{type,...point,button:'left',clickCount:1});await settle()} const clickText=async(selector,label)=>{const match=await read(`(()=>{const elements=[...document.querySelectorAll(${JSON.stringify(selector)})];const e=elements.find(e=>e.textContent.trim()===${JSON.stringify(label)});if(!e)throw Error('Missing text '+${JSON.stringify(label)});e.dataset.qaTarget='true';return '[data-qa-target="true"]'})()`);await click(match);await read(`document.querySelector(${JSON.stringify(match)})?.removeAttribute('data-qa-target')`)} const capture=async name=>fs.writeFileSync(path.join(output,name+'.png'),Buffer.from((await cdp('Page.captureScreenshot',{format:'png',fromSurface:true})).data,'base64')) const key=async(key,shift=false)=>{for(const type of ['keyDown','keyUp'])await cdp('Input.dispatchKeyEvent',{type,key,code:key,modifiers:shift?8:0,windowsVirtualKeyCode:key==='Escape'?27:key==='Enter'?13:9})} try { await wait('!!document.querySelector(".um-tab")');await clickText('.um-tab','技能');await wait('!!document.querySelector(".yc-skills-page")') check('one shared topbar, no duplicate page header',await read('document.querySelectorAll(".workbuddy-topbar").length'),1) check('remote market has no invented cards',await read('document.querySelectorAll(".sm-card").length'),0) await capture('market');await click('[aria-label="添加技能"]');check('source add menu shows all three routes',await read('[...document.querySelectorAll(".yc-skill-add-menu button")].map(e=>e.textContent)'),['查找技能','上传技能','创建技能']);await capture('add-menu') await clickText('.yc-skill-add-menu button','上传技能');await wait('!!document.querySelector(".skill-upload-modal")');check('import modal has source DOM and accessible label',await read('document.querySelector(".skill-upload-modal").getAttribute("aria-label")'),'导入技能');await capture('import-modal') check('import modal takes focus',await read('document.activeElement.getAttribute("aria-label")'),'关闭导入技能');await key('Tab',true);check('ShiftTab wraps to last modal control',await read('document.activeElement.matches(".yc-skill-overwrite input")'),true);await key('Tab');check('Tab wraps back to first modal control',await read('document.activeElement.getAttribute("aria-label")'),'关闭导入技能') await click('[aria-label="选择文件夹"]');await wait('document.querySelectorAll(".sm-card").length===1');check('picker reaches real import with folder kind',imports[0],[undefined,false,'directory']) check('actual managed package is installed',(await api.listSkills(workspace))[0].name,'Native Local Skill');await capture('installed') const style=await read(`(()=>{const card=document.querySelector('.sm-card'),s=getComputedStyle(card),r=card.getBoundingClientRect();return{padding:s.padding,radius:s.borderRadius,shadow:s.boxShadow,width:r.width,switchWidth:document.querySelector('.wb-switch__track').getBoundingClientRect().width}})()`) check('source installed card padding and radius',[style.padding,style.radius],['20px','16px']);check('source medium switch geometry',style.switchWidth,26) check('success notice does not affect normal flow',await read('getComputedStyle(document.querySelector(".yc-skill-notice")).position'),'absolute');await wait('!document.querySelector(".yc-skill-notice")');check('success notice disappears automatically',await read('!!document.querySelector(".yc-skill-notice")'),false) await clickText('.sm-installed-batch-manage','批量管理');await click('.sm-card');await clickText('.sm-batch-toolbar button','禁用');check('batch disable persists real state',(await api.listSkills(workspace))[0].enabled,false);await clickText('.sm-batch-toolbar button','启用');check('batch enable persists real state',(await api.listSkills(workspace))[0].enabled,true);await clickText('.sm-installed-batch-manage','完成') await click('.wb-switch__input');check('native toggle persists actual disabled state',(await api.listSkills(workspace))[0].enabled,false) await click('.wb-switch__input');await click('.sm-card-name');await wait('document.querySelector(".sm-skill-detail-overview-body")?.textContent.includes("NATIVE_SKILL_PROOF")');check('detail renders real SKILL.md markdown',await read('document.querySelector(".sm-skill-detail-overview-body").textContent.includes("Actual skill instructions")'),true);await capture('detail') await click('[aria-label="使用技能"]');check('use callback receives actual enabled identity',await read('window.__skillsQa.uses[0].id'),(await api.listSkills(workspace))[0].id) await clickText('.sm-skill-detail-action-row button','导出');check('export writes real ZIP',fs.existsSync(path.join(output,'export.zip')),true) await click('[aria-label="卸载技能"]');await key('Escape');await settle();check('nested Escape closes only top uninstall dialog',await read('!!document.querySelector(".sm-skill-detail-card")&&!document.querySelector("[aria-label=确认卸载]")'),true) await click('[aria-label="关闭技能详情"]');await click('[aria-label="搜索技能"]');await cdp('Input.insertText',{text:'NO_MATCH'});await settle();check('real search filters installed list',await read('document.querySelectorAll(".sm-card").length'),0);await click('[aria-label="清空技能搜索"]');await wait('document.querySelectorAll(".sm-card").length===1') await click('.sm-card-pin');check('pin updates shared slash menu memory',await read(`JSON.parse(localStorage.getItem(${JSON.stringify('ycode.skills.menu.v1:'+workspace.replaceAll('\\','/').toLowerCase())})).pinned.length`),1) await click('.sm-card-more');await clickText('.sm-card-menu button','卸载技能');await wait('!!document.querySelector("[aria-label=确认卸载]")');await key('Escape');check('Escape cancels uninstall without deleting',(await api.listSkills(workspace)).length,1) await click('.sm-card-more');await clickText('.sm-card-menu button','卸载技能');await click('[aria-label="确认卸载"]');await wait('document.querySelectorAll(".sm-card").length===0');check('confirmed uninstall removes managed registration',(await api.listSkills(workspace)).length,0);check('uninstall preserves source',fs.existsSync(path.join(source,'SKILL.md')),true) await click('[aria-label="添加技能"]');await clickText('.yc-skill-add-menu button','上传技能');await click('[aria-label="关闭导入技能"]');check('import close is usable',await read('!!document.querySelector(".skill-upload-modal")'),false) check('closed import restores focus to add trigger',await read('document.activeElement.getAttribute("aria-label")'),'添加技能') await click('[aria-label="添加技能"]');await clickText('.yc-skill-add-menu button','查找技能');check('find invokes real host callback',await read('window.__skillsQa.find'),1) await click('[aria-label="添加技能"]');await clickText('.yc-skill-add-menu button','创建技能');check('create invokes real host callback',await read('window.__skillsQa.created'),1) await click('[aria-label="添加技能"]');await clickText('.yc-skill-add-menu button','上传技能');await clickText('.skill-upload-modal button','选择 ZIP 文件');await wait('document.querySelectorAll(".sm-card").length===1');check('ZIP picker sends explicit kind',imports.at(-1)[2],'zip');check('ZIP picker imports actual exported package',(await api.listSkills(workspace))[0].name,'Native Local Skill') await click('[aria-label="添加技能"]');await clickText('.yc-skill-add-menu button','上传技能');await click('[aria-label="选择文件夹"]');await wait('document.querySelector(".skill-upload-modal [role=alert]")?.textContent.includes("同名")');check('duplicate import displays actual backend conflict',await read('!!document.querySelector(".skill-upload-modal [role=alert]")'),true);await click('.yc-skill-overwrite input');await click('[aria-label="选择文件夹"]');await wait('!document.querySelector(".skill-upload-modal")');check('explicit overwrite flag reaches real import',imports.at(-1)[1],true) const damaged=(await api.listSkills(workspace))[0];fs.writeFileSync(path.join(damaged.path,'SKILL.md'),'invalid metadata') await clickText('.sm-installed-batch-manage','批量管理');await click('.sm-card');await clickText('.sm-batch-toolbar button','禁用');await clickText('.sm-installed-batch-manage','完成');await wait('document.querySelector(".sm-card-desc")?.textContent.includes("不可用")');check('invalid package is visible without blocking list',await read('document.querySelectorAll(".sm-card").length'),1);check('invalid package toggle disabled',await read('document.querySelector(".wb-switch__input").disabled'),true);await capture('invalid-package');await click('.sm-card-more');await clickText('.sm-card-menu button','卸载技能');await click('[aria-label="确认卸载"]');await wait('document.querySelectorAll(".sm-card").length===0');check('UI can remove invalid registration',(await api.listSkills(workspace)).length,0) check('no renderer errors',errors,[]) fs.writeFileSync(path.join(output,'report.json'),JSON.stringify({result:'PASS',checks,style,limits:'Controlled native fixture; actual production storage and IPC, controlled picker; not WorkBuddy full-pixel acceptance.'},null,2));console.log(JSON.stringify({result:'PASS',checks:checks.length,output}));win.destroy();app.exit(0) } catch(error) {await capture('failure');fs.writeFileSync(path.join(output,'failure.json'),JSON.stringify({error:String(error),checks,errors,html:await read('document.body.innerHTML')},null,2));console.error(error);win.destroy();app.exit(1)} })().catch(error=>{console.error(error);electron.app.exit(1)}) }