From 04a3fb766123c29f7b491531b2d65905d4c42cb8 Mon Sep 17 00:00:00 2001 From: Steve Churchill Date: Mon, 11 May 2026 11:05:15 +0100 Subject: [PATCH 1/2] e2e: add locators, env tweaks and stability logs Various e2e test improvements and stability fixes: - recommendations: added savings/table locators and no-recommendations locator; updated mocks to include table savings and placeholder modalColumnLocator; added debug logging in getCurrencyValue. - auth.setup: improved wait logic for initialization message using LARGE_DATA_TIMEOUT, added debug/error logging and a short presence check to avoid hanging. - tests: made several tests environment-aware (use env to pick regions/tags/data sources), added conditional skips when no recommendations or low numeric values, and added additional debug logs to reduce flakiness. - policies & tagging-policy tests: replaced hardcoded region/tag values with env-based choices and updated assertions accordingly. These changes aim to make tests more robust across environments, handle cases with no or zero recommendations, and improve observability when waiting for slow/large-data initializations. --- .../mocks/recommendation-card-metadata.mocks.ts | 3 +++ e2etests/pages/recommendations-page.ts | 7 +++++++ e2etests/setup/auth.setup.ts | 16 ++++++++++++++-- e2etests/tests/homepage-tests.spec.ts | 11 ++++++++++- e2etests/tests/perspective-tests.spec.ts | 14 ++++++++------ e2etests/tests/policies-tests.spec.ts | 14 ++++++-------- e2etests/tests/recommendations-tests.spec.ts | 7 ++++++- e2etests/tests/tagging-policy-tests.spec.ts | 16 +++++++++++----- 8 files changed, 65 insertions(+), 23 deletions(-) diff --git a/e2etests/mocks/recommendation-card-metadata.mocks.ts b/e2etests/mocks/recommendation-card-metadata.mocks.ts index 579d9be4e..96354588e 100644 --- a/e2etests/mocks/recommendation-card-metadata.mocks.ts +++ b/e2etests/mocks/recommendation-card-metadata.mocks.ts @@ -162,9 +162,12 @@ export const getCardMetaData = (recommendationsPage: RecommendationsPage): CardM }, { name: 'Resources With Insecure Security Groups Settings', + savingsValue: recommendationsPage.resourcesWithInsecureSecurityGroupsSettingsCardSavingsValue, countValue: recommendationsPage.resourcesWithInsecureSecurityGroupsSettingsCountValue, seeAllBtn: recommendationsPage.resourcesWithInsecureSecurityGroupsSettingsSeeAllBtn, errorLocator: recommendationsPage.resourcesWithInsecureSecurityGroupsSettingsError, + tableLocator: recommendationsPage.resourcesWithInsecureSecurityGroupsSettingsTableSavingsValue, + modalColumnLocator: undefined, //TODO: Unable to determine which column this card uses without any savings data }, { name: 'Snapshots with non-used Images', diff --git a/e2etests/pages/recommendations-page.ts b/e2etests/pages/recommendations-page.ts index 6eda2be6f..3cf66a30c 100644 --- a/e2etests/pages/recommendations-page.ts +++ b/e2etests/pages/recommendations-page.ts @@ -168,9 +168,11 @@ export class RecommendationsPage extends BasePage { readonly reservedInstancesOpportunitiesError: Locator; readonly reservedInstancesOpportunitiesTableSavingsValue: Locator; + readonly resourcesWithInsecureSecurityGroupsSettingsCardSavingsValue: Locator; readonly resourcesWithInsecureSecurityGroupsSettingsCountValue: Locator; readonly resourcesWithInsecureSecurityGroupsSettingsSeeAllBtn: Locator; readonly resourcesWithInsecureSecurityGroupsSettingsError: Locator; + readonly resourcesWithInsecureSecurityGroupsSettingsTableSavingsValue: Locator; readonly publicS3BucketsCardSavingsValue: Locator; readonly publicS3BucketsSeeAllBtn: Locator; @@ -190,6 +192,8 @@ export class RecommendationsPage extends BasePage { readonly snapshotsWithNonUsedImagesError: Locator; readonly snapshotsWithNonUsedImagesTableSavingsValue: Locator; + readonly noRecommendationsMessage: Locator; + /** * Initializes a new instance of the RecommendationsPage class. * @param {Page} page - The Playwright page object. @@ -239,6 +243,7 @@ export class RecommendationsPage extends BasePage { this.searchInput = this.main.getByPlaceholder('Search'); this.cardsGrid = this.main.locator('//div[contains(@class, "cardsGrid MuiBox-root")]'); this.table = this.main.locator('table'); + this.noRecommendationsMessage = this.main.getByText('No recommendations found'); // Data source icons const brandConfigs = { @@ -482,6 +487,7 @@ export class RecommendationsPage extends BasePage { async getCurrencyValue(currencyLocator: Locator): Promise { await currencyLocator.scrollIntoViewIfNeeded(); const text = await currencyLocator.textContent(); + debugLog(`getCurrencyValue raw text: "${text}"`); // add this return this.parseCurrencyValue(text); } @@ -522,6 +528,7 @@ export class RecommendationsPage extends BasePage { { label: 'Obsolete Snapshots', locator: this.obsoleteSnapshotsCardSavingsValue }, { label: 'Obsolete Snapshot Chains', locator: this.obsoleteSnapshotChainsCardSavingsValue }, { label: 'Reserved Instances Opportunities', locator: this.reservedInstancesOpportunitiesCardSavingsValue }, + { label: 'Resources with Insecure Security Groups settings', locator: this.resourcesWithInsecureSecurityGroupsSettingsTableSavingsValue }, { label: 'Public S3 Buckets', locator: this.publicS3BucketsCardSavingsValue }, { label: 'Snapshots With Non-used Images', locator: this.snapshotsWithNonUsedImagesCardSavingsValue }, { label: 'Underutilized Instances', locator: this.underutilizedInstancesCardSavingsValue }, diff --git a/e2etests/setup/auth.setup.ts b/e2etests/setup/auth.setup.ts index b203f12c6..171fafd14 100644 --- a/e2etests/setup/auth.setup.ts +++ b/e2etests/setup/auth.setup.ts @@ -3,6 +3,8 @@ import { test as setup } from '@playwright/test'; import { getLocalforageRoot, injectLocalforage } from '../utils/auth-session-storage/localforage-service'; import { safeWriteJsonFile } from '../utils/file'; import { LiveDemoService } from '../utils/auth-session-storage/auth-helpers'; +import { LARGE_DATA_TIMEOUT } from '../playwright.config'; +import { debugLog, errorLog } from '../utils/debug-logging'; const useLiveDemoCredentials = LiveDemoService.shouldUseLiveDemo(); @@ -40,8 +42,18 @@ setup.describe('Auth Setup', () => { await page.getByTestId('input_pass').fill(password); await page.getByTestId('btn_login').click(); const initializingMessage = page.getByTestId('p_initializing') - await initializingMessage.waitFor({ timeout: 20000 }); - await initializingMessage.waitFor({ state: 'detached', timeout: 20000 }); + try { + await initializingMessage.first().waitFor({ timeout: 1000 }); + } catch (_error) { + // Exit the method if the initialisation message is not present. + return; + } + try { + debugLog('Waiting for initialisaton message to disappear...'); + await initializingMessage.waitFor({ state: 'hidden', timeout: LARGE_DATA_TIMEOUT }); + } catch (_error) { + errorLog('[ERROR] initialisaton message did not disappear within the timeout.'); + } const loadingImage = page.getByRole('img', { name: 'Loading page' }); await loadingImage.waitFor(); await loadingImage.waitFor({ state: 'detached', timeout: 20000 }); diff --git a/e2etests/tests/homepage-tests.spec.ts b/e2etests/tests/homepage-tests.spec.ts index b70b865e7..3d7a662ff 100644 --- a/e2etests/tests/homepage-tests.spec.ts +++ b/e2etests/tests/homepage-tests.spec.ts @@ -1,8 +1,10 @@ +/* eslint-disable playwright/no-conditional-in-test, playwright/no-conditional-expect */ import { test } from '../fixtures/page.fixture'; import { expect } from '@playwright/test'; import { isWithinRoundingDrift } from '../utils/custom-assertions'; import { InterceptionEntry } from '../types/interceptor.types'; import { OrganisationConstraintsMock } from '../mocks/home-page.mocks'; +import { debugLog } from '../utils/debug-logging'; test.describe('[MPT-11464] Home Page Recommendations block tests', { tag: ['@ui', '@recommendations', '@homepage'] }, () => { test.describe.configure({ mode: 'default' }); @@ -48,13 +50,19 @@ test.describe('[MPT-11464] Home Page Recommendations block tests', { tag: ['@ui' expect.soft(await recommendationsPage.getTotalSumOfItemsFromSeeItemsButtons()).toBe(homePageValue); }); - // Test failing due to bug MPT-11558 The home page recommendations block not returning the real Critical item count test('[230553] Verify Critical items displayed in the recommendations block match the sum total of items displayed on cards with the critical status', async ({ homePage, recommendationsPage, }) => { const homePageValue = await homePage.getRecommendationsCriticalValue(); await homePage.recommendationsCriticalLink.click(); + + if(homePageValue === 0){ + debugLog('No critical recommendations, verifying that the no recommendations message is shown'); + await expect(recommendationsPage.noRecommendationsMessage).toBeVisible(); + return; + } + expect.soft(await recommendationsPage.selectedComboBoxOption(recommendationsPage.categoriesSelect)).toEqual('Critical'); expect.soft(await recommendationsPage.getTotalSumOfItemsFromSeeItemsButtons()).toBe(homePageValue); }); @@ -186,6 +194,7 @@ test.describe('[MPT-12743] Home Page test for Pools requiring attention block', const limitValue = Math.round(expenseValue - 1); await poolsPage.toggleExpandPool(); subPoolExpenseValue = await poolsPage.getSubPoolExpensesThisMonth(1); + test.skip(subPoolExpenseValue <= 1, 'Sub-pool expenses are too low to set a limit below them and still have a positive limit value'); const subPoolLimitValue = Math.round(subPoolExpenseValue - 1); await poolsPage.editSubPoolMonthlyLimit(subPoolLimitValue, true, 1); await poolsPage.editPoolMonthlyLimit(limitValue); diff --git a/e2etests/tests/perspective-tests.spec.ts b/e2etests/tests/perspective-tests.spec.ts index c8d1e3a5d..e4a587477 100644 --- a/e2etests/tests/perspective-tests.spec.ts +++ b/e2etests/tests/perspective-tests.spec.ts @@ -8,6 +8,7 @@ test.describe('[MPT-18579] Perspective Tests', { tag: ['@ui', '@resources', '@pe test.describe.configure({ mode: 'default' }); test.use({ restoreSession: true }); test.slow(); + const env = process.env.ENVIRONMENT.toLowerCase(); test('[232963] User can create an Expenses perspective and the chart options are saved and applied correctly', async ({ resourcesPage, @@ -16,9 +17,9 @@ test.describe('[MPT-18579] Perspective Tests', { tag: ['@ui', '@resources', '@pe await resourcesPage.navigateToResourcesPageAndResetFilters(); const filter = 'Region'; - const filterOption = 'East US'; + const filterOption = env !== 'test' ? 'eu-west-1' : 'West Europe'; const categorizeBy = 'Resource type'; - const groupByTag = 'costcenter'; + const groupByTag = env !== 'test' ? 'Component' : 'devops-component'; const perspectiveName = `Test Perspective ${new Date().getTime()}`; await test.step('Select options to save as a perspective', async () => { @@ -128,7 +129,7 @@ test.describe('[MPT-18579] Perspective Tests', { tag: ['@ui', '@resources', '@pe await resourcesPage.navigateToResourcesPageAndResetFilters(); const filter = 'Pool'; - const filterOption = 'Marketplace (Dev)'; + const filterOption = env !== 'test' ? 'Marketplace (Staging)' : 'Marketplace (Test)'; const perspectiveName = `Test Perspective ${new Date().getTime()}`; await test.step('Select options to save as a perspective', async () => { @@ -206,6 +207,7 @@ test.describe('[MPT-18579] Perspective Tests', { tag: ['@ui', '@resources', '@pe }); test('[232967] User can create a perspective and delete it via the perspectives table', async ({ resourcesPage, perspectivesPage }) => { + const tag= env !== 'test' ? 'Component' : 'devops-component'; await perspectivesPage.navigateToURL(); const initialPerspectivesCount = await perspectivesPage.getPerspectivesCount(); debugLog(`Initial perspectives count: ${initialPerspectivesCount}`); @@ -216,7 +218,7 @@ test.describe('[MPT-18579] Perspective Tests', { tag: ['@ui', '@resources', '@pe await test.step('Create and save a perspective', async () => { await resourcesPage.clickExpensesTab(); - await resourcesPage.selectGroupByTag('environment'); + await resourcesPage.selectGroupByTag(tag); await resourcesPage.click(resourcesPage.savePerspectiveBtn); await resourcesPage.savePerspective(perspectiveName); }); @@ -247,7 +249,7 @@ test.describe('[MPT-18579] Perspective Tests', { tag: ['@ui', '@resources', '@pe await resourcesPage.navigateToResourcesPageAndResetFilters(); const filter1 = 'Region'; - const filterOption1 = 'West Europe'; + const filterOption1 = env !== 'test' ? 'eu-west-1' : 'West Europe'; const filter2 = 'Recommendations'; const filterOption2 = 'With recommendations'; const perspectiveName = `Test Perspective ${new Date().getTime()}`; @@ -290,7 +292,7 @@ test.describe('[MPT-18579] Perspective Tests', { tag: ['@ui', '@resources', '@pe await resourcesPage.navigateToResourcesPageAndResetFilters(); const filter1 = 'Region'; - const filterOption1 = 'West Europe'; + const filterOption1 = env !== 'test' ? 'eu-west-1' : 'West Europe'; const filter2 = 'Recommendations'; const filterOption2 = 'With recommendations'; const perspectiveName = `Test Perspective ${new Date().getTime()}`; diff --git a/e2etests/tests/policies-tests.spec.ts b/e2etests/tests/policies-tests.spec.ts index 0d35ac6ee..1e858ab77 100644 --- a/e2etests/tests/policies-tests.spec.ts +++ b/e2etests/tests/policies-tests.spec.ts @@ -59,14 +59,12 @@ test.describe('[MPT-16366] Policies Tests', { tag: ['@ui', '@policies'] }, () => }); test('[232287] Verify that user can add a resource quota policy', async ({ policiesPage, policiesCreatePage }) => { - const NBSP = '\u00A0' + const env = process.env.ENVIRONMENT.toLowerCase(); const policyName = `Resource Policy ${Date.now()}`; const resourceCount = 10; - const filterOption = 'West Europe'; - const filterData = `Region:${NBSP}.a{fill:url(#a);} - .b{fill:#0078d4;} - .c{fill:url(#b);} - .d{fill:url(#c);} ${filterOption}`; + // eslint-disable-next-line playwright/no-conditional-in-test + const filterOption = env !== 'test' ? 'eu-west-1' : 'West Europe'; + await test.step('Create Resource Policy', async () => { await policiesPage.navigateToCreatePolicy(); @@ -80,7 +78,7 @@ test.describe('[MPT-16366] Policies Tests', { tag: ['@ui', '@policies'] }, () => await expect.soft(targetPolicyRow.locator('xpath=/td[1]')).toHaveText(policyName); await expect.soft(targetPolicyRow.locator('xpath=/td[3]')).toHaveText(`Resource count must not exceed ${resourceCount}.`); - await expect.soft(targetPolicyRow.locator('xpath=/td[4]')).toContainText(filterData); + await expect.soft(targetPolicyRow.locator('xpath=/td[4]')).toContainText(filterOption); }); await test.step('Navigate to the created policy details page', async () => { @@ -92,7 +90,7 @@ test.describe('[MPT-16366] Policies Tests', { tag: ['@ui', '@policies'] }, () => await expect.soft(policiesPage.policyDetailsDiv).toContainText(`Name: ${policyName}`); await expect.soft(policiesPage.policyDetailsDiv).toContainText('Type: Resource quota'); await expect.soft(policiesPage.policyDetailsDiv).toContainText(`Resource count: ${resourceCount}`); - await expect.soft(policiesPage.policyDetailsDiv).toContainText(`${filterData}`); + await expect.soft(policiesPage.policyDetailsDiv).toContainText(`${filterOption}`); }); }); diff --git a/e2etests/tests/recommendations-tests.spec.ts b/e2etests/tests/recommendations-tests.spec.ts index a51a1e1f1..8bb8001a5 100644 --- a/e2etests/tests/recommendations-tests.spec.ts +++ b/e2etests/tests/recommendations-tests.spec.ts @@ -38,7 +38,7 @@ test.describe('[MPT-11310] Recommendations page tests', { tag: ['@ui', '@recomme }); test('[230597] Verify Data Source selection works correctly', async ({ recommendationsPage }) => { - const dataSource = process.env.USE_LIVE_DEMO === 'true' ? 'Azure QA' : 'CPA (Development and Test)'; + const dataSource = process.env.USE_LIVE_DEMO === 'true' ? 'Azure QA' : 'Marketplace (Dev)'; await test.step(`Select data source: ${dataSource}`, async () => { await recommendationsPage.selectDataSource(dataSource); @@ -293,6 +293,8 @@ test.describe('[MPT-11310] Recommendations page tests', { tag: ['@ui', '@recomme await test.step('Select Critical category and verify every card has a critical icon', async () => { await recommendationsPage.selectCategory('Critical'); + test.skip(await recommendationsPage.noRecommendationsMessage.isVisible(), 'No recommendations are marked as Critical'); + await recommendationsPage.allCardHeadings.last().waitFor(); count = await recommendationsPage.allCardHeadings.count(); actualHeadings = await recommendationsPage.allCardHeadings.allTextContents(); @@ -439,8 +441,11 @@ test.describe('[MPT-11310] Recommendations page tests', { tag: ['@ui', '@recomme let cardSavings = undefined; let cardCount = undefined; + debugLog(`${cardName} savingsValue defined: ${!!savingsValue}`); if (savingsValue) { cardSavings = await recommendationsPage.getCurrencyValue(savingsValue); + debugLog(`${cardName} Card Savings Value: ${cardSavings}`); + if (cardSavings === 0) { const value = await savingsValue.textContent(); diff --git a/e2etests/tests/tagging-policy-tests.spec.ts b/e2etests/tests/tagging-policy-tests.spec.ts index 604352da7..e41db7423 100644 --- a/e2etests/tests/tagging-policy-tests.spec.ts +++ b/e2etests/tests/tagging-policy-tests.spec.ts @@ -23,6 +23,7 @@ async function deleteAllPolicies() { test.describe('[MPT-17042] Tagging Policy Tests', { tag: ['@ui', '@tagging-policies'] }, () => { test.describe.configure({ mode: 'default' }); test.use({ restoreSession: true }); + const env = process.env.ENVIRONMENT.toLowerCase(); test.beforeEach('Login admin user', async ({ taggingPoliciesPage }) => { await test.step('Login admin user', async () => { @@ -48,7 +49,8 @@ test.describe('[MPT-17042] Tagging Policy Tests', { tag: ['@ui', '@tagging-polic test('[232656] Verify that a user can create a required tagging policy', async ({ taggingPoliciesPage, taggingPoliciesCreatePage }) => { const policyName = `Required Tag Policy ${Date.now()}`; - const tagName = 'AccountId'; + // eslint-disable-next-line playwright/no-conditional-in-test + const tagName = env !== 'test' ? 'Component' : 'devops-component'; await test.step('Create required Tagging Policy page', async () => { await taggingPoliciesPage.navigateToCreateTaggingPolicy(); @@ -68,7 +70,8 @@ test.describe('[MPT-17042] Tagging Policy Tests', { tag: ['@ui', '@tagging-polic test('[232657] Verify that a user can create a prohibited tagging policy', async ({ taggingPoliciesPage, taggingPoliciesCreatePage }) => { const policyName = `Prohibited Tag Policy ${Date.now()}`; - const tagName = '__department'; + // eslint-disable-next-line playwright/no-conditional-in-test + const tagName = env !== 'test' ? 'Component' : 'devops-component'; const filter = 'Activity'; const filterOption = 'Active'; @@ -101,8 +104,10 @@ test.describe('[MPT-17042] Tagging Policy Tests', { tag: ['@ui', '@tagging-polic taggingPoliciesCreatePage, }) => { const policyName = `Correlated Tag Policy ${Date.now()}`; - const tagName = 'Instance'; - const secondaryTagName = 'Environment'; + // eslint-disable-next-line playwright/no-conditional-in-test + const tagName = env !== 'test' ? 'Component' : 'devops-component'; + // eslint-disable-next-line playwright/no-conditional-in-test + const secondaryTagName = env !== 'test' ? 'aws:backup:source-resource' : 'CostCenter'; await test.step('Create tags correlation Tagging Policy page', async () => { await taggingPoliciesPage.navigateToCreateTaggingPolicy(); @@ -127,7 +132,8 @@ test.describe('[MPT-17042] Tagging Policy Tests', { tag: ['@ui', '@tagging-polic taggingPoliciesCreatePage, }) => { const policyName = `Policy To Be Deleted ${Date.now()}`; - const tagName = 'Application'; + // eslint-disable-next-line playwright/no-conditional-in-test + const tagName = env !== 'test' ? 'Component' : 'devops-component'; await test.step('Create a policy to be deleted', async () => { await taggingPoliciesPage.navigateToCreateTaggingPolicy(); From cd101d2809dbfd13e0227fa8dc698898ee13e19c Mon Sep 17 00:00:00 2001 From: Steve Churchill Date: Mon, 11 May 2026 11:14:33 +0100 Subject: [PATCH 2/2] Remove stray debug comment in recommendations page Remove a leftover inline comment after the debugLog call in RecommendationsPage.getCurrencyValue. This is a non-functional cleanup to tidy the code and avoid unnecessary noise in the source. --- e2etests/pages/recommendations-page.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2etests/pages/recommendations-page.ts b/e2etests/pages/recommendations-page.ts index 3cf66a30c..b8fa3ea81 100644 --- a/e2etests/pages/recommendations-page.ts +++ b/e2etests/pages/recommendations-page.ts @@ -487,7 +487,7 @@ export class RecommendationsPage extends BasePage { async getCurrencyValue(currencyLocator: Locator): Promise { await currencyLocator.scrollIntoViewIfNeeded(); const text = await currencyLocator.textContent(); - debugLog(`getCurrencyValue raw text: "${text}"`); // add this + debugLog(`getCurrencyValue raw text: "${text}"`); return this.parseCurrencyValue(text); }