Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Findings Ledger CI / build-test (push) Has been cancelled
Findings Ledger CI / migration-validation (push) Has been cancelled
Findings Ledger CI / generate-manifest (push) Has been cancelled
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Lighthouse CI / Lighthouse Audit (push) Has been cancelled
Lighthouse CI / Axe Accessibility Audit (push) Has been cancelled
Manifest Integrity / Validate Contract Documents (push) Has been cancelled
Manifest Integrity / Validate Pack Fixtures (push) Has been cancelled
Manifest Integrity / Audit SHA256SUMS Files (push) Has been cancelled
Manifest Integrity / Verify Merkle Roots (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Policy Simulation / policy-simulate (push) Has been cancelled
- Implemented tests for Cryptographic Failures (A02) to ensure proper handling of sensitive data, secure algorithms, and key management. - Added tests for Security Misconfiguration (A05) to validate production configurations, security headers, CORS settings, and feature management. - Developed tests for Authentication Failures (A07) to enforce strong password policies, rate limiting, session management, and MFA support. - Created tests for Software and Data Integrity Failures (A08) to verify artifact signatures, SBOM integrity, attestation chains, and feed updates.
314 lines
11 KiB
TypeScript
314 lines
11 KiB
TypeScript
/**
|
|
* Evidence Panel Micro-Interactions E2E Tests
|
|
* SPRINT_0341_0001_0001 - T8: Playwright tests for EvidencePanel micro-interactions
|
|
*
|
|
* Tests the "Verify locally" commands, copy affordances, and ProofSpine interactions.
|
|
*/
|
|
|
|
import { test, expect, Page } from '@playwright/test';
|
|
|
|
// Test fixtures for deterministic evidence data
|
|
const MOCK_EVIDENCE = {
|
|
digest: 'sha256:abc123def456',
|
|
artifactPurl: 'pkg:oci/myimage@sha256:abc123def456',
|
|
sbomDigest: 'sha256:sbom789012',
|
|
rekorLogIndex: 12345678,
|
|
rekorLogId: 'test-rekor-log-id',
|
|
bundleDigest: 'sha256:bundle456789'
|
|
};
|
|
|
|
test.describe('Evidence Panel - Verify Locally Commands', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
// Navigate to evidence panel with mock data
|
|
await page.goto('/evidence?digest=' + MOCK_EVIDENCE.digest);
|
|
|
|
// Wait for evidence panel to load
|
|
await page.waitForSelector('.evidence-panel');
|
|
|
|
// Navigate to Linkset tab
|
|
await page.click('[role="tab"]:has-text("Linkset")');
|
|
await page.waitForSelector('.linkset-panel');
|
|
});
|
|
|
|
test('should display verify locally section when linkset has verification data', async ({ page }) => {
|
|
// Arrange - Wait for verify section
|
|
const verifySection = page.locator('.linkset-panel__verify');
|
|
|
|
// Assert
|
|
await expect(verifySection).toBeVisible();
|
|
await expect(verifySection.locator('h4')).toHaveText('Verify Locally');
|
|
await expect(verifySection.locator('.linkset-panel__verify-description')).toContainText(
|
|
'independently verify the evidence chain'
|
|
);
|
|
});
|
|
|
|
test('should display artifact signature verification command', async ({ page }) => {
|
|
// Arrange
|
|
const verifyCommands = page.locator('.verify-command');
|
|
|
|
// Find the artifact signature command
|
|
const signatureCommand = verifyCommands.filter({ hasText: 'Verify Artifact Signature' });
|
|
|
|
// Assert
|
|
await expect(signatureCommand).toBeVisible();
|
|
await expect(signatureCommand.locator('.verify-command__description')).toContainText('Cosign');
|
|
|
|
// The command should contain the artifact reference
|
|
const codeBlock = signatureCommand.locator('.verify-command__code code');
|
|
await expect(codeBlock).toContainText('cosign verify');
|
|
});
|
|
|
|
test('should display SBOM attestation verification command', async ({ page }) => {
|
|
// Arrange
|
|
const verifyCommands = page.locator('.verify-command');
|
|
|
|
// Find the SBOM attestation command
|
|
const sbomCommand = verifyCommands.filter({ hasText: 'Verify SBOM Attestation' });
|
|
|
|
// Assert
|
|
await expect(sbomCommand).toBeVisible();
|
|
await expect(sbomCommand.locator('.verify-command__description')).toContainText('attestation');
|
|
|
|
// The command should contain the predicate type
|
|
const codeBlock = sbomCommand.locator('.verify-command__code code');
|
|
await expect(codeBlock).toContainText('--type spdxjson');
|
|
});
|
|
|
|
test('should display Rekor transparency verification command when available', async ({ page }) => {
|
|
// Arrange
|
|
const verifyCommands = page.locator('.verify-command');
|
|
|
|
// Find the Rekor command
|
|
const rekorCommand = verifyCommands.filter({ hasText: 'Verify Transparency Log' });
|
|
|
|
// Assert
|
|
await expect(rekorCommand).toBeVisible();
|
|
await expect(rekorCommand.locator('.verify-command__description')).toContainText('Rekor');
|
|
|
|
// The command should contain rekor-cli
|
|
const codeBlock = rekorCommand.locator('.verify-command__code code');
|
|
await expect(codeBlock).toContainText('rekor-cli get');
|
|
});
|
|
|
|
test('should display policy decision verification command', async ({ page }) => {
|
|
// Arrange
|
|
const verifyCommands = page.locator('.verify-command');
|
|
|
|
// Find the policy command
|
|
const policyCommand = verifyCommands.filter({ hasText: 'Verify Policy Decision' });
|
|
|
|
// Assert
|
|
await expect(policyCommand).toBeVisible();
|
|
|
|
// The command should contain stella policy
|
|
const codeBlock = policyCommand.locator('.verify-command__code code');
|
|
await expect(codeBlock).toContainText('stella policy verify');
|
|
});
|
|
});
|
|
|
|
test.describe('Evidence Panel - Copy Interactions', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await page.goto('/evidence?digest=' + MOCK_EVIDENCE.digest);
|
|
await page.waitForSelector('.evidence-panel');
|
|
await page.click('[role="tab"]:has-text("Linkset")');
|
|
await page.waitForSelector('.linkset-panel__verify');
|
|
});
|
|
|
|
test('should copy verification command on copy button click', async ({ page }) => {
|
|
// Arrange
|
|
const copyButton = page.locator('.verify-command__copy').first();
|
|
|
|
// Act
|
|
await copyButton.click();
|
|
|
|
// Assert - button should show "Copied!" state
|
|
await expect(copyButton).toHaveText('Copied!');
|
|
await expect(copyButton).toHaveClass(/copied/);
|
|
});
|
|
|
|
test('should reset copy button state after delay', async ({ page }) => {
|
|
// Arrange
|
|
const copyButton = page.locator('.verify-command__copy').first();
|
|
|
|
// Act
|
|
await copyButton.click();
|
|
await expect(copyButton).toHaveText('Copied!');
|
|
|
|
// Wait for reset (typically 2-3 seconds)
|
|
await page.waitForTimeout(3500);
|
|
|
|
// Assert - button should reset to "Copy"
|
|
await expect(copyButton).toHaveText('Copy');
|
|
await expect(copyButton).not.toHaveClass(/copied/);
|
|
});
|
|
|
|
test('should copy correct command text to clipboard', async ({ page, context }) => {
|
|
// Grant clipboard permissions
|
|
await context.grantPermissions(['clipboard-read', 'clipboard-write']);
|
|
|
|
// Arrange
|
|
const firstCommand = page.locator('.verify-command').first();
|
|
const expectedCommand = await firstCommand.locator('.verify-command__code code').textContent();
|
|
|
|
// Act
|
|
await firstCommand.locator('.verify-command__copy').click();
|
|
|
|
// Assert - check clipboard content
|
|
const clipboardText = await page.evaluate(() => navigator.clipboard.readText());
|
|
expect(clipboardText).toBe(expectedCommand?.trim());
|
|
});
|
|
|
|
test('should be keyboard accessible', async ({ page }) => {
|
|
// Arrange
|
|
const copyButton = page.locator('.verify-command__copy').first();
|
|
|
|
// Act - focus and press Enter
|
|
await copyButton.focus();
|
|
await page.keyboard.press('Enter');
|
|
|
|
// Assert
|
|
await expect(copyButton).toHaveText('Copied!');
|
|
});
|
|
|
|
test('should have proper aria-label for copy button', async ({ page }) => {
|
|
// Arrange
|
|
const copyButton = page.locator('.verify-command__copy').first();
|
|
|
|
// Assert - initial state
|
|
await expect(copyButton).toHaveAttribute('aria-label', 'Copy command');
|
|
|
|
// Act
|
|
await copyButton.click();
|
|
|
|
// Assert - copied state
|
|
await expect(copyButton).toHaveAttribute('aria-label', 'Copied!');
|
|
});
|
|
});
|
|
|
|
test.describe('Evidence Panel - ProofSpine Component', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await page.goto('/evidence?digest=' + MOCK_EVIDENCE.digest);
|
|
await page.waitForSelector('.evidence-panel');
|
|
await page.click('[role="tab"]:has-text("Linkset")');
|
|
});
|
|
|
|
test('should display bundle hash in ProofSpine', async ({ page }) => {
|
|
// The ProofSpine should show the evidence bundle digest
|
|
const proofSpine = page.locator('.linkset-panel__provenance');
|
|
|
|
await expect(proofSpine).toBeVisible();
|
|
// Check for bundle hash display
|
|
const bundleHash = proofSpine.locator('code').filter({ hasText: /sha256:/ });
|
|
await expect(bundleHash.first()).toBeVisible();
|
|
});
|
|
|
|
test('should truncate long hashes with copy on click', async ({ page }) => {
|
|
// Find truncated hash
|
|
const truncatedHash = page.locator('.linkset-panel__provenance code').first();
|
|
|
|
// Verify it shows truncated form
|
|
const text = await truncatedHash.textContent();
|
|
expect(text?.length).toBeLessThan(64); // SHA256 is 64 chars
|
|
});
|
|
});
|
|
|
|
test.describe('Evidence Panel - Tab Navigation', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await page.goto('/evidence?digest=' + MOCK_EVIDENCE.digest);
|
|
await page.waitForSelector('.evidence-panel');
|
|
});
|
|
|
|
test('should support keyboard navigation between tabs', async ({ page }) => {
|
|
// Focus first tab
|
|
const firstTab = page.locator('[role="tab"]').first();
|
|
await firstTab.focus();
|
|
|
|
// Press right arrow to move to next tab
|
|
await page.keyboard.press('ArrowRight');
|
|
|
|
// Verify focus moved
|
|
const focusedElement = page.locator(':focus');
|
|
await expect(focusedElement).toHaveAttribute('role', 'tab');
|
|
});
|
|
|
|
test('should announce tab content changes to screen readers', async ({ page }) => {
|
|
// The tabpanel should have proper aria attributes
|
|
const tabpanel = page.locator('[role="tabpanel"]');
|
|
|
|
await expect(tabpanel).toHaveAttribute('aria-label');
|
|
});
|
|
});
|
|
|
|
test.describe('Evidence Panel - Responsive Behavior', () => {
|
|
test('should stack verify commands on mobile viewport', async ({ page }) => {
|
|
// Set mobile viewport
|
|
await page.setViewportSize({ width: 375, height: 667 });
|
|
|
|
await page.goto('/evidence?digest=' + MOCK_EVIDENCE.digest);
|
|
await page.waitForSelector('.evidence-panel');
|
|
await page.click('[role="tab"]:has-text("Linkset")');
|
|
|
|
// Verify commands container should be flex column
|
|
const commandsContainer = page.locator('.verify-commands');
|
|
|
|
const display = await commandsContainer.evaluate((el) =>
|
|
window.getComputedStyle(el).flexDirection
|
|
);
|
|
|
|
expect(display).toBe('column');
|
|
});
|
|
|
|
test('should wrap long command text on small screens', async ({ page }) => {
|
|
// Set small viewport
|
|
await page.setViewportSize({ width: 375, height: 667 });
|
|
|
|
await page.goto('/evidence?digest=' + MOCK_EVIDENCE.digest);
|
|
await page.click('[role="tab"]:has-text("Linkset")');
|
|
|
|
// Command code should wrap
|
|
const codeBlock = page.locator('.verify-command__code').first();
|
|
|
|
const whiteSpace = await codeBlock.evaluate((el) =>
|
|
window.getComputedStyle(el).whiteSpace
|
|
);
|
|
|
|
expect(whiteSpace).toBe('pre-wrap');
|
|
});
|
|
});
|
|
|
|
test.describe('Evidence Panel - Error States', () => {
|
|
test('should not show verify section when no verification data available', async ({ page }) => {
|
|
// Navigate to evidence without Rekor/signature data
|
|
await page.goto('/evidence?digest=sha256:nosig123');
|
|
await page.waitForSelector('.evidence-panel');
|
|
await page.click('[role="tab"]:has-text("Linkset")');
|
|
|
|
// Verify section should be hidden or empty
|
|
const verifySection = page.locator('.linkset-panel__verify');
|
|
|
|
// Either hidden or shows no commands
|
|
const verifyCommands = page.locator('.verify-command');
|
|
const count = await verifyCommands.count();
|
|
|
|
if (count === 0) {
|
|
await expect(verifySection).not.toBeVisible();
|
|
}
|
|
});
|
|
|
|
test('should handle clipboard API failure gracefully', async ({ page, context }) => {
|
|
// Deny clipboard permissions
|
|
await context.clearPermissions();
|
|
|
|
await page.goto('/evidence?digest=' + MOCK_EVIDENCE.digest);
|
|
await page.click('[role="tab"]:has-text("Linkset")');
|
|
|
|
// Click copy - should not crash
|
|
const copyButton = page.locator('.verify-command__copy').first();
|
|
await copyButton.click();
|
|
|
|
// Should show error state or fallback
|
|
// Implementation may vary - check it doesn't throw
|
|
await expect(page.locator('.evidence-panel')).toBeVisible();
|
|
});
|
|
});
|