From 9651ff5e2060e16162e967d883db0068c82b2c06 Mon Sep 17 00:00:00 2001 From: Santiago Greco Date: Mon, 17 Aug 2026 17:31:08 +0200 Subject: [PATCH] CONSOLE-5233: Migrate 6 Cypress e2e tests from app/ to Playwright Migrate debug-pod, deployments, start-job-from-cronjob, machine-config, auth-multiuser-login, and admission-webhook-warning-notifications from Cypress to Playwright, validated against a live cluster. Assisted-by: Claude Opus 4.6 (1M context) --- frontend/e2e/pages/list-page.ts | 7 + frontend/e2e/pages/yaml-editor-page.ts | 4 +- ...sion-webhook-warning-notifications.spec.ts | 186 +++++++++++++++++ .../console/app/auth-multiuser-login.spec.ts | 105 ++++++++++ .../e2e/tests/console/app/debug-pod.spec.ts | 190 ++++++++++++++++++ .../e2e/tests/console/app/deployments.spec.ts | 64 ++++++ .../tests/console/app/machine-config.spec.ts | 63 ++++++ .../app/start-job-from-cronjob.spec.ts | 115 +++++++++++ frontend/e2e/utils/retry-model-error.ts | 20 ++ ...ission-webhook-warning-notifications.cy.ts | 167 --------------- .../tests/app/auth-multiuser-login.cy.ts | 89 -------- .../tests/app/debug-pod.cy.ts | 113 ----------- .../tests/app/deployments.cy.ts | 55 ----- .../tests/app/machine-config.cy.ts | 68 ------- .../tests/app/start-job-from-cronjob.cy.ts | 76 ------- 15 files changed, 752 insertions(+), 570 deletions(-) create mode 100644 frontend/e2e/tests/console/app/admission-webhook-warning-notifications.spec.ts create mode 100644 frontend/e2e/tests/console/app/auth-multiuser-login.spec.ts create mode 100644 frontend/e2e/tests/console/app/debug-pod.spec.ts create mode 100644 frontend/e2e/tests/console/app/deployments.spec.ts create mode 100644 frontend/e2e/tests/console/app/machine-config.spec.ts create mode 100644 frontend/e2e/tests/console/app/start-job-from-cronjob.spec.ts create mode 100644 frontend/e2e/utils/retry-model-error.ts delete mode 100644 frontend/packages/integration-tests/tests/app/admission-webhook-warning-notifications.cy.ts delete mode 100644 frontend/packages/integration-tests/tests/app/auth-multiuser-login.cy.ts delete mode 100644 frontend/packages/integration-tests/tests/app/debug-pod.cy.ts delete mode 100644 frontend/packages/integration-tests/tests/app/deployments.cy.ts delete mode 100644 frontend/packages/integration-tests/tests/app/machine-config.cy.ts delete mode 100644 frontend/packages/integration-tests/tests/app/start-job-from-cronjob.cy.ts diff --git a/frontend/e2e/pages/list-page.ts b/frontend/e2e/pages/list-page.ts index 716c87275d8..5a101645f45 100644 --- a/frontend/e2e/pages/list-page.ts +++ b/frontend/e2e/pages/list-page.ts @@ -151,6 +151,13 @@ export class ListPage extends BasePage { } } + async clickStatusButton(resourceName: string): Promise { + const cell = this.getCell(resourceName); + const row = cell.locator('xpath=ancestor::tr'); + const statusButton = row.getByTestId('popover-status-button'); + await this.robustClick(statusButton, { timeout: 60_000 }); + } + async clickFirstRowLink(): Promise { const firstLink = this.dataViewCells.first().locator('a').first(); await this.robustClick(firstLink); diff --git a/frontend/e2e/pages/yaml-editor-page.ts b/frontend/e2e/pages/yaml-editor-page.ts index 4fcb249c714..ab1110da14d 100644 --- a/frontend/e2e/pages/yaml-editor-page.ts +++ b/frontend/e2e/pages/yaml-editor-page.ts @@ -12,8 +12,8 @@ export class YamlEditorPage extends BasePage { private readonly yamlError = this.page.getByTestId('yaml-error'); private readonly resourceSidebar = this.page.getByTestId('resource-sidebar'); - async navigateToImportYaml(): Promise { - await this.goTo('/k8s/ns/default/import'); + async navigateToImportYaml(namespace = 'default'): Promise { + await this.goTo(`/k8s/ns/${namespace}/import`); } async waitForEditorReady(): Promise { diff --git a/frontend/e2e/tests/console/app/admission-webhook-warning-notifications.spec.ts b/frontend/e2e/tests/console/app/admission-webhook-warning-notifications.spec.ts new file mode 100644 index 00000000000..ed99f954f02 --- /dev/null +++ b/frontend/e2e/tests/console/app/admission-webhook-warning-notifications.spec.ts @@ -0,0 +1,186 @@ +import { test, expect } from '../../../fixtures'; +import { YamlEditorPage } from '../../../pages/yaml-editor-page'; + +const POD_NAME = 'pod1'; +const DEPLOY_NAME = 'deploy1'; +const CONTAINER_NAME = 'container1'; +const WARNING_FOO = '299 - "[pod-must-have-label-foo] you must provide labels: {"foo"}"'; +const WARNING_BAR = '299 - "[deployment-must-have-label-bar] you must provide labels: {"bar"}"'; +const WARNING_ID = 'admission-webhook-warning'; +const LEARN_MORE_ID = 'admission-webhook-warning-learn-more'; + +test.describe('Admission Webhook warning notification', () => { + let ns: string; + + test.beforeAll(async ({ k8sClient }) => { + ns = `test-webhook-warn-${Date.now()}`; + await k8sClient.createNamespace(ns); + }); + + test.afterAll(async ({ k8sClient }) => { + await k8sClient.deleteNamespace(ns); + }); + + test('displays warning notification when creating a pod', async ({ page }) => { + const yamlEditorPage = new YamlEditorPage(page); + + const podYaml = `apiVersion: v1 +kind: Pod +metadata: + name: ${POD_NAME}-a + labels: + app: httpd + namespace: ${ns} +spec: + securityContext: + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + containers: + - name: ${CONTAINER_NAME} + image: 'image-registry.openshift-image-registry.svc:5000/openshift/httpd:latest' + ports: + - containerPort: 8080 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL`; + + await yamlEditorPage.navigateToImportYaml(ns); + await yamlEditorPage.waitForEditorReady(); + await yamlEditorPage.setEditorContent(podYaml); + + await page.route(`**/api/kubernetes/api/v1/namespaces/${ns}/pods`, async (route) => { + if (route.request().method() === 'POST') { + const response = await route.fetch(); + await route.fulfill({ + response, + headers: { ...response.headers(), Warning: WARNING_FOO }, + }); + } else { + await route.continue(); + } + }); + + await yamlEditorPage.clickSave(); + + await expect(page.getByTestId('section-heading-Pod details')).toBeVisible({ timeout: 30_000 }); + const warning = page.getByTestId(WARNING_ID); + await expect(warning).toContainText('Admission Webhook Warning', { timeout: 10_000 }); + await expect(warning).toContainText( + `Pod ${POD_NAME}-a violates policy ${WARNING_FOO}`, + ); + await expect(page.getByTestId(LEARN_MORE_ID)).toContainText('Learn more'); + await page.getByTestId(LEARN_MORE_ID).click(); + }); + + test('displays warning notifications when creating bulk resources', async ({ page }) => { + const yamlEditorPage = new YamlEditorPage(page); + + const bulkYaml = `apiVersion: v1 +kind: Pod +metadata: + name: ${POD_NAME}-b + labels: + app: httpd + namespace: ${ns} +spec: + securityContext: + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + containers: + - name: ${CONTAINER_NAME} + image: 'image-registry.openshift-image-registry.svc:5000/openshift/httpd:latest' + ports: + - containerPort: 8080 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ${DEPLOY_NAME} + namespace: ${ns} +spec: + selector: + matchLabels: + app: deploy1 + replicas: 3 + template: + metadata: + labels: + app: deploy1 + spec: + containers: + - name: ${CONTAINER_NAME} + image: >- + image-registry.openshift-image-registry.svc:5000/openshift/httpd:latest + ports: + - containerPort: 8080 + protocol: TCP + env: + - name: app + value: frontennd + imagePullSecrets: [] + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + paused: false`; + + await yamlEditorPage.navigateToImportYaml(ns); + await yamlEditorPage.waitForEditorReady(); + await yamlEditorPage.setEditorContent(bulkYaml); + + await page.route(`**/api/kubernetes/api/v1/namespaces/${ns}/pods`, async (route) => { + if (route.request().method() === 'POST') { + const response = await route.fetch(); + await route.fulfill({ + response, + headers: { ...response.headers(), Warning: WARNING_FOO }, + }); + } else { + await route.continue(); + } + }); + + await page.route( + `**/api/kubernetes/apis/apps/v1/namespaces/${ns}/deployments`, + async (route) => { + if (route.request().method() === 'POST') { + const response = await route.fetch(); + await route.fulfill({ + response, + headers: { ...response.headers(), Warning: WARNING_BAR }, + }); + } else { + await route.continue(); + } + }, + ); + + await yamlEditorPage.clickSave(); + + await expect(page.getByTestId('resources-successfully-created')).toContainText( + 'Resources successfully created', + { timeout: 30_000 }, + ); + const warning = page.getByTestId(WARNING_ID); + await expect(warning).toHaveCount(2, { timeout: 10_000 }); + await expect(warning.first()).toContainText('Admission Webhook Warning'); + await expect( + warning.filter({ hasText: `Pod ${POD_NAME}-b violates policy ${WARNING_FOO}` }), + ).toBeVisible(); + await expect( + warning.filter({ hasText: `Deployment ${DEPLOY_NAME} violates policy ${WARNING_BAR}` }), + ).toBeVisible(); + await expect(page.getByTestId(LEARN_MORE_ID).first()).toContainText('Learn more'); + await page.getByTestId(LEARN_MORE_ID).first().click(); + }); +}); diff --git a/frontend/e2e/tests/console/app/auth-multiuser-login.spec.ts b/frontend/e2e/tests/console/app/auth-multiuser-login.spec.ts new file mode 100644 index 00000000000..d6779b71e5f --- /dev/null +++ b/frontend/e2e/tests/console/app/auth-multiuser-login.spec.ts @@ -0,0 +1,105 @@ +import { test, expect } from '../../../fixtures'; +import { performLogin } from '../../../setup/login-helper'; + +const KUBEADMIN_IDP = 'kube:admin'; +const KUBEADMIN_USERNAME = 'kubeadmin'; + +test.describe('Auth test', () => { + test.use({ storageState: { cookies: [], origins: [] } }); + + test('logs in as test user via htpasswd identity provider', async ({ page }) => { + const htpasswdPassword = process.env.BRIDGE_HTPASSWD_PASSWORD; + const idp = process.env.BRIDGE_HTPASSWD_IDP || 'test'; + const username = process.env.BRIDGE_HTPASSWD_USERNAME || 'test'; + const baseURL = process.env.WEB_CONSOLE_URL || 'http://localhost:9000'; + + test.skip(!htpasswdPassword, 'BRIDGE_HTPASSWD_PASSWORD not set'); + + await performLogin(page, baseURL, username, htpasswdPassword, idp); + await expect(page).toHaveURL(new RegExp(baseURL.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')), { + timeout: 30_000, + }); + + await test.step('Verify username is displayed', async () => { + await expect(page.getByTestId('user-dropdown-toggle')).toHaveText(username, { + timeout: 30_000, + }); + }); + + await test.step('Switch to Admin perspective', async () => { + const toggle = page.getByTestId('perspective-switcher-toggle'); + await toggle.click(); + const adminOption = page + .getByTestId('perspective-switcher-menu-option') + .filter({ hasText: 'Core platform' }); + await adminOption.click(); + await expect(toggle).toContainText('Core platform', { timeout: 30_000 }); + }); + + await test.step('Verify restricted admin nav items are not visible', async () => { + const sidebar = page.locator('#page-sidebar'); + await expect(sidebar).toBeVisible({ timeout: 30_000 }); + + for (const section of ['Compute', 'Monitoring']) { + await expect(sidebar.getByRole('button', { name: section })).not.toBeAttached({ + timeout: 30_000, + }); + } + + for (const link of [ + 'Cluster Status', + 'Cluster Settings', + 'Namespaces', + 'Custom Resource Definitions', + 'Software Catalog', + 'Persistent Volumes', + ]) { + await expect(sidebar.getByRole('link', { name: link })).not.toBeAttached(); + } + }); + }); + + test('logs in as kubeadmin user', async ({ page }) => { + const kubeadminPassword = process.env.BRIDGE_KUBEADMIN_PASSWORD; + const baseURL = process.env.WEB_CONSOLE_URL || 'http://localhost:9000'; + + test.skip(!kubeadminPassword, 'BRIDGE_KUBEADMIN_PASSWORD not set'); + + await performLogin(page, baseURL, KUBEADMIN_USERNAME, kubeadminPassword!, KUBEADMIN_IDP); + await expect(page.getByTestId('loading-indicator')).not.toBeAttached({ timeout: 30_000 }); + + await test.step('Verify kubeadmin username', async () => { + await expect(page.getByTestId('user-dropdown-toggle')).toHaveText(KUBEADMIN_IDP, { + timeout: 30_000, + }); + }); + + await test.step('Verify temporary admin notification', async () => { + await expect(page.getByTestId('global-notifications')).toContainText( + 'You are logged in as a temporary administrative user', + { timeout: 30_000 }, + ); + }); + + await test.step('Verify Admin perspective and nav sections', async () => { + const toggle = page.getByTestId('perspective-switcher-toggle'); + await expect(toggle).toContainText('Core platform', { timeout: 30_000 }); + + const sidebar = page.locator('#page-sidebar'); + await expect(sidebar.getByRole('button', { name: 'Compute' })).toBeVisible({ + timeout: 30_000, + }); + await expect(sidebar.getByRole('button', { name: 'Administration' })).toBeVisible(); + }); + + await test.step('Navigate to Cluster Settings', async () => { + const sidebar = page.locator('#page-sidebar'); + const adminSection = sidebar.getByRole('button', { name: 'Administration' }); + await adminSection.click(); + await sidebar.getByRole('link', { name: 'Cluster Settings' }).click(); + await expect(page.getByTestId('cluster-settings-page-heading')).toBeVisible({ + timeout: 30_000, + }); + }); + }); +}); diff --git a/frontend/e2e/tests/console/app/debug-pod.spec.ts b/frontend/e2e/tests/console/app/debug-pod.spec.ts new file mode 100644 index 00000000000..28fa1794bd7 --- /dev/null +++ b/frontend/e2e/tests/console/app/debug-pod.spec.ts @@ -0,0 +1,190 @@ +import type KubernetesClient from '../../../clients/kubernetes-client'; +import { test, expect } from '../../../fixtures'; +import { DetailsPage } from '../../../pages/details-page'; +import { ListPage } from '../../../pages/list-page'; +import { YamlEditorPage } from '../../../pages/yaml-editor-page'; +import { retryOnModelNotFound } from '../../../utils/retry-model-error'; + +const POD_NAME = 'pod1'; +const CONTAINER_NAME = 'container1'; + +const podYaml = `apiVersion: v1 +kind: Pod +metadata: + name: ${POD_NAME} +spec: + securityContext: + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + containers: + - name: ${CONTAINER_NAME} + image: quay.io/fedora/fedora + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + restartPolicy: Always`; + +async function waitForPodCrashState( + k8sClient: KubernetesClient, + namespace: string, + podName: string, + timeoutMs = 120_000, +): Promise { + const deadline = Date.now() + timeoutMs; + + while (Date.now() < deadline) { + try { + const pods = await k8sClient.getPods(namespace); + const pod = pods.find((p) => p.metadata?.name === podName); + if (!pod) { + await new Promise((r) => setTimeout(r, 3_000)); + continue; + } + const container = pod.status?.containerStatuses?.[0]; + const waitingReason = container?.state?.waiting?.reason; + const restartCount = container?.restartCount ?? 0; + if ( + waitingReason === 'CrashLoopBackOff' || + waitingReason === 'CreateContainerConfigError' || + restartCount >= 1 + ) { + return true; + } + } catch { + // API call failed, retry + } + + await new Promise((r) => setTimeout(r, 3_000)); + } + return false; +} + +test.describe('Debug pod', () => { + let ns: string; + + test.beforeAll(async ({ k8sClient }) => { + ns = `test-debug-pod-${Date.now()}`; + // Create namespace without openshift.io/run-level label so that SCC + // injects the correct runAsUser for pods with runAsNonRoot: true. + await k8sClient.coreV1Api.createNamespace({ + body: { metadata: { name: ns } }, + }); + await k8sClient.waitForNamespaceReady(ns); + }); + + test.afterAll(async ({ k8sClient }) => { + await k8sClient.deleteNamespace(ns); + }); + + test('debug terminal is accessible from logs, pod details status, and pod list status', async ({ + page, + k8sClient, + }) => { + test.setTimeout(300_000); + + const detailsPage = new DetailsPage(page); + const listPage = new ListPage(page); + const yamlEditorPage = new YamlEditorPage(page); + + await test.step('Create pod via YAML import', async () => { + await yamlEditorPage.navigateToImportYaml(ns); + await yamlEditorPage.waitForEditorReady(); + await yamlEditorPage.setEditorContent(podYaml); + await yamlEditorPage.clickSave(); + await expect(yamlEditorPage.getYamlError()).not.toBeAttached(); + await expect( + page.getByTestId('section-heading-Pod details'), + ).toBeVisible({ timeout: 30_000 }); + }); + + await test.step('Wait for pod to enter CrashLoopBackOff', async () => { + const crashed = await waitForPodCrashState(k8sClient, ns, POD_NAME); + expect(crashed, 'Pod never entered a crash/error state').toBe(true); + }); + + await test.step('Open debug terminal from Logs tab', async () => { + await listPage.navigateToListPage(`/k8s/ns/${ns}/pods`); + await expect(listPage.cell(POD_NAME)).toBeVisible({ timeout: 30_000 }); + + await detailsPage.navigateToDetailsPage(`/k8s/ns/${ns}/pods/${POD_NAME}`); + await detailsPage.waitForPageLoad(); + await retryOnModelNotFound(page); + await detailsPage.selectTab('Logs'); + + await page.getByTestId('debug-container-link').click({ timeout: 30_000 }); + await expect(listPage.heading).toContainText(`Debug ${CONTAINER_NAME}`, { + timeout: 30_000, + }); + await expect(detailsPage.xtermViewport).toBeAttached({ timeout: 30_000 }); + + await detailsPage.getBreadcrumb(0).click(); + await expect(listPage.cell(POD_NAME)).toBeVisible({ timeout: 30_000 }); + }); + + await test.step('Open debug terminal from Pod Details status popover', async () => { + await detailsPage.navigateToDetailsPage(`/k8s/ns/${ns}/pods/${POD_NAME}`); + await detailsPage.waitForPageLoad(); + await retryOnModelNotFound(page); + + await page.getByTestId('popover-status-button').click({ timeout: 60_000 }); + const debugLink = page.getByTestId(`popup-debug-container-link-${CONTAINER_NAME}`); + await expect(debugLink).toBeVisible({ timeout: 10_000 }); + await debugLink.click(); + + await expect(listPage.heading).toContainText(`Debug ${CONTAINER_NAME}`, { + timeout: 30_000, + }); + await expect(detailsPage.xtermViewport).toBeAttached({ timeout: 30_000 }); + + await detailsPage.getBreadcrumb(0).click(); + await expect(listPage.cell(POD_NAME)).toBeVisible({ timeout: 30_000 }); + }); + + await test.step('Open debug terminal from Pods list status popover', async () => { + await listPage.navigateToListPage(`/k8s/ns/${ns}/pods`); + await expect(listPage.cell(POD_NAME)).toBeVisible({ timeout: 30_000 }); + + await listPage.clickStatusButton(POD_NAME); + const debugLink = page.getByTestId(`popup-debug-container-link-${CONTAINER_NAME}`); + await expect(debugLink).toBeVisible({ timeout: 10_000 }); + await debugLink.click(); + + await expect(listPage.heading).toContainText(`Debug ${CONTAINER_NAME}`, { + timeout: 30_000, + }); + await expect(detailsPage.xtermViewport).toBeAttached({ timeout: 30_000 }); + }); + + await test.step('Verify debug pod has a different IP than the main pod', async () => { + const pods = await k8sClient.getPods(ns); + expect(pods.length).toBeGreaterThanOrEqual(2); + const mainPod = pods.find((p) => p.metadata?.name === POD_NAME); + const debugPod = pods.find((p) => p.metadata?.name !== POD_NAME); + expect(mainPod?.status?.podIP).toBeTruthy(); + expect(debugPod?.status?.podIP).toBeTruthy(); + expect(mainPod?.status?.podIP).not.toEqual(debugPod?.status?.podIP); + }); + + await test.step('Verify debug pod is terminated after leaving debug page', async () => { + await detailsPage.getBreadcrumb(0).click(); + await expect(listPage.cell(POD_NAME)).toBeVisible({ timeout: 30_000 }); + + await listPage.navigateToListPage(`/k8s/ns/${ns}/pods`); + await expect(listPage.cell(POD_NAME)).toBeVisible({ timeout: 30_000 }); + await listPage.filterByCheckbox('Status', 'Running'); + + await expect + .poll( + async () => { + const pods = await k8sClient.getPods(ns); + return pods.filter((p) => p.metadata?.name !== POD_NAME).length; + }, + { timeout: 60_000 }, + ) + .toBe(0); + }); + }); +}); diff --git a/frontend/e2e/tests/console/app/deployments.spec.ts b/frontend/e2e/tests/console/app/deployments.spec.ts new file mode 100644 index 00000000000..e7ad324bb00 --- /dev/null +++ b/frontend/e2e/tests/console/app/deployments.spec.ts @@ -0,0 +1,64 @@ +import { test, expect } from '../../../fixtures'; +import { DetailsPage } from '../../../pages/details-page'; +import { retryOnModelNotFound } from '../../../utils/retry-model-error'; + +test.describe('Deployment resource details page', () => { + let ns: string; + const workloadName = `deployment-test`; + + test.beforeAll(async ({ k8sClient }) => { + ns = `test-deployments-${Date.now()}`; + await k8sClient.createNamespace(ns); + + await k8sClient.createDeployment(ns, { + metadata: { name: workloadName, namespace: ns }, + spec: { + replicas: 0, + selector: { matchLabels: { app: workloadName } }, + template: { + metadata: { labels: { app: workloadName } }, + spec: { + containers: [{ name: 'httpd', image: 'httpd' }], + }, + }, + }, + }); + + await k8sClient.createCustomResource('autoscaling', 'v1', ns, 'horizontalpodautoscalers', { + metadata: { name: workloadName, namespace: ns }, + spec: { + scaleTargetRef: { + apiVersion: 'apps/v1', + kind: 'Deployment', + name: workloadName, + }, + minReplicas: 1, + maxReplicas: 10, + }, + }); + }); + + test.afterAll(async ({ k8sClient }) => { + await k8sClient.deleteNamespace(ns); + }); + + test('enable autoscale button toggles when clicked', async ({ page }) => { + const detailsPage = new DetailsPage(page); + + await test.step('Enable autoscale button should exist and be clickable', async () => { + await detailsPage.navigateToDetailsPage(`/k8s/ns/${ns}/deployments/${workloadName}`); + await detailsPage.waitForPageLoad(); + await retryOnModelNotFound(page); + const enableAutoscale = page.getByTestId('enable-autoscale'); + await expect(enableAutoscale).toBeVisible({ timeout: 30_000 }); + await enableAutoscale.click(); + }); + + await test.step('Enable autoscale button should not exist after enabling', async () => { + await detailsPage.navigateToDetailsPage(`/k8s/ns/${ns}/deployments/${workloadName}`); + await detailsPage.waitForPageLoad(); + await retryOnModelNotFound(page); + await expect(page.getByTestId('enable-autoscale')).not.toBeAttached({ timeout: 30_000 }); + }); + }); +}); diff --git a/frontend/e2e/tests/console/app/machine-config.spec.ts b/frontend/e2e/tests/console/app/machine-config.spec.ts new file mode 100644 index 00000000000..47da761bee5 --- /dev/null +++ b/frontend/e2e/tests/console/app/machine-config.spec.ts @@ -0,0 +1,63 @@ +import { test, expect } from '../../../fixtures'; +import { DetailsPage } from '../../../pages/details-page'; +import { retryOnModelNotFound } from '../../../utils/retry-model-error'; + +const MC_WITH_CONFIG_FILES = '00-master'; +const MC_WITHOUT_CONFIG_FILES = '99-master-ssh'; +const MC_DETAILS_PAGE_URL = '/k8s/cluster/machineconfiguration.openshift.io~v1~MachineConfig/'; +const MC_SECTION_HEADING = 'section-heading-Configuration files'; + +test.describe('MachineConfig resource details page', () => { + test(`${MC_WITH_CONFIG_FILES} displays configuration files`, async ({ page, k8sClient }) => { + const detailsPage = new DetailsPage(page); + + await detailsPage.navigateToDetailsPage(`${MC_DETAILS_PAGE_URL}${MC_WITH_CONFIG_FILES}`); + await detailsPage.waitForPageLoad(); + await retryOnModelNotFound(page); + await expect(detailsPage.title).toContainText(MC_WITH_CONFIG_FILES, { timeout: 30_000 }); + await expect(page.getByTestId(MC_SECTION_HEADING)).toBeVisible({ timeout: 30_000 }); + await expect(page.getByTestId('config-file-path-0')).toBeVisible(); + await expect(page.locator('.co-copy-to-clipboard__text').first()).toBeVisible(); + + const mc = (await k8sClient.getClusterCustomResource( + 'machineconfiguration.openshift.io', + 'v1', + 'machineconfigs', + MC_WITH_CONFIG_FILES, + )) as { spec?: { config?: { storage?: { files?: Array<{ contents?: { source?: string }; mode?: number; overwrite?: boolean }> } } } }; + + const file = mc.spec?.config?.storage?.files?.[0]; + expect(file).toBeDefined(); + expect(file?.contents).toBeDefined(); + expect(file?.mode).toBeDefined(); + expect(file?.overwrite).toBeDefined(); + + await page.getByTestId('config-file-path-0').scrollIntoViewIfNeeded(); + await page.locator('button[aria-label="Info"]').first().click(); + + const descriptionList = page.locator('[class*="description-list"]'); + await expect(descriptionList.getByText(String(file!.mode), { exact: true })).toBeVisible({ + timeout: 10_000, + }); + await expect( + descriptionList.getByText(String(file!.overwrite), { exact: true }), + ).toBeVisible({ timeout: 10_000 }); + + const decodedContent = decodeURIComponent(file!.contents!.source!) + .replace(/^(data:,)/, '') + .slice(0, 30); + await expect(page.locator('code').first()).toContainText(decodedContent, { timeout: 10_000 }); + }); + + test(`${MC_WITHOUT_CONFIG_FILES} does not display configuration files`, async ({ page }) => { + const detailsPage = new DetailsPage(page); + + await detailsPage.navigateToDetailsPage(`${MC_DETAILS_PAGE_URL}${MC_WITHOUT_CONFIG_FILES}`); + await detailsPage.waitForPageLoad(); + await retryOnModelNotFound(page); + await expect(detailsPage.title).toContainText(MC_WITHOUT_CONFIG_FILES, { timeout: 30_000 }); + await expect(page.getByTestId(MC_SECTION_HEADING)).not.toBeAttached(); + await expect(page.getByTestId('config-file-path-0')).not.toBeAttached(); + await expect(page.locator('.co-copy-to-clipboard__text')).not.toBeAttached(); + }); +}); diff --git a/frontend/e2e/tests/console/app/start-job-from-cronjob.spec.ts b/frontend/e2e/tests/console/app/start-job-from-cronjob.spec.ts new file mode 100644 index 00000000000..78565708b8c --- /dev/null +++ b/frontend/e2e/tests/console/app/start-job-from-cronjob.spec.ts @@ -0,0 +1,115 @@ +import { test, expect } from '../../../fixtures'; +import { DetailsPage } from '../../../pages/details-page'; +import { ListPage } from '../../../pages/list-page'; +import { YamlEditorPage } from '../../../pages/yaml-editor-page'; +import { retryOnModelNotFound } from '../../../utils/retry-model-error'; + +const CRONJOB_NAME = 'cronjob1'; + +test.describe('Start a Job from a CronJob', () => { + let ns: string; + + test.beforeAll(async ({ k8sClient }) => { + ns = `test-cronjob-${Date.now()}`; + await k8sClient.createNamespace(ns); + }); + + test.afterAll(async ({ k8sClient }) => { + await k8sClient.deleteNamespace(ns); + }); + + test('start jobs from CronJob details and list pages', async ({ page }) => { + const detailsPage = new DetailsPage(page); + const listPage = new ListPage(page); + const yamlEditorPage = new YamlEditorPage(page); + + const cronJobYaml = `apiVersion: batch/v1 +kind: CronJob +metadata: + name: ${CRONJOB_NAME} + namespace: ${ns} +spec: + schedule: '@daily' + jobTemplate: + spec: + template: + spec: + containers: + - name: hello + image: busybox + args: + - /bin/sh + - '-c' + - date; echo Hello from the Openshift cluster + restartPolicy: OnFailure`; + + await test.step('Create CronJob via YAML import', async () => { + await yamlEditorPage.navigateToImportYaml(ns); + await yamlEditorPage.waitForEditorReady(); + await yamlEditorPage.setEditorContent(cronJobYaml); + await yamlEditorPage.clickSave(); + await expect( + page.getByTestId('section-heading-CronJob details'), + ).toBeVisible({ timeout: 30_000 }); + }); + + await test.step('Start Job from CronJob details page', async () => { + await detailsPage.clickActionsMenuAction('Start Job'); + await detailsPage.waitForPageLoad(); + await retryOnModelNotFound(page); + await expect( + page.getByTestId('section-heading-Job details'), + ).toBeVisible({ timeout: 30_000 }); + await expect(detailsPage.title).toContainText(CRONJOB_NAME, { timeout: 30_000 }); + }); + + await test.step('Start Job from CronJob list page kebab', async () => { + await listPage.navigateToListPage(`/k8s/ns/${ns}/cronjobs`); + await listPage.waitForRows(); + await expect(listPage.cell(CRONJOB_NAME)).toBeVisible({ timeout: 60_000 }); + + // WebSocket updates can re-render the table and close the kebab menu. + // Retry opening the kebab if the action disappears. + const row = listPage.cell(CRONJOB_NAME).locator('xpath=ancestor::tr'); + const kebab = row.getByTestId('kebab-button'); + const action = page.getByTestId('Start Job'); + + const deadline = Date.now() + 30_000; + let found = false; + while (Date.now() < deadline) { + await kebab.click(); + try { + // eslint-disable-next-line no-restricted-syntax + await action.waitFor({ state: 'visible', timeout: 5_000 }); + found = true; + break; + } catch { + // Menu closed due to table re-render; retry + } + } + expect(found, 'Kebab action "Start Job" was not visible after retries').toBeTruthy(); + await action.click(); + + await detailsPage.waitForPageLoad(); + await retryOnModelNotFound(page); + await expect( + page.getByTestId('section-heading-Job details'), + ).toBeVisible({ timeout: 30_000 }); + await expect(detailsPage.title).toContainText(CRONJOB_NAME, { timeout: 30_000 }); + }); + + await test.step('Verify number of Jobs in CronJob Jobs tab', async () => { + await page.goto(`/k8s/ns/${ns}/cronjobs/${CRONJOB_NAME}/jobs`); + await listPage.waitForRows(); + await expect(listPage.cells).toHaveCount(2, { timeout: 30_000 }); + }); + + await test.step('Verify number of events in CronJob Events tab', async () => { + await page.goto(`/k8s/ns/${ns}/cronjobs/${CRONJOB_NAME}/events`); + await retryOnModelNotFound(page); + await expect(page.getByTestId('event-totals')).toHaveText('Showing 2 events', { + timeout: 30_000, + }); + }); + }); +}); diff --git a/frontend/e2e/utils/retry-model-error.ts b/frontend/e2e/utils/retry-model-error.ts new file mode 100644 index 00000000000..9d858e2f4f8 --- /dev/null +++ b/frontend/e2e/utils/retry-model-error.ts @@ -0,0 +1,20 @@ +import type { Page } from '@playwright/test'; + +export async function retryOnModelNotFound(page: Page, maxRetries = 3): Promise { + const errorLocator = page.getByText('Model does not exist'); + + for (let attempt = 0; attempt < maxRetries; attempt++) { + try { + // eslint-disable-next-line no-restricted-syntax + await errorLocator.waitFor({ state: 'visible', timeout: 5_000 }); + } catch { + return; + } + await page.reload({ waitUntil: 'load' }); + } + + // eslint-disable-next-line no-restricted-syntax + if (await errorLocator.isVisible().catch(() => false)) { + throw new Error(`"Model does not exist" persisted after ${maxRetries} reload attempts`); + } +} diff --git a/frontend/packages/integration-tests/tests/app/admission-webhook-warning-notifications.cy.ts b/frontend/packages/integration-tests/tests/app/admission-webhook-warning-notifications.cy.ts deleted file mode 100644 index a418150e6dd..00000000000 --- a/frontend/packages/integration-tests/tests/app/admission-webhook-warning-notifications.cy.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { checkErrors, testName } from '../../support'; -import { detailsPage } from '../../views/details-page'; -import * as yamlEditor from '../../views/yaml-editor'; - -const POD_NAME = 'pod1'; -const DEPLOY_NAME = 'deploy1'; -const CONTAINER_NAME = 'container1'; -const WARNING_FOO = '299 - "[pod-must-have-label-foo] you must provide labels: {"foo"}"'; -const WARNING_BAR = '299 - "[deployment-must-have-label-bar] you must provide labels: {"bar"}"'; -const WAIT_OPTION = { timeout: 5000 }; -const POD_CREATED_ALIAS = 'podCreated'; -const BULK_RESOURCES_CREATED_ALIAS = 'bulkResourcesCreated'; -const LEARN_MORE_ID = 'admission-webhook-warning-learn-more'; -const WARNING_ID = 'admission-webhook-warning'; -const resources = [ - { kind: 'Pod', name: `${POD_NAME}-b`, warning: WARNING_FOO, resource: 'pods', path: 'api' }, - { - kind: 'Deployment', - name: DEPLOY_NAME, - warning: WARNING_BAR, - resource: 'deployments', - path: 'apis/apps', - }, -]; -const pod1ReqObj = `apiVersion: v1 -kind: Pod -metadata: - name: ${POD_NAME}-a - labels: - app: httpd - namespace: ${testName} -spec: - securityContext: - runAsNonRoot: true - seccompProfile: - type: RuntimeDefault - containers: - - name: ${CONTAINER_NAME} - image: 'image-registry.openshift-image-registry.svc:5000/openshift/httpd:latest' - ports: - - containerPort: 8080 - securityContext: - allowPrivilegeEscalation: false - capabilities: - drop: - - ALL`; - -const bulkResourcesReqObj = `apiVersion: v1 -kind: Pod -metadata: - name: ${POD_NAME}-b - labels: - app: httpd - namespace: ${testName} -spec: - securityContext: - runAsNonRoot: true - seccompProfile: - type: RuntimeDefault - containers: - - name: ${CONTAINER_NAME} - image: 'image-registry.openshift-image-registry.svc:5000/openshift/httpd:latest' - ports: - - containerPort: 8080 - securityContext: - allowPrivilegeEscalation: false - capabilities: - drop: - - ALL ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: ${DEPLOY_NAME} - annotations: {} - namespace: ${testName} -spec: - selector: - matchLabels: - app: deploy1 - replicas: 3 - template: - metadata: - labels: - app: deploy1 - spec: - containers: - - name: ${CONTAINER_NAME} - image: >- - image-registry.openshift-image-registry.svc:5000/openshift/httpd:latest - ports: - - containerPort: 8080 - protocol: TCP - env: - - name: app - value: frontennd - imagePullSecrets: [] - strategy: - type: RollingUpdate - rollingUpdate: - maxSurge: 25% - maxUnavailable: 25% - paused: false -`; - -describe('Admission Webhook warning notification', () => { - before(() => { - cy.login(); - cy.createProjectWithCLI(testName); - }); - - afterEach(() => { - checkErrors(); - }); - - after(() => { - cy.visit('/'); - cy.deleteProjectWithCLI(testName); - }); - - it('Create a pod and display Admission Webhook warning notification', () => { - cy.visit(`/k8s/ns/${testName}/import`); - yamlEditor.isImportLoaded(); - yamlEditor.setEditorContent(pod1ReqObj).then(() => { - cy.intercept('POST', `/api/kubernetes/api/v1/namespaces/${testName}/pods`, (req) => { - req.continue((res) => { - res.headers = { - Warning: WARNING_FOO, - }; - }); - }).as(POD_CREATED_ALIAS); - yamlEditor.clickSaveCreateButton(); - cy.wait(`@${POD_CREATED_ALIAS}`, WAIT_OPTION); - detailsPage.sectionHeaderShouldExist('Pod details'); - cy.byTestID(WARNING_ID).contains('Admission Webhook Warning'); - cy.byTestID(WARNING_ID).contains(`Pod ${POD_NAME}-a violates policy ${WARNING_FOO}`); - cy.byTestID(LEARN_MORE_ID).contains('Learn more').click(); - }); - }); - - it('Create bulk resources and display Admission Webhook warning notifications', () => { - cy.visit(`/k8s/ns/${testName}/import`); - yamlEditor.isImportLoaded(); - yamlEditor.setEditorContent(bulkResourcesReqObj).then(() => { - for (const resource of resources) { - cy.intercept( - 'POST', - `/api/kubernetes/${resource.path}/v1/namespaces/${testName}/${resource.resource}`, - (req) => { - req.continue((res) => { - res.headers = { - Warning: resource.warning, - }; - }); - }, - ).as(BULK_RESOURCES_CREATED_ALIAS); - } - yamlEditor.clickSaveCreateButton(); - cy.wait(`@${BULK_RESOURCES_CREATED_ALIAS}`, WAIT_OPTION); - cy.byTestID('resources-successfully-created').contains('Resources successfully created'); - cy.byTestID(WARNING_ID).contains('Admission Webhook Warning'); - cy.byTestID(WARNING_ID).contains(`Pod ${POD_NAME}-b violates policy ${WARNING_FOO}`); - cy.byTestID(WARNING_ID).contains(`Deployment ${DEPLOY_NAME} violates policy ${WARNING_BAR}`); - cy.byTestID(LEARN_MORE_ID).contains('Learn more').click(); - }); - }); -}); diff --git a/frontend/packages/integration-tests/tests/app/auth-multiuser-login.cy.ts b/frontend/packages/integration-tests/tests/app/auth-multiuser-login.cy.ts deleted file mode 100644 index c373f4d85d1..00000000000 --- a/frontend/packages/integration-tests/tests/app/auth-multiuser-login.cy.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { checkErrors } from '../../support'; -import { masthead } from '../../views/masthead'; -import { nav } from '../../views/nav'; - -describe('Auth test', () => { - const KUBEADMIN_IDP = 'kube:admin'; - const KUBEADMIN_USERNAME = 'kubeadmin'; - - beforeEach(() => { - // clear any existing sessions - Cypress.session.clearAllSavedSessions(); - }); - - afterEach(() => { - checkErrors(); - Cypress.session.clearAllSavedSessions(); - }); - - it(`logs in as 'test' user via htpasswd identity provider`, function () { - cy.env(['BRIDGE_KUBEADMIN_PASSWORD', 'BRIDGE_HTPASSWD_PASSWORD']).then( - ({ BRIDGE_KUBEADMIN_PASSWORD, BRIDGE_HTPASSWD_PASSWORD }) => { - if (!BRIDGE_KUBEADMIN_PASSWORD) { - this.skip(); - return; - } - const idp = Cypress.expose('BRIDGE_HTPASSWD_IDP') || 'test'; - const username = Cypress.expose('BRIDGE_HTPASSWD_USERNAME') || 'test'; - const passwd = BRIDGE_HTPASSWD_PASSWORD || 'test'; - cy.login(idp, username, passwd); - cy.url().should('include', Cypress.config('baseUrl')); - - // test Developer perspective is default for test user - // Below line to be uncommented after pr https://github.com/openshift/console-operator/pull/954 is merged - masthead.username.shouldHaveText(username); - - cy.log('switches from dev to admin perspective'); - // nav.sidenav.switcher.shouldHaveText('Developer'); - nav.sidenav.switcher.changePerspectiveTo('Core platform'); - nav.sidenav.switcher.shouldHaveText('Core platform'); - - cy.log('does not show admin nav items in Administration to test user'); - // eslint-disable-next-line cypress/no-unnecessary-waiting - cy.wait(10000); // wait for feature FLAGS to load - nav.sidenav.shouldNotHaveNavSection(['Administration', 'Cluster Status']); - nav.sidenav.shouldNotHaveNavSection(['Administration', 'Cluster Settings']); - nav.sidenav.shouldNotHaveNavSection(['Administration', 'Namespaces']); - nav.sidenav.shouldNotHaveNavSection(['Administration', 'Custom Resource Definitions']); - - cy.log('does not show admin nav items in Ecosystem to test user'); - nav.sidenav.shouldNotHaveNavSection(['Ecosystem', 'Software Catalog']); - - cy.log('does not show admin nav items in Storage to test user'); - nav.sidenav.shouldNotHaveNavSection(['Storage', 'Persistent Volumes']); - - cy.log('does not show Compute or Monitoring to test user'); - nav.sidenav.shouldNotHaveNavSection(['Compute']); - nav.sidenav.shouldNotHaveNavSection(['Monitoring']); - }, - ); - }); - - it(`log in as 'kubeadmin' user`, () => { - cy.env(['BRIDGE_KUBEADMIN_PASSWORD']).then(({ BRIDGE_KUBEADMIN_PASSWORD }) => { - cy.login(KUBEADMIN_IDP, KUBEADMIN_USERNAME, BRIDGE_KUBEADMIN_PASSWORD); - cy.byTestID('loading-indicator').should('not.exist'); - cy.url().should('include', Cypress.config('baseUrl')); - masthead.username.shouldHaveText(KUBEADMIN_IDP); - cy.byTestID('global-notifications').contains( - 'You are logged in as a temporary administrative user. Update the cluster OAuth configuration to allow others to log in.', - ); - - // test Administrator perspective is default for kubeadmin - nav.sidenav.switcher.shouldHaveText('Core platform'); - // test guided tour is displayed first time switching to 'Developer' perspective - // skip if running localhost - if (!Cypress.config('baseUrl').includes('localhost')) { - // nav.sidenav.switcher.changePerspectiveTo('Developer'); - // nav.sidenav.switcher.shouldHaveText('Developer'); - nav.sidenav.switcher.changePerspectiveTo('Core platform'); - nav.sidenav.switcher.shouldHaveText('Core platform'); - } - cy.log('verify sidenav menus and Administration menu access for cluster admin user'); - nav.sidenav.shouldHaveNavSection(['Compute']); - nav.sidenav.shouldHaveNavSection(['Operators']); - nav.sidenav.clickNavLink(['Administration', 'Cluster Settings']); - cy.byLegacyTestID('cluster-settings-page-heading').should('be.visible'); - }); - }); -}); diff --git a/frontend/packages/integration-tests/tests/app/debug-pod.cy.ts b/frontend/packages/integration-tests/tests/app/debug-pod.cy.ts deleted file mode 100644 index e5714a694f3..00000000000 --- a/frontend/packages/integration-tests/tests/app/debug-pod.cy.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { checkErrors, testName } from '../../support'; -import { detailsPage } from '../../views/details-page'; -import { listPage } from '../../views/list-page'; -import * as yamlEditor from '../../views/yaml-editor'; - -const POD_NAME = `pod1`; -const CONTAINER_NAME = `container1`; -const XTERM_CLASS = `[class="xterm-viewport"]`; -const podToDebug = `apiVersion: v1 -kind: Pod -metadata: - name: ${POD_NAME} -spec: - securityContext: - runAsNonRoot: true - seccompProfile: - type: RuntimeDefault - containers: - - name: ${CONTAINER_NAME} - image: quay.io/fedora/fedora - securityContext: - allowPrivilegeEscalation: false - capabilities: - drop: - - ALL - restartPolicy: Always`; - -describe('Debug pod', () => { - before(() => { - cy.login(); - cy.createProjectWithCLI(testName); - }); - - afterEach(() => { - checkErrors(); - }); - - after(() => { - cy.visit('/'); - cy.deleteProjectWithCLI(testName); - }); - - it('Create pod that has crashbackloop error', () => { - cy.visit(`/k8s/ns/${testName}/import`); - yamlEditor.isImportLoaded(); - yamlEditor.setEditorContent(podToDebug).then(() => { - yamlEditor.clickSaveCreateButton(); - cy.byTestID('yaml-error').should('not.exist'); - detailsPage.sectionHeaderShouldExist('Pod details'); - }); - }); - - it('Opens debug terminal page from Logs subsection', () => { - cy.visit(`/k8s/ns/${testName}/pods`); - listPage.dvRows.shouldExist(POD_NAME); - cy.visit(`/k8s/ns/${testName}/pods/${POD_NAME}`); - detailsPage.isLoaded(); - detailsPage.selectTab('Logs'); - detailsPage.isLoaded(); - cy.byTestID('debug-container-link').click(); - listPage.titleShouldHaveText(`Debug ${CONTAINER_NAME}`); - cy.get(XTERM_CLASS).should('exist'); - cy.get('[data-test-id="breadcrumb-link-0"]').click(); - listPage.dvRows.shouldExist(POD_NAME); - }); - - it('Opens debug terminal page from Pod Details - Status tool tip', () => { - cy.visit(`/k8s/ns/${testName}/pods/${POD_NAME}`); - detailsPage.isLoaded(); - cy.byTestID('popover-status-button', { timeout: 60000 }).click(); - // Regression test for OCPBUGS-83813: Wait for popover content to be stable before clicking - // https://issues.redhat.com/browse/OCPBUGS-83813 - cy.byTestID(`popup-debug-container-link-${CONTAINER_NAME}`).should('be.visible'); - cy.byTestID(`popup-debug-container-link-${CONTAINER_NAME}`).click(); - listPage.titleShouldHaveText(`Debug ${CONTAINER_NAME}`); - cy.get(XTERM_CLASS).should('exist'); - cy.get('[data-test-id="breadcrumb-link-0"]').click(); - listPage.dvRows.shouldExist(POD_NAME); - }); - - it('Opens debug terminal page from Pods Page - Status tool tip', () => { - cy.visit(`/k8s/ns/${testName}/pods`); - listPage.dvRows.shouldExist(POD_NAME); - listPage.dvRows.clickStatusButton(POD_NAME); - // Regression test for OCPBUGS-83813: Wait for popover content to be stable before clicking - // https://issues.redhat.com/browse/OCPBUGS-83813 - cy.byTestID(`popup-debug-container-link-${CONTAINER_NAME}`).should('be.visible').click(); - listPage.titleShouldHaveText(`Debug ${CONTAINER_NAME}`); - cy.get(XTERM_CLASS).should('exist'); - - cy.log('debug pod should not copy main pod network info'); - cy.exec( - `oc get pods -n ${testName} -o jsonpath='{.items[0].status.podIP}{"#"}{.items[1].status.podIP}'`, - ).then((result) => { - const [ipAddressOne, ipAddressTwo] = result.stdout.split('#'); - expect(`${ipAddressOne}`).to.not.equal(`${ipAddressTwo}`); - }); - cy.get('[data-test-id="breadcrumb-link-0"]').click(); - listPage.dvRows.shouldExist(POD_NAME); - }); - - it('Debug pod should be terminated after leaving debug container page', () => { - cy.visit(`/k8s/ns/${testName}/pods`); - listPage.dvRows.shouldExist(POD_NAME); - listPage.dvFilter.by('Status', 'Running'); - cy.exec( - `oc get pods -n ${testName} -o jsonpath='{.items[0].metadata.name}{"#"}{.items[1].metadata.name}'`, - ).then((result) => { - const debugPodName = result.stdout.split('#')[1]; - listPage.dvRows.shouldNotExist(debugPodName); - }); - }); -}); diff --git a/frontend/packages/integration-tests/tests/app/deployments.cy.ts b/frontend/packages/integration-tests/tests/app/deployments.cy.ts deleted file mode 100644 index 1f5f56f00b8..00000000000 --- a/frontend/packages/integration-tests/tests/app/deployments.cy.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { checkErrors, testName } from '../../support'; -import { detailsPage } from '../../views/details-page'; -import { listPage } from '../../views/list-page'; -import { modal } from '../../views/modal'; - -describe('Deployment resource details page', () => { - let WORKLOAD_NAME; - let CREATE_DEPLOYMENT; - let CREATE_HPA; - - before(() => { - cy.login(); - cy.initAdmin(); - cy.createProjectWithCLI(testName); - - WORKLOAD_NAME = `deployment-${testName}`; - CREATE_DEPLOYMENT = `oc create deployment ${WORKLOAD_NAME} --image=httpd --replicas=0`; - CREATE_HPA = `oc autoscale deployment ${WORKLOAD_NAME} --min=1 --max=10`; - - // Create a deployment named foo with 0 replicas using the cli - cy.exec(CREATE_DEPLOYMENT, { - failOnNonZeroExit: false, - }); - // Create an HorizontalPodAutoscaler using the cli that autoscales the deployment foo - cy.exec(CREATE_HPA, { failOnNonZeroExit: false }); - cy.visit(`/k8s/ns/${testName}/deployments`); - }); - - beforeEach(() => { - cy.visitAndWait(`/k8s/ns/${testName}/deployments/${WORKLOAD_NAME}`); - detailsPage.isLoaded(); - }); - - afterEach(() => { - checkErrors(); - }); - - after(() => { - cy.visit(`/k8s/ns/${testName}/deployments`); - listPage.dvRows.shouldBeLoaded(); - listPage.dvFilter.byName(WORKLOAD_NAME); - listPage.dvRows.clickKebabAction(WORKLOAD_NAME, 'Delete Deployment'); - modal.shouldBeOpened(); - modal.submit(); - modal.shouldBeClosed(); - cy.deleteProjectWithCLI(testName); - }); - - it('Enable deployment autoscale button should exist', () => { - cy.byTestID('enable-autoscale').should('exist').click(); - }); - it('Enable deployment autoscale button should not exist', () => { - cy.byTestID('enable-autoscale').should('not.exist'); - }); -}); diff --git a/frontend/packages/integration-tests/tests/app/machine-config.cy.ts b/frontend/packages/integration-tests/tests/app/machine-config.cy.ts deleted file mode 100644 index 6f8bd0bc50f..00000000000 --- a/frontend/packages/integration-tests/tests/app/machine-config.cy.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { checkErrors } from '../../support'; -import { detailsPage } from '../../views/details-page'; - -const MC_WITH_CONFIG_FILES = '00-master'; -const MC_WITHOUT_CONFIG_FILES = '99-master-ssh'; -const MC_DETAILS_PAGE_URL = '/k8s/cluster/machineconfiguration.openshift.io~v1~MachineConfig/'; -const MC_SECTION_HEADING = 'Configuration files'; -const MC_CONFIG_FILE_PATH_ID = 'config-file-path-0'; -const MC_C2C = '.co-copy-to-clipboard__text'; -const checkMachineConfigDetails = (mode, overwrite, content) => { - cy.byTestID(MC_CONFIG_FILE_PATH_ID).scrollIntoView(); - cy.get('button[aria-label="Info"]').first().click(); - cy.contains(mode).should('exist'); - cy.contains(overwrite.toString()).should('exist'); - cy.get('code') - .first() - .should(($code) => { - const text = $code.text(); - expect(text).to.include( - decodeURIComponent(content) - .replace(/^(data:,)/, '') - .slice(0, 30), - ); - }); -}; - -describe('MachineConfig resource details page', () => { - before(() => { - cy.login(); - cy.initAdmin(); - }); - - afterEach(() => { - checkErrors(); - }); - - it(`${MC_WITH_CONFIG_FILES} displays configuration files`, () => { - cy.visit(`${MC_DETAILS_PAGE_URL}${MC_WITH_CONFIG_FILES}`); - detailsPage.titleShouldContain(`${MC_WITH_CONFIG_FILES}`); - detailsPage.isLoaded(); - cy.byTestSectionHeading(MC_SECTION_HEADING).should('exist'); - cy.byTestID(MC_CONFIG_FILE_PATH_ID).should('exist'); - cy.get(MC_C2C).should('exist'); - cy.exec(`oc get mc ${MC_WITH_CONFIG_FILES} -o jsonpath='{.spec.config.storage.files[0]}'`).then( - (result) => { - const mcContents = JSON.parse(result.stdout); - expect(mcContents).to.have.property('contents'); - expect(mcContents).to.have.property('mode'); - expect(mcContents).to.have.property('overwrite'); - const { - contents: { source }, - mode, - overwrite, - } = mcContents; - checkMachineConfigDetails(mode, overwrite, source); - }, - ); - }); - - it(`${MC_WITHOUT_CONFIG_FILES} does not display configuration files`, () => { - cy.visit(`${MC_DETAILS_PAGE_URL}${MC_WITHOUT_CONFIG_FILES}`); - detailsPage.titleShouldContain(`${MC_WITHOUT_CONFIG_FILES}`); - detailsPage.isLoaded(); - cy.byTestSectionHeading(MC_SECTION_HEADING).should('not.exist'); - cy.byTestID(MC_CONFIG_FILE_PATH_ID).should('not.exist'); - cy.get(MC_C2C).should('not.exist'); - }); -}); diff --git a/frontend/packages/integration-tests/tests/app/start-job-from-cronjob.cy.ts b/frontend/packages/integration-tests/tests/app/start-job-from-cronjob.cy.ts deleted file mode 100644 index 08efc0ae1c4..00000000000 --- a/frontend/packages/integration-tests/tests/app/start-job-from-cronjob.cy.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { checkErrors, testName } from '../../support'; -import { detailsPage } from '../../views/details-page'; -import { listPage } from '../../views/list-page'; -import * as yamlEditor from '../../views/yaml-editor'; - -const CRONJOB_NAME = 'cronjob1'; - -const cronJobReqPayload = `apiVersion: batch/v1 -kind: CronJob -metadata: - name: ${CRONJOB_NAME} - namespace: ${testName} -spec: - schedule: '@daily' - jobTemplate: - spec: - template: - spec: - containers: - - name: hello - image: busybox - args: - - /bin/sh - - '-c' - - date; echo Hello from the Openshift cluster - restartPolicy: OnFailure`; - -describe('Start a Job from a CronJob', () => { - before(() => { - cy.login(); - cy.createProjectWithCLI(testName); - }); - - afterEach(() => { - checkErrors(); - }); - - after(() => { - cy.visit('/'); - cy.deleteProjectWithCLI(testName); - }); - - it('verify "Start Job" on the CronJob details page', () => { - cy.visit(`/k8s/ns/${testName}/import`); - yamlEditor.isImportLoaded(); - yamlEditor.setEditorContent(cronJobReqPayload).then(() => { - yamlEditor.clickSaveCreateButton(); - detailsPage.sectionHeaderShouldExist('CronJob details'); - }); - detailsPage.clickPageActionFromDropdown('Start Job'); - detailsPage.isLoaded(); - detailsPage.sectionHeaderShouldExist('Job details'); - detailsPage.titleShouldContain(`${CRONJOB_NAME}`); - }); - - it('verify "Start Job" on the CronJob list page', () => { - cy.visit(`/k8s/ns/${testName}/cronjobs`); - listPage.dvRows.shouldBeLoaded(); - listPage.dvRows.clickKebabAction(CRONJOB_NAME, 'Start Job'); - detailsPage.isLoaded(); - detailsPage.sectionHeaderShouldExist('Job details'); - detailsPage.titleShouldContain(`${CRONJOB_NAME}`); - }); - - it('verify the number of Jobs in CronJob > Jobs tab list page', () => { - cy.visit(`/k8s/ns/${testName}/cronjobs`); - listPage.dvRows.shouldBeLoaded(); - cy.visit(`/k8s/ns/${testName}/cronjobs/${CRONJOB_NAME}/jobs`); - listPage.dvRows.countShouldBe(2); - }); - - it('verify the number of events in CronJob > Events tab list page', () => { - cy.visit(`/k8s/ns/${testName}/cronjobs/${CRONJOB_NAME}/events`); - cy.byTestID('event-totals').should('have.text', 'Showing 2 events'); - }); -});