Files
Claude-Code-Workflow/ccw/frontend/tests/e2e/history.spec.ts

366 lines
11 KiB
TypeScript

// ========================================
// E2E Tests: History - Archived Session Management
// ========================================
// End-to-end tests for history page with search, filter, restore operations
import { test, expect } from '@playwright/test';
import { setupEnhancedMonitoring, switchLanguageAndVerify } from './helpers/i18n-helpers';
test.describe('[History] - Archived Session Management Tests', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/history', { waitUntil: 'networkidle' as const });
});
test('L3.31 - Page loads and displays archived sessions', async ({ page }) => {
const monitoring = setupEnhancedMonitoring(page);
// Mock API for history
await page.route('**/api/history', (route) => {
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
sessions: [
{
id: 'session-1',
title: 'Archived Session 1',
archivedAt: Date.now(),
status: 'completed'
}
]
})
});
});
// Reload to trigger API
await page.reload({ waitUntil: 'networkidle' as const });
// Look for history list
const historyList = page.getByTestId('history-list').or(
page.locator('.history-list')
);
const isListVisible = await historyList.isVisible().catch(() => false);
if (isListVisible) {
// Verify session items are displayed
const sessionItems = page.getByTestId(/session-item|history-item/).or(
page.locator('.history-item')
);
const itemCount = await sessionItems.count();
expect(itemCount).toBeGreaterThanOrEqual(0);
}
monitoring.assertClean({ allowWarnings: true });
monitoring.stop();
});
test('L3.32 - Search archived sessions', async ({ page }) => {
const monitoring = setupEnhancedMonitoring(page);
// Mock API for search
await page.route('**/api/history?q=**', (route) => {
const requestUrl = route.request().url();
const url = new URL(requestUrl);
const query = url.searchParams.get('q');
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
sessions: [
{
id: 'session-search',
title: `Search Result for ${query}`,
archivedAt: Date.now()
}
]
})
});
});
// Look for search input
const searchInput = page.getByRole('textbox', { name: /search|find/i }).or(
page.getByTestId('search-input')
);
const hasSearchInput = await searchInput.isVisible().catch(() => false);
if (hasSearchInput) {
await searchInput.fill('test');
await page.waitForTimeout(500);
// Verify search was performed
const searchResults = page.getByTestId(/history-item|session-item/);
const resultCount = await searchResults.count();
expect(resultCount).toBeGreaterThanOrEqual(0);
}
monitoring.assertClean({ allowWarnings: true });
monitoring.stop();
});
test('L3.33 - Filter by date range', async ({ page }) => {
const monitoring = setupEnhancedMonitoring(page);
// Mock API for date filter
await page.route('**/api/history**', (route) => {
const requestUrl = route.request().url();
const url = new URL(requestUrl);
const fromDate = url.searchParams.get('from');
const toDate = url.searchParams.get('to');
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
sessions: [
{
id: 'session-filtered',
title: 'Filtered by Date',
archivedAt: Date.now()
}
]
})
});
});
// Look for date filter controls
const dateFilter = page.getByTestId('date-filter').or(
page.locator('*').filter({ hasText: /date|filter/i })
);
const hasDateFilter = await dateFilter.isVisible().catch(() => false);
if (hasDateFilter) {
const fromDateInput = page.getByRole('textbox', { name: /from|start/i });
const hasFromDate = await fromDateInput.isVisible().catch(() => false);
if (hasFromDate) {
await fromDateInput.fill('2024-01-01');
const toDateInput = page.getByRole('textbox', { name: /to|end/i });
const hasToDate = await toDateInput.isVisible().catch(() => false);
if (hasToDate) {
await toDateInput.fill('2024-12-31');
// Look for apply button
const applyButton = page.getByRole('button', { name: /apply|filter/i });
const hasApplyButton = await applyButton.isVisible().catch(() => false);
if (hasApplyButton) {
await applyButton.click();
await page.waitForTimeout(500);
}
}
}
}
monitoring.assertClean({ allowWarnings: true });
monitoring.stop();
});
test('L3.34 - Restore archived session', async ({ page }) => {
const monitoring = setupEnhancedMonitoring(page);
// Mock API for restore
await page.route('**/api/history/*/restore', (route) => {
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ success: true, sessionId: 'restored-session' })
});
});
// Look for archived session
const sessionItems = page.getByTestId(/session-item|history-item/).or(
page.locator('.history-item')
);
const itemCount = await sessionItems.count();
if (itemCount > 0) {
const firstSession = sessionItems.first();
// Look for restore button
const restoreButton = firstSession.getByRole('button', { name: /restore|reload/i }).or(
firstSession.getByTestId('restore-button')
);
const hasRestoreButton = await restoreButton.isVisible().catch(() => false);
if (hasRestoreButton) {
await restoreButton.click();
// Verify success message
const successMessage = page.getByText(/restored|success/i);
const hasSuccess = await successMessage.isVisible().catch(() => false);
expect(hasSuccess).toBe(true);
}
}
monitoring.assertClean({ allowWarnings: true });
monitoring.stop();
});
test('L3.35 - Delete archived session', async ({ page }) => {
const monitoring = setupEnhancedMonitoring(page);
// Mock API for delete
await page.route('**/api/history/*', (route) => {
if (route.request().method() === 'DELETE') {
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ success: true })
});
} else {
route.continue();
}
});
// Look for archived session
const sessionItems = page.getByTestId(/session-item|history-item/).or(
page.locator('.history-item')
);
const itemCount = await sessionItems.count();
if (itemCount > 0) {
const firstSession = sessionItems.first();
// Look for delete button
const deleteButton = firstSession.getByRole('button', { name: /delete|remove/i }).or(
firstSession.getByTestId('delete-button')
);
const hasDeleteButton = await deleteButton.isVisible().catch(() => false);
if (hasDeleteButton) {
await deleteButton.click();
// Confirm deletion if dialog appears
const confirmDialog = page.getByRole('dialog').filter({ hasText: /delete|confirm/i });
const hasDialog = await confirmDialog.isVisible().catch(() => false);
if (hasDialog) {
const confirmButton = confirmDialog.getByRole('button', { name: /delete|confirm|yes/i });
await confirmButton.click();
}
// Verify success message
const successMessage = page.getByText(/deleted|success/i);
const hasSuccess = await successMessage.isVisible().catch(() => false);
expect(hasSuccess).toBe(true);
}
}
monitoring.assertClean({ allowWarnings: true });
monitoring.stop();
});
test('L3.36 - i18n - Archive messages in EN/ZH', 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 history content is in Chinese
const pageContent = await page.content();
const hasChineseText = /[\u4e00-\u9fa5]/.test(pageContent);
expect(hasChineseText).toBe(true);
// Switch back to English
await switchLanguageAndVerify(page, 'en', languageSwitcher);
}
monitoring.assertClean({ allowWarnings: true });
monitoring.stop();
});
test('L3.37 - Error - Restore fails', async ({ page }) => {
const monitoring = setupEnhancedMonitoring(page);
// Mock API error for restore
await page.route('**/api/history/*/restore', (route) => {
route.fulfill({
status: 500,
contentType: 'application/json',
body: JSON.stringify({ error: 'Failed to restore session' })
});
});
// Look for archived session
const sessionItems = page.getByTestId(/session-item|history-item/).or(
page.locator('.history-item')
);
const itemCount = await sessionItems.count();
if (itemCount > 0) {
const firstSession = sessionItems.first();
// Look for restore button
const restoreButton = firstSession.getByRole('button', { name: /restore|reload/i });
const hasRestoreButton = await restoreButton.isVisible().catch(() => false);
if (hasRestoreButton) {
await restoreButton.click();
// Verify error message
const errorMessage = page.getByText(/error|failed|unable/i);
const hasError = await errorMessage.isVisible().catch(() => false);
expect(hasError).toBe(true);
}
}
monitoring.assertClean({ ignoreAPIPatterns: ['/api/history'], allowWarnings: true });
monitoring.stop();
});
test('L3.38 - Edge - Empty archive state', async ({ page }) => {
const monitoring = setupEnhancedMonitoring(page);
// Mock API for empty history
await page.route('**/api/history', (route) => {
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ sessions: [] })
});
});
// Reload to trigger API
await page.reload({ waitUntil: 'networkidle' as const });
// Look for empty state UI OR validate that list is empty (defensive check)
const emptyState = page.getByTestId('empty-state').or(
page.getByText(/no history|empty|no sessions/i)
);
const hasEmptyState = await emptyState.isVisible().catch(() => false);
// Fallback: check if history list is empty
const listItems = page.getByTestId(/session-item|history-item/).or(
page.locator('.history-item')
);
const itemCount = await listItems.count();
// Test passes if: empty state UI is visible OR list has 0 items
const isValidEmptyState = hasEmptyState || itemCount === 0;
expect(isValidEmptyState).toBe(true);
monitoring.assertClean({ allowWarnings: true });
monitoring.stop();
});
});