Files
Gandalf the Greybeard 175d3ec6a2 fix(e2e): clean-delete existing deployment before redeploy for guaranteed fresh pod
kubectl apply without prior deletion patches in place: if the pod spec is
unchanged between runs, no rollout is triggered and a potentially degraded
pod from a prior run keeps serving. This caused the auth.setup.ts timeout
(waiting for the "use a token" button) even when no concurrent runs were
present — the headlamp-e2e pod was in an inconsistent state from a previous
run that didn't tear down cleanly.

Changes:
- deploy-e2e-headlamp.sh: delete Deployment, Service, and ServiceAccount
  (with --wait) before applying, guaranteeing a fresh pod each run
- auth.setup.ts: add explicit waitFor({ state: 'visible', timeout: 15_000 })
  before the "use a token" button click, so failures surface at 15 s with a
  clear locator error rather than silently timing out at 60 s

Fixes the pre-existing infra issue blocking PR#110.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 16:40:30 +00:00
..

E2E Smoke Tests

Playwright-based smoke tests that validate the Polaris plugin against a live Headlamp deployment.

CI

E2E tests run automatically in GitHub Actions on pushes to main and pull requests. The workflow (.github/workflows/e2e.yaml):

  1. Builds the plugin (npm run build)
  2. Creates a ConfigMap from the built dist/ output
  3. Deploys a stock Headlamp instance via Helm with the plugin mounted as a ConfigMap volume
  4. Generates a ServiceAccount token for test auth
  5. Runs Playwright tests against the E2E instance
  6. Tears down the E2E instance

This approach uses the stock ghcr.io/headlamp-k8s/headlamp image with no custom Docker builds. The plugin is loaded via HEADLAMP_PLUGINS_DIR volume mount.

Required GitHub Secrets

Configure these in GitHub repository settings (Settings → Secrets and variables → Actions):

Secret Required Description
AUTHENTIK_USERNAME OIDC Authentik email or username for a CI user with Headlamp access
AUTHENTIK_PASSWORD OIDC Password for that user

Token-based auth is auto-generated by the deploy script. OIDC secrets are only needed if testing against the shared Headlamp instance.

No GHCR_TOKEN or Docker registry secrets are needed — the stock Headlamp image is public.

Running Locally

Option 1: OIDC via Authentik (same as CI)

AUTHENTIK_USERNAME=you@example.com AUTHENTIK_PASSWORD=... npm run e2e

The default base URL is https://headlamp.animaniacs.farh.net. Override with HEADLAMP_URL if needed.

Option 2: K8s bearer token (port-forward)

kubectl port-forward -n kube-system svc/headlamp 4466:80
export HEADLAMP_TOKEN=$(kubectl create token headlamp -n kube-system)
HEADLAMP_URL=http://localhost:4466 npm run e2e

Or in headed mode (opens a browser window):

HEADLAMP_URL=http://localhost:4466 npm run e2e:headed

Environment Variables

Variable Required Default Description
HEADLAMP_URL No https://headlamp.animaniacs.farh.net Base URL of the Headlamp instance
AUTHENTIK_USERNAME OIDC Authentik email/username
AUTHENTIK_PASSWORD OIDC Authentik password
HEADLAMP_TOKEN Token Kubernetes bearer token (auto-generated in CI)

In CI, HEADLAMP_URL and HEADLAMP_TOKEN are set automatically by the deploy script. For local runs, set either OIDC credentials or a token manually.

What the Tests Validate

  • Sidebar entry — The Polaris sidebar item appears after login
  • Overview page — Cluster score and check distribution render correctly
  • Namespaces page — Table of namespaces loads with clickable links
  • Namespace detail — Clicking a namespace shows its score and resource table

These are smoke tests against real cluster data. They verify the plugin loads and renders without errors, not specific data values.

Test Coverage

Current Tests (polaris.spec.ts)

  1. sidebar contains Polaris entry

    • Verifies Polaris appears in the navigation sidebar
    • Ensures plugin successfully registered sidebar entry
  2. overview page renders cluster score

    • Navigates to /c/main/polaris
    • Checks for "Polaris — Overview" heading
    • Verifies cluster score percentage is displayed
    • Validates data fetching and rendering
  3. namespaces page renders table with namespace buttons

    • Navigates to /c/main/polaris/namespaces
    • Checks for "Polaris — Namespaces" heading
    • Verifies table is visible with at least one row
    • Ensures namespace buttons are clickable
  4. namespace detail drawer opens from table button

    • Clicks first namespace button in table
    • Verifies drawer opens with namespace name in heading
    • Checks "Namespace Score" section is visible
    • Confirms "Resources" table is displayed
    • Validates URL hash is updated with namespace name
  5. namespace detail drawer closes with Escape key

    • Opens namespace drawer
    • Presses Escape key
    • Verifies drawer closes
    • Checks URL hash is cleared
  6. namespace detail drawer opens from URL hash

    • Navigates directly to /c/main/polaris/namespaces#<namespace>
    • Verifies drawer automatically opens
    • Checks namespace details are displayed

Prerequisites

Cluster Requirements

  1. Polaris Deployment

    # Verify Polaris is running
    kubectl -n polaris get pods
    kubectl -n polaris get svc polaris-dashboard
    
  2. Polaris Audit Data

    # Check if Polaris has generated audit results
    kubectl get --raw /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json | jq '.AuditTime'
    
  3. RBAC Permissions

    • Headlamp service account (or test user) needs get on services/proxy for polaris-dashboard
    • See main README for RBAC setup

Local Setup

# 1. Install dependencies
npm install
npx playwright install chromium

# 2. Create .env file (optional, for persistent config)
cp .env.example .env

# 3. Set environment variables
export HEADLAMP_URL=https://your-headlamp-instance.com
export HEADLAMP_TOKEN=$(kubectl create token headlamp -n kube-system)

# 4. Run tests
npm run e2e

Debugging

Run in Headed Mode

See the browser UI while tests run:

npm run e2e:headed

Enable Debug Mode

Step through tests with Playwright Inspector:

npx playwright test --debug

Generate Trace

Record full trace for failed tests:

npx playwright test --trace on
npx playwright show-trace test-results/<test-name>/trace.zip

Screenshot on Failure

Tests automatically capture screenshots on failure in test-results/

Common Issues

Auth fails with "Sign In button not found":

  • Check HEADLAMP_URL is correct
  • Verify Headlamp is accessible
  • Ensure OIDC is configured if using Authentik

Polaris sidebar entry not found:

  • Plugin may not be installed: Check Settings → Plugins in Headlamp
  • Plugin may have failed to load: Check browser console
  • Clear browser cache and hard refresh

Cluster score not displayed:

  • Polaris may not have audit data yet
  • Check Polaris is running: kubectl -n polaris get pods
  • Verify service proxy: kubectl get --raw /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json

Namespace table empty:

  • Polaris hasn't run audit yet (wait a few minutes)
  • Check Polaris logs: kubectl -n polaris logs -l app.kubernetes.io/name=polaris

Writing New Tests

Example: Testing Plugin Settings

test('plugin settings page shows Polaris configuration', async ({ page }) => {
  await page.goto('/c/main/settings/plugins');

  // Find and click Polaris plugin
  await page.getByText('headlamp-polaris-plugin').click();

  // Check settings are visible
  await expect(page.getByText('Polaris Settings')).toBeVisible();
  await expect(page.getByText('Refresh Interval')).toBeVisible();
  await expect(page.getByText('Dashboard URL')).toBeVisible();
});

Example: Testing App Bar Badge

test('app bar displays Polaris score badge', async ({ page }) => {
  await page.goto('/c/main');

  // Badge should be visible in app bar
  const badge = page.getByRole('button', { name: /Polaris: \d+%/ });
  await expect(badge).toBeVisible();

  // Clicking should navigate to overview
  await badge.click();
  await expect(page).toHaveURL(/\/c\/main\/polaris$/);
});

Example: Testing Dark Mode

test('plugin UI adapts to dark mode', async ({ page }) => {
  await page.goto('/c/main/polaris');

  // Toggle dark mode
  await page.getByRole('button', { name: /theme/i }).click();

  // Check background color changes
  const body = page.locator('body');
  await expect(body).toHaveCSS('background-color', 'rgb(18, 18, 18)');

  // Plugin components should adapt
  const sectionBox = page.locator('[class*="MuiPaper"]').first();
  await expect(sectionBox).not.toHaveCSS('background-color', 'rgb(255, 255, 255)');
});

CI/CD Integration

Tests run automatically in GitHub Actions on pushes to main and pull requests. See .github/workflows/e2e.yaml for workflow configuration.

Architecture

The E2E workflow deploys a dedicated Headlamp instance for each test run:

  1. Build plugin (npm run build)
  2. Create ConfigMap from dist/ output (scripts/deploy-e2e-headlamp.sh)
  3. Deploy stock Headlamp via Helm with ConfigMap volume mount
  4. Run Playwright tests against the E2E instance
  5. Tear down (scripts/teardown-e2e-headlamp.sh)

No custom Docker images, no PVCs, no kubectl exec/cp, no patching of existing deployments. The plugin is mounted from a ConfigMap into the stock Headlamp image.

Cluster Prerequisites

One-time setup by a cluster admin:

kubectl apply -f deployment/e2e-ci-runner-rbac.yaml

Manual Trigger

You can manually trigger E2E tests from GitHub Actions:

  1. Go to Actions → E2E Tests
  2. Click "Run workflow"
  3. Select branch and run

Best Practices

  1. Use semantic selectors: getByRole, getByText over CSS selectors
  2. Wait for visibility: Use await expect(...).toBeVisible() instead of waitForTimeout
  3. Keep tests independent: Each test should work in isolation
  4. Test user flows: Complete journeys, not just page loads
  5. Clean up state: Close drawers/modals after tests
  6. Use storage state: Reuse auth across tests (already configured)
  7. Parallelize carefully: Currently disabled due to shared state

Resources