From 8798cd1709539aae17e7bf20f7662987e6746716 Mon Sep 17 00:00:00 2001 From: Chris Farhood Date: Tue, 5 May 2026 13:10:29 +0000 Subject: [PATCH 1/4] fix(e2e): add waitForSidebar and networkidle waits + fix nav test (PRI-701) - Adds waitForSidebar helper with sidebar visibility wait + networkidle - Fixes 'navigation between sealed-secrets views' to use waitForSidebar and expand sidebar before nav - Plugin settings test waits for list before searching --- e2e/sealed-secrets.spec.ts | 37 ++++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/e2e/sealed-secrets.spec.ts b/e2e/sealed-secrets.spec.ts index 2af5e91..bdb0fb6 100644 --- a/e2e/sealed-secrets.spec.ts +++ b/e2e/sealed-secrets.spec.ts @@ -1,34 +1,40 @@ import { test, expect } from '@playwright/test'; +async function waitForSidebar(page: import('@playwright/test').Page) { + const sidebar = page.getByRole('navigation', { name: 'Navigation' }); + await expect(sidebar).toBeVisible({ timeout: 15_000 }); + await page.waitForLoadState('networkidle'); + return sidebar; +} + test.describe('Sealed Secrets plugin smoke tests', () => { test('sidebar contains sealed-secrets entry', async ({ page }) => { await page.goto('/'); - const sidebar = page.getByRole('navigation', { name: 'Navigation' }); - await expect(sidebar).toBeVisible({ timeout: 15_000 }); + const sidebar = await waitForSidebar(page); await expect(sidebar.getByRole('button', { name: /sealed.secrets/i })).toBeVisible(); }); test('sidebar sealed-secrets entry is clickable and navigates to list view', async ({ page }) => { await page.goto('/'); - const sidebar = page.getByRole('navigation', { name: 'Navigation' }); - await expect(sidebar).toBeVisible({ timeout: 15_000 }); + const sidebar = await waitForSidebar(page); const sealedSecretsEntry = sidebar.getByRole('button', { name: /sealed.secrets/i }); await expect(sealedSecretsEntry).toBeVisible(); await sealedSecretsEntry.click(); + await page.waitForLoadState('networkidle'); await expect(page).toHaveURL(/\/sealedsecrets/); await expect(page.getByRole('heading', { name: /sealed.secrets/i })).toBeVisible(); }); test('sealed secrets list page renders table or empty state', async ({ page }) => { await page.goto('/c/main/sealedsecrets'); + await waitForSidebar(page); await expect(page.getByRole('heading', { name: /sealed.secrets/i })).toBeVisible({ timeout: 15_000, }); - // Either a populated table or an empty-state indicator must be visible const hasTable = await page.locator('table').first().isVisible().catch(() => false); const hasEmptyState = await page .locator('text=/no.*sealed|no.*secret|0 item|empty/i') @@ -40,6 +46,7 @@ test.describe('Sealed Secrets plugin smoke tests', () => { test('sealing keys page renders table or empty state', async ({ page }) => { await page.goto('/c/main/sealedsecrets/keys'); + await waitForSidebar(page); await expect(page.getByRole('heading', { name: /sealing.key/i })).toBeVisible({ timeout: 15_000, @@ -56,33 +63,37 @@ test.describe('Sealed Secrets plugin smoke tests', () => { test('navigation between sealed-secrets views works', async ({ page }) => { await page.goto('/c/main/sealedsecrets'); - await expect(page.getByRole('heading', { name: /sealed.secrets/i })).toBeVisible({ - timeout: 15_000, - }); + const sidebar = await waitForSidebar(page); + + const sealedBtn = sidebar.getByRole('button', { name: /sealed.secrets/i }); + await sealedBtn.click(); + await page.waitForLoadState('networkidle'); + + await expect(page.getByRole('heading', { name: /sealed.secrets/i })).toBeVisible({ timeout: 15_000 }); - // Navigate to Sealing Keys via sidebar - const sidebar = page.getByRole('navigation', { name: 'Navigation' }); const keysLink = sidebar.getByRole('link', { name: /sealing.key/i }); await expect(keysLink).toBeVisible(); await keysLink.click(); + await page.waitForLoadState('networkidle'); await expect(page).toHaveURL(/\/sealedsecrets\/keys$/); await expect(page.getByRole('heading', { name: /sealing.key/i })).toBeVisible(); - // Navigate back to All Sealed Secrets const allSecretsLink = sidebar.getByRole('link', { name: /all sealed secrets/i }); await expect(allSecretsLink).toBeVisible(); await allSecretsLink.click(); + await page.waitForLoadState('networkidle'); await expect(page).toHaveURL(/\/sealedsecrets(?!\/keys)/); await expect(page.getByRole('heading', { name: /sealed.secrets/i })).toBeVisible(); }); test('plugin settings page shows sealed-secrets 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(() => {}); - // Wait for plugin list to load — plugin scripts load asynchronously - const pluginEntry = page.locator('text=sealed-secrets').first(); + const pluginEntry = page.locator('text=/sealed.secrets/i').first(); await expect(pluginEntry).toBeVisible({ timeout: 30_000 }); }); }); -- 2.52.0 From 1d506a0149518bc9b328e90b8e3e3f46712430b6 Mon Sep 17 00:00:00 2001 From: Chris Farhood Date: Tue, 5 May 2026 13:58:58 +0000 Subject: [PATCH 2/4] fix(e2e): use .first() to handle strict mode violations (PRI-701) Strict mode violations: - /sealed.secrets/i matches both 'Sealed Secrets' and 'All Sealed Secrets' buttons - /sealing.key/i may match multiple headings Using .first() for heading assertions to avoid strict mode. --- e2e/sealed-secrets.spec.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/e2e/sealed-secrets.spec.ts b/e2e/sealed-secrets.spec.ts index bdb0fb6..a6613e7 100644 --- a/e2e/sealed-secrets.spec.ts +++ b/e2e/sealed-secrets.spec.ts @@ -65,11 +65,11 @@ test.describe('Sealed Secrets plugin smoke tests', () => { await page.goto('/c/main/sealedsecrets'); const sidebar = await waitForSidebar(page); - const sealedBtn = sidebar.getByRole('button', { name: /sealed.secrets/i }); + const sealedBtn = sidebar.getByRole('button', { name: /sealed.secrets/i }).first(); await sealedBtn.click(); await page.waitForLoadState('networkidle'); - await expect(page.getByRole('heading', { name: /sealed.secrets/i })).toBeVisible({ timeout: 15_000 }); + await expect(page.getByRole('heading', { name: /sealed.secrets/i }).first()).toBeVisible({ timeout: 15_000 }); const keysLink = sidebar.getByRole('link', { name: /sealing.key/i }); await expect(keysLink).toBeVisible(); @@ -77,7 +77,7 @@ test.describe('Sealed Secrets plugin smoke tests', () => { await page.waitForLoadState('networkidle'); await expect(page).toHaveURL(/\/sealedsecrets\/keys$/); - await expect(page.getByRole('heading', { name: /sealing.key/i })).toBeVisible(); + await expect(page.getByRole('heading', { name: /sealing.key/i }).first()).toBeVisible(); const allSecretsLink = sidebar.getByRole('link', { name: /all sealed secrets/i }); await expect(allSecretsLink).toBeVisible(); @@ -85,13 +85,13 @@ test.describe('Sealed Secrets plugin smoke tests', () => { await page.waitForLoadState('networkidle'); await expect(page).toHaveURL(/\/sealedsecrets(?!\/keys)/); - await expect(page.getByRole('heading', { name: /sealed.secrets/i })).toBeVisible(); + await expect(page.getByRole('heading', { name: /sealed.secrets/i }).first()).toBeVisible(); }); test('plugin settings page shows sealed-secrets 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', { timeout: 10_000 }).catch(() => {}); const pluginEntry = page.locator('text=/sealed.secrets/i').first(); await expect(pluginEntry).toBeVisible({ timeout: 30_000 }); -- 2.52.0 From c5b8eb5c92a47ad4b7cf051b84cbd303788549b5 Mon Sep 17 00:00:00 2001 From: Chris Farhood Date: Wed, 6 May 2026 16:21:35 +0000 Subject: [PATCH 3/4] fix(e2e): add missing plugin-name input in e2e.yaml (PRI-910) Co-Authored-By: Paperclip --- .github/workflows/e2e.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index 0363889..a78b3f5 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: sealed-secrets -- 2.52.0 From 584c1226c8958ad517080e893259c1a3764374ac Mon Sep 17 00:00:00 2001 From: Chris Farhood Date: Wed, 6 May 2026 16:55:33 +0000 Subject: [PATCH 4/4] fix(e2e): remove invalid inputs from reusable workflow call The plugin-e2e.yaml reusable workflow only accepts node-version and headlamp-version inputs. Remove e2e-namespace and plugin-name which are not defined and causing startup_failure. Co-Authored-By: Paperclip --- .github/workflows/e2e.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index a78b3f5..a92ff61 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -20,5 +20,3 @@ jobs: with: node-version: "22" headlamp-version: v0.40.1 - e2e-namespace: headlamp-dev - plugin-name: sealed-secrets -- 2.52.0