/** * ZCLAW Tauri 模式 E2E 测试 * * 测试 Tauri 桌面应用特有的功能和集成 * 验证 Kernel Client、Rust 后端和 Native 功能的完整性 */ import { test, expect, Page } from '@playwright/test'; test.setTimeout(120000); async function waitForAppReady(page: Page) { await page.waitForLoadState('domcontentloaded'); await page.waitForTimeout(2000); } async function takeScreenshot(page: Page, name: string) { await page.screenshot({ path: `test-results/tauri-artifacts/${name}.png`, fullPage: true, }); } test.describe('ZCLAW Tauri 模式核心功能', () => { test.beforeEach(async ({ page }) => { await page.goto('/'); await waitForAppReady(page); }); test.describe('1. Tauri 运行时检测', () => { test('应该检测到 Tauri 运行时环境', async ({ page }) => { const isTauri = await page.evaluate(() => { return '__TAURI_INTERNALS__' in window; }); console.log('[Tauri Check] isTauriRuntime:', isTauri); if (!isTauri) { console.warn('[Tauri Check] Warning: Not running in Tauri environment'); console.warn('[Tauri Check] Some tests may not work correctly'); } await takeScreenshot(page, '01-tauri-runtime-check'); }); test('Tauri API 应该可用', async ({ page }) => { const tauriAvailable = await page.evaluate(async () => { try { const { invoke } = await import('@tauri-apps/api/core'); const result = await invoke('kernel_status'); return { available: true, result }; } catch (error) { return { available: false, error: String(error) }; } }); console.log('[Tauri API] Available:', tauriAvailable); if (tauriAvailable.available) { console.log('[Tauri API] Kernel status:', tauriAvailable.result); } else { console.warn('[Tauri API] Not available:', tauriAvailable.error); } await takeScreenshot(page, '02-tauri-api-check'); }); }); test.describe('2. 内核状态验证', () => { test('内核初始化状态', async ({ page }) => { const kernelStatus = await page.evaluate(async () => { try { const { invoke } = await import('@tauri-apps/api/core'); const status = await invoke<{ initialized: boolean; agentCount: number; databaseUrl: string | null; defaultProvider: string | null; defaultModel: string | null; }>('kernel_status'); return { success: true, initialized: status.initialized, agentCount: status.agentCount, provider: status.defaultProvider, model: status.defaultModel, }; } catch (error) { return { success: false, error: String(error), }; } }); console.log('[Kernel Status]', kernelStatus); if (kernelStatus.success) { console.log('[Kernel] Initialized:', kernelStatus.initialized); console.log('[Kernel] Agents:', kernelStatus.agentCount); console.log('[Kernel] Provider:', kernelStatus.provider); console.log('[Kernel] Model:', kernelStatus.model); } await takeScreenshot(page, '03-kernel-status'); }); test('Agent 列表获取', async ({ page }) => { const agents = await page.evaluate(async () => { try { const { invoke } = await import('@tauri-apps/api/core'); const agentList = await invoke>('agent_list'); return { success: true, agents: agentList }; } catch (error) { return { success: false, error: String(error) }; } }); console.log('[Agent List]', agents); if (agents.success) { console.log('[Agents] Count:', agents.agents?.length); agents.agents?.forEach((agent, i) => { console.log(`[Agent ${i + 1}]`, agent); }); } await takeScreenshot(page, '04-agent-list'); }); }); test.describe('3. 连接状态', () => { test('应用应该正确显示连接状态', async ({ page }) => { await page.waitForTimeout(3000); const connectionState = await page.evaluate(() => { const statusElements = document.querySelectorAll('[class*="status"], [class*="connection"]'); return { foundElements: statusElements.length, texts: Array.from(statusElements).map((el) => el.textContent?.trim()).filter(Boolean), }; }); console.log('[Connection State]', connectionState); const pageText = await page.textContent('body'); console.log('[Page Text]', pageText?.substring(0, 500)); await takeScreenshot(page, '05-connection-state'); }); test('设置按钮应该可用', async ({ page }) => { const settingsBtn = page.locator('button').filter({ hasText: /设置|Settings|⚙/i }); if (await settingsBtn.isVisible()) { await settingsBtn.click(); await page.waitForTimeout(1000); await takeScreenshot(page, '06-settings-access'); } else { console.log('[Settings] Button not visible'); } }); }); test.describe('4. UI 布局验证', () => { test('主布局应该正确渲染', async ({ page }) => { const layout = await page.evaluate(() => { const app = document.querySelector('.h-screen'); const sidebar = document.querySelector('aside'); const main = document.querySelector('main'); return { hasApp: !!app, hasSidebar: !!sidebar, hasMain: !!main, appClasses: app?.className, }; }); console.log('[Layout]', layout); expect(layout.hasApp).toBe(true); expect(layout.hasSidebar).toBe(true); expect(layout.hasMain).toBe(true); await takeScreenshot(page, '07-layout'); }); test('侧边栏导航应该存在', async ({ page }) => { const navButtons = await page.locator('aside button').count(); console.log('[Navigation] Button count:', navButtons); expect(navButtons).toBeGreaterThan(0); await takeScreenshot(page, '08-navigation'); }); }); test.describe('5. 聊天功能 (Tauri 模式)', () => { test('聊天输入框应该可用', async ({ page }) => { const chatInput = page.locator('textarea').first(); if (await chatInput.isVisible()) { await chatInput.fill('你好,ZCLAW'); const value = await chatInput.inputValue(); console.log('[Chat Input] Value:', value); expect(value).toBe('你好,ZCLAW'); } else { console.log('[Chat Input] Not visible - may need connection'); } await takeScreenshot(page, '09-chat-input'); }); test('模型选择器应该可用', async ({ page }) => { const modelSelector = page.locator('button').filter({ hasText: /模型|Model|选择/i }); if (await modelSelector.isVisible()) { await modelSelector.click(); await page.waitForTimeout(500); console.log('[Model Selector] Clicked'); } else { console.log('[Model Selector] Not visible'); } await takeScreenshot(page, '10-model-selector'); }); }); test.describe('6. 设置页面 (Tauri 模式)', () => { test('设置页面应该能打开', async ({ page }) => { const settingsBtn = page.getByRole('button', { name: /设置|Settings/i }).first(); if (await settingsBtn.isVisible()) { await settingsBtn.click(); await page.waitForTimeout(1000); const settingsContent = await page.locator('[class*="settings"]').count(); console.log('[Settings] Content elements:', settingsContent); expect(settingsContent).toBeGreaterThan(0); } else { console.log('[Settings] Button not found'); } await takeScreenshot(page, '11-settings-page'); }); test('通用设置标签应该可见', async ({ page }) => { await page.getByRole('button', { name: /设置|Settings/i }).first().click(); await page.waitForTimeout(500); const tabs = await page.getByRole('tab').count(); console.log('[Settings Tabs] Count:', tabs); await takeScreenshot(page, '12-settings-tabs'); }); }); test.describe('7. 控制台日志检查', () => { test('应该没有严重 JavaScript 错误', async ({ page }) => { const errors: string[] = []; page.on('pageerror', (error) => { errors.push(error.message); }); page.on('console', (msg) => { if (msg.type() === 'error') { errors.push(msg.text()); } }); await page.waitForTimeout(3000); const criticalErrors = errors.filter( (e) => !e.includes('Warning') && !e.includes('DevTools') && !e.includes('extension') && !e.includes('favicon') ); console.log('[Console Errors]', criticalErrors.length); criticalErrors.forEach((e) => console.log(' -', e.substring(0, 200))); await takeScreenshot(page, '13-console-errors'); }); test('Tauri 特定日志应该存在', async ({ page }) => { const logs: string[] = []; page.on('console', (msg) => { if (msg.type() === 'log' || msg.type() === 'info') { const text = msg.text(); if (text.includes('Tauri') || text.includes('Kernel') || text.includes('tauri')) { logs.push(text); } } }); await page.waitForTimeout(2000); console.log('[Tauri Logs]', logs.length); logs.forEach((log) => console.log(' -', log.substring(0, 200))); await takeScreenshot(page, '14-tauri-logs'); }); }); }); test.describe('ZCLAW Tauri 设置页面测试', () => { test.beforeEach(async ({ page }) => { await page.goto('/'); await page.waitForLoadState('domcontentloaded'); }); test('模型与 API 设置', async ({ page }) => { await page.getByRole('button', { name: /设置|Settings/i }).first().click(); await page.waitForTimeout(1000); const modelSettings = await page.getByText(/模型|Model|API/i).count(); console.log('[Model Settings] Found:', modelSettings); await takeScreenshot(page, '15-model-settings'); }); test('安全设置', async ({ page }) => { await page.getByRole('button', { name: /设置|Settings/i }).first().click(); await page.waitForTimeout(500); const securityTab = page.getByRole('tab', { name: /安全|Security|Privacy/i }); if (await securityTab.isVisible()) { await securityTab.click(); await page.waitForTimeout(500); } await takeScreenshot(page, '16-security-settings'); }); });