From 15d161c3124d1e9a12ed09bab1cc7704070047fc Mon Sep 17 00:00:00 2001 From: Chris Farhood Date: Tue, 5 May 2026 12:21:22 +0000 Subject: [PATCH 1/8] fix(e2e): use pnpm-capable workflow branch with namespace param --- .github/workflows/e2e.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index 4ee85a4..fef1a13 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -18,6 +18,6 @@ jobs: e2e: uses: privilegedescalation/.github/.github/workflows/plugin-e2e.yaml@hugh/add-pnpm-support-plugin-e2e with: - node-version: '22' + node-version: "22" headlamp-version: v0.40.1 e2e-namespace: headlamp-dev -- 2.52.0 From 61df61c691868d6e587e4866e1575d01d0d2d253 Mon Sep 17 00:00:00 2001 From: Chris Farhood Date: Tue, 5 May 2026 13:04:45 +0000 Subject: [PATCH 2/8] fix(e2e): expand storage classes sidebar nav and relax plugin settings locator The 'Storage Classes' link is nested under the Rook sidebar button, not at the top level. Expand the Rook section before asserting visibility. Also uses /rook/i case-insensitive regex and waits for the plugins list to render before searching. --- e2e/rook.spec.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/e2e/rook.spec.ts b/e2e/rook.spec.ts index bb6f15d..cdf26f6 100644 --- a/e2e/rook.spec.ts +++ b/e2e/rook.spec.ts @@ -42,8 +42,12 @@ test.describe('Rook plugin smoke tests', () => { test('navigation to storage classes view works', async ({ page }) => { await page.goto('/c/main/rook-ceph'); - const sidebar = page.getByRole('navigation', { name: 'Navigation' }); + + const rookBtn = sidebar.getByRole('button', { name: /rook/i }); + await rookBtn.click(); + await page.waitForLoadState('networkidle'); + const storageClassesLink = sidebar.getByRole('link', { name: /storage classes/i }); await expect(storageClassesLink).toBeVisible({ timeout: 10_000 }); await storageClassesLink.click(); @@ -56,8 +60,9 @@ test.describe('Rook plugin smoke tests', () => { test('plugin settings page shows rook plugin entry', async ({ page }) => { await page.goto('/settings/plugins'); await page.waitForLoadState('networkidle'); + await page.waitForSelector('[class*="PluginList"], [class*="plugins"], table, list', { timeout: 10_000 }).catch(() => {}); - const pluginEntry = page.locator('text=rook').first(); + const pluginEntry = page.locator('text=/rook/i').first(); await expect(pluginEntry).toBeVisible({ timeout: 30_000 }); }); }); -- 2.52.0 From 0d9f9d859a35cae5650b244d8dc62a04ae951f41 Mon Sep 17 00:00:00 2001 From: Chris Farhood Date: Tue, 5 May 2026 13:56:35 +0000 Subject: [PATCH 3/8] fix(e2e): use .first() to handle strict mode violations (PRI-699) Similar to the kube-vip fix, /overview/i and /storage class/i can match multiple headings. Using .first() to avoid strict mode violations. --- e2e/rook.spec.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/e2e/rook.spec.ts b/e2e/rook.spec.ts index cdf26f6..1b3d556 100644 --- a/e2e/rook.spec.ts +++ b/e2e/rook.spec.ts @@ -24,14 +24,14 @@ test.describe('Rook plugin smoke tests', () => { await page.waitForLoadState('networkidle'); await expect(page).toHaveURL(/rook-ceph/); - await expect(page.getByRole('heading', { name: /overview/i })).toBeVisible(); + await expect(page.getByRole('heading', { name: /overview/i }).first()).toBeVisible(); }); test('overview page renders content', async ({ page }) => { await page.goto('/c/main/rook-ceph'); await waitForSidebar(page); - await expect(page.getByRole('heading', { name: /overview/i })).toBeVisible({ + await expect(page.getByRole('heading', { name: /overview/i }).first()).toBeVisible({ timeout: 15_000, }); @@ -54,13 +54,13 @@ test.describe('Rook plugin smoke tests', () => { await page.waitForLoadState('networkidle'); await expect(page).toHaveURL(/rook-ceph\/storage-classes/); - await expect(page.getByRole('heading', { name: /storage class/i })).toBeVisible({ timeout: 15_000 }); + await expect(page.getByRole('heading', { name: /storage class/i }).first()).toBeVisible({ timeout: 15_000 }); }); test('plugin settings page shows rook plugin entry', async ({ page }) => { await page.goto('/settings/plugins'); await page.waitForLoadState('networkidle'); - await page.waitForSelector('[class*="PluginList"], [class*="plugins"], table, list', { timeout: 10_000 }).catch(() => {}); + await page.waitForSelector('table, [class*="PluginList"], [class*="plugin"]', { timeout: 10_000 }).catch(() => {}); const pluginEntry = page.locator('text=/rook/i').first(); await expect(pluginEntry).toBeVisible({ timeout: 30_000 }); -- 2.52.0 From 3f93e71f28df712f99488757f73a6940a3c09056 Mon Sep 17 00:00:00 2001 From: Chris Farhood Date: Tue, 5 May 2026 17:43:54 +0000 Subject: [PATCH 4/8] fix(e2e): reference @main workflow after .github merge Co-Authored-By: Paperclip --- .github/workflows/e2e.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index fef1a13..0363889 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -16,7 +16,7 @@ concurrency: jobs: e2e: - uses: privilegedescalation/.github/.github/workflows/plugin-e2e.yaml@hugh/add-pnpm-support-plugin-e2e + uses: privilegedescalation/.github/.github/workflows/plugin-e2e.yaml@main with: node-version: "22" headlamp-version: v0.40.1 -- 2.52.0 From 540f0a7890fe92ddf97fe4a5351fdebe1dfd4868 Mon Sep 17 00:00:00 2001 From: Chris Farhood Date: Wed, 6 May 2026 12:36:08 +0000 Subject: [PATCH 5/8] fix e2e: add missing plugin-name input to plugin-e2e workflow The rook plugin E2E workflow was calling the reusable plugin-e2e workflow without the required plugin-name input. This caused the ConfigMap naming and mount path to fall back to the headlamp-kube-vip defaults, breaking E2E runs for the rook plugin. Fix: pass plugin-name: rook to the reusable workflow. --- .github/workflows/e2e.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index 0363889..90a5fb8 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -21,3 +21,4 @@ jobs: node-version: "22" headlamp-version: v0.40.1 e2e-namespace: headlamp-dev + plugin-name: rook -- 2.52.0 From 6c6cfc88f40fea3eeb8ebe56dd7196ad6be33bb8 Mon Sep 17 00:00:00 2001 From: Chris Farhood Date: Wed, 6 May 2026 18:30:43 +0000 Subject: [PATCH 6/8] fix(e2e): add cluster diagnostics to deploy step for faster triage Add pre-deployment node/namespace/resource diagnostics and wrap kubectl apply in explicit error handling with cluster state dump on failure. This gives us actionable output in the GitHub Actions logs when the Deploy E2E step fails, instead of a silent exit code. PRI-956 Co-Authored-By: Paperclip --- scripts/deploy-e2e-headlamp.sh | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/scripts/deploy-e2e-headlamp.sh b/scripts/deploy-e2e-headlamp.sh index 30edb91..df4ab68 100755 --- a/scripts/deploy-e2e-headlamp.sh +++ b/scripts/deploy-e2e-headlamp.sh @@ -35,6 +35,17 @@ if ! kubectl auth can-i delete configmaps -n "$E2E_NAMESPACE" --quiet 2>/dev/nul exit 1 fi +echo "" +echo "=== Pre-deployment cluster diagnostics ===" +echo "Nodes:" +kubectl get nodes -o wide 2>&1 || true +echo "" +echo "headlamp-dev namespace state:" +kubectl get ns headlamp-dev -o yaml 2>&1 || true +echo "" +echo "Existing E2E resources in namespace:" +kubectl get all -n "$E2E_NAMESPACE" -l "app.kubernetes.io/instance=$E2E_RELEASE" 2>&1 || true + echo "=== E2E Headlamp Deployment ===" echo " Image: ghcr.io/headlamp-k8s/headlamp:${HEADLAMP_VERSION}" echo " Namespace: $E2E_NAMESPACE" @@ -60,7 +71,7 @@ kubectl delete serviceaccount "${E2E_RELEASE}" -n "$E2E_NAMESPACE" --ignore-not- echo "" echo "Deploying Headlamp E2E instance..." -kubectl apply -f - <&2 + kubectl get all -n "$E2E_NAMESPACE" 2>&1 || true + kubectl get events -n "$E2E_NAMESPACE" --sort-by='.lastTimestamp' 2>&1 | tail -30 || true + exit 1 +fi echo "Waiting for rollout..." kubectl rollout status "deployment/${E2E_RELEASE}" \ -- 2.52.0 From 8f8c48522845e3ae66a2b3de879aac93b7d0d5a8 Mon Sep 17 00:00:00 2001 From: Chris Farhood Date: Tue, 5 May 2026 16:12:03 +0000 Subject: [PATCH 7/8] fix(e2e): add cluster-scoped RBAC for E2E service account The headlamp-e2e-test service account needs cluster-wide read permissions for storageclasses, cephclusters, persistentvolumes, and persistentvolumeclaims so the Rook plugin sidebar can populate these resources without errors. - Add ClusterRole headlamp-e2e-test-reader with get/list/watch on storageclasses, cephclusters, cephclusters/status, persistentvolumes, persistentvolumeclaims - Add ClusterRoleBinding headlamp-e2e-test-crb binding the role to the headlamp-e2e-test service account - Update teardown to also clean up the ClusterRole and ClusterRoleBinding Fixes: PRI-741 Co-Authored-By: Paperclip --- scripts/deploy-e2e-headlamp.sh | 48 ++++++++++++++++++++++++++++++-- scripts/teardown-e2e-headlamp.sh | 4 ++- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/scripts/deploy-e2e-headlamp.sh b/scripts/deploy-e2e-headlamp.sh index df4ab68..8c68562 100755 --- a/scripts/deploy-e2e-headlamp.sh +++ b/scripts/deploy-e2e-headlamp.sh @@ -64,9 +64,54 @@ kubectl create configmap headlamp-rook-plugin \ echo "" echo "Removing any existing E2E deployment (clean-start)..." +kubectl delete clusterrolebinding headlamp-e2e-test-crb --ignore-not-found 2>/dev/null || true kubectl delete deployment "${E2E_RELEASE}" -n "$E2E_NAMESPACE" --ignore-not-found --wait kubectl delete service "${E2E_RELEASE}" -n "$E2E_NAMESPACE" --ignore-not-found --wait kubectl delete serviceaccount "${E2E_RELEASE}" -n "$E2E_NAMESPACE" --ignore-not-found --wait +kubectl delete serviceaccount headlamp-e2e-test -n "$E2E_NAMESPACE" --ignore-not-found 2>/dev/null || true + +echo "" +echo "Creating E2E service account..." +kubectl create serviceaccount headlamp-e2e-test -n "$E2E_NAMESPACE" + +echo "" +echo "Creating RBAC for E2E service account..." +kubectl apply -f - </dev/null || echo "") if [ -n "$TOKEN" ]; then echo "HEADLAMP_URL=${SVC_URL}" > "$REPO_ROOT/.env.e2e" diff --git a/scripts/teardown-e2e-headlamp.sh b/scripts/teardown-e2e-headlamp.sh index 218d74b..28063df 100755 --- a/scripts/teardown-e2e-headlamp.sh +++ b/scripts/teardown-e2e-headlamp.sh @@ -25,8 +25,10 @@ kubectl delete serviceaccount "${E2E_RELEASE}" -n "$E2E_NAMESPACE" --ignore-not- echo "Cleaning up ConfigMap..." kubectl delete configmap headlamp-rook-plugin -n "$E2E_NAMESPACE" --ignore-not-found -echo "Cleaning up test service account..." +echo "Cleaning up test service account and RBAC..." kubectl delete serviceaccount headlamp-e2e-test -n "$E2E_NAMESPACE" --ignore-not-found +kubectl delete clusterrolebinding headlamp-e2e-test-crb --ignore-not-found 2>/dev/null || true +kubectl delete clusterrole headlamp-e2e-test-reader --ignore-not-found 2>/dev/null || true if [ -f "$REPO_ROOT/.env.e2e" ]; then rm "$REPO_ROOT/.env.e2e" -- 2.52.0 From 83a453a0f0356d5ce091e70e6c83ffc33fe941e1 Mon Sep 17 00:00:00 2001 From: Chris Farhood Date: Wed, 6 May 2026 13:33:41 +0000 Subject: [PATCH 8/8] fix(e2e): write HEADLAMP_URL before token gen; add pods RBAC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix two bugs from PRI-879 QA review: - HEADLAMP_URL is now written to .env.e2e unconditionally, before attempting token generation. Previously it was only written when token generation succeeded, causing tests to fail if the token command errored. - ClusterRole headlamp-e2e-test-reader now includes pods get/list/watch so the Rook PodsPage can populate without permission errors. Does not address the popup race in auth.setup.ts — that file was not changed because the popup race claim in PRI-879 does not match the actual code order. The popupPromise (line 9) is already captured before the click (line 10) in the source file. Fixes: PRI-879 Co-Authored-By: Paperclip --- scripts/deploy-e2e-headlamp.sh | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/scripts/deploy-e2e-headlamp.sh b/scripts/deploy-e2e-headlamp.sh index 8c68562..2b33bb5 100755 --- a/scripts/deploy-e2e-headlamp.sh +++ b/scripts/deploy-e2e-headlamp.sh @@ -98,6 +98,9 @@ rules: - apiGroups: [""] resources: ["persistentvolumeclaims"] verbs: ["get", "list", "watch"] + - apiGroups: [""] + resources: ["pods"] + verbs: ["get", "list", "watch"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding @@ -233,15 +236,18 @@ done echo "" echo "E2E Headlamp is ready at: ${SVC_URL}" +echo "" +echo "Writing E2E env file..." +echo "HEADLAMP_URL=${SVC_URL}" > "$REPO_ROOT/.env.e2e" + echo "" echo "Creating service account token for E2E auth..." TOKEN=$(kubectl create token headlamp-e2e-test -n "$E2E_NAMESPACE" --duration=1h 2>/dev/null || echo "") if [ -n "$TOKEN" ]; then - echo "HEADLAMP_URL=${SVC_URL}" > "$REPO_ROOT/.env.e2e" echo "HEADLAMP_TOKEN=${TOKEN}" >> "$REPO_ROOT/.env.e2e" echo "Wrote .env.e2e with HEADLAMP_URL and HEADLAMP_TOKEN" else - echo " WARNING: Could not generate token." + echo "Wrote .env.e2e with HEADLAMP_URL only (token generation failed, using OIDC fallback)" fi echo "" -- 2.52.0