Spike deliverable for RHIDP-13501 — E2E Test Optimization (Layer 4a), building on the PoC in PR #4523 and the backend dynamic-plugin loader from RHIDP-13508.
Run real Playwright E2E against RHDH without an OpenShift/Kubernetes cluster or
container images — a single run that boots the backend and the legacy frontend dev
server in-process and drives a browser against them.
The harness targets the legacy frontend (packages/app, Tier B): it is what RHDH ships
today, and the existing Playwright specs already target it, so they run unmodified.
Dynamic frontend plugins load through Scalprum exactly as in-cluster (the legacy
scalprum-backend serves the plugin config by default).
The guest-auth + in-memory-SQLite overlay app-config.local-e2e.yaml is layered on top
of app-config.yaml. Guest sign-in must be configured explicitly — the auth backend
otherwise rejects guest with "you must … configure the auth backend to support guest
sign in."
Production-faithful — full plugin set and generated config, the same source CI uses:
# main branch -> :latest; release branches -> the matching :1.y tag
CATALOG_INDEX_IMAGE=quay.io/rhdh/plugin-catalog-index:latest \
npx @red-hat-developer-hub/cli-module-install-dynamic-plugins install dynamic-plugins-rootOffline alternative (frontend plugins only; requires a reconciled workspace — see "Known issues"):
yarn --cwd dynamic-plugins export-dynamic
yarn --cwd dynamic-plugins copy-dynamic-plugins ../dynamic-plugins-rootyarn --cwd e2e-tests e2e:legacy-localPlaywright (playwright.legacy-local.config.ts) boots the backend and the legacy app
dev server with app-config.yaml + app-config.dynamic-plugins.yaml +
app-config.local-e2e.yaml. A globalSetup first fails fast with the populate command
if dynamic-plugins-root is empty.
By default the run is scoped (via grep) to the one test verified green off-cluster so
far — the guest-signin-happy-path home-page test. Widen testMatch/grep as more
specs are validated (see "Known issues").
With plugins populated, the legacy app renders the full production RHDH UI off-cluster
(branding, sidebar, and Quick Access from the dynamic home-page plugin). The existing
guest-signin-happy-path home-page test passes unmodified — confirming a dynamic
frontend plugin renders with no cluster.
.github/workflows/e2e-cluster-free.yaml runs this harness on GitHub Actions in a
cluster-free phase: it installs deps + skopeo, populates dynamic-plugins-root from the
public catalog index via the install-dynamic-plugins CLI (the same mechanism the
nightly sanity check uses), then runs yarn e2e:legacy-local. No cluster or container
image is built. It triggers on e2e-tests/** and app-config*.yaml changes; the scope
can widen to packages/app/** / packages/backend/** once it is proven stable.
The harness targets the legacy app because dynamic frontend plugins do not load on
packages/app-next yet: app-next's dynamicFrontendFeaturesLoader() fetches Module
Federation remotes from the backend, but that endpoint is no-op'd unless
ENABLE_STANDARD_MODULE_FEDERATION=true, and even then RHDH's exported dynamic frontend
plugins do not contain standard MF assets (see packages/backend/src/index.ts). Until
that lands upstream, app-next can only exercise core/static plugin UIs. An app-next
harness is tracked as a follow-up (RHIDP-13501 / spike RHIDP-15075).
rhdh-local runs RHDH via
Podman/Docker Compose using the production container image. It is great for manual
feature testing with guest auth and UI-installed plugins, but it is container-based:
it requires a container runtime and pulling/running the RHDH image. For fast automated
E2E it is heavier than this in-process harness (no image pull, no container runtime —
just run), which is why this harness boots the dev servers directly instead.
- Workspace must be reconciled for the offline (from-source) populate path. If
node_modulesis out of sync withyarn.lock(e.g. just after a rebase that changed dependency versions), backend dynamic-plugin builds fail with version-mismatch errors and yarn may not surface workspace bins. Runyarn installfirst. Theinstall-dynamic-pluginspopulate path avoids building from source and is unaffected. global-headerplugin mounting still needs config sorting for the legacy harness; specs that navigate via the top-right profile dropdown depend on it.- Live-external-service specs (real k8s cluster, GitHub org, Quay, Tekton, Keycloak) still need those services or mocks; this harness covers UI/plugin-rendering scenarios that don't require live external infra.
janus-cli/backstage-clilive in the repo-rootnode_modules/.bin, which yarn does not surface for theapp/backendworkspaces, so the webServer commands invoke them directly with the root.binprepended toPATH.