mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-10 02:24:35 +08:00
- Added Phase 6: Fix Discovery & Batching with intelligent grouping and batching of findings. - Added Phase 7: Fix Parallel Planning to launch planning agents for concurrent analysis and aggregation of partial plans. - Added Phase 8: Fix Execution for stage-based execution of fixes with conservative test verification. - Added Phase 9: Fix Completion to aggregate results, generate summary reports, and handle session completion. - Introduced new frontend components: ResizeHandle for draggable resizing of sidebar panels and useResizablePanel hook for managing panel sizes with localStorage persistence. - Added PowerShell script for checking TypeScript errors in source code, excluding test files.
551 lines
18 KiB
TypeScript
551 lines
18 KiB
TypeScript
// ========================================
|
|
// E2E Tests: Commands Management
|
|
// ========================================
|
|
// End-to-end tests for commands list and info display
|
|
|
|
import { test, expect } from '@playwright/test';
|
|
import { setupEnhancedMonitoring } from './helpers/i18n-helpers';
|
|
|
|
test.describe('[Commands] - Commands Management Tests', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
// Navigate to commands page directly and wait for full load
|
|
await page.goto('/react/commands', { waitUntil: 'networkidle' as const });
|
|
});
|
|
|
|
test('L3.1 - should display commands list', async ({ page }) => {
|
|
const monitoring = setupEnhancedMonitoring(page);
|
|
|
|
// Commands page already loaded in beforeEach
|
|
|
|
// Look for commands list container
|
|
const commandsList = page.getByTestId('commands-list').or(
|
|
page.locator('.commands-list')
|
|
);
|
|
|
|
const isVisible = await commandsList.isVisible().catch(() => false);
|
|
|
|
if (isVisible) {
|
|
// Verify command items exist
|
|
const commandItems = page.getByTestId(/command-item|command-card/).or(
|
|
page.locator('.command-item')
|
|
);
|
|
|
|
const itemCount = await commandItems.count();
|
|
expect(itemCount).toBeGreaterThan(0);
|
|
}
|
|
|
|
monitoring.assertClean({ ignoreAPIPatterns: ['/api/'], allowWarnings: true });
|
|
monitoring.stop();
|
|
});
|
|
|
|
test('L3.2 - should display command name', async ({ page }) => {
|
|
const monitoring = setupEnhancedMonitoring(page);
|
|
|
|
// Commands page already loaded in beforeEach
|
|
|
|
// Look for command items
|
|
const commandItems = page.getByTestId(/command-item|command-card/).or(
|
|
page.locator('.command-item')
|
|
);
|
|
|
|
const itemCount = await commandItems.count();
|
|
|
|
if (itemCount > 0) {
|
|
// Check each command has a name
|
|
for (let i = 0; i < Math.min(itemCount, 5); i++) {
|
|
const command = commandItems.nth(i);
|
|
|
|
const nameElement = command.getByTestId('command-name').or(
|
|
command.locator('.command-name')
|
|
);
|
|
|
|
const hasName = await nameElement.isVisible().catch(() => false);
|
|
expect(hasName).toBe(true);
|
|
|
|
const name = await nameElement.textContent();
|
|
expect(name).toBeTruthy();
|
|
expect(name?.length).toBeGreaterThan(0);
|
|
}
|
|
}
|
|
|
|
monitoring.assertClean({ ignoreAPIPatterns: ['/api/'], allowWarnings: true });
|
|
monitoring.stop();
|
|
});
|
|
|
|
test('L3.3 - should display command description', async ({ page }) => {
|
|
const monitoring = setupEnhancedMonitoring(page);
|
|
|
|
// Commands page already loaded in beforeEach
|
|
|
|
// Look for command items
|
|
const commandItems = page.getByTestId(/command-item|command-card/).or(
|
|
page.locator('.command-item')
|
|
);
|
|
|
|
const itemCount = await commandItems.count();
|
|
|
|
if (itemCount > 0) {
|
|
const firstCommand = commandItems.first();
|
|
|
|
// Look for description
|
|
const description = firstCommand.getByTestId('command-description').or(
|
|
firstCommand.locator('.command-description')
|
|
);
|
|
|
|
const hasDescription = await description.isVisible().catch(() => false);
|
|
|
|
if (hasDescription) {
|
|
const text = await description.textContent();
|
|
expect(text).toBeTruthy();
|
|
}
|
|
}
|
|
|
|
monitoring.assertClean({ ignoreAPIPatterns: ['/api/'], allowWarnings: true });
|
|
monitoring.stop();
|
|
});
|
|
|
|
test('L3.4 - should display command usage', async ({ page }) => {
|
|
const monitoring = setupEnhancedMonitoring(page);
|
|
|
|
// Navigate to commands page
|
|
await page.goto('/react/commands', { waitUntil: 'networkidle' as const });
|
|
|
|
// Look for command items
|
|
const commandItems = page.getByTestId(/command-item|command-card/).or(
|
|
page.locator('.command-item')
|
|
);
|
|
|
|
const itemCount = await commandItems.count();
|
|
|
|
if (itemCount > 0) {
|
|
const firstCommand = commandItems.first();
|
|
|
|
// Look for usage info
|
|
const usage = firstCommand.getByTestId('command-usage').or(
|
|
firstCommand.locator('*').filter({ hasText: /usage|how to use/i })
|
|
);
|
|
|
|
const hasUsage = await usage.isVisible().catch(() => false);
|
|
|
|
if (hasUsage) {
|
|
const text = await usage.textContent();
|
|
expect(text).toBeTruthy();
|
|
}
|
|
}
|
|
|
|
monitoring.assertClean({ ignoreAPIPatterns: ['/api/'], allowWarnings: true });
|
|
monitoring.stop();
|
|
});
|
|
|
|
test('L3.5 - should display command examples', async ({ page }) => {
|
|
const monitoring = setupEnhancedMonitoring(page);
|
|
|
|
// Navigate to commands page
|
|
await page.goto('/react/commands', { waitUntil: 'networkidle' as const });
|
|
|
|
// Look for command items
|
|
const commandItems = page.getByTestId(/command-item|command-card/).or(
|
|
page.locator('.command-item')
|
|
);
|
|
|
|
const itemCount = await commandItems.count();
|
|
|
|
if (itemCount > 0) {
|
|
const firstCommand = commandItems.first();
|
|
|
|
// Look for examples section
|
|
const examples = firstCommand.getByTestId('command-examples').or(
|
|
firstCommand.locator('*').filter({ hasText: /example/i })
|
|
);
|
|
|
|
const hasExamples = await examples.isVisible().catch(() => false);
|
|
|
|
if (hasExamples) {
|
|
const text = await examples.textContent();
|
|
expect(text).toBeTruthy();
|
|
}
|
|
}
|
|
|
|
monitoring.assertClean({ ignoreAPIPatterns: ['/api/'], allowWarnings: true });
|
|
monitoring.stop();
|
|
});
|
|
|
|
test('L3.6 - should display command category', async ({ page }) => {
|
|
const monitoring = setupEnhancedMonitoring(page);
|
|
|
|
// Navigate to commands page
|
|
await page.goto('/react/commands', { waitUntil: 'networkidle' as const });
|
|
|
|
// Look for command items
|
|
const commandItems = page.getByTestId(/command-item|command-card/).or(
|
|
page.locator('.command-item')
|
|
);
|
|
|
|
const itemCount = await commandItems.count();
|
|
|
|
if (itemCount > 0) {
|
|
const firstCommand = commandItems.first();
|
|
|
|
// Look for category badge
|
|
const categoryBadge = firstCommand.getByTestId('command-category').or(
|
|
firstCommand.locator('.command-category')
|
|
);
|
|
|
|
const hasCategory = await categoryBadge.isVisible().catch(() => false);
|
|
|
|
if (hasCategory) {
|
|
const text = await categoryBadge.textContent();
|
|
expect(text).toBeTruthy();
|
|
}
|
|
}
|
|
|
|
monitoring.assertClean({ ignoreAPIPatterns: ['/api/'], allowWarnings: true });
|
|
monitoring.stop();
|
|
});
|
|
|
|
test('L3.7 - should filter commands by category', async ({ page }) => {
|
|
const monitoring = setupEnhancedMonitoring(page);
|
|
|
|
// Navigate to commands page
|
|
await page.goto('/react/commands', { waitUntil: 'networkidle' as const });
|
|
|
|
// Look for category filter
|
|
const categoryFilter = page.getByRole('combobox', { name: /category|filter/i }).or(
|
|
page.getByTestId('category-filter')
|
|
);
|
|
|
|
const hasCategoryFilter = await categoryFilter.isVisible().catch(() => false);
|
|
|
|
if (hasCategoryFilter) {
|
|
// Check if there are category options
|
|
const categoryOptions = await categoryFilter.locator('option').count();
|
|
|
|
if (categoryOptions > 1) {
|
|
await categoryFilter.selectOption({ index: 1 });
|
|
|
|
// Wait for filtered results
|
|
|
|
const commandItems = page.getByTestId(/command-item|command-card/).or(
|
|
page.locator('.command-item')
|
|
);
|
|
|
|
const commandCount = await commandItems.count();
|
|
expect(commandCount).toBeGreaterThanOrEqual(0);
|
|
}
|
|
}
|
|
|
|
monitoring.assertClean({ ignoreAPIPatterns: ['/api/'], allowWarnings: true });
|
|
monitoring.stop();
|
|
});
|
|
|
|
test('L3.8 - should search commands', async ({ page }) => {
|
|
const monitoring = setupEnhancedMonitoring(page);
|
|
|
|
// Navigate to commands page
|
|
await page.goto('/react/commands', { waitUntil: 'networkidle' as const });
|
|
|
|
// Look for search input
|
|
const searchInput = page.getByRole('textbox', { name: /search|find/i }).or(
|
|
page.getByTestId('command-search')
|
|
);
|
|
|
|
const hasSearch = await searchInput.isVisible().catch(() => false);
|
|
|
|
if (hasSearch) {
|
|
await searchInput.fill('test');
|
|
|
|
// Wait for search results
|
|
|
|
// Search should either show results or no results message
|
|
const noResults = page.getByText(/no results|not found/i);
|
|
const hasNoResults = await noResults.isVisible().catch(() => false);
|
|
|
|
const commandItems = page.getByTestId(/command-item|command-card/).or(
|
|
page.locator('.command-item')
|
|
);
|
|
|
|
const commandCount = await commandItems.count();
|
|
|
|
// Either no results message or filtered commands
|
|
expect(hasNoResults || commandCount >= 0).toBe(true);
|
|
}
|
|
|
|
monitoring.assertClean({ ignoreAPIPatterns: ['/api/'], allowWarnings: true });
|
|
monitoring.stop();
|
|
});
|
|
|
|
test('L3.9 - should display command source type', async ({ page }) => {
|
|
const monitoring = setupEnhancedMonitoring(page);
|
|
|
|
// Navigate to commands page
|
|
await page.goto('/react/commands', { waitUntil: 'networkidle' as const });
|
|
|
|
// Look for command items
|
|
const commandItems = page.getByTestId(/command-item|command-card/).or(
|
|
page.locator('.command-item')
|
|
);
|
|
|
|
const itemCount = await commandItems.count();
|
|
|
|
if (itemCount > 0) {
|
|
const firstCommand = commandItems.first();
|
|
|
|
// Look for source badge
|
|
const sourceBadge = firstCommand.getByTestId('command-source').or(
|
|
firstCommand.locator('*').filter({ hasText: /builtin|custom/i })
|
|
);
|
|
|
|
const hasSource = await sourceBadge.isVisible().catch(() => false);
|
|
|
|
if (hasSource) {
|
|
const text = await sourceBadge.textContent();
|
|
expect(text).toBeTruthy();
|
|
}
|
|
}
|
|
|
|
monitoring.assertClean({ ignoreAPIPatterns: ['/api/'], allowWarnings: true });
|
|
monitoring.stop();
|
|
});
|
|
|
|
test('L3.10 - should display command aliases', async ({ page }) => {
|
|
const monitoring = setupEnhancedMonitoring(page);
|
|
|
|
// Navigate to commands page
|
|
await page.goto('/react/commands', { waitUntil: 'networkidle' as const });
|
|
|
|
// Look for command items
|
|
const commandItems = page.getByTestId(/command-item|command-card/).or(
|
|
page.locator('.command-item')
|
|
);
|
|
|
|
const itemCount = await commandItems.count();
|
|
|
|
if (itemCount > 0) {
|
|
const firstCommand = commandItems.first();
|
|
|
|
// Look for aliases display
|
|
const aliases = firstCommand.getByTestId('command-aliases').or(
|
|
firstCommand.locator('*').filter({ hasText: /alias|also known as/i })
|
|
);
|
|
|
|
const hasAliases = await aliases.isVisible().catch(() => false);
|
|
|
|
if (hasAliases) {
|
|
const text = await aliases.textContent();
|
|
expect(text).toBeTruthy();
|
|
}
|
|
}
|
|
|
|
monitoring.assertClean({ ignoreAPIPatterns: ['/api/'], allowWarnings: true });
|
|
monitoring.stop();
|
|
});
|
|
|
|
// ========================================
|
|
// API Error Scenarios
|
|
// ========================================
|
|
// Note: These tests use separate describe block to control navigation timing
|
|
|
|
test.describe('API Error Tests', () => {
|
|
// Each test sets up mock BEFORE navigation, then navigates
|
|
// No shared beforeEach - each test handles its own navigation
|
|
|
|
test('L3.11 - API Error - 400 Bad Request', async ({ page }) => {
|
|
const monitoring = setupEnhancedMonitoring(page);
|
|
|
|
// Mock API FIRST, before navigation
|
|
await page.route('**/api/commands**', (route) => {
|
|
route.fulfill({
|
|
status: 400,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({ error: 'Bad Request', message: 'Invalid command data' }),
|
|
});
|
|
});
|
|
|
|
// Navigate AFTER mock is set up
|
|
await page.goto('/react/commands', { waitUntil: 'networkidle' as const });
|
|
|
|
// Debug: Check if page loaded
|
|
const url = page.url();
|
|
console.log('[L3.11] Current URL:', url);
|
|
|
|
// Wait for React Query to complete with retries
|
|
await page.waitForTimeout(3000);
|
|
|
|
// Debug: Check page content
|
|
const bodyContent = await page.locator('body').textContent();
|
|
console.log('[L3.11] Page content (first 300 chars):', bodyContent?.substring(0, 300));
|
|
|
|
// Debug: Check for any error-related text
|
|
const hasErrorText = /Failed to load data|加载失败|Invalid command data|Bad Request/.test(bodyContent || '');
|
|
console.log('[L3.11] Has error-related text:', hasErrorText);
|
|
|
|
// Verify error message is displayed
|
|
const errorMessage = page.locator('text=/Failed to load data|加载失败/');
|
|
const hasError = await errorMessage.isVisible().catch(() => false);
|
|
expect(hasError).toBe(true);
|
|
|
|
// Clean up route after verification
|
|
await page.unroute('**/api/commands**');
|
|
|
|
// Skip console error check for API error tests - errors are expected
|
|
monitoring.stop();
|
|
});
|
|
|
|
test('L3.12 - API Error - 401 Unauthorized', async ({ page }) => {
|
|
const monitoring = setupEnhancedMonitoring(page);
|
|
|
|
// Mock API FIRST, before navigation
|
|
await page.route('**/api/commands**', (route) => {
|
|
route.fulfill({
|
|
status: 401,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({ error: 'Unauthorized', message: 'Authentication required' }),
|
|
});
|
|
});
|
|
|
|
// Navigate AFTER mock is set up
|
|
// Use domcontentloaded instead of networkidle to avoid hanging on failed requests
|
|
await page.goto('/react/commands', { waitUntil: 'networkidle' as const });
|
|
|
|
// Wait for React Query to complete retries and set error state
|
|
await page.waitForTimeout(3000);
|
|
|
|
// Debug: Check if error UI is in DOM
|
|
const errorInDOM = await page.locator('body').evaluate((el) => {
|
|
const errorElements = el.querySelectorAll('[class*="destructive"]');
|
|
return {
|
|
count: errorElements.length,
|
|
content: errorElements[0]?.textContent?.substring(0, 100) || null,
|
|
};
|
|
});
|
|
console.log('[L3.12] Error UI in DOM:', errorInDOM);
|
|
|
|
// Debug: Check if error text is anywhere on page
|
|
const bodyText = await page.locator('body').textContent();
|
|
const hasErrorTextInBody = /Failed to load data|加载失败/.test(bodyText || '');
|
|
console.log('[L3.12] Has error text in body:', hasErrorTextInBody);
|
|
|
|
// Verify auth error is displayed
|
|
const authError = page.locator('text=/Failed to load data|加载失败/');
|
|
const hasError = await authError.isVisible().catch(() => false);
|
|
expect(hasError).toBe(true);
|
|
|
|
await page.unroute('**/api/commands**');
|
|
|
|
monitoring.stop();
|
|
});
|
|
|
|
test('L3.13 - API Error - 403 Forbidden', async ({ page }) => {
|
|
const monitoring = setupEnhancedMonitoring(page);
|
|
|
|
// Mock API FIRST, before navigation
|
|
await page.route('**/api/commands**', (route) => {
|
|
route.fulfill({
|
|
status: 403,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({ error: 'Forbidden', message: 'Access denied' }),
|
|
});
|
|
});
|
|
|
|
// Navigate AFTER mock is set up
|
|
// Use domcontentloaded instead of networkidle to avoid hanging on failed requests
|
|
await page.goto('/react/commands', { waitUntil: 'networkidle' as const });
|
|
|
|
// Wait for React Query to complete retries and set error state
|
|
await page.waitForTimeout(3000);
|
|
|
|
// Verify forbidden message is displayed
|
|
const errorMessage = page.locator('text=/Failed to load data|加载失败/');
|
|
const hasError = await errorMessage.isVisible().catch(() => false);
|
|
expect(hasError).toBe(true);
|
|
|
|
await page.unroute('**/api/commands**');
|
|
|
|
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**', (route) => {
|
|
route.fulfill({
|
|
status: 404,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({ error: 'Not Found', message: 'Command not found' }),
|
|
});
|
|
});
|
|
|
|
// Navigate AFTER mock is set up
|
|
// Use domcontentloaded instead of networkidle to avoid hanging on failed requests
|
|
await page.goto('/react/commands', { waitUntil: 'networkidle' as const });
|
|
|
|
// Wait for React Query to complete retries and set error state
|
|
await page.waitForTimeout(3000);
|
|
|
|
// Verify not found message is displayed
|
|
const errorMessage = page.locator('text=/Failed to load data|加载失败/');
|
|
const hasError = await errorMessage.isVisible().catch(() => false);
|
|
expect(hasError).toBe(true);
|
|
|
|
await page.unroute('**/api/commands**');
|
|
|
|
monitoring.stop();
|
|
});
|
|
|
|
test('L3.15 - API Error - 500 Internal Server Error', async ({ page }) => {
|
|
const monitoring = setupEnhancedMonitoring(page);
|
|
|
|
// Mock API FIRST, before navigation
|
|
await page.route('**/api/commands**', (route) => {
|
|
route.fulfill({
|
|
status: 500,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({ error: 'Internal Server Error' }),
|
|
});
|
|
});
|
|
|
|
// Navigate AFTER mock is set up
|
|
// Use domcontentloaded instead of networkidle to avoid hanging on failed requests
|
|
await page.goto('/react/commands', { waitUntil: 'networkidle' as const });
|
|
|
|
// Wait for React Query to complete retries and set error state
|
|
await page.waitForTimeout(3000);
|
|
|
|
// Verify server error message is displayed
|
|
const errorMessage = page.locator('text=/Failed to load data|加载失败/');
|
|
const hasError = await errorMessage.isVisible().catch(() => false);
|
|
expect(hasError).toBe(true);
|
|
|
|
await page.unroute('**/api/commands**');
|
|
|
|
monitoring.stop();
|
|
});
|
|
|
|
test('L3.16 - API Error - Network Timeout', async ({ page }) => {
|
|
const monitoring = setupEnhancedMonitoring(page);
|
|
|
|
// Mock API timeout - abort connection
|
|
await page.route('**/api/commands**', (route) => {
|
|
route.abort('failed');
|
|
});
|
|
|
|
// Navigate AFTER mock is set up
|
|
// Use domcontentloaded instead of networkidle to avoid hanging on failed requests
|
|
await page.goto('/react/commands', { waitUntil: 'networkidle' as const });
|
|
|
|
// Wait for timeout handling
|
|
await page.waitForTimeout(5000);
|
|
|
|
// Verify timeout message is displayed
|
|
const timeoutMessage = page.locator('text=/Failed to load data|加载失败/');
|
|
const hasTimeout = await timeoutMessage.isVisible().catch(() => false);
|
|
expect(hasTimeout).toBe(true);
|
|
|
|
await page.unroute('**/api/commands**');
|
|
|
|
monitoring.stop();
|
|
});
|
|
});
|
|
});
|