// ----------------------------------------------------------------------------- // triage-card.spec.ts // Sprint: SPRINT_20260117_018_FE_ux_components // Task: UXC-008 - Integration tests with Playwright // Description: Playwright e2e tests for Triage Card component // ----------------------------------------------------------------------------- import { expect, test } from '@playwright/test'; import { policyAuthorSession } from '../../src/app/testing'; const mockConfig = { authority: { issuer: 'https://authority.local', clientId: 'stellaops-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 vuln:investigate', 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, }; const mockTriageData = { vulnId: 'CVE-2024-1234', packageName: 'lodash', packageVersion: '4.17.20', scope: 'direct', riskScore: 8.5, riskReason: 'High CVSS + Exploited', evidence: [ { type: 'openvex', status: 'verified', value: 'not_affected' }, { type: 'patch-proof', status: 'verified' }, { type: 'reachability', status: 'pending', value: 'analyzing' }, { type: 'epss', status: 'verified', value: 0.67 }, ], digest: 'sha256:abc123def456789012345678901234567890123456789012345678901234', attestationDigest: 'sha256:attestation123456789012345678901234567890123456789012', }; 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('Triage Card Component' /* TODO: Triage card selectors need alignment with actual triage workspace DOM */, () => { test('renders vulnerability information correctly', async ({ page }) => { await page.goto('/triage/artifacts/test-artifact'); await expect(page.getByRole('article', { name: /CVE-2024/ })).toBeVisible({ timeout: 10000 }); // Verify header content await expect(page.getByText('CVE-2024-1234')).toBeVisible(); await expect(page.getByText('lodash@4.17.20')).toBeVisible(); await expect(page.getByText('direct')).toBeVisible(); // Verify risk chip const riskChip = page.locator('.risk-chip'); await expect(riskChip).toContainText('8.5'); }); test('displays evidence chips with correct status', async ({ page }) => { await page.goto('/triage/artifacts/test-artifact'); await expect(page.getByRole('article', { name: /CVE-2024/ })).toBeVisible({ timeout: 10000 }); // Verify evidence chips await expect(page.getByRole('button', { name: /OpenVEX/ })).toBeVisible(); await expect(page.getByRole('button', { name: /Patch Proof/ })).toBeVisible(); await expect(page.getByRole('button', { name: /Reachability/ })).toBeVisible(); await expect(page.getByRole('button', { name: /EPSS/ })).toBeVisible(); }); test('action buttons are visible and functional', async ({ page }) => { await page.goto('/triage/artifacts/test-artifact'); await expect(page.getByRole('article', { name: /CVE-2024/ })).toBeVisible({ timeout: 10000 }); // Verify action buttons await expect(page.getByRole('button', { name: /Explain/ })).toBeVisible(); await expect(page.getByRole('button', { name: /Create task/ })).toBeVisible(); await expect(page.getByRole('button', { name: /Mute/ })).toBeVisible(); await expect(page.getByRole('button', { name: /Export/ })).toBeVisible(); await expect(page.getByRole('button', { name: /Rekor Verify/ })).toBeVisible(); }); test('keyboard shortcut V triggers Rekor Verify', async ({ page }) => { await page.goto('/triage/artifacts/test-artifact'); const card = page.getByRole('article', { name: /CVE-2024/ }); await expect(card).toBeVisible({ timeout: 10000 }); // Focus the card and press V await card.focus(); await page.keyboard.press('v'); // Verify loading state or verification panel appears await expect( page.getByText('Verifying...').or(page.getByText('Verified')).or(page.getByText('Rekor Verification Details')) ).toBeVisible({ timeout: 5000 }); }); test('keyboard shortcut M triggers Mute action', async ({ page }) => { await page.goto('/triage/artifacts/test-artifact'); const card = page.getByRole('article', { name: /CVE-2024/ }); await expect(card).toBeVisible({ timeout: 10000 }); // Focus the card and press M await card.focus(); await page.keyboard.press('m'); // Verify mute action was triggered (modal or confirmation) // This depends on implementation - checking for any response await expect(page.locator('[role="dialog"]').or(page.getByText(/mute/i))).toBeVisible({ timeout: 3000 }); }); test('keyboard shortcut E triggers Export action', async ({ page }) => { await page.goto('/triage/artifacts/test-artifact'); const card = page.getByRole('article', { name: /CVE-2024/ }); await expect(card).toBeVisible({ timeout: 10000 }); // Focus the card and press E await card.focus(); await page.keyboard.press('e'); // Verify export action was triggered await expect(page.locator('[role="dialog"]').or(page.getByText(/export/i))).toBeVisible({ timeout: 3000 }); }); test('Rekor Verify expands verification panel', async ({ page }) => { await page.goto('/triage/artifacts/test-artifact'); await expect(page.getByRole('article', { name: /CVE-2024/ })).toBeVisible({ timeout: 10000 }); // Click Rekor Verify button await page.getByRole('button', { name: /Rekor Verify/ }).click(); // Wait for verification to complete await expect(page.getByText('Rekor Verification Details')).toBeVisible({ timeout: 10000 }); // Verify details are displayed await expect(page.getByText('Subject')).toBeVisible(); await expect(page.getByText('Issuer')).toBeVisible(); await expect(page.getByText('Timestamp')).toBeVisible(); await expect(page.getByText('Rekor Index')).toBeVisible(); }); test('copy buttons work for digest and Rekor entry', async ({ page }) => { await page.goto('/triage/artifacts/test-artifact'); await expect(page.getByRole('article', { name: /CVE-2024/ })).toBeVisible({ timeout: 10000 }); // Find and click copy button for digest const copyBtn = page.getByRole('button', { name: /Copy digest/ }); await expect(copyBtn).toBeVisible(); // Click and verify clipboard (mock) await copyBtn.click(); // Clipboard API may not be available in test context, but button should be clickable }); test('evidence chips show tooltips on hover', async ({ page }) => { await page.goto('/triage/artifacts/test-artifact'); await expect(page.getByRole('article', { name: /CVE-2024/ })).toBeVisible({ timeout: 10000 }); // Hover over evidence chip const chip = page.getByRole('button', { name: /OpenVEX/ }); await chip.hover(); // Verify tooltip appears (title attribute) await expect(chip).toHaveAttribute('title'); }); });