import { test, expect } from '@playwright/test'; /** * ZCLAW E2E Tests * * These tests verify the UI layer of the application. * For Tauri-specific tests, use Tauri's built-in testing tools. * * Prerequisites: * - Dev server running: `pnpm dev` (or `pnpm start:dev`) * - Or let Playwright start it via webServer config */ test.describe('ZCLAW App — Smoke Tests', () => { test('should load the main page and render the app shell', async ({ page }) => { await page.goto('/'); // App should render — look for the root container or any ZCLAW branding const root = page.locator('#root'); await expect(root).toBeAttached({ timeout: 10_000 }); // Page title should contain ZCLAW await expect(page).toHaveTitle(/ZCLAW/i); }); test('should show connection status indicator', async ({ page }) => { await page.goto('/'); // Wait for the app to fully render await page.waitForTimeout(1000); // Connection status may be shown in sidebar or header // Check for either the data-testid or a visible status text const statusElement = page.locator('[data-testid="connection-status"]'); const isVisible = await statusElement.isVisible().catch(() => false); if (!isVisible) { // Fallback: check if any status-related text is visible const bodyText = await page.locator('body').textContent(); // The app should have loaded without errors expect(bodyText).toBeTruthy(); } }); }); test.describe('Settings Navigation', () => { test('should navigate to settings page', async ({ page }) => { await page.goto('/'); // Look for settings button/icon — may be in sidebar or header const settingsButton = page.locator( '[data-testid="settings-button"], [data-testid="settings-tab"], button:has-text("设置"), a:has-text("设置"), [aria-label*="设置"], [aria-label*="Settings"]' ); // If settings navigation exists, click it const count = await settingsButton.count(); if (count > 0) { await settingsButton.first().click(); // Settings page should have loaded — check for common setting labels await page.waitForTimeout(500); const settingsContent = page.locator( 'text=通用, text=General, text=API, text=模型, [data-testid="settings-content"]' ); // At least one settings element should be visible await expect(settingsContent.first()).toBeVisible({ timeout: 5_000 }); } }); }); test.describe('Chat Interface', () => { test('should display chat input area', async ({ page }) => { await page.goto('/'); // Chat input may be a textarea or contenteditable div const chatInput = page.locator( '[data-testid="chat-input"], textarea[placeholder*="消息"], textarea[placeholder*="message"], [contenteditable="true"], input[placeholder*="消息"]' ); // Chat input should exist in the DOM (may not be visible if on different tab) const count = await chatInput.count(); // If we find chat inputs, at least one should be visible if (count > 0) { await expect(chatInput.first()).toBeVisible({ timeout: 5_000 }); } }); }); test.describe('Error Resilience', () => { test('should not show uncaught errors on load', async ({ page }) => { // Collect console errors const errors: string[] = []; page.on('console', (msg) => { if (msg.type() === 'error') { errors.push(msg.text()); } }); await page.goto('/'); await page.waitForTimeout(2000); // Filter out known benign errors (e.g., network errors when backend is not running) const criticalErrors = errors.filter( (e) => !e.includes('net::ERR_CONNECTION_REFUSED') && !e.includes('Failed to fetch') && !e.includes('WebSocket') ); // No critical JS errors should occur on page load expect(criticalErrors).toEqual([]); }); });