Files
Infrastruktur-Monitoring-Sy…/frontend/e2e/vuln-scopes.spec.ts
Dennis Nemec 278b5e47a3 Roll findings up per package and image, expandable to their CVEs
The findings table listed every CVE, which is thousands of rows on a real
host. It now shows one row per affected package (host) or image
(containers) with its severity split, how many findings it has and how
many of them have a fix. Clicking a row loads and shows the CVEs of that
group; collapsing keeps them cached.

The rollup is a GROUP BY in SQLite behind a new groups endpoint, so the
page loads a few dozen rows instead of the full finding list, and the
flat list gained a package filter to expand one group.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 20:02:25 +02:00

45 lines
2.3 KiB
TypeScript

import { test, expect, type Page } from '@playwright/test'
async function scan(page: Page) {
await page.goto('/login')
await page.getByLabel('Email').fill('admin@example.com')
await page.getByLabel('Password').fill('admin-password-123')
await page.getByRole('button', { name: 'Sign in' }).click()
await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible()
await page.goto('/vulnerabilities')
await page.getByRole('button', { name: 'Scan now' }).click()
await expect(page.getByTestId('scope-host-critical')).not.toHaveText('0', { timeout: 20_000 })
}
test('host and container findings are separated into two categories', async ({ page }) => {
await scan(page)
// both categories are visible with their own numbers
await expect(page.getByTestId('scope-host')).toContainText('OS, packages and applications')
await expect(page.getByTestId('scope-container')).toContainText('images')
await expect(page.getByTestId('scope-container-critical')).not.toHaveText('0')
// host is selected first and lists host packages with their source
await expect(page.getByTestId('scope-host')).toHaveAttribute('aria-pressed', 'true')
await expect(page.getByRole('row', { name: /zlib1g/ })).toBeVisible()
await expect(page.getByRole('row', { name: /gitea\/gitea/ })).toHaveCount(0)
await expect(page.getByTestId('findings-scroll')).toContainText('Source')
// switching to containers swaps the table
await page.getByTestId('scope-container').click()
await expect(page.getByTestId('scope-container')).toHaveAttribute('aria-pressed', 'true')
await expect(page.getByRole('row', { name: /gitea\/gitea/ }).first()).toBeVisible()
await expect(page.getByRole('row', { name: /zlib1g/ })).toHaveCount(0)
await expect(page.getByTestId('findings-scroll')).toContainText('Image')
// the filter is labelled per category and only offers targets of that category
const images = await page.getByLabel('Image', { exact: true }).locator('option').allTextContents()
expect(images.some((o) => o.includes('gitea'))).toBe(true)
expect(images.some((o) => o.startsWith('os'))).toBe(false)
await page.getByTestId('scope-host').click()
const hosts = await page.getByLabel('Target', { exact: true }).locator('option').allTextContents()
expect(hosts.some((o) => o.startsWith('os'))).toBe(true)
expect(hosts.some((o) => o.includes('gitea'))).toBe(false)
})