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:
@@ -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 });
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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');
|
||||
|
||||
Reference in New Issue
Block a user