mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-13 02:41:50 +08:00
feat: Implement dynamic test-fix execution phase with adaptive task generation
- Added Phase 2: Test-Cycle Execution documentation outlining the process for dynamic test-fix execution, including agent roles, core responsibilities, intelligent strategy engine, and progressive testing. - Introduced new PowerShell scripts for analyzing TypeScript errors, focusing on error categorization and reporting. - Created end-to-end tests for the Help Page, ensuring content visibility, documentation navigation, internationalization support, and accessibility compliance.
This commit is contained in:
@@ -495,4 +495,100 @@ test.describe('[API Settings] - CLI Provider Configuration Tests', () => {
|
||||
monitoring.assertClean({ ignoreAPIPatterns: ['/api/settings/cli'], allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.31 - API Error - 401 Unauthorized', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API to return 401
|
||||
await page.route('**/api/settings/cli', (route) => {
|
||||
route.fulfill({
|
||||
status: 401,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ error: 'Unauthorized', message: 'Authentication required' }),
|
||||
});
|
||||
});
|
||||
|
||||
await page.reload({ waitUntil: 'networkidle' as const });
|
||||
|
||||
// Verify auth error or redirect
|
||||
const authError = page.getByText(/unauthorized|not authenticated|未经授权/i);
|
||||
await page.unroute('**/api/settings/cli');
|
||||
const hasError = await authError.isVisible().catch(() => false);
|
||||
expect(hasError).toBe(true);
|
||||
|
||||
monitoring.assertClean({ ignoreAPIPatterns: ['/api/settings/cli'], allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.32 - API Error - 403 Forbidden', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API to return 403
|
||||
await page.route('**/api/settings/cli', (route) => {
|
||||
route.fulfill({
|
||||
status: 403,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ error: 'Forbidden', message: 'Access denied' }),
|
||||
});
|
||||
});
|
||||
|
||||
await page.reload({ waitUntil: 'networkidle' as const });
|
||||
|
||||
// Verify forbidden message
|
||||
const errorMessage = page.getByText(/forbidden|not allowed|禁止访问/i);
|
||||
await page.unroute('**/api/settings/cli');
|
||||
const hasError = await errorMessage.isVisible().catch(() => false);
|
||||
expect(hasError).toBe(true);
|
||||
|
||||
monitoring.assertClean({ ignoreAPIPatterns: ['/api/settings/cli'], allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.33 - API Error - 404 Not Found', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API to return 404
|
||||
await page.route('**/api/settings/cli', (route) => {
|
||||
route.fulfill({
|
||||
status: 404,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ error: 'Not Found', message: 'Settings not found' }),
|
||||
});
|
||||
});
|
||||
|
||||
await page.reload({ waitUntil: 'networkidle' as const });
|
||||
|
||||
// Verify not found message
|
||||
const errorMessage = page.getByText(/not found|doesn't exist|未找到/i);
|
||||
await page.unroute('**/api/settings/cli');
|
||||
const hasError = await errorMessage.isVisible().catch(() => false);
|
||||
expect(hasError).toBe(true);
|
||||
|
||||
monitoring.assertClean({ ignoreAPIPatterns: ['/api/settings/cli'], allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.34 - API Error - 500 Internal Server Error', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API to return 500
|
||||
await page.route('**/api/settings/cli', (route) => {
|
||||
route.fulfill({
|
||||
status: 500,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ error: 'Internal Server Error' }),
|
||||
});
|
||||
});
|
||||
|
||||
await page.reload({ waitUntil: 'networkidle' as const });
|
||||
|
||||
// Verify server error message
|
||||
const errorMessage = page.getByText(/server error|try again|服务器错误/i);
|
||||
await page.unroute('**/api/settings/cli');
|
||||
const hasError = await errorMessage.isVisible().catch(() => false);
|
||||
expect(hasError).toBe(true);
|
||||
|
||||
monitoring.assertClean({ ignoreAPIPatterns: ['/api/settings/cli'], allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { setupEnhancedMonitoring } from './helpers/i18n-helpers';
|
||||
|
||||
test.describe.skip('[Commands] - Commands Management Tests', () => {
|
||||
test.describe('[Commands] - Commands Management Tests', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/', { waitUntil: 'networkidle' as const });
|
||||
});
|
||||
@@ -341,4 +341,151 @@ test.describe.skip('[Commands] - Commands Management Tests', () => {
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
// ========================================
|
||||
// API Error Scenarios
|
||||
// ========================================
|
||||
|
||||
test('L3.11 - API Error - 400 Bad Request', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API to return 400
|
||||
await page.route('**/api/commands/**', (route) => {
|
||||
route.fulfill({
|
||||
status: 400,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ error: 'Bad Request', message: 'Invalid command data' }),
|
||||
});
|
||||
});
|
||||
|
||||
await page.goto('/commands', { waitUntil: 'networkidle' as const });
|
||||
|
||||
// Verify error message is displayed
|
||||
const errorMessage = page.getByText(/invalid|bad request|输入无效/i);
|
||||
await page.unroute('**/api/commands/**');
|
||||
const hasError = await errorMessage.isVisible().catch(() => false);
|
||||
expect(hasError).toBe(true);
|
||||
|
||||
monitoring.assertClean({ ignoreAPIPatterns: ['/api/commands'], allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.12 - API Error - 401 Unauthorized', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API to return 401
|
||||
await page.route('**/api/commands', (route) => {
|
||||
route.fulfill({
|
||||
status: 401,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ error: 'Unauthorized', message: 'Authentication required' }),
|
||||
});
|
||||
});
|
||||
|
||||
await page.goto('/commands', { waitUntil: 'networkidle' as const });
|
||||
|
||||
// Verify auth error
|
||||
const authError = page.getByText(/unauthorized|not authenticated|未经授权/i);
|
||||
await page.unroute('**/api/commands');
|
||||
const hasError = await authError.isVisible().catch(() => false);
|
||||
expect(hasError).toBe(true);
|
||||
|
||||
monitoring.assertClean({ ignoreAPIPatterns: ['/api/commands'], allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.13 - API Error - 403 Forbidden', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API to return 403
|
||||
await page.route('**/api/commands', (route) => {
|
||||
route.fulfill({
|
||||
status: 403,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ error: 'Forbidden', message: 'Access denied' }),
|
||||
});
|
||||
});
|
||||
|
||||
await page.goto('/commands', { waitUntil: 'networkidle' as const });
|
||||
|
||||
// Verify forbidden message
|
||||
const errorMessage = page.getByText(/forbidden|not allowed|禁止访问/i);
|
||||
await page.unroute('**/api/commands');
|
||||
const hasError = await errorMessage.isVisible().catch(() => false);
|
||||
expect(hasError).toBe(true);
|
||||
|
||||
monitoring.assertClean({ ignoreAPIPatterns: ['/api/commands'], allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.14 - API Error - 404 Not Found', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API to return 404
|
||||
await page.route('**/api/commands/nonexistent', (route) => {
|
||||
route.fulfill({
|
||||
status: 404,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ error: 'Not Found', message: 'Command not found' }),
|
||||
});
|
||||
});
|
||||
|
||||
// Try to access a non-existent command
|
||||
await page.goto('/commands/nonexistent-command-id', { waitUntil: 'networkidle' as const });
|
||||
|
||||
// Verify not found message
|
||||
const errorMessage = page.getByText(/not found|doesn't exist|未找到/i);
|
||||
await page.unroute('**/api/commands/nonexistent');
|
||||
const hasError = await errorMessage.isVisible().catch(() => false);
|
||||
expect(hasError).toBe(true);
|
||||
|
||||
monitoring.assertClean({ ignoreAPIPatterns: ['/api/commands'], allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.15 - API Error - 500 Internal Server Error', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API to return 500
|
||||
await page.route('**/api/commands', (route) => {
|
||||
route.fulfill({
|
||||
status: 500,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ error: 'Internal Server Error' }),
|
||||
});
|
||||
});
|
||||
|
||||
await page.goto('/commands', { waitUntil: 'networkidle' as const });
|
||||
|
||||
// Verify server error message
|
||||
const errorMessage = page.getByText(/server error|try again|服务器错误/i);
|
||||
await page.unroute('**/api/commands');
|
||||
const hasError = await errorMessage.isVisible().catch(() => false);
|
||||
expect(hasError).toBe(true);
|
||||
|
||||
monitoring.assertClean({ ignoreAPIPatterns: ['/api/commands'], allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.16 - API Error - Network Timeout', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API timeout
|
||||
await page.route('**/api/commands', () => {
|
||||
// Never fulfill - simulate timeout
|
||||
});
|
||||
|
||||
await page.goto('/commands', { waitUntil: 'networkidle' as const });
|
||||
|
||||
// Wait for timeout handling
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
// Verify timeout message
|
||||
const timeoutMessage = page.getByText(/timeout|network error|unavailable|网络超时/i);
|
||||
await page.unroute('**/api/commands');
|
||||
const hasTimeout = await timeoutMessage.isVisible().catch(() => false);
|
||||
|
||||
monitoring.assertClean({ ignoreAPIPatterns: ['/api/commands'], allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
});
|
||||
|
||||
174
ccw/frontend/tests/e2e/help.spec.ts
Normal file
174
ccw/frontend/tests/e2e/help.spec.ts
Normal file
@@ -0,0 +1,174 @@
|
||||
// ========================================
|
||||
// E2E Tests: Help Page
|
||||
// ========================================
|
||||
// End-to-end tests for help documentation page
|
||||
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { setupEnhancedMonitoring, switchLanguageAndVerify } from './helpers/i18n-helpers';
|
||||
|
||||
test.describe('[Help] - Help Page Tests', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/help', { waitUntil: 'networkidle' as const });
|
||||
});
|
||||
|
||||
test('L3.50 - should display help documentation content', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Look for help page content
|
||||
const helpContent = page.getByTestId('help-content').or(
|
||||
page.locator('.help-documentation')
|
||||
).or(
|
||||
page.locator('main')
|
||||
);
|
||||
|
||||
await expect(helpContent).toBeVisible();
|
||||
|
||||
// Verify page title is present
|
||||
const pageTitle = page.getByRole('heading', { name: /help|帮助/i }).or(
|
||||
page.locator('h1')
|
||||
);
|
||||
|
||||
const hasTitle = await pageTitle.isVisible().catch(() => false);
|
||||
expect(hasTitle).toBe(true);
|
||||
|
||||
// Verify help sections are displayed
|
||||
const helpSections = page.locator('a[href*="/docs"], a[href^="/docs"]').or(
|
||||
page.locator('[data-testid*="help"]')
|
||||
);
|
||||
|
||||
const sectionCount = await helpSections.count();
|
||||
expect(sectionCount).toBeGreaterThan(0);
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.51 - should display documentation navigation links', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Look for documentation links
|
||||
const docLinks = page.locator('a[href*="/docs/"], a[href^="/docs"]').or(
|
||||
page.locator('[data-testid="docs-link"]')
|
||||
);
|
||||
|
||||
const linkCount = await docLinks.count();
|
||||
expect(linkCount).toBeGreaterThan(0);
|
||||
|
||||
// Verify links have proper structure
|
||||
for (let i = 0; i < Math.min(linkCount, 3); i++) {
|
||||
const link = docLinks.nth(i);
|
||||
await expect(link).toHaveAttribute('href');
|
||||
}
|
||||
|
||||
// Look for "Full Documentation" button/link
|
||||
const fullDocsLink = page.getByRole('link', { name: /full.*docs|documentation/i }).or(
|
||||
page.locator('a[href="/docs"]')
|
||||
);
|
||||
|
||||
const hasFullDocs = await fullDocsLink.isVisible().catch(() => false);
|
||||
expect(hasFullDocs).toBe(true);
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.52 - should support i18n (English/Chinese switching)', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Get language switcher
|
||||
const languageSwitcher = page.getByRole('combobox', { name: /select language|language/i }).first();
|
||||
|
||||
const hasLanguageSwitcher = await languageSwitcher.isVisible().catch(() => false);
|
||||
|
||||
if (hasLanguageSwitcher) {
|
||||
// Switch to Chinese
|
||||
await switchLanguageAndVerify(page, 'zh', languageSwitcher);
|
||||
|
||||
// Verify help content is in Chinese
|
||||
const pageContent = await page.content();
|
||||
const hasChinese = /[\u4e00-\u9fa5]/.test(pageContent);
|
||||
expect(hasChinese).toBe(true);
|
||||
|
||||
// Switch back to English
|
||||
await switchLanguageAndVerify(page, 'en', languageSwitcher);
|
||||
|
||||
// Verify help content is in English
|
||||
const englishContent = await page.content();
|
||||
const hasEnglish = /[a-zA-Z]{5,}/.test(englishContent);
|
||||
expect(hasEnglish).toBe(true);
|
||||
}
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.53 - should display quick links and overview cards', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Look for quick link cards
|
||||
const quickLinkCards = page.locator('a[href*="/docs"], a[href="/sessions"]').or(
|
||||
page.locator('[data-testid*="card"], .card')
|
||||
);
|
||||
|
||||
const cardCount = await quickLinkCards.count();
|
||||
expect(cardCount).toBeGreaterThan(0);
|
||||
|
||||
// Verify documentation overview cards exist
|
||||
const overviewCards = page.locator('a[href*="/docs/commands"], a[href*="/docs/workflows"], a[href*="/docs/overview"]').or(
|
||||
page.locator('[data-testid*="overview"]')
|
||||
);
|
||||
|
||||
const overviewCount = await overviewCards.count();
|
||||
expect(overviewCount).toBeGreaterThan(0);
|
||||
|
||||
// Look for specific help sections (Getting Started, Orchestrator Guide, Commands)
|
||||
const gettingStartedLink = page.getByRole('link', { name: /getting.*started|入门/i });
|
||||
const orchestratorGuideLink = page.getByRole('link', { name: /orchestrator.*guide|编排指南/i });
|
||||
const commandsLink = page.getByRole('link', { name: /commands|命令/i });
|
||||
|
||||
const hasGettingStarted = await gettingStartedLink.isVisible().catch(() => false);
|
||||
const hasOrchestratorGuide = await orchestratorGuideLink.isVisible().catch(() => false);
|
||||
const hasCommands = await commandsLink.isVisible().catch(() => false);
|
||||
|
||||
// At least one help section should be visible
|
||||
expect(hasGettingStarted || hasOrchestratorGuide || hasCommands).toBe(true);
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.54 - should ensure basic accessibility and page structure', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Verify main content area exists
|
||||
const mainContent = page.locator('main').or(
|
||||
page.locator('#main-content')
|
||||
).or(
|
||||
page.locator('[role="main"]')
|
||||
);
|
||||
|
||||
await expect(mainContent).toBeVisible();
|
||||
|
||||
// Verify page has proper heading structure
|
||||
const h1 = page.locator('h1');
|
||||
const hasH1 = await h1.count();
|
||||
expect(hasH1).toBeGreaterThanOrEqual(1);
|
||||
|
||||
// Look for skip to main content link (accessibility feature)
|
||||
const skipLink = page.getByRole('link', { name: /skip to main content|跳转到主要内容/i });
|
||||
|
||||
const hasSkipLink = await skipLink.isVisible().catch(() => false);
|
||||
// Skip link may not be visible by default, so we don't fail if missing
|
||||
if (hasSkipLink) {
|
||||
await expect(skipLink).toHaveAttribute('href');
|
||||
}
|
||||
|
||||
// Verify focus management on interactive elements
|
||||
const interactiveElements = page.locator('button, a[href], [tabindex]:not([tabindex="-1"])');
|
||||
const interactiveCount = await interactiveElements.count();
|
||||
expect(interactiveCount).toBeGreaterThan(0);
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
});
|
||||
@@ -46,8 +46,8 @@ export async function waitForDashboardLoad(page: Page, timeout = 30000): Promise
|
||||
await expect(statsCards.first()).toBeVisible({ timeout });
|
||||
}
|
||||
|
||||
// Small delay to ensure all animations complete
|
||||
await page.waitForTimeout(500);
|
||||
// Wait for animations to complete - use waitForLoadState instead of timeout
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -146,16 +146,14 @@ export async function simulateDragDrop(
|
||||
// Perform drag-drop
|
||||
await page.mouse.move(startX, startY);
|
||||
await page.mouse.down();
|
||||
await page.waitForTimeout(100); // Small delay to register drag start
|
||||
|
||||
// Move to target position
|
||||
await page.mouse.move(targetX, targetY, { steps: 10 });
|
||||
await page.waitForTimeout(100); // Small delay before release
|
||||
|
||||
await page.mouse.up();
|
||||
|
||||
// Wait for layout to settle
|
||||
await page.waitForTimeout(500);
|
||||
// Wait for layout to settle - use waitForLoadState instead of timeout
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -209,7 +207,11 @@ export async function toggleNavGroup(page: Page, groupName: string): Promise<voi
|
||||
await expect(groupTrigger).toBeVisible();
|
||||
|
||||
await groupTrigger.click();
|
||||
await page.waitForTimeout(300); // Wait for accordion animation
|
||||
// Wait for accordion animation - use explicit wait
|
||||
await page.waitForFunction(() => {
|
||||
const group = document.querySelector('[aria-expanded]');
|
||||
return group !== null;
|
||||
}, { timeout: 3000 });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -261,7 +263,8 @@ export async function simulateTickerMessage(
|
||||
}
|
||||
}, message);
|
||||
|
||||
await page.waitForTimeout(100); // Wait for message to be processed
|
||||
// Wait for message to be processed - use explicit wait
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -296,7 +299,11 @@ export async function verifyChartTooltip(
|
||||
|
||||
// Hover over chart
|
||||
await chartElement.hover({ position: { x: 50, y: 50 } });
|
||||
await page.waitForTimeout(200); // Wait for tooltip animation
|
||||
// Wait for tooltip animation - use explicit wait
|
||||
await page.waitForFunction(() => {
|
||||
const tooltip = document.querySelector('.recharts-tooltip-wrapper, [role="tooltip"]');
|
||||
return tooltip !== null && window.getComputedStyle(tooltip).opacity !== '0';
|
||||
}, { timeout: 3000 }).catch(() => true); // Don't fail if tooltip doesn't appear
|
||||
|
||||
// Check if tooltip is visible
|
||||
const tooltip = page.locator('.recharts-tooltip-wrapper').or(
|
||||
@@ -369,7 +376,8 @@ export async function verifyResponsiveLayout(
|
||||
};
|
||||
|
||||
await page.setViewportSize(viewportSizes[breakpoint]);
|
||||
await page.waitForTimeout(300); // Wait for layout reflow
|
||||
// Wait for layout reflow - use explicit wait
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
|
||||
// Verify grid layout adjusts
|
||||
const grid = page.getByTestId('dashboard-grid-container');
|
||||
|
||||
@@ -29,9 +29,12 @@ export async function switchLanguageAndVerify(
|
||||
await expectToBeVisible(targetOption);
|
||||
await targetOption.click();
|
||||
|
||||
// Wait for language change to take effect
|
||||
// Note: Using hardcoded wait as per existing pattern - should be improved in future
|
||||
await page.waitForTimeout(500);
|
||||
// Wait for HTML lang attribute update (explicit wait instead of timeout)
|
||||
await page.waitForFunction(
|
||||
(expectedLocale) => document.documentElement.lang === expectedLocale,
|
||||
locale,
|
||||
{ timeout: 5000 }
|
||||
);
|
||||
|
||||
// Verify the switcher text content is updated
|
||||
const expectedText = locale === 'zh' ? '中文' : 'English';
|
||||
|
||||
@@ -584,4 +584,161 @@ test.describe('[MCP] - MCP Management Tests', () => {
|
||||
monitoring.assertClean({ ignoreAPIPatterns: ['/api/mcp'], allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
// ========================================
|
||||
// API Error Scenarios
|
||||
// ========================================
|
||||
|
||||
test('L3.15 - API Error - 400 Bad Request', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API to return 400
|
||||
await page.route('**/api/mcp', (route) => {
|
||||
route.fulfill({
|
||||
status: 400,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ error: 'Bad Request', message: 'Invalid MCP server data' }),
|
||||
});
|
||||
});
|
||||
|
||||
await page.goto('/settings/mcp', { waitUntil: 'networkidle' as const });
|
||||
|
||||
// Try to create a server
|
||||
const createButton = page.getByRole('button', { name: /create|new|add/i });
|
||||
const hasCreateButton = await createButton.isVisible().catch(() => false);
|
||||
|
||||
if (hasCreateButton) {
|
||||
await createButton.click();
|
||||
const submitButton = page.getByRole('button', { name: /create|save/i });
|
||||
await submitButton.click();
|
||||
|
||||
// Verify error message
|
||||
const errorMessage = page.getByText(/invalid|bad request|输入无效/i);
|
||||
await page.unroute('**/api/mcp');
|
||||
const hasError = await errorMessage.isVisible().catch(() => false);
|
||||
expect(hasError).toBe(true);
|
||||
}
|
||||
|
||||
monitoring.assertClean({ ignoreAPIPatterns: ['/api/mcp'], allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.16 - API Error - 401 Unauthorized', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API to return 401
|
||||
await page.route('**/api/mcp', (route) => {
|
||||
route.fulfill({
|
||||
status: 401,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ error: 'Unauthorized', message: 'Authentication required' }),
|
||||
});
|
||||
});
|
||||
|
||||
await page.goto('/settings/mcp', { waitUntil: 'networkidle' as const });
|
||||
|
||||
// Verify auth error
|
||||
const authError = page.getByText(/unauthorized|not authenticated|未经授权/i);
|
||||
await page.unroute('**/api/mcp');
|
||||
const hasError = await authError.isVisible().catch(() => false);
|
||||
expect(hasError).toBe(true);
|
||||
|
||||
monitoring.assertClean({ ignoreAPIPatterns: ['/api/mcp'], allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.17 - API Error - 403 Forbidden', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API to return 403
|
||||
await page.route('**/api/mcp', (route) => {
|
||||
route.fulfill({
|
||||
status: 403,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ error: 'Forbidden', message: 'Access denied' }),
|
||||
});
|
||||
});
|
||||
|
||||
await page.goto('/settings/mcp', { waitUntil: 'networkidle' as const });
|
||||
|
||||
// Verify forbidden message
|
||||
const errorMessage = page.getByText(/forbidden|not allowed|禁止访问/i);
|
||||
await page.unroute('**/api/mcp');
|
||||
const hasError = await errorMessage.isVisible().catch(() => false);
|
||||
expect(hasError).toBe(true);
|
||||
|
||||
monitoring.assertClean({ ignoreAPIPatterns: ['/api/mcp'], allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.18 - API Error - 404 Not Found', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API to return 404
|
||||
await page.route('**/api/mcp/servers/nonexistent', (route) => {
|
||||
route.fulfill({
|
||||
status: 404,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ error: 'Not Found', message: 'MCP server not found' }),
|
||||
});
|
||||
});
|
||||
|
||||
// Try to access a non-existent server
|
||||
await page.goto('/settings/mcp/servers/nonexistent-server-id', { waitUntil: 'networkidle' as const });
|
||||
|
||||
// Verify not found message
|
||||
const errorMessage = page.getByText(/not found|doesn't exist|未找到/i);
|
||||
await page.unroute('**/api/mcp/servers/nonexistent');
|
||||
const hasError = await errorMessage.isVisible().catch(() => false);
|
||||
expect(hasError).toBe(true);
|
||||
|
||||
monitoring.assertClean({ ignoreAPIPatterns: ['/api/mcp'], allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.19 - API Error - 500 Internal Server Error', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API to return 500
|
||||
await page.route('**/api/mcp', (route) => {
|
||||
route.fulfill({
|
||||
status: 500,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ error: 'Internal Server Error' }),
|
||||
});
|
||||
});
|
||||
|
||||
await page.goto('/settings/mcp', { waitUntil: 'networkidle' as const });
|
||||
|
||||
// Verify server error message
|
||||
const errorMessage = page.getByText(/server error|try again|服务器错误/i);
|
||||
await page.unroute('**/api/mcp');
|
||||
const hasError = await errorMessage.isVisible().catch(() => false);
|
||||
expect(hasError).toBe(true);
|
||||
|
||||
monitoring.assertClean({ ignoreAPIPatterns: ['/api/mcp'], allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.20 - API Error - Network Timeout', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API timeout
|
||||
await page.route('**/api/mcp', () => {
|
||||
// Never fulfill - simulate timeout
|
||||
});
|
||||
|
||||
await page.goto('/settings/mcp', { waitUntil: 'networkidle' as const });
|
||||
|
||||
// Wait for timeout handling
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
// Verify timeout message
|
||||
const timeoutMessage = page.getByText(/timeout|network error|unavailable|网络超时/i);
|
||||
await page.unroute('**/api/mcp');
|
||||
const hasTimeout = await timeoutMessage.isVisible().catch(() => false);
|
||||
|
||||
monitoring.assertClean({ ignoreAPIPatterns: ['/api/mcp'], allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -403,4 +403,161 @@ test.describe('[Memory] - Memory Management Tests', () => {
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
// ========================================
|
||||
// API Error Scenarios
|
||||
// ========================================
|
||||
|
||||
test('L3.11 - API Error - 400 Bad Request', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API to return 400
|
||||
await page.route('**/api/memory', (route) => {
|
||||
route.fulfill({
|
||||
status: 400,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ error: 'Bad Request', message: 'Invalid memory data' }),
|
||||
});
|
||||
});
|
||||
|
||||
await page.goto('/memory', { waitUntil: 'networkidle' as const });
|
||||
|
||||
// Try to create a memory
|
||||
const createButton = page.getByRole('button', { name: /create|new|add/i });
|
||||
const hasCreateButton = await createButton.isVisible().catch(() => false);
|
||||
|
||||
if (hasCreateButton) {
|
||||
await createButton.click();
|
||||
const submitButton = page.getByRole('button', { name: /create|save/i });
|
||||
await submitButton.click();
|
||||
|
||||
// Verify error message
|
||||
const errorMessage = page.getByText(/invalid|bad request|输入无效/i);
|
||||
await page.unroute('**/api/memory');
|
||||
const hasError = await errorMessage.isVisible().catch(() => false);
|
||||
expect(hasError).toBe(true);
|
||||
}
|
||||
|
||||
monitoring.assertClean({ ignoreAPIPatterns: ['/api/memory'], allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.12 - API Error - 401 Unauthorized', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API to return 401
|
||||
await page.route('**/api/memory', (route) => {
|
||||
route.fulfill({
|
||||
status: 401,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ error: 'Unauthorized', message: 'Authentication required' }),
|
||||
});
|
||||
});
|
||||
|
||||
await page.goto('/memory', { waitUntil: 'networkidle' as const });
|
||||
|
||||
// Verify auth error
|
||||
const authError = page.getByText(/unauthorized|not authenticated|未经授权/i);
|
||||
await page.unroute('**/api/memory');
|
||||
const hasError = await authError.isVisible().catch(() => false);
|
||||
expect(hasError).toBe(true);
|
||||
|
||||
monitoring.assertClean({ ignoreAPIPatterns: ['/api/memory'], allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.13 - API Error - 403 Forbidden', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API to return 403
|
||||
await page.route('**/api/memory', (route) => {
|
||||
route.fulfill({
|
||||
status: 403,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ error: 'Forbidden', message: 'Access denied' }),
|
||||
});
|
||||
});
|
||||
|
||||
await page.goto('/memory', { waitUntil: 'networkidle' as const });
|
||||
|
||||
// Verify forbidden message
|
||||
const errorMessage = page.getByText(/forbidden|not allowed|禁止访问/i);
|
||||
await page.unroute('**/api/memory');
|
||||
const hasError = await errorMessage.isVisible().catch(() => false);
|
||||
expect(hasError).toBe(true);
|
||||
|
||||
monitoring.assertClean({ ignoreAPIPatterns: ['/api/memory'], allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.14 - API Error - 404 Not Found', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API to return 404
|
||||
await page.route('**/api/memory/nonexistent', (route) => {
|
||||
route.fulfill({
|
||||
status: 404,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ error: 'Not Found', message: 'Memory not found' }),
|
||||
});
|
||||
});
|
||||
|
||||
// Try to access a non-existent memory
|
||||
await page.goto('/memory/nonexistent-memory-id', { waitUntil: 'networkidle' as const });
|
||||
|
||||
// Verify not found message
|
||||
const errorMessage = page.getByText(/not found|doesn't exist|未找到/i);
|
||||
await page.unroute('**/api/memory/nonexistent');
|
||||
const hasError = await errorMessage.isVisible().catch(() => false);
|
||||
expect(hasError).toBe(true);
|
||||
|
||||
monitoring.assertClean({ ignoreAPIPatterns: ['/api/memory'], allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.15 - API Error - 500 Internal Server Error', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API to return 500
|
||||
await page.route('**/api/memory', (route) => {
|
||||
route.fulfill({
|
||||
status: 500,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ error: 'Internal Server Error' }),
|
||||
});
|
||||
});
|
||||
|
||||
await page.goto('/memory', { waitUntil: 'networkidle' as const });
|
||||
|
||||
// Verify server error message
|
||||
const errorMessage = page.getByText(/server error|try again|服务器错误/i);
|
||||
await page.unroute('**/api/memory');
|
||||
const hasError = await errorMessage.isVisible().catch(() => false);
|
||||
expect(hasError).toBe(true);
|
||||
|
||||
monitoring.assertClean({ ignoreAPIPatterns: ['/api/memory'], allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.16 - API Error - Network Timeout', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API timeout
|
||||
await page.route('**/api/memory', () => {
|
||||
// Never fulfill - simulate timeout
|
||||
});
|
||||
|
||||
await page.goto('/memory', { waitUntil: 'networkidle' as const });
|
||||
|
||||
// Wait for timeout handling
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
// Verify timeout message
|
||||
const timeoutMessage = page.getByText(/timeout|network error|unavailable|网络超时/i);
|
||||
await page.unroute('**/api/memory');
|
||||
const hasTimeout = await timeoutMessage.isVisible().catch(() => false);
|
||||
|
||||
monitoring.assertClean({ ignoreAPIPatterns: ['/api/memory'], allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -594,4 +594,161 @@ test.describe('[Orchestrator] - Workflow Canvas Tests', () => {
|
||||
monitoring.assertClean({ ignoreAPIPatterns: ['/api/workflows'], allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
// ========================================
|
||||
// API Error Scenarios
|
||||
// ========================================
|
||||
|
||||
test('L3.13 - API Error - 400 Bad Request', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API to return 400
|
||||
await page.route('**/api/workflows', (route) => {
|
||||
if (route.request().method() === 'POST') {
|
||||
route.fulfill({
|
||||
status: 400,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ error: 'Bad Request', message: 'Invalid workflow data' }),
|
||||
});
|
||||
} else {
|
||||
route.continue();
|
||||
}
|
||||
});
|
||||
|
||||
// Try to create a node
|
||||
const createButton = page.getByRole('button', { name: /create|add/i });
|
||||
const hasCreateButton = await createButton.isVisible().catch(() => false);
|
||||
|
||||
if (hasCreateButton) {
|
||||
await createButton.click();
|
||||
|
||||
// Verify error message
|
||||
const errorMessage = page.getByText(/invalid|bad request|输入无效/i);
|
||||
await page.unroute('**/api/workflows');
|
||||
const hasError = await errorMessage.isVisible().catch(() => false);
|
||||
expect(hasError).toBe(true);
|
||||
}
|
||||
|
||||
monitoring.assertClean({ ignoreAPIPatterns: ['/api/workflows'], allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.14 - API Error - 401 Unauthorized', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API to return 401
|
||||
await page.route('**/api/workflows', (route) => {
|
||||
route.fulfill({
|
||||
status: 401,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ error: 'Unauthorized', message: 'Authentication required' }),
|
||||
});
|
||||
});
|
||||
|
||||
await page.reload({ waitUntil: 'networkidle' as const });
|
||||
|
||||
// Verify auth error
|
||||
const authError = page.getByText(/unauthorized|not authenticated|未经授权/i);
|
||||
await page.unroute('**/api/workflows');
|
||||
const hasError = await authError.isVisible().catch(() => false);
|
||||
expect(hasError).toBe(true);
|
||||
|
||||
monitoring.assertClean({ ignoreAPIPatterns: ['/api/workflows'], allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.15 - API Error - 403 Forbidden', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API to return 403
|
||||
await page.route('**/api/workflows', (route) => {
|
||||
route.fulfill({
|
||||
status: 403,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ error: 'Forbidden', message: 'Access denied' }),
|
||||
});
|
||||
});
|
||||
|
||||
await page.reload({ waitUntil: 'networkidle' as const });
|
||||
|
||||
// Verify forbidden message
|
||||
const errorMessage = page.getByText(/forbidden|not allowed|禁止访问/i);
|
||||
await page.unroute('**/api/workflows');
|
||||
const hasError = await errorMessage.isVisible().catch(() => false);
|
||||
expect(hasError).toBe(true);
|
||||
|
||||
monitoring.assertClean({ ignoreAPIPatterns: ['/api/workflows'], allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.16 - API Error - 404 Not Found', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API to return 404
|
||||
await page.route('**/api/workflows/nonexistent', (route) => {
|
||||
route.fulfill({
|
||||
status: 404,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ error: 'Not Found', message: 'Workflow not found' }),
|
||||
});
|
||||
});
|
||||
|
||||
// Try to access a non-existent workflow
|
||||
await page.goto('/orchestrator?workflow=nonexistent-workflow-id', { waitUntil: 'networkidle' as const });
|
||||
|
||||
// Verify not found message
|
||||
const errorMessage = page.getByText(/not found|doesn't exist|未找到/i);
|
||||
await page.unroute('**/api/workflows/nonexistent');
|
||||
const hasError = await errorMessage.isVisible().catch(() => false);
|
||||
expect(hasError).toBe(true);
|
||||
|
||||
monitoring.assertClean({ ignoreAPIPatterns: ['/api/workflows'], allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.17 - API Error - 500 Internal Server Error', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API to return 500
|
||||
await page.route('**/api/workflows', (route) => {
|
||||
route.fulfill({
|
||||
status: 500,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ error: 'Internal Server Error' }),
|
||||
});
|
||||
});
|
||||
|
||||
await page.reload({ waitUntil: 'networkidle' as const });
|
||||
|
||||
// Verify server error message
|
||||
const errorMessage = page.getByText(/server error|try again|服务器错误/i);
|
||||
await page.unroute('**/api/workflows');
|
||||
const hasError = await errorMessage.isVisible().catch(() => false);
|
||||
expect(hasError).toBe(true);
|
||||
|
||||
monitoring.assertClean({ ignoreAPIPatterns: ['/api/workflows'], allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.18 - API Error - Network Timeout', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API timeout
|
||||
await page.route('**/api/workflows', () => {
|
||||
// Never fulfill - simulate timeout
|
||||
});
|
||||
|
||||
await page.reload({ waitUntil: 'networkidle' as const });
|
||||
|
||||
// Wait for timeout handling
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
// Verify timeout message
|
||||
const timeoutMessage = page.getByText(/timeout|network error|unavailable|网络超时/i);
|
||||
await page.unroute('**/api/workflows');
|
||||
const hasTimeout = await timeoutMessage.isVisible().catch(() => false);
|
||||
|
||||
monitoring.assertClean({ ignoreAPIPatterns: ['/api/workflows'], allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -352,17 +352,20 @@ test.describe('[Sessions CRUD] - Session Management Tests', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// Navigate to sessions page
|
||||
await page.reload({ waitUntil: 'networkidle' as const });
|
||||
// Navigate to sessions page to trigger API call
|
||||
await page.goto('/sessions', { waitUntil: 'networkidle' as const });
|
||||
|
||||
// Look for error indicator
|
||||
const errorIndicator = page.getByText(/error|failed|unable to load/i).or(
|
||||
// Look for error indicator - SessionsPage shows "Failed to load data"
|
||||
const errorIndicator = page.getByText(/Failed to load data|failed|加载失败/i).or(
|
||||
page.getByTestId('error-state')
|
||||
);
|
||||
|
||||
// Wait a bit for error to appear
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
const hasError = await errorIndicator.isVisible().catch(() => false);
|
||||
|
||||
// Restore routing
|
||||
// Restore routing AFTER checking for error
|
||||
await page.unroute('**/api/sessions');
|
||||
|
||||
// Error should be displayed or handled gracefully
|
||||
@@ -446,4 +449,185 @@ test.describe('[Sessions CRUD] - Session Management Tests', () => {
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
// ========================================
|
||||
// API Error Scenarios
|
||||
// ========================================
|
||||
|
||||
test('L3.11 - API Error - 400 Bad Request on create session', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API to return 400
|
||||
await page.route('**/api/sessions', (route) => {
|
||||
if (route.request().method() === 'POST') {
|
||||
route.fulfill({
|
||||
status: 400,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ error: 'Bad Request', message: 'Invalid session data' }),
|
||||
});
|
||||
} else {
|
||||
route.continue();
|
||||
}
|
||||
});
|
||||
|
||||
await page.goto('/sessions', { waitUntil: 'networkidle' as const });
|
||||
|
||||
// Try to create a session
|
||||
const createButton = page.getByRole('button', { name: /create|new|add/i });
|
||||
const hasCreateButton = await createButton.isVisible().catch(() => false);
|
||||
|
||||
if (hasCreateButton) {
|
||||
await createButton.click();
|
||||
|
||||
const submitButton = page.getByRole('button', { name: /create|save|submit/i });
|
||||
await submitButton.click();
|
||||
|
||||
// Wait for error to appear
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Verify error message - look for toast or inline error
|
||||
const errorMessage = page.getByText(/invalid|bad request|输入无效|failed|error/i);
|
||||
const hasError = await errorMessage.isVisible().catch(() => false);
|
||||
await page.unroute('**/api/sessions');
|
||||
expect(hasError).toBe(true);
|
||||
}
|
||||
|
||||
monitoring.assertClean({ ignoreAPIPatterns: ['/api/sessions'], allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.12 - API Error - 401 Unauthorized', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API to return 401
|
||||
await page.route('**/api/sessions', (route) => {
|
||||
route.fulfill({
|
||||
status: 401,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ error: 'Unauthorized', message: 'Authentication required' }),
|
||||
});
|
||||
});
|
||||
|
||||
await page.goto('/sessions', { waitUntil: 'networkidle' as const });
|
||||
|
||||
// Wait for error to appear
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// 401 might redirect to login or show auth error
|
||||
const loginRedirect = page.url().includes('/login');
|
||||
// SessionsPage shows "Failed to load data" for any error
|
||||
const authError = page.getByText(/Failed to load data|failed|Unauthorized|Authentication required|加载失败/i);
|
||||
|
||||
const hasAuthError = await authError.isVisible().catch(() => false);
|
||||
await page.unroute('**/api/sessions');
|
||||
expect(loginRedirect || hasAuthError).toBe(true);
|
||||
|
||||
monitoring.assertClean({ ignoreAPIPatterns: ['/api/sessions'], allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.13 - API Error - 403 Forbidden', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API to return 403
|
||||
await page.route('**/api/sessions', (route) => {
|
||||
route.fulfill({
|
||||
status: 403,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ error: 'Forbidden', message: 'Access denied' }),
|
||||
});
|
||||
});
|
||||
|
||||
await page.goto('/sessions', { waitUntil: 'networkidle' as const });
|
||||
|
||||
// Wait for error to appear
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Verify error message - SessionsPage shows "Failed to load data"
|
||||
const errorMessage = page.getByText(/Failed to load data|failed|加载失败|Forbidden|Access denied/i);
|
||||
const hasError = await errorMessage.isVisible().catch(() => false);
|
||||
await page.unroute('**/api/sessions');
|
||||
expect(hasError).toBe(true);
|
||||
|
||||
monitoring.assertClean({ ignoreAPIPatterns: ['/api/sessions'], allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.14 - API Error - 404 Not Found', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API to return 404 for specific session
|
||||
await page.route('**/api/sessions/nonexistent', (route) => {
|
||||
route.fulfill({
|
||||
status: 404,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ error: 'Not Found', message: 'Session not found' }),
|
||||
});
|
||||
});
|
||||
|
||||
// Navigate to a non-existent session
|
||||
await page.goto('/sessions/nonexistent-session-id', { waitUntil: 'networkidle' as const });
|
||||
|
||||
// Wait for error to appear
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Verify not found message - Session detail page shows error
|
||||
const errorMessage = page.getByText(/Failed to load|failed|not found|doesn't exist|未找到|加载失败|404|Session not found/i);
|
||||
const hasError = await errorMessage.isVisible().catch(() => false);
|
||||
await page.unroute('**/api/sessions/nonexistent');
|
||||
expect(hasError).toBe(true);
|
||||
|
||||
monitoring.assertClean({ ignoreAPIPatterns: ['/api/sessions'], allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.15 - API Error - 500 Internal Server Error', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API to return 500
|
||||
await page.route('**/api/sessions', (route) => {
|
||||
route.fulfill({
|
||||
status: 500,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ error: 'Internal Server Error', message: 'Something went wrong' }),
|
||||
});
|
||||
});
|
||||
|
||||
await page.goto('/sessions', { waitUntil: 'networkidle' as const });
|
||||
|
||||
// Wait for error to appear
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Verify server error message - SessionsPage shows "Failed to load data"
|
||||
const errorMessage = page.getByText(/Failed to load data|failed|加载失败|Internal Server Error|Something went wrong/i);
|
||||
const hasError = await errorMessage.isVisible().catch(() => false);
|
||||
await page.unroute('**/api/sessions');
|
||||
expect(hasError).toBe(true);
|
||||
|
||||
monitoring.assertClean({ ignoreAPIPatterns: ['/api/sessions'], allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.16 - API Error - Network Timeout', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API timeout by not fulfilling
|
||||
await page.route('**/api/sessions', () => {
|
||||
// Never fulfill - simulate timeout
|
||||
});
|
||||
|
||||
await page.goto('/sessions', { waitUntil: 'networkidle' as const });
|
||||
|
||||
// Wait for timeout handling
|
||||
await page.waitForTimeout(5000);
|
||||
|
||||
// Verify timeout message
|
||||
const timeoutMessage = page.getByText(/timeout|network error|unavailable|网络超时/i);
|
||||
await page.unroute('**/api/sessions');
|
||||
const hasTimeout = await timeoutMessage.isVisible().catch(() => false);
|
||||
// Timeout message may or may not appear depending on implementation
|
||||
|
||||
monitoring.assertClean({ ignoreAPIPatterns: ['/api/sessions'], allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -361,4 +361,169 @@ test.describe('[Skills] - Skills Management Tests', () => {
|
||||
monitoring.assertClean({ ignoreAPIPatterns: ['/api/skills'], allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
// ========================================
|
||||
// API Error Scenarios
|
||||
// ========================================
|
||||
|
||||
test('L3.11 - API Error - 400 Bad Request', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API to return 400
|
||||
await page.route('**/api/skills/**', (route) => {
|
||||
route.fulfill({
|
||||
status: 400,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ error: 'Bad Request', message: 'Invalid skill data' }),
|
||||
});
|
||||
});
|
||||
|
||||
await page.goto('/skills', { waitUntil: 'networkidle' as const });
|
||||
|
||||
// Try to toggle a skill (should fail with 400)
|
||||
const skillItems = page.getByTestId(/skill-item|skill-card/).or(
|
||||
page.locator('.skill-item')
|
||||
);
|
||||
|
||||
const itemCount = await skillItems.count();
|
||||
if (itemCount > 0) {
|
||||
const firstSkill = skillItems.first();
|
||||
const toggleSwitch = firstSkill.getByRole('switch').or(
|
||||
firstSkill.getByTestId('skill-toggle')
|
||||
);
|
||||
|
||||
const hasToggle = await toggleSwitch.isVisible().catch(() => false);
|
||||
if (hasToggle) {
|
||||
await toggleSwitch.click();
|
||||
|
||||
// Verify error message
|
||||
const errorMessage = page.getByText(/invalid|bad request|输入无效/i);
|
||||
await page.unroute('**/api/skills/**');
|
||||
const hasError = await errorMessage.isVisible().catch(() => false);
|
||||
expect(hasError).toBe(true);
|
||||
}
|
||||
}
|
||||
|
||||
monitoring.assertClean({ ignoreAPIPatterns: ['/api/skills'], allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.12 - API Error - 401 Unauthorized', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API to return 401
|
||||
await page.route('**/api/skills', (route) => {
|
||||
route.fulfill({
|
||||
status: 401,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ error: 'Unauthorized', message: 'Authentication required' }),
|
||||
});
|
||||
});
|
||||
|
||||
await page.goto('/skills', { waitUntil: 'networkidle' as const });
|
||||
|
||||
// Verify auth error
|
||||
const authError = page.getByText(/unauthorized|not authenticated|未经授权/i);
|
||||
await page.unroute('**/api/skills');
|
||||
const hasError = await authError.isVisible().catch(() => false);
|
||||
expect(hasError).toBe(true);
|
||||
|
||||
monitoring.assertClean({ ignoreAPIPatterns: ['/api/skills'], allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.13 - API Error - 403 Forbidden', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API to return 403
|
||||
await page.route('**/api/skills', (route) => {
|
||||
route.fulfill({
|
||||
status: 403,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ error: 'Forbidden', message: 'Access denied' }),
|
||||
});
|
||||
});
|
||||
|
||||
await page.goto('/skills', { waitUntil: 'networkidle' as const });
|
||||
|
||||
// Verify forbidden message
|
||||
const errorMessage = page.getByText(/forbidden|not allowed|禁止访问/i);
|
||||
await page.unroute('**/api/skills');
|
||||
const hasError = await errorMessage.isVisible().catch(() => false);
|
||||
expect(hasError).toBe(true);
|
||||
|
||||
monitoring.assertClean({ ignoreAPIPatterns: ['/api/skills'], allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.14 - API Error - 404 Not Found', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API to return 404
|
||||
await page.route('**/api/skills/nonexistent', (route) => {
|
||||
route.fulfill({
|
||||
status: 404,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ error: 'Not Found', message: 'Skill not found' }),
|
||||
});
|
||||
});
|
||||
|
||||
// Try to access a non-existent skill
|
||||
await page.goto('/skills/nonexistent-skill-id', { waitUntil: 'networkidle' as const });
|
||||
|
||||
// Verify not found message
|
||||
const errorMessage = page.getByText(/not found|doesn't exist|未找到/i);
|
||||
await page.unroute('**/api/skills/nonexistent');
|
||||
const hasError = await errorMessage.isVisible().catch(() => false);
|
||||
expect(hasError).toBe(true);
|
||||
|
||||
monitoring.assertClean({ ignoreAPIPatterns: ['/api/skills'], allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.15 - API Error - 500 Internal Server Error', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API to return 500
|
||||
await page.route('**/api/skills', (route) => {
|
||||
route.fulfill({
|
||||
status: 500,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ error: 'Internal Server Error' }),
|
||||
});
|
||||
});
|
||||
|
||||
await page.goto('/skills', { waitUntil: 'networkidle' as const });
|
||||
|
||||
// Verify server error message
|
||||
const errorMessage = page.getByText(/server error|try again|服务器错误/i);
|
||||
await page.unroute('**/api/skills');
|
||||
const hasError = await errorMessage.isVisible().catch(() => false);
|
||||
expect(hasError).toBe(true);
|
||||
|
||||
monitoring.assertClean({ ignoreAPIPatterns: ['/api/skills'], allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.16 - API Error - Network Timeout', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API timeout
|
||||
await page.route('**/api/skills', () => {
|
||||
// Never fulfill - simulate timeout
|
||||
});
|
||||
|
||||
await page.goto('/skills', { waitUntil: 'networkidle' as const });
|
||||
|
||||
// Wait for timeout handling
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
// Verify timeout message
|
||||
const timeoutMessage = page.getByText(/timeout|network error|unavailable|网络超时/i);
|
||||
await page.unroute('**/api/skills');
|
||||
const hasTimeout = await timeoutMessage.isVisible().catch(() => false);
|
||||
|
||||
monitoring.assertClean({ ignoreAPIPatterns: ['/api/skills'], allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user