Upgrade waitForAngular to wait for route content, fix remaining UI tests
The generic waitForAngular matched the sidebar nav immediately but route content (tables, tabs, forms) hadn't rendered yet. Updated waitForAngular selector to wait for route-level elements: stella-page-tabs, .integration-list, .source-catalog, table tbody tr, h1, [role=tablist], .detail-grid, .wizard-step, form. Also fixed activity-timeline and pagination tests (still had waitForTimeout(2_000) instead of waitForAngular). Increased fallback timeout from 5s to 8s for slow-loading pages. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -13,7 +13,7 @@
|
||||
*/
|
||||
|
||||
import { test, expect } from './live-auth.fixture';
|
||||
import { snap } from './helpers';
|
||||
import { snap, waitForAngular } from './helpers';
|
||||
|
||||
const BASE = process.env['PLAYWRIGHT_BASE_URL'] || 'https://stella-ops.local';
|
||||
|
||||
@@ -27,7 +27,7 @@ test.describe('Activity Timeline — Page Load', () => {
|
||||
waitUntil: 'domcontentloaded',
|
||||
timeout: 45_000,
|
||||
});
|
||||
await page.waitForTimeout(2_000);
|
||||
await waitForAngular(page);
|
||||
|
||||
// Should show activity timeline or related content
|
||||
const pageContent = await page.textContent('body');
|
||||
@@ -46,7 +46,7 @@ test.describe('Activity Timeline — Page Load', () => {
|
||||
waitUntil: 'domcontentloaded',
|
||||
timeout: 45_000,
|
||||
});
|
||||
await page.waitForTimeout(2_000);
|
||||
await waitForAngular(page);
|
||||
|
||||
// Look for the timeline container or activity items
|
||||
const timeline = page.locator('.activity-timeline, .activity-list, [class*="activity"]').first();
|
||||
@@ -70,7 +70,7 @@ test.describe('Activity Timeline — Stats', () => {
|
||||
waitUntil: 'domcontentloaded',
|
||||
timeout: 45_000,
|
||||
});
|
||||
await page.waitForTimeout(2_000);
|
||||
await waitForAngular(page);
|
||||
|
||||
const content = await page.textContent('body');
|
||||
|
||||
@@ -97,7 +97,7 @@ test.describe('Activity Timeline — Items', () => {
|
||||
waitUntil: 'domcontentloaded',
|
||||
timeout: 45_000,
|
||||
});
|
||||
await page.waitForTimeout(2_000);
|
||||
await waitForAngular(page);
|
||||
|
||||
// Look for individual activity/event items
|
||||
const items = page.locator('.activity-item, .event-item, [class*="activity-item"]');
|
||||
@@ -125,7 +125,7 @@ test.describe('Activity Timeline — Filters', () => {
|
||||
waitUntil: 'domcontentloaded',
|
||||
timeout: 45_000,
|
||||
});
|
||||
await page.waitForTimeout(2_000);
|
||||
await waitForAngular(page);
|
||||
|
||||
// Look for filter dropdowns
|
||||
const filterSelect = page.locator('.filter-select, select, [class*="filter"]').first();
|
||||
@@ -145,7 +145,7 @@ test.describe('Activity Timeline — Filters', () => {
|
||||
waitUntil: 'domcontentloaded',
|
||||
timeout: 45_000,
|
||||
});
|
||||
await page.waitForTimeout(2_000);
|
||||
await waitForAngular(page);
|
||||
|
||||
// Apply a filter first
|
||||
const filterSelect = page.locator('.filter-select, select').first();
|
||||
@@ -175,12 +175,12 @@ test.describe('Activity Timeline — Navigation', () => {
|
||||
waitUntil: 'domcontentloaded',
|
||||
timeout: 45_000,
|
||||
});
|
||||
await page.waitForTimeout(2_000);
|
||||
await waitForAngular(page);
|
||||
|
||||
const backLink = page.locator('.back-link, a:has-text("Back"), a:has-text("Integrations")').first();
|
||||
if (await backLink.isVisible({ timeout: 3_000 }).catch(() => false)) {
|
||||
await backLink.click();
|
||||
await page.waitForTimeout(2_000);
|
||||
await waitForAngular(page);
|
||||
|
||||
// Should navigate back to integrations
|
||||
const url = page.url();
|
||||
|
||||
@@ -6,18 +6,34 @@ import type { APIRequestContext, Page } from '@playwright/test';
|
||||
const SCREENSHOT_DIR = 'tests/e2e/screenshots/integrations';
|
||||
|
||||
/**
|
||||
* Wait for Angular to bootstrap and render the app shell.
|
||||
* Wait for Angular to bootstrap and render route content.
|
||||
* Call after page.goto() instead of a fixed waitForTimeout.
|
||||
* Waits for the sidebar navigation or any heading to appear.
|
||||
*
|
||||
* Waits for route-level content (tables, tab bars, forms, headings inside main)
|
||||
* not just the shell sidebar. Falls back to 8s delay if no element matches.
|
||||
*/
|
||||
export async function waitForAngular(page: Page, timeoutMs = 15_000): Promise<void> {
|
||||
export async function waitForAngular(page: Page, timeoutMs = 20_000): Promise<void> {
|
||||
try {
|
||||
await page.waitForSelector('nav[aria-label], h1, h2, .stella-page, [class*="dashboard"]', {
|
||||
timeout: timeoutMs,
|
||||
});
|
||||
// Wait for content that only appears AFTER the route component renders.
|
||||
// The shell sidebar (nav) loads first, but we need the route content.
|
||||
await page.waitForSelector(
|
||||
[
|
||||
'stella-page-tabs', // Integration hub tabs
|
||||
'.integration-list', // Integration list table
|
||||
'.source-catalog', // Advisory source catalog
|
||||
'.activity-timeline', // Activity timeline
|
||||
'table tbody tr', // Any data table with rows
|
||||
'.detail-grid', // Detail page grid
|
||||
'.wizard-step', // Onboarding wizard
|
||||
'form', // Any form
|
||||
'h1', // Page heading (rendered by route component)
|
||||
'[role="tablist"]', // Tab bar (integration shell)
|
||||
].join(', '),
|
||||
{ timeout: timeoutMs },
|
||||
);
|
||||
} catch {
|
||||
// If no known element appears, fall back to a delay
|
||||
await page.waitForTimeout(5_000);
|
||||
// Last resort: wait 8s for slow pages
|
||||
await page.waitForTimeout(8_000);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -635,7 +635,7 @@ test.describe('Integration Services — UI Verification', () => {
|
||||
|
||||
test('Registries tab lists registry integrations', async ({ liveAuthPage: page }) => {
|
||||
await page.goto(`${BASE}/setup/integrations/registries`, { waitUntil: 'domcontentloaded', timeout: 45_000 });
|
||||
await page.waitForTimeout(2_000);
|
||||
await waitForAngular(page);
|
||||
|
||||
const heading = page.getByRole('heading', { name: /registry/i });
|
||||
await expect(heading).toBeVisible({ timeout: 5_000 });
|
||||
@@ -650,7 +650,7 @@ test.describe('Integration Services — UI Verification', () => {
|
||||
|
||||
test('SCM tab lists SCM integrations', async ({ liveAuthPage: page }) => {
|
||||
await page.goto(`${BASE}/setup/integrations/scm`, { waitUntil: 'domcontentloaded', timeout: 45_000 });
|
||||
await page.waitForTimeout(2_000);
|
||||
await waitForAngular(page);
|
||||
|
||||
const heading = page.getByRole('heading', { name: /scm/i });
|
||||
await expect(heading).toBeVisible({ timeout: 5_000 });
|
||||
@@ -664,7 +664,7 @@ test.describe('Integration Services — UI Verification', () => {
|
||||
|
||||
test('CI/CD tab lists CI/CD integrations', async ({ liveAuthPage: page }) => {
|
||||
await page.goto(`${BASE}/setup/integrations/ci`, { waitUntil: 'domcontentloaded', timeout: 45_000 });
|
||||
await page.waitForTimeout(2_000);
|
||||
await waitForAngular(page);
|
||||
|
||||
const heading = page.getByRole('heading', { name: /ci\/cd/i });
|
||||
await expect(heading).toBeVisible({ timeout: 5_000 });
|
||||
@@ -678,7 +678,7 @@ test.describe('Integration Services — UI Verification', () => {
|
||||
|
||||
test('tab switching navigates between all tabs', async ({ liveAuthPage: page }) => {
|
||||
await page.goto(`${BASE}/setup/integrations`, { waitUntil: 'domcontentloaded', timeout: 45_000 });
|
||||
await page.waitForTimeout(2_000);
|
||||
await waitForAngular(page);
|
||||
|
||||
const tabs = ['Registries', 'SCM', 'CI/CD', 'Runtimes / Hosts', 'Advisory & VEX', 'Secrets'];
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import { test, expect } from './live-auth.fixture';
|
||||
import {
|
||||
INTEGRATION_CONFIGS,
|
||||
createIntegrationViaApi,
|
||||
waitForAngular,
|
||||
cleanupIntegrations,
|
||||
snap,
|
||||
} from './helpers';
|
||||
@@ -131,10 +132,10 @@ test.describe('Pagination — API', () => {
|
||||
test.describe('Pagination — UI Pager', () => {
|
||||
test('pager info renders on registries tab', async ({ liveAuthPage: page }) => {
|
||||
await page.goto(`${BASE}/setup/integrations/registries`, {
|
||||
waitUntil: 'networkidle',
|
||||
timeout: 30_000,
|
||||
waitUntil: 'domcontentloaded',
|
||||
timeout: 45_000,
|
||||
});
|
||||
await page.waitForTimeout(2_000);
|
||||
await waitForAngular(page);
|
||||
|
||||
// The pager should show "X total · page Y of Z"
|
||||
const pagerInfo = page.locator('.pager__info');
|
||||
@@ -151,10 +152,10 @@ test.describe('Pagination — UI Pager', () => {
|
||||
|
||||
test('pager controls are present', async ({ liveAuthPage: page }) => {
|
||||
await page.goto(`${BASE}/setup/integrations/registries`, {
|
||||
waitUntil: 'networkidle',
|
||||
timeout: 30_000,
|
||||
waitUntil: 'domcontentloaded',
|
||||
timeout: 45_000,
|
||||
});
|
||||
await page.waitForTimeout(2_000);
|
||||
await waitForAngular(page);
|
||||
|
||||
// Check for pagination navigation
|
||||
const pager = page.locator('.pager');
|
||||
|
||||
Reference in New Issue
Block a user