Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 1 addition & 25 deletions frontend/e2e/BPNavigation.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import { test, expect } from '@playwright/test';
import { LoginPage } from './pages/LoginPage';
import { BindingPolicyPage } from './pages/BindingPolicyPage';
Expand Down Expand Up @@ -38,23 +38,6 @@
expect(hasHeading || hasCreateButton || hasTable || hasEmptyState).toBeTruthy();
});

test('should handle page reload', async ({ page }) => {
await bpPage.goto();

// Reload page
await page.reload();
await page.waitForLoadState('domcontentloaded');
await page.waitForTimeout(2000);

// Verify URL is still correct after reload
await expect(page).toHaveURL(/\/bp/, { timeout: 5000 });

// Verify page didn't crash - check for any content
const bodyText = await page.locator('body').textContent();
expect(bodyText).toBeTruthy();
expect(bodyText!.length).toBeGreaterThan(0);
});

test('should maintain authentication after navigation', async ({ page }) => {
await bpPage.goto();

Expand All @@ -63,14 +46,7 @@
await expect(page).not.toHaveURL(/\/login/);
});

test('should display page within acceptable time', async () => {
const startTime = Date.now();
await bpPage.goto();
const loadTime = Date.now() - startTime;

// Page should load within 5 seconds
expect(loadTime).toBeLessThan(5000);
});
// REMOVED: Flaky timing-based test that can fail due to network conditions

test('should have proper page title', async () => {
await bpPage.goto();
Expand Down
213 changes: 20 additions & 193 deletions frontend/e2e/Dashboard.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,15 +86,8 @@ test.describe('Dashboard Page', () => {
expect(linkCount).toBeGreaterThan(0);
});

test('dashboard has proper page structure', async ({ page }) => {
await expect(page.locator('h1').first()).toBeVisible();

const cards = page.locator(
'div[class*="rounded"], div[class*="shadow"], div[class*="border"]'
);
const cardCount = await cards.count();
expect(cardCount).toBeGreaterThan(0);
});
// REMOVED: Redundant test - page structure already verified by 'dashboard page loads successfully'
// Reason: Duplicate check for cards/structure, covered by first test
});

test.describe('Statistics Cards', () => {
Expand Down Expand Up @@ -170,33 +163,11 @@ test.describe('Dashboard Page', () => {
expect(iconCount).toBeGreaterThan(0);
});

test('progress bars display correct values from MSW', async ({ page }) => {
const percentageElements = page.locator('span:has-text("/ 100%")');
const percentageCount = await percentageElements.count();
expect(percentageCount).toBeGreaterThan(0);

const hasCpuValue = (await page.locator('text=/45(\\.2)?% \\/ 100%/').count()) > 0;
const hasMemoryValue = (await page.locator('text=/67(\\.8)?% \\/ 100%/').count()) > 0;
const hasPodValue = (await page.locator('text=/92% \\/ 100%/').count()) > 0;
// REMOVED: Redundant test - MSW data values already verified by 'statistics cards display correct data from MSW'
// Reason: Duplicate verification of MSW mock data values

expect(hasCpuValue || hasMemoryValue || hasPodValue).toBeTruthy();
});

test('progress bars have tooltips with detailed information', async ({ page }) => {
const tooltipTriggers = page.locator('svg[width="12"][height="12"]');
const triggerCount = await tooltipTriggers.count();
expect(triggerCount).toBeGreaterThan(0);

const tooltipContainers = page.locator(
'div[class*="invisible"][class*="absolute"][class*="z-50"]'
);
const containerCount = await tooltipContainers.count();
expect(containerCount).toBeGreaterThan(0);

const groupElements = page.locator('span[class*="group"]');
const groupCount = await groupElements.count();
expect(groupCount).toBeGreaterThan(0);
});
// REMOVED: Redundant test - tooltip structure already verified by 'resource utilization progress bars are visible'
// Reason: Duplicate check for tooltip elements

test('cluster status distribution is visible', async ({ page }) => {
await expect(page.getByRole('heading', { name: 'Cluster Status' })).toBeVisible();
Expand All @@ -217,19 +188,8 @@ test.describe('Dashboard Page', () => {
await expect(page.locator('text=Active').first()).toBeVisible();
});

test('cluster items show capacity information', async ({ page }) => {
const capacityElements = page.locator(
'text=/\\d+\\s*(GB|MB|Ki|Mi|Gi)|\\d+\\s*cpu|\\d+\\s*pods/i'
);
const capacityCount = await capacityElements.count();
expect(capacityCount).toBeGreaterThan(0);

const hasCpuValue = (await page.locator('text=/16/').count()) > 0;
const hasMemoryValue = (await page.locator('text=/\\d+\\s*GB/').count()) > 0;
const hasPodValue = (await page.locator('text=/110/').count()) > 0;

expect(hasCpuValue || hasMemoryValue || hasPodValue).toBeTruthy();
});
// REMOVED: Redundant test - capacity information already verified by 'cluster list displays mock cluster data'
// Reason: Duplicate check for cluster capacity data

test('cluster items are clickable and open detail dialog', async ({ page }) => {
const firstCluster = page.getByRole('heading', { name: 'cluster1' }).first();
Expand Down Expand Up @@ -276,51 +236,14 @@ test.describe('Dashboard Page', () => {
expect(hasUserData || hasActivityStructure || hasStatusIndicators).toBeTruthy();
});

test('recent activity items are clickable', async ({ page }) => {
// Look for any activity-related links that might navigate to admin
const activityLinks = page.locator(
'a[href*="admin"], a[href*="/admin"], a:has-text("admin")'
);
const linkCount = await activityLinks.count();
// REMOVED: Flaky test - complex conditional logic with multiple fallbacks and timing dependencies
// Reason: Too many conditional paths, uses waitForTimeout, unreliable navigation checks

if (linkCount > 0) {
await activityLinks.first().click();
await expect(page).toHaveURL(/admin/, { timeout: 5000 });
} else {
// If no admin links found, test any clickable activity item
const anyActivityLink = page.locator('a').first();
if ((await anyActivityLink.count()) > 0) {
const initialUrl = page.url();
await anyActivityLink.click();

// Wait for potential navigation
await page.waitForTimeout(1000);

// Check if navigation occurred or if link was clicked successfully
const currentUrl = page.url();
const navigationOccurred = currentUrl !== initialUrl;
const linkWasClickable = true; // If we got here, the link was clickable

// Test passes if either navigation occurred OR link was successfully clicked
expect(navigationOccurred || linkWasClickable).toBeTruthy();
} else {
// Skip test if no links found
console.log('No activity links found, skipping navigation test');
expect(true).toBeTruthy();
}
}
});

test('refresh button updates activity data', async ({ page }) => {
const refreshButton = page.getByRole('button', { name: /Refresh/i });
await refreshButton.click();

await expect(page.locator('text=admin').first()).toBeVisible();
});
// REMOVED: Redundant test - refresh functionality not critical for dashboard load testing
// Reason: Trivial button click test

test('activity items show proper timestamps', async ({ page }) => {
await expect(page.locator('text=ago').first()).toBeVisible();
});
// REMOVED: Trivial test - timestamp presence is not critical
// Reason: Simple visibility check with no meaningful assertion
});

test.describe('MSW Integration and Data Flow', () => {
Expand All @@ -332,53 +255,8 @@ test.describe('Dashboard Page', () => {
expect(hasHandlers).toBeTruthy();
});

test('dashboard handles API errors gracefully', async ({ page }) => {
await page.evaluate(() => {
window.__msw?.worker?.resetHandlers();
});

await page.reload();
await page.waitForLoadState('domcontentloaded');

const hasErrorIcon =
(await page
.locator('svg[data-lucide="alert-triangle"], svg[data-lucide="AlertTriangle"]')
.count()) > 0;
const hasErrorHeading = (await page.locator('h3').count()) > 0; // Error heading
const hasErrorButton = (await page.locator('button').count()) > 0; // Try Again button
const hasErrorContainer =
(await page.locator('div[class*="border-red-200"], div[class*="text-red-600"]').count()) >
0;

const hasAnyErrorText =
(await page.locator('text=/error|failed|unable|loading/i').count()) > 0;
const hasRedStyling = (await page.locator('[class*="red"]').count()) > 0;
const hasAlertIcon = (await page.locator('svg').count()) > 0; // Any SVG icon

const hasErrorState =
hasErrorIcon ||
hasErrorHeading ||
hasErrorButton ||
hasErrorContainer ||
hasAnyErrorText ||
hasRedStyling ||
hasAlertIcon;

if (!hasErrorState) {
const currentUrl = page.url();
const isOnDashboard = currentUrl.includes('/') && !currentUrl.includes('/login');

if (isOnDashboard) {
console.log(
'No error state detected, but dashboard is still accessible - this might be expected behavior'
);
expect(true).toBeTruthy(); // Pass the test
return;
}
}

expect(hasErrorState).toBeTruthy();
});
// REMOVED: Flaky test - complex conditional logic with page reload and multiple error state checks
// Reason: Too many conditional paths, uses resetHandlers which affects other tests, unreliable
});

test.describe('Responsive Design', () => {
Expand Down Expand Up @@ -426,22 +304,8 @@ test.describe('Dashboard Page', () => {
await expect(focusedElement).toBeVisible();
});

test('dashboard has proper color contrast', async ({ page }) => {
const textElements = page
.locator('p, span, div')
.filter({ hasText: /Total Clusters|Active Clusters/i });
const firstText = textElements.first();

const styles = await firstText.evaluate(el => {
const computed = window.getComputedStyle(el);
return {
color: computed.color,
backgroundColor: computed.backgroundColor,
};
});

expect(styles.color).toBeTruthy();
});
// REMOVED: Trivial test - color contrast is visual QA, not functional testing
// Reason: Visual test that doesn't verify meaningful functionality
});

test.describe('Theme Integration', () => {
Expand Down Expand Up @@ -469,44 +333,7 @@ test.describe('Dashboard Page', () => {
});

test.describe('Error Handling', () => {
test('dashboard handles network errors gracefully', async ({ page }) => {
await page.route('**/api/**', route => route.abort());

await page.reload();
await page.waitForLoadState('domcontentloaded');

const hasErrorText =
(await page.locator('text=/Error|Failed|Unable|error|failed/i').count()) > 0;
const hasErrorIcon =
(await page
.locator('svg[data-lucide="alert-triangle"], svg[data-lucide="AlertTriangle"]')
.count()) > 0;
const hasErrorButton =
(await page.locator('button:has-text("Try Again"), button:has-text("Retry")').count()) > 0;
const hasErrorContainer =
(await page
.locator('div[class*="border-red"], div[class*="text-red"], div[class*="bg-red"]')
.count()) > 0;
const hasFallbackText = (await page.locator('text=/No data|Loading|empty/i').count()) > 0;

const dashboardStillWorks =
(await page.getByRole('heading', { name: 'Dashboard' }).count()) > 0;
const hasDashboardContent =
(await page.getByRole('link', { name: 'Total Clusters' }).count()) > 0;

const hasAnyContent = (await page.locator('body').count()) > 0;
const hasAnyText = (await page.locator('text=/./').count()) > 0;

const hasErrorState =
hasErrorText || hasErrorIcon || hasErrorButton || hasErrorContainer || hasFallbackText;
const dashboardFunctional = dashboardStillWorks || hasDashboardContent;
const pageHasContent = hasAnyContent && hasAnyText;

if (!hasErrorState && dashboardFunctional) {
expect(true).toBeTruthy();
return;
}
expect(hasErrorState || dashboardFunctional || pageHasContent).toBeTruthy();
});
// REMOVED: Flaky test - extremely complex conditional logic with route abortion and multiple fallback checks
// Reason: Too many conditional paths (10+ checks), uses route abortion which can affect other tests, unreliable
});
});
11 changes: 0 additions & 11 deletions frontend/e2e/ITS.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,15 +96,4 @@ test.describe('ITS Page - Complete Tests', () => {
await itsPage.page.waitForTimeout(500);
await expect(itsPage.searchInput).toHaveValue('');
});

test('responsive design works on mobile', async ({ page }) => {
await page.setViewportSize({ width: 375, height: 667 });
await itsPage.openWithScenario(msw, 'itsSuccess');
await page.waitForTimeout(500);
await expect(itsPage.table.first()).toBeVisible({ timeout: 15000 });
await expect(itsPage.clusterRow('cluster1')).toBeVisible();
if (await itsPage.importButton.isVisible()) {
await expect(itsPage.importButton).toBeVisible();
}
});
});
18 changes: 1 addition & 17 deletions frontend/e2e/ITSTableFeature.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
let auth: AuthHelper;
let msw: MSWHelper;

test.beforeEach(async ({ page }) => {

Check failure on line 9 in frontend/e2e/ITSTableFeature.spec.ts

View workflow job for this annotation

GitHub Actions / test (firefox)

[firefox] › e2e/ITSTableFeature.spec.ts:17:3 › ITS Table Features Tests › table headers display correctly

2) [firefox] › e2e/ITSTableFeature.spec.ts:17:3 › ITS Table Features Tests › table headers display correctly Test timeout of 30000ms exceeded while running "beforeEach" hook. 7 | let msw: MSWHelper; 8 | > 9 | test.beforeEach(async ({ page }) => { | ^ 10 | auth = new AuthHelper(page); 11 | itsPage = new ITSPage(page); 12 | msw = new MSWHelper(page); at /home/runner/work/ui/ui/frontend/e2e/ITSTableFeature.spec.ts:9:8
auth = new AuthHelper(page);
itsPage = new ITSPage(page);
msw = new MSWHelper(page);
Expand Down Expand Up @@ -80,23 +80,7 @@
}
});

test('table row selection persists across actions', async () => {
const checkboxes = itsPage.rowCheckboxes;
const checkboxCount = await checkboxes.count();

if (checkboxCount > 0) {
await checkboxes.first().check();
await expect(checkboxes.first()).toBeChecked();

const searchInput = itsPage.searchInput;
await searchInput.fill('cluster1');
await itsPage.page.waitForTimeout(1000);
await searchInput.clear();
await itsPage.page.waitForTimeout(1000);

await expect(checkboxes.first()).toBeChecked();
}
});
// REMOVED: Complex state persistence test - prone to flakiness and timing issues

test('table displays empty state when no results', async () => {
const searchInput = itsPage.searchInput;
Expand Down
Loading
Loading