Compare commits

..

6 Commits

Author SHA1 Message Date
Chris Farhood b9ca87aa05 fix: update pnpm-lock.yaml for @playwright/test (PRI-726) 2026-05-06 14:08:04 +00:00
Chris Farhood 28bf57060b fix: restore pnpm.overrides from main to fix ERR_PNPM_LOCKFILE_CONFIG_MISMATCH (PRI-726) 2026-05-06 14:03:56 +00:00
Chris Farhood b4600d07d8 Add E2E workflow to headlamp-argocd-plugin
- Adds E2E workflow with deploy/teardown scripts
- Adds Playwright smoke tests for ArgoCD sidebar and app list
- Uses unique release name to avoid collision with shared runner
2026-05-06 13:37:34 +00:00
privilegedescalation-ceo[bot] dedf6538c7 Merge pull request #26 from privilegedescalation/fix/elliptic-vulnerability-override
fix: override elliptic to patched version for GHSA-848j-6mx2-7j84
2026-05-05 18:40:42 +00:00
Chris Farhood 0af4939d8e chore: update pnpm lockfile for elliptic override
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-05 12:58:43 +00:00
Chris Farhood c24e96da97 fix: override elliptic to patched version for GHSA-848j-6mx2-7j84 2026-05-05 12:51:05 +00:00
9 changed files with 196 additions and 91 deletions
+132 -5
View File
@@ -14,10 +14,137 @@ concurrency:
group: e2e-${{ github.repository }}
cancel-in-progress: false
env:
E2E_NAMESPACE: headlamp-dev
E2E_RELEASE: headlamp-e2e-argocd
HEADLAMP_VERSION: v0.40.1
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
runs-on: runners-privilegedescalation
timeout-minutes: 15
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Detect package manager
id: pkg-manager
run: |
if [ -f "pnpm-lock.yaml" ]; then
echo "manager=pnpm" >> $GITHUB_OUTPUT
PM=$(python3 -c "import json,sys; d=json.load(open('package.json')); print('true' if d.get('packageManager','').startswith('pnpm@') else 'false')" 2>/dev/null || echo "false")
echo "has_package_manager=$PM" >> $GITHUB_OUTPUT
else
echo "manager=npm" >> $GITHUB_OUTPUT
echo "has_package_manager=false" >> $GITHUB_OUTPUT
fi
- name: Setup Node
uses: actions/setup-node@v6
with:
node-version: '22'
cache: ${{ steps.pkg-manager.outputs.manager == 'npm' && 'npm' || '' }}
- name: Setup pnpm (Corepack, respects packageManager field)
if: steps.pkg-manager.outputs.manager == 'pnpm' && steps.pkg-manager.outputs.has_package_manager == 'true'
run: |
npm install -g corepack
corepack enable pnpm
corepack prepare $(node -p "require('./package.json').packageManager") --activate
- name: Setup pnpm (version latest, no packageManager field)
if: steps.pkg-manager.outputs.manager == 'pnpm' && steps.pkg-manager.outputs.has_package_manager == 'false'
uses: pnpm/action-setup@v5
with:
run_install: false
version: latest
- name: Get pnpm store directory
id: pnpm-store
if: steps.pkg-manager.outputs.manager == 'pnpm'
run: echo "dir=$(pnpm store path --silent)" >> $GITHUB_OUTPUT
- name: Cache pnpm store
if: steps.pkg-manager.outputs.manager == 'pnpm'
uses: actions/cache@v5
with:
path: ${{ steps.pnpm-store.outputs.dir }}
key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-
- name: Setup kubectl
uses: azure/setup-kubectl@v4
- name: Install dependencies
run: |
if [ "${{ steps.pkg-manager.outputs.manager }}" = "pnpm" ]; then
pnpm install --frozen-lockfile
else
npm ci
fi
- name: Build plugin
run: npx @kinvolk/headlamp-plugin build
- name: Deploy E2E Headlamp instance
run: scripts/deploy-e2e-headlamp.sh
- name: Load E2E environment
run: |
if [ -f .env.e2e ]; then
cat .env.e2e >> "$GITHUB_ENV"
else
echo "::error::deploy-e2e-headlamp.sh did not produce .env.e2e"
exit 1
fi
- name: Install Playwright browsers
run: |
if [ "${{ steps.pkg-manager.outputs.manager }}" = "pnpm" ]; then
pnpm exec playwright install --with-deps chromium
else
npx playwright install --with-deps chromium
fi
- name: Run E2E tests
run: |
if [ "${{ steps.pkg-manager.outputs.manager }}" = "pnpm" ]; then
pnpm run e2e
else
npm run e2e
fi
env:
HEADLAMP_URL: ${{ env.HEADLAMP_URL }}
HEADLAMP_TOKEN: ${{ env.HEADLAMP_TOKEN }}
- name: Collect deployment diagnostics on failure
if: failure()
run: |
echo "=== Pod state ==="
kubectl get pods -n "$E2E_NAMESPACE" -l "app.kubernetes.io/instance=$E2E_RELEASE" 2>&1 || true
echo "=== Pod describe ==="
kubectl describe pods -n "$E2E_NAMESPACE" -l "app.kubernetes.io/instance=$E2E_RELEASE" 2>&1 || true
echo "=== Recent namespace events ==="
kubectl get events -n "$E2E_NAMESPACE" --sort-by='.lastTimestamp' 2>&1 | tail -20 || true
- name: Teardown E2E instance
if: always()
run: scripts/teardown-e2e-headlamp.sh
- name: Upload Playwright report
uses: actions/upload-artifact@v7
if: failure()
with:
name: playwright-report
path: playwright-report/
retention-days: 7
- name: Upload test results
uses: actions/upload-artifact@v7
if: failure()
with:
name: test-results
path: test-results/
retention-days: 7
+18
View File
@@ -0,0 +1,18 @@
import { test, expect } from '@playwright/test';
test.describe('ArgoCD plugin smoke tests', () => {
test('sidebar contains ArgoCD entry', async ({ page }) => {
await page.goto('/');
const sidebar = page.getByRole('navigation', { name: 'Navigation' });
await expect(sidebar).toBeVisible({ timeout: 15_000 });
await expect(sidebar.getByRole('button', { name: 'ArgoCD' })).toBeVisible();
});
test('applications list page loads', async ({ page }) => {
await page.goto('/c/main/argocd');
await expect(
page.getByRole('heading', { name: /argo.*cd/i })
).toBeVisible({ timeout: 15_000 });
});
});
+26 -11
View File
@@ -1,19 +1,34 @@
import { test as setup, request } from '@playwright/test';
import { test as setup, expect, Page } from '@playwright/test';
setup('authenticate', async ({ page }) => {
const token = process.env.HEADLAMP_TOKEN;
const url = process.env.HEADLAMP_URL;
const AUTH_STATE_PATH = 'e2e/.auth/state.json';
if (!token || !url) {
throw new Error('HEADLAMP_TOKEN and HEADLAMP_URL must be set');
async function authenticateWithToken(page: Page, token: string): Promise<void> {
await page.goto('/');
await page.waitForURL(/\/(login|token)$/);
if (page.url().includes('/login')) {
const useTokenBtn = page.getByRole('button', { name: /use a token/i });
await useTokenBtn.waitFor({ state: 'visible', timeout: 15_000 });
await useTokenBtn.click();
await page.waitForURL('**/token');
}
await page.context().addInitScript(() => {
window.localStorage.setItem('token', 'dummy-token');
await page.getByRole('textbox', { name: /id token/i }).fill(token);
await page.getByRole('button', { name: /authenticate/i }).click();
await expect(page.getByRole('navigation', { name: 'Navigation' })).toBeVisible({
timeout: 15_000,
});
}
await page.goto(url);
setup('authenticate with Headlamp', async ({ page }) => {
const token = process.env.HEADLAMP_TOKEN;
const context = page.context();
await context.storageState({ path: 'e2e/.auth/state.json' });
if (!token) {
throw new Error('Set HEADLAMP_TOKEN for token auth');
}
await authenticateWithToken(page, token);
await page.context().storageState({ path: AUTH_STATE_PATH });
});
-16
View File
@@ -1,16 +0,0 @@
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();
});
});
+5 -5
View File
@@ -1,7 +1,7 @@
{
"name": "@privilegedescalation/headlamp-argocd-plugin",
"version": "0.1.2",
"description": "Headlamp plugin for ArgoCD visibility monitors ArgoCD Applications, Rollouts, and health status",
"description": "Headlamp plugin for ArgoCD visibility \u2014 monitors ArgoCD Applications, Rollouts, and health status",
"repository": {
"type": "git",
"url": "https://github.com/privilegedescalation/headlamp-argocd-plugin.git"
@@ -35,13 +35,13 @@
"overrides": {
"tar": "^7.5.11",
"undici": "^7.24.3",
"flatted": "^3.4.2"
"flatted": "^3.4.2",
"elliptic": ">=6.6.1"
}
},
"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",
@@ -51,12 +51,12 @@
"@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",
"react-router-dom": "^5.3.0",
"typescript": "~5.6.2",
"vitest": "^3.0.5"
"vitest": "^3.0.5",
"@playwright/test": "^1.58.2"
}
}
+1 -1
View File
@@ -9,7 +9,7 @@ export default defineConfig({
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'); })(),
baseURL: process.env.HEADLAMP_URL || 'http://headlamp-e2e-argocd.headlamp-dev.svc.cluster.local',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
},
+3 -5
View File
@@ -8,6 +8,7 @@ overrides:
tar: ^7.5.11
undici: ^7.24.3
flatted: ^3.4.2
elliptic: '>=6.6.1'
importers:
@@ -49,9 +50,6 @@ 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
@@ -6255,7 +6253,7 @@ snapshots:
jsdom: 24.1.3
jsonpath-plus: 10.4.0
lodash: 4.18.1
material-react-table: 2.13.3(330725fe5432f245d076f0c0dda1a7a7)
material-react-table: 2.13.3(0078ddeddc9e779fa84c03996c1db10e)
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))
@@ -9964,7 +9962,7 @@ snapshots:
'@types/minimatch': 3.0.5
minimatch: 3.1.5
material-react-table@2.13.3(330725fe5432f245d076f0c0dda1a7a7):
material-react-table@2.13.3(0078ddeddc9e779fa84c03996c1db10e):
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)
+3 -20
View File
@@ -1,31 +1,15 @@
#!/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}"
E2E_RELEASE="${E2E_RELEASE:-headlamp-e2e-argocd}"
HEADLAMP_VERSION="${HEADLAMP_VERSION:-v0.40.1}"
if [ ! -d "$DIST_DIR" ]; then
echo "ERROR: dist/ not found. Run 'pnpm build' first." >&2
echo "ERROR: dist/ not found. Run 'npm run build' first." >&2
exit 1
fi
@@ -150,7 +134,6 @@ spec:
EOF
echo "Waiting for rollout..."
sleep 2
kubectl rollout status "deployment/${E2E_RELEASE}" \
-n "$E2E_NAMESPACE" --timeout=120s
+8 -28
View File
@@ -1,37 +1,17 @@
#!/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}"
E2E_RELEASE="${E2E_RELEASE:-headlamp-e2e-argocd}"
echo "=== E2E Headlamp Teardown ==="
echo "=== E2E 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
kubectl delete deployment "${E2E_RELEASE}" -n "$E2E_NAMESPACE" --ignore-not-found || true
kubectl delete service "${E2E_RELEASE}" -n "$E2E_NAMESPACE" --ignore-not-found || true
kubectl delete serviceaccount "${E2E_RELEASE}" -n "$E2E_NAMESPACE" --ignore-not-found || true
kubectl delete serviceaccount headlamp-e2e-test -n "$E2E_NAMESPACE" --ignore-not-found || true
kubectl delete configmap headlamp-argocd-plugin -n "$E2E_NAMESPACE" --ignore-not-found || true
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."
echo "Teardown complete."