import electronBinaryPath from 'electron'; import { _electron as electron, test } from '@playwright/test'; import { mkdir, mkdtemp, rm } from 'node:fs/promises'; import { createServer } from 'node:net'; import { tmpdir } from 'node:os'; import { join, resolve } from 'node:path'; import { pathToFileURL } from 'node:url'; import { closeElectronApp, expect, getStableWindow } from './fixtures/electron'; const repoRoot = resolve(process.cwd()); const electronEntry = join(repoRoot, 'dist-electron/main/index.js'); const rendererEntry = pathToFileURL(join(repoRoot, 'dist/index.html')).toString(); const screenshotDir = join(repoRoot, 'test-results', 'yinian-visual'); async function allocatePort(): Promise { return await new Promise((resolvePort, reject) => { const server = createServer(); server.once('error', reject); server.listen(0, '127.0.0.1', () => { const address = server.address(); if (!address || typeof address === 'string') { server.close(() => reject(new Error('Failed to allocate an ephemeral port'))); return; } server.close((error) => { if (error) { reject(error); return; } resolvePort(address.port); }); }); }); } test('captures the core Zhinian production UI surfaces', async () => { await mkdir(screenshotDir, { recursive: true }); const homeDir = await mkdtemp(join(tmpdir(), 'zhinian-visual-home-')); const userDataDir = await mkdtemp(join(tmpdir(), 'zhinian-visual-user-data-')); const hostApiPort = await allocatePort(); const electronEnv = process.platform === 'linux' ? { ELECTRON_DISABLE_SANDBOX: '1' } : {}; const app = await electron.launch({ executablePath: electronBinaryPath, args: [electronEntry], env: { ...process.env, ...electronEnv, HOME: homeDir, USERPROFILE: homeDir, APPDATA: join(homeDir, 'AppData', 'Roaming'), LOCALAPPDATA: join(homeDir, 'AppData', 'Local'), XDG_CONFIG_HOME: join(homeDir, '.config'), CLAWX_E2E: '1', CLAWX_USER_DATA_DIR: userDataDir, CLAWX_PORT_CLAWX_HOST_API: String(hostApiPort), VITE_DEV_SERVER_URL: '', }, timeout: 90_000, }); try { const page = await getStableWindow(app); await page.setViewportSize({ width: 1440, height: 900 }); // The main process stays in E2E mode to avoid startup side effects, while // the renderer is reloaded without the e2e query so the production YINIAN // login and tenant flow are exercised. await page.goto(rendererEntry); await expect(page.getByTestId('yinian-login-page')).toBeVisible(); await page.screenshot({ path: join(screenshotDir, '01-login.png'), fullPage: true }); await page.getByRole('textbox', { name: '账号' }).fill('admin'); await page.locator('#password').fill('123456'); const captchaInput = page.getByLabel('图形验证码'); if (await captchaInput.count()) { await captchaInput.fill('5678'); } await page.getByRole('button', { name: /^登录$/ }).click(); await expect(page.getByTestId('today-page')).toBeVisible(); await page.getByTestId('sidebar-chat-history').hover(); await expect(page.getByTestId('sidebar-chat-history-popover')).toBeVisible(); await page.screenshot({ path: join(screenshotDir, '02-today.png'), fullPage: true }); await page.getByTestId('sidebar-nav-skills').click(); await expect(page.getByTestId('yinian-skills-page')).toBeVisible(); await page.screenshot({ path: join(screenshotDir, '03-skills.png'), fullPage: true }); await page.getByTestId('sidebar-nav-knowledge').click(); await expect(page.getByTestId('knowledge-page')).toBeVisible(); await page.screenshot({ path: join(screenshotDir, '04-knowledge.png'), fullPage: true }); await page.getByTestId('sidebar-nav-settings').click(); await expect(page.getByTestId('settings-page')).toBeVisible(); await expect(page.getByTestId('settings-service-section')).toBeVisible(); await page.screenshot({ path: join(screenshotDir, '05-settings.png'), fullPage: true }); } finally { await closeElectronApp(app); await rm(homeDir, { recursive: true, force: true }); await rm(userDataDir, { recursive: true, force: true }); } });