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 { test, expect } from './live-auth.fixture';
|
||||||
import { snap } from './helpers';
|
import { snap, waitForAngular } from './helpers';
|
||||||
|
|
||||||
test.setTimeout(180_000);
|
test.setTimeout(180_000);
|
||||||
|
|
||||||
@@ -179,7 +179,7 @@ test.describe('Advisory Sync — UI Verification', () => {
|
|||||||
waitUntil: 'domcontentloaded',
|
waitUntil: 'domcontentloaded',
|
||||||
timeout: 45_000,
|
timeout: 45_000,
|
||||||
});
|
});
|
||||||
await page.waitForTimeout(3_000);
|
await waitForAngular(page);
|
||||||
|
|
||||||
const content = await page.textContent('body');
|
const content = await page.textContent('body');
|
||||||
expect(content?.length).toBeGreaterThan(100);
|
expect(content?.length).toBeGreaterThan(100);
|
||||||
@@ -193,7 +193,7 @@ test.describe('Advisory Sync — UI Verification', () => {
|
|||||||
waitUntil: 'domcontentloaded',
|
waitUntil: 'domcontentloaded',
|
||||||
timeout: 45_000,
|
timeout: 45_000,
|
||||||
});
|
});
|
||||||
await page.waitForTimeout(3_000);
|
await waitForAngular(page);
|
||||||
|
|
||||||
const tab = page.getByRole('tab', { name: /advisory/i });
|
const tab = page.getByRole('tab', { name: /advisory/i });
|
||||||
await expect(tab).toBeVisible({ timeout: 10_000 });
|
await expect(tab).toBeVisible({ timeout: 10_000 });
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
createIntegrationViaApi,
|
createIntegrationViaApi,
|
||||||
cleanupIntegrations,
|
cleanupIntegrations,
|
||||||
snap,
|
snap,
|
||||||
|
waitForAngular,
|
||||||
} from './helpers';
|
} from './helpers';
|
||||||
|
|
||||||
const BASE = process.env['PLAYWRIGHT_BASE_URL'] || 'https://stella-ops.local';
|
const BASE = process.env['PLAYWRIGHT_BASE_URL'] || 'https://stella-ops.local';
|
||||||
@@ -150,7 +151,7 @@ test.describe('Error Resilience — UI Empty States', () => {
|
|||||||
waitUntil: 'domcontentloaded',
|
waitUntil: 'domcontentloaded',
|
||||||
timeout: 45_000,
|
timeout: 45_000,
|
||||||
});
|
});
|
||||||
await page.waitForTimeout(3_000);
|
await waitForAngular(page);
|
||||||
|
|
||||||
const content = await page.textContent('body');
|
const content = await page.textContent('body');
|
||||||
expect(content!.length).toBeGreaterThan(50);
|
expect(content!.length).toBeGreaterThan(50);
|
||||||
@@ -172,7 +173,7 @@ test.describe('Error Resilience — UI Empty States', () => {
|
|||||||
waitUntil: 'domcontentloaded',
|
waitUntil: 'domcontentloaded',
|
||||||
timeout: 45_000,
|
timeout: 45_000,
|
||||||
});
|
});
|
||||||
await page.waitForTimeout(3_000);
|
await waitForAngular(page);
|
||||||
|
|
||||||
const content = await page.textContent('body');
|
const content = await page.textContent('body');
|
||||||
expect(content!.length).toBeGreaterThan(20);
|
expect(content!.length).toBeGreaterThan(20);
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
createIntegrationViaApi,
|
createIntegrationViaApi,
|
||||||
cleanupIntegrations,
|
cleanupIntegrations,
|
||||||
snap,
|
snap,
|
||||||
|
waitForAngular,
|
||||||
} from './helpers';
|
} from './helpers';
|
||||||
|
|
||||||
const BASE = process.env['PLAYWRIGHT_BASE_URL'] || 'https://stella-ops.local';
|
const BASE = process.env['PLAYWRIGHT_BASE_URL'] || 'https://stella-ops.local';
|
||||||
@@ -119,7 +120,7 @@ test.describe('GitLab Integration — UI Verification', () => {
|
|||||||
waitUntil: 'domcontentloaded',
|
waitUntil: 'domcontentloaded',
|
||||||
timeout: 45_000,
|
timeout: 45_000,
|
||||||
});
|
});
|
||||||
await page.waitForTimeout(3_000);
|
await waitForAngular(page);
|
||||||
|
|
||||||
const pageContent = await page.textContent('body');
|
const pageContent = await page.textContent('body');
|
||||||
expect(pageContent).toContain('GitLab');
|
expect(pageContent).toContain('GitLab');
|
||||||
|
|||||||
@@ -5,6 +5,22 @@ import type { APIRequestContext, Page } from '@playwright/test';
|
|||||||
|
|
||||||
const SCREENSHOT_DIR = 'tests/e2e/screenshots/integrations';
|
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
|
// Integration configs for each provider type
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
import { execSync } from 'child_process';
|
import { execSync } from 'child_process';
|
||||||
import { test, expect } from './live-auth.fixture';
|
import { test, expect } from './live-auth.fixture';
|
||||||
|
import { waitForAngular } from './helpers';
|
||||||
|
|
||||||
const SCREENSHOT_DIR = 'e2e/screenshots/integrations';
|
const SCREENSHOT_DIR = 'e2e/screenshots/integrations';
|
||||||
const BASE = process.env['PLAYWRIGHT_BASE_URL'] || 'https://stella-ops.local';
|
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 }) => {
|
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.goto(`${BASE}/setup/integrations`, { waitUntil: 'domcontentloaded', timeout: 45_000 });
|
||||||
await page.waitForLoadState('domcontentloaded');
|
await page.waitForLoadState('domcontentloaded');
|
||||||
await page.waitForTimeout(3_000);
|
await waitForAngular(page);
|
||||||
|
|
||||||
const url = page.url();
|
const url = page.url();
|
||||||
// Should either redirect to a tab (registries/scm/ci) or show onboarding
|
// Should either redirect to a tab (registries/scm/ci) or show onboarding
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
createIntegrationViaApi,
|
createIntegrationViaApi,
|
||||||
cleanupIntegrations,
|
cleanupIntegrations,
|
||||||
snap,
|
snap,
|
||||||
|
waitForAngular,
|
||||||
} from './helpers';
|
} from './helpers';
|
||||||
|
|
||||||
const BASE = process.env['PLAYWRIGHT_BASE_URL'] || 'https://stella-ops.local';
|
const BASE = process.env['PLAYWRIGHT_BASE_URL'] || 'https://stella-ops.local';
|
||||||
@@ -147,7 +148,7 @@ test.describe('Runtime Host — UI Verification', () => {
|
|||||||
waitUntil: 'domcontentloaded',
|
waitUntil: 'domcontentloaded',
|
||||||
timeout: 45_000,
|
timeout: 45_000,
|
||||||
});
|
});
|
||||||
await page.waitForTimeout(3_000);
|
await waitForAngular(page);
|
||||||
|
|
||||||
const heading = page.getByRole('heading', { name: /runtime host/i });
|
const heading = page.getByRole('heading', { name: /runtime host/i });
|
||||||
await expect(heading).toBeVisible({ timeout: 5_000 });
|
await expect(heading).toBeVisible({ timeout: 5_000 });
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
createIntegrationViaApi,
|
createIntegrationViaApi,
|
||||||
cleanupIntegrations,
|
cleanupIntegrations,
|
||||||
snap,
|
snap,
|
||||||
|
waitForAngular,
|
||||||
} from './helpers';
|
} from './helpers';
|
||||||
|
|
||||||
const BASE = process.env['PLAYWRIGHT_BASE_URL'] || 'https://stella-ops.local';
|
const BASE = process.env['PLAYWRIGHT_BASE_URL'] || 'https://stella-ops.local';
|
||||||
@@ -48,7 +49,7 @@ test.describe('UI CRUD — Search and Filter', () => {
|
|||||||
waitUntil: 'domcontentloaded',
|
waitUntil: 'domcontentloaded',
|
||||||
timeout: 45_000,
|
timeout: 45_000,
|
||||||
});
|
});
|
||||||
await page.waitForTimeout(3_000);
|
await waitForAngular(page);
|
||||||
|
|
||||||
// Find the search input
|
// Find the search input
|
||||||
const searchInput = page.locator('input[aria-label*="Search"], input[placeholder*="Search"]').first();
|
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',
|
waitUntil: 'domcontentloaded',
|
||||||
timeout: 45_000,
|
timeout: 45_000,
|
||||||
});
|
});
|
||||||
await page.waitForTimeout(3_000);
|
await waitForAngular(page);
|
||||||
|
|
||||||
const searchInput = page.locator('input[aria-label*="Search"], input[placeholder*="Search"]').first();
|
const searchInput = page.locator('input[aria-label*="Search"], input[placeholder*="Search"]').first();
|
||||||
await expect(searchInput).toBeVisible({ timeout: 5_000 });
|
await expect(searchInput).toBeVisible({ timeout: 5_000 });
|
||||||
@@ -130,7 +131,7 @@ test.describe('UI CRUD — Sorting', () => {
|
|||||||
waitUntil: 'domcontentloaded',
|
waitUntil: 'domcontentloaded',
|
||||||
timeout: 45_000,
|
timeout: 45_000,
|
||||||
});
|
});
|
||||||
await page.waitForTimeout(3_000);
|
await waitForAngular(page);
|
||||||
|
|
||||||
// Find a sortable column header (Name is typically first)
|
// Find a sortable column header (Name is typically first)
|
||||||
const nameHeader = page.locator('th:has-text("Name"), th:has-text("name")').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',
|
waitUntil: 'domcontentloaded',
|
||||||
timeout: 45_000,
|
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();
|
const deleteBtn = page.locator('button:has-text("Delete"), button[aria-label*="delete" i]').first();
|
||||||
if (await deleteBtn.isVisible({ timeout: 3_000 }).catch(() => false)) {
|
if (await deleteBtn.isVisible({ timeout: 3_000 }).catch(() => false)) {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
createIntegrationViaApi,
|
createIntegrationViaApi,
|
||||||
cleanupIntegrations,
|
cleanupIntegrations,
|
||||||
snap,
|
snap,
|
||||||
|
waitForAngular,
|
||||||
} from './helpers';
|
} from './helpers';
|
||||||
|
|
||||||
const BASE = process.env['PLAYWRIGHT_BASE_URL'] || 'https://stella-ops.local';
|
const BASE = process.env['PLAYWRIGHT_BASE_URL'] || 'https://stella-ops.local';
|
||||||
@@ -43,7 +44,7 @@ test.describe('UI Integration Detail — Harbor', () => {
|
|||||||
waitUntil: 'domcontentloaded',
|
waitUntil: 'domcontentloaded',
|
||||||
timeout: 45_000,
|
timeout: 45_000,
|
||||||
});
|
});
|
||||||
await page.waitForTimeout(3_000);
|
await waitForAngular(page);
|
||||||
|
|
||||||
const pageContent = await page.textContent('body');
|
const pageContent = await page.textContent('body');
|
||||||
expect(pageContent).toContain('Harbor');
|
expect(pageContent).toContain('Harbor');
|
||||||
@@ -57,7 +58,7 @@ test.describe('UI Integration Detail — Harbor', () => {
|
|||||||
waitUntil: 'domcontentloaded',
|
waitUntil: 'domcontentloaded',
|
||||||
timeout: 45_000,
|
timeout: 45_000,
|
||||||
});
|
});
|
||||||
await page.waitForTimeout(3_000);
|
await waitForAngular(page);
|
||||||
|
|
||||||
// Should display provider, type, endpoint info
|
// Should display provider, type, endpoint info
|
||||||
const pageContent = await page.textContent('body');
|
const pageContent = await page.textContent('body');
|
||||||
@@ -74,7 +75,7 @@ test.describe('UI Integration Detail — Harbor', () => {
|
|||||||
waitUntil: 'domcontentloaded',
|
waitUntil: 'domcontentloaded',
|
||||||
timeout: 45_000,
|
timeout: 45_000,
|
||||||
});
|
});
|
||||||
await page.waitForTimeout(3_000);
|
await waitForAngular(page);
|
||||||
|
|
||||||
// StellaPageTabsComponent renders buttons with role="tab" and aria-selected
|
// StellaPageTabsComponent renders buttons with role="tab" and aria-selected
|
||||||
// Tab labels from HUB_DETAIL_TABS: Overview, Credentials, Scopes & Rules, Events, Health, Config Audit
|
// 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',
|
waitUntil: 'domcontentloaded',
|
||||||
timeout: 45_000,
|
timeout: 45_000,
|
||||||
});
|
});
|
||||||
await page.waitForTimeout(3_000);
|
await waitForAngular(page);
|
||||||
|
|
||||||
// Click Health tab
|
// Click Health tab
|
||||||
const healthTab = page.getByRole('tab', { name: /health/i });
|
const healthTab = page.getByRole('tab', { name: /health/i });
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { test, expect } from './live-auth.fixture';
|
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 BASE = process.env['PLAYWRIGHT_BASE_URL'] || 'https://stella-ops.local';
|
||||||
const runId = process.env['E2E_RUN_ID'] || 'run1';
|
const runId = process.env['E2E_RUN_ID'] || 'run1';
|
||||||
@@ -32,7 +32,7 @@ test.describe('UI Onboarding Wizard — Registry', () => {
|
|||||||
waitUntil: 'domcontentloaded',
|
waitUntil: 'domcontentloaded',
|
||||||
timeout: 45_000,
|
timeout: 45_000,
|
||||||
});
|
});
|
||||||
await page.waitForTimeout(3_000);
|
await waitForAngular(page);
|
||||||
|
|
||||||
// Should show the provider catalog or wizard
|
// Should show the provider catalog or wizard
|
||||||
const pageContent = await page.textContent('body');
|
const pageContent = await page.textContent('body');
|
||||||
@@ -51,7 +51,7 @@ test.describe('UI Onboarding Wizard — Registry', () => {
|
|||||||
waitUntil: 'domcontentloaded',
|
waitUntil: 'domcontentloaded',
|
||||||
timeout: 45_000,
|
timeout: 45_000,
|
||||||
});
|
});
|
||||||
await page.waitForTimeout(3_000);
|
await waitForAngular(page);
|
||||||
|
|
||||||
// Look for Harbor option (could be button, pill, or card)
|
// Look for Harbor option (could be button, pill, or card)
|
||||||
const harborOption = page.locator('text=Harbor').first();
|
const harborOption = page.locator('text=Harbor').first();
|
||||||
@@ -68,7 +68,7 @@ test.describe('UI Onboarding Wizard — Registry', () => {
|
|||||||
waitUntil: 'domcontentloaded',
|
waitUntil: 'domcontentloaded',
|
||||||
timeout: 45_000,
|
timeout: 45_000,
|
||||||
});
|
});
|
||||||
await page.waitForTimeout(3_000);
|
await waitForAngular(page);
|
||||||
|
|
||||||
// Select Harbor first
|
// Select Harbor first
|
||||||
const harborOption = page.locator('text=Harbor').first();
|
const harborOption = page.locator('text=Harbor').first();
|
||||||
@@ -117,7 +117,7 @@ test.describe('UI Onboarding Wizard — SCM', () => {
|
|||||||
waitUntil: 'domcontentloaded',
|
waitUntil: 'domcontentloaded',
|
||||||
timeout: 45_000,
|
timeout: 45_000,
|
||||||
});
|
});
|
||||||
await page.waitForTimeout(3_000);
|
await waitForAngular(page);
|
||||||
|
|
||||||
const pageContent = await page.textContent('body');
|
const pageContent = await page.textContent('body');
|
||||||
const hasScmContent =
|
const hasScmContent =
|
||||||
@@ -142,7 +142,7 @@ test.describe('UI Onboarding Wizard — CI/CD', () => {
|
|||||||
waitUntil: 'domcontentloaded',
|
waitUntil: 'domcontentloaded',
|
||||||
timeout: 45_000,
|
timeout: 45_000,
|
||||||
});
|
});
|
||||||
await page.waitForTimeout(3_000);
|
await waitForAngular(page);
|
||||||
|
|
||||||
const pageContent = await page.textContent('body');
|
const pageContent = await page.textContent('body');
|
||||||
const hasCiContent =
|
const hasCiContent =
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
createIntegrationViaApi,
|
createIntegrationViaApi,
|
||||||
cleanupIntegrations,
|
cleanupIntegrations,
|
||||||
snap,
|
snap,
|
||||||
|
waitForAngular,
|
||||||
} from './helpers';
|
} from './helpers';
|
||||||
|
|
||||||
const BASE = process.env['PLAYWRIGHT_BASE_URL'] || 'https://stella-ops.local';
|
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);
|
createdIds.push(vaultId, consulId);
|
||||||
|
|
||||||
await page.goto(`${BASE}/setup/integrations/secrets`, { waitUntil: 'domcontentloaded', timeout: 45_000 });
|
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
|
// Verify the page loaded with the correct heading
|
||||||
const heading = page.getByRole('heading', { name: /secrets/i });
|
const heading = page.getByRole('heading', { name: /secrets/i });
|
||||||
@@ -193,7 +194,7 @@ test.describe('Secrets Integration — UI Verification', () => {
|
|||||||
waitUntil: 'domcontentloaded',
|
waitUntil: 'domcontentloaded',
|
||||||
timeout: 45_000,
|
timeout: 45_000,
|
||||||
});
|
});
|
||||||
await page.waitForTimeout(3_000);
|
await waitForAngular(page);
|
||||||
|
|
||||||
// Verify detail page loaded — should show integration name
|
// Verify detail page loaded — should show integration name
|
||||||
const pageContent = await page.textContent('body');
|
const pageContent = await page.textContent('body');
|
||||||
|
|||||||
Reference in New Issue
Block a user