Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
62 changes: 62 additions & 0 deletions frontend/e2e/pages/dev-console/config-map-page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import type { Page } from '@playwright/test';

import BasePage from '../base-page';

export class ConfigMapPage extends BasePage {
private readonly createButton = this.page.locator('[data-test="item-create"]');
private readonly nameInput = this.page.getByTestId('configmap-name');
private readonly initialKeyInput = this.page.getByTestId('key-0');
private readonly secondKeyInput = this.page.getByTestId('key-1');
private readonly valueTextarea = this.page.locator('[data-test-id="file-input-textarea"]');
private readonly addKeyValueButton = this.page.getByTestId('add-key-value-button');
private readonly submitButton = this.page.locator('[data-test-id="submit-button"]');
private readonly dataSectionBody = this.page.locator('.co-m-pane__body');

constructor(page: Page) {
super(page);
}

async clickCreate(): Promise<void> {
await this.robustClick(this.createButton);
}

async fillName(name: string): Promise<void> {
await this.nameInput.fill(name);
}

async fillKey(key: string): Promise<void> {
await this.initialKeyInput.scrollIntoViewIfNeeded();
await this.initialKeyInput.fill(key);
}

async fillValue(value: string): Promise<void> {
await this.valueTextarea.scrollIntoViewIfNeeded();
await this.valueTextarea.fill(value);
}

async submitForm(): Promise<void> {
await this.robustClick(this.submitButton);
}

async addKeyValue(): Promise<void> {
await this.robustClick(this.addKeyValueButton.first());
}

async fillSecondKey(key: string): Promise<void> {
await this.secondKeyInput.scrollIntoViewIfNeeded();
await this.secondKeyInput.fill(key);
}

async createConfigMap(name: string, key = 'test-key', value = 'test-value'): Promise<void> {
await this.clickCreate();
await this.fillName(name);
await this.fillKey(key);
await this.fillValue(value);
await this.submitForm();
}

async expectDataSectionToContain(text: string): Promise<void> {
const { expect } = await import('@playwright/test');
await expect(this.dataSectionBody).toContainText(text);
}
}
113 changes: 113 additions & 0 deletions frontend/e2e/pages/dev-console/customization-page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import type { Page } from '@playwright/test';
import { expect } from '@playwright/test';

import BasePage from '../base-page';

export class CustomizationPage extends BasePage {
private readonly successAlert = this.page.locator('[aria-label="Success Alert"]');
private readonly clusterLink = this.page.getByTestId('cluster');
private readonly customizeAction = this.page.locator('[data-test-action="Customize"]');
private readonly actionsMenuButton = this.page.locator('[data-test-id="actions-menu-button"]');

constructor(page: Page) {
super(page);
}

async navigateToConsoles(): Promise<void> {
await this.goTo('/search?kind=console.operator.openshift.io~v1~Console');
await this.waitForLoadingComplete();
}

async clickCluster(): Promise<void> {
await this.robustClick(this.clusterLink);
}

async openCustomization(): Promise<void> {
await this.robustClick(this.actionsMenuButton);
await this.robustClick(this.customizeAction);
await this.waitForLoadingComplete();
}

async clickDeveloperTab(): Promise<void> {
const tab = this.page.locator('[role="presentation"]').filter({ hasText: 'Developer' });
await this.robustClick(tab);
}

async disableAllSoftwareCatalogItems(): Promise<void> {
const formSection = this.page.getByTestId('catalog-types form-section');
const removeAll = formSection.locator('[aria-label="Remove all"]');
await removeAll.scrollIntoViewIfNeeded();
if (await removeAll.isEnabled()) {
await removeAll.click();
}
const addAll = formSection.locator('[aria-label="Add all"]');
await addAll.scrollIntoViewIfNeeded();
await addAll.click();
}

async disableSoftwareCatalogItem(itemName: string): Promise<void> {
const formSection = this.page.getByTestId('catalog-types form-section');
await formSection.locator('[aria-label="Remove all"]').click();
await formSection.locator('[aria-label="Available search input"]').fill(itemName);
const option = formSection.locator('[role="option"]').filter({ hasText: itemName });
await option.scrollIntoViewIfNeeded();
await option.click();
await formSection.locator('[aria-label="Add selected"]').scrollIntoViewIfNeeded();
await formSection.locator('[aria-label="Add selected"]').click();
}

async enableOnlySoftwareCatalogItem(itemName: string): Promise<void> {
const formSection = this.page.getByTestId('catalog-types form-section');
await formSection.locator('[aria-label="Add all"]').click();
await formSection.locator('[aria-label="Chosen search input"]').fill(itemName);
const option = formSection.locator('[role="option"]').filter({ hasText: itemName });
await option.click();
await formSection.locator('[aria-label="Remove selected"]').click();
}

async disableAllAddPageItems(): Promise<void> {
const formSection = this.page.getByTestId('add-page form-section');
await formSection.locator('[aria-label="Add all"]').scrollIntoViewIfNeeded();
await formSection.locator('[aria-label="Add all"]').click();
}

async disableAddPageItem(itemName: string): Promise<void> {
const formSection = this.page.getByTestId('add-page form-section');
await formSection.locator('[aria-label="Remove all"]').click();
await formSection.locator('[aria-label="Available search input"]').fill(itemName);
const option = formSection.locator('[role="option"]').filter({ hasText: itemName });
await option.scrollIntoViewIfNeeded();
await option.click();
await formSection.locator('[aria-label="Add selected"]').scrollIntoViewIfNeeded();
await formSection.locator('[aria-label="Add selected"]').click();
}

async expectSaveMessage(): Promise<void> {
await this.successAlert.scrollIntoViewIfNeeded();
await expect(this.successAlert).toBeVisible();
}

async expectPinnedResourceSection(): Promise<void> {
await expect(this.page.getByTestId('pinned-resource form-section')).toBeVisible();
}

async expectPerspectiveDropdownValue(value: string): Promise<void> {
const perspectiveGroup = this.page.locator('[data-test="perspectives form-group"]').nth(1);
await expect(perspectiveGroup.locator('button[class*="menu-toggle"]')).toContainText(value);
}

async selectPerspectiveState(state: string): Promise<void> {
const perspectiveGroup = this.page.locator('[data-test="perspectives form-group"]').nth(1);
const toggle = perspectiveGroup
.locator('button')
.filter({ hasText: /Disabled|Enabled|AccessReview/ });
await this.robustClick(toggle);
await expect(this.page.locator('[role="listbox"]')).toBeVisible();
const option = this.page.locator('button[role="option"]').filter({ hasText: state });
await this.robustClick(option);
}

async expectSuccessAlert(): Promise<void> {
await expect(this.page.getByTestId('success-alert')).toBeVisible();
}
}
69 changes: 69 additions & 0 deletions frontend/e2e/pages/dev-console/health-checks-page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import type { Page } from '@playwright/test';
import { expect } from '@playwright/test';

import BasePage from '../base-page';

export class HealthChecksPage extends BasePage {
private readonly healthChecksForm = this.page.locator('div.odc-heath-check-probe-form');
private readonly successText = this.page.locator('span.odc-heath-check-probe__successText');
private readonly checkIcon = this.page.locator('[data-test-id="check-icon"]');
private readonly addButton = this.page.locator('[data-test-id="submit-button"]');
private readonly saveButton = this.page.locator('[data-test-id="submit-button"]');
private readonly typeToggle = this.page.getByTestId('console-select-menu-toggle');

constructor(page: Page) {
super(page);
}

async clickAddProbe(probeName: string): Promise<void> {
const probeButton = this.page.getByRole('button', { name: probeName });
await probeButton.scrollIntoViewIfNeeded();
await this.robustClick(probeButton);
await expect(this.healthChecksForm).toBeVisible();
}

async selectProbeType(type: string): Promise<void> {
await this.robustClick(this.typeToggle);
const option = this.page.getByTestId('console-select-item').filter({ hasText: type });
await this.robustClick(option);

if (type === 'Container command') {
const argInput = this.page.locator('[placeholder="argument"]');
if ((await argInput.count()) > 0) {
await argInput.fill('example');
}
}
}

async clickCheckIcon(): Promise<void> {
await this.robustClick(this.checkIcon);
}

async clickAddButton(): Promise<void> {
await this.robustClick(this.addButton);
}

async clickSaveButton(): Promise<void> {
await this.robustClick(this.saveButton);
}

async removeProbe(probeName: string): Promise<void> {
const probeSuccess = this.page.getByRole('button', { name: probeName });
const removeIcon = probeSuccess.locator('..').locator('..').locator('[role="img"]');
await this.robustClick(removeIcon);
}

async expectProbesAdded(count: number): Promise<void> {
await expect(this.successText).toHaveCount(count);
}

async expectProbeAdded(probeName: string): Promise<void> {
await expect(this.successText.filter({ hasText: probeName })).toBeVisible();
}

async expectEditHealthChecksTitle(): Promise<void> {
await expect(this.page.locator('[data-test="page-heading"] h1')).toContainText(
'Edit health checks',
);
}
}
103 changes: 103 additions & 0 deletions frontend/e2e/pages/dev-console/perspective-page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import type { Page } from '@playwright/test';

import BasePage from '../base-page';

export class PerspectivePage extends BasePage {
private readonly perspectiveSwitcherToggle = this.page.locator(
'[data-test-id="perspective-switcher-toggle"]',
);
private readonly perspectiveSwitcherMenu = this.page.locator(
'[data-test-id="perspective-switcher-menu"]',
);
private readonly sidebar = this.page.locator('#page-sidebar');
private readonly projectDropdown = this.page.locator('[data-test-id="namespace-bar-dropdown"]');
private readonly projectFilterInput = this.page.locator(
'[data-test="namespace-dropdown-filter"]',
);
private readonly createProjectButton = this.page.locator(
'[data-test="namespace-dropdown-create-project"]',
);

constructor(page: Page) {
super(page);
}

private async switchToPerspective(perspectiveName: string): Promise<void> {
await this.perspectiveSwitcherToggle.waitFor({ state: 'visible', timeout: 60_000 });
const currentText = await this.perspectiveSwitcherToggle.textContent();
if (currentText?.includes(perspectiveName)) {
return;
}
await this.robustClick(this.perspectiveSwitcherToggle);
const option = this.perspectiveSwitcherMenu
.locator('[data-test-id="perspective-switcher-menu-option"]')
.filter({ hasText: perspectiveName });
await this.robustClick(option);
await this.waitForLoadingComplete();
}

async switchToDeveloper(): Promise<void> {
await this.switchToPerspective('Developer');
}

async switchToAdministrator(): Promise<void> {
await this.switchToPerspective('Administrator');
// Also handle "Core platform" label (OpenShift 5.0+)
const currentText = await this.perspectiveSwitcherToggle.textContent();
if (
!currentText?.includes('Administrator') &&
!currentText?.includes('Core platform')
) {
await this.switchToPerspective('Core platform');
}
Comment on lines +43 to +52
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix fallback order for Administrator/Core platform switching.

On Line 44, switchToPerspective('Administrator') can fail before the Core platform fallback runs (Lines 46–52) when only the new label is present.

Proposed fix
   async switchToAdministrator(): Promise<void> {
-    await this.switchToPerspective('Administrator');
-    // Also handle "Core platform" label (OpenShift 5.0+)
-    const currentText = await this.perspectiveSwitcherToggle.textContent();
-    if (
-      !currentText?.includes('Administrator') &&
-      !currentText?.includes('Core platform')
-    ) {
-      await this.switchToPerspective('Core platform');
-    }
+    const currentText = await this.perspectiveSwitcherToggle.textContent();
+    if (currentText?.includes('Administrator') || currentText?.includes('Core platform')) {
+      return;
+    }
+
+    await this.robustClick(this.perspectiveSwitcherToggle);
+    const options = this.perspectiveSwitcherMenu.locator(
+      '[data-test-id="perspective-switcher-menu-option"]',
+    );
+    const administratorOption = options.filter({ hasText: 'Administrator' });
+    const corePlatformOption = options.filter({ hasText: 'Core platform' });
+
+    if ((await administratorOption.count()) > 0) {
+      await this.robustClick(administratorOption);
+    } else {
+      await this.robustClick(corePlatformOption);
+    }
+    await this.waitForLoadingComplete();
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/e2e/pages/dev-console/perspective-page.ts` around lines 43 - 52, The
current implementation in switchToAdministrator calls
switchToPerspective('Administrator') unconditionally which can fail when the UI
uses the new "Core platform" label; first read
perspectiveSwitcherToggle.textContent() and if it already contains
"Administrator" return, otherwise if it contains "Core platform" call
switchToPerspective('Core platform'); only if neither label is present attempt
switchToPerspective('Administrator') and if that doesn't result in the expected
label then fallback to switchToPerspective('Core platform') so the function
handles both legacy and new labels reliably.

}

async selectOrCreateProject(name: string): Promise<void> {
await this.robustClick(this.projectDropdown);
await this.projectFilterInput.fill(name);

const projectLink = this.page.locator(`[id="${name}-link"]`);
if ((await projectLink.count()) > 0) {
await this.robustClick(projectLink);
} else {
await this.robustClick(this.createProjectButton);
await this.page.locator('#input-name').fill(name);
await this.robustClick(this.page.locator('[data-test="confirm-action"]'));
}
await this.waitForLoadingComplete();
}

async navigateToDevMenu(menuItem: string): Promise<void> {
const menuLink = this.sidebar.locator(`[data-test-id="${menuItem}-header"]`);
await this.robustClick(menuLink);
await this.waitForLoadingComplete();
}

async navigateToAdd(): Promise<void> {
await this.navigateToDevMenu('+Add');
}

async navigateToTopology(): Promise<void> {
await this.navigateToDevMenu('topology');
}

async navigateToSearch(): Promise<void> {
await this.navigateToDevMenu('search');
}

async navigateToProject(): Promise<void> {
await this.navigateToDevMenu('project-details');
}

async navigateToRoutes(): Promise<void> {
const routesLink = this.sidebar.locator('a[href*="Route"]');
await this.robustClick(routesLink);
await this.waitForLoadingComplete();
}

async navigateToConfigMaps(): Promise<void> {
const configMapsLink = this.sidebar.locator('a[href*="ConfigMap"]');
await this.robustClick(configMapsLink);
await this.waitForLoadingComplete();
}
}
29 changes: 29 additions & 0 deletions frontend/e2e/pages/dev-console/pod-list-page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import type { Page } from '@playwright/test';
import { expect } from '@playwright/test';

import BasePage from '../base-page';

export class PodListPage extends BasePage {
private readonly manageColumnsButton = this.page.locator('button[data-test="manage-columns"]');
private readonly createdColumnCheckbox = this.page.locator('input[id="created"]');
private readonly receivingTrafficCheckbox = this.page.locator('input[id="trafficStatus"]');
private readonly confirmActionButton = this.page.locator('button[data-test="confirm-action"]');
private readonly receivingTrafficColumnLabel = this.page.locator(
'[data-label="Receiving Traffic"]',
);

constructor(page: Page) {
super(page);
}

async enableReceivingTrafficColumn(): Promise<void> {
await this.robustClick(this.manageColumnsButton);
await this.createdColumnCheckbox.uncheck();
await this.receivingTrafficCheckbox.check();
await this.robustClick(this.confirmActionButton);
}

async expectReceivingTrafficColumnVisible(): Promise<void> {
await expect(this.receivingTrafficColumnLabel).toBeVisible();
}
}
Loading