294 lines
10 KiB
TypeScript
294 lines
10 KiB
TypeScript
// -----------------------------------------------------------------------------
|
|
// ux-components-visual.spec.ts
|
|
// Sprint: SPRINT_20260117_018_FE_ux_components
|
|
// Task: UXC-008 - Integration tests with Playwright
|
|
// Description: Visual regression tests for new UX components
|
|
// -----------------------------------------------------------------------------
|
|
|
|
import { expect, test } from '@playwright/test';
|
|
|
|
import { policyAuthorSession } from '../../src/app/testing';
|
|
|
|
const mockConfig = {
|
|
authority: {
|
|
issuer: 'https://authority.local',
|
|
clientId: 'stella-ops-ui',
|
|
authorizeEndpoint: 'https://authority.local/connect/authorize',
|
|
tokenEndpoint: 'https://authority.local/connect/token',
|
|
logoutEndpoint: 'https://authority.local/connect/logout',
|
|
redirectUri: 'http://127.0.0.1:4400/auth/callback',
|
|
postLogoutRedirectUri: 'http://127.0.0.1:4400/',
|
|
scope: 'openid profile email ui.read findings:read vuln:view binary:read',
|
|
audience: 'https://scanner.local',
|
|
dpopAlgorithms: ['ES256'],
|
|
refreshLeewaySeconds: 60,
|
|
},
|
|
apiBaseUrls: {
|
|
authority: 'https://authority.local',
|
|
scanner: 'https://scanner.local',
|
|
policy: 'https://scanner.local',
|
|
concelier: 'https://concelier.local',
|
|
attestor: 'https://attestor.local',
|
|
},
|
|
quickstartMode: true,
|
|
};
|
|
|
|
test.beforeEach(async ({ page }) => {
|
|
await page.addInitScript((session) => {
|
|
try {
|
|
window.sessionStorage.clear();
|
|
} catch {
|
|
// ignore storage errors in restricted contexts
|
|
}
|
|
(window as any).__stellaopsTestSession = session;
|
|
}, policyAuthorSession);
|
|
|
|
await page.route('**/config.json', (route) =>
|
|
route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify(mockConfig),
|
|
})
|
|
);
|
|
|
|
await page.route('https://authority.local/**', (route) => route.abort());
|
|
});
|
|
|
|
test.describe.skip('UX Components Visual Regression' /* TODO: Visual regression tests depend on filter-strip, triage-card, binary-diff components that need selector alignment */, () => {
|
|
test.describe('Triage Card', () => {
|
|
test('default state screenshot', async ({ page }) => {
|
|
await page.goto('/triage/artifacts/test-artifact');
|
|
await expect(page.locator('.triage-card').first()).toBeVisible({ timeout: 10000 });
|
|
|
|
// Wait for any animations to complete
|
|
await page.waitForTimeout(500);
|
|
|
|
// Take screenshot of first triage card
|
|
await expect(page.locator('.triage-card').first()).toHaveScreenshot('triage-card-default.png', {
|
|
maxDiffPixelRatio: 0.02,
|
|
});
|
|
});
|
|
|
|
test('hover state screenshot', async ({ page }) => {
|
|
await page.goto('/triage/artifacts/test-artifact');
|
|
const card = page.locator('.triage-card').first();
|
|
await expect(card).toBeVisible({ timeout: 10000 });
|
|
|
|
// Hover over card
|
|
await card.hover();
|
|
await page.waitForTimeout(300);
|
|
|
|
await expect(card).toHaveScreenshot('triage-card-hover.png', {
|
|
maxDiffPixelRatio: 0.02,
|
|
});
|
|
});
|
|
|
|
test('expanded verification state screenshot', async ({ page }) => {
|
|
await page.goto('/triage/artifacts/test-artifact');
|
|
const card = page.locator('.triage-card').first();
|
|
await expect(card).toBeVisible({ timeout: 10000 });
|
|
|
|
// Click Rekor Verify and wait for expansion
|
|
await page.getByRole('button', { name: /Rekor Verify/ }).first().click();
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Screenshot expanded state
|
|
await expect(card).toHaveScreenshot('triage-card-expanded.png', {
|
|
maxDiffPixelRatio: 0.05,
|
|
});
|
|
});
|
|
|
|
test('risk chip variants screenshot', async ({ page }) => {
|
|
await page.goto('/triage/artifacts/test-artifact');
|
|
await expect(page.locator('.risk-chip').first()).toBeVisible({ timeout: 10000 });
|
|
|
|
// Screenshot all risk chips
|
|
const riskChips = page.locator('.risk-chip');
|
|
for (let i = 0; i < Math.min(4, await riskChips.count()); i++) {
|
|
await expect(riskChips.nth(i)).toHaveScreenshot(`risk-chip-variant-${i}.png`, {
|
|
maxDiffPixelRatio: 0.02,
|
|
});
|
|
}
|
|
});
|
|
});
|
|
|
|
test.describe('Filter Strip', () => {
|
|
test('default state screenshot', async ({ page }) => {
|
|
await page.goto('/triage/artifacts/test-artifact');
|
|
await expect(page.locator('.filter-strip')).toBeVisible({ timeout: 10000 });
|
|
|
|
await page.waitForTimeout(500);
|
|
|
|
await expect(page.locator('.filter-strip')).toHaveScreenshot('filter-strip-default.png', {
|
|
maxDiffPixelRatio: 0.02,
|
|
});
|
|
});
|
|
|
|
test('with filters active screenshot', async ({ page }) => {
|
|
await page.goto('/triage/artifacts/test-artifact');
|
|
await expect(page.locator('.filter-strip')).toBeVisible({ timeout: 10000 });
|
|
|
|
// Activate some filters
|
|
await page.getByLabel(/Only reachable/i).check();
|
|
await page.locator('#epss-slider').fill('50');
|
|
|
|
await page.waitForTimeout(300);
|
|
|
|
await expect(page.locator('.filter-strip')).toHaveScreenshot('filter-strip-active.png', {
|
|
maxDiffPixelRatio: 0.02,
|
|
});
|
|
});
|
|
|
|
test('deterministic toggle states screenshot', async ({ page }) => {
|
|
await page.goto('/triage/artifacts/test-artifact');
|
|
await expect(page.locator('.filter-strip')).toBeVisible({ timeout: 10000 });
|
|
|
|
const toggle = page.locator('.determinism-toggle');
|
|
|
|
// Active state (default)
|
|
await expect(toggle).toHaveScreenshot('determinism-toggle-active.png', {
|
|
maxDiffPixelRatio: 0.02,
|
|
});
|
|
|
|
// Inactive state
|
|
await toggle.click();
|
|
await page.waitForTimeout(300);
|
|
|
|
await expect(toggle).toHaveScreenshot('determinism-toggle-inactive.png', {
|
|
maxDiffPixelRatio: 0.02,
|
|
});
|
|
});
|
|
});
|
|
|
|
test.describe('Binary-Diff Panel', () => {
|
|
test('default state screenshot', async ({ page }) => {
|
|
await page.goto('/sbom/diff/sha256:base123/sha256:head456');
|
|
await expect(page.locator('.binary-diff-panel')).toBeVisible({ timeout: 10000 });
|
|
|
|
await page.waitForTimeout(500);
|
|
|
|
await expect(page.locator('.binary-diff-panel')).toHaveScreenshot('binary-diff-panel-default.png', {
|
|
maxDiffPixelRatio: 0.02,
|
|
});
|
|
});
|
|
|
|
test('scope selector states screenshot', async ({ page }) => {
|
|
await page.goto('/sbom/diff/sha256:base123/sha256:head456');
|
|
await expect(page.locator('.binary-diff-panel')).toBeVisible({ timeout: 10000 });
|
|
|
|
const scopeSelector = page.locator('.scope-selector');
|
|
|
|
// File scope (default)
|
|
await expect(scopeSelector).toHaveScreenshot('scope-selector-file.png', {
|
|
maxDiffPixelRatio: 0.02,
|
|
});
|
|
|
|
// Section scope
|
|
await page.getByRole('button', { name: /Section/i }).click();
|
|
await page.waitForTimeout(300);
|
|
await expect(scopeSelector).toHaveScreenshot('scope-selector-section.png', {
|
|
maxDiffPixelRatio: 0.02,
|
|
});
|
|
|
|
// Function scope
|
|
await page.getByRole('button', { name: /Function/i }).click();
|
|
await page.waitForTimeout(300);
|
|
await expect(scopeSelector).toHaveScreenshot('scope-selector-function.png', {
|
|
maxDiffPixelRatio: 0.02,
|
|
});
|
|
});
|
|
|
|
test('tree item change indicators screenshot', async ({ page }) => {
|
|
await page.goto('/sbom/diff/sha256:base123/sha256:head456');
|
|
await expect(page.locator('.binary-diff-panel')).toBeVisible({ timeout: 10000 });
|
|
|
|
const tree = page.locator('.scope-tree');
|
|
|
|
await expect(tree).toHaveScreenshot('diff-tree-items.png', {
|
|
maxDiffPixelRatio: 0.02,
|
|
});
|
|
});
|
|
|
|
test('diff view lines screenshot', async ({ page }) => {
|
|
await page.goto('/sbom/diff/sha256:base123/sha256:head456');
|
|
await expect(page.locator('.binary-diff-panel')).toBeVisible({ timeout: 10000 });
|
|
|
|
// Select an entry to show diff
|
|
await page.locator('.tree-item').first().click();
|
|
await page.waitForTimeout(300);
|
|
|
|
const diffView = page.locator('.diff-view');
|
|
|
|
await expect(diffView).toHaveScreenshot('diff-view-lines.png', {
|
|
maxDiffPixelRatio: 0.02,
|
|
});
|
|
});
|
|
});
|
|
|
|
test.describe('Dark Mode', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
// Enable dark mode
|
|
await page.emulateMedia({ colorScheme: 'dark' });
|
|
});
|
|
|
|
test('triage card dark mode screenshot', async ({ page }) => {
|
|
await page.goto('/triage/artifacts/test-artifact');
|
|
await expect(page.locator('.triage-card').first()).toBeVisible({ timeout: 10000 });
|
|
|
|
await page.waitForTimeout(500);
|
|
|
|
await expect(page.locator('.triage-card').first()).toHaveScreenshot('triage-card-dark.png', {
|
|
maxDiffPixelRatio: 0.02,
|
|
});
|
|
});
|
|
|
|
test('filter strip dark mode screenshot', async ({ page }) => {
|
|
await page.goto('/triage/artifacts/test-artifact');
|
|
await expect(page.locator('.filter-strip')).toBeVisible({ timeout: 10000 });
|
|
|
|
await page.waitForTimeout(500);
|
|
|
|
await expect(page.locator('.filter-strip')).toHaveScreenshot('filter-strip-dark.png', {
|
|
maxDiffPixelRatio: 0.02,
|
|
});
|
|
});
|
|
|
|
test('binary diff panel dark mode screenshot', async ({ page }) => {
|
|
await page.goto('/sbom/diff/sha256:base123/sha256:head456');
|
|
await expect(page.locator('.binary-diff-panel')).toBeVisible({ timeout: 10000 });
|
|
|
|
await page.waitForTimeout(500);
|
|
|
|
await expect(page.locator('.binary-diff-panel')).toHaveScreenshot('binary-diff-panel-dark.png', {
|
|
maxDiffPixelRatio: 0.02,
|
|
});
|
|
});
|
|
});
|
|
|
|
test.describe('Responsive', () => {
|
|
test('filter strip mobile viewport', async ({ page }) => {
|
|
await page.setViewportSize({ width: 375, height: 667 });
|
|
await page.goto('/triage/artifacts/test-artifact');
|
|
await expect(page.locator('.filter-strip')).toBeVisible({ timeout: 10000 });
|
|
|
|
await page.waitForTimeout(500);
|
|
|
|
await expect(page.locator('.filter-strip')).toHaveScreenshot('filter-strip-mobile.png', {
|
|
maxDiffPixelRatio: 0.05,
|
|
});
|
|
});
|
|
|
|
test('triage card mobile viewport', async ({ page }) => {
|
|
await page.setViewportSize({ width: 375, height: 667 });
|
|
await page.goto('/triage/artifacts/test-artifact');
|
|
await expect(page.locator('.triage-card').first()).toBeVisible({ timeout: 10000 });
|
|
|
|
await page.waitForTimeout(500);
|
|
|
|
await expect(page.locator('.triage-card').first()).toHaveScreenshot('triage-card-mobile.png', {
|
|
maxDiffPixelRatio: 0.05,
|
|
});
|
|
});
|
|
});
|
|
});
|