import { test, expect, Page } from '@playwright/test'; /** * API Key Management Tests based on feature_list.json * Covers test cases: 18-29 */ const BASE_URL = process.env.PLAYWRIGHT_BASE_URL || 'http://127.0.0.1:3000'; async function login(page: Page) { await page.goto(`${BASE_URL}/login`); await page.fill('#username', 'testuser'); await page.fill('#password', 'Test123456'); await page.click('button[type="submit"]'); await page.waitForURL(`${BASE_URL}/`, { timeout: 10000 }); } async function openCanvasSettings(page: Page) { await page.goto(`${BASE_URL}/canvas`); // Open settings panel - adjust selector based on actual UI await page.click('[data-testid="settings-button"], button:has-text("设置"), [aria-label*="设置"], [title*="设置"]'); // Wait for settings panel to open await page.waitForSelector('[role="dialog"], .settings-panel, [data-testid="settings-panel"]', { timeout: 5000 }); } test.describe('API Key Page', () => { // Test 18: API Key 页面正常显示 (在画布设置面板中) test('should display API Key management page correctly', async ({ page }) => { await login(page); await openCanvasSettings(page); // Click on API Keys tab if exists const apiKeysTab = page.locator('button:has-text("API Key"), [role="tab"]:has-text("API Key")').first(); if (await apiKeysTab.isVisible().catch(() => false)) { await apiKeysTab.click(); } // Verify Add Key button exists await expect(page.locator('button:has-text("添加 Key")').first()).toBeVisible(); // Verify refresh button exists (has RefreshCw icon) await expect(page.locator('button svg[class*="lucide-refresh"]').or( page.locator('button:has(.animate-spin)') ).first()).toBeVisible(); }); // Test 19: 添加 API Key 表单弹出 test('should open add API Key form dialog', async ({ page }) => { await login(page); await openCanvasSettings(page); // Click on API Keys tab if exists const apiKeysTab = page.locator('button:has-text("API Key"), [role="tab"]:has-text("API Key")').first(); if (await apiKeysTab.isVisible().catch(() => false)) { await apiKeysTab.click(); } // Click Add Key button await page.click('button:has-text("添加 Key")'); // Verify dialog opens (DialogContent with role="dialog") const dialog = page.locator('[role="dialog"]'); await expect(dialog).toBeVisible(); // Verify dialog title await expect(page.locator('[role="dialog"] h2').or( page.locator('[role="dialog"] [class*="DialogTitle"]') ).first()).toContainText(/添加|添加 API Key/i); // Verify Provider dropdown exists (SelectTrigger) await expect(page.locator('[role="dialog"] button:has-text("选择")').or( page.locator('[role="dialog"] [class*="SelectTrigger"]') ).first()).toBeVisible(); // Click cancel to close (AlertDialogCancel or close button) await page.click('button:has-text("取消")'); await expect(dialog).not.toBeVisible(); }); // Test 20: 添加单 Key 提供商 API Key test('should add single key provider API Key', async ({ page }) => { await login(page); await openCanvasSettings(page); // Click on API Keys tab if exists const apiKeysTab = page.locator('button:has-text("API Key"), [role="tab"]:has-text("API Key")').first(); if (await apiKeysTab.isVisible().catch(() => false)) { await apiKeysTab.click(); } // Click Add Key await page.click('button:has-text("添加 Key")'); // Wait for dialog const dialog = page.locator('[role="dialog"]'); await expect(dialog).toBeVisible(); // Open provider select dropdown await page.click('[role="dialog"] [class*="SelectTrigger"], [role="dialog"] button:has-text("选择")'); await page.waitForTimeout(200); // Select OpenAI provider await page.click('text=OpenAI'); // Fill in name await page.fill('[role="dialog"] input[type="text"]:not([name="apiKey"]):not([name="accessKey"]):not([name="secretKey"])', 'My OpenAI Key'); // Fill in API Key (primary field) await page.fill('[role="dialog"] input[name="apiKey"]', 'sk-test1234567890abcdef'); // Click add/submit button await page.click('[role="dialog"] button[type="submit"], [role="dialog"] button:has-text("添加")'); // Verify dialog closes await expect(dialog).not.toBeVisible({ timeout: 5000 }); }); // Test 21: 添加双 Key 提供商 API Key (Kling) test('should add dual key provider API Key (Kling)', async ({ page }) => { await login(page); await openCanvasSettings(page); // Click on API Keys tab if exists const apiKeysTab = page.locator('button:has-text("API Key"), [role="tab"]:has-text("API Key")').first(); if (await apiKeysTab.isVisible().catch(() => false)) { await apiKeysTab.click(); } // Click Add Key await page.click('button:has-text("添加 Key")'); const dialog = page.locator('[role="dialog"]'); await expect(dialog).toBeVisible(); // Open provider select await page.click('[role="dialog"] [class*="SelectTrigger"]'); await page.waitForTimeout(200); // Select Kling provider (可灵 AI) await page.click('text=/可灵|Kling/i'); // Wait for dual key fields to appear await page.waitForTimeout(300); // Verify two input fields for dual key provider const inputs = page.locator('[role="dialog"] input[type="text"], [role="dialog"] input[type="password"]'); await expect(inputs).toHaveCount(3); // name + accessKey + secretKey (or similar) // Fill in details await page.fill('[role="dialog"] input[type="text"]:not([name="apiKey"]):first-of-type', 'My Kling Key'); // Fill access and secret keys (order may vary) const textInputs = page.locator('[role="dialog"] input[type="text"]'); const count = await textInputs.count(); for (let i = 0; i < count && i < 3; i++) { const input = textInputs.nth(i); const isVisible = await input.isVisible().catch(() => false); if (isVisible) { await input.fill(`test-key-${i}`); } } // Submit await page.click('[role="dialog"] button[type="submit"], [role="dialog"] button:has-text("添加")'); // Verify dialog closes await expect(dialog).not.toBeVisible({ timeout: 5000 }); }); // Test 22: API Key 脱敏显示 test('should mask API Key display', async ({ page }) => { await login(page); await openCanvasSettings(page); // Click on API Keys tab if exists const apiKeysTab = page.locator('button:has-text("API Key"), [role="tab"]:has-text("API Key")').first(); if (await apiKeysTab.isVisible().catch(() => false)) { await apiKeysTab.click(); } // Add a key first await page.click('button:has-text("添加 Key")'); const dialog = page.locator('[role="dialog"]'); await expect(dialog).toBeVisible(); await page.click('[role="dialog"] [class*="SelectTrigger"]'); await page.waitForTimeout(200); await page.click('text=OpenAI'); await page.fill('[role="dialog"] input[type="text"]:not([name="apiKey"]):first-of-type', 'Masked Key Test'); await page.fill('[role="dialog"] input[name="apiKey"]', 'sk-test1234567890abcdef'); await page.click('[role="dialog"] button[type="submit"]'); await expect(dialog).not.toBeVisible({ timeout: 5000 }); // Verify masked key display (contains ***) const maskedKey = page.locator('code:has-text("***")').first(); await expect(maskedKey).toBeVisible(); // Full key should not be visible in plain text await expect(page.locator('text=sk-test1234567890abcdef').first()).not.toBeVisible(); }); // Test 23: 编辑 API Key test('should edit API Key', async ({ page }) => { await login(page); await openCanvasSettings(page); // Click on API Keys tab if exists const apiKeysTab = page.locator('button:has-text("API Key"), [role="tab"]:has-text("API Key")').first(); if (await apiKeysTab.isVisible().catch(() => false)) { await apiKeysTab.click(); } // Wait for list to load await page.waitForTimeout(1000); // Find and click edit button (Edit2 icon) const editButton = page.locator('button svg[class*="lucide-edit"]').or( page.locator('button:has([class*="lucide-edit2"])') ).first(); if (await editButton.isVisible().catch(() => false)) { await editButton.click(); // Verify form opens const dialog = page.locator('[role="dialog"]'); await expect(dialog).toBeVisible(); // Modify name await page.fill('[role="dialog"] input[type="text"]:not([name="apiKey"]):not([name="accessKey"]):first-of-type', 'Updated Key Name'); // Save await page.click('[role="dialog"] button[type="submit"], [role="dialog"] button:has-text("保存")'); // Verify updated name appears await expect(page.locator('h3:has-text("Updated Key Name")').first()).toBeVisible({ timeout: 5000 }); } else { test.skip(); } }); // Test 24: 删除 API Key test('should delete API Key', async ({ page }) => { await login(page); await openCanvasSettings(page); // Click on API Keys tab if exists const apiKeysTab = page.locator('button:has-text("API Key"), [role="tab"]:has-text("API Key")').first(); if (await apiKeysTab.isVisible().catch(() => false)) { await apiKeysTab.click(); } // Wait for list to load await page.waitForTimeout(1000); // Find first key card const firstCard = page.locator('.card, [class*="Card"]').filter({ has: page.locator('h3') }).first(); if (await firstCard.isVisible().catch(() => false)) { // Get key name for verification const keyName = await firstCard.locator('h3').textContent(); // Click delete button (Trash2 icon) const deleteButton = firstCard.locator('button svg[class*="lucide-trash"]').or( firstCard.locator('button:has([class*="lucide-trash2"])') ).first(); if (await deleteButton.isVisible().catch(() => false)) { await deleteButton.click(); // Verify confirmation dialog appears await expect(page.locator('text=/确认删除|Confirm delete/i').first()).toBeVisible(); // Confirm delete await page.click('button:has-text("删除"), button:has-text("Confirm"), [class*="AlertDialogAction"]'); // Verify key is removed await expect(page.locator(`h3:has-text("${keyName}")`).first()).not.toBeVisible({ timeout: 5000 }); } else { test.skip(); } } else { test.skip(); } }); // Test 25: 复制 API Key test('should copy API Key to clipboard', async ({ page }) => { await login(page); await openCanvasSettings(page); // Click on API Keys tab if exists const apiKeysTab = page.locator('button:has-text("API Key"), [role="tab"]:has-text("API Key")').first(); if (await apiKeysTab.isVisible().catch(() => false)) { await apiKeysTab.click(); } // Wait for list to load await page.waitForTimeout(1000); // Find copy button (Copy icon) const copyButton = page.locator('button svg[class*="lucide-copy"]').first(); if (await copyButton.isVisible().catch(() => false)) { await copyButton.click(); // Verify copy success - icon changes to Check await expect(page.locator('button svg[class*="lucide-check"]').or( page.locator('svg[class*="text-green"]').or( page.locator('svg.text-green-500') ) ).first()).toBeVisible({ timeout: 3000 }); } else { test.skip(); } }); // Test 26: 刷新 API Key 列表 test('should refresh API Key list', async ({ page }) => { await login(page); await openCanvasSettings(page); // Click on API Keys tab if exists const apiKeysTab = page.locator('button:has-text("API Key"), [role="tab"]:has-text("API Key")').first(); if (await apiKeysTab.isVisible().catch(() => false)) { await apiKeysTab.click(); } // Click refresh button (button with RefreshCw icon, not the "添加 Key" button) const refreshButton = page.locator('button').filter({ has: page.locator('svg[class*="lucide-refresh"]') }).first(); if (await refreshButton.isVisible().catch(() => false)) { await refreshButton.click(); // Verify loading state (spinning icon) await expect(page.locator('svg.animate-spin').first()).toBeVisible({ timeout: 2000 }); } else { test.skip(); } }); // Test 27: API Key 表单验证 test('should validate API Key form', async ({ page }) => { await login(page); await openCanvasSettings(page); // Click on API Keys tab if exists const apiKeysTab = page.locator('button:has-text("API Key"), [role="tab"]:has-text("API Key")').first(); if (await apiKeysTab.isVisible().catch(() => false)) { await apiKeysTab.click(); } // Click Add Key await page.click('button:has-text("添加 Key")'); const dialog = page.locator('[role="dialog"]'); await expect(dialog).toBeVisible(); // Try to submit without selecting provider await page.click('[role="dialog"] button[type="submit"], [role="dialog"] button:has-text("添加")'); // Verify validation error or dialog still open await expect(dialog).toBeVisible(); // Close dialog await page.click('button:has-text("取消")'); await expect(dialog).not.toBeVisible(); }); // Test 28: 取消添加 API Key test('should cancel adding API Key', async ({ page }) => { await login(page); await openCanvasSettings(page); // Click on API Keys tab if exists const apiKeysTab = page.locator('button:has-text("API Key"), [role="tab"]:has-text("API Key")').first(); if (await apiKeysTab.isVisible().catch(() => false)) { await apiKeysTab.click(); } // Count initial cards const initialCount = await page.locator('.card, [class*="Card"]').filter({ has: page.locator('h3') }).count(); // Click Add Key await page.click('button:has-text("添加 Key")'); const dialog = page.locator('[role="dialog"]'); await expect(dialog).toBeVisible(); // Select provider and fill some data await page.click('[role="dialog"] [class*="SelectTrigger"]'); await page.waitForTimeout(200); await page.click('text=OpenAI'); await page.fill('[role="dialog"] input[name="apiKey"]', 'sk-test-key'); // Click cancel await page.click('button:has-text("取消")'); // Verify dialog closes await expect(dialog).not.toBeVisible(); }); // Test 29: 启用/禁用 API Key test('should toggle API Key enabled state', async ({ page }) => { await login(page); await openCanvasSettings(page); // Click on API Keys tab if exists const apiKeysTab = page.locator('button:has-text("API Key"), [role="tab"]:has-text("API Key")').first(); if (await apiKeysTab.isVisible().catch(() => false)) { await apiKeysTab.click(); } // Wait for list to load await page.waitForTimeout(1000); // Find first card with checkbox const firstCard = page.locator('.card, [class*="Card"]').filter({ has: page.locator('h3') }).first(); const checkbox = firstCard.locator('[role="checkbox"], input[type="checkbox"]').first(); if (await checkbox.isVisible().catch(() => false)) { // Get initial state const initialChecked = await checkbox.isChecked().catch(() => true); // Click checkbox await checkbox.click(); await page.waitForTimeout(500); // Verify card opacity changes when disabled if (!initialChecked) { await expect(firstCard).toHaveClass(/opacity-60/); } } else { test.skip(); } }); });