From 7fc130b735ea8ff5b3305a17432d562a577f8370 Mon Sep 17 00:00:00 2001 From: Matt Raible Date: Tue, 2 Dec 2025 08:55:54 -0700 Subject: [PATCH] Improve E2E test reliability with better selectors and error handling - Replace searchbox selectors with filter patterns for UI consistency - Add comprehensive error message surfacing in AppCatalogPage - Fix navigation reliability by scoping selectors to navigation context - Use exact matches for Fusion SOAR button to avoid content card conflicts - Handle dynamic button text with regex patterns (e.g., 'Endpoint security') - Remove waitForTimeout anti-patterns and unnecessary loader checks - Scope workflow menu buttons to specific rows to avoid conflicts These changes resolve selector ambiguity issues and improve test stability across different UI states and environments. --- e2e/src/pages/AppCatalogPage.ts | 26 +++++++++++++++----------- e2e/src/pages/SocketNavigationPage.ts | 7 ++++--- e2e/src/pages/WorkflowsPage.ts | 17 +++++++++-------- 3 files changed, 28 insertions(+), 22 deletions(-) diff --git a/e2e/src/pages/AppCatalogPage.ts b/e2e/src/pages/AppCatalogPage.ts index ab5a3d6..9ce79e8 100644 --- a/e2e/src/pages/AppCatalogPage.ts +++ b/e2e/src/pages/AppCatalogPage.ts @@ -33,10 +33,11 @@ export class AppCatalogPage extends BasePage { await this.navigateToPath('/foundry/app-catalog', 'App catalog page'); - const searchBox = this.page.getByRole('searchbox', { name: 'Search' }); - await searchBox.fill(appName); - await this.page.keyboard.press('Enter'); - await this.page.waitForLoadState('networkidle'); + const filterBox = this.page.getByPlaceholder('Type to filter'); + if (await filterBox.isVisible().catch(() => false)) { + await filterBox.fill(appName); + await this.page.waitForLoadState('networkidle'); + } const appLink = this.page.getByRole('link', { name: appName, exact: true }); @@ -220,15 +221,18 @@ export class AppCatalogPage extends BasePage { const errorMessage = this.page.getByText(`Error installing ${appName}`).first(); try { - await Promise.race([ + const result = await Promise.race([ installedMessage.waitFor({ state: 'visible', timeout: 60000 }).then(() => 'success'), errorMessage.waitFor({ state: 'visible', timeout: 60000 }).then(() => 'error') - ]).then(result => { - if (result === 'error') { - throw new Error(`Installation failed for app '${appName}' - error message appeared`); - } - this.logger.success('Installation completed successfully - "installed" message appeared'); - }); + ]); + + if (result === 'error') { + // Get the actual error message from the toast and clean up formatting + const errorText = await errorMessage.textContent(); + const cleanError = errorText?.replace(/\s+/g, ' ').trim() || 'Unknown error'; + throw new Error(`Installation failed for app '${appName}': ${cleanError}`); + } + this.logger.success('Installation completed successfully - "installed" message appeared'); } catch (error) { if (error.message.includes('Installation failed')) { throw error; diff --git a/e2e/src/pages/SocketNavigationPage.ts b/e2e/src/pages/SocketNavigationPage.ts index 5565fa1..2bd2bc3 100644 --- a/e2e/src/pages/SocketNavigationPage.ts +++ b/e2e/src/pages/SocketNavigationPage.ts @@ -42,12 +42,13 @@ export class SocketNavigationPage extends BasePage { await this.page.waitForLoadState('networkidle'); // Click "Endpoint security" - const endpointSecurityButton = this.page.getByRole('button', { name: /Endpoint security/i }); + const navigation = this.page.getByRole('navigation'); + const endpointSecurityButton = navigation.getByRole('button', { name: /Endpoint security/ }); await endpointSecurityButton.click(); await this.waiter.delay(500); // Click "Monitor" to expand submenu (if not already expanded) - const monitorButton = this.page.getByRole('button', { name: /^Monitor$/i }); + const monitorButton = navigation.getByRole('button', { name: 'Monitor', exact: true }); const isExpanded = await monitorButton.getAttribute('aria-expanded'); if (isExpanded !== 'true') { await monitorButton.click(); @@ -55,7 +56,7 @@ export class SocketNavigationPage extends BasePage { } // Click "Endpoint detections" link - const endpointDetectionsLink = this.page.getByRole('link', { name: /Endpoint detections/i }); + const endpointDetectionsLink = navigation.getByRole('link', { name: /Endpoint detections/ }); await endpointDetectionsLink.click(); // Wait for page to load diff --git a/e2e/src/pages/WorkflowsPage.ts b/e2e/src/pages/WorkflowsPage.ts index ac3579d..596a7d1 100644 --- a/e2e/src/pages/WorkflowsPage.ts +++ b/e2e/src/pages/WorkflowsPage.ts @@ -36,11 +36,11 @@ export class WorkflowsPage extends BasePage { await menuButton.click(); await this.page.waitForLoadState('networkidle'); - // Click Fusion SOAR in the navigation menu (not the home page cards) - const navigation = this.page.locator('nav, [role="navigation"]'); - const fusionSoarButton = navigation.getByRole('button', { name: 'Fusion SOAR' }); + // Click Fusion SOAR button in the navigation menu (not the content buttons) + // Look for the navigation and find the exact "Fusion SOAR" button (not content that mentions Fusion SOAR) + const navigation = this.page.getByRole('navigation'); + const fusionSoarButton = navigation.getByRole('button', { name: 'Fusion SOAR', exact: true }); await fusionSoarButton.click(); - await this.page.waitForTimeout(500); // Click Workflows link const workflowsLink = this.page.getByRole('link', { name: 'Workflows' }); @@ -161,11 +161,12 @@ export class WorkflowsPage extends BasePage { async () => { this.logger.info(`Executing workflow: ${workflowName}`); - // Open the workflow - await this.openWorkflow(workflowName); + // Ensure we're on the workflows list page, not an individual workflow page + await this.navigateToWorkflows(); - // Click "Open menu" button - const openMenuButton = this.page.getByRole('button', { name: /open menu/i }); + // Click "Open menu" button for the specific workflow row + const workflowRow = this.page.getByRole('row', { name: new RegExp(workflowName, 'i') }); + const openMenuButton = workflowRow.getByRole('button', { name: /open menu/i }); await openMenuButton.click(); // Click "Execute workflow" option