e2ae92648c
* docs: update Headlamp install namespace references from kube-system to headlamp Updates all documentation references to the Headlamp install namespace from kube-system to headlamp as part of PRI-433. In-scope files updated: - README.md, SECURITY.md - docs/getting-started/installation.md, quick-start.md, prerequisites.md - docs/deployment/helm.md, kubernetes.md, production.md - docs/troubleshooting/README.md, common-issues.md, rbac-issues.md - docs/user-guide/configuration.md, rbac-permissions.md - docs/TESTING.md, TROUBLESHOOTING.md, DEPLOYMENT.md Out-of-scope (unchanged): - Source files referencing upstream workload namespace - RBAC manifests describing Polaris namespace (polaris ns is unchanged) - NetworkPolicy namespaceSelector (API server runs in kube-system) - design-decisions.md and ARCHITECTURE.md (URL hashes refer to cluster namespaces, not Headlamp install ns) Co-Authored-By: Paperclip <noreply@paperclip.ing> * fix: correct RBAC manifest per QA review (PRI-555) - Remove rbac.authorization.k8s.io privilege escalation block - Fix orphaned comment from round 1 - Add EOF newline - Keep serviceaccounts/token for E2E auth (confirmed needed) - Namespace already correct (privilegedescalation-dev) Co-Authored-By: Paperclip <noreply@paperclip.ing> * docs: replace hardcoded namespace with <your-namespace> placeholder Users choose their own namespace for Headlamp. Replace all hardcoded namespace references (headlamp, kube-system) in user-facing docs with <your-namespace> so users substitute their own value. Conventions: - Helm install: --namespace <your-namespace> --create-namespace - kubectl commands: -n <your-namespace> - YAML metadata: namespace: <your-namespace> - Prose: "the namespace where Headlamp is installed" Out-of-scope references left untouched: - kube-system in NetworkPolicy selectors (API server namespace) - polaris namespace references (upstream workload namespace) - Source code and test files Refs: PRI-433 Co-Authored-By: Paperclip <noreply@paperclip.ing> * docs: fix remaining hardcoded headlamp namespace to <your-namespace> placeholder Prior commit was inconsistent — some files used <your-namespace> while DEPLOYMENT.md, TROUBLESHOOTING.md and several troubleshooting/user-guide docs still hardcoded headlamp as the namespace. Co-Authored-By: Paperclip <noreply@paperclip.ing> --------- Co-authored-by: Chris Farhood <chris@farhood.org> Co-authored-by: Paperclip <noreply@paperclip.ing>
703 lines
18 KiB
Markdown
703 lines
18 KiB
Markdown
# Testing Guide
|
|
|
|
Comprehensive guide to testing the Headlamp Polaris Plugin, covering unit tests, E2E tests, and CI/CD integration.
|
|
|
|
## Table of Contents
|
|
|
|
- [Overview](#overview)
|
|
- [Unit Testing](#unit-testing)
|
|
- [E2E Testing](#e2e-testing)
|
|
- [CI/CD Integration](#cicd-integration)
|
|
- [Test Coverage](#test-coverage)
|
|
- [Best Practices](#best-practices)
|
|
- [Debugging](#debugging)
|
|
|
|
---
|
|
|
|
## Overview
|
|
|
|
The Headlamp Polaris Plugin uses a multi-layered testing approach:
|
|
|
|
| Test Type | Framework | Purpose | Location |
|
|
| ----------------- | ---------- | ------------------------------------------------------- | ----------------------- |
|
|
| **Unit Tests** | Vitest | Test individual functions and components in isolation | `src/**/*.test.ts(x)` |
|
|
| **E2E Tests** | Playwright | Test complete user flows against live Headlamp instance | `e2e/*.spec.ts` |
|
|
| **Type Checking** | TypeScript | Ensure type safety across codebase | `tsc --noEmit` |
|
|
| **Linting** | ESLint | Enforce code style and catch common errors | `eslint src/` |
|
|
| **Formatting** | Prettier | Maintain consistent code formatting | `prettier --check src/` |
|
|
|
|
### Test Philosophy
|
|
|
|
1. **Unit tests** focus on business logic (data parsing, score calculation, filtering)
|
|
2. **E2E tests** validate user-facing functionality (navigation, rendering, interactions)
|
|
3. **Both** run automatically in CI on every commit
|
|
4. **Coverage** targets meaningful tests, not arbitrary percentages
|
|
|
|
---
|
|
|
|
## Unit Testing
|
|
|
|
### Framework: Vitest
|
|
|
|
Vitest is a fast, modern testing framework compatible with Jest APIs but optimized for Vite-based projects.
|
|
|
|
### Running Unit Tests
|
|
|
|
```bash
|
|
# Run all unit tests
|
|
npm test
|
|
|
|
# Run in watch mode (re-runs on file changes)
|
|
npm run test:watch
|
|
|
|
# Run specific test file
|
|
npx vitest src/api/polaris.test.ts
|
|
|
|
# Run with coverage
|
|
npx vitest --coverage
|
|
```
|
|
|
|
### Test Structure
|
|
|
|
Unit tests are colocated with source files:
|
|
|
|
```
|
|
src/
|
|
├── api/
|
|
│ ├── polaris.ts
|
|
│ ├── polaris.test.ts # Unit tests for polaris.ts
|
|
│ ├── PolarisDataContext.tsx
|
|
│ └── PolarisDataContext.test.tsx
|
|
└── components/
|
|
├── DashboardView.tsx
|
|
└── DashboardView.test.tsx
|
|
```
|
|
|
|
### Example: Testing Utility Functions
|
|
|
|
**File:** `src/api/polaris.test.ts`
|
|
|
|
```typescript
|
|
import { describe, it, expect } from 'vitest';
|
|
import { countResults, computeScore, getNamespaces, filterResultsByNamespace } from './polaris';
|
|
|
|
describe('countResults', () => {
|
|
it('counts passing, warning, danger, and skipped results correctly', () => {
|
|
const data = {
|
|
Results: [
|
|
{
|
|
Name: 'test-deployment',
|
|
Namespace: 'default',
|
|
Kind: 'Deployment',
|
|
Results: {
|
|
'check-1': { Success: true, Severity: 'warning' },
|
|
'check-2': { Success: false, Severity: 'danger' },
|
|
'check-3': { Success: false, Severity: 'ignore' }, // skipped
|
|
},
|
|
CreatedTime: '2024-01-01T00:00:00Z',
|
|
},
|
|
],
|
|
};
|
|
|
|
const counts = countResults(data);
|
|
|
|
expect(counts).toEqual({
|
|
total: 3,
|
|
pass: 1,
|
|
warning: 0,
|
|
danger: 1,
|
|
skipped: 1,
|
|
});
|
|
});
|
|
|
|
it('handles empty results', () => {
|
|
const data = { Results: [] };
|
|
const counts = countResults(data);
|
|
|
|
expect(counts).toEqual({
|
|
total: 0,
|
|
pass: 0,
|
|
warning: 0,
|
|
danger: 0,
|
|
skipped: 0,
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('computeScore', () => {
|
|
it('returns 0 for zero total checks', () => {
|
|
expect(computeScore({ total: 0, pass: 0, warning: 0, danger: 0, skipped: 0 })).toBe(0);
|
|
});
|
|
|
|
it('calculates percentage correctly', () => {
|
|
expect(computeScore({ total: 100, pass: 75, warning: 20, danger: 5, skipped: 0 })).toBe(75);
|
|
});
|
|
|
|
it('rounds to nearest integer', () => {
|
|
expect(computeScore({ total: 3, pass: 2, warning: 1, danger: 0, skipped: 0 })).toBe(67);
|
|
});
|
|
});
|
|
```
|
|
|
|
### Example: Testing React Components
|
|
|
|
**File:** `src/components/DashboardView.test.tsx`
|
|
|
|
```typescript
|
|
import { describe, it, expect, vi } from 'vitest';
|
|
import { render, screen } from '@testing-library/react';
|
|
import { DashboardView } from './DashboardView';
|
|
import * as PolarisDataContext from '../api/PolarisDataContext';
|
|
|
|
describe('DashboardView', () => {
|
|
it('renders loading state', () => {
|
|
vi.spyOn(PolarisDataContext, 'usePolarisDataContext').mockReturnValue({
|
|
data: null,
|
|
loading: true,
|
|
error: null,
|
|
refresh: vi.fn(),
|
|
});
|
|
|
|
render(<DashboardView />);
|
|
expect(screen.getByText(/loading/i)).toBeInTheDocument();
|
|
});
|
|
|
|
it('renders error state', () => {
|
|
vi.spyOn(PolarisDataContext, 'usePolarisDataContext').mockReturnValue({
|
|
data: null,
|
|
loading: false,
|
|
error: '403 Forbidden',
|
|
refresh: vi.fn(),
|
|
});
|
|
|
|
render(<DashboardView />);
|
|
expect(screen.getByText(/403 Forbidden/i)).toBeInTheDocument();
|
|
});
|
|
|
|
it('displays cluster score when data is loaded', () => {
|
|
const mockData = {
|
|
DisplayName: 'test-cluster',
|
|
ClusterInfo: { Version: '1.27', Nodes: 3, Pods: 100, Namespaces: 10, Controllers: 50 },
|
|
Results: [
|
|
/* ... */
|
|
],
|
|
};
|
|
|
|
vi.spyOn(PolarisDataContext, 'usePolarisDataContext').mockReturnValue({
|
|
data: mockData,
|
|
loading: false,
|
|
error: null,
|
|
refresh: vi.fn(),
|
|
});
|
|
|
|
render(<DashboardView />);
|
|
expect(screen.getByText(/Cluster Score/i)).toBeInTheDocument();
|
|
});
|
|
});
|
|
```
|
|
|
|
### What to Unit Test
|
|
|
|
✅ **Do test:**
|
|
|
|
- Pure functions (score calculation, filtering, data transformation)
|
|
- Data parsing and validation
|
|
- Utility functions
|
|
- Error handling logic
|
|
- Edge cases (empty arrays, null values, invalid input)
|
|
|
|
❌ **Don't test:**
|
|
|
|
- Third-party libraries (Headlamp, React)
|
|
- Simple prop passing
|
|
- Trivial getters/setters
|
|
- Implementation details (internal state)
|
|
|
|
---
|
|
|
|
## E2E Testing
|
|
|
|
### Framework: Playwright
|
|
|
|
Playwright provides cross-browser testing with auto-wait, network interception, and screenshot/video capture.
|
|
|
|
### Running E2E Tests
|
|
|
|
```bash
|
|
# Run all E2E tests (headless)
|
|
npm run e2e
|
|
|
|
# Run with browser visible (headed mode)
|
|
npm run e2e:headed
|
|
|
|
# Run specific test file
|
|
npx playwright test e2e/polaris.spec.ts
|
|
|
|
# Debug mode (step through tests)
|
|
npx playwright test --debug
|
|
|
|
# Generate trace for debugging
|
|
npx playwright test --trace on
|
|
npx playwright show-trace test-results/<test-name>/trace.zip
|
|
```
|
|
|
|
### Prerequisites
|
|
|
|
**1. Headlamp Instance**
|
|
|
|
E2E tests require a running Headlamp instance with:
|
|
|
|
- Polaris plugin installed (version being tested)
|
|
- Polaris dashboard deployed and accessible
|
|
- RBAC configured (service proxy permissions)
|
|
|
|
**2. Authentication**
|
|
|
|
Choose one of two authentication methods:
|
|
|
|
**Option A: OIDC via Authentik**
|
|
|
|
```bash
|
|
export AUTHENTIK_USERNAME="user@example.com"
|
|
export AUTHENTIK_PASSWORD="password"
|
|
export HEADLAMP_URL="https://headlamp.example.com"
|
|
npm run e2e
|
|
```
|
|
|
|
**Option B: Kubernetes Token**
|
|
|
|
```bash
|
|
# Create token
|
|
export HEADLAMP_TOKEN=$(kubectl create token headlamp -n <your-namespace> --duration=24h)
|
|
|
|
kubectl port-forward -n <your-namespace> svc/headlamp 4466:80
|
|
|
|
# Run tests
|
|
HEADLAMP_URL=http://localhost:4466 npm run e2e
|
|
```
|
|
|
|
**3. Environment Variables**
|
|
|
|
Create `.env` file (optional, for persistent config):
|
|
|
|
```bash
|
|
cp .env.example .env
|
|
```
|
|
|
|
Edit `.env`:
|
|
|
|
```bash
|
|
HEADLAMP_URL=https://headlamp.example.com
|
|
HEADLAMP_TOKEN=eyJhbGciOi...
|
|
# OR
|
|
AUTHENTIK_USERNAME=user@example.com
|
|
AUTHENTIK_PASSWORD=secret
|
|
```
|
|
|
|
### Test Coverage
|
|
|
|
#### Current E2E Tests
|
|
|
|
**File:** `e2e/polaris.spec.ts`
|
|
|
|
| Test | Description | Validates |
|
|
| --------------------------------------------- | ------------------------------- | ----------------------------- |
|
|
| `sidebar contains Polaris entry` | Polaris appears in sidebar | Plugin registration |
|
|
| `overview page renders cluster score` | Score displayed on overview | Data fetching, rendering |
|
|
| `namespaces page renders table` | Namespace table loads | Data parsing, table rendering |
|
|
| `namespace detail drawer opens` | Clicking namespace shows drawer | Navigation, drawer UI |
|
|
| `namespace detail drawer closes with Escape` | Keyboard shortcut works | Keyboard navigation |
|
|
| `namespace detail drawer opens from URL hash` | Direct URL navigation | URL routing, deep linking |
|
|
|
|
**File:** `e2e/settings.spec.ts`
|
|
|
|
| Test | Description | Validates |
|
|
| ------------------------------------ | ------------------- | --------------------------- |
|
|
| `plugin settings page is accessible` | Settings page loads | Settings registration |
|
|
| `refresh interval can be changed` | Dropdown works | User preference persistence |
|
|
| `dashboard URL can be customized` | Input field works | URL configuration |
|
|
| `connection test button works` | Test functionality | API connectivity validation |
|
|
|
|
**File:** `e2e/appbar.spec.ts`
|
|
|
|
| Test | Description | Validates |
|
|
| -------------------------------------- | ------------------------------- | ------------------- |
|
|
| `app bar displays Polaris badge` | Badge visible in header | App bar integration |
|
|
| `badge shows cluster score` | Score matches dashboard | Data consistency |
|
|
| `clicking badge navigates to overview` | Navigation works | App bar action |
|
|
| `badge color reflects score` | Red/yellow/green based on score | Visual feedback |
|
|
|
|
### Writing E2E Tests
|
|
|
|
**Example: Testing User Flow**
|
|
|
|
```typescript
|
|
import { test, expect } from '@playwright/test';
|
|
|
|
test('user can view namespace details and navigate back', async ({ page }) => {
|
|
// Navigate to namespaces page
|
|
await page.goto('/c/main/polaris/namespaces');
|
|
await expect(page.getByText('Polaris — Namespaces')).toBeVisible();
|
|
|
|
// Click first namespace
|
|
const firstNamespace = page.locator('table tbody tr').first();
|
|
const namespaceName = await firstNamespace.locator('td').first().textContent();
|
|
await firstNamespace.getByRole('button').click();
|
|
|
|
// Drawer should open
|
|
await expect(page.getByText(`Polaris — ${namespaceName}`)).toBeVisible();
|
|
await expect(page).toHaveURL(new RegExp(`#${namespaceName}`));
|
|
|
|
// Close drawer with Escape
|
|
await page.keyboard.press('Escape');
|
|
await expect(page.getByText(`Polaris — ${namespaceName}`)).not.toBeVisible();
|
|
await expect(page).toHaveURL(/namespaces$/);
|
|
});
|
|
```
|
|
|
|
**Example: Testing Dark Mode Adaptation**
|
|
|
|
```typescript
|
|
test('plugin adapts to dark mode', async ({ page }) => {
|
|
await page.goto('/c/main/polaris/namespaces');
|
|
|
|
// Toggle dark mode
|
|
await page.getByLabel(/theme/i).click();
|
|
|
|
// Open namespace drawer
|
|
const firstNamespace = page.locator('table tbody tr button').first();
|
|
await firstNamespace.click();
|
|
|
|
// Drawer background should be dark
|
|
const drawer = page.locator('[style*="position: fixed"][style*="right: 0"]');
|
|
await expect(drawer).toHaveCSS('background-color', /rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
|
|
});
|
|
```
|
|
|
|
### Debugging E2E Tests
|
|
|
|
**1. Headed Mode (See Browser)**
|
|
|
|
```bash
|
|
npm run e2e:headed
|
|
```
|
|
|
|
**2. Debug Mode (Step Through Tests)**
|
|
|
|
```bash
|
|
npx playwright test --debug
|
|
```
|
|
|
|
This opens Playwright Inspector where you can:
|
|
|
|
- Step through each test action
|
|
- Inspect page state
|
|
- Edit test selectors live
|
|
|
|
**3. Screenshots on Failure**
|
|
|
|
Screenshots are automatically saved to `test-results/` on failure:
|
|
|
|
```bash
|
|
test-results/
|
|
└── polaris-overview-page-renders-cluster-score/
|
|
├── test-failed-1.png
|
|
└── trace.zip
|
|
```
|
|
|
|
**4. Trace Viewer**
|
|
|
|
Record full trace (DOM snapshots, network, console):
|
|
|
|
```bash
|
|
npx playwright test --trace on
|
|
npx playwright show-trace test-results/<test-name>/trace.zip
|
|
```
|
|
|
|
**5. Verbose Logging**
|
|
|
|
```bash
|
|
DEBUG=pw:api npx playwright test
|
|
```
|
|
|
|
---
|
|
|
|
## CI/CD Integration
|
|
|
|
### GitHub Actions Workflows
|
|
|
|
#### CI Workflow (`.github/workflows/ci.yaml`)
|
|
|
|
Runs on every push to `main` and all pull requests:
|
|
|
|
```yaml
|
|
jobs:
|
|
lint-and-test:
|
|
steps:
|
|
- Build plugin
|
|
- Lint (eslint)
|
|
- Type-check (tsc)
|
|
- Format check (prettier)
|
|
- Run unit tests
|
|
```
|
|
|
|
#### E2E Workflow (`.github/workflows/e2e.yaml`)
|
|
|
|
Runs E2E tests against live Headlamp instance:
|
|
|
|
```yaml
|
|
jobs:
|
|
e2e:
|
|
steps:
|
|
- Install dependencies
|
|
- Install Playwright browsers
|
|
- Run auth setup
|
|
- Run E2E tests
|
|
- Upload artifacts on failure
|
|
```
|
|
|
|
### Required GitHub Secrets
|
|
|
|
Configure in GitHub repository settings (Settings → Secrets and variables → Actions):
|
|
|
|
| Secret | Required | Description |
|
|
| -------------------- | -------- | ------------------------------------------------------- |
|
|
| `HEADLAMP_URL` | Optional | Headlamp instance URL (defaults to configured instance) |
|
|
| `AUTHENTIK_USERNAME` | OIDC | Authentik username/email for CI user |
|
|
| `AUTHENTIK_PASSWORD` | OIDC | Authentik password |
|
|
| `HEADLAMP_TOKEN` | Token | Kubernetes service account token (alternative to OIDC) |
|
|
|
|
Set either `AUTHENTIK_USERNAME` + `AUTHENTIK_PASSWORD` **or** `HEADLAMP_TOKEN`. OIDC takes priority if both are set.
|
|
|
|
### Manual Trigger
|
|
|
|
Trigger workflows manually from GitHub Actions UI:
|
|
|
|
1. Go to Actions → [Workflow Name]
|
|
2. Click "Run workflow"
|
|
3. Select branch and run
|
|
|
|
---
|
|
|
|
## Test Coverage
|
|
|
|
### Current Coverage
|
|
|
|
| Category | Coverage | Notes |
|
|
| -------------------- | -------- | ------------------------------ |
|
|
| **API Functions** | 95% | Core utilities fully tested |
|
|
| **React Components** | 60% | Focus on critical render paths |
|
|
| **E2E User Flows** | 80% | Main features covered |
|
|
|
|
### Coverage Goals
|
|
|
|
- **Unit Tests**: 80%+ for `src/api/` (business logic)
|
|
- **Component Tests**: 50%+ for `src/components/` (critical rendering)
|
|
- **E2E Tests**: Cover all major user journeys
|
|
|
|
### Generating Coverage Reports
|
|
|
|
```bash
|
|
# Unit test coverage
|
|
npx vitest --coverage
|
|
|
|
# View HTML report
|
|
open coverage/index.html
|
|
```
|
|
|
|
---
|
|
|
|
## Best Practices
|
|
|
|
### Unit Testing
|
|
|
|
1. **Test behavior, not implementation**
|
|
|
|
- ✅ `expect(computeScore({ total: 100, pass: 75 })).toBe(75)`
|
|
- ❌ `expect(mockInternalFunction).toHaveBeenCalled()`
|
|
|
|
2. **Use descriptive test names**
|
|
|
|
- ✅ `it('returns 0 when total checks is zero')`
|
|
- ❌ `it('works')`
|
|
|
|
3. **One assertion per test (when possible)**
|
|
|
|
- Makes failures easier to debug
|
|
- Exceptions: testing multiple properties of same object
|
|
|
|
4. **Mock external dependencies**
|
|
|
|
- Mock API calls, context providers, external libraries
|
|
- Don't mock the code you're testing
|
|
|
|
5. **Test edge cases**
|
|
- Empty arrays, null values, zero counts
|
|
- Invalid input, malformed data
|
|
|
|
### E2E Testing
|
|
|
|
1. **Use semantic selectors**
|
|
|
|
- ✅ `page.getByRole('button', { name: 'Close' })`
|
|
- ✅ `page.getByText('Polaris — Overview')`
|
|
- ❌ `page.locator('.MuiButton-root')`
|
|
|
|
2. **Wait for visibility, not arbitrary timeouts**
|
|
|
|
- ✅ `await expect(element).toBeVisible()`
|
|
- ❌ `await page.waitForTimeout(5000)`
|
|
|
|
3. **Keep tests independent**
|
|
|
|
- Each test should work in isolation
|
|
- Don't rely on previous tests' state
|
|
|
|
4. **Test complete user flows**
|
|
|
|
- Navigate → Interact → Verify outcome
|
|
- Don't just test page loads
|
|
|
|
5. **Clean up after tests**
|
|
|
|
- Close drawers/modals
|
|
- Reset state if needed
|
|
|
|
6. **Use storage state for auth**
|
|
|
|
- Reuse authenticated session across tests
|
|
- Faster than logging in for every test
|
|
|
|
7. **Parallelize carefully**
|
|
- Tests must not interfere with each other
|
|
- Currently disabled due to shared cluster state
|
|
|
|
### General
|
|
|
|
1. **Run tests before committing**
|
|
|
|
```bash
|
|
npm run build && npm run lint && npm test
|
|
```
|
|
|
|
2. **Fix failing tests immediately**
|
|
|
|
- Don't commit failing tests
|
|
- Don't skip tests to "fix later"
|
|
|
|
3. **Update tests when changing code**
|
|
|
|
- Tests are documentation
|
|
- Keep them in sync with implementation
|
|
|
|
4. **Review test failures in CI**
|
|
- Check artifacts (screenshots, traces)
|
|
- Reproduce locally before fixing
|
|
|
|
---
|
|
|
|
## Debugging
|
|
|
|
### Common Issues
|
|
|
|
#### Unit Tests
|
|
|
|
**Issue: Mock not working**
|
|
|
|
```typescript
|
|
// ❌ Wrong: Mock after import
|
|
import { usePolarisData } from './polaris';
|
|
vi.mock('./polaris');
|
|
|
|
// ✅ Correct: Mock before import
|
|
vi.mock('./polaris', () => ({
|
|
usePolarisData: vi.fn(),
|
|
}));
|
|
import { usePolarisData } from './polaris';
|
|
```
|
|
|
|
**Issue: "Cannot read property of undefined"**
|
|
|
|
Check mocks are returning expected structure:
|
|
|
|
```typescript
|
|
vi.spyOn(PolarisDataContext, 'usePolarisDataContext').mockReturnValue({
|
|
data: mockData, // Ensure mockData has all required fields
|
|
loading: false,
|
|
error: null,
|
|
refresh: vi.fn(),
|
|
});
|
|
```
|
|
|
|
#### E2E Tests
|
|
|
|
**Issue: "Element not found"**
|
|
|
|
```bash
|
|
# Enable verbose logging
|
|
DEBUG=pw:api npx playwright test
|
|
|
|
# Use headed mode to see what's happening
|
|
npm run e2e:headed
|
|
```
|
|
|
|
Common causes:
|
|
|
|
- Element hasn't rendered yet (use `toBeVisible()` instead of `toBeDefined()`)
|
|
- Wrong selector (use Playwright Inspector to verify)
|
|
- Authentication failed (check token/credentials)
|
|
|
|
**Issue: "Test timeout"**
|
|
|
|
Increase timeout for slow operations:
|
|
|
|
```typescript
|
|
test('slow operation', async ({ page }) => {
|
|
test.setTimeout(60000); // 60 seconds
|
|
|
|
await page.goto('/c/main/polaris');
|
|
// ...
|
|
});
|
|
```
|
|
|
|
**Issue: Network errors in E2E tests**
|
|
|
|
```bash
|
|
# Check Headlamp accessibility
|
|
curl -I $HEADLAMP_URL
|
|
|
|
# Check Polaris service
|
|
kubectl -n polaris get svc polaris-dashboard
|
|
kubectl get --raw /api/v1/namespaces/polaris/services/polaris-dashboard:80/proxy/results.json
|
|
```
|
|
|
|
### Useful Commands
|
|
|
|
```bash
|
|
# Run specific test file
|
|
npx vitest src/api/polaris.test.ts
|
|
|
|
# Run specific test case
|
|
npx vitest -t "computes score correctly"
|
|
|
|
# Run E2E test by name
|
|
npx playwright test -g "sidebar contains Polaris"
|
|
|
|
# Update snapshots
|
|
npx vitest -u
|
|
|
|
# Clear test cache
|
|
npx vitest --clearCache
|
|
```
|
|
|
|
---
|
|
|
|
## Resources
|
|
|
|
- [Vitest Documentation](https://vitest.dev/)
|
|
- [Playwright Documentation](https://playwright.dev/)
|
|
- [Testing Library (React)](https://testing-library.com/docs/react-testing-library/intro/)
|
|
- [Headlamp Plugin Development](https://headlamp.dev/docs/latest/development/plugins/)
|
|
- [Project Architecture](./ARCHITECTURE.md)
|
|
- [Contributing Guide](../CONTRIBUTING.md)
|