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 authority:tenants.read advisory:read vex:read exceptions:read exceptions:approve aoc:verify findings:read orch:read vuln:view vuln:investigate vuln:operate vuln:audit', 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('triage workflow: pills navigate + open drawer', async ({ page }) => { await page.goto('/triage/artifacts/asset-web-prod'); await expect(page.getByRole('heading', { name: 'Artifact triage' })).toBeVisible({ timeout: 10000 }); await expect(page.getByRole('tab', { name: 'Evidence' })).toHaveAttribute('aria-selected', 'true'); const reachabilityPill = page.getByRole('button', { name: /^Reachability:/ }); const vexPill = page.getByRole('button', { name: /^VEX:/ }); await expect(reachabilityPill).toBeVisible(); await expect(vexPill).toBeVisible(); await reachabilityPill.click(); await expect(page.getByRole('tab', { name: 'Reachability' })).toHaveAttribute('aria-selected', 'true'); await expect(page.locator('#triage-panel-reachability')).toBeVisible(); await vexPill.click(); const drawer = page.getByRole('dialog', { name: 'Record Decision' }); await expect(drawer).toHaveClass(/open/); await drawer.getByRole('button', { name: 'Close drawer' }).click(); await expect(drawer).not.toHaveClass(/open/); }); test('triage workflow: record decision opens VEX modal', async ({ page }) => { await page.goto('/triage/artifacts/asset-web-prod'); await expect(page.getByRole('heading', { name: 'Artifact triage' })).toBeVisible({ timeout: 10000 }); await page.getByRole('tab', { name: 'Evidence' }).click(); await expect(page.locator('#triage-panel-evidence')).toBeVisible(); await page.getByRole('button', { name: 'Record Decision' }).first().click(); const drawer = page.getByRole('dialog', { name: 'Record Decision' }); await expect(drawer).toHaveClass(/open/); const submit = drawer.getByRole('button', { name: 'Record Decision' }); await expect(submit).toBeDisabled(); await drawer.getByLabel('Select reason').selectOption('component_not_present'); await drawer.getByLabel('Additional notes').fill('E2E: not affected via quickstart fixture'); await expect(submit).toBeEnabled(); await submit.click(); await expect(drawer).not.toHaveClass(/open/); const vexDialog = page.getByRole('dialog', { name: 'VEX decision' }); await expect(vexDialog).toBeVisible({ timeout: 10000 }); await page.getByRole('button', { name: 'Close VEX decision dialog' }).click(); await expect(vexDialog).toBeHidden(); });