The E2E Headlamp instance is deployed without OIDC configuration, so Headlamp redirects / → /token directly instead of / → /login. The authenticateWithToken function was hardcoded to expect /login first, causing a 60s timeout on every run. - e2e.yaml: remove unused Setup Helm step (deploy script uses kubectl) - e2e.yaml: remove AUTHENTIK_USERNAME/PASSWORD (no OIDC in E2E instance) - auth.setup.ts: waitForURL accepts both /login and /token; only clicks "use a token" if landed on /login (OIDC-configured Headlamp) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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):
- Builds the plugin (
npm run build) - Creates a ConfigMap from the built
dist/output - Deploys a stock Headlamp instance via Helm with the plugin mounted as a ConfigMap volume
- Generates a ServiceAccount token for test auth
- Runs Playwright tests against the E2E instance
- 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)
-
sidebar contains Polaris entry- Verifies Polaris appears in the navigation sidebar
- Ensures plugin successfully registered sidebar entry
-
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
- Navigates to
-
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
- Navigates to
-
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
-
namespace detail drawer closes with Escape key- Opens namespace drawer
- Presses Escape key
- Verifies drawer closes
- Checks URL hash is cleared
-
namespace detail drawer opens from URL hash- Navigates directly to
/c/main/polaris/namespaces#<namespace> - Verifies drawer automatically opens
- Checks namespace details are displayed
- Navigates directly to
Prerequisites
Cluster Requirements
-
Polaris Deployment
# Verify Polaris is running kubectl -n polaris get pods kubectl -n polaris get svc polaris-dashboard -
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' -
RBAC Permissions
- Headlamp service account (or test user) needs
getonservices/proxyforpolaris-dashboard - See main README for RBAC setup
- Headlamp service account (or test user) needs
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:
- Build plugin (
npm run build) - Create ConfigMap from
dist/output (scripts/deploy-e2e-headlamp.sh) - Deploy stock Headlamp via Helm with ConfigMap volume mount
- Run Playwright tests against the E2E instance
- 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:
- Go to Actions → E2E Tests
- Click "Run workflow"
- Select branch and run
Best Practices
- Use semantic selectors:
getByRole,getByTextover CSS selectors - Wait for visibility: Use
await expect(...).toBeVisible()instead ofwaitForTimeout - Keep tests independent: Each test should work in isolation
- Test user flows: Complete journeys, not just page loads
- Clean up state: Close drawers/modals after tests
- Use storage state: Reuse auth across tests (already configured)
- Parallelize carefully: Currently disabled due to shared state