import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import path from 'node:path'; import { closeElectronApp, expect, getStableWindow, test } from './fixtures/electron'; import { createProjectConfig } from '../../shared/project-config'; test.describe('Project configuration skills', () => { test('does not expose the removed Superpowers feature and supports Skill details', async ({ launchElectronApp }) => { const parentPath = await mkdtemp(path.join(tmpdir(), 'niancode-project-configuration-e2e-')); const app = await launchElectronApp({ skipSetup: true }); try { const userDataDir = await app.evaluate(({ app: electronApp }) => electronApp.getPath('userData')); const skillDir = path.join(userDataDir, 'opencode', 'niancode-config', 'skills', 'agent-browser'); await mkdir(path.join(skillDir, 'references'), { recursive: true }); await writeFile(path.join(skillDir, 'SKILL.md'), '---\nname: agent-browser\ndescription: E2E browser debugging skill.\n---\n\n# Browser debugging\n'); await writeFile(path.join(skillDir, 'references', 'guide.md'), '# Reference guide\n'); await app.evaluate(({ ipcMain }, selectedPath) => { ipcMain.removeHandler('dialog:open'); ipcMain.handle('dialog:open', async () => ({ canceled: false, filePaths: [selectedPath] })); }, parentPath); const page = await getStableWindow(app); await expect(page.getByTestId('ai-module-selection-page')).toBeVisible(); await page.getByTestId('ai-module-option-programming').click(); await expect(page.getByTestId('main-layout')).toBeVisible(); await page.getByTestId('sidebar-create-project').click(); await expect(page.getByRole('radio', { name: '小游戏' })).toBeChecked(); await expect(page.getByRole('radio', { name: '小程序' })).not.toBeChecked(); await expect(page.getByRole('radio', { name: '自定义项目' })).not.toBeChecked(); await page.getByRole('radio', { name: '小程序' }).click(); await expect(page.getByRole('radio', { name: '小程序' })).toBeChecked(); await page.getByRole('radio', { name: '小游戏' }).click(); await expect(page.getByRole('radio', { name: '直接使用所选文件夹(默认)' })).toBeChecked(); await page.getByRole('button', { name: '选择路径' }).click(); await expect(page.getByLabel('项目路径')).toHaveValue(parentPath); await page.getByRole('button', { name: '确认创建' }).click(); await expect(page.getByTestId('project-configuration-page')).toBeVisible(); await expect(page.getByTestId('sidebar-nav-publish')).toHaveCount(0); await expect(page.getByText(/新项目还没有伙伴/)).toHaveCount(0); await expect(page.getByTestId('project-configuration-actions')).toBeVisible(); const actionBarBottomGap = await page.getByTestId('project-configuration-actions').evaluate((element) => window.innerHeight - element.getBoundingClientRect().bottom); expect(actionBarBottomGap).toBeLessThan(40); await expect(page.getByRole('button', { name: '创建伙伴' })).toBeVisible(); for (const [resourceId, title] of [ ['resource-card-models', '大脑(大语言模型)'], ['resource-card-knowledge', '笔记本(知识库)'], ['resource-card-skills', '工具箱(技能)'], ] as const) { await page.getByTestId(resourceId).click(); const drawer = page.getByRole('dialog', { name: title }); await expect(drawer).toBeVisible(); if (resourceId === 'resource-card-models') { await expect(drawer.getByRole('combobox')).toHaveCount(0); await expect(drawer.locator('[data-testid="project-model-list"], [data-testid="project-model-empty-state"]')).toHaveCount(1); } const closeButton = drawer.getByRole('button', { name: '关闭' }); const closeGeometry = await closeButton.evaluate((button) => { const icon = button.querySelector('svg'); if (!icon) return null; const buttonRect = button.getBoundingClientRect(); const iconRect = icon.getBoundingClientRect(); return { button: { x: buttonRect.x, y: buttonRect.y, width: buttonRect.width, height: buttonRect.height }, icon: { x: iconRect.x, y: iconRect.y, width: iconRect.width, height: iconRect.height }, }; }); if (!closeGeometry) { throw new Error(`未能读取 ${title} 抽屉关闭按钮的布局区域`); } expect(closeGeometry.button.width).toBe(40); expect(closeGeometry.button.height).toBe(40); expect(Math.abs((closeGeometry.icon.x + closeGeometry.icon.width / 2) - (closeGeometry.button.x + closeGeometry.button.width / 2))).toBeLessThanOrEqual(0.5); expect(Math.abs((closeGeometry.icon.y + closeGeometry.icon.height / 2) - (closeGeometry.button.y + closeGeometry.button.height / 2))).toBeLessThanOrEqual(0.5); await closeButton.locator('svg').click(); await expect(page.getByRole('dialog', { name: title })).toHaveCount(0); } const now = new Date().toISOString(); await mkdir(path.join(parentPath, '.niancode'), { recursive: true }); await writeFile(path.join(parentPath, '.niancode', 'project.json'), JSON.stringify({ ...createProjectConfig(undefined, 'mini_game'), initialized: true, agents: [{ id: 'e2e-partner', avatarId: 'avatar-01', roleName: '项目伙伴', name: 'E2E伙伴', builtIn: false, enabled: true, model: 'moonshot/kimi-k2.6', skillIds: [], responsibility: { mission: '验证伙伴维护弹窗。', owns: [], boundaries: [], collaborators: [], principles: [] }, prompt: '', archivedAt: null, pinned: false, createdAt: now, updatedAt: now, }], }, null, 2)); await page.reload(); await expect(page.getByTestId('project-configuration-page')).toBeVisible(); await page.getByTestId('resource-card-skills').click(); await expect(page.getByTestId('superpowers-card')).toHaveCount(0); await expect(page.getByRole('switch', { name: '启用 Superpowers' })).toHaveCount(0); await expect(page.getByTestId('skill-card-agent-browser')).toBeVisible(); await page.getByTestId('skill-card-agent-browser').click(); await expect(page.getByTestId('skill-detail-view')).toBeVisible(); await expect(page.getByTestId('skill-structure')).toContainText('references/guide.md'); await expect(page.getByTestId('skill-main-file')).toContainText('# Browser debugging'); await page.getByRole('button', { name: '返回技能列表' }).locator('svg').click(); await expect(page.getByTestId('skill-card-agent-browser')).toBeVisible(); await page.getByRole('button', { name: '关闭' }).click(); await expect(page.getByTestId('resource-card-publish')).toHaveCount(0); await expect(page.getByRole('button', { name: '一键提交审核' })).toBeVisible(); const agentCard = page.getByTestId('project-agent-e2e-partner'); await expect(agentCard).toContainText('E2E伙伴'); await agentCard.click(); const maintenanceDialog = page.getByRole('dialog', { name: 'E2E伙伴 · 伙伴维护' }); await expect(maintenanceDialog).toBeVisible(); await expect(maintenanceDialog.getByLabel(/伙伴名称/)).toHaveValue('E2E伙伴'); await maintenanceDialog.getByRole('button', { name: '更换头像' }).click(); const avatarDialog = page.getByRole('dialog', { name: '选择头像' }); await expect(avatarDialog).toBeVisible(); await avatarDialog.getByLabel('上传伙伴头像').setInputFiles(path.join(process.cwd(), 'src/assets/agent-avatars/avatar-01.png')); await expect(avatarDialog).toHaveCount(0); await expect(maintenanceDialog.getByAltText('当前上传头像')).toBeVisible(); await expect(page.getByText('完成配置')).toHaveCount(0); } finally { await closeElectronApp(app); await rm(parentPath, { recursive: true, force: true }); } }); });