The 3s waitForTimeout after page.goto wasn't enough for Angular to bootstrap and render content. Replace with waitForAngular() helper that waits for actual DOM elements (nav, headings) up to 15s, with 5s fallback. 32 calls updated across 10 test files. Also adds waitForAngular to helpers.ts export. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
167 lines
5.8 KiB
TypeScript
167 lines
5.8 KiB
TypeScript
/**
|
|
* Runtime Host Integration — End-to-End Tests
|
|
*
|
|
* Validates the full lifecycle for runtime-host integrations (eBPF Agent):
|
|
* 1. Fixture compose health
|
|
* 2. Direct endpoint probe
|
|
* 3. Connector plugin API (create, test-connection, health, delete)
|
|
* 4. UI: Runtimes / Hosts tab shows created integration
|
|
*
|
|
* Prerequisites:
|
|
* - Main Stella Ops stack running
|
|
* - docker-compose.integration-fixtures.yml (includes runtime-host-fixture)
|
|
*/
|
|
|
|
import { execSync } from 'child_process';
|
|
import { test, expect } from './live-auth.fixture';
|
|
import {
|
|
INTEGRATION_CONFIGS,
|
|
createIntegrationViaApi,
|
|
cleanupIntegrations,
|
|
snap,
|
|
waitForAngular,
|
|
} from './helpers';
|
|
|
|
const BASE = process.env['PLAYWRIGHT_BASE_URL'] || 'https://stella-ops.local';
|
|
const runId = process.env['E2E_RUN_ID'] || 'run1';
|
|
|
|
function dockerHealthy(containerName: string): boolean {
|
|
try {
|
|
const out = execSync(
|
|
`docker ps --filter "name=${containerName}" --format "{{.Status}}"`,
|
|
{ encoding: 'utf-8', timeout: 5_000 },
|
|
).trim();
|
|
return out.includes('(healthy)') || (out.startsWith('Up') && !out.includes('health: starting'));
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 1. Compose Health
|
|
// ---------------------------------------------------------------------------
|
|
|
|
test.describe('Runtime Host — Compose Health', () => {
|
|
test('runtime-host-fixture container is healthy', () => {
|
|
expect(
|
|
dockerHealthy('stellaops-runtime-host-fixture'),
|
|
'runtime-host-fixture should be healthy',
|
|
).toBe(true);
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 2. Direct Endpoint Probe
|
|
// ---------------------------------------------------------------------------
|
|
|
|
test.describe('Runtime Host — Direct Probe', () => {
|
|
test('eBPF agent /api/v1/health returns 200 with healthy status', async ({ playwright }) => {
|
|
const ctx = await playwright.request.newContext({ ignoreHTTPSErrors: true });
|
|
try {
|
|
const resp = await ctx.get('http://127.1.1.9/api/v1/health', { timeout: 10_000 });
|
|
expect(resp.status()).toBeLessThan(300);
|
|
const body = await resp.json();
|
|
expect(body.status).toBe('healthy');
|
|
expect(body.agent).toBe('ebpf');
|
|
expect(body.probes_loaded).toBeGreaterThan(0);
|
|
} finally {
|
|
await ctx.dispose();
|
|
}
|
|
});
|
|
|
|
test('eBPF agent /api/v1/info returns agent details', async ({ playwright }) => {
|
|
const ctx = await playwright.request.newContext({ ignoreHTTPSErrors: true });
|
|
try {
|
|
const resp = await ctx.get('http://127.1.1.9/api/v1/info', { timeout: 10_000 });
|
|
expect(resp.status()).toBeLessThan(300);
|
|
const body = await resp.json();
|
|
expect(body.agent_type).toBe('ebpf');
|
|
expect(body.probes).toBeDefined();
|
|
expect(body.probes.length).toBeGreaterThan(0);
|
|
} finally {
|
|
await ctx.dispose();
|
|
}
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 3. Connector Lifecycle (API)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
test.describe('Runtime Host — Connector Lifecycle', () => {
|
|
const createdIds: string[] = [];
|
|
|
|
test('create eBPF Agent integration returns 201', async ({ apiRequest }) => {
|
|
const id = await createIntegrationViaApi(apiRequest, INTEGRATION_CONFIGS.ebpfAgent, runId);
|
|
createdIds.push(id);
|
|
expect(id).toBeTruthy();
|
|
|
|
const getResp = await apiRequest.get(`/api/v1/integrations/${id}`);
|
|
expect(getResp.status()).toBe(200);
|
|
const body = await getResp.json();
|
|
expect(body.type).toBe(5); // RuntimeHost
|
|
expect(body.provider).toBe(500); // EbpfAgent
|
|
});
|
|
|
|
test('test-connection on eBPF Agent returns success', async ({ apiRequest }) => {
|
|
expect(createdIds.length).toBeGreaterThan(0);
|
|
const resp = await apiRequest.post(`/api/v1/integrations/${createdIds[0]}/test`);
|
|
expect(resp.status()).toBe(200);
|
|
const body = await resp.json();
|
|
expect(body.success).toBe(true);
|
|
});
|
|
|
|
test('health-check on eBPF Agent returns Healthy', async ({ apiRequest }) => {
|
|
expect(createdIds.length).toBeGreaterThan(0);
|
|
const resp = await apiRequest.get(`/api/v1/integrations/${createdIds[0]}/health`);
|
|
expect(resp.status()).toBe(200);
|
|
const body = await resp.json();
|
|
expect(body.status).toBe(1); // Healthy
|
|
});
|
|
|
|
test('list RuntimeHost integrations returns at least 1', async ({ apiRequest }) => {
|
|
const resp = await apiRequest.get('/api/v1/integrations?type=5&pageSize=100');
|
|
expect(resp.status()).toBe(200);
|
|
const body = await resp.json();
|
|
expect(body.totalCount).toBeGreaterThanOrEqual(1);
|
|
});
|
|
|
|
test.afterAll(async ({ apiRequest }) => {
|
|
await cleanupIntegrations(apiRequest, createdIds);
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 4. UI: Runtimes / Hosts Tab
|
|
// ---------------------------------------------------------------------------
|
|
|
|
test.describe('Runtime Host — UI Verification', () => {
|
|
const createdIds: string[] = [];
|
|
|
|
test.beforeAll(async ({ apiRequest }) => {
|
|
const id = await createIntegrationViaApi(apiRequest, INTEGRATION_CONFIGS.ebpfAgent, `ui-${runId}`);
|
|
createdIds.push(id);
|
|
});
|
|
|
|
test('Runtimes / Hosts tab loads and shows integration', async ({ liveAuthPage: page }) => {
|
|
await page.goto(`${BASE}/setup/integrations/runtime-hosts`, {
|
|
waitUntil: 'domcontentloaded',
|
|
timeout: 45_000,
|
|
});
|
|
await waitForAngular(page);
|
|
|
|
const heading = page.getByRole('heading', { name: /runtime host/i });
|
|
await expect(heading).toBeVisible({ timeout: 5_000 });
|
|
|
|
const rows = page.locator('table tbody tr');
|
|
const count = await rows.count();
|
|
expect(count).toBeGreaterThanOrEqual(1);
|
|
|
|
await snap(page, 'runtime-hosts-tab');
|
|
});
|
|
|
|
test.afterAll(async ({ apiRequest }) => {
|
|
await cleanupIntegrations(apiRequest, createdIds);
|
|
});
|
|
});
|