Replace fixed waits with waitForAngular in UI tests

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>
This commit is contained in:
master
2026-04-02 20:31:34 +03:00
parent 624e132a61
commit 744637c7c6
10 changed files with 47 additions and 24 deletions

View File

@@ -9,7 +9,7 @@
*/
import { test, expect } from './live-auth.fixture';
import { snap } from './helpers';
import { snap, waitForAngular } from './helpers';
test.setTimeout(180_000);
@@ -179,7 +179,7 @@ test.describe('Advisory Sync — UI Verification', () => {
waitUntil: 'domcontentloaded',
timeout: 45_000,
});
await page.waitForTimeout(3_000);
await waitForAngular(page);
const content = await page.textContent('body');
expect(content?.length).toBeGreaterThan(100);
@@ -193,7 +193,7 @@ test.describe('Advisory Sync — UI Verification', () => {
waitUntil: 'domcontentloaded',
timeout: 45_000,
});
await page.waitForTimeout(3_000);
await waitForAngular(page);
const tab = page.getByRole('tab', { name: /advisory/i });
await expect(tab).toBeVisible({ timeout: 10_000 });

View File

@@ -18,6 +18,7 @@ import {
createIntegrationViaApi,
cleanupIntegrations,
snap,
waitForAngular,
} from './helpers';
const BASE = process.env['PLAYWRIGHT_BASE_URL'] || 'https://stella-ops.local';
@@ -150,7 +151,7 @@ test.describe('Error Resilience — UI Empty States', () => {
waitUntil: 'domcontentloaded',
timeout: 45_000,
});
await page.waitForTimeout(3_000);
await waitForAngular(page);
const content = await page.textContent('body');
expect(content!.length).toBeGreaterThan(50);
@@ -172,7 +173,7 @@ test.describe('Error Resilience — UI Empty States', () => {
waitUntil: 'domcontentloaded',
timeout: 45_000,
});
await page.waitForTimeout(3_000);
await waitForAngular(page);
const content = await page.textContent('body');
expect(content!.length).toBeGreaterThan(20);

View File

@@ -18,6 +18,7 @@ import {
createIntegrationViaApi,
cleanupIntegrations,
snap,
waitForAngular,
} from './helpers';
const BASE = process.env['PLAYWRIGHT_BASE_URL'] || 'https://stella-ops.local';
@@ -119,7 +120,7 @@ test.describe('GitLab Integration — UI Verification', () => {
waitUntil: 'domcontentloaded',
timeout: 45_000,
});
await page.waitForTimeout(3_000);
await waitForAngular(page);
const pageContent = await page.textContent('body');
expect(pageContent).toContain('GitLab');

View File

@@ -5,6 +5,22 @@ import type { APIRequestContext, Page } from '@playwright/test';
const SCREENSHOT_DIR = 'tests/e2e/screenshots/integrations';
/**
* Wait for Angular to bootstrap and render the app shell.
* Call after page.goto() instead of a fixed waitForTimeout.
* Waits for the sidebar navigation or any heading to appear.
*/
export async function waitForAngular(page: Page, timeoutMs = 15_000): Promise<void> {
try {
await page.waitForSelector('nav[aria-label], h1, h2, .stella-page, [class*="dashboard"]', {
timeout: timeoutMs,
});
} catch {
// If no known element appears, fall back to a delay
await page.waitForTimeout(5_000);
}
}
// ---------------------------------------------------------------------------
// Integration configs for each provider type
// ---------------------------------------------------------------------------

View File

@@ -19,6 +19,7 @@
import { execSync } from 'child_process';
import { test, expect } from './live-auth.fixture';
import { waitForAngular } from './helpers';
const SCREENSHOT_DIR = 'e2e/screenshots/integrations';
const BASE = process.env['PLAYWRIGHT_BASE_URL'] || 'https://stella-ops.local';
@@ -620,7 +621,7 @@ test.describe('Integration Services — UI Verification', () => {
test('landing page redirects to first populated tab or shows onboarding', async ({ liveAuthPage: page }) => {
await page.goto(`${BASE}/setup/integrations`, { waitUntil: 'domcontentloaded', timeout: 45_000 });
await page.waitForLoadState('domcontentloaded');
await page.waitForTimeout(3_000);
await waitForAngular(page);
const url = page.url();
// Should either redirect to a tab (registries/scm/ci) or show onboarding

View File

@@ -19,6 +19,7 @@ import {
createIntegrationViaApi,
cleanupIntegrations,
snap,
waitForAngular,
} from './helpers';
const BASE = process.env['PLAYWRIGHT_BASE_URL'] || 'https://stella-ops.local';
@@ -147,7 +148,7 @@ test.describe('Runtime Host — UI Verification', () => {
waitUntil: 'domcontentloaded',
timeout: 45_000,
});
await page.waitForTimeout(3_000);
await waitForAngular(page);
const heading = page.getByRole('heading', { name: /runtime host/i });
await expect(heading).toBeVisible({ timeout: 5_000 });

View File

@@ -18,6 +18,7 @@ import {
createIntegrationViaApi,
cleanupIntegrations,
snap,
waitForAngular,
} from './helpers';
const BASE = process.env['PLAYWRIGHT_BASE_URL'] || 'https://stella-ops.local';
@@ -48,7 +49,7 @@ test.describe('UI CRUD — Search and Filter', () => {
waitUntil: 'domcontentloaded',
timeout: 45_000,
});
await page.waitForTimeout(3_000);
await waitForAngular(page);
// Find the search input
const searchInput = page.locator('input[aria-label*="Search"], input[placeholder*="Search"]').first();
@@ -80,7 +81,7 @@ test.describe('UI CRUD — Search and Filter', () => {
waitUntil: 'domcontentloaded',
timeout: 45_000,
});
await page.waitForTimeout(3_000);
await waitForAngular(page);
const searchInput = page.locator('input[aria-label*="Search"], input[placeholder*="Search"]').first();
await expect(searchInput).toBeVisible({ timeout: 5_000 });
@@ -130,7 +131,7 @@ test.describe('UI CRUD — Sorting', () => {
waitUntil: 'domcontentloaded',
timeout: 45_000,
});
await page.waitForTimeout(3_000);
await waitForAngular(page);
// Find a sortable column header (Name is typically first)
const nameHeader = page.locator('th:has-text("Name"), th:has-text("name")').first();
@@ -171,7 +172,7 @@ test.describe('UI CRUD — Delete', () => {
waitUntil: 'domcontentloaded',
timeout: 45_000,
});
await page.waitForTimeout(3_000);
await waitForAngular(page);
const deleteBtn = page.locator('button:has-text("Delete"), button[aria-label*="delete" i]').first();
if (await deleteBtn.isVisible({ timeout: 3_000 }).catch(() => false)) {

View File

@@ -18,6 +18,7 @@ import {
createIntegrationViaApi,
cleanupIntegrations,
snap,
waitForAngular,
} from './helpers';
const BASE = process.env['PLAYWRIGHT_BASE_URL'] || 'https://stella-ops.local';
@@ -43,7 +44,7 @@ test.describe('UI Integration Detail — Harbor', () => {
waitUntil: 'domcontentloaded',
timeout: 45_000,
});
await page.waitForTimeout(3_000);
await waitForAngular(page);
const pageContent = await page.textContent('body');
expect(pageContent).toContain('Harbor');
@@ -57,7 +58,7 @@ test.describe('UI Integration Detail — Harbor', () => {
waitUntil: 'domcontentloaded',
timeout: 45_000,
});
await page.waitForTimeout(3_000);
await waitForAngular(page);
// Should display provider, type, endpoint info
const pageContent = await page.textContent('body');
@@ -74,7 +75,7 @@ test.describe('UI Integration Detail — Harbor', () => {
waitUntil: 'domcontentloaded',
timeout: 45_000,
});
await page.waitForTimeout(3_000);
await waitForAngular(page);
// StellaPageTabsComponent renders buttons with role="tab" and aria-selected
// Tab labels from HUB_DETAIL_TABS: Overview, Credentials, Scopes & Rules, Events, Health, Config Audit
@@ -99,7 +100,7 @@ test.describe('UI Integration Detail — Harbor', () => {
waitUntil: 'domcontentloaded',
timeout: 45_000,
});
await page.waitForTimeout(3_000);
await waitForAngular(page);
// Click Health tab
const healthTab = page.getByRole('tab', { name: /health/i });

View File

@@ -15,7 +15,7 @@
*/
import { test, expect } from './live-auth.fixture';
import { cleanupIntegrations, snap } from './helpers';
import { cleanupIntegrations, snap, waitForAngular } from './helpers';
const BASE = process.env['PLAYWRIGHT_BASE_URL'] || 'https://stella-ops.local';
const runId = process.env['E2E_RUN_ID'] || 'run1';
@@ -32,7 +32,7 @@ test.describe('UI Onboarding Wizard — Registry', () => {
waitUntil: 'domcontentloaded',
timeout: 45_000,
});
await page.waitForTimeout(3_000);
await waitForAngular(page);
// Should show the provider catalog or wizard
const pageContent = await page.textContent('body');
@@ -51,7 +51,7 @@ test.describe('UI Onboarding Wizard — Registry', () => {
waitUntil: 'domcontentloaded',
timeout: 45_000,
});
await page.waitForTimeout(3_000);
await waitForAngular(page);
// Look for Harbor option (could be button, pill, or card)
const harborOption = page.locator('text=Harbor').first();
@@ -68,7 +68,7 @@ test.describe('UI Onboarding Wizard — Registry', () => {
waitUntil: 'domcontentloaded',
timeout: 45_000,
});
await page.waitForTimeout(3_000);
await waitForAngular(page);
// Select Harbor first
const harborOption = page.locator('text=Harbor').first();
@@ -117,7 +117,7 @@ test.describe('UI Onboarding Wizard — SCM', () => {
waitUntil: 'domcontentloaded',
timeout: 45_000,
});
await page.waitForTimeout(3_000);
await waitForAngular(page);
const pageContent = await page.textContent('body');
const hasScmContent =
@@ -142,7 +142,7 @@ test.describe('UI Onboarding Wizard — CI/CD', () => {
waitUntil: 'domcontentloaded',
timeout: 45_000,
});
await page.waitForTimeout(3_000);
await waitForAngular(page);
const pageContent = await page.textContent('body');
const hasCiContent =

View File

@@ -20,6 +20,7 @@ import {
createIntegrationViaApi,
cleanupIntegrations,
snap,
waitForAngular,
} from './helpers';
const BASE = process.env['PLAYWRIGHT_BASE_URL'] || 'https://stella-ops.local';
@@ -172,7 +173,7 @@ test.describe('Secrets Integration — UI Verification', () => {
createdIds.push(vaultId, consulId);
await page.goto(`${BASE}/setup/integrations/secrets`, { waitUntil: 'domcontentloaded', timeout: 45_000 });
await page.waitForTimeout(3_000);
await waitForAngular(page);
// Verify the page loaded with the correct heading
const heading = page.getByRole('heading', { name: /secrets/i });
@@ -193,7 +194,7 @@ test.describe('Secrets Integration — UI Verification', () => {
waitUntil: 'domcontentloaded',
timeout: 45_000,
});
await page.waitForTimeout(3_000);
await waitForAngular(page);
// Verify detail page loaded — should show integration name
const pageContent = await page.textContent('body');