diff --git a/frontend/e2e/pages/base-page.ts b/frontend/e2e/pages/base-page.ts index 2d5740d3623..c87294812cc 100644 --- a/frontend/e2e/pages/base-page.ts +++ b/frontend/e2e/pages/base-page.ts @@ -21,9 +21,19 @@ export async function setEditorContent(page: Page, content: string): Promise (window as any).monaco?.editor?.getModels()?.[0], { timeout: 10_000, }); - await page.evaluate((text) => { - (window as any).monaco.editor.getModels()[0].setValue(text); - }, content); + // Monaco can swap its model during initialisation, silently dropping an early + // setValue and leaving the editor empty — which then submits an empty + // definition. Set and verify with retries so the content is guaranteed to + // stick before the caller proceeds. + await expect(async () => { + await page.evaluate((text) => { + (window as any).monaco.editor.getModels()[0].setValue(text); + }, content); + const value = await page.evaluate(() => + (window as any).monaco.editor.getModels()[0].getValue(), + ); + expect(value.trim()).toBe(content.trim()); + }).toPass({ timeout: 15_000, intervals: [300, 700, 1500] }); } export async function warmupSPA(page: Page): Promise { diff --git a/frontend/e2e/tests/console/app/debug-pod.spec.ts b/frontend/e2e/tests/console/app/debug-pod.spec.ts index 28fa1794bd7..9dc6a45eea1 100644 --- a/frontend/e2e/tests/console/app/debug-pod.spec.ts +++ b/frontend/e2e/tests/console/app/debug-pod.spec.ts @@ -83,7 +83,10 @@ test.describe('Debug pod', () => { page, k8sClient, }) => { - test.setTimeout(300_000); + // This test is image-pull and reconcile heavy: it waits for a pod to + // CrashLoopBackOff and then spins up three separate debug pods. On a cold or + // slow CI cluster the default 300s is not enough, so allow more headroom. + test.setTimeout(480_000); const detailsPage = new DetailsPage(page); const listPage = new ListPage(page); diff --git a/frontend/e2e/tests/console/nodes/node-groups-filter.spec.ts b/frontend/e2e/tests/console/nodes/node-groups-filter.spec.ts index 1b13b090bf2..320733e5bf8 100644 --- a/frontend/e2e/tests/console/nodes/node-groups-filter.spec.ts +++ b/frontend/e2e/tests/console/nodes/node-groups-filter.spec.ts @@ -1,6 +1,7 @@ import type { Page } from '@playwright/test'; import { test, expect } from '../../../fixtures'; +import { warmupSPA } from '../../../pages/base-page'; /** * E2E tests for Node Groups filtering functionality @@ -8,10 +9,14 @@ import { test, expect } from '../../../fixtures'; */ async function gotoNodesPage(page: Page): Promise { + // Warm the SPA shell first (self-heals a lost session and waits out plugin + // init) so navigating straight to this data-heavy page doesn't race a cold + // bootstrap — the direct goto was timing out on CI before the shell rendered. + await warmupSPA(page); await page.goto('/k8s/cluster/nodes'); await expect( page.getByTestId('data-view-table').or(page.getByTestId('page-heading')).first(), - ).toBeVisible(); + ).toBeVisible({ timeout: 30_000 }); } function groupsFilter(page: Page) { @@ -240,8 +245,17 @@ test.describe('Edit Groups Button', () => { await skipIfEditGroupsButtonHidden(page); const editButton = page.getByRole('button', { name: /edit groups/i }); - if (!(await editButton.isDisabled())) { - test.skip(true, 'Edit groups button is not disabled'); + // The button starts disabled while the permission (SelfSubjectAccessReview) + // check is in flight, then enables for users who can edit. Poll until that + // settles: if it becomes enabled the user has permission and this test does + // not apply (e.g. cluster-admin CI runs); only a button that stays disabled + // indicates a genuine lack of permission. + const deadline = Date.now() + 15_000; + while ((await editButton.isDisabled()) && Date.now() < deadline) { + await new Promise((r) => setTimeout(r, 500)); + } + if (await editButton.isEnabled()) { + test.skip(true, 'User has permission to edit groups; tooltip not applicable'); return; } @@ -250,7 +264,7 @@ test.describe('Edit Groups Button', () => { const tooltip = page.locator('[role="tooltip"]').filter({ hasText: /permission.*edit groups.*administrator/i, }); - await expect(tooltip).toBeVisible(); + await expect(tooltip).toBeVisible({ timeout: 10_000 }); }); test('should open Groups Editor modal when clicked', async ({ page }) => { diff --git a/frontend/e2e/tests/dev-console/create-from-yaml.spec.ts b/frontend/e2e/tests/dev-console/create-from-yaml.spec.ts index 30359f44c16..9b52d164a2f 100644 --- a/frontend/e2e/tests/dev-console/create-from-yaml.spec.ts +++ b/frontend/e2e/tests/dev-console/create-from-yaml.spec.ts @@ -1,5 +1,5 @@ import { test, expect } from '../../fixtures'; -import { warmupSPA } from '../../pages/base-page'; +import { setEditorContent, warmupSPA } from '../../pages/base-page'; import { AddPage, ImportYAMLPage } from '../../pages/dev-console/add-page'; import { TopologyPage } from '../../pages/topology-page'; @@ -68,14 +68,13 @@ test.describe( }); await test.step('Enter YAML content and create', async () => { - await page.waitForFunction( - () => !!(window as any).monaco?.editor?.getModels()?.[0], - { timeout: 30_000 }, - ); - await page.evaluate((yaml) => { - (window as any).monaco.editor.getModels()[0].setValue(yaml); - }, GIT_DC_YAML); + await setEditorContent(page, GIT_DC_YAML); await yamlPage.getSubmitButton().click(); + // The editor issues the create POST asynchronously and redirects to the + // new resource on success. Wait for that redirect before navigating + // away — otherwise the next navigation aborts the in-flight create + // request ("Failed to fetch") and the workload is never created. + await page.waitForURL(/\/deploymentconfigs\/shell-app(\/|$|\?)/, { timeout: 60_000 }); }); await test.step('Verify workload in topology', async () => { diff --git a/frontend/e2e/tests/dev-console/import-from-devfile.spec.ts b/frontend/e2e/tests/dev-console/import-from-devfile.spec.ts index b6ac57fccd2..5234252a108 100644 --- a/frontend/e2e/tests/dev-console/import-from-devfile.spec.ts +++ b/frontend/e2e/tests/dev-console/import-from-devfile.spec.ts @@ -53,7 +53,7 @@ test.describe( await test.step('Verify workload appears in topology', async () => { await topologyPage.waitForWorkload('node-example'); await topologyPage.clickWorkload('node-example'); - await expect(topologyPage.getSidebarTitle()).toContainText('node-example', { timeout: 15_000 }); + await expect(topologyPage.getSidebarTitle()).toContainText('node-example', { timeout: 30_000 }); }); }); }, diff --git a/frontend/e2e/tests/topology/topology-ci.spec.ts b/frontend/e2e/tests/topology/topology-ci.spec.ts index 98aed438d60..8c2258f41e9 100644 --- a/frontend/e2e/tests/topology/topology-ci.spec.ts +++ b/frontend/e2e/tests/topology/topology-ci.spec.ts @@ -153,18 +153,17 @@ test.describe('Perform actions on topology', { tag: ['@smoke'] }, () => { test('Edit workload application groupings: T-09-TC01', async ({ page }) => { test.setTimeout(300_000); const topology = new TopologyPage(page); + const sidebar = new TopologySidebarPage(page); await createWorkload(page, 'dotnet-edit-test'); - - await test.step('Prepare for right-click: clear search and close any sidebar', async () => { - // Clear the search field to avoid interference - await topology.search(''); - // Close sidebar if open - await topology.closeSidebarIfOpen(); - }); - - await test.step('Right-click workload and select Edit', async () => { - await topology.rightClickOnNode('dotnet-edit-test'); - await topology.selectContextMenuAction('Edit dotnet-edit-test'); + + await test.step('Open workload sidebar and select Edit', async () => { + // Use the sidebar Actions menu rather than the topology right-click context + // menu: PF Topology nodes are SVG groups with no usable bounding box, so a + // real right-click cannot be dispatched reliably in headless runs. This is + // the same interaction path deleteWorkload uses. + await topology.clickOnNode('dotnet-edit-test'); + await sidebar.verify(); + await sidebar.selectAction('Edit dotnet-edit-test'); }); await test.step('Change application groupings to "app"', async () => {