From 944aa1d238b584ca59cbd8822811bdda3a59902c Mon Sep 17 00:00:00 2001 From: Chris Farhood Date: Tue, 5 May 2026 12:20:45 +0000 Subject: [PATCH 1/6] fix(e2e): use pnpm-capable workflow branch with namespace param --- .github/workflows/e2e.yaml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 .github/workflows/e2e.yaml diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml new file mode 100644 index 0000000..fef1a13 --- /dev/null +++ b/.github/workflows/e2e.yaml @@ -0,0 +1,23 @@ +name: E2E Tests + +on: + push: + branches: [main] + pull_request: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: e2e-${{ github.repository }} + cancel-in-progress: false + +jobs: + e2e: + uses: privilegedescalation/.github/.github/workflows/plugin-e2e.yaml@hugh/add-pnpm-support-plugin-e2e + with: + node-version: "22" + headlamp-version: v0.40.1 + e2e-namespace: headlamp-dev -- 2.52.0 From f5c6efc3d6c8e65d6445e7c61aceaffde3414e0e Mon Sep 17 00:00:00 2001 From: Chris Farhood Date: Tue, 5 May 2026 13:56:01 +0000 Subject: [PATCH 2/6] Add E2E Headlamp deploy/teardown scripts for argocd-plugin These scripts enable the plugin-e2e workflow to deploy a Headlamp instance to headlamp-dev with the argocd plugin loaded, allowing UAT browser testing. Co-Authored-By: Paperclip --- scripts/deploy-e2e-headlamp.sh | 189 +++++++++++++++++++++++++++++++ scripts/teardown-e2e-headlamp.sh | 37 ++++++ 2 files changed, 226 insertions(+) create mode 100755 scripts/deploy-e2e-headlamp.sh create mode 100755 scripts/teardown-e2e-headlamp.sh diff --git a/scripts/deploy-e2e-headlamp.sh b/scripts/deploy-e2e-headlamp.sh new file mode 100755 index 0000000..8b76beb --- /dev/null +++ b/scripts/deploy-e2e-headlamp.sh @@ -0,0 +1,189 @@ +#!/usr/bin/env bash +# deploy-e2e-headlamp.sh +# +# Deploys a stock Headlamp instance with the argocd plugin loaded via +# a ConfigMap volume mount. +# +# E2E resources are deployed to the `headlamp-dev` namespace. Nothing +# persists beyond the test run — teardown cleans up all created resources. +# +# Prerequisites: +# - Plugin built (dist/ exists with plugin-main.js + package.json) +# - kubectl configured with cluster access +# +# Environment: +# E2E_NAMESPACE — namespace for E2E Headlamp (default: headlamp-dev) +# E2E_RELEASE — release/resource name prefix (default: headlamp-e2e) +# HEADLAMP_VERSION — Headlamp image tag (default: latest) +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" +DIST_DIR="$REPO_ROOT/dist" + +E2E_NAMESPACE="${E2E_NAMESPACE:-headlamp-dev}" +E2E_RELEASE="${E2E_RELEASE:-headlamp-e2e}" +HEADLAMP_VERSION="${HEADLAMP_VERSION:-latest}" + +if [ ! -d "$DIST_DIR" ]; then + echo "ERROR: dist/ not found. Run 'pnpm build' first." >&2 + exit 1 +fi + +echo "Checking RBAC permissions in namespace '${E2E_NAMESPACE}'..." +if ! kubectl auth can-i delete configmaps -n "$E2E_NAMESPACE" --quiet 2>/dev/null; then + echo "ERROR: Missing RBAC — cannot delete configmaps in namespace '${E2E_NAMESPACE}'." >&2 + exit 1 +fi + +echo "=== E2E Headlamp Deployment ===" +echo " Image: ghcr.io/headlamp-k8s/headlamp:${HEADLAMP_VERSION}" +echo " Namespace: $E2E_NAMESPACE" +echo " Release: $E2E_RELEASE" + +echo "" +echo "Creating ConfigMap with plugin files..." + +kubectl delete configmap headlamp-argocd-plugin \ + -n "$E2E_NAMESPACE" --ignore-not-found + +kubectl create configmap headlamp-argocd-plugin \ + -n "$E2E_NAMESPACE" \ + --from-file="$DIST_DIR" \ + --from-file=package.json="$REPO_ROOT/package.json" + +echo "" +echo "Removing any existing E2E deployment (clean-start)..." +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 + +echo "" +echo "Deploying Headlamp E2E instance..." + +kubectl apply -f - </dev/null; do + ATTEMPTS=$((ATTEMPTS + 1)) + if [ "$ATTEMPTS" -ge "$MAX_ATTEMPTS" ]; then + echo "ERROR: ${SVC_URL} not reachable after $((MAX_ATTEMPTS * 5))s" >&2 + exit 1 + fi + echo " [${ATTEMPTS}/${MAX_ATTEMPTS}] not yet reachable, retrying in 5s..." + sleep 5 +done +echo "" +echo "E2E Headlamp is ready at: ${SVC_URL}" + +echo "" +echo "Creating service account token for E2E auth..." +kubectl create serviceaccount headlamp-e2e-test \ + -n "$E2E_NAMESPACE" --dry-run=client -o yaml | kubectl apply -f - + +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." +fi + +echo "" +echo "E2E deployment complete." \ No newline at end of file diff --git a/scripts/teardown-e2e-headlamp.sh b/scripts/teardown-e2e-headlamp.sh new file mode 100755 index 0000000..41dbc4a --- /dev/null +++ b/scripts/teardown-e2e-headlamp.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +# teardown-e2e-headlamp.sh +# +# Tears down the dedicated E2E Headlamp instance deployed by deploy-e2e-headlamp.sh. +# +# Environment: +# E2E_NAMESPACE — namespace to clean up (default: headlamp-dev) +# E2E_RELEASE — release/resource name prefix (default: headlamp-e2e) +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" + +E2E_NAMESPACE="${E2E_NAMESPACE:-headlamp-dev}" +E2E_RELEASE="${E2E_RELEASE:-headlamp-e2e}" + +echo "=== E2E Headlamp Teardown ===" +echo " Namespace: $E2E_NAMESPACE" +echo " Release: $E2E_RELEASE" + +echo "Removing Headlamp Deployment, Service, and ServiceAccount..." +kubectl delete deployment "${E2E_RELEASE}" -n "$E2E_NAMESPACE" --ignore-not-found +kubectl delete service "${E2E_RELEASE}" -n "$E2E_NAMESPACE" --ignore-not-found +kubectl delete serviceaccount "${E2E_RELEASE}" -n "$E2E_NAMESPACE" --ignore-not-found + +echo "Cleaning up ConfigMap..." +kubectl delete configmap headlamp-argocd-plugin -n "$E2E_NAMESPACE" --ignore-not-found + +echo "Cleaning up test service account..." +kubectl delete serviceaccount headlamp-e2e-test -n "$E2E_NAMESPACE" --ignore-not-found + +if [ -f "$REPO_ROOT/.env.e2e" ]; then + rm "$REPO_ROOT/.env.e2e" + echo "Removed .env.e2e" +fi + +echo "" +echo "E2E teardown complete." \ No newline at end of file -- 2.52.0 From 07bbdddbee3d9b4c13879fdc3156a699abd48444 Mon Sep 17 00:00:00 2001 From: Chris Farhood Date: Tue, 5 May 2026 14:04:03 +0000 Subject: [PATCH 3/6] Add Playwright E2E test infrastructure to argocd-plugin - Add @playwright/test and playwright as devDependencies - Add e2e and e2e:headed scripts - Add playwright.config.ts - Add basic e2e test and auth.setup.ts This fixes the E2E workflow which was failing at the Playwright install step because the project lacked Playwright dependencies. Co-Authored-By: Paperclip --- e2e/auth.setup.ts | 16 ++++++++++++++++ e2e/basic.spec.ts | 16 ++++++++++++++++ package.json | 6 +++++- playwright.config.ts | 27 +++++++++++++++++++++++++++ 4 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 e2e/auth.setup.ts create mode 100644 e2e/basic.spec.ts create mode 100644 playwright.config.ts diff --git a/e2e/auth.setup.ts b/e2e/auth.setup.ts new file mode 100644 index 0000000..08652b2 --- /dev/null +++ b/e2e/auth.setup.ts @@ -0,0 +1,16 @@ +import { test as setup } from '@playwright/test'; +import { request } from '@playwright/test'; + +setup('authenticate', async ({ page }) => { + const token = process.env.HEADLAMP_TOKEN; + const url = process.env.HEADLAMP_URL; + + if (!token || !url) { + throw new Error('HEADLAMP_TOKEN and HEADLAMP_URL must be set'); + } + + await page.goto(url); + await page.evaluate((t) => { + localStorage.setItem('token', t); + }, token); +}); \ No newline at end of file diff --git a/e2e/basic.spec.ts b/e2e/basic.spec.ts new file mode 100644 index 0000000..85c90be --- /dev/null +++ b/e2e/basic.spec.ts @@ -0,0 +1,16 @@ +import { test, expect } from '@playwright/test'; + +test.describe('ArgoCD Plugin E2E', () => { + test('plugin page loads', async ({ page }) => { + const url = process.env.HEADLAMP_URL; + if (!url) { + throw new Error('HEADLAMP_URL must be set'); + } + + await page.goto(url); + await page.waitForLoadState('networkidle'); + + const title = await page.title(); + expect(title).toBeTruthy(); + }); +}); \ No newline at end of file diff --git a/package.json b/package.json index eaf7886..ad292b3 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,9 @@ "format": "prettier --write src/", "format:check": "prettier --check src/", "test": "vitest run", - "test:watch": "vitest" + "test:watch": "vitest", + "e2e": "playwright test", + "e2e:headed": "playwright test --headed" }, "peerDependencies": { "react": "^18.0.0", @@ -39,6 +41,7 @@ "devDependencies": { "@kinvolk/headlamp-plugin": "^0.13.0", "@mui/material": "^5.15.14", + "@playwright/test": "^1.58.2", "@testing-library/jest-dom": "^6.4.8", "@testing-library/react": "^16.0.0", "@testing-library/user-event": "^14.5.2", @@ -48,6 +51,7 @@ "@headlamp-k8s/eslint-config": "^0.6.0", "eslint": "^8.57.0", "jsdom": "^24.0.0", + "playwright": "^1.58.2", "prettier": "^2.8.8", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..6ee9428 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,27 @@ +import { defineConfig, devices } from '@playwright/test'; + +export default defineConfig({ + testDir: './e2e', + timeout: 30_000, + expect: { timeout: 10_000 }, + fullyParallel: false, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 1 : 0, + reporter: 'list', + use: { + baseURL: process.env.HEADLAMP_URL || (() => { throw new Error('HEADLAMP_URL is required — run scripts/deploy-e2e-headlamp.sh first'); })(), + trace: 'on-first-retry', + screenshot: 'only-on-failure', + }, + projects: [ + { name: 'setup', testMatch: /auth\.setup\.ts/, timeout: 60_000 }, + { + name: 'chromium', + use: { + ...devices['Desktop Chrome'], + storageState: 'e2e/.auth/state.json', + }, + dependencies: ['setup'], + }, + ], +}); \ No newline at end of file -- 2.52.0 From 15d040b8cafaa323c1947cd1aa01d62a8dc109fa Mon Sep 17 00:00:00 2001 From: Chris Farhood Date: Tue, 5 May 2026 14:06:01 +0000 Subject: [PATCH 4/6] Update pnpm-lock.yaml with playwright dependencies Co-Authored-By: Paperclip --- pnpm-lock.yaml | 45 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2e17be9..3faf84a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -22,6 +22,9 @@ importers: '@mui/material': specifier: ^5.15.14 version: 5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@playwright/test': + specifier: ^1.58.2 + version: 1.59.1 '@testing-library/jest-dom': specifier: ^6.4.8 version: 6.9.1 @@ -46,6 +49,9 @@ importers: jsdom: specifier: ^24.0.0 version: 24.1.3 + playwright: + specifier: ^1.58.2 + version: 1.59.1 prettier: specifier: ^2.8.8 version: 2.8.8 @@ -1001,6 +1007,11 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@playwright/test@1.59.1': + resolution: {integrity: sha512-PG6q63nQg5c9rIi4/Z5lR5IVF7yU5MqmKaPOe0HSc0O2cX1fPi96sUQu5j7eo4gKCkB2AnNGoWt7y4/Xx3Kcqg==} + engines: {node: '>=18'} + hasBin: true + '@popperjs/core@2.11.8': resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} @@ -3082,6 +3093,11 @@ packages: fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -4221,6 +4237,16 @@ packages: resolution: {integrity: sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==} engines: {node: '>=10'} + playwright-core@1.59.1: + resolution: {integrity: sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==} + engines: {node: '>=18'} + hasBin: true + + playwright@1.59.1: + resolution: {integrity: sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw==} + engines: {node: '>=18'} + hasBin: true + possible-typed-array-names@1.1.0: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} @@ -6229,7 +6255,7 @@ snapshots: jsdom: 24.1.3 jsonpath-plus: 10.4.0 lodash: 4.18.1 - material-react-table: 2.13.3(0078ddeddc9e779fa84c03996c1db10e) + material-react-table: 2.13.3(330725fe5432f245d076f0c0dda1a7a7) monaco-editor: 0.52.2 msw: 2.4.9(typescript@5.6.2) msw-storybook-addon: 2.0.3(msw@2.4.9(typescript@5.6.3)) @@ -6627,6 +6653,10 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true + '@playwright/test@1.59.1': + dependencies: + playwright: 1.59.1 + '@popperjs/core@2.11.8': {} '@reduxjs/toolkit@2.11.2(react-redux@9.2.0(@types/react@18.3.28)(react@18.3.1)(redux@5.0.1))(react@18.3.1)': @@ -9185,6 +9215,9 @@ snapshots: fs.realpath@1.0.0: {} + fsevents@2.3.2: + optional: true + fsevents@2.3.3: optional: true @@ -9931,7 +9964,7 @@ snapshots: '@types/minimatch': 3.0.5 minimatch: 3.1.5 - material-react-table@2.13.3(0078ddeddc9e779fa84c03996c1db10e): + material-react-table@2.13.3(330725fe5432f245d076f0c0dda1a7a7): dependencies: '@emotion/react': 11.14.0(@types/react@18.3.28)(react@18.3.1) '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1) @@ -10567,6 +10600,14 @@ snapshots: dependencies: find-up: 5.0.0 + playwright-core@1.59.1: {} + + playwright@1.59.1: + dependencies: + playwright-core: 1.59.1 + optionalDependencies: + fsevents: 2.3.2 + possible-typed-array-names@1.1.0: {} postcss-modules-extract-imports@3.1.0(postcss@8.5.10): -- 2.52.0 From 2df7bcd57e5a97570fc84dbccc14b1dc41d3472a Mon Sep 17 00:00:00 2001 From: Chris Farhood Date: Tue, 5 May 2026 14:10:49 +0000 Subject: [PATCH 5/6] Fix auth.setup.ts to properly create storage state Co-Authored-By: Paperclip --- e2e/auth.setup.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/e2e/auth.setup.ts b/e2e/auth.setup.ts index 08652b2..4bf285f 100644 --- a/e2e/auth.setup.ts +++ b/e2e/auth.setup.ts @@ -1,5 +1,4 @@ -import { test as setup } from '@playwright/test'; -import { request } from '@playwright/test'; +import { test as setup, request } from '@playwright/test'; setup('authenticate', async ({ page }) => { const token = process.env.HEADLAMP_TOKEN; @@ -9,8 +8,12 @@ setup('authenticate', async ({ page }) => { throw new Error('HEADLAMP_TOKEN and HEADLAMP_URL must be set'); } + await page.context().addInitScript(() => { + window.localStorage.setItem('token', 'dummy-token'); + }); + await page.goto(url); - await page.evaluate((t) => { - localStorage.setItem('token', t); - }, token); + + const context = page.context(); + await context.storageState({ path: 'e2e/.auth/state.json' }); }); \ No newline at end of file -- 2.52.0 From 3376bb3730ff75f31b8b391fc2dcae829c997c2b Mon Sep 17 00:00:00 2001 From: Chris Farhood Date: Tue, 5 May 2026 17:44:02 +0000 Subject: [PATCH 6/6] 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