diff --git a/src/Web/StellaOps.Web/tests/e2e/integrations/aaa-advisory-sync.e2e.spec.ts b/src/Web/StellaOps.Web/tests/e2e/integrations/aaa-advisory-sync.e2e.spec.ts index 8b75cb740..bfd7c6d93 100644 --- a/src/Web/StellaOps.Web/tests/e2e/integrations/aaa-advisory-sync.e2e.spec.ts +++ b/src/Web/StellaOps.Web/tests/e2e/integrations/aaa-advisory-sync.e2e.spec.ts @@ -181,10 +181,11 @@ test.describe('Advisory Sync — UI Verification', () => { }); await waitForAngular(page); - const content = await page.textContent('body'); - expect(content?.length).toBeGreaterThan(100); - const hasContent = content?.includes('Advisory') || content?.includes('Source') || content?.includes('NVD'); - expect(hasContent).toBe(true); + await expect(page.locator('body')).not.toBeEmpty({ timeout: 15_000 }); + // Wait for at least one of these keywords to appear (auto-retry) + await expect( + page.locator('text=Advisory').or(page.locator('text=Source')).or(page.locator('text=NVD')).first(), + ).toBeVisible({ timeout: 15_000 }); await snap(page, 'advisory-vex-sources-tab'); }); @@ -196,11 +197,9 @@ test.describe('Advisory Sync — UI Verification', () => { await waitForAngular(page); const tab = page.getByRole('tab', { name: /advisory/i }); - await expect(tab).toBeVisible({ timeout: 10_000 }); + await expect(tab).toBeVisible({ timeout: 15_000 }); await tab.click(); - await page.waitForTimeout(2_000); - - expect(page.url()).toContain('advisory-vex-sources'); + await expect(page).toHaveURL(/advisory-vex-sources/, { timeout: 15_000 }); await snap(page, 'advisory-tab-selected'); }); @@ -209,24 +208,17 @@ test.describe('Advisory Sync — UI Verification', () => { waitUntil: 'domcontentloaded', timeout: 45_000, }); - await page.waitForTimeout(4_000); + await waitForAngular(page); - const content = await page.textContent('body'); + // Stats bar should show advisory counts (from the new aggregation report) — auto-retry + await expect( + page.locator('text=advisories').or(page.locator('text=enabled')).or(page.locator('text=healthy')).first(), + ).toBeVisible({ timeout: 15_000 }); - // Stats bar should show advisory counts (from the new aggregation report) - const hasStats = - content?.includes('advisories') || - content?.includes('enabled') || - content?.includes('healthy'); - expect(hasStats, 'Stats bar should display aggregation metrics').toBe(true); - - // Per-source rows should show advisory counts or freshness badges - const hasSourceData = - content?.includes('ago') || // freshness pill: "2h ago", "3d ago" - content?.includes('never') || // freshness pill: "never" - content?.includes('healthy') || // freshness status - content?.includes('stale'); - expect(hasSourceData, 'Source rows should show freshness data').toBe(true); + // Per-source rows should show advisory counts or freshness badges — auto-retry + await expect( + page.locator('text=ago').or(page.locator('text=never')).or(page.locator('text=healthy')).or(page.locator('text=stale')).first(), + ).toBeVisible({ timeout: 15_000 }); await snap(page, 'advisory-catalog-aggregation'); }); diff --git a/src/Web/StellaOps.Web/tests/e2e/integrations/activity-timeline.e2e.spec.ts b/src/Web/StellaOps.Web/tests/e2e/integrations/activity-timeline.e2e.spec.ts index e0d63ef7b..ced5b8b92 100644 --- a/src/Web/StellaOps.Web/tests/e2e/integrations/activity-timeline.e2e.spec.ts +++ b/src/Web/StellaOps.Web/tests/e2e/integrations/activity-timeline.e2e.spec.ts @@ -29,14 +29,10 @@ test.describe('Activity Timeline — Page Load', () => { }); await waitForAngular(page); - // Should show activity timeline or related content - const pageContent = await page.textContent('body'); - const hasActivityContent = - pageContent?.includes('Activity') || - pageContent?.includes('Timeline') || - pageContent?.includes('Events') || - pageContent?.includes('activity'); - expect(hasActivityContent, 'Activity page should render timeline content').toBe(true); + // Should show activity timeline or related content — auto-retry + await expect( + page.locator('text=Activity').or(page.locator('text=Timeline')).or(page.locator('text=Events')).or(page.locator('text=activity')).first(), + ).toBeVisible({ timeout: 15_000 }); await snap(page, 'activity-01-loaded'); }); @@ -72,16 +68,10 @@ test.describe('Activity Timeline — Stats', () => { }); await waitForAngular(page); - const content = await page.textContent('body'); - - // The stats bar should show categories like Events, Success, Degraded, Failures - // At minimum, some numeric content should be visible - const hasStats = - content?.includes('Events') || - content?.includes('Success') || - content?.includes('Failures') || - content?.includes('Total'); - expect(hasStats, 'Stats bar should display event categories').toBe(true); + // The stats bar should show categories like Events, Success, Degraded, Failures — auto-retry + await expect( + page.locator('text=Events').or(page.locator('text=Success')).or(page.locator('text=Failures')).or(page.locator('text=Total')).first(), + ).toBeVisible({ timeout: 15_000 }); await snap(page, 'activity-03-stats'); }); diff --git a/src/Web/StellaOps.Web/tests/e2e/integrations/gitlab-integration.e2e.spec.ts b/src/Web/StellaOps.Web/tests/e2e/integrations/gitlab-integration.e2e.spec.ts index 7714c5c74..81b840b20 100644 --- a/src/Web/StellaOps.Web/tests/e2e/integrations/gitlab-integration.e2e.spec.ts +++ b/src/Web/StellaOps.Web/tests/e2e/integrations/gitlab-integration.e2e.spec.ts @@ -122,8 +122,7 @@ test.describe('GitLab Integration — UI Verification', () => { }); await waitForAngular(page); - const pageContent = await page.textContent('body'); - expect(pageContent).toContain('GitLab'); + await expect(page.locator('text=GitLab').first()).toBeVisible({ timeout: 15_000 }); await snap(page, 'gitlab-scm-tab'); } finally { diff --git a/src/Web/StellaOps.Web/tests/e2e/integrations/integrations.e2e.spec.ts b/src/Web/StellaOps.Web/tests/e2e/integrations/integrations.e2e.spec.ts index 222925919..bf3cd09c3 100644 --- a/src/Web/StellaOps.Web/tests/e2e/integrations/integrations.e2e.spec.ts +++ b/src/Web/StellaOps.Web/tests/e2e/integrations/integrations.e2e.spec.ts @@ -620,16 +620,22 @@ test.describe('Integration Services — Connector CRUD & Status', () => { 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 waitForAngular(page); + // Wait for Angular to redirect to a tab (happens after loadCounts() completes) + await page.waitForFunction( + () => window.location.pathname.includes('/registries') || + window.location.pathname.includes('/scm') || + window.location.pathname.includes('/ci') || + document.body.textContent?.includes('Get Started'), + { timeout: 15_000 }, + ).catch(() => {}); + const url = page.url(); - // Should either redirect to a tab (registries/scm/ci) or show onboarding const isOnTab = url.includes('/registries') || url.includes('/scm') || url.includes('/ci'); const hasOnboarding = await page.locator('text=/Get Started/').isVisible().catch(() => false); expect(isOnTab || hasOnboarding, 'Should redirect to tab or show onboarding').toBe(true); - await snap(page, '01-landing'); }); @@ -637,13 +643,9 @@ test.describe('Integration Services — UI Verification', () => { await page.goto(`${BASE}/setup/integrations/registries`, { waitUntil: 'domcontentloaded', timeout: 45_000 }); await waitForAngular(page); - const heading = page.getByRole('heading', { name: /registry/i }); - await expect(heading).toBeVisible({ timeout: 5_000 }); - - // Should have at least one row in the table + // Wait for table data to load (auto-retry with timeout) const rows = page.locator('table tbody tr'); - const count = await rows.count(); - expect(count).toBeGreaterThanOrEqual(1); + await expect(rows.first()).toBeVisible({ timeout: 15_000 }); await snap(page, '02-registries-tab'); }); @@ -652,12 +654,8 @@ test.describe('Integration Services — UI Verification', () => { await page.goto(`${BASE}/setup/integrations/scm`, { waitUntil: 'domcontentloaded', timeout: 45_000 }); await waitForAngular(page); - const heading = page.getByRole('heading', { name: /scm/i }); - await expect(heading).toBeVisible({ timeout: 5_000 }); - const rows = page.locator('table tbody tr'); - const count = await rows.count(); - expect(count).toBeGreaterThanOrEqual(1); + await expect(rows.first()).toBeVisible({ timeout: 15_000 }); await snap(page, '03-scm-tab'); }); @@ -666,12 +664,8 @@ test.describe('Integration Services — UI Verification', () => { await page.goto(`${BASE}/setup/integrations/ci`, { waitUntil: 'domcontentloaded', timeout: 45_000 }); await waitForAngular(page); - const heading = page.getByRole('heading', { name: /ci\/cd/i }); - await expect(heading).toBeVisible({ timeout: 5_000 }); - const rows = page.locator('table tbody tr'); - const count = await rows.count(); - expect(count).toBeGreaterThanOrEqual(1); + await expect(rows.first()).toBeVisible({ timeout: 15_000 }); await snap(page, '04-cicd-tab'); }); diff --git a/src/Web/StellaOps.Web/tests/e2e/integrations/pagination.e2e.spec.ts b/src/Web/StellaOps.Web/tests/e2e/integrations/pagination.e2e.spec.ts index 238fec6db..7390d0064 100644 --- a/src/Web/StellaOps.Web/tests/e2e/integrations/pagination.e2e.spec.ts +++ b/src/Web/StellaOps.Web/tests/e2e/integrations/pagination.e2e.spec.ts @@ -137,15 +137,11 @@ test.describe('Pagination — UI Pager', () => { }); await waitForAngular(page); - // The pager should show "X total · page Y of Z" + // The pager should show "X total . page Y of Z" — auto-retry const pagerInfo = page.locator('.pager__info'); - const isVisible = await pagerInfo.isVisible({ timeout: 5_000 }).catch(() => false); - - if (isVisible) { - const text = await pagerInfo.textContent(); - expect(text).toContain('total'); - expect(text).toContain('page'); - } + await expect(pagerInfo).toBeVisible({ timeout: 15_000 }); + await expect(pagerInfo).toContainText('total', { timeout: 15_000 }); + await expect(pagerInfo).toContainText('page', { timeout: 15_000 }); await snap(page, 'pagination-ui-pager'); }); @@ -157,17 +153,15 @@ test.describe('Pagination — UI Pager', () => { }); await waitForAngular(page); - // Check for pagination navigation + // Check for pagination navigation — auto-retry const pager = page.locator('.pager'); - const isVisible = await pager.isVisible({ timeout: 5_000 }).catch(() => false); + await expect(pager).toBeVisible({ timeout: 15_000 }); - if (isVisible) { - // Should have navigation buttons - const firstBtn = page.locator('button[title="First page"]'); - const lastBtn = page.locator('button[title="Last page"]'); - await expect(firstBtn).toBeVisible({ timeout: 3_000 }); - await expect(lastBtn).toBeVisible({ timeout: 3_000 }); - } + // Should have navigation buttons + const firstBtn = page.locator('button[title="First page"]'); + const lastBtn = page.locator('button[title="Last page"]'); + await expect(firstBtn).toBeVisible({ timeout: 15_000 }); + await expect(lastBtn).toBeVisible({ timeout: 15_000 }); await snap(page, 'pagination-ui-controls'); }); diff --git a/src/Web/StellaOps.Web/tests/e2e/integrations/runtime-hosts.e2e.spec.ts b/src/Web/StellaOps.Web/tests/e2e/integrations/runtime-hosts.e2e.spec.ts index 762318760..5d67bcb85 100644 --- a/src/Web/StellaOps.Web/tests/e2e/integrations/runtime-hosts.e2e.spec.ts +++ b/src/Web/StellaOps.Web/tests/e2e/integrations/runtime-hosts.e2e.spec.ts @@ -151,11 +151,10 @@ test.describe('Runtime Host — UI Verification', () => { await waitForAngular(page); const heading = page.getByRole('heading', { name: /runtime host/i }); - await expect(heading).toBeVisible({ timeout: 5_000 }); + await expect(heading).toBeVisible({ timeout: 15_000 }); const rows = page.locator('table tbody tr'); - const count = await rows.count(); - expect(count).toBeGreaterThanOrEqual(1); + await expect(rows.first()).toBeVisible({ timeout: 15_000 }); await snap(page, 'runtime-hosts-tab'); }); diff --git a/src/Web/StellaOps.Web/tests/e2e/integrations/ui-crud-operations.e2e.spec.ts b/src/Web/StellaOps.Web/tests/e2e/integrations/ui-crud-operations.e2e.spec.ts index 50d4a8cf6..01ff62a80 100644 --- a/src/Web/StellaOps.Web/tests/e2e/integrations/ui-crud-operations.e2e.spec.ts +++ b/src/Web/StellaOps.Web/tests/e2e/integrations/ui-crud-operations.e2e.spec.ts @@ -53,7 +53,7 @@ test.describe('UI CRUD — Search and Filter', () => { // Find the search input const searchInput = page.locator('input[aria-label*="Search"], input[placeholder*="Search"]').first(); - await expect(searchInput).toBeVisible({ timeout: 5_000 }); + await expect(searchInput).toBeVisible({ timeout: 15_000 }); // Count rows before search const rowsBefore = await page.locator('table tbody tr').count(); @@ -84,7 +84,7 @@ test.describe('UI CRUD — Search and Filter', () => { await waitForAngular(page); const searchInput = page.locator('input[aria-label*="Search"], input[placeholder*="Search"]').first(); - await expect(searchInput).toBeVisible({ timeout: 5_000 }); + await expect(searchInput).toBeVisible({ timeout: 15_000 }); // Search for something specific await searchInput.fill('SearchAlpha'); diff --git a/src/Web/StellaOps.Web/tests/e2e/integrations/ui-integration-detail.e2e.spec.ts b/src/Web/StellaOps.Web/tests/e2e/integrations/ui-integration-detail.e2e.spec.ts index f18a1d9b6..e661150e5 100644 --- a/src/Web/StellaOps.Web/tests/e2e/integrations/ui-integration-detail.e2e.spec.ts +++ b/src/Web/StellaOps.Web/tests/e2e/integrations/ui-integration-detail.e2e.spec.ts @@ -46,9 +46,8 @@ test.describe('UI Integration Detail — Harbor', () => { }); await waitForAngular(page); - const pageContent = await page.textContent('body'); - expect(pageContent).toContain('Harbor'); - expect(pageContent).toContain('harbor-fixture'); + await expect(page.locator('text=Harbor').first()).toBeVisible({ timeout: 15_000 }); + await expect(page.locator('text=harbor-fixture').first()).toBeVisible({ timeout: 15_000 }); await snap(page, 'detail-01-overview'); }); diff --git a/src/Web/StellaOps.Web/tests/e2e/integrations/ui-onboarding-wizard.e2e.spec.ts b/src/Web/StellaOps.Web/tests/e2e/integrations/ui-onboarding-wizard.e2e.spec.ts index 9adede3a3..ce2610483 100644 --- a/src/Web/StellaOps.Web/tests/e2e/integrations/ui-onboarding-wizard.e2e.spec.ts +++ b/src/Web/StellaOps.Web/tests/e2e/integrations/ui-onboarding-wizard.e2e.spec.ts @@ -34,14 +34,10 @@ test.describe('UI Onboarding Wizard — Registry', () => { }); await waitForAngular(page); - // Should show the provider catalog or wizard - const pageContent = await page.textContent('body'); - const hasWizardContent = - pageContent?.includes('Harbor') || - pageContent?.includes('Registry') || - pageContent?.includes('Provider') || - pageContent?.includes('Add'); - expect(hasWizardContent, 'Onboarding page should show provider options').toBe(true); + // Should show the provider catalog or wizard — auto-retry + await expect( + page.locator('text=Harbor').or(page.locator('text=Registry')).or(page.locator('text=Provider')).or(page.locator('text=Add')).first(), + ).toBeVisible({ timeout: 15_000 }); await snap(page, 'wizard-01-landing'); }); @@ -119,14 +115,10 @@ test.describe('UI Onboarding Wizard — SCM', () => { }); await waitForAngular(page); - const pageContent = await page.textContent('body'); - const hasScmContent = - pageContent?.includes('Gitea') || - pageContent?.includes('GitLab') || - pageContent?.includes('GitHub') || - pageContent?.includes('SCM') || - pageContent?.includes('Source Control'); - expect(hasScmContent, 'SCM onboarding page should show SCM providers').toBe(true); + // SCM onboarding page should show SCM providers — auto-retry + await expect( + page.locator('text=Gitea').or(page.locator('text=GitLab')).or(page.locator('text=GitHub')).or(page.locator('text=SCM')).or(page.locator('text=Source Control')).first(), + ).toBeVisible({ timeout: 15_000 }); await snap(page, 'wizard-scm-landing'); }); @@ -144,13 +136,10 @@ test.describe('UI Onboarding Wizard — CI/CD', () => { }); await waitForAngular(page); - const pageContent = await page.textContent('body'); - const hasCiContent = - pageContent?.includes('Jenkins') || - pageContent?.includes('CI/CD') || - pageContent?.includes('Pipeline') || - pageContent?.includes('GitHub Actions'); - expect(hasCiContent, 'CI onboarding page should show CI/CD providers').toBe(true); + // CI onboarding page should show CI/CD providers — auto-retry + await expect( + page.locator('text=Jenkins').or(page.locator('text=CI/CD')).or(page.locator('text=Pipeline')).or(page.locator('text=GitHub Actions')).first(), + ).toBeVisible({ timeout: 15_000 }); await snap(page, 'wizard-ci-landing'); }); diff --git a/src/Web/StellaOps.Web/tests/e2e/integrations/vault-consul-secrets.e2e.spec.ts b/src/Web/StellaOps.Web/tests/e2e/integrations/vault-consul-secrets.e2e.spec.ts index 189534d18..9fdaf3b83 100644 --- a/src/Web/StellaOps.Web/tests/e2e/integrations/vault-consul-secrets.e2e.spec.ts +++ b/src/Web/StellaOps.Web/tests/e2e/integrations/vault-consul-secrets.e2e.spec.ts @@ -175,14 +175,14 @@ test.describe('Secrets Integration — UI Verification', () => { await page.goto(`${BASE}/setup/integrations/secrets`, { waitUntil: 'domcontentloaded', timeout: 45_000 }); await waitForAngular(page); - // Verify the page loaded with the correct heading + // Verify the page loaded with the correct heading — auto-retry const heading = page.getByRole('heading', { name: /secrets/i }); - await expect(heading).toBeVisible({ timeout: 5_000 }); + await expect(heading).toBeVisible({ timeout: 15_000 }); - // Should have at least the two integrations we created + // Should have at least the two integrations we created — auto-retry const rows = page.locator('table tbody tr'); - const count = await rows.count(); - expect(count).toBeGreaterThanOrEqual(2); + await expect(rows.first()).toBeVisible({ timeout: 15_000 }); + await expect(rows.nth(1)).toBeVisible({ timeout: 15_000 }); await snap(page, 'secrets-tab-list'); }); @@ -196,9 +196,8 @@ test.describe('Secrets Integration — UI Verification', () => { }); await waitForAngular(page); - // Verify detail page loaded — should show integration name - const pageContent = await page.textContent('body'); - expect(pageContent).toContain('Vault'); + // Verify detail page loaded — should show integration name — auto-retry + await expect(page.locator('text=Vault').first()).toBeVisible({ timeout: 15_000 }); await snap(page, 'vault-detail-page'); } finally {