585 lines
21 KiB
TypeScript
585 lines
21 KiB
TypeScript
// -----------------------------------------------------------------------------
|
|
// diff-runtime-tabs.e2e.spec.ts
|
|
// Sprint: SPRINT_20260107_006_002_FE_diff_runtime_tabs
|
|
// Task: DR-013 — E2E tests for Diff and Runtime tabs
|
|
// -----------------------------------------------------------------------------
|
|
|
|
import { test, expect, Page } from '@playwright/test';
|
|
|
|
test.describe('Diff Tab', () => {
|
|
let page: Page;
|
|
|
|
test.beforeEach(async ({ browser }) => {
|
|
page = await browser.newPage();
|
|
// Navigate to a finding detail page with diff evidence
|
|
await page.goto('/triage/findings/test-finding-001');
|
|
// Wait for evidence panel to load
|
|
await page.waitForSelector('[data-testid="evidence-panel"]');
|
|
});
|
|
|
|
test.afterEach(async () => {
|
|
await page.close();
|
|
});
|
|
|
|
test('should display Diff tab in evidence panel', async () => {
|
|
const diffTab = page.locator('[data-testid="tab-diff"]');
|
|
await expect(diffTab).toBeVisible();
|
|
await expect(diffTab).toHaveText(/Diff/i);
|
|
});
|
|
|
|
test('should switch to Diff tab on click', async () => {
|
|
const diffTab = page.locator('[data-testid="tab-diff"]');
|
|
await diffTab.click();
|
|
|
|
const diffPanel = page.locator('[data-testid="panel-diff"]');
|
|
await expect(diffPanel).toBeVisible();
|
|
});
|
|
|
|
test('should switch to Diff tab with keyboard shortcut', async () => {
|
|
// Press '4' to switch to Diff tab (assuming it's the 4th tab)
|
|
await page.keyboard.press('4');
|
|
|
|
const diffPanel = page.locator('[data-testid="panel-diff"]');
|
|
await expect(diffPanel).toBeVisible();
|
|
});
|
|
|
|
test('should display backport verdict badge', async () => {
|
|
await page.locator('[data-testid="tab-diff"]').click();
|
|
await page.waitForSelector('[data-testid="backport-verdict-badge"]');
|
|
|
|
const badge = page.locator('[data-testid="backport-verdict-badge"]');
|
|
await expect(badge).toBeVisible();
|
|
// Should show one of the verdict states
|
|
await expect(badge).toHaveAttribute('data-status', /verified|unverified|unknown|partial/);
|
|
});
|
|
|
|
test('should display confidence percentage', async () => {
|
|
await page.locator('[data-testid="tab-diff"]').click();
|
|
await page.waitForSelector('[data-testid="backport-verdict-badge"]');
|
|
|
|
const confidence = page.locator('[data-testid="verdict-confidence"]');
|
|
await expect(confidence).toBeVisible();
|
|
// Should show percentage
|
|
await expect(confidence).toHaveText(/%/);
|
|
});
|
|
|
|
test('should show tooltip with tier info on badge hover', async () => {
|
|
await page.locator('[data-testid="tab-diff"]').click();
|
|
await page.waitForSelector('[data-testid="backport-verdict-badge"]');
|
|
|
|
const badge = page.locator('[data-testid="backport-verdict-badge"]');
|
|
await badge.hover();
|
|
|
|
const tooltip = page.locator('[data-testid="verdict-tooltip"]');
|
|
await expect(tooltip).toBeVisible();
|
|
await expect(tooltip).toHaveText(/Tier/i);
|
|
});
|
|
|
|
test('should display version comparison section', async () => {
|
|
await page.locator('[data-testid="tab-diff"]').click();
|
|
await page.waitForSelector('[data-testid="version-compare"]');
|
|
|
|
const upstream = page.locator('[data-testid="version-upstream"]');
|
|
const distro = page.locator('[data-testid="version-distro"]');
|
|
|
|
await expect(upstream).toBeVisible();
|
|
await expect(distro).toBeVisible();
|
|
});
|
|
|
|
test('should display upstream commit link', async () => {
|
|
await page.locator('[data-testid="tab-diff"]').click();
|
|
await page.waitForSelector('[data-testid="commit-link"]');
|
|
|
|
const commitLink = page.locator('[data-testid="commit-link"]');
|
|
await expect(commitLink).toBeVisible();
|
|
await expect(commitLink).toHaveAttribute('href', /github\.com|gitlab\.com/);
|
|
await expect(commitLink).toHaveAttribute('target', '_blank');
|
|
});
|
|
|
|
test('should display patch diff viewer', async () => {
|
|
await page.locator('[data-testid="tab-diff"]').click();
|
|
await page.waitForSelector('[data-testid="patch-diff-viewer"]');
|
|
|
|
const diffViewer = page.locator('[data-testid="patch-diff-viewer"]');
|
|
await expect(diffViewer).toBeVisible();
|
|
});
|
|
|
|
test('should expand and collapse diff hunks', async () => {
|
|
await page.locator('[data-testid="tab-diff"]').click();
|
|
await page.waitForSelector('[data-testid="patch-diff-viewer"]');
|
|
|
|
const hunkHeader = page.locator('[data-testid="hunk-header"]').first();
|
|
const hunkContent = page.locator('[data-testid="hunk-content"]').first();
|
|
|
|
// Initially expanded
|
|
await expect(hunkContent).toBeVisible();
|
|
|
|
// Click to collapse
|
|
await hunkHeader.click();
|
|
await expect(hunkContent).not.toBeVisible();
|
|
|
|
// Click to expand again
|
|
await hunkHeader.click();
|
|
await expect(hunkContent).toBeVisible();
|
|
});
|
|
|
|
test('should display line numbers in diff', async () => {
|
|
await page.locator('[data-testid="tab-diff"]').click();
|
|
await page.waitForSelector('[data-testid="patch-diff-viewer"]');
|
|
|
|
const lineNumbers = page.locator('[data-testid="line-number"]');
|
|
await expect(lineNumbers.first()).toBeVisible();
|
|
});
|
|
|
|
test('should highlight additions in green', async () => {
|
|
await page.locator('[data-testid="tab-diff"]').click();
|
|
await page.waitForSelector('[data-testid="patch-diff-viewer"]');
|
|
|
|
const additionLine = page.locator('[data-testid="diff-line-addition"]').first();
|
|
await expect(additionLine).toBeVisible();
|
|
// Check for green background styling
|
|
await expect(additionLine).toHaveClass(/addition/);
|
|
});
|
|
|
|
test('should highlight deletions in red', async () => {
|
|
await page.locator('[data-testid="tab-diff"]').click();
|
|
await page.waitForSelector('[data-testid="patch-diff-viewer"]');
|
|
|
|
const deletionLine = page.locator('[data-testid="diff-line-deletion"]').first();
|
|
await expect(deletionLine).toBeVisible();
|
|
await expect(deletionLine).toHaveClass(/deletion/);
|
|
});
|
|
|
|
test('should copy diff to clipboard', async () => {
|
|
await page.locator('[data-testid="tab-diff"]').click();
|
|
await page.waitForSelector('[data-testid="patch-diff-viewer"]');
|
|
|
|
const copyBtn = page.locator('[data-testid="copy-diff-btn"]');
|
|
await copyBtn.click();
|
|
|
|
// Check clipboard content (requires clipboard permissions)
|
|
const clipboardText = await page.evaluate(() => navigator.clipboard.readText());
|
|
expect(clipboardText).toContain('@@');
|
|
});
|
|
|
|
test('should copy hunk signature', async () => {
|
|
await page.locator('[data-testid="tab-diff"]').click();
|
|
await page.waitForSelector('[data-testid="hunk-signature"]');
|
|
|
|
const copyHashBtn = page.locator('[data-testid="copy-hash-btn"]');
|
|
await copyHashBtn.click();
|
|
|
|
const clipboardText = await page.evaluate(() => navigator.clipboard.readText());
|
|
expect(clipboardText).toMatch(/sha256:/);
|
|
});
|
|
|
|
test('should show empty state when no diff evidence', async () => {
|
|
// Navigate to finding without diff evidence
|
|
await page.goto('/triage/findings/no-diff-finding');
|
|
await page.waitForSelector('[data-testid="evidence-panel"]');
|
|
|
|
await page.locator('[data-testid="tab-diff"]').click();
|
|
|
|
const emptyState = page.locator('[data-testid="diff-empty-state"]');
|
|
await expect(emptyState).toBeVisible();
|
|
await expect(emptyState).toHaveText(/No backport evidence/i);
|
|
});
|
|
|
|
test('should handle loading state', async () => {
|
|
// Slow down network to observe loading state
|
|
await page.route('**/api/v1/findings/**/backport', async (route) => {
|
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
await route.continue();
|
|
});
|
|
|
|
await page.locator('[data-testid="tab-diff"]').click();
|
|
|
|
const loadingState = page.locator('[data-testid="diff-loading"]');
|
|
await expect(loadingState).toBeVisible();
|
|
});
|
|
|
|
test('should handle error state with retry', async () => {
|
|
// Mock API error
|
|
await page.route('**/api/v1/findings/**/backport', (route) => {
|
|
route.fulfill({ status: 500, body: 'Server Error' });
|
|
});
|
|
|
|
await page.locator('[data-testid="tab-diff"]').click();
|
|
|
|
const errorState = page.locator('[data-testid="diff-error"]');
|
|
await expect(errorState).toBeVisible();
|
|
|
|
const retryBtn = page.locator('[data-testid="diff-retry-btn"]');
|
|
await expect(retryBtn).toBeVisible();
|
|
});
|
|
});
|
|
|
|
test.describe('Runtime Tab', () => {
|
|
let page: Page;
|
|
|
|
test.beforeEach(async ({ browser }) => {
|
|
page = await browser.newPage();
|
|
await page.goto('/triage/findings/test-finding-001');
|
|
await page.waitForSelector('[data-testid="evidence-panel"]');
|
|
});
|
|
|
|
test.afterEach(async () => {
|
|
await page.close();
|
|
});
|
|
|
|
test('should display Runtime tab in evidence panel', async () => {
|
|
const runtimeTab = page.locator('[data-testid="tab-runtime"]');
|
|
await expect(runtimeTab).toBeVisible();
|
|
await expect(runtimeTab).toHaveText(/Runtime/i);
|
|
});
|
|
|
|
test('should switch to Runtime tab on click', async () => {
|
|
const runtimeTab = page.locator('[data-testid="tab-runtime"]');
|
|
await runtimeTab.click();
|
|
|
|
const runtimePanel = page.locator('[data-testid="panel-runtime"]');
|
|
await expect(runtimePanel).toBeVisible();
|
|
});
|
|
|
|
test('should switch to Runtime tab with keyboard shortcut', async () => {
|
|
await page.keyboard.press('5');
|
|
|
|
const runtimePanel = page.locator('[data-testid="panel-runtime"]');
|
|
await expect(runtimePanel).toBeVisible();
|
|
});
|
|
|
|
test('should display live indicator when collection active', async () => {
|
|
// Mock active collection response
|
|
await page.route('**/api/v1/findings/**/runtime/traces', (route) => {
|
|
route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({
|
|
findingId: 'test-finding-001',
|
|
collectionActive: true,
|
|
collectionStarted: new Date().toISOString(),
|
|
summary: { totalHits: 100, uniquePaths: 3 },
|
|
traces: [],
|
|
}),
|
|
});
|
|
});
|
|
|
|
await page.locator('[data-testid="tab-runtime"]').click();
|
|
await page.waitForSelector('[data-testid="live-indicator"]');
|
|
|
|
const liveIndicator = page.locator('[data-testid="live-indicator"]');
|
|
await expect(liveIndicator).toBeVisible();
|
|
await expect(liveIndicator).toHaveClass(/active/);
|
|
await expect(liveIndicator).toHaveText(/Live/i);
|
|
});
|
|
|
|
test('should show offline indicator when collection stopped', async () => {
|
|
await page.route('**/api/v1/findings/**/runtime/traces', (route) => {
|
|
route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({
|
|
findingId: 'test-finding-001',
|
|
collectionActive: false,
|
|
summary: { totalHits: 100, uniquePaths: 3 },
|
|
traces: [],
|
|
}),
|
|
});
|
|
});
|
|
|
|
await page.locator('[data-testid="tab-runtime"]').click();
|
|
await page.waitForSelector('[data-testid="live-indicator"]');
|
|
|
|
const liveIndicator = page.locator('[data-testid="live-indicator"]');
|
|
await expect(liveIndicator).toHaveClass(/inactive/);
|
|
await expect(liveIndicator).toHaveText(/Offline/i);
|
|
});
|
|
|
|
test('should show tooltip on live indicator hover', async () => {
|
|
await page.locator('[data-testid="tab-runtime"]').click();
|
|
await page.waitForSelector('[data-testid="live-indicator"]');
|
|
|
|
const indicator = page.locator('[data-testid="live-indicator"]');
|
|
await indicator.hover();
|
|
|
|
const tooltip = page.locator('[data-testid="live-tooltip"]');
|
|
await expect(tooltip).toBeVisible();
|
|
});
|
|
|
|
test('should display summary stats', async () => {
|
|
await page.locator('[data-testid="tab-runtime"]').click();
|
|
await page.waitForSelector('[data-testid="summary-stats"]');
|
|
|
|
const totalHits = page.locator('[data-testid="stat-total-hits"]');
|
|
const uniquePaths = page.locator('[data-testid="stat-unique-paths"]');
|
|
const containers = page.locator('[data-testid="stat-containers"]');
|
|
|
|
await expect(totalHits).toBeVisible();
|
|
await expect(uniquePaths).toBeVisible();
|
|
await expect(containers).toBeVisible();
|
|
});
|
|
|
|
test('should display RTS score', async () => {
|
|
await page.locator('[data-testid="tab-runtime"]').click();
|
|
await page.waitForSelector('[data-testid="rts-score-display"]');
|
|
|
|
const rtsScore = page.locator('[data-testid="rts-score-display"]');
|
|
await expect(rtsScore).toBeVisible();
|
|
|
|
const scoreValue = page.locator('[data-testid="rts-value"]');
|
|
await expect(scoreValue).toHaveText(/%/);
|
|
});
|
|
|
|
test('should display posture badge', async () => {
|
|
await page.locator('[data-testid="tab-runtime"]').click();
|
|
await page.waitForSelector('[data-testid="posture-badge"]');
|
|
|
|
const postureBadge = page.locator('[data-testid="posture-badge"]');
|
|
await expect(postureBadge).toBeVisible();
|
|
await expect(postureBadge).toHaveText(/Excellent|Good|Limited|None/i);
|
|
});
|
|
|
|
test('should expand RTS score breakdown', async () => {
|
|
await page.locator('[data-testid="tab-runtime"]').click();
|
|
await page.waitForSelector('[data-testid="rts-score-display"]');
|
|
|
|
const breakdownToggle = page.locator('[data-testid="breakdown-toggle"]');
|
|
await breakdownToggle.click();
|
|
|
|
const breakdownContent = page.locator('[data-testid="breakdown-content"]');
|
|
await expect(breakdownContent).toBeVisible();
|
|
});
|
|
|
|
test('should display function traces list', async () => {
|
|
await page.locator('[data-testid="tab-runtime"]').click();
|
|
await page.waitForSelector('[data-testid="traces-list"]');
|
|
|
|
const tracesList = page.locator('[data-testid="traces-list"]');
|
|
await expect(tracesList).toBeVisible();
|
|
|
|
const traceItems = page.locator('[data-testid="function-trace"]');
|
|
await expect(traceItems.first()).toBeVisible();
|
|
});
|
|
|
|
test('should expand function trace to show call stack', async () => {
|
|
await page.locator('[data-testid="tab-runtime"]').click();
|
|
await page.waitForSelector('[data-testid="function-trace"]');
|
|
|
|
const traceHeader = page.locator('[data-testid="trace-header"]').first();
|
|
await traceHeader.click();
|
|
|
|
const callPath = page.locator('[data-testid="call-path"]').first();
|
|
await expect(callPath).toBeVisible();
|
|
});
|
|
|
|
test('should display stack frames with file:line links', async () => {
|
|
await page.locator('[data-testid="tab-runtime"]').click();
|
|
await page.waitForSelector('[data-testid="function-trace"]');
|
|
|
|
// Expand first trace
|
|
await page.locator('[data-testid="trace-header"]').first().click();
|
|
|
|
const frameLocation = page.locator('[data-testid="frame-location"]').first();
|
|
await expect(frameLocation).toBeVisible();
|
|
await expect(frameLocation).toHaveText(/:\d+/); // file:line format
|
|
});
|
|
|
|
test('should highlight vulnerable function in stack', async () => {
|
|
await page.locator('[data-testid="tab-runtime"]').click();
|
|
await page.waitForSelector('[data-testid="function-trace"]');
|
|
|
|
await page.locator('[data-testid="trace-header"]').first().click();
|
|
|
|
const vulnFrame = page.locator('[data-testid="stack-frame-vulnerable"]');
|
|
await expect(vulnFrame).toBeVisible();
|
|
await expect(vulnFrame).toHaveClass(/vulnerable/);
|
|
});
|
|
|
|
test('should copy stack trace', async () => {
|
|
await page.locator('[data-testid="tab-runtime"]').click();
|
|
await page.waitForSelector('[data-testid="function-trace"]');
|
|
|
|
await page.locator('[data-testid="trace-header"]').first().click();
|
|
|
|
const copyBtn = page.locator('[data-testid="copy-stack-btn"]').first();
|
|
await copyBtn.click();
|
|
|
|
const clipboardText = await page.evaluate(() => navigator.clipboard.readText());
|
|
expect(clipboardText).toContain('at ');
|
|
});
|
|
|
|
test('should sort traces by hits', async () => {
|
|
await page.locator('[data-testid="tab-runtime"]').click();
|
|
await page.waitForSelector('[data-testid="traces-list"]');
|
|
|
|
const sortByHits = page.locator('[data-testid="sort-by-hits"]');
|
|
await sortByHits.click();
|
|
|
|
// First trace should have highest hit count
|
|
const firstHitCount = await page.locator('[data-testid="hit-count"]').first().textContent();
|
|
const secondHitCount = await page.locator('[data-testid="hit-count"]').nth(1).textContent();
|
|
|
|
// Parse hit counts (handle K/M suffixes)
|
|
const parseCount = (str: string | null) => {
|
|
if (!str) return 0;
|
|
const num = parseFloat(str.replace(/[KM]/g, ''));
|
|
if (str.includes('K')) return num * 1000;
|
|
if (str.includes('M')) return num * 1000000;
|
|
return num;
|
|
};
|
|
|
|
expect(parseCount(firstHitCount)).toBeGreaterThanOrEqual(parseCount(secondHitCount));
|
|
});
|
|
|
|
test('should sort traces by recency', async () => {
|
|
await page.locator('[data-testid="tab-runtime"]').click();
|
|
await page.waitForSelector('[data-testid="traces-list"]');
|
|
|
|
const sortByRecent = page.locator('[data-testid="sort-by-recent"]');
|
|
await sortByRecent.click();
|
|
|
|
// Verify sorting changed
|
|
await expect(sortByRecent).toHaveClass(/active/);
|
|
});
|
|
|
|
test('should load more traces on button click', async () => {
|
|
await page.locator('[data-testid="tab-runtime"]').click();
|
|
await page.waitForSelector('[data-testid="traces-list"]');
|
|
|
|
const initialCount = await page.locator('[data-testid="function-trace"]').count();
|
|
|
|
const loadMoreBtn = page.locator('[data-testid="load-more-btn"]');
|
|
if (await loadMoreBtn.isVisible()) {
|
|
await loadMoreBtn.click();
|
|
|
|
const newCount = await page.locator('[data-testid="function-trace"]').count();
|
|
expect(newCount).toBeGreaterThan(initialCount);
|
|
}
|
|
});
|
|
|
|
test('should show direct path indicator', async () => {
|
|
await page.locator('[data-testid="tab-runtime"]').click();
|
|
await page.waitForSelector('[data-testid="indicator-direct-path"]');
|
|
|
|
const directPathIndicator = page.locator('[data-testid="indicator-direct-path"]');
|
|
await expect(directPathIndicator).toBeVisible();
|
|
});
|
|
|
|
test('should show production traffic indicator', async () => {
|
|
await page.locator('[data-testid="tab-runtime"]').click();
|
|
await page.waitForSelector('[data-testid="indicator-production"]');
|
|
|
|
const prodIndicator = page.locator('[data-testid="indicator-production"]');
|
|
await expect(prodIndicator).toBeVisible();
|
|
});
|
|
|
|
test('should show empty state when no runtime data', async () => {
|
|
await page.goto('/triage/findings/no-runtime-finding');
|
|
await page.waitForSelector('[data-testid="evidence-panel"]');
|
|
|
|
await page.locator('[data-testid="tab-runtime"]').click();
|
|
|
|
const emptyState = page.locator('[data-testid="runtime-empty-state"]');
|
|
await expect(emptyState).toBeVisible();
|
|
await expect(emptyState).toHaveText(/No runtime observations/i);
|
|
});
|
|
|
|
test('should poll for updates when live', async () => {
|
|
let requestCount = 0;
|
|
|
|
await page.route('**/api/v1/findings/**/runtime/traces', (route) => {
|
|
requestCount++;
|
|
route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({
|
|
findingId: 'test-finding-001',
|
|
collectionActive: true,
|
|
summary: { totalHits: 100 + requestCount * 10, uniquePaths: 3 },
|
|
traces: [],
|
|
}),
|
|
});
|
|
});
|
|
|
|
await page.locator('[data-testid="tab-runtime"]').click();
|
|
|
|
// Wait for initial request
|
|
await page.waitForTimeout(100);
|
|
const initialCount = requestCount;
|
|
|
|
// Wait for poll interval (assuming 30s, we'll use a shorter timeout in test)
|
|
await page.waitForTimeout(35000);
|
|
|
|
expect(requestCount).toBeGreaterThan(initialCount);
|
|
});
|
|
});
|
|
|
|
test.describe('Tab Persistence', () => {
|
|
test('should persist selected tab in URL', async ({ page }) => {
|
|
await page.goto('/triage/findings/test-finding-001');
|
|
await page.waitForSelector('[data-testid="evidence-panel"]');
|
|
|
|
await page.locator('[data-testid="tab-diff"]').click();
|
|
|
|
// Check URL contains tab parameter
|
|
expect(page.url()).toContain('tab=diff');
|
|
});
|
|
|
|
test('should restore tab from URL on page load', async ({ page }) => {
|
|
await page.goto('/triage/findings/test-finding-001?tab=runtime');
|
|
await page.waitForSelector('[data-testid="evidence-panel"]');
|
|
|
|
const runtimePanel = page.locator('[data-testid="panel-runtime"]');
|
|
await expect(runtimePanel).toBeVisible();
|
|
});
|
|
});
|
|
|
|
test.describe('Accessibility', () => {
|
|
test('should have proper ARIA roles for Diff tab', async ({ page }) => {
|
|
await page.goto('/triage/findings/test-finding-001');
|
|
await page.waitForSelector('[data-testid="evidence-panel"]');
|
|
|
|
await page.locator('[data-testid="tab-diff"]').click();
|
|
|
|
const diffPanel = page.locator('[data-testid="panel-diff"]');
|
|
await expect(diffPanel).toHaveAttribute('role', 'tabpanel');
|
|
|
|
const badge = page.locator('[data-testid="backport-verdict-badge"]');
|
|
await expect(badge).toHaveAttribute('role', 'status');
|
|
});
|
|
|
|
test('should have proper ARIA roles for Runtime tab', async ({ page }) => {
|
|
await page.goto('/triage/findings/test-finding-001');
|
|
await page.waitForSelector('[data-testid="evidence-panel"]');
|
|
|
|
await page.locator('[data-testid="tab-runtime"]').click();
|
|
|
|
const runtimePanel = page.locator('[data-testid="panel-runtime"]');
|
|
await expect(runtimePanel).toHaveAttribute('role', 'tabpanel');
|
|
|
|
const indicator = page.locator('[data-testid="live-indicator"]');
|
|
await expect(indicator).toHaveAttribute('role', 'status');
|
|
});
|
|
|
|
test('should be keyboard navigable', async ({ page }) => {
|
|
await page.goto('/triage/findings/test-finding-001');
|
|
await page.waitForSelector('[data-testid="evidence-panel"]');
|
|
|
|
// Tab to diff tab
|
|
await page.keyboard.press('Tab');
|
|
await page.keyboard.press('Tab');
|
|
await page.keyboard.press('Tab');
|
|
await page.keyboard.press('Tab');
|
|
|
|
// Check diff tab is focused
|
|
const focusedElement = await page.locator(':focus');
|
|
await expect(focusedElement).toHaveAttribute('data-testid', 'tab-diff');
|
|
|
|
// Press Enter to activate
|
|
await page.keyboard.press('Enter');
|
|
|
|
const diffPanel = page.locator('[data-testid="panel-diff"]');
|
|
await expect(diffPanel).toBeVisible();
|
|
});
|
|
});
|