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>
45 lines
2.3 KiB
TypeScript
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)
|
|
})
|