// Production app + real GitHub package, isolated from the user's skill registry. const fs = require('node:fs/promises') const path = require('node:path') const assert = require('node:assert/strict') if (!process.versions.electron) { ;(async () => { const directory = await fs.mkdtemp(path.resolve('.tmp/recommended-app-')) await Promise.all(['data', 'home', 'workspace'].map(name => fs.mkdir(path.join(directory, name)))) const now = Date.now() await fs.writeFile(path.join(directory, 'data', 'sessions.json'), JSON.stringify({ sessions: [{ id: 'skill-existing', title: 'Skill existing draft', cwd: path.join(directory, 'workspace'), createdAt: now, updatedAt: now, messages: [] }] })) const env = { ...process.env, YCODE_QA_DIRECTORY: directory, YCODE_USER_DATA_DIR: path.join(directory, 'data'), HOME: path.join(directory, 'home'), USERPROFILE: path.join(directory, 'home'), YCODE_QA_OFFSCREEN: '1', YCODE_DISABLE_GPU: '1', YCODE_DISABLE_SYSTEM_INTEGRATION: '1' } delete env.ELECTRON_RUN_AS_NODE delete env.ELECTRON_RENDERER_URL const child = require('node:child_process').spawn(require('electron'), ['--no-sandbox', '--disable-gpu-sandbox', __filename], { env, windowsHide: true, stdio: ['ignore', 'pipe', 'pipe'] }) let output = '' child.stdout.on('data', value => { output += value; if (String(value).includes('RECOMMENDED_APP_')) process.stdout.write(value) }) child.stderr.on('data', value => { output += value }) const timer = setTimeout(() => child.kill(), 200000) child.once('exit', async code => { clearTimeout(timer) await fs.writeFile(path.join(directory, 'process.log'), output) console.log(JSON.stringify({ directory, exitCode: code })) if (code !== 0) console.error(output.slice(-7000)) process.exitCode = code === 0 ? 0 : 1 }) })().catch(error => { console.error(error); process.exitCode = 1 }) } else { const { app, BrowserWindow } = require('electron') const { pathToFileURL } = require('node:url') const directory = path.resolve(process.env.YCODE_QA_DIRECTORY) const rel = path.relative(path.resolve('.tmp'), directory) assert.ok(rel.startsWith('recommended-app-') && !rel.includes('..')) const checks = [], errors = [] let stage = 'boot', win const pause = ms => new Promise(resolve => setTimeout(resolve, ms)) const read = source => win.webContents.executeJavaScript(source) const check = (label, value) => { assert.ok(value, label); checks.push(label) } async function click(selector) { const point = await read(`(()=>{const e=document.querySelector(${JSON.stringify(selector)});if(!e)throw Error('Missing target');e.scrollIntoView({block:'nearest'});const r=e.getBoundingClientRect();const x=Math.round(r.x+r.width/2),y=Math.round(r.y+r.height/2);const hit=document.elementFromPoint(x,y);if(!e.contains(hit))throw Error('Target is covered: '+${JSON.stringify(selector)});return{x,y}})()`) win.webContents.sendInputEvent({ type: 'mouseDown', ...point, button: 'left', clickCount: 1 }) win.webContents.sendInputEvent({ type: 'mouseUp', ...point, button: 'left', clickCount: 1 }) await pause(100) } async function wait(source, label, timeout = 15000) { const end = Date.now() + timeout while (Date.now() < end) { const value = await read(source); if (value) return value; await pause(80) } throw Error('Timeout: ' + label) } async function shot(name) { win.webContents.invalidate() await win.webContents.capturePage() await pause(250) await fs.writeFile(path.join(directory, name + '.png'), (await win.webContents.capturePage()).toPNG()) } app.on('browser-window-created', (_event, window) => { window.setSkipTaskbar(true) window.on('show', () => window.hide()) window.webContents.on('console-message', (event) => { if (event.level === 'error') errors.push(event.message) }) }) ;(async () => { try { await import(pathToFileURL(path.resolve('out/main/index.mjs')).href) await app.whenReady() for (let index = 0; index < 200; index++) { win = BrowserWindow.getAllWindows().find(window => window.getTitle() === 'YCode') if (win && !win.webContents.isLoading()) break await pause(30) } check('production window loaded', !!win) await wait('!!window.ycode?.skills?.recommendations', 'real preload') await wait('!!document.querySelector("[data-session-id=skill-existing]")', 'original session') await click('[data-session-id=skill-existing]') await wait('!!document.querySelector(".chat-pane .wb-composer textarea")', 'initial composer') await read('document.querySelector(".chat-pane .wb-composer textarea").focus()') await win.webContents.insertText('KEEP_ORIGINAL_DRAFT') const catalog = await read('window.ycode.skills.recommendations()') check('fixed Archify recommendation is exposed by real IPC', catalog.some(item => item.id === 'archify' && item.commit === '25cf37080fc8b1dbe758a4efc3935b55f3327083')) await read(`(()=>{window.__installEvents=[];window.ycode.skills.onInstallProgress(event=>window.__installEvents.push(event));window.__skillChanges=0;window.ycode.skills.onChanged(()=>window.__skillChanges++);window.dispatchEvent(new CustomEvent('ycode:open-capabilities',{detail:{tab:'skills'}}));return true})()`) await wait('!!document.querySelector("[data-recommendation-id=archify]")', 'recommendation card') await shot('recommendation') stage = 'install' // Use the real card and confirmation; no direct import and no picker stub. await click('[data-recommendation-id=archify] [aria-label="安装 archify"]') await wait('!!document.querySelector("[aria-label=确认安装推荐技能]")', 'install confirmation') check('the modal host escapes the isolated capability center', await read('document.querySelector(".yc-skill-modal-host")?.parentElement===document.body')) check('the background root is inert while the skill modal is open', await read('document.getElementById("root").inert')) check('sidebar sticky headers are behind the global mask', await read(`(()=>{const e=document.querySelector('.conversation-section-label');if(!e)return false;const r=e.getBoundingClientRect();return !!document.elementFromPoint(r.left+5,r.top+5)?.closest('.yc-skill-modal-host')})()`)) await read('window.__sameDetail=document.querySelector(".yc-skill-detail-shell")') await shot('confirm-full-window') await read('document.documentElement.dataset.theme="dark"') await pause(350); await shot('confirm-dark') const originalSize = win.getSize() win.setSize(720, 900); await pause(350) check('the full-window skill detail fits a narrow viewport', await read('(()=>{const r=document.querySelector(".yc-skill-detail-shell").getBoundingClientRect();return r.left>=0&&r.right<=innerWidth&&r.top>=0&&r.bottom<=innerHeight})()')) await shot('confirm-narrow') win.setSize(...originalSize); await read('document.documentElement.dataset.theme="light"'); await pause(300) await click('[aria-label=确认安装推荐技能]') await wait(`window.__installEvents.some(event=>['installed','failed','cancelled'].includes(event.phase))`, 'native installation', 150000) const progress = await read('window.__installEvents') check('actual download, validation and commit phases are observed', ['downloading', 'validating', 'installing', 'installed'].every(phase => progress.some(event => event.phase === phase))) const installed = await read('window.ycode.skills.list()') const skill = installed.find(item => item.name === 'archify') check('installed skill is enabled, intact and has source provenance', skill?.enabled && !skill.userModified && skill.origin?.commit === catalog[0].commit) const document = await read(`window.ycode.skills.read(${JSON.stringify(skill.id)})`) check('skill body and packaged CLI resources are present', document.content.includes('Archify') && document.files.includes('bin/archify.mjs') && document.files.length > 100) check('committed mutation broadcasts a skill refresh', await read('window.__skillChanges>0')) await wait(`document.querySelector('[data-recommendation-id=archify]')?.textContent.includes('已安装')`, 'installed card refresh') check('installation keeps the same detail DOM mounted', await read('window.__sameDetail===document.querySelector(".yc-skill-detail-shell")')) await wait('document.querySelector("[role=dialog] .sm-skill-detail-action-row")?.textContent.includes("去使用")', 'post-install action') check('the avatar is a packaged offline image', await read('(()=>{const i=document.querySelector("[role=dialog] .sm-skill-detail-icon-wrap img");return i?.complete&&i.naturalWidth>0&&!i.src.startsWith("http")})()')) await shot('installed') stage = 'idempotency' const again = await read(`window.ycode.skills.installRecommended('archify',{operationId:crypto.randomUUID()})`) check('same unchanged version returns existing install', again.id === skill.id && again.origin.installedAt === skill.origin.installedAt) await read(`window.ycode.skills.setEnabled(${JSON.stringify(skill.id)},false)`) const disabled = await read(`window.ycode.skills.installRecommended('archify',{operationId:crypto.randomUUID()})`) check('idempotent install does not silently re-enable disabled skill', disabled.enabled === false) await read(`window.ycode.skills.setEnabled(${JSON.stringify(skill.id)},true)`) stage = 'use-skill' await wait(`!!document.querySelector('[role=dialog] [aria-label="使用推荐技能 archify"]')`, 'use installed recommendation') await click('[role=dialog] [aria-label="使用推荐技能 archify"]') await wait(`document.querySelector('.chat-pane .wb-composer')?.textContent.includes('archify')`, 'skill reference in the real composer') check('using the installed recommendation fills the real composer', true) check('use opens a clean new draft instead of overwriting the old draft', await read('!document.querySelector(".chat-pane .wb-composer").textContent.includes("KEEP_ORIGINAL_DRAFT")')) check('the background is no longer inert after use', await read('!document.getElementById("root").inert')) await shot('use-skill') check('browsing, installation and use do not create persisted chat sessions', (await read('window.ycode.session.list()')).length === 1) await click('[data-session-id=skill-existing]') await wait('document.querySelector(".chat-pane .wb-composer textarea")?.value.includes("KEEP_ORIGINAL_DRAFT")', 'original session draft restored') check('the original session draft remains intact after using the skill', true) check('no renderer errors', errors.length === 0) await fs.writeFile(path.join(directory, 'report.json'), JSON.stringify({ result: 'PASS', checks, progress, skill, files: document.files, errors, limits: 'Real fixed GitHub package and production IPC; no skill commands or model requests executed.' }, null, 2)) console.log('RECOMMENDED_APP_PASS ' + directory) app.quit() } catch (error) { await fs.writeFile(path.join(directory, 'failure.json'), JSON.stringify({ stage, error: String(error.stack ?? error), checks, errors }, null, 2)) if (win) await shot('failure').catch(() => {}) console.error('RECOMMENDED_APP_FAIL ' + String(error)) app.exit(1) } })() }