chore: clean .gitignore cache

This commit is contained in:
GitHub Actions
2026-01-26 19:21:33 +00:00
parent 1b1b3a70b1
commit e5f0fec5db
1483 changed files with 0 additions and 472793 deletions

View File

@@ -1,367 +0,0 @@
/**
* Audit Logs E2E Tests
*
* Tests the audit logs functionality:
* - Page loading and data display
* - Log filtering and search
* - Export functionality (CSV)
* - Pagination
* - Log details view
*
* @see /projects/Charon/docs/plans/current_spec.md - Phase 3
*/
import { test, expect, loginUser } from '../fixtures/auth-fixtures';
import { waitForLoadingComplete, waitForToast } from '../utils/wait-helpers';
test.describe('Audit Logs', () => {
test.beforeEach(async ({ page, adminUser }) => {
await loginUser(page, adminUser);
await waitForLoadingComplete(page);
await page.goto('/security/audit-logs');
await waitForLoadingComplete(page);
});
test.describe('Page Loading', () => {
test('should display audit logs page', async ({ page }) => {
// Look for audit logs heading or any audit-related content
const heading = page.getByRole('heading', { name: /audit|log/i });
const headingVisible = await heading.isVisible().catch(() => false);
if (headingVisible) {
await expect(heading).toBeVisible();
} else {
// Try finding audit logs content by text
const content = page.getByText(/audit|security.*log|activity.*log/i).first();
const contentVisible = await content.isVisible().catch(() => false);
if (contentVisible) {
await expect(content).toBeVisible();
} else {
// Page should at least be loaded
await expect(page).toHaveURL(/audit-logs/);
}
}
});
test('should display log data table', async ({ page }) => {
// Wait for the app-level loading to complete (showing "Loading application...")
try {
await page.waitForSelector('[role="status"]', { state: 'hidden', timeout: 5000 });
} catch {
// Loading might already be done
}
// Wait for content to appear
await page.waitForTimeout(500);
// Try to find table first
const table = page.getByRole('table');
const tableVisible = await table.isVisible().catch(() => false);
if (tableVisible) {
await expect(table).toBeVisible();
} else {
// Might use a different table/grid component, check for common patterns
const dataGrid = page.locator('table, [role="grid"], [data-testid*="table"], [data-testid*="grid"], [class*="log"]').first();
const dataGridVisible = await dataGrid.isVisible().catch(() => false);
if (dataGridVisible) {
await expect(dataGrid).toBeVisible();
} else {
// Check for empty state, loading, or heading (page might still be loading)
const emptyOrLoading = page.getByText(/no.*logs|no.*data|loading|empty|audit/i).first();
const emptyVisible = await emptyOrLoading.isVisible().catch(() => false);
// Check URL at minimum - page should be correct URL
const currentUrl = page.url();
const isCorrectPage = currentUrl.includes('audit-logs');
// Either data display, empty state, or correct page should be present
expect(dataGridVisible || emptyVisible || isCorrectPage).toBeTruthy();
}
}
});
});
test.describe('Log Table Structure', () => {
test('should display timestamp column', async ({ page }) => {
const timestampHeader = page.locator('th, [role="columnheader"]').filter({
hasText: /timestamp|date|time|when/i
}).first();
const headerVisible = await timestampHeader.isVisible().catch(() => false);
if (headerVisible) {
await expect(timestampHeader).toBeVisible();
}
});
test('should display action/event column', async ({ page }) => {
const actionHeader = page.locator('th, [role="columnheader"]').filter({
hasText: /action|event|type|activity/i
}).first();
const headerVisible = await actionHeader.isVisible().catch(() => false);
if (headerVisible) {
await expect(actionHeader).toBeVisible();
}
});
test('should display user column', async ({ page }) => {
const userHeader = page.locator('th, [role="columnheader"]').filter({
hasText: /user|actor|who/i
}).first();
const headerVisible = await userHeader.isVisible().catch(() => false);
if (headerVisible) {
await expect(userHeader).toBeVisible();
}
});
test('should display log entries', async ({ page }) => {
// Wait for logs to load
await page.waitForResponse(resp =>
resp.url().includes('/audit') || resp.url().includes('/logs'),
{ timeout: 10000 }
).catch(() => {
// API might not be called if no logs
});
const rows = page.locator('tr, [class*="row"]');
const count = await rows.count();
// At least header row should exist
expect(count >= 0).toBeTruthy();
});
});
test.describe('Filtering', () => {
test('should have search input', async ({ page }) => {
const searchInput = page.getByPlaceholder(/search|filter/i);
const searchVisible = await searchInput.isVisible().catch(() => false);
if (searchVisible) {
await expect(searchInput).toBeEnabled();
}
});
test('should filter by action type', async ({ page }) => {
const typeFilter = page.locator('select, [role="listbox"]').filter({
hasText: /type|action|all|login|create|update|delete/i
}).first();
const filterVisible = await typeFilter.isVisible().catch(() => false);
if (filterVisible) {
await expect(typeFilter).toBeVisible();
}
});
test('should filter by date range', async ({ page }) => {
const dateFilter = page.locator('input[type="date"], [class*="datepicker"]').first();
const dateVisible = await dateFilter.isVisible().catch(() => false);
if (dateVisible) {
await expect(dateFilter).toBeVisible();
}
});
test('should filter by user', async ({ page }) => {
const userFilter = page.locator('select, [role="listbox"]').filter({
hasText: /user|actor|all.*user/i
}).first();
const userVisible = await userFilter.isVisible().catch(() => false);
expect(userVisible !== undefined).toBeTruthy();
});
test('should perform search when input changes', async ({ page }) => {
const searchInput = page.getByPlaceholder(/search|filter/i);
const searchVisible = await searchInput.isVisible().catch(() => false);
if (searchVisible) {
await test.step('Enter search term', async () => {
await searchInput.fill('login');
await page.waitForTimeout(500); // Debounce
});
await test.step('Clear search', async () => {
await searchInput.clear();
});
}
});
});
test.describe('Export Functionality', () => {
test('should have export button', async ({ page }) => {
const exportButton = page.getByRole('button', { name: /export|download|csv/i });
const exportVisible = await exportButton.isVisible().catch(() => false);
if (exportVisible) {
await expect(exportButton).toBeEnabled();
}
});
test('should export logs to CSV', async ({ page }) => {
const exportButton = page.getByRole('button', { name: /export|download|csv/i });
const exportVisible = await exportButton.isVisible().catch(() => false);
if (exportVisible) {
// Set up download handler
const downloadPromise = page.waitForEvent('download', { timeout: 5000 }).catch(() => null);
await exportButton.click();
const download = await downloadPromise;
if (download) {
// Verify download started
expect(download.suggestedFilename()).toMatch(/\.csv$/i);
}
}
});
});
test.describe('Pagination', () => {
test('should have pagination controls', async ({ page }) => {
const pagination = page.locator('[class*="pagination"], nav').filter({
has: page.locator('button, a')
}).first();
const paginationVisible = await pagination.isVisible().catch(() => false);
expect(paginationVisible !== undefined).toBeTruthy();
});
test('should display current page info', async ({ page }) => {
const pageInfo = page.getByText(/page|of|showing|entries/i).first();
const pageInfoVisible = await pageInfo.isVisible().catch(() => false);
expect(pageInfoVisible !== undefined).toBeTruthy();
});
test('should navigate between pages', async ({ page }) => {
const nextButton = page.getByRole('button', { name: /next||»/i });
const nextVisible = await nextButton.isVisible().catch(() => false);
if (nextVisible) {
const isEnabled = await nextButton.isEnabled();
if (isEnabled) {
await test.step('Go to next page', async () => {
await nextButton.click();
await page.waitForTimeout(500);
});
await test.step('Go back to previous page', async () => {
const prevButton = page.getByRole('button', { name: /previous||«/i });
await prevButton.click();
});
}
}
});
});
test.describe('Log Details', () => {
test('should show log details on row click', async ({ page }) => {
const firstRow = page.locator('tr, [class*="row"]').filter({
hasText: /\d{1,2}:\d{2}|login|create|update|delete/i
}).first();
const rowVisible = await firstRow.isVisible().catch(() => false);
if (rowVisible) {
await firstRow.click();
// Should show details modal or expand row
const detailsModal = page.getByRole('dialog');
const modalVisible = await detailsModal.isVisible().catch(() => false);
if (modalVisible) {
const closeButton = page.getByRole('button', { name: /close|cancel/i });
await closeButton.click();
}
}
});
});
test.describe('Refresh', () => {
test('should have refresh button', async ({ page }) => {
const refreshButton = page.getByRole('button', { name: /refresh|reload|sync/i });
const refreshVisible = await refreshButton.isVisible().catch(() => false);
if (refreshVisible) {
await test.step('Click refresh', async () => {
await refreshButton.click();
await page.waitForTimeout(500);
});
}
});
});
test.describe('Navigation', () => {
test('should navigate back to security dashboard', async ({ page }) => {
const backLink = page.getByRole('link', { name: /security|back/i });
const backVisible = await backLink.isVisible().catch(() => false);
if (backVisible) {
await backLink.click();
await waitForLoadingComplete(page);
}
});
});
test.describe('Accessibility', () => {
test('should have accessible table structure', async ({ page }) => {
const table = page.getByRole('table');
const tableVisible = await table.isVisible().catch(() => false);
if (tableVisible) {
// Table should have headers
const headers = page.locator('th, [role="columnheader"]');
const headerCount = await headers.count();
expect(headerCount).toBeGreaterThan(0);
}
});
test('should be keyboard navigable', async ({ page }) => {
// Wait for the app-level loading to complete
try {
await page.waitForSelector('[role="status"]', { state: 'hidden', timeout: 5000 });
} catch {
// Loading might already be done
}
// Wait a moment for focus management
await page.waitForTimeout(300);
// Tab to the first focusable element
await page.keyboard.press('Tab');
await page.waitForTimeout(100);
// Check if any element received focus
const focusedElement = page.locator(':focus');
const hasFocus = await focusedElement.count() > 0;
// Also check if body has focus (acceptable default)
const bodyFocused = await page.evaluate(() => document.activeElement?.tagName === 'BODY');
// Page must have some kind of focus state (either element or body)
// If still loading, the URL being correct is acceptable
const isCorrectPage = page.url().includes('audit-logs');
expect(hasFocus || bodyFocused || isCorrectPage).toBeTruthy();
});
});
test.describe('Empty State', () => {
test('should show empty state message when no logs', async ({ page }) => {
// This is a soft check - logs may or may not exist
const emptyState = page.getByText(/no.*logs|no.*entries|empty|no.*data/i);
const emptyVisible = await emptyState.isVisible().catch(() => false);
// Either empty state or data should be shown
expect(emptyVisible !== undefined).toBeTruthy();
});
});
});

View File

@@ -1,301 +0,0 @@
/**
* CrowdSec Configuration E2E Tests
*
* Tests the CrowdSec configuration page functionality including:
* - Page loading and status display
* - Preset management (view, apply, preview)
* - Configuration file management
* - Import/Export functionality
* - Console enrollment (if enabled)
*
* @see /projects/Charon/docs/plans/current_spec.md - Phase 3
*/
import { test, expect, loginUser } from '../fixtures/auth-fixtures';
import { waitForLoadingComplete, waitForToast } from '../utils/wait-helpers';
test.describe('CrowdSec Configuration', () => {
test.beforeEach(async ({ page, adminUser }) => {
await loginUser(page, adminUser);
await waitForLoadingComplete(page);
await page.goto('/security/crowdsec');
await waitForLoadingComplete(page);
});
test.describe('Page Loading', () => {
test('should display CrowdSec configuration page', async ({ page }) => {
// The page should load without errors
await expect(page.getByRole('heading', { name: /crowdsec/i }).first()).toBeVisible();
});
test('should show navigation back to security dashboard', async ({ page }) => {
// Should have breadcrumb, back link, or navigation element
const backLink = page.getByRole('link', { name: /security|back/i });
const backLinkVisible = await backLink.isVisible().catch(() => false);
if (backLinkVisible) {
await expect(backLink).toBeVisible();
} else {
// Check for navigation button instead
const backButton = page.getByRole('button', { name: /back/i });
const buttonVisible = await backButton.isVisible().catch(() => false);
if (!buttonVisible) {
// Check for breadcrumbs or navigation in header
const breadcrumb = page.locator('nav, [class*="breadcrumb"], [aria-label*="breadcrumb"]');
const breadcrumbVisible = await breadcrumb.isVisible().catch(() => false);
// At minimum, the page should be loaded
if (!breadcrumbVisible) {
await expect(page).toHaveURL(/crowdsec/);
}
}
}
});
test('should display presets section', async ({ page }) => {
// Look for presets, packages, scenarios, or collections section
// This feature may not be fully implemented
const presetsSection = page.getByText(/packages|presets|scenarios|collections|bouncers/i).first();
const presetsVisible = await presetsSection.isVisible().catch(() => false);
if (presetsVisible) {
await expect(presetsSection).toBeVisible();
} else {
test.info().annotations.push({
type: 'info',
description: 'Presets section not visible - feature may not be implemented'
});
}
});
});
test.describe('Preset Management', () => {
// Preset management may not be fully implemented
test('should display list of available presets', async ({ page }) => {
await test.step('Verify presets are listed', async () => {
// Wait for presets to load
await page.waitForResponse(resp =>
resp.url().includes('/presets') || resp.url().includes('/crowdsec') || resp.url().includes('/hub'),
{ timeout: 10000 }
).catch(() => {
// If no API call, presets might be loaded statically or not implemented
});
// Should show preset cards or list items
const presetElements = page.locator('[class*="card"], [class*="preset"], button').filter({
hasText: /apply|install|owasp|basic|advanced|paranoid/i
});
const count = await presetElements.count();
if (count === 0) {
// Presets might not be implemented - check for config file management instead
const configSection = page.getByText(/configuration|file|config/i).first();
const configVisible = await configSection.isVisible().catch(() => false);
if (!configVisible) {
test.info().annotations.push({
type: 'info',
description: 'No presets displayed - feature may not be implemented'
});
}
}
});
});
test('should allow searching presets', async ({ page }) => {
const searchInput = page.getByPlaceholder(/search/i);
const searchVisible = await searchInput.isVisible().catch(() => false);
if (searchVisible) {
await test.step('Search for a preset', async () => {
await searchInput.fill('basic');
// Results should be filtered
await page.waitForTimeout(500); // Debounce
});
}
});
test('should show preset preview when selected', async ({ page }) => {
// Find and click on a preset to preview
const presetButton = page.locator('button').filter({ hasText: /preview|view|select/i }).first();
const buttonVisible = await presetButton.isVisible().catch(() => false);
if (buttonVisible) {
await presetButton.click();
// Should show preview content
await page.waitForTimeout(500);
}
});
test('should apply preset with confirmation', async ({ page }) => {
// Find apply button
const applyButton = page.locator('button').filter({ hasText: /apply/i }).first();
const buttonVisible = await applyButton.isVisible().catch(() => false);
if (buttonVisible) {
await test.step('Click apply button', async () => {
await applyButton.click();
});
await test.step('Handle confirmation or result', async () => {
// Either a confirmation dialog or success toast should appear
const confirmDialog = page.getByRole('dialog');
const dialogVisible = await confirmDialog.isVisible().catch(() => false);
if (dialogVisible) {
// Cancel to not make permanent changes
const cancelButton = page.getByRole('button', { name: /cancel/i });
await cancelButton.click();
}
});
}
});
});
test.describe('Configuration Files', () => {
test('should display configuration file list', async ({ page }) => {
// Look for file selector or list
const fileSelector = page.locator('select, [role="listbox"]').filter({
hasNotText: /sort|filter/i
}).first();
const filesVisible = await fileSelector.isVisible().catch(() => false);
if (filesVisible) {
await expect(fileSelector).toBeVisible();
}
});
test('should show file content when selected', async ({ page }) => {
// Find file selector
const fileSelector = page.locator('select').first();
const selectorVisible = await fileSelector.isVisible().catch(() => false);
if (selectorVisible) {
// Select a file - content area should appear
const contentArea = page.locator('textarea, pre, [class*="editor"]');
const contentVisible = await contentArea.isVisible().catch(() => false);
// Content area may or may not be visible depending on selection
expect(contentVisible !== undefined).toBeTruthy();
}
});
});
test.describe('Import/Export', () => {
test('should have export functionality', async ({ page }) => {
const exportButton = page.getByRole('button', { name: /export/i });
const exportVisible = await exportButton.isVisible().catch(() => false);
if (exportVisible) {
await expect(exportButton).toBeEnabled();
}
});
test('should have import functionality', async ({ page }) => {
// Look for import button or file input
const importButton = page.getByRole('button', { name: /import/i });
const importInput = page.locator('input[type="file"]');
const importVisible = await importButton.isVisible().catch(() => false);
const inputVisible = await importInput.isVisible().catch(() => false);
// Import functionality may not be implemented
if (importVisible || inputVisible) {
expect(importVisible || inputVisible).toBeTruthy();
} else {
test.info().annotations.push({
type: 'info',
description: 'Import functionality not visible - feature may not be implemented'
});
}
});
});
test.describe('Console Enrollment', () => {
test('should display console enrollment section if feature enabled', async ({ page }) => {
// Console enrollment is a feature-flagged component
const enrollmentSection = page.getByTestId('console-section').or(
page.locator('[class*="card"]').filter({ hasText: /console.*enrollment|enroll/i })
);
const enrollmentVisible = await enrollmentSection.isVisible().catch(() => false);
if (enrollmentVisible) {
await test.step('Verify enrollment form elements', async () => {
// Should have token input
const tokenInput = page.getByTestId('crowdsec-token-input').or(
page.getByPlaceholder(/token|key/i)
);
await expect(tokenInput).toBeVisible();
});
} else {
// Feature might be disabled - that's OK
test.info().annotations.push({
type: 'info',
description: 'Console enrollment feature not enabled'
});
}
});
test('should show enrollment status when enrolled', async ({ page }) => {
const statusText = page.getByTestId('console-token-state').or(
page.locator('text=/enrolled|pending|not enrolled/i')
);
const statusVisible = await statusText.isVisible().catch(() => false);
if (statusVisible) {
// Verify status is displayed
await expect(statusText).toBeVisible();
}
});
});
test.describe('Status Indicators', () => {
test('should display CrowdSec running status', async ({ page }) => {
// Look for status indicators
const statusBadge = page.locator('[class*="badge"]').filter({
hasText: /running|stopped|enabled|disabled/i
});
const statusVisible = await statusBadge.first().isVisible().catch(() => false);
if (statusVisible) {
await expect(statusBadge.first()).toBeVisible();
}
});
test('should display LAPI status', async ({ page }) => {
// LAPI ready status might be displayed
const lapiStatus = page.getByText(/lapi.*ready|lapi.*status/i);
const lapiVisible = await lapiStatus.isVisible().catch(() => false);
// LAPI status might not always be visible
expect(lapiVisible !== undefined).toBeTruthy();
});
});
test.describe('Accessibility', () => {
test('should have accessible form controls', async ({ page }) => {
// Check that inputs have associated labels
const inputs = page.locator('input:not([type="hidden"])');
const count = await inputs.count();
for (let i = 0; i < Math.min(count, 5); i++) {
const input = inputs.nth(i);
const visible = await input.isVisible();
if (visible) {
// Input should have some form of label (explicit, aria-label, or placeholder)
const hasLabel = await input.getAttribute('aria-label') ||
await input.getAttribute('placeholder') ||
await input.getAttribute('id');
expect(hasLabel).toBeTruthy();
}
}
});
});
});

View File

@@ -1,251 +0,0 @@
/**
* CrowdSec Decisions (Bans) E2E Tests
*
* Tests the CrowdSec decisions/bans management functionality:
* - Viewing active decisions/bans
* - Adding manual IP bans
* - Removing bans (unban)
* - Decision details and filtering
*
* @see /projects/Charon/docs/plans/current_spec.md - Phase 3
*/
import { test, expect, loginUser } from '../fixtures/auth-fixtures';
import { waitForLoadingComplete, waitForToast } from '../utils/wait-helpers';
// NOTE: The /security/crowdsec/decisions route doesn't exist as a separate page.
// Decisions are displayed within the main CrowdSec config page at /security/crowdsec.
// This test suite is skipped until the dedicated decisions route is implemented.
test.describe.skip('CrowdSec Decisions Management', () => {
test.beforeEach(async ({ page, adminUser }) => {
await loginUser(page, adminUser);
await waitForLoadingComplete(page);
await page.goto('/security/crowdsec');
await waitForLoadingComplete(page);
});
test.describe('Decisions List', () => {
test('should display decisions page', async ({ page }) => {
// The page should load - look for heading or table
const heading = page.getByRole('heading', { name: /decisions|bans/i });
const table = page.getByRole('table');
const grid = page.locator('[class*="grid"], [class*="table"], [class*="list"]');
const headingVisible = await heading.isVisible().catch(() => false);
const tableVisible = await table.isVisible().catch(() => false);
const gridVisible = await grid.first().isVisible().catch(() => false);
// At least one should be visible
expect(headingVisible || tableVisible || gridVisible).toBeTruthy();
});
test('should show active decisions if any exist', async ({ page }) => {
// Wait for decisions to load
await page.waitForResponse(resp =>
resp.url().includes('/decisions') || resp.url().includes('/crowdsec'),
{ timeout: 10000 }
).catch(() => {
// API might not be called if no decisions
});
// Could be empty state or list of decisions
const emptyState = page.getByText(/no.*decisions|no.*bans|empty/i);
const decisionRows = page.locator('tr, [class*="row"]').filter({
hasText: /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|ban|captcha/i
});
const emptyVisible = await emptyState.isVisible().catch(() => false);
const rowCount = await decisionRows.count();
// Either empty state or some decisions
expect(emptyVisible || rowCount >= 0).toBeTruthy();
});
test('should display decision columns (IP, type, duration, reason)', async ({ page }) => {
const table = page.getByRole('table');
const tableVisible = await table.isVisible().catch(() => false);
if (tableVisible) {
await test.step('Verify table headers', async () => {
const headers = page.locator('th, [role="columnheader"]');
const headerTexts = await headers.allTextContents();
// Headers might include IP, type, duration, scope, reason, origin
const headerString = headerTexts.join(' ').toLowerCase();
const hasRelevantHeaders = headerString.includes('ip') ||
headerString.includes('type') ||
headerString.includes('scope') ||
headerString.includes('origin');
expect(hasRelevantHeaders).toBeTruthy();
});
}
});
});
test.describe('Add Decision (Ban IP)', () => {
test('should have add ban button', async ({ page }) => {
const addButton = page.getByRole('button', { name: /add|ban|new/i });
const addButtonVisible = await addButton.isVisible().catch(() => false);
if (addButtonVisible) {
await expect(addButton).toBeEnabled();
} else {
test.info().annotations.push({
type: 'info',
description: 'Add ban functionality might not be exposed in UI'
});
}
});
test('should open ban modal on add button click', async ({ page }) => {
const addButton = page.getByRole('button', { name: /add.*ban|ban.*ip/i });
const addButtonVisible = await addButton.isVisible().catch(() => false);
if (addButtonVisible) {
await addButton.click();
await test.step('Verify ban modal opens', async () => {
const modal = page.getByRole('dialog');
const modalVisible = await modal.isVisible().catch(() => false);
if (modalVisible) {
// Modal should have IP input field
const ipInput = modal.getByPlaceholder(/ip|address/i).or(
modal.locator('input').first()
);
await expect(ipInput).toBeVisible();
// Close modal
const closeButton = modal.getByRole('button', { name: /cancel|close/i });
await closeButton.click();
}
});
}
});
test('should validate IP address format', async ({ page }) => {
const addButton = page.getByRole('button', { name: /add.*ban|ban.*ip/i });
const addButtonVisible = await addButton.isVisible().catch(() => false);
if (addButtonVisible) {
await addButton.click();
const modal = page.getByRole('dialog');
const modalVisible = await modal.isVisible().catch(() => false);
if (modalVisible) {
const ipInput = modal.locator('input').first();
await test.step('Enter invalid IP', async () => {
await ipInput.fill('invalid-ip');
// Look for submit button
const submitButton = modal.getByRole('button', { name: /ban|submit|add/i });
await submitButton.click();
// Should show validation error
await page.waitForTimeout(500);
});
// Close modal
const closeButton = modal.getByRole('button', { name: /cancel|close/i });
const closeVisible = await closeButton.isVisible().catch(() => false);
if (closeVisible) {
await closeButton.click();
}
}
}
});
});
test.describe('Remove Decision (Unban)', () => {
test('should show unban action for each decision', async ({ page }) => {
// If there are decisions, each should have an unban action
const unbanButtons = page.getByRole('button', { name: /unban|remove|delete/i });
const count = await unbanButtons.count();
// Just verify the selector works - actual decisions may or may not exist
expect(count >= 0).toBeTruthy();
});
test('should confirm before unbanning', async ({ page }) => {
const unbanButton = page.getByRole('button', { name: /unban|remove/i }).first();
const unbanVisible = await unbanButton.isVisible().catch(() => false);
if (unbanVisible) {
await unbanButton.click();
// Should show confirmation dialog
const confirmDialog = page.getByRole('dialog');
const dialogVisible = await confirmDialog.isVisible().catch(() => false);
if (dialogVisible) {
// Cancel the action
const cancelButton = page.getByRole('button', { name: /cancel|no/i });
await cancelButton.click();
}
}
});
});
test.describe('Filtering and Search', () => {
test('should have search/filter input', async ({ page }) => {
const searchInput = page.getByPlaceholder(/search|filter/i);
const searchVisible = await searchInput.isVisible().catch(() => false);
if (searchVisible) {
await expect(searchInput).toBeEnabled();
}
});
test('should filter decisions by type', async ({ page }) => {
const typeFilter = page.locator('select, [role="listbox"]').filter({
hasText: /type|all|ban|captcha/i
}).first();
const filterVisible = await typeFilter.isVisible().catch(() => false);
if (filterVisible) {
await expect(typeFilter).toBeVisible();
}
});
});
test.describe('Refresh and Sync', () => {
test('should have refresh button', async ({ page }) => {
const refreshButton = page.getByRole('button', { name: /refresh|sync|reload/i });
const refreshVisible = await refreshButton.isVisible().catch(() => false);
if (refreshVisible) {
await test.step('Click refresh button', async () => {
await refreshButton.click();
// Should trigger API call
await page.waitForTimeout(500);
});
}
});
});
test.describe('Navigation', () => {
test('should navigate back to CrowdSec config', async ({ page }) => {
const backLink = page.getByRole('link', { name: /crowdsec|back|config/i });
const backVisible = await backLink.isVisible().catch(() => false);
if (backVisible) {
await backLink.click();
await waitForLoadingComplete(page);
await expect(page).toHaveURL(/\/security\/crowdsec(?!\/decisions)/);
}
});
});
test.describe('Accessibility', () => {
test('should be keyboard navigable', async ({ page }) => {
await page.keyboard.press('Tab');
// Some element should receive focus
const focusedElement = page.locator(':focus');
await expect(focusedElement).toBeVisible();
});
});
});

View File

@@ -1,227 +0,0 @@
/**
* Rate Limiting E2E Tests
*
* Tests the rate limiting configuration:
* - Page loading and status
* - RPS/Burst settings
* - Time window configuration
* - Per-route settings
*
* @see /projects/Charon/docs/plans/current_spec.md - Phase 3
*/
import { test, expect, loginUser } from '../fixtures/auth-fixtures';
import { waitForLoadingComplete, waitForToast } from '../utils/wait-helpers';
test.describe('Rate Limiting Configuration', () => {
test.beforeEach(async ({ page, adminUser }) => {
await loginUser(page, adminUser);
await waitForLoadingComplete(page);
await page.goto('/security/rate-limiting');
await waitForLoadingComplete(page);
});
test.describe('Page Loading', () => {
test('should display rate limiting configuration page', async ({ page }) => {
const heading = page.getByRole('heading', { name: /rate.*limit/i });
const headingVisible = await heading.isVisible().catch(() => false);
if (!headingVisible) {
const content = page.getByText(/rate.*limit|rps|requests per second/i).first();
await expect(content).toBeVisible();
} else {
await expect(heading).toBeVisible();
}
});
test('should display rate limiting status', async ({ page }) => {
const statusBadge = page.locator('[class*="badge"]').filter({
hasText: /enabled|disabled|active|inactive/i
});
const statusVisible = await statusBadge.first().isVisible().catch(() => false);
expect(statusVisible !== undefined).toBeTruthy();
});
});
test.describe('Rate Limiting Toggle', () => {
test('should have enable/disable toggle', async ({ page }) => {
// The toggle may be on this page or only on the main security dashboard
const toggle = page.getByTestId('toggle-rate-limit').or(
page.locator('input[type="checkbox"]').first()
);
const toggleVisible = await toggle.isVisible().catch(() => false);
if (toggleVisible) {
// Toggle may be disabled if Cerberus is not enabled
const isDisabled = await toggle.isDisabled();
if (!isDisabled) {
await expect(toggle).toBeEnabled();
}
} else {
test.info().annotations.push({
type: 'info',
description: 'Toggle not present on rate limiting config page - located on main security dashboard'
});
}
});
test('should toggle rate limiting on/off', async ({ page }) => {
// The toggle uses checkbox type, not switch role
const toggle = page.locator('input[type="checkbox"]').first();
const toggleVisible = await toggle.isVisible().catch(() => false);
if (toggleVisible) {
const isDisabled = await toggle.isDisabled();
if (isDisabled) {
test.info().annotations.push({
type: 'skip-reason',
description: 'Toggle is disabled - Cerberus may not be enabled'
});
test.skip();
return;
}
await test.step('Toggle rate limiting', async () => {
await toggle.click();
await page.waitForTimeout(500);
});
await test.step('Revert toggle', async () => {
await toggle.click();
await page.waitForTimeout(500);
});
} else {
test.skip();
}
});
});
test.describe('RPS Settings', () => {
test('should display RPS input field', async ({ page }) => {
const rpsInput = page.getByLabel(/rps|requests per second/i).or(
page.locator('input[type="number"]').first()
);
const inputVisible = await rpsInput.isVisible().catch(() => false);
if (inputVisible) {
await expect(rpsInput).toBeEnabled();
}
});
test('should validate RPS input (minimum value)', async ({ page }) => {
const rpsInput = page.getByLabel(/rps|requests per second/i).or(
page.locator('input[type="number"]').first()
);
const inputVisible = await rpsInput.isVisible().catch(() => false);
if (inputVisible) {
const originalValue = await rpsInput.inputValue();
await test.step('Enter invalid RPS value', async () => {
await rpsInput.fill('-1');
await rpsInput.blur();
});
await test.step('Restore original value', async () => {
await rpsInput.fill(originalValue || '100');
});
}
});
test('should accept valid RPS value', async ({ page }) => {
const rpsInput = page.getByLabel(/rps|requests per second/i).or(
page.locator('input[type="number"]').first()
);
const inputVisible = await rpsInput.isVisible().catch(() => false);
if (inputVisible) {
const originalValue = await rpsInput.inputValue();
await test.step('Enter valid RPS value', async () => {
await rpsInput.fill('100');
// Should not show error
});
await test.step('Restore original value', async () => {
await rpsInput.fill(originalValue || '100');
});
}
});
});
test.describe('Burst Settings', () => {
test('should display burst limit input', async ({ page }) => {
const burstInput = page.getByLabel(/burst/i).or(
page.locator('input[type="number"]').nth(1)
);
const inputVisible = await burstInput.isVisible().catch(() => false);
if (inputVisible) {
await expect(burstInput).toBeEnabled();
}
});
});
test.describe('Time Window Settings', () => {
test('should display time window setting', async ({ page }) => {
const windowInput = page.getByLabel(/window|duration|period/i).or(
page.locator('select, input[type="number"]').filter({
hasText: /second|minute|hour/i
}).first()
);
const inputVisible = await windowInput.isVisible().catch(() => false);
expect(inputVisible !== undefined).toBeTruthy();
});
});
test.describe('Save Settings', () => {
test('should have save button', async ({ page }) => {
const saveButton = page.getByRole('button', { name: /save|apply|update/i });
const saveVisible = await saveButton.isVisible().catch(() => false);
if (saveVisible) {
await expect(saveButton).toBeVisible();
}
});
});
test.describe('Navigation', () => {
test('should navigate back to security dashboard', async ({ page }) => {
const backLink = page.getByRole('link', { name: /security|back/i });
const backVisible = await backLink.isVisible().catch(() => false);
if (backVisible) {
await backLink.click();
await waitForLoadingComplete(page);
}
});
});
test.describe('Accessibility', () => {
test('should have labeled input fields', async ({ page }) => {
const inputs = page.locator('input[type="number"]');
const count = await inputs.count();
for (let i = 0; i < Math.min(count, 3); i++) {
const input = inputs.nth(i);
const visible = await input.isVisible();
if (visible) {
const id = await input.getAttribute('id');
const label = await input.getAttribute('aria-label');
const placeholder = await input.getAttribute('placeholder');
// Should have some form of accessible identification
expect(id || label || placeholder).toBeTruthy();
}
}
});
});
});

View File

@@ -1,424 +0,0 @@
/**
* Security Dashboard E2E Tests
*
* Tests the Security (Cerberus) dashboard functionality including:
* - Page loading and layout
* - Module toggle states (CrowdSec, ACL, WAF, Rate Limiting)
* - Status indicators
* - Navigation to sub-pages
*
* @see /projects/Charon/docs/plans/current_spec.md - Phase 3
*/
import { test, expect, loginUser } from '../fixtures/auth-fixtures';
import { request } from '@playwright/test';
import type { APIRequestContext } from '@playwright/test';
import { waitForLoadingComplete, waitForToast } from '../utils/wait-helpers';
import {
captureSecurityState,
restoreSecurityState,
CapturedSecurityState,
} from '../utils/security-helpers';
test.describe('Security Dashboard', () => {
test.beforeEach(async ({ page, adminUser }) => {
await loginUser(page, adminUser);
await waitForLoadingComplete(page);
await page.goto('/security');
await waitForLoadingComplete(page);
});
test.describe('Page Loading', () => {
test('should display security dashboard page title', async ({ page }) => {
await expect(page.getByRole('heading', { name: /security/i }).first()).toBeVisible();
});
test('should display Cerberus dashboard header', async ({ page }) => {
await expect(page.getByText(/cerberus.*dashboard/i)).toBeVisible();
});
test('should show all 4 security module cards', async ({ page }) => {
await test.step('Verify CrowdSec card exists', async () => {
await expect(page.getByText(/crowdsec/i).first()).toBeVisible();
});
await test.step('Verify ACL card exists', async () => {
await expect(page.getByText(/access.*control/i).first()).toBeVisible();
});
await test.step('Verify WAF card exists', async () => {
await expect(page.getByText(/coraza.*waf|waf/i).first()).toBeVisible();
});
await test.step('Verify Rate Limiting card exists', async () => {
await expect(page.getByText(/rate.*limiting/i).first()).toBeVisible();
});
});
test('should display layer badges for each module', async ({ page }) => {
await expect(page.getByText(/layer.*1/i)).toBeVisible();
await expect(page.getByText(/layer.*2/i)).toBeVisible();
await expect(page.getByText(/layer.*3/i)).toBeVisible();
await expect(page.getByText(/layer.*4/i)).toBeVisible();
});
test('should show audit logs button in header', async ({ page }) => {
const auditLogsButton = page.getByRole('button', { name: /audit.*logs/i });
await expect(auditLogsButton).toBeVisible();
});
test('should show docs button in header', async ({ page }) => {
const docsButton = page.getByRole('button', { name: /docs/i });
await expect(docsButton).toBeVisible();
});
});
test.describe('Module Status Indicators', () => {
test('should show enabled/disabled badge for each module', async ({ page }) => {
// Each card should have an enabled or disabled badge
// Look for text that matches enabled/disabled patterns
// The Badge component may use various styling approaches
await page.waitForTimeout(500); // Wait for UI to settle
const enabledTexts = page.getByText(/^enabled$/i);
const disabledTexts = page.getByText(/^disabled$/i);
const enabledCount = await enabledTexts.count();
const disabledCount = await disabledTexts.count();
// Should have at least 4 status badges (one per security layer card)
expect(enabledCount + disabledCount).toBeGreaterThanOrEqual(4);
});
test('should display CrowdSec toggle switch', async ({ page }) => {
const toggle = page.getByTestId('toggle-crowdsec');
await expect(toggle).toBeVisible();
});
test('should display ACL toggle switch', async ({ page }) => {
const toggle = page.getByTestId('toggle-acl');
await expect(toggle).toBeVisible();
});
test('should display WAF toggle switch', async ({ page }) => {
const toggle = page.getByTestId('toggle-waf');
await expect(toggle).toBeVisible();
});
test('should display Rate Limiting toggle switch', async ({ page }) => {
const toggle = page.getByTestId('toggle-rate-limit');
await expect(toggle).toBeVisible();
});
});
test.describe('Module Toggle Actions', () => {
// Capture state ONCE for this describe block
let originalState: CapturedSecurityState;
test.beforeAll(async ({ request: reqFixture }) => {
try {
originalState = await captureSecurityState(reqFixture);
} catch (error) {
console.warn('Could not capture initial security state:', error);
}
});
test.afterAll(async () => {
// CRITICAL: Restore original state even if tests fail
if (!originalState) {
return;
}
// Create fresh request context for cleanup (cannot reuse fixture from beforeAll)
const cleanupRequest = await request.newContext({
baseURL: 'http://localhost:8080',
});
try {
await restoreSecurityState(cleanupRequest, originalState);
console.log('✓ Security state restored after toggle tests');
} catch (error) {
console.error('Failed to restore security state:', error);
} finally {
await cleanupRequest.dispose();
}
});
test('should toggle ACL enabled/disabled', async ({ page }) => {
const toggle = page.getByTestId('toggle-acl');
const isDisabled = await toggle.isDisabled();
if (isDisabled) {
test.info().annotations.push({
type: 'skip-reason',
description: 'Toggle is disabled because Cerberus security is not enabled',
});
test.skip();
return;
}
await test.step('Toggle ACL state', async () => {
await page.waitForLoadState('networkidle');
await toggle.scrollIntoViewIfNeeded();
await page.waitForTimeout(200);
await toggle.click({ force: true });
await waitForToast(page, /updated|success|enabled|disabled/i, 10000);
});
// NOTE: Do NOT toggle back here - afterAll handles cleanup
});
test('should toggle WAF enabled/disabled', async ({ page }) => {
const toggle = page.getByTestId('toggle-waf');
const isDisabled = await toggle.isDisabled();
if (isDisabled) {
test.info().annotations.push({
type: 'skip-reason',
description: 'Toggle is disabled because Cerberus security is not enabled',
});
test.skip();
return;
}
await test.step('Toggle WAF state', async () => {
await page.waitForLoadState('networkidle');
await toggle.scrollIntoViewIfNeeded();
await page.waitForTimeout(200);
await toggle.click({ force: true });
await waitForToast(page, /updated|success|enabled|disabled/i, 10000);
});
// NOTE: Do NOT toggle back here - afterAll handles cleanup
});
test('should toggle Rate Limiting enabled/disabled', async ({ page }) => {
const toggle = page.getByTestId('toggle-rate-limit');
const isDisabled = await toggle.isDisabled();
if (isDisabled) {
test.info().annotations.push({
type: 'skip-reason',
description: 'Toggle is disabled because Cerberus security is not enabled',
});
test.skip();
return;
}
await test.step('Toggle Rate Limit state', async () => {
await page.waitForLoadState('networkidle');
await toggle.scrollIntoViewIfNeeded();
await page.waitForTimeout(200);
await toggle.click({ force: true });
await waitForToast(page, /updated|success|enabled|disabled/i, 10000);
});
// NOTE: Do NOT toggle back here - afterAll handles cleanup
});
test('should persist toggle state after page reload', async ({ page }) => {
const toggle = page.getByTestId('toggle-acl');
const isDisabled = await toggle.isDisabled();
if (isDisabled) {
test.info().annotations.push({
type: 'skip-reason',
description: 'Toggle is disabled because Cerberus security is not enabled',
});
test.skip();
return;
}
const initialChecked = await toggle.isChecked();
await test.step('Toggle ACL state', async () => {
await page.waitForLoadState('networkidle');
await toggle.scrollIntoViewIfNeeded();
await page.waitForTimeout(200);
await toggle.click({ force: true });
await waitForToast(page, /updated|success|enabled|disabled/i, 10000);
});
await test.step('Reload page', async () => {
await page.reload();
await waitForLoadingComplete(page);
});
await test.step('Verify state persisted', async () => {
const newChecked = await page.getByTestId('toggle-acl').isChecked();
expect(newChecked).toBe(!initialChecked);
});
// NOTE: Do NOT restore here - afterAll handles cleanup
});
});
test.describe('Navigation', () => {
test('should navigate to CrowdSec page when configure clicked', async ({ page }) => {
// Find the CrowdSec card by locating the configure button within a container that has CrowdSec text
// Cards use rounded-lg border classes, not [class*="card"]
const crowdsecSection = page.locator('div').filter({ hasText: /crowdsec/i }).filter({ has: page.getByRole('button', { name: /configure/i }) }).first();
const configureButton = crowdsecSection.getByRole('button', { name: /configure/i });
// Button may be disabled when Cerberus is off
const isDisabled = await configureButton.isDisabled().catch(() => true);
if (isDisabled) {
test.info().annotations.push({
type: 'skip-reason',
description: 'Configure button is disabled because Cerberus security is not enabled'
});
test.skip();
return;
}
// Wait for any loading overlays to disappear
await page.waitForLoadState('networkidle');
await page.waitForTimeout(300);
// Scroll element into view and use force click to bypass pointer interception
await configureButton.scrollIntoViewIfNeeded();
await configureButton.click({ force: true });
await expect(page).toHaveURL(/\/security\/crowdsec/);
});
test('should navigate to Access Lists page when clicked', async ({ page }) => {
// The ACL card has a "Manage Lists" or "Configure" button
// Find button by looking for buttons with matching text within the page
const allConfigButtons = page.getByRole('button', { name: /manage.*lists|configure/i });
const count = await allConfigButtons.count();
// The ACL button should be the second configure button (after CrowdSec)
// Or we can find it near the "Access Control" or "ACL" text
let aclButton = null;
for (let i = 0; i < count; i++) {
const btn = allConfigButtons.nth(i);
const btnText = await btn.textContent();
// The ACL button says "Manage Lists" when enabled, "Configure" when disabled
if (btnText?.match(/manage.*lists/i)) {
aclButton = btn;
break;
}
}
// Fallback to second configure button if no "Manage Lists" found
if (!aclButton) {
aclButton = allConfigButtons.nth(1);
}
// Wait for any loading overlays and scroll into view
await page.waitForLoadState('networkidle');
await aclButton.scrollIntoViewIfNeeded();
await page.waitForTimeout(200);
await aclButton.click({ force: true });
await expect(page).toHaveURL(/\/security\/access-lists|\/access-lists/);
});
test('should navigate to WAF page when configure clicked', async ({ page }) => {
// WAF is Layer 3 - the third configure button in the security cards grid
const allConfigButtons = page.getByRole('button', { name: /configure/i });
const count = await allConfigButtons.count();
// Should have at least 3 configure buttons (CrowdSec, ACL/Manage Lists, WAF)
if (count < 3) {
test.info().annotations.push({
type: 'skip-reason',
description: 'Not enough configure buttons found on page'
});
test.skip();
return;
}
// WAF is the 3rd configure button (index 2)
const wafButton = allConfigButtons.nth(2);
// Wait and scroll into view
await page.waitForLoadState('networkidle');
await wafButton.scrollIntoViewIfNeeded();
await page.waitForTimeout(200);
await wafButton.click({ force: true });
await expect(page).toHaveURL(/\/security\/waf/);
});
test('should navigate to Rate Limiting page when configure clicked', async ({ page }) => {
// Rate Limiting is Layer 4 - the fourth configure button in the security cards grid
const allConfigButtons = page.getByRole('button', { name: /configure/i });
const count = await allConfigButtons.count();
// Should have at least 4 configure buttons
if (count < 4) {
test.info().annotations.push({
type: 'skip-reason',
description: 'Not enough configure buttons found on page'
});
test.skip();
return;
}
// Rate Limit is the 4th configure button (index 3)
const rateLimitButton = allConfigButtons.nth(3);
// Wait and scroll into view
await page.waitForLoadState('networkidle');
await rateLimitButton.scrollIntoViewIfNeeded();
await page.waitForTimeout(200);
await rateLimitButton.click({ force: true });
await expect(page).toHaveURL(/\/security\/rate-limiting/);
});
test('should navigate to Audit Logs page', async ({ page }) => {
const auditLogsButton = page.getByRole('button', { name: /audit.*logs/i });
await auditLogsButton.click();
await expect(page).toHaveURL(/\/security\/audit-logs/);
});
});
test.describe('Admin Whitelist', () => {
test('should display admin whitelist section when Cerberus enabled', async ({ page }) => {
// Check if the admin whitelist input is visible (only shown when Cerberus is enabled)
const whitelistInput = page.getByPlaceholder(/192\.168|cidr/i);
const isVisible = await whitelistInput.isVisible().catch(() => false);
if (isVisible) {
await expect(whitelistInput).toBeVisible();
} else {
// Cerberus might be disabled - just verify the page loaded correctly
// by checking for the Cerberus Dashboard header which is always visible
const cerberusHeader = page.getByText(/cerberus.*dashboard/i);
await expect(cerberusHeader).toBeVisible();
test.info().annotations.push({
type: 'info',
description: 'Admin whitelist section not visible - Cerberus may be disabled'
});
}
});
});
test.describe('Accessibility', () => {
test('should have accessible toggle switches with labels', async ({ page }) => {
// Each toggle should be within a tooltip that describes its purpose
// The Switch component uses an input[type="checkbox"] under the hood
const toggles = [
page.getByTestId('toggle-crowdsec'),
page.getByTestId('toggle-acl'),
page.getByTestId('toggle-waf'),
page.getByTestId('toggle-rate-limit'),
];
for (const toggle of toggles) {
await expect(toggle).toBeVisible();
// Switch uses checkbox input type (visually styled as toggle)
await expect(toggle).toHaveAttribute('type', 'checkbox');
}
});
test('should navigate with keyboard', async ({ page }) => {
await test.step('Tab through header buttons', async () => {
await page.keyboard.press('Tab');
await page.keyboard.press('Tab');
// Should be able to tab to various interactive elements
});
});
});
});

View File

@@ -1,233 +0,0 @@
/**
* Security Headers E2E Tests
*
* Tests the security headers configuration:
* - Page loading and status
* - Header profile management (CRUD)
* - Preset selection
* - Header score display
* - Individual header configuration
*
* @see /projects/Charon/docs/plans/current_spec.md - Phase 3
*/
import { test, expect, loginUser } from '../fixtures/auth-fixtures';
import { waitForLoadingComplete, waitForToast } from '../utils/wait-helpers';
test.describe('Security Headers Configuration', () => {
test.beforeEach(async ({ page, adminUser }) => {
await loginUser(page, adminUser);
await waitForLoadingComplete(page);
await page.goto('/security/headers');
await waitForLoadingComplete(page);
});
test.describe('Page Loading', () => {
test('should display security headers page', async ({ page }) => {
const heading = page.getByRole('heading', { name: /security.*headers|headers/i });
const headingVisible = await heading.isVisible().catch(() => false);
if (!headingVisible) {
const content = page.getByText(/security.*headers|csp|hsts|x-frame/i).first();
await expect(content).toBeVisible();
} else {
await expect(heading).toBeVisible();
}
});
});
test.describe('Header Score Display', () => {
test('should display security score', async ({ page }) => {
const scoreDisplay = page.getByText(/score|grade|rating/i).first();
const scoreVisible = await scoreDisplay.isVisible().catch(() => false);
if (scoreVisible) {
await expect(scoreDisplay).toBeVisible();
}
});
test('should show score breakdown', async ({ page }) => {
const scoreDetails = page.locator('[class*="score"], [class*="grade"]').filter({
hasText: /a|b|c|d|f|\d+%/i
});
const detailsVisible = await scoreDetails.first().isVisible().catch(() => false);
expect(detailsVisible !== undefined).toBeTruthy();
});
});
test.describe('Preset Profiles', () => {
test('should display preset profiles', async ({ page }) => {
const presetSection = page.getByText(/preset|profile|template/i).first();
const presetVisible = await presetSection.isVisible().catch(() => false);
if (presetVisible) {
await expect(presetSection).toBeVisible();
}
});
test('should have preset options (Basic, Strict, Custom)', async ({ page }) => {
const presets = page.locator('button, [role="option"]').filter({
hasText: /basic|strict|custom|minimal|paranoid/i
});
const count = await presets.count();
expect(count >= 0).toBeTruthy();
});
test('should apply preset when selected', async ({ page }) => {
const presetButton = page.locator('button').filter({
hasText: /basic|strict|apply/i
}).first();
const presetVisible = await presetButton.isVisible().catch(() => false);
if (presetVisible) {
await test.step('Click preset button', async () => {
await presetButton.click();
await page.waitForTimeout(500);
});
}
});
});
test.describe('Individual Header Configuration', () => {
test('should display CSP (Content-Security-Policy) settings', async ({ page }) => {
const cspSection = page.getByText(/content-security-policy|csp/i).first();
const cspVisible = await cspSection.isVisible().catch(() => false);
if (cspVisible) {
await expect(cspSection).toBeVisible();
}
});
test('should display HSTS settings', async ({ page }) => {
const hstsSection = page.getByText(/strict-transport-security|hsts/i).first();
const hstsVisible = await hstsSection.isVisible().catch(() => false);
if (hstsVisible) {
await expect(hstsSection).toBeVisible();
}
});
test('should display X-Frame-Options settings', async ({ page }) => {
const xframeSection = page.getByText(/x-frame-options|frame/i).first();
const xframeVisible = await xframeSection.isVisible().catch(() => false);
expect(xframeVisible !== undefined).toBeTruthy();
});
test('should display X-Content-Type-Options settings', async ({ page }) => {
const xctSection = page.getByText(/x-content-type|nosniff/i).first();
const xctVisible = await xctSection.isVisible().catch(() => false);
expect(xctVisible !== undefined).toBeTruthy();
});
});
test.describe('Header Toggle Controls', () => {
test('should have toggles for individual headers', async ({ page }) => {
const toggles = page.locator('[role="switch"]');
const count = await toggles.count();
// Should have multiple header toggles
expect(count >= 0).toBeTruthy();
});
test('should toggle header on/off', async ({ page }) => {
const toggle = page.locator('[role="switch"]').first();
const toggleVisible = await toggle.isVisible().catch(() => false);
if (toggleVisible) {
await test.step('Toggle header', async () => {
await toggle.click();
await page.waitForTimeout(500);
});
await test.step('Revert toggle', async () => {
await toggle.click();
await page.waitForTimeout(500);
});
}
});
});
test.describe('Profile Management', () => {
test('should have create profile button', async ({ page }) => {
const createButton = page.getByRole('button', { name: /create|new|add.*profile/i });
const createVisible = await createButton.isVisible().catch(() => false);
if (createVisible) {
await expect(createButton).toBeEnabled();
}
});
test('should open profile creation modal', async ({ page }) => {
const createButton = page.getByRole('button', { name: /create|new.*profile/i });
const createVisible = await createButton.isVisible().catch(() => false);
if (createVisible) {
await createButton.click();
const modal = page.getByRole('dialog');
const modalVisible = await modal.isVisible().catch(() => false);
if (modalVisible) {
// Close modal
const closeButton = page.getByRole('button', { name: /cancel|close/i });
await closeButton.click();
}
}
});
test('should list existing profiles', async ({ page }) => {
const profileList = page.locator('[class*="list"], [class*="grid"]').filter({
has: page.locator('[class*="card"], tr, [class*="item"]')
}).first();
const listVisible = await profileList.isVisible().catch(() => false);
expect(listVisible !== undefined).toBeTruthy();
});
});
test.describe('Save Configuration', () => {
test('should have save button', async ({ page }) => {
const saveButton = page.getByRole('button', { name: /save|apply|update/i });
const saveVisible = await saveButton.isVisible().catch(() => false);
if (saveVisible) {
await expect(saveButton).toBeVisible();
}
});
});
test.describe('Navigation', () => {
test('should navigate back to security dashboard', async ({ page }) => {
const backLink = page.getByRole('link', { name: /security|back/i });
const backVisible = await backLink.isVisible().catch(() => false);
if (backVisible) {
await backLink.click();
await waitForLoadingComplete(page);
}
});
});
test.describe('Accessibility', () => {
test('should have accessible toggle controls', async ({ page }) => {
const toggles = page.locator('[role="switch"]');
const count = await toggles.count();
for (let i = 0; i < Math.min(count, 5); i++) {
const toggle = toggles.nth(i);
const visible = await toggle.isVisible();
if (visible) {
// Toggle should have accessible state
const checked = await toggle.getAttribute('aria-checked');
expect(['true', 'false', 'mixed'].includes(checked || '')).toBeTruthy();
}
}
});
});
});

View File

@@ -1,236 +0,0 @@
/**
* WAF (Coraza) Configuration E2E Tests
*
* Tests the Web Application Firewall configuration:
* - Page loading and status display
* - WAF mode toggle (blocking/detection)
* - Ruleset management
* - Rule group configuration
* - Whitelist/exclusions
*
* @see /projects/Charon/docs/plans/current_spec.md - Phase 3
*/
import { test, expect, loginUser } from '../fixtures/auth-fixtures';
import { waitForLoadingComplete, waitForToast } from '../utils/wait-helpers';
test.describe('WAF Configuration', () => {
test.beforeEach(async ({ page, adminUser }) => {
await loginUser(page, adminUser);
await waitForLoadingComplete(page);
await page.goto('/security/waf');
await waitForLoadingComplete(page);
});
test.describe('Page Loading', () => {
test('should display WAF configuration page', async ({ page }) => {
// Page should load with WAF heading or content
const heading = page.getByRole('heading', { name: /waf|firewall|coraza/i });
const headingVisible = await heading.isVisible().catch(() => false);
if (!headingVisible) {
// Might have different page structure
const wafContent = page.getByText(/web application firewall|waf|coraza/i).first();
await expect(wafContent).toBeVisible();
} else {
await expect(heading).toBeVisible();
}
});
test('should display WAF status indicator', async ({ page }) => {
const statusBadge = page.locator('[class*="badge"]').filter({
hasText: /enabled|disabled|blocking|detection|active/i
});
const statusVisible = await statusBadge.first().isVisible().catch(() => false);
if (statusVisible) {
await expect(statusBadge.first()).toBeVisible();
}
});
});
test.describe('WAF Mode Toggle', () => {
test('should display current WAF mode', async ({ page }) => {
const modeIndicator = page.getByText(/blocking|detection|mode/i).first();
const modeVisible = await modeIndicator.isVisible().catch(() => false);
if (modeVisible) {
await expect(modeIndicator).toBeVisible();
}
});
test('should have mode toggle switch or selector', async ({ page }) => {
const modeToggle = page.getByTestId('waf-mode-toggle').or(
page.locator('button, [role="switch"]').filter({
hasText: /blocking|detection/i
})
);
const toggleVisible = await modeToggle.isVisible().catch(() => false);
if (toggleVisible) {
await expect(modeToggle).toBeEnabled();
}
});
test('should toggle between blocking and detection mode', async ({ page }) => {
const modeSwitch = page.locator('[role="switch"]').first();
const switchVisible = await modeSwitch.isVisible().catch(() => false);
if (switchVisible) {
await test.step('Click mode switch', async () => {
await modeSwitch.click();
await page.waitForTimeout(500);
});
await test.step('Revert mode switch', async () => {
await modeSwitch.click();
await page.waitForTimeout(500);
});
}
});
});
test.describe('Ruleset Management', () => {
test('should display available rulesets', async ({ page }) => {
const rulesetSection = page.getByText(/ruleset|rules|owasp|crs/i).first();
await expect(rulesetSection).toBeVisible();
});
test('should show rule groups with toggle controls', async ({ page }) => {
// Each rule group should be toggleable
const ruleGroupToggles = page.locator('[role="switch"], input[type="checkbox"]').filter({
has: page.locator('text=/sql|xss|rce|lfi|rfi|scanner/i')
});
// Count available toggles
const count = await ruleGroupToggles.count().catch(() => 0);
expect(count >= 0).toBeTruthy();
});
test('should allow enabling/disabling rule groups', async ({ page }) => {
const ruleToggle = page.locator('[role="switch"]').first();
const toggleVisible = await ruleToggle.isVisible().catch(() => false);
if (toggleVisible) {
// Record initial state
const wasPressed = await ruleToggle.getAttribute('aria-pressed') === 'true' ||
await ruleToggle.getAttribute('aria-checked') === 'true';
await test.step('Toggle rule group', async () => {
await ruleToggle.click();
await page.waitForTimeout(500);
});
await test.step('Restore original state', async () => {
await ruleToggle.click();
await page.waitForTimeout(500);
});
}
});
});
test.describe('Anomaly Threshold', () => {
test('should display anomaly threshold setting', async ({ page }) => {
const thresholdSection = page.getByText(/threshold|score|anomaly/i).first();
const thresholdVisible = await thresholdSection.isVisible().catch(() => false);
if (thresholdVisible) {
await expect(thresholdSection).toBeVisible();
}
});
test('should have threshold input control', async ({ page }) => {
const thresholdInput = page.locator('input[type="number"], input[type="range"]').filter({
has: page.locator('text=/threshold|score/i')
}).first();
const inputVisible = await thresholdInput.isVisible().catch(() => false);
// Threshold control might not be visible on all pages
expect(inputVisible !== undefined).toBeTruthy();
});
});
test.describe('Whitelist/Exclusions', () => {
test('should display whitelist section', async ({ page }) => {
const whitelistSection = page.getByText(/whitelist|exclusion|exception|ignore/i).first();
const whitelistVisible = await whitelistSection.isVisible().catch(() => false);
if (whitelistVisible) {
await expect(whitelistSection).toBeVisible();
}
});
test('should have ability to add whitelist entries', async ({ page }) => {
const addButton = page.getByRole('button', { name: /add.*whitelist|add.*exclusion|add.*exception/i });
const addVisible = await addButton.isVisible().catch(() => false);
if (addVisible) {
await expect(addButton).toBeEnabled();
}
});
});
test.describe('Save and Apply', () => {
test('should have save button', async ({ page }) => {
const saveButton = page.getByRole('button', { name: /save|apply|update/i });
const saveVisible = await saveButton.isVisible().catch(() => false);
if (saveVisible) {
await expect(saveButton).toBeVisible();
}
});
test('should show confirmation on save', async ({ page }) => {
const saveButton = page.getByRole('button', { name: /save|apply/i });
const saveVisible = await saveButton.isVisible().catch(() => false);
if (saveVisible) {
await saveButton.click();
// Should show either confirmation dialog or success toast
const dialog = page.getByRole('dialog');
const dialogVisible = await dialog.isVisible().catch(() => false);
if (dialogVisible) {
const cancelButton = page.getByRole('button', { name: /cancel|close/i });
await cancelButton.click();
}
}
});
});
test.describe('Navigation', () => {
test('should navigate back to security dashboard', async ({ page }) => {
const backLink = page.getByRole('link', { name: /security|back/i });
const backVisible = await backLink.isVisible().catch(() => false);
if (backVisible) {
await backLink.click();
await waitForLoadingComplete(page);
await expect(page).toHaveURL(/\/security(?!\/waf)/);
}
});
});
test.describe('Accessibility', () => {
test('should have accessible controls', async ({ page }) => {
const switches = page.locator('[role="switch"]');
const count = await switches.count();
for (let i = 0; i < Math.min(count, 5); i++) {
const switchEl = switches.nth(i);
const visible = await switchEl.isVisible();
if (visible) {
// Each switch should have accessible name
const name = await switchEl.getAttribute('aria-label') ||
await switchEl.getAttribute('aria-labelledby');
// Some form of accessible name should exist
}
}
});
});
});