diff --git a/.archive/legacy-tests-phase3/frontend-e2e/security-mobile.spec.ts b/.archive/legacy-tests-phase3/frontend-e2e/security-mobile.spec.ts new file mode 100644 index 00000000..f31660e4 --- /dev/null +++ b/.archive/legacy-tests-phase3/frontend-e2e/security-mobile.spec.ts @@ -0,0 +1,297 @@ +/** + * Security Dashboard Mobile Responsive E2E Tests + * Test IDs: MR-01 through MR-10 + * + * Tests mobile viewport (375x667), tablet viewport (768x1024), + * touch targets, scrolling, and layout responsiveness. + */ +import { test, expect } from '@bgotink/playwright-coverage' + +const base = process.env.CHARON_BASE_URL || 'http://localhost:8080' + +test.describe('Security Dashboard Mobile (375x667)', () => { + test.use({ viewport: { width: 375, height: 667 } }) + + test('MR-01: cards stack vertically on mobile', async ({ page }) => { + await page.goto(`${base}/security`) + + // Wait for page to load + await page.waitForSelector('[data-testid="toggle-crowdsec"]', { timeout: 10000 }) + + // On mobile, grid should be single column + const grid = page.locator('.grid.grid-cols-1') + await expect(grid).toBeVisible() + + // Get the computed grid-template-columns + const cardsContainer = page.locator('.grid').first() + const gridStyle = await cardsContainer.evaluate((el) => { + const style = window.getComputedStyle(el) + return style.gridTemplateColumns + }) + + // Single column should have just one value (not multiple columns like "repeat(4, ...)") + const columns = gridStyle.split(' ').filter((s) => s.trim().length > 0) + expect(columns.length).toBeLessThanOrEqual(2) // Single column or flexible + }) + + test('MR-04: toggle switches have accessible touch targets', async ({ page }) => { + await page.goto(`${base}/security`) + await page.waitForSelector('[data-testid="toggle-crowdsec"]', { timeout: 10000 }) + + // Check CrowdSec toggle + const crowdsecToggle = page.getByTestId('toggle-crowdsec') + const crowdsecBox = await crowdsecToggle.boundingBox() + + // Touch target should be at least 24px (component) + padding + // Most switches have a reasonable touch target + expect(crowdsecBox).not.toBeNull() + if (crowdsecBox) { + expect(crowdsecBox.height).toBeGreaterThanOrEqual(20) + expect(crowdsecBox.width).toBeGreaterThanOrEqual(35) + } + + // Check WAF toggle + const wafToggle = page.getByTestId('toggle-waf') + const wafBox = await wafToggle.boundingBox() + expect(wafBox).not.toBeNull() + if (wafBox) { + expect(wafBox.height).toBeGreaterThanOrEqual(20) + } + }) + + test('MR-05: config buttons are tappable on mobile', async ({ page }) => { + await page.goto(`${base}/security`) + await page.waitForSelector('[data-testid="toggle-crowdsec"]', { timeout: 10000 }) + + // Find config/configure buttons + const configButtons = page.locator('button:has-text("Config"), button:has-text("Configure")') + const buttonCount = await configButtons.count() + + expect(buttonCount).toBeGreaterThan(0) + + // Check first config button has reasonable size + const firstButton = configButtons.first() + const box = await firstButton.boundingBox() + expect(box).not.toBeNull() + if (box) { + expect(box.height).toBeGreaterThanOrEqual(28) // Minimum tap height + } + }) + + test('MR-06: page content is scrollable on mobile', async ({ page }) => { + await page.goto(`${base}/security`) + await page.waitForSelector('[data-testid="toggle-crowdsec"]', { timeout: 10000 }) + + // Check if page is scrollable (content height > viewport) + const bodyHeight = await page.evaluate(() => document.body.scrollHeight) + const viewportHeight = 667 + + // If content is taller than viewport, page should scroll + if (bodyHeight > viewportHeight) { + // Attempt to scroll down + await page.evaluate(() => window.scrollBy(0, 200)) + const scrollY = await page.evaluate(() => window.scrollY) + expect(scrollY).toBeGreaterThan(0) + } + }) + + test('MR-10: navigation is accessible on mobile', async ({ page }) => { + await page.goto(`${base}/security`) + await page.waitForSelector('[data-testid="toggle-crowdsec"]', { timeout: 10000 }) + + // On mobile, there should be some form of navigation + // Check if sidebar or mobile menu toggle exists + const sidebar = page.locator('nav, aside, [role="navigation"]') + const sidebarCount = await sidebar.count() + + // Navigation should exist in some form + expect(sidebarCount).toBeGreaterThanOrEqual(0) // May be hidden on mobile + }) + + test('MR-06b: overlay renders correctly on mobile', async ({ page }) => { + await page.goto(`${base}/security`) + await page.waitForSelector('[data-testid="toggle-crowdsec"]', { timeout: 10000 }) + + // Skip if Cerberus is disabled (toggles would be disabled) + const cerberusDisabled = await page.locator('text=Cerberus Disabled').isVisible() + if (cerberusDisabled) { + test.skip() + return + } + + // Trigger loading state by clicking a toggle + const wafToggle = page.getByTestId('toggle-waf') + const isDisabled = await wafToggle.isDisabled() + + if (!isDisabled) { + await wafToggle.click() + + // Check for overlay (may appear briefly) + // Use a short timeout since it might disappear quickly + try { + const overlay = page.locator('.fixed.inset-0') + await overlay.waitFor({ state: 'visible', timeout: 2000 }) + + // If overlay appeared, verify it fits screen + const box = await overlay.boundingBox() + if (box) { + expect(box.width).toBeLessThanOrEqual(375 + 10) // Allow small margin + } + } catch { + // Overlay might have disappeared before we could check + // This is acceptable for a fast operation + } + } + }) +}) + +test.describe('Security Dashboard Tablet (768x1024)', () => { + test.use({ viewport: { width: 768, height: 1024 } }) + + test('MR-02: cards show 2 columns on tablet', async ({ page }) => { + await page.goto(`${base}/security`) + await page.waitForSelector('[data-testid="toggle-crowdsec"]', { timeout: 10000 }) + + // On tablet (md breakpoint), should have md:grid-cols-2 + const grid = page.locator('.grid').first() + await expect(grid).toBeVisible() + + // Get computed style + const gridStyle = await grid.evaluate((el) => { + const style = window.getComputedStyle(el) + return style.gridTemplateColumns + }) + + // Should have 2 columns at md breakpoint + const columns = gridStyle.split(' ').filter((s) => s.trim().length > 0 && s !== 'none') + expect(columns.length).toBeGreaterThanOrEqual(2) + }) + + test('MR-08: cards have proper spacing on tablet', async ({ page }) => { + await page.goto(`${base}/security`) + await page.waitForSelector('[data-testid="toggle-crowdsec"]', { timeout: 10000 }) + + // Check gap between cards + const grid = page.locator('.grid.gap-6').first() + const hasGap = await grid.isVisible() + expect(hasGap).toBe(true) + }) +}) + +test.describe('Security Dashboard Desktop (1920x1080)', () => { + test.use({ viewport: { width: 1920, height: 1080 } }) + + test('MR-03: cards show 4 columns on desktop', async ({ page }) => { + await page.goto(`${base}/security`) + await page.waitForSelector('[data-testid="toggle-crowdsec"]', { timeout: 10000 }) + + // On desktop (lg breakpoint), should have lg:grid-cols-4 + const grid = page.locator('.grid').first() + await expect(grid).toBeVisible() + + // Get computed style + const gridStyle = await grid.evaluate((el) => { + const style = window.getComputedStyle(el) + return style.gridTemplateColumns + }) + + // Should have 4 columns at lg breakpoint + const columns = gridStyle.split(' ').filter((s) => s.trim().length > 0 && s !== 'none') + expect(columns.length).toBeGreaterThanOrEqual(4) + }) +}) + +test.describe('Security Dashboard Layout Tests', () => { + test('cards maintain correct order across viewports', async ({ page }) => { + // Test on mobile + await page.setViewportSize({ width: 375, height: 667 }) + await page.goto(`${base}/security`) + await page.waitForSelector('[data-testid="toggle-crowdsec"]', { timeout: 10000 }) + + // Get card headings + const getCardOrder = async () => { + const headings = await page.locator('h3').allTextContents() + return headings.filter((h) => ['CrowdSec', 'Access Control', 'Coraza', 'Rate Limiting'].includes(h)) + } + + const mobileOrder = await getCardOrder() + + // Test on tablet + await page.setViewportSize({ width: 768, height: 1024 }) + await page.waitForTimeout(100) // Allow reflow + const tabletOrder = await getCardOrder() + + // Test on desktop + await page.setViewportSize({ width: 1920, height: 1080 }) + await page.waitForTimeout(100) // Allow reflow + const desktopOrder = await getCardOrder() + + // Order should be consistent + expect(mobileOrder).toEqual(tabletOrder) + expect(tabletOrder).toEqual(desktopOrder) + expect(desktopOrder).toEqual(['CrowdSec', 'Access Control', 'Coraza', 'Rate Limiting']) + }) + + test('MR-09: all security cards are visible on scroll', async ({ page }) => { + await page.setViewportSize({ width: 375, height: 667 }) + await page.goto(`${base}/security`) + await page.waitForSelector('[data-testid="toggle-crowdsec"]', { timeout: 10000 }) + + // Scroll to each card type + const cardTypes = ['CrowdSec', 'Access Control', 'Coraza', 'Rate Limiting'] + + for (const cardType of cardTypes) { + const card = page.locator(`h3:has-text("${cardType}")`) + await card.scrollIntoViewIfNeeded() + await expect(card).toBeVisible() + } + }) +}) + +test.describe('Security Dashboard Interaction Tests', () => { + test.use({ viewport: { width: 375, height: 667 } }) + + test('MR-07: config buttons navigate correctly on mobile', async ({ page }) => { + await page.goto(`${base}/security`) + await page.waitForSelector('[data-testid="toggle-crowdsec"]', { timeout: 10000 }) + + // Skip if Cerberus disabled + const cerberusDisabled = await page.locator('text=Cerberus Disabled').isVisible() + if (cerberusDisabled) { + test.skip() + return + } + + // Find and click WAF Configure button + const configureButton = page.locator('button:has-text("Configure")').first() + + if (await configureButton.isVisible()) { + await configureButton.click() + + // Should navigate to a config page + await page.waitForTimeout(500) + const url = page.url() + + // URL should include security/waf or security/rate-limiting etc + expect(url).toMatch(/security\/(waf|rate-limiting|access-lists|crowdsec)/i) + } + }) + + test('documentation button works on mobile', async ({ page }) => { + await page.goto(`${base}/security`) + await page.waitForSelector('[data-testid="toggle-crowdsec"]', { timeout: 10000 }) + + // Find documentation button + const docButton = page.locator('button:has-text("Documentation"), a:has-text("Documentation")').first() + + if (await docButton.isVisible()) { + // Check it has correct external link behavior + const href = await docButton.getAttribute('href') + + // Should open external docs + if (href) { + expect(href).toContain('wikid82.github.io') + } + } + }) +}) diff --git a/.archive/legacy-tests-phase3/frontend-e2e/waf.spec.ts b/.archive/legacy-tests-phase3/frontend-e2e/waf.spec.ts new file mode 100644 index 00000000..8cf53121 --- /dev/null +++ b/.archive/legacy-tests-phase3/frontend-e2e/waf.spec.ts @@ -0,0 +1,34 @@ +import { test, expect } from '@bgotink/playwright-coverage' + +const base = process.env.CHARON_BASE_URL || 'http://localhost:8080' + +// Hit an API route inside /api/v1 to ensure Cerberus middleware executes. +const targetPath = '/api/v1/system/my-ip' + +test.describe('WAF blocking and monitoring', () => { + test('blocks malicious query when mode=block', async ({ request }) => { + // Use literal '