Skip to content

Commit faa8069

Browse files
gustavoliraclaude
andcommitted
test(e2e): widen cluster-free run to full guest-signin + learning-paths
Expand the harness from 1 to 4 tests by fixing the global-header mounting and enabling the learning-paths spec: - global-header: the repo's static app-config.dynamic-plugins.yaml only mounts the bare GlobalHeader container with no children, which is why the plugin loaded but rendered nothing off-cluster. In-cluster the full mount points (ProfileDropdown, Settings/Sign-out menu items, search, etc.) come from the plugin's pluginConfig in the catalog index. Install the plugin from OCI with that canonical pluginConfig (copied from rhdh-plugins workspaces/global-header app-config.dynamic.yaml) and load the generated dynamic-plugins-root/app-config.dynamic-plugins.yaml last in the webServer config args — the same file and merge order the production container uses. This unblocks the guest-signin Settings and Sign-out tests, which navigate via the header's profile dropdown. - learning-paths: the page renders off-cluster from the static fallback data bundled with packages/app. The spec navigates through the "References" sidebar group, which is a CI config-map menu customization — mirror that menuItems nesting in app-config.local-e2e.yaml (objects deep-merge across config files, so only the nesting keys are needed). - Tag the three newly validated tests @cluster-free and allowlist learning-path-page.spec.ts in testMatch. - settings.spec.ts and home-page-customization.spec.ts stay out for now: they assert CI test data (catalog ownership entities, customized home cards) that the harness does not provide yet — documented in Known issues. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 203f7aa commit faa8069

6 files changed

Lines changed: 201 additions & 41 deletions

File tree

app-config.local-e2e.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,19 @@ backend:
2424
database:
2525
client: better-sqlite3
2626
connection: ":memory:"
27+
28+
# The e2e specs are written against the CI deployment's menu customization
29+
# (.ci/pipelines/resources/config_map/dynamic-plugins-config.yaml): APIs and
30+
# Learning Paths nest under a "References" sidebar group. Mirror it here so
31+
# navigation helpers like openSidebarButton("References") work off-cluster.
32+
dynamicPlugins:
33+
frontend:
34+
default.main-menu-items:
35+
menuItems:
36+
default.list:
37+
title: References
38+
icon: bookmarks
39+
default.apis:
40+
parent: default.list
41+
default.learning-path:
42+
parent: default.list

docs/e2e-tests/local-e2e-harness.md

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -65,16 +65,24 @@ dev server with `app-config.yaml` + `app-config.dynamic-plugins.yaml` +
6565
if `dynamic-plugins-root` has no plugins.
6666

6767
The run is scoped to tests tagged `@cluster-free` within the spec files allowlisted in
68-
`testMatch` — today the one test verified green off-cluster, the
69-
`guest-signin-happy-path` home-page test. To widen coverage, tag a validated test with
70-
`@cluster-free` and add its spec file to `testMatch` (see "Known issues").
68+
`testMatch`. To widen coverage, tag a validated test with `@cluster-free` and add its
69+
spec file to `testMatch`; if the test needs extra plugins, add them (with their
70+
`pluginConfig`) to `e2e-tests/local-harness/dynamic-plugins.yaml` and re-run
71+
`populate.sh` (see "Known issues").
7172

7273
### Verified
7374

7475
With plugins populated, the legacy app renders the full production RHDH UI off-cluster
75-
(branding, sidebar, and Quick Access from the dynamic home-page plugin). The existing
76-
`guest-signin-happy-path` **home-page test passes unmodified** — confirming a dynamic
77-
frontend plugin renders with no cluster.
76+
(branding, sidebar, global header, and Quick Access from the dynamic plugins). The
77+
existing specs **pass unmodified**:
78+
79+
- `guest-signin-happy-path` — all three tests: home page (dynamic-home-page plugin),
80+
Settings and Sign-out (navigation via the global-header profile dropdown, using the
81+
plugin's canonical `pluginConfig` merged through the generated
82+
`dynamic-plugins-root/app-config.dynamic-plugins.yaml`, exactly as in-cluster).
83+
- `learning-path-page` — renders from the static fallback data bundled with
84+
`packages/app`; the "References" sidebar group mirrors the CI menu customization via
85+
`app-config.local-e2e.yaml`.
7886

7987
## CI
8088

@@ -111,8 +119,17 @@ just `run`), which is why this harness boots the dev servers directly instead.
111119
dependency versions), backend dynamic-plugin builds fail with version-mismatch errors
112120
and yarn may not surface workspace bins. Run `yarn install` first. The
113121
`install-dynamic-plugins` populate path avoids building from source and is unaffected.
114-
- **`global-header` plugin mounting** still needs config sorting for the legacy harness;
115-
specs that navigate via the top-right profile dropdown depend on it.
122+
- **Re-run `populate.sh` after changing the harness plugin set.** The `pluginConfig`
123+
blocks in `e2e-tests/local-harness/dynamic-plugins.yaml` (e.g. the global-header
124+
mount points) only take effect through the generated
125+
`dynamic-plugins-root/app-config.dynamic-plugins.yaml`, which the webServer loads
126+
last. A stale populate leaves plugins loaded but unconfigured (the header renders
127+
empty).
128+
- **Specs that need CI test data are not enabled yet.** `settings.spec.ts` asserts
129+
ownership entities ("Guest User, team-a") that come from catalog locations in the CI
130+
config map; `home-page-customization.spec.ts` needs the home-page card customization
131+
from `.ci/pipelines/resources/config_map/dynamic-plugins-config.yaml`. Enabling them
132+
means mirroring that data/config into the harness overlay.
116133
- **Live-external-service specs** (real k8s cluster, GitHub org, Quay, Tekton, Keycloak)
117134
still need those services or mocks; this harness covers UI/plugin-rendering scenarios
118135
that don't require live external infra.

e2e-tests/local-harness/dynamic-plugins.yaml

Lines changed: 119 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,122 @@ plugins:
1111
# Home page: route / -> DynamicHomePage, SearchBar, QuickAccessCard, Starred Entities.
1212
- package: oci://ghcr.io/redhat-developer/rhdh-plugin-export-overlays/red-hat-developer-hub-backstage-plugin-dynamic-home-page:bs_1.49.4__1.13.1!red-hat-developer-hub-backstage-plugin-dynamic-home-page
1313
disabled: false
14-
# TODO(follow-up): the global-header plugin (top bar + profile dropdown) loads off-cluster
15-
# but does not mount in the layout with the default config, so the Settings/Sign-out specs
16-
# can't run yet. Add it here once the header render config is sorted:
17-
# oci://ghcr.io/redhat-developer/rhdh-plugin-export-overlays/red-hat-developer-hub-backstage-plugin-global-header:bs_1.49.4__1.21.6!red-hat-developer-hub-backstage-plugin-global-header
14+
# Global header: top bar + profile dropdown (Settings / Sign-out navigation).
15+
#
16+
# The repo's static app-config.dynamic-plugins.yaml only mounts the bare GlobalHeader
17+
# container (no children), so the header renders empty off-cluster. In-cluster the full
18+
# config comes from the plugin's pluginConfig in the catalog index's
19+
# dynamic-plugins.default.yaml; the block below is that canonical config, copied from
20+
# rhdh-plugins workspaces/global-header/plugins/global-header/app-config.dynamic.yaml.
21+
# install-dynamic-plugins merges it into the generated
22+
# dynamic-plugins-root/app-config.dynamic-plugins.yaml, which the harness loads last —
23+
# exactly how the production container consumes it.
24+
- package: oci://ghcr.io/redhat-developer/rhdh-plugin-export-overlays/red-hat-developer-hub-backstage-plugin-global-header:bs_1.49.4__1.21.6!red-hat-developer-hub-backstage-plugin-global-header
25+
disabled: false
26+
pluginConfig:
27+
dynamicPlugins:
28+
frontend:
29+
red-hat-developer-hub.backstage-plugin-global-header:
30+
translationResources:
31+
- importName: globalHeaderTranslations
32+
ref: globalHeaderTranslationRef
33+
mountPoints:
34+
- mountPoint: application/header
35+
importName: GlobalHeader
36+
config:
37+
position: above-sidebar # above-main-content | above-sidebar
38+
- mountPoint: global.header/component
39+
importName: CompanyLogo
40+
config:
41+
priority: 200
42+
props:
43+
to: "/"
44+
- mountPoint: global.header/component
45+
importName: SearchComponent
46+
config:
47+
priority: 100
48+
- mountPoint: global.header/component
49+
importName: Spacer
50+
config:
51+
priority: 99
52+
props:
53+
growFactor: 0
54+
- mountPoint: global.header/component
55+
importName: HeaderIconButton
56+
config:
57+
priority: 90
58+
props:
59+
title: Self-service
60+
titleKey: create.title
61+
icon: add
62+
to: create
63+
- mountPoint: global.header/component
64+
importName: StarredDropdown
65+
config:
66+
priority: 85
67+
- mountPoint: global.header/component
68+
importName: ApplicationLauncherDropdown
69+
config:
70+
priority: 82
71+
- mountPoint: global.header/application-launcher
72+
importName: MenuItemLink
73+
config:
74+
section: Documentation
75+
priority: 150
76+
props:
77+
title: Developer Hub
78+
titleKey: applicationLauncher.developerHub
79+
icon: developerHub
80+
link: https://docs.redhat.com/en/documentation/red_hat_developer_hub
81+
- mountPoint: global.header/application-launcher
82+
importName: MenuItemLink
83+
config:
84+
section: Developer Tools
85+
priority: 100
86+
props:
87+
title: RHDH Local
88+
titleKey: applicationLauncher.rhdhLocal
89+
icon: developerHub
90+
link: https://github.com/redhat-developer/rhdh-local
91+
- mountPoint: global.header/component
92+
importName: HelpDropdown
93+
config:
94+
priority: 80
95+
- mountPoint: global.header/help
96+
importName: SupportButton
97+
config:
98+
priority: 10
99+
- mountPoint: global.header/component
100+
importName: NotificationButton
101+
config:
102+
priority: 70
103+
- mountPoint: global.header/component
104+
importName: Divider
105+
config:
106+
priority: 50
107+
- mountPoint: global.header/component
108+
importName: ProfileDropdown
109+
config:
110+
priority: 10
111+
- mountPoint: global.header/profile
112+
importName: MenuItemLink
113+
config:
114+
priority: 100
115+
props:
116+
title: Settings
117+
titleKey: profile.settings
118+
link: /settings
119+
icon: manageAccounts
120+
- mountPoint: global.header/profile
121+
importName: MenuItemLink
122+
config:
123+
priority: 90
124+
props:
125+
title: My profile
126+
titleKey: profile.myProfile
127+
type: myProfile
128+
icon: account
129+
- mountPoint: global.header/profile
130+
importName: LogoutButton
131+
config:
132+
priority: 10

e2e-tests/playwright.legacy-local.config.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,16 @@ const repoRootBin = resolve(process.cwd(), "..", "node_modules", ".bin");
2727
const pathWithRepoBin = `${repoRootBin}:${process.env.PATH ?? ""}`;
2828
const isCI = process.env.CI !== undefined && process.env.CI !== "";
2929

30+
// The last entry is generated by e2e-tests/local-harness/populate.sh: it carries the
31+
// pluginConfig blocks from the harness dynamic-plugins.yaml (e.g. the full global-header
32+
// mount points), merged by install-dynamic-plugins exactly like the production container
33+
// does. It must come after the static app-config.dynamic-plugins.yaml so its plugin
34+
// config wins. Re-run populate.sh after changing the harness plugin set.
3035
const sharedConfigArgs = [
3136
"--config ../../app-config.yaml",
3237
"--config ../../app-config.dynamic-plugins.yaml",
3338
"--config ../../app-config.local-e2e.yaml",
39+
"--config ../../dynamic-plugins-root/app-config.dynamic-plugins.yaml",
3440
].join(" ");
3541

3642
export default defineConfig({
@@ -40,12 +46,11 @@ export default defineConfig({
4046
// A test runs cluster-free when its spec file is listed in `testMatch` (an
4147
// allowlist, so unvalidated specs are never loaded) AND it carries the
4248
// @cluster-free tag. To widen coverage: tag the test where it lives and add its
43-
// spec file here. Today that is the guest-signin home-page test, verified green
44-
// off-cluster (the dynamic home-page plugin renders from OCI); its sibling
45-
// Settings/Sign-out tests stay untagged because they navigate via the top-right
46-
// profile dropdown, which needs the global-header plugin to *mount* in the
47-
// layout — it loads off-cluster but rendering it needs more config (follow-up).
48-
testMatch: ["e2e/guest-signin-happy-path.spec.ts"],
49+
// spec file here. Validated so far: the full guest-signin spec (home page via the
50+
// dynamic-home-page OCI plugin; Settings/Sign-out via the global-header OCI plugin
51+
// with its canonical pluginConfig) and the learning-paths spec (static fallback
52+
// data bundled with packages/app).
53+
testMatch: ["e2e/guest-signin-happy-path.spec.ts", "e2e/learning-path-page.spec.ts"],
4954
grep: /@cluster-free/u,
5055
timeout: 90 * 1000,
5156
forbidOnly: isCI,

e2e-tests/playwright/e2e/guest-signin-happy-path.spec.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,19 @@ test.describe("Guest Signing Happy path", () => {
3838
},
3939
);
4040

41-
test("Verify Profile is Guest in the Settings page", async () => {
41+
test("Verify Profile is Guest in the Settings page", { tag: "@cluster-free" }, async () => {
4242
await uiHelper.goToSettingsPage();
4343
await uiHelper.verifyHeading("Guest");
4444
await uiHelper.verifyHeading("User Entity: guest");
4545
});
4646

47-
test("Sign Out and Verify that you return to the Sign-in page", async () => {
48-
await uiHelper.goToSettingsPage();
49-
await common.signOut();
50-
await uiHelper.verifyHeading(t["rhdh"][lang]["signIn.page.title"]);
51-
});
47+
test(
48+
"Sign Out and Verify that you return to the Sign-in page",
49+
{ tag: "@cluster-free" },
50+
async () => {
51+
await uiHelper.goToSettingsPage();
52+
await common.signOut();
53+
await uiHelper.verifyHeading(t["rhdh"][lang]["signIn.page.title"]);
54+
},
55+
);
5256
});

e2e-tests/playwright/e2e/learning-path-page.spec.ts

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,24 @@ test.describe("Learning Paths", { tag: "@layer3-equivalent" }, () => {
2121
await common.loginAsGuest();
2222
});
2323

24-
test("Verify that links in Learning Paths for Backstage opens in a new tab", async ({
25-
page,
26-
}, testInfo) => {
27-
await uiHelper.openSidebarButton("References");
28-
await uiHelper.openSidebar("Learning Paths");
29-
30-
// Scope to main content area to get only Learning Path links
31-
const learningPathLinks = page.getByRole("main").getByRole("link");
32-
33-
for (const learningPathCard of await learningPathLinks.all()) {
34-
await expect(learningPathCard).toBeVisible();
35-
await expect(learningPathCard).toHaveAttribute("target", "_blank");
36-
await expect(learningPathCard).not.toHaveAttribute("href", "");
37-
}
38-
39-
await runAccessibilityTests(page, testInfo);
40-
});
24+
// @cluster-free: verified green on the cluster-free harness (playwright.legacy-local.config.ts)
25+
test(
26+
"Verify that links in Learning Paths for Backstage opens in a new tab",
27+
{ tag: "@cluster-free" },
28+
async ({ page }, testInfo) => {
29+
await uiHelper.openSidebarButton("References");
30+
await uiHelper.openSidebar("Learning Paths");
31+
32+
// Scope to main content area to get only Learning Path links
33+
const learningPathLinks = page.getByRole("main").getByRole("link");
34+
35+
for (const learningPathCard of await learningPathLinks.all()) {
36+
await expect(learningPathCard).toBeVisible();
37+
await expect(learningPathCard).toHaveAttribute("target", "_blank");
38+
await expect(learningPathCard).not.toHaveAttribute("href", "");
39+
}
40+
41+
await runAccessibilityTests(page, testInfo);
42+
},
43+
);
4144
});

0 commit comments

Comments
 (0)