Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions catalog-entities/extensions/plugins/home-page.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,31 @@ 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)**, 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:
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:
Expand Down
11 changes: 11 additions & 0 deletions workspaces/homepage/e2e-tests/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -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",
},
],
});
14 changes: 14 additions & 0 deletions workspaces/homepage/e2e-tests/tests/config/app-config-rhdh.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
# Shared by legacy and app-next projects.
# 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:
config:
path: /
- api:home/visits: true
- app-root-element:home/visit-listener: true
- home-page-layout:home/dynamic-homepage-layout:
config:
customizable: true

permission:
enabled: true
rbac:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# 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:
# 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
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
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
# 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:
dynamicPlugins:
Expand Down
119 changes: 107 additions & 12 deletions workspaces/homepage/e2e-tests/tests/specs/homepage.spec.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,28 @@
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";

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;
Expand All @@ -25,45 +31,83 @@ 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);

await test.runOnce("homepage-setup", async () => {
const namespace = rhdh.deploymentConfig.namespace;
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",
);
const namespace = rhdh.deploymentConfig.namespace;
await $`oc apply -f ${rbacConfigmapPath} -n ${namespace}`;

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"),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] correctness

Duplicate test.runOnce("homepage-keycloak-groups", ...) call in beforeAll — the identical block appears at lines ~41 and ~68. The second invocation is a no-op because runOnce uses file-based flags keyed by the first argument. This is dead code (likely a copy-paste artifact) and should be removed to avoid confusion.

Suggested fix: Remove the second test.runOnce("homepage-keycloak-groups", ...) block (lines ~66-70 in the new code).

});
await rhdh.deploy();
});

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);
});

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();
});

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({ requireWidgets: false });
}
await home.seedHomePageWidgets();
await home.verifyHomePageLoaded();
await home.verifyAllCardsDisplayed();
Expand Down Expand Up @@ -99,7 +143,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();
Expand Down Expand Up @@ -198,17 +242,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();
Expand All @@ -219,20 +271,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({ requireWidgets: false });
await home.seedHomePageWidgets();
const seededCount = await home.getVisibleCardCount();
expect(seededCount).toBe(home.availableWidgets.length);

await home.reloginAsKeycloakUser("test2", "test2@123", {
clearHomeStorage: true,
});
// test2 has no server defaultWidgets on NFS — home can load empty.
await home.verifyHomePageLoaded({ requireWidgets: false });
await home.enterEditMode();
await home.clearAllCardsIfPresent();
await home.exitEditMode();

await home.reloginAsKeycloakUser("test1", "test1@123", {
clearHomeStorage: true,
});
await home.verifyHomePageLoaded({ requireWidgets: false });
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) {
Expand Down
Loading
Loading