From df468661cfcfe76c50df30e530f2e1ab9c92a308 Mon Sep 17 00:00:00 2001 From: Debsmita Santra Date: Tue, 28 Jul 2026 11:42:43 +0530 Subject: [PATCH 1/5] include homepage-app-next in e2e tests rh-pre-commit.version: 2.3.2 rh-pre-commit.check-secrets: ENABLED --- workspaces/homepage/e2e-tests/playwright.config.ts | 11 +++++++++++ .../homepage/e2e-tests/tests/specs/homepage.spec.ts | 5 +++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/workspaces/homepage/e2e-tests/playwright.config.ts b/workspaces/homepage/e2e-tests/playwright.config.ts index d823241d94..46fae6a266 100644 --- a/workspaces/homepage/e2e-tests/playwright.config.ts +++ b/workspaces/homepage/e2e-tests/playwright.config.ts @@ -1,9 +1,20 @@ import { defineConfig } from "@red-hat-developer-hub/e2e-test-utils/playwright-config"; +/** + * Homepage plugin e2e test configuration. + * + * Projects: + * - homepage — legacy app shell (default RHIDP merge layers). + * - homepage-app-next — namespace ends with -app-next, so e2e-test-utils merges + * NFS (app-next) secrets and default app-auth / app-integrations automatically. + */ export default defineConfig({ projects: [ { name: "homepage", }, + { + name: "homepage-app-next", + }, ], }); diff --git a/workspaces/homepage/e2e-tests/tests/specs/homepage.spec.ts b/workspaces/homepage/e2e-tests/tests/specs/homepage.spec.ts index be17d7eee8..cf7f97029a 100644 --- a/workspaces/homepage/e2e-tests/tests/specs/homepage.spec.ts +++ b/workspaces/homepage/e2e-tests/tests/specs/homepage.spec.ts @@ -29,13 +29,14 @@ test.describe.serial("Dynamic home page customization", () => { test.beforeAll(async ({ browser, rhdh }) => { test.setTimeout(10 * 60 * 1000); - await test.runOnce("homepage-setup", async () => { + const namespace = rhdh.deploymentConfig.namespace; + // Key must be unique per Playwright project — each project deploys its own namespace. + await test.runOnce(`homepage-setup-${namespace}`, async () => { await setupKeycloakGroups(); const rbacConfigmapPath = WorkspacePaths.resolve( "tests/config/rbac-configmap.yaml", ); - const namespace = rhdh.deploymentConfig.namespace; await $`oc apply -f ${rbacConfigmapPath} -n ${namespace}`; await rhdh.configure({ From 56ebc5246f6ea65461baf382c72dd39e4fcdfe5d Mon Sep 17 00:00:00 2001 From: Debsmita Santra Date: Tue, 28 Jul 2026 16:07:12 +0530 Subject: [PATCH 2/5] add nfs config for homepage rh-pre-commit.version: 2.3.2 rh-pre-commit.check-secrets: ENABLED --- .../extensions/plugins/home-page.yaml | 15 +++++++ .../tests/config/app-config-rhdh.yaml | 42 +++++++++++++++++++ ...veloper-hub-backstage-plugin-homepage.yaml | 36 ++++++++++++++++ 3 files changed, 93 insertions(+) diff --git a/catalog-entities/extensions/plugins/home-page.yaml b/catalog-entities/extensions/plugins/home-page.yaml index 7960526a22..99dc46b7df 100644 --- a/catalog-entities/extensions/plugins/home-page.yaml +++ b/catalog-entities/extensions/plugins/home-page.yaml @@ -73,7 +73,22 @@ spec: Add the following configuration to your dynamic plugin yaml. Use the OCI image tags from the Homepage and Homepage Backend package entries in the Extensions catalog for your RHDH version if they differ from the examples below. + For the **new frontend system (app-next / NFS)**, also set `app.extensions` (in app-config or pluginConfig) so Home is registered — legacy `dynamicRoutes` / `mountPoints` alone are not enough. The homepage alpha default export embeds `@backstage/plugin-home` (`page:home`); a separate home OCI is not required: + ```yaml + app: + extensions: + - page:home: + config: + path: / + - api:home/visits: true + - app-root-element:home/visit-listener: true + - home-page-layout:home/dynamic-homepage-layout: + config: + customizable: true + ``` + + Legacy Scalprum example: includes: - dynamic-plugins.default.yaml plugins: diff --git a/workspaces/homepage/e2e-tests/tests/config/app-config-rhdh.yaml b/workspaces/homepage/e2e-tests/tests/config/app-config-rhdh.yaml index 22d4d40638..e77ff7e5ca 100644 --- a/workspaces/homepage/e2e-tests/tests/config/app-config-rhdh.yaml +++ b/workspaces/homepage/e2e-tests/tests/config/app-config-rhdh.yaml @@ -1,3 +1,45 @@ +# Shared by legacy and app-next projects. +# NFS (app-next) requires app.extensions to enable/configure page:home (from homepage +# alpha homePagePlugin). Legacy ignores these. +app: + extensions: + - page:home: + config: + path: / + - api:home/visits: true + - app-root-element:home/visit-listener: true + - home-page-layout:home/dynamic-homepage-layout: + config: + customizable: true + # widgetLayout: + # RhdhTemplateSection: + # priority: 300 + # breakpoints: + # xl: { w: 12, h: 5 } + # lg: { w: 12, h: 5 } + # md: { w: 12, h: 5 } + # sm: { w: 12, h: 5 } + # xs: { w: 12, h: 7.5 } + # xxs: { w: 12, h: 13.5 } + # RhdhEntitySection: + # priority: 200 + # breakpoints: + # xl: { w: 12, h: 7 } + # lg: { w: 12, h: 7 } + # md: { w: 12, h: 8 } + # sm: { w: 12, h: 9 } + # xs: { w: 12, h: 11 } + # xxs: { w: 12, h: 15 } + # RhdhOnboardingSection: + # priority: 100 + # breakpoints: + # xl: { w: 12, h: 6 } + # lg: { w: 12, h: 6 } + # md: { w: 12, h: 7 } + # sm: { w: 12, h: 8 } + # xs: { w: 12, h: 9 } + # xxs: { w: 12, h: 14 } + permission: enabled: true rbac: diff --git a/workspaces/homepage/metadata/red-hat-developer-hub-backstage-plugin-homepage.yaml b/workspaces/homepage/metadata/red-hat-developer-hub-backstage-plugin-homepage.yaml index 7a898fb237..999a594a09 100644 --- a/workspaces/homepage/metadata/red-hat-developer-hub-backstage-plugin-homepage.yaml +++ b/workspaces/homepage/metadata/red-hat-developer-hub-backstage-plugin-homepage.yaml @@ -92,3 +92,39 @@ spec: sm: { w: 12, h: 3 } xs: { w: 12, h: 3 } xxs: { w: 12, h: 3 } + - title: New frontend system (app-next / NFS) + content: + app: + extensions: + - page:home: + config: + path: / + - api:home/visits: true + - app-root-element:home/visit-listener: true + - home-page-layout:home/dynamic-homepage-layout: + config: + customizable: true + widgetLayout: + RhdhTemplateSection: + priority: 300 + breakpoints: + xl: { w: 12, h: 5 } + lg: { w: 12, h: 5 } + RhdhEntitySection: + priority: 200 + breakpoints: + xl: { w: 12, h: 7 } + lg: { w: 12, h: 7 } + RhdhOnboardingSection: + priority: 100 + breakpoints: + xl: { w: 12, h: 6 } + lg: { w: 12, h: 6 } + homepage: + defaultWidgets: + - id: onboarding + ref: rhdh-onboarding-section + - id: entity-list + ref: rhdh-entity-section + - id: template-list + ref: rhdh-template-section From 251abf91e54977146617ca9f5101593767812960 Mon Sep 17 00:00:00 2001 From: Debsmita Santra Date: Tue, 11 Aug 2026 20:57:18 +0530 Subject: [PATCH 3/5] update yaml rh-pre-commit.version: 2.3.2 rh-pre-commit.check-secrets: ENABLED --- .../extensions/plugins/home-page.yaml | 11 +- .../tests/config/app-config-rhdh.yaml | 32 +--- .../config/dynamic-plugins-app-next.yaml | 82 +++++++++++ .../tests/config/dynamic-plugins.yaml | 26 +++- .../e2e-tests/tests/specs/homepage.spec.ts | 116 +++++++++++++-- .../e2e-tests/tests/utils/dynamic-homepage.ts | 137 ++++++++++++++++-- ...veloper-hub-backstage-plugin-homepage.yaml | 18 ++- 7 files changed, 360 insertions(+), 62 deletions(-) create mode 100644 workspaces/homepage/e2e-tests/tests/config/dynamic-plugins-app-next.yaml diff --git a/catalog-entities/extensions/plugins/home-page.yaml b/catalog-entities/extensions/plugins/home-page.yaml index 99dc46b7df..a0ba5037ac 100644 --- a/catalog-entities/extensions/plugins/home-page.yaml +++ b/catalog-entities/extensions/plugins/home-page.yaml @@ -73,7 +73,16 @@ spec: Add the following configuration to your dynamic plugin yaml. Use the OCI image tags from the Homepage and Homepage Backend package entries in the Extensions catalog for your RHDH version if they differ from the examples below. - For the **new frontend system (app-next / NFS)**, also set `app.extensions` (in app-config or pluginConfig) so Home is registered — legacy `dynamicRoutes` / `mountPoints` alone are not enough. The homepage alpha default export embeds `@backstage/plugin-home` (`page:home`); a separate home OCI is not required: + For the **new frontend system (app-next / NFS)**, enable the upstream Home plugin OCI and set `app.extensions` so Home is registered — legacy `dynamicRoutes` / `mountPoints` alone are not enough. The homepage alpha module extends `pluginId: home` (`page:home`, visits, layout blueprints) and requires `@backstage/plugin-home` to be loaded: + + ```yaml + plugins: + - package: oci://ghcr.io/redhat-developer/rhdh-plugin-export-overlays/backstage-plugin-home:bs_1.52.0__0.9.7 + disabled: false + - package: oci://ghcr.io/redhat-developer/rhdh-plugin-export-overlays/red-hat-developer-hub-backstage-plugin-homepage:bs_1.52.0__1.17.1 + disabled: false + # …homepage-backend as needed + ``` ```yaml app: diff --git a/workspaces/homepage/e2e-tests/tests/config/app-config-rhdh.yaml b/workspaces/homepage/e2e-tests/tests/config/app-config-rhdh.yaml index e77ff7e5ca..b1797a330e 100644 --- a/workspaces/homepage/e2e-tests/tests/config/app-config-rhdh.yaml +++ b/workspaces/homepage/e2e-tests/tests/config/app-config-rhdh.yaml @@ -1,6 +1,6 @@ # Shared by legacy and app-next projects. -# NFS (app-next) requires app.extensions to enable/configure page:home (from homepage -# alpha homePagePlugin). Legacy ignores these. +# NFS (app-next) requires app.extensions so Home is registered, plus the upstream +# backstage-plugin-home OCI (see dynamic-plugins-app-next.yaml). Legacy ignores these. app: extensions: - page:home: @@ -11,34 +11,6 @@ app: - home-page-layout:home/dynamic-homepage-layout: config: customizable: true - # widgetLayout: - # RhdhTemplateSection: - # priority: 300 - # breakpoints: - # xl: { w: 12, h: 5 } - # lg: { w: 12, h: 5 } - # md: { w: 12, h: 5 } - # sm: { w: 12, h: 5 } - # xs: { w: 12, h: 7.5 } - # xxs: { w: 12, h: 13.5 } - # RhdhEntitySection: - # priority: 200 - # breakpoints: - # xl: { w: 12, h: 7 } - # lg: { w: 12, h: 7 } - # md: { w: 12, h: 8 } - # sm: { w: 12, h: 9 } - # xs: { w: 12, h: 11 } - # xxs: { w: 12, h: 15 } - # RhdhOnboardingSection: - # priority: 100 - # breakpoints: - # xl: { w: 12, h: 6 } - # lg: { w: 12, h: 6 } - # md: { w: 12, h: 7 } - # sm: { w: 12, h: 8 } - # xs: { w: 12, h: 9 } - # xxs: { w: 12, h: 14 } permission: enabled: true diff --git a/workspaces/homepage/e2e-tests/tests/config/dynamic-plugins-app-next.yaml b/workspaces/homepage/e2e-tests/tests/config/dynamic-plugins-app-next.yaml new file mode 100644 index 0000000000..8062a034e5 --- /dev/null +++ b/workspaces/homepage/e2e-tests/tests/config/dynamic-plugins-app-next.yaml @@ -0,0 +1,82 @@ +# NFS (app-next) dynamic plugins for homepage e2e. +# Upstream @backstage/plugin-home provides page:home / visits / layout blueprints; +# the RHDH homepage alpha module extends pluginId "home" and requires this OCI. +# +# homepage.defaultWidgets (homepage-backend) is not supported on NFS yet — keep the +# commented block below and enable it when support lands. NFS tests seed widgets via +# the UI until then. +plugins: + # Auth layer adds this with {{inherit}}; hub image has no base version, so install + # fails. Merge is by exact package string (no normalizeKey), so disable it explicitly + # and enable a pinned GHCR artifact below (workspaces/keycloak metadata). + - package: oci://registry.access.redhat.com/rhdh/backstage-community-plugin-catalog-backend-module-keycloak:{{inherit}} + disabled: true + - package: oci://ghcr.io/redhat-developer/rhdh-plugin-export-overlays/backstage-community-plugin-catalog-backend-module-keycloak:bs_1.49.4__3.19.2 + disabled: false + pluginConfig: + catalog: + providers: + keycloakOrg: + default: + baseUrl: ${KEYCLOAK_BASE_URL} + loginRealm: ${KEYCLOAK_LOGIN_REALM} + realm: ${KEYCLOAK_REALM} + clientId: ${KEYCLOAK_CLIENT_ID} + clientSecret: ${KEYCLOAK_CLIENT_SECRET} + schedule: + frequency: + minutes: 1 + initialDelay: + seconds: 15 + timeout: + minutes: 1 + # e2e-test-utils NFS layer still pins app-auth / app-integrations at bs_1.49.4. + # Merge is by exact package string, so disable those and enable current metadata tags. + - package: oci://ghcr.io/redhat-developer/rhdh-plugin-export-overlays/red-hat-developer-hub-backstage-plugin-app-auth:bs_1.49.4__0.0.1 + disabled: true + - package: oci://ghcr.io/redhat-developer/rhdh-plugin-export-overlays/red-hat-developer-hub-backstage-plugin-app-auth:bs_1.52.0__0.1.1 + disabled: false + - package: oci://ghcr.io/redhat-developer/rhdh-plugin-export-overlays/red-hat-developer-hub-backstage-plugin-app-integrations:bs_1.49.4__0.0.1 + disabled: true + - package: oci://ghcr.io/redhat-developer/rhdh-plugin-export-overlays/red-hat-developer-hub-backstage-plugin-app-integrations:bs_1.52.0__0.1.1 + disabled: false + # Catalog / DPDY enables app-defaults from quay midstream 2.0.0--0.0.2, which lacks + # the io.backstage.dynamic-packages annotation (InstallException). Disable it and use + # the community GHCR artifact from workspaces/app-defaults metadata instead. + - package: oci://quay.io/rhdh/red-hat-developer-hub-backstage-plugin-app-defaults:2.0.0--0.0.2 + disabled: true + # Version from workspaces/app-defaults/metadata/red-hat-developer-hub-backstage-plugin-app-defaults.yaml + - package: oci://ghcr.io/redhat-developer/rhdh-plugin-export-overlays/red-hat-developer-hub-backstage-plugin-app-defaults:bs_1.52.0__0.0.3 + disabled: false + # Version from workspaces/backstage export: backstage-plugin-home + - package: oci://ghcr.io/redhat-developer/rhdh-plugin-export-overlays/backstage-plugin-home:bs_1.52.0__0.9.7 + disabled: false + # Version from workspaces/homepage/metadata/red-hat-developer-hub-backstage-plugin-homepage.yaml + - package: oci://ghcr.io/redhat-developer/rhdh-plugin-export-overlays/red-hat-developer-hub-backstage-plugin-homepage:bs_1.52.0__1.17.1 + disabled: false + # Version from workspaces/homepage/metadata/red-hat-developer-hub-backstage-plugin-homepage-backend.yaml + - package: oci://ghcr.io/redhat-developer/rhdh-plugin-export-overlays/red-hat-developer-hub-backstage-plugin-homepage-backend:bs_1.52.0__0.4.0 + disabled: false + # homepage.defaultWidgets is not supported on NFS yet — uncomment when it is: + # pluginConfig: + # homepage: + # # NFS refs are HomePageWidgetBlueprint names (not legacy mount-point config.id). + # defaultWidgets: + # - id: onboarding + # ref: rhdh-onboarding-section + # - id: entity-list + # ref: rhdh-entity-section + # - if: + # groups: [group:default/admins] + # children: + # - id: template-list + # ref: rhdh-template-section + # - id: quickaccess-card + # ref: quick-access-card + # - if: + # groups: [group:default/developers] + # children: + # - id: recently-visited-card + # ref: recently-visited + # - id: top-visited-card + # ref: top-visited diff --git a/workspaces/homepage/e2e-tests/tests/config/dynamic-plugins.yaml b/workspaces/homepage/e2e-tests/tests/config/dynamic-plugins.yaml index bef0fd0fca..3cad7d1911 100644 --- a/workspaces/homepage/e2e-tests/tests/config/dynamic-plugins.yaml +++ b/workspaces/homepage/e2e-tests/tests/config/dynamic-plugins.yaml @@ -1,5 +1,29 @@ plugins: - - package: oci://ghcr.io/redhat-developer/rhdh-plugin-export-overlays/red-hat-developer-hub-backstage-plugin-homepage:bs_1.52.0__1.17.0 + # Auth layer adds this with {{inherit}}; hub image has no base version, so install + # fails. Merge is by exact package string (no normalizeKey), so disable it explicitly + # and enable a pinned GHCR artifact below (workspaces/keycloak metadata). + - package: oci://registry.access.redhat.com/rhdh/backstage-community-plugin-catalog-backend-module-keycloak:{{inherit}} + disabled: true + - package: oci://ghcr.io/redhat-developer/rhdh-plugin-export-overlays/backstage-community-plugin-catalog-backend-module-keycloak:bs_1.49.4__3.19.2 + disabled: false + pluginConfig: + catalog: + providers: + keycloakOrg: + default: + baseUrl: ${KEYCLOAK_BASE_URL} + loginRealm: ${KEYCLOAK_LOGIN_REALM} + realm: ${KEYCLOAK_REALM} + clientId: ${KEYCLOAK_CLIENT_ID} + clientSecret: ${KEYCLOAK_CLIENT_SECRET} + schedule: + frequency: + minutes: 1 + initialDelay: + seconds: 15 + timeout: + minutes: 1 + - package: oci://ghcr.io/redhat-developer/rhdh-plugin-export-overlays/red-hat-developer-hub-backstage-plugin-homepage:bs_1.52.0__1.17.1 enabled: true pluginConfig: dynamicPlugins: diff --git a/workspaces/homepage/e2e-tests/tests/specs/homepage.spec.ts b/workspaces/homepage/e2e-tests/tests/specs/homepage.spec.ts index cf7f97029a..6f66fdbec4 100644 --- a/workspaces/homepage/e2e-tests/tests/specs/homepage.spec.ts +++ b/workspaces/homepage/e2e-tests/tests/specs/homepage.spec.ts @@ -1,15 +1,13 @@ import { test, expect } from "@red-hat-developer-hub/e2e-test-utils/test"; -import { - LoginHelper, - UIhelper, -} from "@red-hat-developer-hub/e2e-test-utils/helpers"; +import { UIhelper } from "@red-hat-developer-hub/e2e-test-utils/helpers"; import { $, WorkspacePaths } from "@red-hat-developer-hub/e2e-test-utils/utils"; import type { BrowserContext, Page } from "@playwright/test"; import { DynamicHomePagePo, - AVAILABLE_WIDGETS, DEFAULT_WIDGETS, HOMEPAGE_ADMIN, + isHomepageAppNext, + loginAsKeycloakUser, setupKeycloakGroups, } from "../utils/dynamic-homepage"; @@ -17,6 +15,14 @@ const HOMEPAGE_WRAPPER_DIST_NAMES: string[] = [ "red-hat-developer-hub-backstage-plugin-homepage", ]; +function isNightlyMode(): boolean { + return ( + process.env.E2E_NIGHTLY_MODE === "true" || + process.env.E2E_NIGHTLY_MODE === "1" || + (process.env.JOB_NAME?.includes("periodic-") ?? false) + ); +} + /* eslint-disable playwright/expect-expect -- assertions in DynamicHomePagePo */ test.describe.serial("Dynamic home page customization", () => { let context: BrowserContext | undefined; @@ -25,15 +31,30 @@ test.describe.serial("Dynamic home page customization", () => { let home: DynamicHomePagePo; let baseURL: string; let test1Count: number; + let isAppNext: boolean; test.beforeAll(async ({ browser, rhdh }) => { test.setTimeout(10 * 60 * 1000); const namespace = rhdh.deploymentConfig.namespace; - // Key must be unique per Playwright project — each project deploys its own namespace. - await test.runOnce(`homepage-setup-${namespace}`, async () => { + isAppNext = isHomepageAppNext(namespace); + + test.skip( + isAppNext && isNightlyMode(), + "homepage-app-next not ready for nightly", + ); + + // Keycloak users are cluster-scoped — create once so parallel legacy/app-next + // projects do not race on delete/create of the same users. + await test.runOnce("homepage-keycloak-groups", async () => { await setupKeycloakGroups(); + }); + // Deploy key must be unique per Playwright project/namespace. + await test.runOnce(`homepage-deploy-${namespace}`, async () => { + if (process.env.SKIP_RHDH_DEPLOY === "true") { + return; + } const rbacConfigmapPath = WorkspacePaths.resolve( "tests/config/rbac-configmap.yaml", ); @@ -42,14 +63,23 @@ test.describe.serial("Dynamic home page customization", () => { await rhdh.configure({ auth: "keycloak", disablePlugins: HOMEPAGE_WRAPPER_DIST_NAMES, + dynamicPlugins: isAppNext + ? WorkspacePaths.resolve("tests/config/dynamic-plugins-app-next.yaml") + : WorkspacePaths.resolve("tests/config/dynamic-plugins.yaml"), }); await rhdh.deploy(); }); + + // Keycloak users are cluster-scoped — create once so parallel legacy/app-next + // projects do not race on delete/create of the same users. + await test.runOnce("homepage-keycloak-groups", async () => { + await setupKeycloakGroups(); + }); baseURL = rhdh.rhdhUrl; context = await browser.newContext({ baseURL }); page = await context.newPage(); uiHelper = new UIhelper(page); - home = new DynamicHomePagePo(page, uiHelper); + home = new DynamicHomePagePo(page, uiHelper, isAppNext); home.setBaseURL(baseURL); }); @@ -58,13 +88,24 @@ test.describe.serial("Dynamic home page customization", () => { }); test("Verify default widgets from server config on first load", async () => { - await new LoginHelper(page).loginAsKeycloakUser(); + test.skip( + isAppNext, + "homepage-backend defaultWidgets are not supported on NFS yet", + ); + + await loginAsKeycloakUser(page); await home.resetToDefaults(); await home.verifyHomePageLoaded(); await home.verifyDefaultWidgetsFromConfig(DEFAULT_WIDGETS.developer); }); test("Verify cards display after seeding widgets", async () => { + // When the server-defaults test is skipped (NFS), log in here first. + // eslint-disable-next-line playwright/no-conditional-in-test -- NFS skips the prior login test + if (isAppNext) { + await loginAsKeycloakUser(page); + await home.verifyHomePageLoaded(); + } await home.seedHomePageWidgets(); await home.verifyHomePageLoaded(); await home.verifyAllCardsDisplayed(); @@ -100,7 +141,7 @@ test.describe.serial("Dynamic home page customization", () => { }); test("Each widget type can be added individually", async () => { - for (const widget of AVAILABLE_WIDGETS) { + for (const widget of home.availableWidgets) { await home.addWidget(widget); } await home.verifyAllCardsDisplayed(); @@ -199,17 +240,25 @@ test.describe.serial("Dynamic home page customization", () => { }); test("Per-user isolation: test2 sees defaults", async () => { + test.skip( + isAppNext, + "homepage-backend defaultWidgets are not supported on NFS yet", + ); await home.reloginAsKeycloakUser(); await home.verifyHomePageLoaded(); await home.seedHomePageWidgets(); test1Count = await home.getVisibleCardCount(); - expect(test1Count).toBe(AVAILABLE_WIDGETS.length); + expect(test1Count).toBe(home.availableWidgets.length); await home.reloginAsKeycloakUser("test2", "test2@123"); await home.verifyHomePageLoaded(); await home.verifyDefaultWidgetsFromConfig(DEFAULT_WIDGETS.developer); }); test("test2 customization does not affect test1 layout", async () => { + test.skip( + isAppNext, + "homepage-backend defaultWidgets / persona defaults are not supported on NFS yet", + ); await home.reloginAsKeycloakUser("test2", "test2@123"); await home.verifyHomePageLoaded(); await home.enterEditMode(); @@ -220,20 +269,63 @@ test.describe.serial("Dynamic home page customization", () => { const test1CountAfter = await home.getVisibleCardCount(); expect(test1CountAfter).toBe(test1Count); }); + + test("NFS: layout persists for same user; clears on account switch", async () => { + test.skip( + !isAppNext, + "legacy relies on backend defaultWidgets for cross-user defaults", + ); + await home.reloginAsKeycloakUser("test1", "test1@123", { + clearHomeStorage: true, + }); + await home.verifyHomePageLoaded(); + await home.seedHomePageWidgets(); + const seededCount = await home.getVisibleCardCount(); + expect(seededCount).toBe(home.availableWidgets.length); + + await home.reloginAsKeycloakUser("test2", "test2@123", { + clearHomeStorage: true, + }); + await home.verifyHomePageLoaded(); + await home.enterEditMode(); + await home.clearAllCardsWithButton(); + await home.verifyCardsDeleted(); + await home.exitEditMode(); + + await home.reloginAsKeycloakUser("test1", "test1@123", { + clearHomeStorage: true, + }); + await home.verifyHomePageLoaded(); + await home.seedHomePageWidgets(); + expect(await home.getVisibleCardCount()).toBe( + home.availableWidgets.length, + ); + }); }); test.describe("Persona-based homepages", () => { + test.beforeEach(() => { + // Enable persona-based tests for NFS, once the backend defaultWidgets config is supported. + test.skip( + isAppNext, + "homepage-backend defaultWidgets are not supported on NFS yet", + ); + }); + test("Admin sees all group widgets", async () => { await home.reloginAsKeycloakUser( HOMEPAGE_ADMIN.username, HOMEPAGE_ADMIN.password, + { clearHomeStorage: true }, ); await home.verifyHomePageLoaded(); await home.verifyDefaultWidgetsFromConfig(DEFAULT_WIDGETS.admin); }); test("Developer sees developer widgets only", async () => { - await home.reloginAsKeycloakUser("test2", "test2@123"); + await home.reloginAsKeycloakUser("test2", "test2@123", { + clearHomeStorage: true, + }); await home.verifyHomePageLoaded(); await home.verifyDefaultWidgetsFromConfig(DEFAULT_WIDGETS.developer); for (const widget of DEFAULT_WIDGETS.adminOnly) { diff --git a/workspaces/homepage/e2e-tests/tests/utils/dynamic-homepage.ts b/workspaces/homepage/e2e-tests/tests/utils/dynamic-homepage.ts index 673c1cc989..b9da3fa3b3 100644 --- a/workspaces/homepage/e2e-tests/tests/utils/dynamic-homepage.ts +++ b/workspaces/homepage/e2e-tests/tests/utils/dynamic-homepage.ts @@ -16,7 +16,7 @@ const EXPECTED_CARD_TEXTS = [ "Top Visited", ] as const; -/** All widgets available in the "Add widget" dialog. */ +/** Legacy Scalprum "Add widget" dialog labels. */ export const AVAILABLE_WIDGETS = [ "Onboarding Section", "Entity Section", @@ -24,6 +24,26 @@ export const AVAILABLE_WIDGETS = [ "Top Visited", ] as const; +/** + * NFS AddWidgetDialog labels use HomePageWidgetBlueprint title/name + * (title || name). Onboarding/Entity have no title — dialog shows name. + */ +export const AVAILABLE_WIDGETS_NFS = [ + "Red Hat Developer Hub - Onboarding", + "Red Hat Developer Hub - Software Catalog", + "Recently Visited", + "Top Visited", +] as const; + +/** Map legacy add-widget labels to NFS dialog labels. */ +const NFS_WIDGET_LABELS = new Map([ + ["Onboarding Section", "Red Hat Developer Hub - Onboarding"], + ["Entity Section", "Red Hat Developer Hub - Software Catalog"], + ["Entity section", "Red Hat Developer Hub - Software Catalog"], + ["Recently Visited", "Recently Visited"], + ["Top Visited", "Top Visited"], +]); + const COMMON = ["Explore Your Software Catalog"]; const ADMIN_ONLY = ["Explore Templates", "Quick Access"]; const DEVELOPER_ONLY = ["Recently Visited", "Top Visited"]; @@ -37,6 +57,10 @@ export const DEFAULT_WIDGETS = { developerOnly: DEVELOPER_ONLY, }; +export function isHomepageAppNext(projectOrNamespace: string): boolean { + return projectOrNamespace.endsWith("-app-next"); +} + export const HOMEPAGE_ADMIN = { username: "homepage-admin", password: "homepage-admin@123", // gitleaks:allow @@ -47,12 +71,59 @@ const HOMEPAGE_TEST3 = { password: "test3@123", // gitleaks:allow }; +/** + * Keycloak login that tolerates slow NFS cold loads. + * + * Stock LoginHelper.loginAsKeycloakUser uses waitForLoad (progressbar state:hidden + * resolves immediately if the bar is not mounted yet) then clickButton("Sign In") + * under actionTimeout=10s. On homepage-app-next the Sign In card often appears only + * after ~30–60s of remote plugin loading, so login races and looks like a blank page. + */ +export async function loginAsKeycloakUser( + page: Page, + username = "test1", + password = "test1@123", +): Promise { + const helper = new LoginHelper(page); + await page.goto("/"); + await page.getByRole("button", { name: "Sign In", exact: true }).waitFor({ + state: "visible", + timeout: 240_000, + }); + const popupPromise = page.waitForEvent("popup"); + await page.getByRole("button", { name: "Sign In", exact: true }).click(); + const popup = await popupPromise; + await helper.logintoKeycloak(popup, username, password); + await page + .locator("nav a") + .first() + .waitFor({ state: "visible", timeout: 60_000 }); +} + export async function setupKeycloakGroups(): Promise { + const baseUrl = process.env.KEYCLOAK_BASE_URL; + if (!baseUrl) { + throw new Error( + "KEYCLOAK_BASE_URL is not set. Global setup should deploy Keycloak and set it; " + + "do not set SKIP_KEYCLOAK_DEPLOYMENT=true for homepage tests.", + ); + } + + // Local Keycloak from e2e-test-utils uses admin/admin123. Vault secrets are for CI. + const username = + process.env.VAULT_KEYCLOAK_ADMIN_USERNAME || + process.env.KEYCLOAK_ADMIN_USERNAME || + "admin"; + const password = + process.env.VAULT_KEYCLOAK_ADMIN_PASSWORD || + process.env.KEYCLOAK_ADMIN_PASSWORD || + "admin123"; + const keycloak = new KeycloakHelper(); await keycloak.connect({ - baseUrl: process.env.KEYCLOAK_BASE_URL!, - username: process.env.VAULT_KEYCLOAK_ADMIN_USERNAME!, - password: process.env.VAULT_KEYCLOAK_ADMIN_PASSWORD!, + baseUrl, + username, + password, }); await keycloak.deleteUser("rhdh", HOMEPAGE_ADMIN.username).catch(() => {}); @@ -86,12 +157,51 @@ export class DynamicHomePagePo { constructor( private readonly page: Page, private readonly ui: UIhelper, + private readonly isAppNext = false, ) {} setBaseURL(url: string): void { this.baseURL = url; } + /** Widget labels shown in the Add widget dialog for the active frontend. */ + get availableWidgets(): readonly string[] { + return this.isAppNext ? AVAILABLE_WIDGETS_NFS : AVAILABLE_WIDGETS; + } + + private widgetDialogLabel(widgetType: string): string { + if (!this.isAppNext) { + return widgetType; + } + return NFS_WIDGET_LABELS.get(widgetType) ?? widgetType; + } + + /** + * Clears the CustomHomepageGrid storage bucket used by NFS/legacy home. + * Layout is stored under storageApi bucket `home.customHomepage` (often + * localStorage). Clear between distinct users so shared browser storage + * cannot leak layouts when user-settings isolation is unavailable. + */ + async clearHomeLayoutStorage(): Promise { + await this.page.evaluate(() => { + const keysToRemove: string[] = []; + for (let i = 0; i < localStorage.length; i++) { + const key = localStorage.key(i); + if ( + key && + (key.includes("home.customHomepage") || + key.includes("customHomepage") || + /[/:]home$/.test(key)) + ) { + keysToRemove.push(key); + } + } + for (const key of keysToRemove) { + localStorage.removeItem(key); + } + }); + } + private async signOut(): Promise { await this.page.goto(`${this.baseURL}/settings`); await this.page.getByTestId("user-settings-menu").click(); @@ -103,18 +213,24 @@ export class DynamicHomePagePo { async reloginAsKeycloakUser( username = "test1", password = "test1@123", + options?: { clearHomeStorage?: boolean }, ): Promise { await this.signOut(); await this.page.context().clearCookies(); - await this.page.goto(this.baseURL); - await new LoginHelper(this.page).loginAsKeycloakUser(username, password); + if (options?.clearHomeStorage) { + await this.clearHomeLayoutStorage(); + } + await loginAsKeycloakUser(this.page, username, password); } async reloginAsNonGroupUser(): Promise { await this.signOut(); await this.page.context().clearCookies(); - await this.page.goto(this.baseURL); - await new LoginHelper(this.page).loginAsKeycloakUser( + // Guest/non-group user must not inherit the previous user's layout from + // shared browser storage when switching accounts in the same context. + await this.clearHomeLayoutStorage(); + await loginAsKeycloakUser( + this.page, HOMEPAGE_TEST3.username, HOMEPAGE_TEST3.password, ); @@ -280,10 +396,11 @@ export class DynamicHomePagePo { } async addWidget(widgetType: string): Promise { + const label = this.widgetDialogLabel(widgetType); await this.ui.clickButton("Add widget"); // eslint-disable-next-line playwright/no-wait-for-timeout -- dialog open await this.page.waitForTimeout(1000); - await this.page.getByRole("button", { name: widgetType }).click(); + await this.page.getByRole("button", { name: label }).click(); // eslint-disable-next-line playwright/no-wait-for-timeout -- widget mount await this.page.waitForTimeout(1000); } @@ -314,7 +431,7 @@ export class DynamicHomePagePo { await this.ui.clickButton("Add widget"); // eslint-disable-next-line playwright/no-wait-for-timeout -- dialog open await this.page.waitForTimeout(1000); - for (const widget of AVAILABLE_WIDGETS) { + for (const widget of this.availableWidgets) { await expect( this.page.getByRole("button", { name: widget }), ).toBeVisible(); diff --git a/workspaces/homepage/metadata/red-hat-developer-hub-backstage-plugin-homepage.yaml b/workspaces/homepage/metadata/red-hat-developer-hub-backstage-plugin-homepage.yaml index 999a594a09..2dbf50fb4a 100644 --- a/workspaces/homepage/metadata/red-hat-developer-hub-backstage-plugin-homepage.yaml +++ b/workspaces/homepage/metadata/red-hat-developer-hub-backstage-plugin-homepage.yaml @@ -120,11 +120,13 @@ spec: breakpoints: xl: { w: 12, h: 6 } lg: { w: 12, h: 6 } - homepage: - defaultWidgets: - - id: onboarding - ref: rhdh-onboarding-section - - id: entity-list - ref: rhdh-entity-section - - id: template-list - ref: rhdh-template-section + # homepage.defaultWidgets is not supported on NFS yet — uncomment when it is: + # homepage: + # defaultWidgets: + # - id: onboarding + # ref: rhdh-onboarding-section + # - id: entity-list + # ref: rhdh-entity-section + # - id: template-list + # ref: rhdh-template-section + From b6aa7e17f15b8d6421d40d8a56c449b033fc65ed Mon Sep 17 00:00:00 2001 From: Debsmita Santra Date: Fri, 14 Aug 2026 15:39:20 +0530 Subject: [PATCH 4/5] fix failing e2e tests rh-pre-commit.version: 2.3.2 rh-pre-commit.check-secrets: ENABLED --- .../config/dynamic-plugins-app-next.yaml | 9 +- .../tests/config/dynamic-plugins.yaml | 3 + .../e2e-tests/tests/specs/homepage.spec.ts | 19 ++- .../e2e-tests/tests/utils/dynamic-homepage.ts | 157 +++++++++++++----- 4 files changed, 136 insertions(+), 52 deletions(-) diff --git a/workspaces/homepage/e2e-tests/tests/config/dynamic-plugins-app-next.yaml b/workspaces/homepage/e2e-tests/tests/config/dynamic-plugins-app-next.yaml index 8062a034e5..4e1dcda9ca 100644 --- a/workspaces/homepage/e2e-tests/tests/config/dynamic-plugins-app-next.yaml +++ b/workspaces/homepage/e2e-tests/tests/config/dynamic-plugins-app-next.yaml @@ -30,16 +30,13 @@ plugins: seconds: 15 timeout: minutes: 1 - # e2e-test-utils NFS layer still pins app-auth / app-integrations at bs_1.49.4. - # Merge is by exact package string, so disable those and enable current metadata tags. + # e2e-test-utils NFS layer pins app-auth / app-integrations at bs_1.49.4. + # dynamic-plugins.default.yaml (catalog index includes) already ships bs_1.52.0 — + # do not add those OCIs here or InstallException reports duplicates. - package: oci://ghcr.io/redhat-developer/rhdh-plugin-export-overlays/red-hat-developer-hub-backstage-plugin-app-auth:bs_1.49.4__0.0.1 disabled: true - - package: oci://ghcr.io/redhat-developer/rhdh-plugin-export-overlays/red-hat-developer-hub-backstage-plugin-app-auth:bs_1.52.0__0.1.1 - disabled: false - package: oci://ghcr.io/redhat-developer/rhdh-plugin-export-overlays/red-hat-developer-hub-backstage-plugin-app-integrations:bs_1.49.4__0.0.1 disabled: true - - package: oci://ghcr.io/redhat-developer/rhdh-plugin-export-overlays/red-hat-developer-hub-backstage-plugin-app-integrations:bs_1.52.0__0.1.1 - disabled: false # Catalog / DPDY enables app-defaults from quay midstream 2.0.0--0.0.2, which lacks # the io.backstage.dynamic-packages annotation (InstallException). Disable it and use # the community GHCR artifact from workspaces/app-defaults metadata instead. diff --git a/workspaces/homepage/e2e-tests/tests/config/dynamic-plugins.yaml b/workspaces/homepage/e2e-tests/tests/config/dynamic-plugins.yaml index 3cad7d1911..21d8c4e0ef 100644 --- a/workspaces/homepage/e2e-tests/tests/config/dynamic-plugins.yaml +++ b/workspaces/homepage/e2e-tests/tests/config/dynamic-plugins.yaml @@ -23,6 +23,9 @@ plugins: seconds: 15 timeout: minutes: 1 + # Version from workspaces/backstage export: backstage-plugin-home + - package: oci://ghcr.io/redhat-developer/rhdh-plugin-export-overlays/backstage-plugin-home:bs_1.52.0__0.9.7 + enabled: true - package: oci://ghcr.io/redhat-developer/rhdh-plugin-export-overlays/red-hat-developer-hub-backstage-plugin-homepage:bs_1.52.0__1.17.1 enabled: true pluginConfig: diff --git a/workspaces/homepage/e2e-tests/tests/specs/homepage.spec.ts b/workspaces/homepage/e2e-tests/tests/specs/homepage.spec.ts index 6f66fdbec4..363c05e008 100644 --- a/workspaces/homepage/e2e-tests/tests/specs/homepage.spec.ts +++ b/workspaces/homepage/e2e-tests/tests/specs/homepage.spec.ts @@ -83,6 +83,13 @@ test.describe.serial("Dynamic home page customization", () => { home.setBaseURL(baseURL); }); + test.beforeEach(() => { + // NFS login + widget seeding can exceed the default 90s per test. + if (isAppNext) { + test.setTimeout(10 * 60 * 1000); + } + }); + test.afterAll(async () => { await context?.close(); }); @@ -104,7 +111,7 @@ test.describe.serial("Dynamic home page customization", () => { // eslint-disable-next-line playwright/no-conditional-in-test -- NFS skips the prior login test if (isAppNext) { await loginAsKeycloakUser(page); - await home.verifyHomePageLoaded(); + await home.verifyHomePageLoaded({ requireWidgets: false }); } await home.seedHomePageWidgets(); await home.verifyHomePageLoaded(); @@ -278,7 +285,7 @@ test.describe.serial("Dynamic home page customization", () => { await home.reloginAsKeycloakUser("test1", "test1@123", { clearHomeStorage: true, }); - await home.verifyHomePageLoaded(); + await home.verifyHomePageLoaded({ requireWidgets: false }); await home.seedHomePageWidgets(); const seededCount = await home.getVisibleCardCount(); expect(seededCount).toBe(home.availableWidgets.length); @@ -286,16 +293,16 @@ test.describe.serial("Dynamic home page customization", () => { await home.reloginAsKeycloakUser("test2", "test2@123", { clearHomeStorage: true, }); - await home.verifyHomePageLoaded(); + // test2 has no server defaultWidgets on NFS — home can load empty. + await home.verifyHomePageLoaded({ requireWidgets: false }); await home.enterEditMode(); - await home.clearAllCardsWithButton(); - await home.verifyCardsDeleted(); + await home.clearAllCardsIfPresent(); await home.exitEditMode(); await home.reloginAsKeycloakUser("test1", "test1@123", { clearHomeStorage: true, }); - await home.verifyHomePageLoaded(); + await home.verifyHomePageLoaded({ requireWidgets: false }); await home.seedHomePageWidgets(); expect(await home.getVisibleCardCount()).toBe( home.availableWidgets.length, diff --git a/workspaces/homepage/e2e-tests/tests/utils/dynamic-homepage.ts b/workspaces/homepage/e2e-tests/tests/utils/dynamic-homepage.ts index b9da3fa3b3..0e8bfcf19f 100644 --- a/workspaces/homepage/e2e-tests/tests/utils/dynamic-homepage.ts +++ b/workspaces/homepage/e2e-tests/tests/utils/dynamic-homepage.ts @@ -236,29 +236,33 @@ export class DynamicHomePagePo { ); } - private readonly editButton = () => this.page.getByText("Edit"); + private readonly editButton = () => + this.page.getByRole("button", { name: "Edit", exact: true }); private readonly saveButton = () => - this.page.getByText("Save", { - exact: true, - }); + this.page.getByRole("button", { name: "Save", exact: true }); + private readonly cancelButton = () => + this.page.getByRole("button", { name: "Cancel", exact: true }); private readonly clearAllButton = () => this.page.getByRole("button", { name: "Clear all" }); private readonly restoreDefaultsButton = () => this.page.getByText("Restore defaults"); private readonly addWidgetButton = () => this.page.getByRole("button", { name: "Add widget" }); - private readonly resizeHandles = () => - this.page.locator(".react-resizable-handle"); private readonly deleteButtons = () => this.page.getByRole("button", { name: "Delete widget" }); private readonly greetingText = () => this.page.getByText(/Good (morning|afternoon|evening)/); - async verifyHomePageLoaded(): Promise { + async verifyHomePageLoaded(options?: { + requireWidgets?: boolean; + }): Promise { await this.ui.verifyHeading("Welcome back"); - await expect( - this.page.locator('[class*="react-grid-item"]').first(), - ).toBeVisible({ timeout: 15_000 }); + const requireWidgets = options?.requireWidgets ?? true; + if (requireWidgets) { + await expect( + this.page.locator('[class*="react-grid-item"]').first(), + ).toBeVisible({ timeout: 15_000 }); + } await this.dismissQuickstart(); } @@ -299,54 +303,114 @@ export class DynamicHomePagePo { async enterEditMode(): Promise { await this.ui.clickButton("Edit"); - await expect(this.saveButton()).toBeVisible(); + // NFS shows both Cancel and Save in edit mode; .or() + toBeVisible() hits strict mode + // when both match — wait for either via .first(). + await this.saveButton() + .or(this.cancelButton()) + .first() + .waitFor({ state: "visible", timeout: 10_000 }); } async exitEditMode(): Promise { - await this.ui.clickButton("Save"); - await expect(this.editButton()).toBeVisible(); + await this.dismissAddWidgetDialog(); + + // NFS only surfaces Save after a layout dimension change; add/remove alone leaves + // Save hidden and Cancel reverts to the last persisted layout. + if (this.isAppNext && !(await this.saveButton().isVisible())) { + await this.nudgeLayoutToEnableSave(); + } + + if (await this.saveButton().isVisible()) { + await this.saveButton().click(); + } else if (await this.cancelButton().isVisible()) { + await this.cancelButton().click(); + } + await expect(this.editButton()).toBeVisible({ timeout: 10_000 }); + } + + private async dismissAddWidgetDialog(): Promise { + const dialog = this.page.getByRole("dialog"); + if (await dialog.isVisible()) { + await this.page.keyboard.press("Escape"); + await expect(dialog).toBeHidden({ timeout: 5_000 }); + } + } + + /** Small resize so NFS edit toolbar exposes Save after widget add/remove. */ + private async nudgeLayoutToEnableSave(): Promise { + const gridItem = this.editableGridItem(); + if ((await gridItem.count()) === 0) { + return; + } + const handle = gridItem + .locator(".react-resizable-handle-se, .react-resizable-handle") + .last(); + if (!(await handle.isVisible())) { + return; + } + await this.dragResizeHandle(handle, { widthDelta: 0, heightDelta: 40 }); + await this.saveButton().waitFor({ state: "visible", timeout: 10_000 }); + } + + /** Grid item that contains a real widget (not an empty NFS placeholder row). */ + private editableGridItem(): Locator { + return this.page + .locator('[class*="react-grid-item"]') + .filter({ has: this.deleteButtons() }) + .last(); } /** - * Resizes one card via the first visible resize handle (while still in edit - * mode, before Save). Call after `enterEditMode` and adding a widget. + * Resizes one card via the SE resize handle (while still in edit mode, before Save). + * Call after `enterEditMode` and adding a widget. */ async resizeFirstCard(): Promise { - const handle = this.resizeHandles().first(); + const gridItem = this.editableGridItem(); + await expect(gridItem).toBeVisible({ timeout: 10_000 }); + + const handle = gridItem + .locator(".react-resizable-handle-se, .react-resizable-handle") + .last(); await expect(handle).toBeVisible(); - const panel = this.resizablePanelForHandle(handle); - const initialBox = await panel.boundingBox(); + + const initialBox = await gridItem.boundingBox(); expect(initialBox).not.toBeNull(); await this.dragResizeHandle(handle); - const finalBox = await panel.boundingBox(); - expect(finalBox).not.toBeNull(); - const widthChanged = finalBox!.width !== initialBox!.width; - const heightChanged = finalBox!.height !== initialBox!.height; - expect(widthChanged || heightChanged).toBe(true); - } - - /** Nearest `react-resizable` root for a handle (`.react-resizable-handle`). */ - private resizablePanelForHandle(handle: Locator): Locator { - return handle.locator( - 'xpath=ancestor::*[contains(@class,"react-resizable")][1]', - ); + // Measure the grid item — NFS widgets are full width so only height changes. + await expect + .poll(async () => { + const box = await gridItem.boundingBox(); + if (!box || !initialBox) { + return false; + } + return ( + Math.abs(box.height - initialBox.height) > 5 || + Math.abs(box.width - initialBox.width) > 5 + ); + }) + .toBe(true); } - private async dragResizeHandle(handle: Locator): Promise { + private async dragResizeHandle( + handle: Locator, + deltas?: { widthDelta?: number; heightDelta?: number }, + ): Promise { await handle.scrollIntoViewIfNeeded(); const box = await handle.boundingBox(); expect(box).not.toBeNull(); const startX = box!.x + box!.width / 2; const startY = box!.y + box!.height / 2; - const delta = 160; + // NFS home widgets default to full grid width — drag vertically to resize height. + const widthDelta = deltas?.widthDelta ?? (this.isAppNext ? 0 : 160); + const heightDelta = deltas?.heightDelta ?? (this.isAppNext ? 220 : 160); await this.page.mouse.move(startX, startY); await this.page.mouse.down(); - await this.page.mouse.move(startX + delta, startY + delta, { steps: 12 }); + await this.page.mouse.move(startX + widthDelta, startY + heightDelta, { + steps: 24, + }); await this.page.mouse.up(); - // eslint-disable-next-line playwright/no-wait-for-timeout -- layout after resize - await this.page.waitForTimeout(500); } async deleteAllCards(): Promise { @@ -368,6 +432,14 @@ export class DynamicHomePagePo { await this.clearAllButton().click(); } + /** Clear all only when the grid has cards (NFS users may start with an empty home). */ + async clearAllCardsIfPresent(): Promise { + if (await this.clearAllButton().isVisible()) { + await this.clearAllCardsWithButton(); + await this.verifyCardsDeleted(); + } + } + async verifyCardsDeleted(): Promise { const gridItems = this.page.locator('[class*="react-grid-item"]'); await expect(gridItems).toHaveCount(0, { timeout: 10_000 }); @@ -397,12 +469,17 @@ export class DynamicHomePagePo { async addWidget(widgetType: string): Promise { const label = this.widgetDialogLabel(widgetType); + const gridItems = this.page.locator('[class*="react-grid-item"]'); + const countBefore = await gridItems.count(); + await this.ui.clickButton("Add widget"); - // eslint-disable-next-line playwright/no-wait-for-timeout -- dialog open - await this.page.waitForTimeout(1000); - await this.page.getByRole("button", { name: label }).click(); - // eslint-disable-next-line playwright/no-wait-for-timeout -- widget mount - await this.page.waitForTimeout(1000); + const widgetOption = this.page.getByRole("button", { name: label }); + await widgetOption.waitFor({ state: "visible", timeout: 30_000 }); + await widgetOption.click(); + + await expect(gridItems).toHaveCount(countBefore + 1, { timeout: 30_000 }); + await expect(gridItems.last()).toBeVisible({ timeout: 30_000 }); + await this.dismissAddWidgetDialog(); } /** Returns count of visible widget cards on the homepage grid. */ From 3c88b9fa596868e79ec91f3c06664681649fb45c Mon Sep 17 00:00:00 2001 From: Debsmita Santra Date: Fri, 14 Aug 2026 16:03:37 +0530 Subject: [PATCH 5/5] addressed review comments rh-pre-commit.version: 2.3.2 rh-pre-commit.check-secrets: ENABLED --- .../config/dynamic-plugins-app-next.yaml | 62 ------------------- .../tests/config/dynamic-plugins.yaml | 24 ------- .../e2e-tests/tests/specs/homepage.spec.ts | 5 -- 3 files changed, 91 deletions(-) diff --git a/workspaces/homepage/e2e-tests/tests/config/dynamic-plugins-app-next.yaml b/workspaces/homepage/e2e-tests/tests/config/dynamic-plugins-app-next.yaml index 4e1dcda9ca..e3be3efba3 100644 --- a/workspaces/homepage/e2e-tests/tests/config/dynamic-plugins-app-next.yaml +++ b/workspaces/homepage/e2e-tests/tests/config/dynamic-plugins-app-next.yaml @@ -6,45 +6,6 @@ # commented block below and enable it when support lands. NFS tests seed widgets via # the UI until then. plugins: - # Auth layer adds this with {{inherit}}; hub image has no base version, so install - # fails. Merge is by exact package string (no normalizeKey), so disable it explicitly - # and enable a pinned GHCR artifact below (workspaces/keycloak metadata). - - package: oci://registry.access.redhat.com/rhdh/backstage-community-plugin-catalog-backend-module-keycloak:{{inherit}} - disabled: true - - package: oci://ghcr.io/redhat-developer/rhdh-plugin-export-overlays/backstage-community-plugin-catalog-backend-module-keycloak:bs_1.49.4__3.19.2 - disabled: false - pluginConfig: - catalog: - providers: - keycloakOrg: - default: - baseUrl: ${KEYCLOAK_BASE_URL} - loginRealm: ${KEYCLOAK_LOGIN_REALM} - realm: ${KEYCLOAK_REALM} - clientId: ${KEYCLOAK_CLIENT_ID} - clientSecret: ${KEYCLOAK_CLIENT_SECRET} - schedule: - frequency: - minutes: 1 - initialDelay: - seconds: 15 - timeout: - minutes: 1 - # e2e-test-utils NFS layer pins app-auth / app-integrations at bs_1.49.4. - # dynamic-plugins.default.yaml (catalog index includes) already ships bs_1.52.0 — - # do not add those OCIs here or InstallException reports duplicates. - - package: oci://ghcr.io/redhat-developer/rhdh-plugin-export-overlays/red-hat-developer-hub-backstage-plugin-app-auth:bs_1.49.4__0.0.1 - disabled: true - - package: oci://ghcr.io/redhat-developer/rhdh-plugin-export-overlays/red-hat-developer-hub-backstage-plugin-app-integrations:bs_1.49.4__0.0.1 - disabled: true - # Catalog / DPDY enables app-defaults from quay midstream 2.0.0--0.0.2, which lacks - # the io.backstage.dynamic-packages annotation (InstallException). Disable it and use - # the community GHCR artifact from workspaces/app-defaults metadata instead. - - package: oci://quay.io/rhdh/red-hat-developer-hub-backstage-plugin-app-defaults:2.0.0--0.0.2 - disabled: true - # Version from workspaces/app-defaults/metadata/red-hat-developer-hub-backstage-plugin-app-defaults.yaml - - package: oci://ghcr.io/redhat-developer/rhdh-plugin-export-overlays/red-hat-developer-hub-backstage-plugin-app-defaults:bs_1.52.0__0.0.3 - disabled: false # Version from workspaces/backstage export: backstage-plugin-home - package: oci://ghcr.io/redhat-developer/rhdh-plugin-export-overlays/backstage-plugin-home:bs_1.52.0__0.9.7 disabled: false @@ -54,26 +15,3 @@ plugins: # Version from workspaces/homepage/metadata/red-hat-developer-hub-backstage-plugin-homepage-backend.yaml - package: oci://ghcr.io/redhat-developer/rhdh-plugin-export-overlays/red-hat-developer-hub-backstage-plugin-homepage-backend:bs_1.52.0__0.4.0 disabled: false - # homepage.defaultWidgets is not supported on NFS yet — uncomment when it is: - # pluginConfig: - # homepage: - # # NFS refs are HomePageWidgetBlueprint names (not legacy mount-point config.id). - # defaultWidgets: - # - id: onboarding - # ref: rhdh-onboarding-section - # - id: entity-list - # ref: rhdh-entity-section - # - if: - # groups: [group:default/admins] - # children: - # - id: template-list - # ref: rhdh-template-section - # - id: quickaccess-card - # ref: quick-access-card - # - if: - # groups: [group:default/developers] - # children: - # - id: recently-visited-card - # ref: recently-visited - # - id: top-visited-card - # ref: top-visited diff --git a/workspaces/homepage/e2e-tests/tests/config/dynamic-plugins.yaml b/workspaces/homepage/e2e-tests/tests/config/dynamic-plugins.yaml index 21d8c4e0ef..baba7c7d98 100644 --- a/workspaces/homepage/e2e-tests/tests/config/dynamic-plugins.yaml +++ b/workspaces/homepage/e2e-tests/tests/config/dynamic-plugins.yaml @@ -1,28 +1,4 @@ plugins: - # Auth layer adds this with {{inherit}}; hub image has no base version, so install - # fails. Merge is by exact package string (no normalizeKey), so disable it explicitly - # and enable a pinned GHCR artifact below (workspaces/keycloak metadata). - - package: oci://registry.access.redhat.com/rhdh/backstage-community-plugin-catalog-backend-module-keycloak:{{inherit}} - disabled: true - - package: oci://ghcr.io/redhat-developer/rhdh-plugin-export-overlays/backstage-community-plugin-catalog-backend-module-keycloak:bs_1.49.4__3.19.2 - disabled: false - pluginConfig: - catalog: - providers: - keycloakOrg: - default: - baseUrl: ${KEYCLOAK_BASE_URL} - loginRealm: ${KEYCLOAK_LOGIN_REALM} - realm: ${KEYCLOAK_REALM} - clientId: ${KEYCLOAK_CLIENT_ID} - clientSecret: ${KEYCLOAK_CLIENT_SECRET} - schedule: - frequency: - minutes: 1 - initialDelay: - seconds: 15 - timeout: - minutes: 1 # Version from workspaces/backstage export: backstage-plugin-home - package: oci://ghcr.io/redhat-developer/rhdh-plugin-export-overlays/backstage-plugin-home:bs_1.52.0__0.9.7 enabled: true diff --git a/workspaces/homepage/e2e-tests/tests/specs/homepage.spec.ts b/workspaces/homepage/e2e-tests/tests/specs/homepage.spec.ts index 363c05e008..bc2cfdb05a 100644 --- a/workspaces/homepage/e2e-tests/tests/specs/homepage.spec.ts +++ b/workspaces/homepage/e2e-tests/tests/specs/homepage.spec.ts @@ -70,11 +70,6 @@ test.describe.serial("Dynamic home page customization", () => { await rhdh.deploy(); }); - // Keycloak users are cluster-scoped — create once so parallel legacy/app-next - // projects do not race on delete/create of the same users. - await test.runOnce("homepage-keycloak-groups", async () => { - await setupKeycloakGroups(); - }); baseURL = rhdh.rhdhUrl; context = await browser.newContext({ baseURL }); page = await context.newPage();