Add GitLab, pagination, activity timeline, and error resilience e2e tests

Four new test suites expanding integration hub e2e coverage:

- gitlab-integration.e2e.spec.ts: Container health, direct probe, connector
  CRUD lifecycle (create/test/health/delete), SCM tab UI verification.
  Gracefully skips when GitLab container not running (heavy profile).

- pagination.e2e.spec.ts: API-level pagination (pageSize, page params,
  totalPages, sorting, last-page edge case, out-of-range page).
  UI pager rendering verification.

- activity-timeline.e2e.spec.ts: Page load, stats bar, activity items,
  event type filter dropdown, clear filters, back navigation.
  Tests against mock data rendered by the activity component.

- error-resilience.e2e.spec.ts: Unreachable endpoint returns failure/unhealthy,
  non-existent resource 404s, malformed input handling, duplicate name
  creation, UI empty tab rendering, deleted integration detail page.

Also adds GitLab config to shared helpers.ts INTEGRATION_CONFIGS.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
master
2026-03-31 16:55:35 +03:00
parent 2fef38b093
commit 3f6fb501dd
5 changed files with 689 additions and 0 deletions

View File

@@ -0,0 +1,132 @@
/**
* GitLab Integration — End-to-End Tests
*
* Validates the GitLab SCM connector lifecycle against the real GitLab CE instance:
* 1. Container health + direct probe
* 2. Connector CRUD via API (create, test-connection, health, delete)
* 3. UI: SCM tab shows GitLab row
*
* Workaround for "heavy profile": Instead of checking Docker CLI (which may
* not be available from Playwright workers), we probe GitLab's HTTP endpoint
* directly. If it responds, the tests run; otherwise they skip gracefully.
*/
import { execSync } from 'child_process';
import { test, expect } from './live-auth.fixture';
import {
INTEGRATION_CONFIGS,
createIntegrationViaApi,
cleanupIntegrations,
snap,
} from './helpers';
const BASE = process.env['PLAYWRIGHT_BASE_URL'] || 'https://stella-ops.local';
const runId = process.env['E2E_RUN_ID'] || 'run1';
const GITLAB_URL = 'http://127.1.2.7:8929';
/**
* Probe GitLab via HTTP instead of Docker CLI.
* Returns true if GitLab responds (any status < 500) within 3 seconds.
*/
function gitlabReachable(): boolean {
try {
// Use curl (available on both Windows and Linux) with a short timeout
const out = execSync(
`curl -sf -o /dev/null -w "%{http_code}" --connect-timeout 3 ${GITLAB_URL}/`,
{ encoding: 'utf-8', timeout: 5_000 },
).trim();
const code = parseInt(out, 10);
return code > 0 && code < 500;
} catch {
return false;
}
}
const gitlabRunning = gitlabReachable();
// ---------------------------------------------------------------------------
// 1. Reachability
// ---------------------------------------------------------------------------
test.describe('GitLab Integration — Reachability', () => {
test.skip(!gitlabRunning, 'GitLab not reachable at 127.1.2.7:8929');
test('GitLab responds to HTTP probe', async ({ playwright }) => {
const ctx = await playwright.request.newContext({ ignoreHTTPSErrors: true });
try {
const resp = await ctx.get(`${GITLAB_URL}/`, { timeout: 10_000 });
// 302 redirect to login = GitLab is running
expect(resp.status()).toBeLessThan(500);
} finally {
await ctx.dispose();
}
});
});
// ---------------------------------------------------------------------------
// 2. Connector Lifecycle (API)
// ---------------------------------------------------------------------------
test.describe('GitLab Integration — Connector Lifecycle', () => {
test.skip(!gitlabRunning, 'GitLab not reachable');
test('create GitLab integration and verify CRUD operations', async ({ apiRequest }) => {
// Create
const id = await createIntegrationViaApi(apiRequest, INTEGRATION_CONFIGS.gitlab, runId);
expect(id).toBeTruthy();
try {
// Verify created with correct fields
const getResp = await apiRequest.get(`/api/v1/integrations/${id}`);
expect(getResp.status()).toBe(200);
const integration = await getResp.json();
expect(integration.type).toBe(2); // Scm
expect(integration.provider).toBe(201); // GitLabServer
expect(integration.name).toContain('GitLab');
expect(integration.endpoint).toContain('gitlab');
// Test connection — returns structured response
// May return success=false if GitLab requires auth token for /api/v4/version
const testResp = await apiRequest.post(`/api/v1/integrations/${id}/test`);
expect(testResp.status()).toBe(200);
const testBody = await testResp.json();
expect(typeof testBody.success).toBe('boolean');
expect(testBody.message).toBeTruthy();
// Health check — returns structured response
const healthResp = await apiRequest.get(`/api/v1/integrations/${id}/health`);
expect(healthResp.status()).toBe(200);
const healthBody = await healthResp.json();
expect(typeof healthBody.status).toBe('number');
} finally {
await cleanupIntegrations(apiRequest, [id]);
}
});
});
// ---------------------------------------------------------------------------
// 3. UI: SCM Tab
// ---------------------------------------------------------------------------
test.describe('GitLab Integration — UI Verification', () => {
test.skip(!gitlabRunning, 'GitLab not reachable');
test('SCM tab shows GitLab integration', async ({ apiRequest, liveAuthPage: page }) => {
const id = await createIntegrationViaApi(apiRequest, INTEGRATION_CONFIGS.gitlab, `ui-${runId}`);
try {
await page.goto(`${BASE}/setup/integrations/scm`, {
waitUntil: 'networkidle',
timeout: 30_000,
});
await page.waitForTimeout(2_000);
const pageContent = await page.textContent('body');
expect(pageContent).toContain('GitLab');
await snap(page, 'gitlab-scm-tab');
} finally {
await cleanupIntegrations(apiRequest, [id]);
}
});
});