diff --git a/frontend/e2e/pages/web-terminal-config-page.ts b/frontend/e2e/pages/web-terminal-config-page.ts index 1df2ff15c49..c47289adbed 100644 --- a/frontend/e2e/pages/web-terminal-config-page.ts +++ b/frontend/e2e/pages/web-terminal-config-page.ts @@ -12,7 +12,21 @@ export class WebTerminalConfigPage extends BasePage { private readonly saveButton = this.page.getByTestId('save-button'); private readonly successAlert = this.page.getByTestId('success-alert'); - async navigateToWebTerminalConfig(): Promise { + async navigateToWebTerminalConfig(maxRetries = 2): Promise { + for (let attempt = 0; attempt <= maxRetries; attempt++) { + try { + await this.openCustomizeDrawer(); + await this.clickWebTerminalTab(); + return; + } catch (error) { + if (attempt >= maxRetries) throw error; + // eslint-disable-next-line playwright/no-wait-for-timeout -- ConsolePlugin may not be registered yet, reload and retry + await this.page.waitForTimeout(5_000); + } + } + } + + private async openCustomizeDrawer(): Promise { await this.goTo('/k8s/cluster/operator.openshift.io~v1~Console/cluster'); await this.waitForLoadingComplete(10_000); const customizeButton = this.page.getByRole('button', { name: 'Customize' }); @@ -30,12 +44,11 @@ export class WebTerminalConfigPage extends BasePage { await this.robustClick(customizeAction); } await this.waitForLoadingComplete(10_000); - await this.clickWebTerminalTab(); } async clickWebTerminalTab(): Promise { const tab = this.page.getByRole('tab', { name: 'Web Terminal' }); - await this.robustClick(tab, { timeout: 60_000 }); + await this.robustClick(tab, { timeout: 20_000 }); await this.waitForLoadingComplete(5_000); } diff --git a/frontend/e2e/tests/webterminal/utils/web-terminal-operator.ts b/frontend/e2e/setup/webterminal.setup.ts similarity index 52% rename from frontend/e2e/tests/webterminal/utils/web-terminal-operator.ts rename to frontend/e2e/setup/webterminal.setup.ts index 7c655cdefc1..b3bfe6f8bb4 100644 --- a/frontend/e2e/tests/webterminal/utils/web-terminal-operator.ts +++ b/frontend/e2e/setup/webterminal.setup.ts @@ -1,4 +1,7 @@ -import KubernetesClient from '../../../clients/kubernetes-client'; +import * as k8s from '@kubernetes/client-node'; +import { test as setup } from '@playwright/test'; + +import KubernetesClient from '../clients/kubernetes-client'; const SUBSCRIPTION_GROUP = 'operators.coreos.com'; const SUBSCRIPTION_VERSION = 'v1alpha1'; @@ -21,9 +24,18 @@ const webTerminalSubscription = { }, }; -export async function ensureWebTerminalOperatorInstalled( - k8sClient: KubernetesClient, -): Promise { +setup('install Web Terminal operator if not present', async () => { + setup.setTimeout(600_000); + + const k8sClient = new KubernetesClient( + { + clusterUrl: process.env.CLUSTER_URL || '', + username: process.env.OPENSHIFT_USERNAME || 'kubeadmin', + password: process.env.BRIDGE_KUBEADMIN_PASSWORD || '', + }, + process.env.KUBECONFIG, + ); + try { await k8sClient.getCustomResource( SUBSCRIPTION_GROUP, @@ -32,22 +44,28 @@ export async function ensureWebTerminalOperatorInstalled( SUBSCRIPTION_PLURAL, 'web-terminal', ); - return; - } catch { - // Subscription doesn't exist — create it + // Subscription exists — fall through to readiness poll + } catch (err) { + if (!(err instanceof k8s.ApiException && err.code === 404)) { + throw err; + } + try { + await k8sClient.createCustomResource( + SUBSCRIPTION_GROUP, + SUBSCRIPTION_VERSION, + OPERATOR_NAMESPACE, + SUBSCRIPTION_PLURAL, + webTerminalSubscription, + ); + } catch (err) { + if (!(err instanceof k8s.ApiException && err.code === 409)) { + throw err; + } + } } - await k8sClient.createCustomResource( - SUBSCRIPTION_GROUP, - SUBSCRIPTION_VERSION, - OPERATOR_NAMESPACE, - SUBSCRIPTION_PLURAL, - webTerminalSubscription, - ); - - const maxWaitMs = 300_000; const pollIntervalMs = 10_000; - const deadline = Date.now() + maxWaitMs; + const deadline = Date.now() + 300_000; while (Date.now() < deadline) { const pods = await k8sClient.getPods(OPERATOR_NAMESPACE); @@ -64,41 +82,4 @@ export async function ensureWebTerminalOperatorInstalled( } throw new Error('Web Terminal operator controller pod not ready within 5 minutes'); -} - -const CSV_PLURAL = 'clusterserviceversions'; - -export async function uninstallWebTerminalOperator( - k8sClient: KubernetesClient, -): Promise { - try { - await k8sClient.deleteCustomResource( - SUBSCRIPTION_GROUP, - SUBSCRIPTION_VERSION, - OPERATOR_NAMESPACE, - SUBSCRIPTION_PLURAL, - 'web-terminal', - ); - - const csvs = await k8sClient.listCustomResources( - SUBSCRIPTION_GROUP, - SUBSCRIPTION_VERSION, - OPERATOR_NAMESPACE, - CSV_PLURAL, - ); - const webTerminalCsv = csvs.find( - (csv) => (csv as any).metadata?.name?.startsWith('web-terminal'), - ); - if (webTerminalCsv) { - await k8sClient.deleteCustomResource( - SUBSCRIPTION_GROUP, - SUBSCRIPTION_VERSION, - OPERATOR_NAMESPACE, - CSV_PLURAL, - (webTerminalCsv as any).metadata.name, - ); - } - } catch (err) { - console.warn('[Cleanup] Failed to uninstall Web Terminal operator:', err); - } -} +}); diff --git a/frontend/e2e/tests/webterminal/developer/web-terminal-basic.spec.ts b/frontend/e2e/tests/webterminal/developer/web-terminal-basic.spec.ts index d6dabcbfdfd..a77d8f68b6f 100644 --- a/frontend/e2e/tests/webterminal/developer/web-terminal-basic.spec.ts +++ b/frontend/e2e/tests/webterminal/developer/web-terminal-basic.spec.ts @@ -1,28 +1,16 @@ import { test, expect } from '../../../fixtures'; import { WebTerminalPage } from '../../../pages/web-terminal-page'; -import { - ensureWebTerminalOperatorInstalled, - uninstallWebTerminalOperator, -} from '../utils/web-terminal-operator'; const INACTIVITY_MESSAGE = 'The terminal connection has closed due to inactivity.'; const TERMINAL_IDLING_TIMEOUT = Number(process.env.TERMINAL_IDLING_TIMEOUT) || 200_000; const TEST_NAMESPACE = 'aut-terminal-basic'; test.describe('Web Terminal basic user', () => { - test.beforeAll(async ({ k8sClient }) => { - await ensureWebTerminalOperatorInstalled(k8sClient); - }); - test.beforeEach(async ({ k8sClient, cleanup }) => { await k8sClient.createNamespace(TEST_NAMESPACE); cleanup.trackNamespace(TEST_NAMESPACE); }); - test.afterAll(async ({ k8sClient }) => { - await uninstallWebTerminalOperator(k8sClient); - }); - test('open terminal with advanced timeout', async ({ page }) => { const webTerminal = new WebTerminalPage(page); diff --git a/frontend/e2e/tests/webterminal/developer/web-terminal-devuser.spec.ts b/frontend/e2e/tests/webterminal/developer/web-terminal-devuser.spec.ts index e816ae011ad..8fdd6fd23e6 100644 --- a/frontend/e2e/tests/webterminal/developer/web-terminal-devuser.spec.ts +++ b/frontend/e2e/tests/webterminal/developer/web-terminal-devuser.spec.ts @@ -3,10 +3,6 @@ import type { Page } from '@playwright/test'; import { test, expect } from '../../../fixtures'; import type KubernetesClient from '../../../clients/kubernetes-client'; import { WebTerminalPage } from '../../../pages/web-terminal-page'; -import { - ensureWebTerminalOperatorInstalled, - uninstallWebTerminalOperator, -} from '../utils/web-terminal-operator'; const DEVWORKSPACE_GROUP = 'workspace.devfile.io'; const DEVWORKSPACE_VERSION = 'v1alpha2'; @@ -38,19 +34,11 @@ async function verifyDevWorkspaceRunning( } test.describe('Web Terminal for Developer user', () => { - test.beforeAll(async ({ k8sClient }) => { - await ensureWebTerminalOperatorInstalled(k8sClient); - }); - test.beforeEach(async ({ k8sClient, cleanup }) => { await k8sClient.createNamespace(EXISTING_PROJECT); cleanup.trackNamespace(EXISTING_PROJECT); }); - test.afterAll(async ({ k8sClient }) => { - await uninstallWebTerminalOperator(k8sClient); - }); - test( 'create new project and use Web Terminal', async ({ page, k8sClient, cleanup }) => { diff --git a/frontend/e2e/tests/webterminal/web-terminal-admin.spec.ts b/frontend/e2e/tests/webterminal/web-terminal-admin.spec.ts index afce4f0cb64..c1303baf6ac 100644 --- a/frontend/e2e/tests/webterminal/web-terminal-admin.spec.ts +++ b/frontend/e2e/tests/webterminal/web-terminal-admin.spec.ts @@ -4,10 +4,6 @@ import { test, expect } from '../../fixtures'; import type KubernetesClient from '../../clients/kubernetes-client'; import { getEditorContent, warmupSPA } from '../../pages/base-page'; import { WebTerminalPage } from '../../pages/web-terminal-page'; -import { - ensureWebTerminalOperatorInstalled, - uninstallWebTerminalOperator, -} from './utils/web-terminal-operator'; const DEVWORKSPACE_GROUP = 'workspace.devfile.io'; const DEVWORKSPACE_VERSION = 'v1alpha2'; @@ -43,10 +39,6 @@ async function verifyDevWorkspaceUid( } test.describe('Web Terminal for Admin user', () => { - test.beforeAll(async ({ k8sClient }) => { - await ensureWebTerminalOperatorInstalled(k8sClient); - }); - test.beforeEach(async ({ k8sClient }) => { const devWorkspaces = await k8sClient.listCustomResources( DEVWORKSPACE_GROUP, @@ -81,10 +73,6 @@ test.describe('Web Terminal for Admin user', () => { } }); - test.afterAll(async ({ k8sClient }) => { - await uninstallWebTerminalOperator(k8sClient); - }); - test( 'open and close multiple terminal tabs', async ({ page }) => { diff --git a/frontend/e2e/tests/webterminal/web-terminal-config.spec.ts b/frontend/e2e/tests/webterminal/web-terminal-config.spec.ts index dd24603d1fa..bb13488254e 100644 --- a/frontend/e2e/tests/webterminal/web-terminal-config.spec.ts +++ b/frontend/e2e/tests/webterminal/web-terminal-config.spec.ts @@ -1,10 +1,6 @@ import { test, expect } from '../../fixtures'; import { warmupSPA } from '../../pages/base-page'; import { WebTerminalConfigPage } from '../../pages/web-terminal-config-page'; -import { - ensureWebTerminalOperatorInstalled, - uninstallWebTerminalOperator, -} from './utils/web-terminal-operator'; const TEST_IMAGE_805 = 'registry.redhat.io/web-terminal/web-terminal-tooling-rhel8@sha256:9ff1f660fccd3a2f0515ba997d48ad87d2ba47c40b67062c74580bbea9446805'; @@ -12,18 +8,10 @@ const TEST_IMAGE_806 = 'registry.redhat.io/web-terminal/web-terminal-tooling-rhel8@sha256:9ff1f660fccd3a2f0515ba997d48ad87d2ba47c40b67062c74580bbea9446806'; test.describe('Customization of web terminal options', () => { - test.beforeAll(async ({ k8sClient }) => { - await ensureWebTerminalOperatorInstalled(k8sClient); - }); - test.beforeEach(async ({ page }) => { await warmupSPA(page); }); - test.afterAll(async ({ k8sClient }) => { - await uninstallWebTerminalOperator(k8sClient); - }); - test( 'navigate to Web Terminal Configuration page', async ({ page }) => { diff --git a/frontend/playwright.config.ts b/frontend/playwright.config.ts index 09a2ed6a63b..ee856d5881f 100644 --- a/frontend/playwright.config.ts +++ b/frontend/playwright.config.ts @@ -135,11 +135,23 @@ export default defineConfig({ dependencies: ['cluster-setup'], }, + { + name: 'webterminal-setup', + testDir: path.resolve(__dirname, 'e2e', 'setup'), + testMatch: 'webterminal.setup.ts', + dependencies: ['cluster-setup'], + }, + ...packages.map((pkg) => ({ name: pkg, testDir: path.resolve(__dirname, 'e2e', 'tests', pkg), testIgnore: '**/developer/**', - dependencies: pkg === 'knative' ? ['admin-auth', 'knative-setup'] : ['admin-auth'], + dependencies: + pkg === 'knative' + ? ['admin-auth', 'knative-setup'] + : pkg === 'webterminal' + ? ['admin-auth', 'webterminal-setup'] + : ['admin-auth'], use: { ...chrome, storageState: adminStorageState, @@ -149,7 +161,8 @@ export default defineConfig({ ? devPackages.map((pkg) => ({ name: `${pkg}-developer`, testDir: path.resolve(__dirname, 'e2e', 'tests', pkg, 'developer'), - dependencies: ['developer-auth'], + dependencies: + pkg === 'webterminal' ? ['developer-auth', 'webterminal-setup'] : ['developer-auth'], use: { ...chrome, storageState: developerStorageState,