Files
Charon/.archive/legacy-tests-phase3/frontend-e2e/security-mobile.spec.ts
GitHub Actions af7a942162 fix(e2e):end-to-end tests for Security Dashboard and WAF functionality
- Implemented mobile and tablet responsive tests for the Security Dashboard, covering layout, touch targets, and navigation.
- Added WAF blocking and monitoring tests to validate API responses under different conditions.
- Created smoke tests for the login page to ensure no console errors on load.
- Updated README with migration options for various configurations.
- Documented Phase 3 blocker remediation, including frontend coverage generation and test results.
- Temporarily skipped failing Security tests due to WebSocket mock issues, with clear documentation for future resolution.
- Enhanced integration test timeout for complex scenarios and improved error handling in TestDataManager.
2026-02-02 22:55:41 +00:00

298 lines
11 KiB
TypeScript

/**
* 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')
}
}
})
})