diff --git a/e2etests/api-requests/auth-request.ts b/e2etests/api-requests/auth-request.ts index ab91de955..2db9b04cc 100644 --- a/e2etests/api-requests/auth-request.ts +++ b/e2etests/api-requests/auth-request.ts @@ -113,34 +113,6 @@ export class AuthRequest extends BaseRequest { }); } - /** - * Creates a new user with the provided email, password, and display name. - * @param {string} email - The email address of the user. - * @param {string} password - The password of the user. - * @param {string} displayName - The display name of the user. - * @returns {Promise} A promise that resolves to the API response. - * @throws Will throw an error if the user creation fails. - */ - async createUser(email: string, password: string, displayName: string): Promise { - const response = await this.request.post(this.userEndpoint, { - headers: { - 'Content-Type': 'application/json', - Secret: process.env.CLUSTER_SECRET, - }, - data: { - email, - display_name: displayName, - password, - verified: true, - }, - }); - - if (response.status() !== 201) { - throw new Error(`Failed to create user: Status ${response.status()}`); - } - return response; - } - /** * Sets a verification code for a user. * This method sends a POST request to the verification codes endpoint with the provided email and code. @@ -173,23 +145,4 @@ export class AuthRequest extends BaseRequest { } return response; } - - /** - * Deletes a user with the provided user ID. - * @param {string} userID - The user identifier. - * @returns {Promise} A promise that resolves when the user is deleted. - * @throws Will throw an error if the user deletion fails. - */ - async deleteUser(userID: string): Promise { - const response = await this.request.delete(`${this.userEndpoint}/${userID}`, { - headers: { - 'Content-Type': 'application/json', - Secret: `${process.env.CLUSTER_SECRET}`, - }, - }); - if (response.status() !== 204) { - throw new Error(`Failed to delete userID ${userID}: response status ${response.status()} reason ${response.statusText}`); - } - console.log(`UserID ${userID} deleted`); - } } diff --git a/e2etests/fixtures/page.fixture.ts b/e2etests/fixtures/page.fixture.ts index 1ade851fc..7518b6678 100644 --- a/e2etests/fixtures/page.fixture.ts +++ b/e2etests/fixtures/page.fixture.ts @@ -1,19 +1,20 @@ -import {test as base, type Page} from '@playwright/test'; +import { type Page, test as base } from '@playwright/test'; import * as Pages from '../pages'; -import {LiveDemoService} from '../utils/auth-session-storage/auth-helpers'; -import {restoreUserSessionInLocalForage} from "../utils/auth-session-storage/localforage-service"; -import {BaseRequest} from "../utils/api-requests/base-request"; -import {apiInterceptors} from "../utils/api-requests/interceptor"; -import {InterceptionEntry} from "../types/interceptor.types"; +import { LiveDemoService } from '../utils/auth-session-storage/auth-helpers'; +import { restoreUserSessionInLocalForage } from '../utils/auth-session-storage/localforage-service'; +import { apiInterceptors } from '../utils/api-interceptor/interceptor'; +import { InterceptionEntry } from '../types/interceptor.types'; +import { AuthRequest } from '../api-requests/auth-request'; interface Options { - baseRequest: BaseRequest; + authRequest: AuthRequest; restoreSession?: boolean; setFixedTime?: boolean; - interceptAPI?: { //List array must be wrapped as object first otherwise it will pass only first array item + interceptAPI?: { + //List array must be wrapped as object first otherwise it will pass only first array item entries: InterceptionEntry[]; failOnInterceptionMissing?: boolean; - } + }; } // Generic "new (page) => instance" type for Page Objects @@ -22,9 +23,9 @@ type PageObjectCtor = new (page: Page) => T; // Build a single fixture from a Page Object constructor const createFixture = (Ctor: PageObjectCtor) => - async ({page}: { page: Page }, use: (po: T) => Promise) => { - await use(new Ctor(page)); - }; + async ({ page }: { page: Page }, use: (po: T) => Promise) => { + await use(new Ctor(page)); + }; // Declare all POs in one place with strong typing const constructors = { @@ -64,15 +65,12 @@ type Fixtures = { [K in keyof Constructors]: InstanceType }; function buildFixtures>(ctors: C) { const result = {} as { - [K in keyof C]: ( - args: { page: Page }, - use: (po: InstanceType) => Promise - ) => Promise; + [K in keyof C]: (args: { page: Page }, use: (po: InstanceType) => Promise) => Promise; }; for (const key in ctors) { const Ctor = ctors[key]; - result[key] = createFixture(Ctor) as typeof result[typeof key]; + result[key] = createFixture(Ctor) as (typeof result)[typeof key]; } return result; @@ -81,21 +79,17 @@ function buildFixtures>(ctors: C) { const fixtures = buildFixtures(constructors); export const test = base.extend({ - restoreSession: [false, {option: true}], - setFixedTime: [false, {option: true}], - interceptAPI: [undefined, {option: true}], // <-- allow full override + restoreSession: [false, { option: true }], + setFixedTime: [false, { option: true }], + interceptAPI: [undefined, { option: true }], // <-- allow full override - page: async ({page, restoreSession, setFixedTime, interceptAPI}, use) => { + page: async ({ page, restoreSession, setFixedTime, interceptAPI }, use) => { let verifyInterceptions: (() => void) | undefined; if (restoreSession) { await restoreUserSessionInLocalForage(page, setFixedTime); } if (interceptAPI?.entries?.length > 0) { - verifyInterceptions = await apiInterceptors( - page, - interceptAPI.entries, - interceptAPI.failOnInterceptionMissing - ); + verifyInterceptions = await apiInterceptors(page, interceptAPI.entries, interceptAPI.failOnInterceptionMissing); } if (process.env.BROWSER_ERROR_LOGGING === 'true') { page.on('console', msg => { @@ -110,11 +104,9 @@ export const test = base.extend({ verifyInterceptions(); } }, - - baseRequest: async ({request}, use) => { - await use(new BaseRequest(request)); + authRequest: async ({ request }, use) => { + await use(new AuthRequest(request)); }, - storageState: async ({}, use) => { await use(LiveDemoService.getDefaultUserStorageState()); }, diff --git a/e2etests/package-lock.json b/e2etests/package-lock.json index 4fd74cfb0..18755362f 100644 --- a/e2etests/package-lock.json +++ b/e2etests/package-lock.json @@ -14,7 +14,7 @@ "devDependencies": { "@eslint/eslintrc": "^3.3.1", "@eslint/js": "^9.36.0", - "@playwright/test": "^1.49.1", + "@playwright/test": "^1.57.0", "@types/node": "^22.10.5", "@types/pngjs": "^6.0.5", "cross-env": "^7.0.3", @@ -263,13 +263,13 @@ } }, "node_modules/@playwright/test": { - "version": "1.49.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.49.1.tgz", - "integrity": "sha512-Ky+BVzPz8pL6PQxHqNRW1k3mIyv933LML7HktS8uik0bUXNCdPhoS/kLihiO1tMf/egaJb4IutXd7UywvXEW+g==", + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.57.0.tgz", + "integrity": "sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.49.1" + "playwright": "1.57.0" }, "bin": { "playwright": "cli.js" @@ -278,38 +278,6 @@ "node": ">=18" } }, - "node_modules/@playwright/test/node_modules/playwright": { - "version": "1.49.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.49.1.tgz", - "integrity": "sha512-VYL8zLoNTBxVOrJBbDuRgDWa3i+mfQgDTrL8Ah9QXZ7ax4Dsj0MSq5bYgytRnDVVe+njoKnfsYkH3HzqVj5UZA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "playwright-core": "1.49.1" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "fsevents": "2.3.2" - } - }, - "node_modules/@playwright/test/node_modules/playwright-core": { - "version": "1.49.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.1.tgz", - "integrity": "sha512-BzmpVcs4kE2CH15rWfzpjzVGhWERJfmnXmniSyKeRZUs9Ws65m+RGIi7mjJK/euCegfn3i7jvqWeWyHe9y3Vgg==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "playwright-core": "cli.js" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -1545,12 +1513,12 @@ } }, "node_modules/playwright": { - "version": "1.56.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.56.1.tgz", - "integrity": "sha512-aFi5B0WovBHTEvpM3DzXTUaeN6eN0qWnTkKx4NQaH4Wvcmc153PdaY2UBdSYKaGYw+UyWXSVyxDUg5DoPEttjw==", + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz", + "integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==", "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.56.1" + "playwright-core": "1.57.0" }, "bin": { "playwright": "cli.js" @@ -1563,9 +1531,9 @@ } }, "node_modules/playwright-core": { - "version": "1.56.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.56.1.tgz", - "integrity": "sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ==", + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz", + "integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==", "license": "Apache-2.0", "bin": { "playwright-core": "cli.js" diff --git a/e2etests/package.json b/e2etests/package.json index 00d0afc5a..e8acf3733 100644 --- a/e2etests/package.json +++ b/e2etests/package.json @@ -38,7 +38,7 @@ "devDependencies": { "@eslint/eslintrc": "^3.3.1", "@eslint/js": "^9.36.0", - "@playwright/test": "^1.49.1", + "@playwright/test": "^1.57.0", "@types/node": "^22.10.5", "@types/pngjs": "^6.0.5", "cross-env": "^7.0.3", diff --git a/e2etests/tests/anomalies-tests.spec.ts b/e2etests/tests/anomalies-tests.spec.ts index 85ebfc0d8..1c108eff8 100644 --- a/e2etests/tests/anomalies-tests.spec.ts +++ b/e2etests/tests/anomalies-tests.spec.ts @@ -30,7 +30,7 @@ test.describe('[MPT-14737] Anomalies Tests', { tag: ['@ui', '@anomalies'] }, () await anomaliesPage.navigateToURL(); }); - test('[231429] Anomalies page components', async ({ anomaliesPage }) => { + test('[231429] Anomalies page components', { tag: ['@fast', '@p2'] }, async ({ anomaliesPage }) => { await test.step('Verify page header components', async () => { await expect.soft(anomaliesPage.heading).toHaveText('Anomaly detection'); await expect.soft(anomaliesPage.addBtn).toBeVisible(); @@ -56,28 +56,32 @@ test.describe('[MPT-14737] Anomalies Tests', { tag: ['@ui', '@anomalies'] }, () }); }); - test('[231432] Verify navigation of link and show resources button', async ({ anomaliesPage, resourcesPage }) => { - await test.step('Navigate to policy details and verify values', async () => { - await anomaliesPage.waitForAllProgressBarsToDisappear(); - await anomaliesPage.click(anomaliesPage.defaultExpenseAnomalyLink); - await expect.soft(anomaliesPage.anomalyDetectionPolicyHeading).toHaveText('Anomaly detection policy'); - await expect.soft(anomaliesPage.policyDetailsNameValue).toHaveText('Default - expense anomaly'); - await expect.soft(anomaliesPage.policyDetailsTypeValue).toHaveText('Expenses'); - await expect.soft(anomaliesPage.policyDetailsEvaluationPeriodValue).toHaveText('7 days'); - await expect.soft(anomaliesPage.policyDetailsThresholdValue).toHaveText('30%'); - }); + test( + '[231432] Verify navigation of link and show resources button', + { tag: ['@fast', '@p2'] }, + async ({ anomaliesPage, resourcesPage }) => { + await test.step('Navigate to policy details and verify values', async () => { + await anomaliesPage.waitForAllProgressBarsToDisappear(); + await anomaliesPage.click(anomaliesPage.defaultExpenseAnomalyLink); + await expect.soft(anomaliesPage.anomalyDetectionPolicyHeading).toHaveText('Anomaly detection policy'); + await expect.soft(anomaliesPage.policyDetailsNameValue).toHaveText('Default - expense anomaly'); + await expect.soft(anomaliesPage.policyDetailsTypeValue).toHaveText('Expenses'); + await expect.soft(anomaliesPage.policyDetailsEvaluationPeriodValue).toHaveText('7 days'); + await expect.soft(anomaliesPage.policyDetailsThresholdValue).toHaveText('30%'); + }); - await test.step('Navigate back and verify Show Resources button navigates to Resources page', async () => { - await anomaliesPage.click(anomaliesPage.anomalyDetectionBreadcrumb); - await anomaliesPage.waitForAllProgressBarsToDisappear(); - await anomaliesPage.click(anomaliesPage.defaultExpenseAnomalyShowResourcesBtn); - await expect(resourcesPage.heading).toBeVisible(); - }); - }); + await test.step('Navigate back and verify Show Resources button navigates to Resources page', async () => { + await anomaliesPage.click(anomaliesPage.anomalyDetectionBreadcrumb); + await anomaliesPage.waitForAllProgressBarsToDisappear(); + await anomaliesPage.click(anomaliesPage.defaultExpenseAnomalyShowResourcesBtn); + await expect(resourcesPage.heading).toBeVisible(); + }); + } + ); test( '[231488] API responses matches expected structure for default anomaly detection policies', - { tag: '@p1' }, + { tag: ['@fast', '@p1'] }, async ({ anomaliesPage }) => { let anomalyData: DefaultAnomalyResponse; await test.step('Load expenses data', async () => { @@ -168,7 +172,7 @@ test.describe('[MPT-14737] Anomalies Tests', { tag: ['@ui', '@anomalies'] }, () } ); - test('[231431] Anomalies page search function', async ({ anomaliesPage }) => { + test('[231431] Anomalies page search function', { tag: ['@fast', '@p2'] }, async ({ anomaliesPage }) => { await test.step('Search by "expense" shows only expense anomaly', async () => { await anomaliesPage.searchAnomaly('expense'); await expect.soft(anomaliesPage.defaultExpenseAnomalyLink).toBeVisible(); @@ -194,52 +198,60 @@ test.describe('[MPT-14737] Anomalies Tests', { tag: ['@ui', '@anomalies'] }, () }); }); - test('[231433] Add a resource count anomaly detection policy', { tag: '@p1' }, async ({ anomaliesPage, anomaliesCreatePage }) => { - const policyName = `E2E Test - Resource Count Anomaly - ${Date.now()}`; - - await test.step('Create a new resource count anomaly policy', async () => { - await anomaliesPage.clickAddBtn(); - const policyId = await anomaliesCreatePage.addNewAnomalyPolicy(policyName, 'Resource count', '14', '25'); - anomalyPolicyId.push(policyId); - }); - - await test.step('Verify policy is visible with correct details', async () => { - await expect.soft(anomaliesPage.policyLinkByName(policyName)).toBeVisible(); - await expect - .soft(anomaliesPage.policyDescriptionByName(policyName)) - .toHaveText('Daily resource count must not exceed the average amount for the last 14 days by 25%.'); - await expect.soft(anomaliesPage.policyFilterByName(policyName)).toHaveText('-'); - }); - }); + test( + '[231433] Add a resource count anomaly detection policy', + { tag: ['@fast', '@p1'] }, + async ({ anomaliesPage, anomaliesCreatePage }) => { + const policyName = `E2E Test - Resource Count Anomaly - ${Date.now()}`; + + await test.step('Create a new resource count anomaly policy', async () => { + await anomaliesPage.clickAddBtn(); + const policyId = await anomaliesCreatePage.addNewAnomalyPolicy(policyName, 'Resource count', '14', '25'); + anomalyPolicyId.push(policyId); + }); - test('[231434] Add an expenses anomaly detection policy with filter', async ({ anomaliesPage, anomaliesCreatePage }) => { - const policyName = `E2E Test - Expense Anomaly - ${Date.now()}`; + await test.step('Verify policy is visible with correct details', async () => { + await expect.soft(anomaliesPage.policyLinkByName(policyName)).toBeVisible(); + await expect + .soft(anomaliesPage.policyDescriptionByName(policyName)) + .toHaveText('Daily resource count must not exceed the average amount for the last 14 days by 25%.'); + await expect.soft(anomaliesPage.policyFilterByName(policyName)).toHaveText('-'); + }); + } + ); - await test.step('Create a new expenses anomaly policy with a filter', async () => { - await anomaliesPage.clickAddBtn(); - const policyId = await anomaliesCreatePage.addNewAnomalyPolicy( - policyName, - 'Expenses', - '10', - '20', - anomaliesCreatePage.suggestionsFilter, - 'Assigned to me' - ); - anomalyPolicyId.push(policyId); - }); + test( + '[231434] Add an expenses anomaly detection policy with filter', + { tag: ['@fast', '@p2'] }, + async ({ anomaliesPage, anomaliesCreatePage }) => { + const policyName = `E2E Test - Expense Anomaly - ${Date.now()}`; + + await test.step('Create a new expenses anomaly policy with a filter', async () => { + await anomaliesPage.clickAddBtn(); + const policyId = await anomaliesCreatePage.addNewAnomalyPolicy( + policyName, + 'Expenses', + '10', + '20', + anomaliesCreatePage.suggestionsFilter, + 'Assigned to me' + ); + anomalyPolicyId.push(policyId); + }); - await test.step('Verify policy is visible with correct details and filter', async () => { - await expect.soft(anomaliesPage.policyLinkByName(policyName)).toBeVisible(); - await expect - .soft(anomaliesPage.policyDescriptionByName(policyName)) - .toHaveText('Daily expenses must not exceed the average amount for the last 10 days by 20%.'); - await expect - .soft(anomaliesPage.policyFilterByName(policyName)) - .toHaveText(`Owner: ${await anomaliesPage.getUserNameByEnvironment()}`); - }); - }); + await test.step('Verify policy is visible with correct details and filter', async () => { + await expect.soft(anomaliesPage.policyLinkByName(policyName)).toBeVisible(); + await expect + .soft(anomaliesPage.policyDescriptionByName(policyName)) + .toHaveText('Daily expenses must not exceed the average amount for the last 10 days by 20%.'); + await expect + .soft(anomaliesPage.policyFilterByName(policyName)) + .toHaveText(`Owner: ${await anomaliesPage.getUserNameByEnvironment()}`); + }); + } + ); - test('[231441] Verify delete policy functions correctly', async ({ anomaliesPage, anomaliesCreatePage }) => { + test('[231441] Verify delete policy functions correctly', { tag: ['@fast', '@p2'] }, async ({ anomaliesPage, anomaliesCreatePage }) => { const policyName = `E2E Test - Delete Anomaly Policy - ${Date.now()}`; await test.step('Create a new anomaly policy', async () => { @@ -316,131 +328,139 @@ test.describe('[MPT-14737] Mocked Anomalies Tests', { tag: ['@ui', '@anomalies'] interceptAPI: { entries: apiInterceptions, failOnInterceptionMissing: false }, }); - test('[231435] Verify Chart export for each category by comparing downloaded png', async ({ anomaliesPage }) => { - test.fixme(process.env.CI === '1', 'Tests do not work in CI. It appears that the png comparison is unsupported on linux'); - let actualPath = path.resolve('tests', 'downloads', 'anomaly-expenses-region-daily-chart-export.png'); - let expectedPath = path.resolve('tests', 'expected', 'expected-anomaly-expenses-region-daily-chart-export.png'); - let diffPath = path.resolve('tests', 'downloads', 'diff-anomaly-expenses-region-daily-chart-export.png'); - let match: boolean; - - await anomaliesPage.page.clock.setFixedTime(new Date('2025-11-13T12:45:00Z')); - await anomaliesPage.navigateToURL(); - - await test.step('Category: Region', async () => { - await anomaliesPage.click(anomaliesPage.defaultExpenseAnomalyLink); - await anomaliesPage.waitForAllProgressBarsToDisappear(); - await anomaliesPage.waitForCanvas(); - await anomaliesPage.selectCategorizeBy('Region'); - - await anomaliesPage.downloadFile(anomaliesPage.exportChartBtn, actualPath); - match = await comparePngImages(expectedPath, actualPath, diffPath); - expect.soft(match).toBe(true); - }); - - await test.step('Category: Resource type', async () => { - actualPath = path.resolve('tests', 'downloads', 'anomaly-expenses-resource-type-daily-chart-export.png'); - expectedPath = path.resolve('tests', 'expected', 'expected-anomaly-expenses-resource-type-daily-chart-export.png'); - diffPath = path.resolve('tests', 'downloads', 'diff-anomaly-expenses-resource-type-daily-chart-export.png'); + test( + '[231435] Verify Chart export for each category by comparing downloaded png', + { tag: ['@fast', '@p2'] }, + async ({ anomaliesPage }) => { + test.fixme(process.env.CI === '1', 'Tests do not work in CI. It appears that the png comparison is unsupported on linux'); + let actualPath = path.resolve('tests', 'downloads', 'anomaly-expenses-region-daily-chart-export.png'); + let expectedPath = path.resolve('tests', 'expected', 'expected-anomaly-expenses-region-daily-chart-export.png'); + let diffPath = path.resolve('tests', 'downloads', 'diff-anomaly-expenses-region-daily-chart-export.png'); + let match: boolean; + + await anomaliesPage.page.clock.setFixedTime(new Date('2025-11-13T12:45:00Z')); + await anomaliesPage.navigateToURL(); + + await test.step('Category: Region', async () => { + await anomaliesPage.click(anomaliesPage.defaultExpenseAnomalyLink); + await anomaliesPage.waitForAllProgressBarsToDisappear(); + await anomaliesPage.waitForCanvas(); + await anomaliesPage.selectCategorizeBy('Region'); + + await anomaliesPage.downloadFile(anomaliesPage.exportChartBtn, actualPath); + match = await comparePngImages(expectedPath, actualPath, diffPath); + expect.soft(match).toBe(true); + }); - await anomaliesPage.selectCategorizeBy('Resource type'); - await anomaliesPage.downloadFile(anomaliesPage.exportChartBtn, actualPath); - match = await comparePngImages(expectedPath, actualPath, diffPath); - expect.soft(match).toBe(true); - }); + await test.step('Category: Resource type', async () => { + actualPath = path.resolve('tests', 'downloads', 'anomaly-expenses-resource-type-daily-chart-export.png'); + expectedPath = path.resolve('tests', 'expected', 'expected-anomaly-expenses-resource-type-daily-chart-export.png'); + diffPath = path.resolve('tests', 'downloads', 'diff-anomaly-expenses-resource-type-daily-chart-export.png'); - await test.step('Category: Data source', async () => { - actualPath = path.resolve('tests', 'downloads', 'anomaly-expenses-data-source-daily-chart-export.png'); - expectedPath = path.resolve('tests', 'expected', 'expected-anomaly-expenses-data-source-daily-chart-export.png'); - diffPath = path.resolve('tests', 'downloads', 'diff-anomaly-expenses-data-source-daily-chart-export.png'); + await anomaliesPage.selectCategorizeBy('Resource type'); + await anomaliesPage.downloadFile(anomaliesPage.exportChartBtn, actualPath); + match = await comparePngImages(expectedPath, actualPath, diffPath); + expect.soft(match).toBe(true); + }); - await anomaliesPage.selectCategorizeBy('Data source'); - await anomaliesPage.downloadFile(anomaliesPage.exportChartBtn, actualPath); - match = await comparePngImages(expectedPath, actualPath, diffPath); - expect.soft(match).toBe(true); - }); + await test.step('Category: Data source', async () => { + actualPath = path.resolve('tests', 'downloads', 'anomaly-expenses-data-source-daily-chart-export.png'); + expectedPath = path.resolve('tests', 'expected', 'expected-anomaly-expenses-data-source-daily-chart-export.png'); + diffPath = path.resolve('tests', 'downloads', 'diff-anomaly-expenses-data-source-daily-chart-export.png'); - await test.step('Category: Owner', async () => { - actualPath = path.resolve('tests', 'downloads/anomaly-expenses-owner-daily-chart-export.png'); - expectedPath = path.resolve('tests', 'expected', 'expected-anomaly-expenses-owner-daily-chart-export.png'); - diffPath = path.resolve('tests', 'downloads', 'diff-anomaly-expenses-owner-daily-chart-export.png'); + await anomaliesPage.selectCategorizeBy('Data source'); + await anomaliesPage.downloadFile(anomaliesPage.exportChartBtn, actualPath); + match = await comparePngImages(expectedPath, actualPath, diffPath); + expect.soft(match).toBe(true); + }); - await anomaliesPage.selectCategorizeBy('Owner'); - await anomaliesPage.downloadFile(anomaliesPage.exportChartBtn, actualPath); - match = await comparePngImages(expectedPath, actualPath, diffPath); - expect.soft(match).toBe(true); - }); + await test.step('Category: Owner', async () => { + actualPath = path.resolve('tests', 'downloads/anomaly-expenses-owner-daily-chart-export.png'); + expectedPath = path.resolve('tests', 'expected', 'expected-anomaly-expenses-owner-daily-chart-export.png'); + diffPath = path.resolve('tests', 'downloads', 'diff-anomaly-expenses-owner-daily-chart-export.png'); - await test.step('Category: Pool', async () => { - actualPath = path.resolve('tests', 'downloads', 'anomaly-expenses-pool-daily-chart-export.png'); - expectedPath = path.resolve('tests', 'expected', 'expected-anomaly-expenses-pool-daily-chart-export.png'); - diffPath = path.resolve('tests', 'downloads', 'diff-anomaly-expenses-pool-daily-chart-export.png'); + await anomaliesPage.selectCategorizeBy('Owner'); + await anomaliesPage.downloadFile(anomaliesPage.exportChartBtn, actualPath); + match = await comparePngImages(expectedPath, actualPath, diffPath); + expect.soft(match).toBe(true); + }); - await anomaliesPage.selectCategorizeBy('Pool'); - await anomaliesPage.downloadFile(anomaliesPage.exportChartBtn, actualPath); - match = await comparePngImages(expectedPath, actualPath, diffPath); - expect.soft(match).toBe(true); - }); - }); + await test.step('Category: Pool', async () => { + actualPath = path.resolve('tests', 'downloads', 'anomaly-expenses-pool-daily-chart-export.png'); + expectedPath = path.resolve('tests', 'expected', 'expected-anomaly-expenses-pool-daily-chart-export.png'); + diffPath = path.resolve('tests', 'downloads', 'diff-anomaly-expenses-pool-daily-chart-export.png'); - test('[231436] Verify Chart export for each expenses option by comparing downloaded png', async ({ anomaliesPage }) => { - test.fixme(process.env.CI === '1', 'Tests do not work in CI. It appears that the png comparison is unsupported on linux'); - let actualPath: string; - let expectedPath: string; - let diffPath: string; - let match: boolean; + await anomaliesPage.selectCategorizeBy('Pool'); + await anomaliesPage.downloadFile(anomaliesPage.exportChartBtn, actualPath); + match = await comparePngImages(expectedPath, actualPath, diffPath); + expect.soft(match).toBe(true); + }); + } + ); - await anomaliesPage.page.clock.setFixedTime(new Date('2025-11-11T14:11:00Z')); - await anomaliesPage.navigateToURL(); - await anomaliesPage.click(anomaliesPage.defaultExpenseAnomalyLink); - await anomaliesPage.waitForAllProgressBarsToDisappear(); - await anomaliesPage.waitForCanvas(); + test( + '[231436] Verify Chart export for each expenses option by comparing downloaded png', + { tag: ['@fast', '@p2'] }, + async ({ anomaliesPage }) => { + test.fixme(process.env.CI === '1', 'Tests do not work in CI. It appears that the png comparison is unsupported on linux'); + let actualPath: string; + let expectedPath: string; + let diffPath: string; + let match: boolean; + + await anomaliesPage.page.clock.setFixedTime(new Date('2025-11-11T14:11:00Z')); + await anomaliesPage.navigateToURL(); + await anomaliesPage.click(anomaliesPage.defaultExpenseAnomalyLink); + await anomaliesPage.waitForAllProgressBarsToDisappear(); + await anomaliesPage.waitForCanvas(); - await test.step('Expenses: Daily (with legend)', async () => { - actualPath = path.resolve('tests', 'downloads', 'anomaly-expenses-service-daily-chart-export.png'); - expectedPath = path.resolve('tests', 'expected', 'expected-anomaly-expenses-service-daily-chart-export.png'); - diffPath = path.resolve('tests', 'downloads', 'diff-anomaly-expenses-service-daily-chart-export.png'); + await test.step('Expenses: Daily (with legend)', async () => { + actualPath = path.resolve('tests', 'downloads', 'anomaly-expenses-service-daily-chart-export.png'); + expectedPath = path.resolve('tests', 'expected', 'expected-anomaly-expenses-service-daily-chart-export.png'); + diffPath = path.resolve('tests', 'downloads', 'diff-anomaly-expenses-service-daily-chart-export.png'); - await anomaliesPage.downloadFile(anomaliesPage.exportChartBtn, actualPath); - match = await comparePngImages(expectedPath, actualPath, diffPath); - expect.soft(match).toBe(true); - }); + await anomaliesPage.downloadFile(anomaliesPage.exportChartBtn, actualPath); + match = await comparePngImages(expectedPath, actualPath, diffPath); + expect.soft(match).toBe(true); + }); - await test.step('Expenses: Daily (no legend)', async () => { - actualPath = path.resolve('tests', 'downloads', 'anomaly-expenses-service-daily-chart-no-legend-export.png'); - expectedPath = path.resolve('tests', 'expected', 'expected-anomaly-expenses-service-daily-chart-no-legend-export.png'); - diffPath = path.resolve('tests', 'downloads', 'diff-anomaly-expenses-service-daily-chart-no-legend-export.png'); + await test.step('Expenses: Daily (no legend)', async () => { + actualPath = path.resolve('tests', 'downloads', 'anomaly-expenses-service-daily-chart-no-legend-export.png'); + expectedPath = path.resolve('tests', 'expected', 'expected-anomaly-expenses-service-daily-chart-no-legend-export.png'); + diffPath = path.resolve('tests', 'downloads', 'diff-anomaly-expenses-service-daily-chart-no-legend-export.png'); - await anomaliesPage.clickShowLegend(); - await anomaliesPage.downloadFile(anomaliesPage.exportChartBtn, actualPath); - match = await comparePngImages(expectedPath, actualPath, diffPath); - expect.soft(match).toBe(true); - }); + await anomaliesPage.clickShowLegend(); + await anomaliesPage.downloadFile(anomaliesPage.exportChartBtn, actualPath); + match = await comparePngImages(expectedPath, actualPath, diffPath); + expect.soft(match).toBe(true); + }); - await test.step('Expenses: Weekly', async () => { - actualPath = path.resolve('tests', 'downloads', 'anomaly-expenses-service-weekly-chart-export.png'); - expectedPath = path.resolve('tests', 'expected', 'expected-anomaly-expenses-service-weekly-chart-export.png'); - diffPath = path.resolve('tests', 'downloads', 'diff-anomaly-expenses-service-weekly-chart-export.png'); + await test.step('Expenses: Weekly', async () => { + actualPath = path.resolve('tests', 'downloads', 'anomaly-expenses-service-weekly-chart-export.png'); + expectedPath = path.resolve('tests', 'expected', 'expected-anomaly-expenses-service-weekly-chart-export.png'); + diffPath = path.resolve('tests', 'downloads', 'diff-anomaly-expenses-service-weekly-chart-export.png'); - await anomaliesPage.clickShowLegend(); - await anomaliesPage.selectExpenses('Weekly'); - await anomaliesPage.downloadFile(anomaliesPage.exportChartBtn, actualPath); - match = await comparePngImages(expectedPath, actualPath, diffPath); - expect.soft(match).toBe(true); - }); + await anomaliesPage.clickShowLegend(); + await anomaliesPage.selectExpenses('Weekly'); + await anomaliesPage.downloadFile(anomaliesPage.exportChartBtn, actualPath); + match = await comparePngImages(expectedPath, actualPath, diffPath); + expect.soft(match).toBe(true); + }); - await test.step('Expenses: Monthly', async () => { - actualPath = path.resolve('tests', 'downloads', 'anomaly-expenses-service-monthly-chart-export.png'); - expectedPath = path.resolve('tests', 'expected', 'expected-anomaly-expenses-service-monthly-chart-export.png'); - diffPath = path.resolve('tests', 'downloads', 'diff-anomaly-expenses-service-monthly-chart-export.png'); + await test.step('Expenses: Monthly', async () => { + actualPath = path.resolve('tests', 'downloads', 'anomaly-expenses-service-monthly-chart-export.png'); + expectedPath = path.resolve('tests', 'expected', 'expected-anomaly-expenses-service-monthly-chart-export.png'); + diffPath = path.resolve('tests', 'downloads', 'diff-anomaly-expenses-service-monthly-chart-export.png'); - await anomaliesPage.selectExpenses('Monthly'); - await anomaliesPage.downloadFile(anomaliesPage.exportChartBtn, actualPath); - match = await comparePngImages(expectedPath, actualPath, diffPath); - expect.soft(match).toBe(true); - }); - }); + await anomaliesPage.selectExpenses('Monthly'); + await anomaliesPage.downloadFile(anomaliesPage.exportChartBtn, actualPath); + match = await comparePngImages(expectedPath, actualPath, diffPath); + expect.soft(match).toBe(true); + }); + } + ); - test('[231439] Verify detected anomalies are displayed in the table correctly', async ({ anomaliesPage }) => { + test('[231439] Verify detected anomalies are displayed in the table correctly', { tag: ['@fast', '@p2'] }, async ({ anomaliesPage }) => { await anomaliesPage.page.clock.setFixedTime(new Date('2025-11-11T14:11:00Z')); await anomaliesPage.navigateToURL(); diff --git a/e2etests/tests/cloud-accounts-tests.spec.ts b/e2etests/tests/cloud-accounts-tests.spec.ts index 4392efeab..77a9d9ccf 100644 --- a/e2etests/tests/cloud-accounts-tests.spec.ts +++ b/e2etests/tests/cloud-accounts-tests.spec.ts @@ -27,7 +27,7 @@ test.describe('Cloud Accounts Tests', { tag: ['@ui', '@cloud-accounts'] }, () => // the test datasource that we can configure without external dependencies. test.fixme( '[231860] A successful billing import should have been successful within the last 24 hours', - { tag: '@p1' }, + { tag: ['@fast', '@p1'] }, async ({ page, cloudAccountsPage }) => { let dataSourceResponse: DataSourceBillingResponse; const now = Math.floor(Date.now() / 1000); @@ -57,30 +57,34 @@ test.describe('Cloud Accounts Tests', { tag: ['@ui', '@cloud-accounts'] }, () => } ); - test('[231861] Verify adding a new AWS Assumed role - Management', async ({ cloudAccountsPage, cloudAccountsConnectPage }) => { - test.fixme(); //'Skipping due to these tests possibly corrupting data due to orphaned sub-pools when disconnecting accounts' - await cloudAccountsPage.navigateToCloudAccountsPage(); - const awsAccountName = 'Marketplace (Dev)'; + test( + '[231861] Verify adding a new AWS Assumed role - Management', + { tag: ['@fast', '@p2'] }, + async ({ cloudAccountsPage, cloudAccountsConnectPage }) => { + test.fixme(); //'Skipping due to these tests possibly corrupting data due to orphaned sub-pools when disconnecting accounts' + await cloudAccountsPage.navigateToCloudAccountsPage(); + const awsAccountName = 'Marketplace (Dev)'; - await test.step(`Disconnect ${awsAccountName} if connected`, async () => { - await cloudAccountsPage.disconnectIfConnectedCloudAccountByName(awsAccountName); - }); + await test.step(`Disconnect ${awsAccountName} if connected`, async () => { + await cloudAccountsPage.disconnectIfConnectedCloudAccountByName(awsAccountName); + }); - await test.step('Add AWS management account with assumed role', async () => { - await cloudAccountsPage.clickAddBtn(); - await cloudAccountsConnectPage.addAWSAssumedRoleAccount(awsAccountName, EAWSAccountType.management); - }); + await test.step('Add AWS management account with assumed role', async () => { + await cloudAccountsPage.clickAddBtn(); + await cloudAccountsConnectPage.addAWSAssumedRoleAccount(awsAccountName, EAWSAccountType.management); + }); - await test.step(`Verify ${awsAccountName} is connected`, async () => { - await cloudAccountsPage.allCloudAccountLinks.last().waitFor(); - const cloudAccountLink = cloudAccountsPage.getCloudAccountLinkByName(awsAccountName); - await expect(cloudAccountLink).toBeVisible(); - }); - }); + await test.step(`Verify ${awsAccountName} is connected`, async () => { + await cloudAccountsPage.allCloudAccountLinks.last().waitFor(); + const cloudAccountLink = cloudAccountsPage.getCloudAccountLinkByName(awsAccountName); + await expect(cloudAccountLink).toBeVisible(); + }); + } + ); test( '[231862] Verify adding a new AWS Assumed role - Member', - { tag: '@p1' }, + { tag: ['@fast', '@p1'] }, async ({ cloudAccountsPage, cloudAccountsConnectPage }) => { test.fixme(); //'Skipping due to these tests possibly corrupting data due to orphaned sub-pools when disconnecting accounts' await cloudAccountsPage.navigateToCloudAccountsPage(); @@ -103,77 +107,85 @@ test.describe('Cloud Accounts Tests', { tag: ['@ui', '@cloud-accounts'] }, () => } ); - test('[231863] Verify adding a new AWS Assumed role - Standalone', async ({ cloudAccountsPage, cloudAccountsConnectPage }) => { - test.fixme(); //'Skipping due to these tests possibly corrupting data due to orphaned sub-pools when disconnecting accounts' - await cloudAccountsPage.navigateToCloudAccountsPage(); - const awsAccountName = 'Marketplace (Dev)'; - - await test.step(`Disconnect ${awsAccountName} if connected`, async () => { - await cloudAccountsPage.disconnectIfConnectedCloudAccountByName(awsAccountName); - }); - - await test.step('Add AWS standalone account with assumed role', async () => { - await cloudAccountsPage.clickAddBtn(); - await cloudAccountsConnectPage.addAWSAssumedRoleAccount(awsAccountName, EAWSAccountType.management); - }); - - await test.step(`Verify ${awsAccountName} is connected`, async () => { - await cloudAccountsPage.allCloudAccountLinks.last().waitFor(); - const cloudAccountLink = cloudAccountsPage.getCloudAccountLinkByName(awsAccountName); - await expect(cloudAccountLink).toBeVisible(); - }); - }); - - test('[232861] Verify that a message is displayed recommending the Assume role method for AWS accounts, when Access key method is selected', async ({ - cloudAccountsPage, - cloudAccountsConnectPage, - }) => { - const expectedMessage = - 'We recommend using the Assume Role method to provide access to your AWS account. For more information, please see the documentation.'; - - - await test.step('Navigate to add cloud account page and select AWS Access key method', async () => { - await cloudAccountsPage.navigateToCloudAccountsPage(); - await cloudAccountsPage.clickAddBtn(); - await cloudAccountsConnectPage.clickDataSourceTileIfNotActive(cloudAccountsConnectPage.awsRootBtn); - await cloudAccountsConnectPage.clickAccessKey(); - }); - - await test.step('Verify that recommendation message is displayed', async () => { - await expect(cloudAccountsConnectPage.alertMessage).toBeVisible(); - await expect(cloudAccountsConnectPage.alertMessage).toHaveText(expectedMessage); - }); - }); - - test('[232862] Verify that the user can schedule a billing reimport, and see warning alert', async ({ cloudAccountsPage }) => { - const expectedAlertMessage = - 'Reimporting billing starting from the selected import date will overwrite existing billing data. This action may cause discrepancies or breaks in the current billing records and can take some time to complete. The new billing data will be imported during the next billing import report processing. Please proceed with caution, as this process cannot be undone. Ensure that this action is necessary and that you are prepared for any potential data loss and inaccuracies in billing tracking.'; - - await test.step('Navigate to the Billing reimport side modal', async () => { + test( + '[231863] Verify adding a new AWS Assumed role - Standalone', + { tag: ['@fast', '@p2'] }, + async ({ cloudAccountsPage, cloudAccountsConnectPage }) => { + test.fixme(); //'Skipping due to these tests possibly corrupting data due to orphaned sub-pools when disconnecting accounts' await cloudAccountsPage.navigateToCloudAccountsPage(); - await cloudAccountsPage.clickCloudAccountLinkByName('Marketplace (Dev)'); - await cloudAccountsPage.clickBillingReimportBtn(); - await cloudAccountsPage.billingReimportSideModal.waitFor(); - }); - - await test.step('Verify that the warning alert is displayed with correct message', async () => { - await expect(cloudAccountsPage.billingReimportAlert).toBeVisible(); - await expect(cloudAccountsPage.billingReimportAlert).toHaveText(expectedAlertMessage); - }); - - await test.step('Verify that API request is successfully made when scheduling a billing reimport with default date', async () => { - let responseStatus: number; - const [response] = await Promise.all([ - cloudAccountsPage.page.waitForResponse( - resp => resp.request().postData().includes('operationName":"UpdateDataSource') && resp.request().method() === 'POST' - ), - cloudAccountsPage.scheduleImportWithDefaultDate(), - ]); - responseStatus = response.status(); - debugLog(`API Response status: ${responseStatus}`); - expect(responseStatus).toBe(200); - }); - }); + const awsAccountName = 'Marketplace (Dev)'; + + await test.step(`Disconnect ${awsAccountName} if connected`, async () => { + await cloudAccountsPage.disconnectIfConnectedCloudAccountByName(awsAccountName); + }); + + await test.step('Add AWS standalone account with assumed role', async () => { + await cloudAccountsPage.clickAddBtn(); + await cloudAccountsConnectPage.addAWSAssumedRoleAccount(awsAccountName, EAWSAccountType.management); + }); + + await test.step(`Verify ${awsAccountName} is connected`, async () => { + await cloudAccountsPage.allCloudAccountLinks.last().waitFor(); + const cloudAccountLink = cloudAccountsPage.getCloudAccountLinkByName(awsAccountName); + await expect(cloudAccountLink).toBeVisible(); + }); + } + ); + + test( + '[232861] Verify that a message is displayed recommending the Assume role method for AWS accounts, when Access key method is selected', + { tag: ['@fast', '@p2'] }, + async ({ cloudAccountsPage, cloudAccountsConnectPage }) => { + const expectedMessage = + 'We recommend using the Assume Role method to provide access to your AWS account. For more information, please see the documentation.'; + + await test.step('Navigate to add cloud account page and select AWS Access key method', async () => { + await cloudAccountsPage.navigateToCloudAccountsPage(); + await cloudAccountsPage.clickAddBtn(); + await cloudAccountsConnectPage.clickDataSourceTileIfNotActive(cloudAccountsConnectPage.awsRootBtn); + await cloudAccountsConnectPage.clickAccessKey(); + }); + + await test.step('Verify that recommendation message is displayed', async () => { + await expect(cloudAccountsConnectPage.alertMessage).toBeVisible(); + await expect(cloudAccountsConnectPage.alertMessage).toHaveText(expectedMessage); + }); + } + ); + + test( + '[232862] Verify that the user can schedule a billing reimport, and see warning alert', + { tag: ['@fast', '@p2'] }, + async ({ cloudAccountsPage }) => { + const expectedAlertMessage = + 'Reimporting billing starting from the selected import date will overwrite existing billing data. This action may cause discrepancies or breaks in the current billing records and can take some time to complete. The new billing data will be imported during the next billing import report processing. Please proceed with caution, as this process cannot be undone. Ensure that this action is necessary and that you are prepared for any potential data loss and inaccuracies in billing tracking.'; + + await test.step('Navigate to the Billing reimport side modal', async () => { + await cloudAccountsPage.navigateToCloudAccountsPage(); + await cloudAccountsPage.clickCloudAccountLinkByName('Marketplace (Dev)'); + await cloudAccountsPage.clickBillingReimportBtn(); + await cloudAccountsPage.billingReimportSideModal.waitFor(); + }); + + await test.step('Verify that the warning alert is displayed with correct message', async () => { + await expect(cloudAccountsPage.billingReimportAlert).toBeVisible(); + await expect(cloudAccountsPage.billingReimportAlert).toHaveText(expectedAlertMessage); + }); + + await test.step('Verify that API request is successfully made when scheduling a billing reimport with default date', async () => { + let responseStatus: number; + const [response] = await Promise.all([ + cloudAccountsPage.page.waitForResponse( + resp => resp.request().postData().includes('operationName":"UpdateDataSource') && resp.request().method() === 'POST' + ), + cloudAccountsPage.scheduleImportWithDefaultDate(), + ]); + responseStatus = response.status(); + debugLog(`API Response status: ${responseStatus}`); + expect(responseStatus).toBe(200); + }); + } + ); }); test.describe('Mocked Cloud Accounts Tests', { tag: ['@ui', '@cloud-accounts'] }, () => { @@ -222,38 +234,41 @@ test.describe('Mocked Cloud Accounts Tests', { tag: ['@ui', '@cloud-accounts'] } ]; test.use({ restoreSession: true, interceptAPI: { entries: apiInterceptions, failOnInterceptionMissing: true } }); - test('[232859] Verify the correct messages are displayed when updating an AWS Access Key account', async ({ cloudAccountsPage }) => { - const accessKeyMessage = - 'Access keys are a set of permanent credentials. This authentication type is not recommended by SoftwareOne or AWS - use an assumed role where possible.More information'; - const permissionsMessage = - 'Please make sure that updated credentials have enough permissions to perform billing import and resource discovery to avoid interruptions in data source processing.'; - const assumeRoleMessage = - 'Switching from an access key to an assumed role is permanent. After you make this change, you can’t switch back to using an access key for this data source.If you later want to use an access key again, you’ll need to delete this data source and recreate it with access key credentials. This will delete all existing data for this data source and require a full reimport of the resource and billing data.'; + test( + '[232859] Verify the correct messages are displayed when updating an AWS Access Key account', + { tag: ['@fast', '@p2'] }, + async ({ cloudAccountsPage }) => { + const accessKeyMessage = + 'Access keys are a set of permanent credentials. This authentication type is not recommended by SoftwareOne or AWS - use an assumed role where possible.More information'; + const permissionsMessage = + 'Please make sure that updated credentials have enough permissions to perform billing import and resource discovery to avoid interruptions in data source processing.'; + const assumeRoleMessage = + 'Switching from an access key to an assumed role is permanent. After you make this change, you can’t switch back to using an access key for this data source.If you later want to use an access key again, you’ll need to delete this data source and recreate it with access key credentials. This will delete all existing data for this data source and require a full reimport of the resource and billing data.'; + + await test.step('Navigate to update credentials side modal for AWS Access Key account', async () => { + await cloudAccountsPage.navigateToCloudAccountsPage(); + await cloudAccountsPage.clickCloudAccountLinkByName('Marketplace (Production)'); + await cloudAccountsPage.clickUpdateCredentialsBtn(); + }); - await test.step('Navigate to update credentials side modal for AWS Access Key account', async () => { - await cloudAccountsPage.navigateToCloudAccountsPage(); - await cloudAccountsPage.clickCloudAccountLinkByName('Marketplace (Production)'); - await cloudAccountsPage.clickUpdateCredentialsBtn(); - }); - - await test.step('Verify that the correct messages are displayed for Access Key selected', async () => { - await cloudAccountsPage.clickButtonIfNotActive(cloudAccountsPage.sideModalAccessKeyButton); - await expect.soft(cloudAccountsPage.sideModalPrimaryAlert).toBeVisible(); - await expect.soft(cloudAccountsPage.sideModalPrimaryAlert).toHaveText(accessKeyMessage); - await expect.soft(cloudAccountsPage.sideModalSecondaryAlert).toBeVisible(); - await expect.soft(cloudAccountsPage.sideModalSecondaryAlert).toHaveText(permissionsMessage); - }); - - await test.step('Verify that the correct messages are displayed for Assume Role selected', async () => { - await cloudAccountsPage.clickButtonIfNotActive(cloudAccountsPage.sideModalAssumedRoleButton); - await expect.soft(cloudAccountsPage.sideModalPrimaryAlert).toBeVisible(); - await expect.soft(cloudAccountsPage.sideModalPrimaryAlert).toHaveText(assumeRoleMessage); - await expect.soft(cloudAccountsPage.sideModalSecondaryAlert).toBeVisible(); - await expect.soft(cloudAccountsPage.sideModalSecondaryAlert).toHaveText(permissionsMessage); - }); - }); -}); + await test.step('Verify that the correct messages are displayed for Access Key selected', async () => { + await cloudAccountsPage.clickButtonIfNotActive(cloudAccountsPage.sideModalAccessKeyButton); + await expect.soft(cloudAccountsPage.sideModalPrimaryAlert).toBeVisible(); + await expect.soft(cloudAccountsPage.sideModalPrimaryAlert).toHaveText(accessKeyMessage); + await expect.soft(cloudAccountsPage.sideModalSecondaryAlert).toBeVisible(); + await expect.soft(cloudAccountsPage.sideModalSecondaryAlert).toHaveText(permissionsMessage); + }); + await test.step('Verify that the correct messages are displayed for Assume Role selected', async () => { + await cloudAccountsPage.clickButtonIfNotActive(cloudAccountsPage.sideModalAssumedRoleButton); + await expect.soft(cloudAccountsPage.sideModalPrimaryAlert).toBeVisible(); + await expect.soft(cloudAccountsPage.sideModalPrimaryAlert).toHaveText(assumeRoleMessage); + await expect.soft(cloudAccountsPage.sideModalSecondaryAlert).toBeVisible(); + await expect.soft(cloudAccountsPage.sideModalSecondaryAlert).toHaveText(permissionsMessage); + }); + } + ); +}); test.describe( '[MPT-18378] Verify Cloud Account actions are recorded correctly in the events log', @@ -263,74 +278,77 @@ test.describe( test.fixme(); //'Skipping due to these tests possibly corrupting data due to orphaned sub-pools when disconnecting accounts' test.use({ restoreSession: true }); - test('[232954] Verify that disconnecting and creating a cloud account is recorded in the events log', async ({ - cloudAccountsPage, - cloudAccountsConnectPage, - eventsPage, - }) => { - const awsAccountName = 'Marketplace (Dev)'; - let timestamp: string; - - await test.step('Login admin user and disconnect cloud account', async () => { - await cloudAccountsPage.navigateToCloudAccountsPage(); - timestamp = getCurrentUTCTimestamp(); - await cloudAccountsPage.disconnectCloudAccountByName(awsAccountName); - - debugLog(`Timestamp: ${timestamp}`); - }); - - await test.step('Navigate to events page and verify disconnect event is recorded with correct time', async () => { - await eventsPage.navigateToURL(); - await eventsPage.waitForAllProgressBarsToDisappear(); - - const disconnectEvent = eventsPage.getEventByMultipleTexts([`Cloud account ${awsAccountName}`, 'deleted']); - await expect.soft(disconnectEvent).toBeVisible(); - - const eventText = await disconnectEvent.textContent(); - debugLog(`Disconnect event text: ${eventText}`); - - // Generate timestamps with ±1 minute variance - const timestamps = getTimestampWithVariance(timestamp); - debugLog(`Checking for timestamps: ${timestamps.join(', ')}`); - - // Assert that the event text contains at least one of the timestamps - const hasMatchingTimestamp = timestamps.some(ts => eventText.includes(`${ts} UTC`)); - expect.soft(hasMatchingTimestamp, `Event should contain one of the timestamps: ${timestamps.join(', ')}`).toBe(true); - }); - - await test.step('Add new cloud account and ensure that the events log includes account and pool creation', async () => { - await cloudAccountsPage.navigateToURL(); - await cloudAccountsPage.clickAddBtn(); - timestamp = getCurrentUTCTimestamp(); - debugLog(`Timestamp: ${timestamp}`); - await cloudAccountsConnectPage.addAWSAssumedRoleAccount(awsAccountName, EAWSAccountType.management); - - await eventsPage.navigateToURL(); - await eventsPage.waitForAllProgressBarsToDisappear(); - - const timestamps = getTimestampWithVariance(timestamp); - debugLog(`Checking for timestamps: ${timestamps.join(', ')}`); - - const creationEvent = eventsPage.getEventByMultipleTexts([`Cloud account ${awsAccountName}`, 'created']); - await expect.soft(creationEvent).toBeVisible(); - - const eventText = await creationEvent.textContent(); - debugLog(`Creation event text: ${eventText}`); - - // Assert that the event text contains at least one of the timestamps - const hasMatchingTimestamp = timestamps.some(ts => eventText.includes(`${ts} UTC`)); - expect.soft(hasMatchingTimestamp, `Event should contain one of the timestamps: ${timestamps.join(', ')}`).toBe(true); - - const poolCreationEvent = eventsPage.getEventByMultipleTexts([`Rule for ${awsAccountName}`, `created for pool ${awsAccountName}`]); - await expect.soft(poolCreationEvent).toBeVisible(); - const poolEventText = await poolCreationEvent.textContent(); - debugLog(`Pool Creation event text: ${poolEventText}`); - - // Assert that the pool event text contains at least one of the timestamps (reusing same timestamps array) - const hasMatchingPoolTimestamp = timestamps.some(ts => poolEventText.includes(`${ts} UTC`)); - expect.soft(hasMatchingPoolTimestamp, `Event should contain one of the timestamps: ${timestamps.join(', ')}`).toBe(true); - expect.soft(poolEventText).toContain(process.env.DEFAULT_USER_EMAIL); - }); - }); + test( + '[232954] Verify that disconnecting and creating a cloud account is recorded in the events log', + { tag: ['@fast', '@p2'] }, + async ({ cloudAccountsPage, cloudAccountsConnectPage, eventsPage }) => { + const awsAccountName = 'Marketplace (Dev)'; + let timestamp: string; + + await test.step('Login admin user and disconnect cloud account', async () => { + await cloudAccountsPage.navigateToCloudAccountsPage(); + timestamp = getCurrentUTCTimestamp(); + await cloudAccountsPage.disconnectCloudAccountByName(awsAccountName); + + debugLog(`Timestamp: ${timestamp}`); + }); + + await test.step('Navigate to events page and verify disconnect event is recorded with correct time', async () => { + await eventsPage.navigateToURL(); + await eventsPage.waitForAllProgressBarsToDisappear(); + + const disconnectEvent = eventsPage.getEventByMultipleTexts([`Cloud account ${awsAccountName}`, 'deleted']); + await expect.soft(disconnectEvent).toBeVisible(); + + const eventText = await disconnectEvent.textContent(); + debugLog(`Disconnect event text: ${eventText}`); + + // Generate timestamps with ±1 minute variance + const timestamps = getTimestampWithVariance(timestamp); + debugLog(`Checking for timestamps: ${timestamps.join(', ')}`); + + // Assert that the event text contains at least one of the timestamps + const hasMatchingTimestamp = timestamps.some(ts => eventText.includes(`${ts} UTC`)); + expect.soft(hasMatchingTimestamp, `Event should contain one of the timestamps: ${timestamps.join(', ')}`).toBe(true); + }); + + await test.step('Add new cloud account and ensure that the events log includes account and pool creation', async () => { + await cloudAccountsPage.navigateToURL(); + await cloudAccountsPage.clickAddBtn(); + timestamp = getCurrentUTCTimestamp(); + debugLog(`Timestamp: ${timestamp}`); + await cloudAccountsConnectPage.addAWSAssumedRoleAccount(awsAccountName, EAWSAccountType.management); + + await eventsPage.navigateToURL(); + await eventsPage.waitForAllProgressBarsToDisappear(); + + const timestamps = getTimestampWithVariance(timestamp); + debugLog(`Checking for timestamps: ${timestamps.join(', ')}`); + + const creationEvent = eventsPage.getEventByMultipleTexts([`Cloud account ${awsAccountName}`, 'created']); + await expect.soft(creationEvent).toBeVisible(); + + const eventText = await creationEvent.textContent(); + debugLog(`Creation event text: ${eventText}`); + + // Assert that the event text contains at least one of the timestamps + const hasMatchingTimestamp = timestamps.some(ts => eventText.includes(`${ts} UTC`)); + expect.soft(hasMatchingTimestamp, `Event should contain one of the timestamps: ${timestamps.join(', ')}`).toBe(true); + + const poolCreationEvent = eventsPage.getEventByMultipleTexts([ + `Rule for ${awsAccountName}`, + `created for pool ${awsAccountName}`, + ]); + await expect.soft(poolCreationEvent).toBeVisible(); + const poolEventText = await poolCreationEvent.textContent(); + debugLog(`Pool Creation event text: ${poolEventText}`); + + // Assert that the pool event text contains at least one of the timestamps (reusing same timestamps array) + const hasMatchingPoolTimestamp = timestamps.some(ts => poolEventText.includes(`${ts} UTC`)); + expect.soft(hasMatchingPoolTimestamp, `Event should contain one of the timestamps: ${timestamps.join(', ')}`).toBe(true); + expect.soft(poolEventText).toContain(process.env.DEFAULT_USER_EMAIL); + }); + } + ); } ); diff --git a/e2etests/tests/expenses-tests.spec.ts b/e2etests/tests/expenses-tests.spec.ts index 626c152b1..5345d4b83 100644 --- a/e2etests/tests/expenses-tests.spec.ts +++ b/e2etests/tests/expenses-tests.spec.ts @@ -44,7 +44,7 @@ test.describe('[MPT-12859] Expenses Page default view Tests', { tag: ['@ui', '@e } }); - test('[231181] Verify default Expenses Page layout', async ({ expensesPage }) => { + test('[231181] Verify default Expenses Page layout', { tag: ['@fast', '@p2'] }, async ({ expensesPage }) => { await expect.soft(expensesPage.downloadButton).toBeVisible(); expect.soft(dateRangeReset).toBe(false); expect.soft(await expensesPage.evaluateActiveButton(expensesPage.dailyBtn)).toBe(true); @@ -55,7 +55,7 @@ test.describe('[MPT-12859] Expenses Page default view Tests', { tag: ['@ui', '@e await expect(expensesPage.geographyBtn).toBeVisible(); }); - test('Validate API default chart data', { tag: '@p1' }, async ({ expensesPage }) => { + test('[231182] Validate API default chart data', { tag: ['@fast', '@p1'] }, async ({ expensesPage }) => { //if it's the first day of the month, the API will return 0 expenses for the current month, so we need to get the date range for last 7 days to validate the data const { startDate, endDate } = currentDate.getDate() === 1 ? getLast7DaysUnixRange() : getThisMonthUnixDateRange(); let expensesData: ExpensesResponse; @@ -69,7 +69,7 @@ test.describe('[MPT-12859] Expenses Page default view Tests', { tag: ['@ui', '@e expensesData = await expensesResponse.json(); }); - await test.step('[231182] Validate expenses date range and breakdown type', async () => { + await test.step('Validate expenses date range and breakdown type', async () => { expect.soft(expensesData.expenses.total).toBeGreaterThan(0); expect.soft(expensesData.expenses.previous_total).toBeLessThan(startDate); expect.soft(expensesData.expenses.name).toBe(name); @@ -96,10 +96,14 @@ test.describe('[MPT-12859] Expenses Page default view Tests', { tag: ['@ui', '@e }); }); - test('[231212] Breakdown by Geography button navigates to Cost map page', async ({ expensesPage, expensesMapPage }) => { - await expensesPage.geographyBtn.click(); - await expect(expensesMapPage.heading).toBeVisible(); - }); + test( + '[231212] Breakdown by Geography button navigates to Cost map page', + { tag: ['@fast', '@p2'] }, + async ({ expensesPage, expensesMapPage }) => { + await expensesPage.geographyBtn.click(); + await expect(expensesMapPage.heading).toBeVisible(); + } + ); }); test.describe('[MPT-12859] Expenses page default view mocked tests', { tag: ['@ui', '@expenses'] }, () => { @@ -127,7 +131,7 @@ test.describe('[MPT-12859] Expenses page default view mocked tests', { tag: ['@u await expensesPage.clickDailyBtnIfNotSelected(); }); }); - test('[231183] Verify expenses chart download', { tag: '@p1' }, async ({ expensesPage }) => { + test('[231183] Verify expenses chart download', { tag: ['@fast', '@p1'] }, async ({ expensesPage }) => { let actualPath = path.resolve('tests', 'downloads', 'expenses-page-daily-chart.pdf'); let expectedPath = path.resolve('tests', 'expected', 'expected-expenses-page-daily-chart.pdf'); let diffPath = path.resolve('tests', 'downloads', 'expenses-page-daily-chart-diff.png'); @@ -175,7 +179,7 @@ test.describe('[MPT-12859] Expenses Page Source Breakdown Tests', { tag: ['@ui', await expensesPage.waitForAllProgressBarsToDisappear(); await expensesPage.waitForCanvas(); - if(currentDate.getDate() === 1) { + if (currentDate.getDate() === 1) { debugLog('Current date is the first of the month, selecting last 7 days date range to ensure data is displayed'); await datePicker.selectLast7DaysDateRange(); await expensesPage.waitForAllProgressBarsToDisappear(); @@ -190,7 +194,7 @@ test.describe('[MPT-12859] Expenses Page Source Breakdown Tests', { tag: ['@ui', await expensesPage.waitForCanvas(); }); - test('[231214] Verify Expenses Page Source Breakdown layout', async ({ expensesPage, datePicker }) => { + test('[231214] Verify Expenses Page Source Breakdown layout', { tag: ['@fast', '@p2'] }, async ({ expensesPage, datePicker }) => { await test.step('Verify Expenses Page Source Breakdown elements', async () => { await expect(expensesPage.downloadButton).toBeHidden(); await expect(expensesPage.seeExpensesBreakdownGrid).toBeHidden(); @@ -207,7 +211,7 @@ test.describe('[MPT-12859] Expenses Page Source Breakdown Tests', { tag: ['@ui', }); }); - test('[231215] Validate API Source Breakdown chart data', async ({ expensesPage }) => { + test('[231215] Validate API Source Breakdown chart data', { tag: ['@fast', '@p2'] }, async ({ expensesPage }) => { //if it's the first day of the month, the API will return 0 expenses for the current month, so we need to get the date range for last 7 days to validate the data const { startDate, endDate } = currentDate.getDate() === 1 ? getLast7DaysUnixRange() : getThisMonthUnixDateRange(); let expensesData: ExpensesFilterByDataSourceResponse; @@ -273,7 +277,7 @@ test.describe('[MPT-12859] Expenses Page Source Breakdown Tests', { tag: ['@ui', test( '[231216] Verify data source expenses total for(default) period matches chart and table totals', - { tag: '@p1' }, + { tag: ['@fast', '@p1'] }, async ({ expensesPage }) => { const totalForPeriod = await expensesPage.getTotalExpensesForSelectedPeriod(); debugLog(`Total expenses for selected period: ${totalForPeriod}`); @@ -289,28 +293,29 @@ test.describe('[MPT-12859] Expenses Page Source Breakdown Tests', { tag: ['@ui', } ); - test('[231217] Verify data source expenses total for (last month) period matches chart and table totals', async ({ - expensesPage, - datePicker, - }) => { - await datePicker.selectLastMonthDateRange(); - const expectedDateRange = getExpectedDateRangeText('last month'); - debugLog(`Selected date range: ${expectedDateRange}`); - const totalForPeriod = await expensesPage.getTotalExpensesForSelectedPeriod(); - debugLog(`Total expenses for selected period: ${totalForPeriod}`); - const chartTotal = await expensesPage.getExpensesPieChartValue(); - debugLog(`Total expenses from pie chart: ${chartTotal}`); - const tableTotal = await expensesPage.getTableItemisedExpensesValue(); - debugLog(`Total expenses from itemised table: ${tableTotal}`); - - await test.step('Compare total expenses values', async () => { - const dateRange = await datePicker.selectedDateText.textContent(); - debugLog(`Actual date range: ${dateRange}`); - expect.soft(dateRange.includes(expectedDateRange)).toBe(true); - expect.soft(isWithinRoundingDrift(chartTotal, totalForPeriod, 0.001)).toBe(true); - expect.soft(isWithinRoundingDrift(tableTotal, totalForPeriod, 0.001)).toBe(true); - }); - }); + test( + '[231217] Verify data source expenses total for (last month) period matches chart and table totals', + { tag: ['@fast', '@p2'] }, + async ({ expensesPage, datePicker }) => { + await datePicker.selectLastMonthDateRange(); + const expectedDateRange = getExpectedDateRangeText('last month'); + debugLog(`Selected date range: ${expectedDateRange}`); + const totalForPeriod = await expensesPage.getTotalExpensesForSelectedPeriod(); + debugLog(`Total expenses for selected period: ${totalForPeriod}`); + const chartTotal = await expensesPage.getExpensesPieChartValue(); + debugLog(`Total expenses from pie chart: ${chartTotal}`); + const tableTotal = await expensesPage.getTableItemisedExpensesValue(); + debugLog(`Total expenses from itemised table: ${tableTotal}`); + + await test.step('Compare total expenses values', async () => { + const dateRange = await datePicker.selectedDateText.textContent(); + debugLog(`Actual date range: ${dateRange}`); + expect.soft(dateRange.includes(expectedDateRange)).toBe(true); + expect.soft(isWithinRoundingDrift(chartTotal, totalForPeriod, 0.001)).toBe(true); + expect.soft(isWithinRoundingDrift(tableTotal, totalForPeriod, 0.001)).toBe(true); + }); + } + ); }); test.describe('[MPT-12859] Expenses Page Pool Breakdown Tests', { tag: ['@ui', '@expenses'] }, () => { @@ -326,7 +331,7 @@ test.describe('[MPT-12859] Expenses Page Pool Breakdown Tests', { tag: ['@ui', ' await expensesPage.waitForAllProgressBarsToDisappear(); await expensesPage.waitForCanvas(); - if(currentDate.getDate() === 1) { + if (currentDate.getDate() === 1) { debugLog('Current date is the first of the month, selecting last 7 days date range to ensure data is displayed'); await datePicker.selectLast7DaysDateRange(); await expensesPage.waitForAllProgressBarsToDisappear(); @@ -341,7 +346,7 @@ test.describe('[MPT-12859] Expenses Page Pool Breakdown Tests', { tag: ['@ui', ' await expensesPage.waitForCanvas(); }); - test('[231218] Verify Expenses Page Pool Breakdown layout', async ({ expensesPage, datePicker }) => { + test('[231218] Verify Expenses Page Pool Breakdown layout', { tag: ['@fast', '@p2'] }, async ({ expensesPage, datePicker }) => { await test.step('Verify Expenses Page Pool Breakdown elements', async () => { await expect(expensesPage.downloadButton).toBeHidden(); await expect(expensesPage.seeExpensesBreakdownGrid).toBeHidden(); @@ -358,7 +363,7 @@ test.describe('[MPT-12859] Expenses Page Pool Breakdown Tests', { tag: ['@ui', ' }); }); - test('[231219] Validate API Pool Breakdown chart data', { tag: '@p1' }, async ({ expensesPage }) => { + test('[231219] Validate API Pool Breakdown chart data', { tag: ['@fast', '@p1'] }, async ({ expensesPage }) => { //if it's the first day of the month, the API will return 0 expenses for the current month, so we need to get the date range for last 7 days to validate the data const { startDate, endDate } = currentDate.getDate() === 1 ? getLast7DaysUnixRange() : getThisMonthUnixDateRange(); let expensesData: ExpensesFilterByPoolResponse; @@ -412,41 +417,46 @@ test.describe('[MPT-12859] Expenses Page Pool Breakdown Tests', { tag: ['@ui', ' }); }); - test('[231220] Verify pool expenses total for(default) period matches chart and table totals', async ({ expensesPage }) => { - const totalForPeriod = await expensesPage.getTotalExpensesForSelectedPeriod(); - debugLog(`Total expenses for selected period: ${totalForPeriod}`); - const chartTotal = await expensesPage.getExpensesPieChartValue(); - debugLog(`Total expenses from pie chart: ${chartTotal}`); - const tableTotal = await expensesPage.getTableItemisedExpensesValue(); - debugLog(`Total expenses from itemised table: ${tableTotal}`); - - await test.step('Compare total expenses values', async () => { - expect.soft(chartTotal).toBe(totalForPeriod); - expect(isWithinRoundingDrift(tableTotal, totalForPeriod, 0.01)).toBe(true); - }); - }); + test( + '[231220] Verify pool expenses total for(default) period matches chart and table totals', + { tag: ['@fast', '@p2'] }, + async ({ expensesPage }) => { + const totalForPeriod = await expensesPage.getTotalExpensesForSelectedPeriod(); + debugLog(`Total expenses for selected period: ${totalForPeriod}`); + const chartTotal = await expensesPage.getExpensesPieChartValue(); + debugLog(`Total expenses from pie chart: ${chartTotal}`); + const tableTotal = await expensesPage.getTableItemisedExpensesValue(); + debugLog(`Total expenses from itemised table: ${tableTotal}`); - test('[231221] Verify pool expenses total for (last 7 days) period matches chart and table totals', async ({ - expensesPage, - datePicker, - }) => { - await datePicker.selectLast7DaysDateRange(); - const expectedDateRange = getExpectedDateRangeText('last 7 days'); - debugLog(`Selected date range: ${expectedDateRange}`); - const totalForPeriod = await expensesPage.getTotalExpensesForSelectedPeriod(); - debugLog(`Total expenses for selected period: ${totalForPeriod}`); - const chartTotal = await expensesPage.getExpensesPieChartValue(); - debugLog(`Total expenses from pie chart: ${chartTotal}`); - const tableTotal = await expensesPage.getTableItemisedExpensesValue(); - debugLog(`Total expenses from itemised table: ${tableTotal}`); - - await test.step('Compare total expenses values', async () => { - const dateRange = await datePicker.selectedDateText.textContent(); - expect(dateRange.includes(expectedDateRange)).toBe(true); - expect.soft(chartTotal).toBe(totalForPeriod); - expect(isWithinRoundingDrift(tableTotal, totalForPeriod, 0.01)).toBe(true); - }); - }); + await test.step('Compare total expenses values', async () => { + expect.soft(chartTotal).toBe(totalForPeriod); + expect(isWithinRoundingDrift(tableTotal, totalForPeriod, 0.01)).toBe(true); + }); + } + ); + + test( + '[231221] Verify pool expenses total for (last 7 days) period matches chart and table totals', + { tag: ['@fast', '@p2'] }, + async ({ expensesPage, datePicker }) => { + await datePicker.selectLast7DaysDateRange(); + const expectedDateRange = getExpectedDateRangeText('last 7 days'); + debugLog(`Selected date range: ${expectedDateRange}`); + const totalForPeriod = await expensesPage.getTotalExpensesForSelectedPeriod(); + debugLog(`Total expenses for selected period: ${totalForPeriod}`); + const chartTotal = await expensesPage.getExpensesPieChartValue(); + debugLog(`Total expenses from pie chart: ${chartTotal}`); + const tableTotal = await expensesPage.getTableItemisedExpensesValue(); + debugLog(`Total expenses from itemised table: ${tableTotal}`); + + await test.step('Compare total expenses values', async () => { + const dateRange = await datePicker.selectedDateText.textContent(); + expect(dateRange.includes(expectedDateRange)).toBe(true); + expect.soft(chartTotal).toBe(totalForPeriod); + expect(isWithinRoundingDrift(tableTotal, totalForPeriod, 0.01)).toBe(true); + }); + } + ); }); test.describe('[MPT-12859] Expenses Page Owner Breakdown Tests', { tag: ['@ui', '@expenses'] }, () => { @@ -462,7 +472,7 @@ test.describe('[MPT-12859] Expenses Page Owner Breakdown Tests', { tag: ['@ui', await expensesPage.waitForAllProgressBarsToDisappear(); await expensesPage.waitForCanvas(); - if(currentDate.getDate() === 1) { + if (currentDate.getDate() === 1) { debugLog('Current date is the first of the month, selecting last 7 days date range to ensure data is displayed'); await datePicker.selectLast7DaysDateRange(); await expensesPage.waitForAllProgressBarsToDisappear(); @@ -477,7 +487,7 @@ test.describe('[MPT-12859] Expenses Page Owner Breakdown Tests', { tag: ['@ui', await expensesPage.waitForCanvas(); }); - test('[231222] Verify Expenses Page Owner Breakdown layout', async ({ expensesPage, datePicker }) => { + test('[231222] Verify Expenses Page Owner Breakdown layout', { tag: ['@fast', '@p2'] }, async ({ expensesPage, datePicker }) => { await test.step('Verify Expenses Page Owner Breakdown elements', async () => { await expect(expensesPage.downloadButton).toBeHidden(); await expect(expensesPage.seeExpensesBreakdownGrid).toBeHidden(); @@ -494,7 +504,7 @@ test.describe('[MPT-12859] Expenses Page Owner Breakdown Tests', { tag: ['@ui', }); }); - test('[231223] Validate API Owner Breakdown chart data', async ({ expensesPage }) => { + test('[231223] Validate API Owner Breakdown chart data', { tag: ['@fast', '@p2'] }, async ({ expensesPage }) => { //if it's the first day of the month, the API will return 0 expenses for the current month, so we need to get the date range for last 7 days to validate the data const { startDate, endDate } = currentDate.getDate() === 1 ? getLast7DaysUnixRange() : getThisMonthUnixDateRange(); let expensesData: ExpensesFilterByEmployeeResponse; @@ -547,39 +557,44 @@ test.describe('[MPT-12859] Expenses Page Owner Breakdown Tests', { tag: ['@ui', }); }); - test('[231224] Verify owner expenses total for(default) period matches chart and table totals', async ({ expensesPage }) => { - const totalForPeriod = await expensesPage.getTotalExpensesForSelectedPeriod(); - debugLog(`Total expenses for selected period: ${totalForPeriod}`); - const chartTotal = await expensesPage.getExpensesPieChartValue(); - debugLog(`Total expenses from pie chart: ${chartTotal}`); - const tableTotal = await expensesPage.getTableItemisedExpensesValue(); - debugLog(`Total expenses from itemised table: ${tableTotal}`); - - await test.step('Compare total expenses values', async () => { - expect.soft(chartTotal).toBe(totalForPeriod); - expect(isWithinRoundingDrift(tableTotal, totalForPeriod, 0.01)).toBe(true); - }); - }); + test( + '[231224] Verify owner expenses total for(default) period matches chart and table totals', + { tag: ['@fast', '@p2'] }, + async ({ expensesPage }) => { + const totalForPeriod = await expensesPage.getTotalExpensesForSelectedPeriod(); + debugLog(`Total expenses for selected period: ${totalForPeriod}`); + const chartTotal = await expensesPage.getExpensesPieChartValue(); + debugLog(`Total expenses from pie chart: ${chartTotal}`); + const tableTotal = await expensesPage.getTableItemisedExpensesValue(); + debugLog(`Total expenses from itemised table: ${tableTotal}`); - test('[231225] Verify owner expenses total for (last 30 days) period matches chart and table totals', async ({ - expensesPage, - datePicker, - }) => { - await datePicker.selectLast30DaysDateRange(); - const expectedDateRange = getExpectedDateRangeText('last 30 days'); - debugLog(`Selected date range: ${expectedDateRange}`); - const totalForPeriod = await expensesPage.getTotalExpensesForSelectedPeriod(); - debugLog(`Total expenses for selected period: ${totalForPeriod}`); - const chartTotal = await expensesPage.getExpensesPieChartValue(); - debugLog(`Total expenses from pie chart: ${chartTotal}`); - const tableTotal = await expensesPage.getTableItemisedExpensesValue(); - debugLog(`Total expenses from itemised table: ${tableTotal}`); - - await test.step('Compare total expenses values', async () => { - const dateRange = await datePicker.selectedDateText.textContent(); - expect(dateRange.includes(expectedDateRange)).toBe(true); - expect.soft(chartTotal).toBe(totalForPeriod); - expect(isWithinRoundingDrift(tableTotal, totalForPeriod, 0.01)).toBe(true); - }); - }); + await test.step('Compare total expenses values', async () => { + expect.soft(chartTotal).toBe(totalForPeriod); + expect(isWithinRoundingDrift(tableTotal, totalForPeriod, 0.01)).toBe(true); + }); + } + ); + + test( + '[231225] Verify owner expenses total for (last 30 days) period matches chart and table totals', + { tag: ['@fast', '@p2'] }, + async ({ expensesPage, datePicker }) => { + await datePicker.selectLast30DaysDateRange(); + const expectedDateRange = getExpectedDateRangeText('last 30 days'); + debugLog(`Selected date range: ${expectedDateRange}`); + const totalForPeriod = await expensesPage.getTotalExpensesForSelectedPeriod(); + debugLog(`Total expenses for selected period: ${totalForPeriod}`); + const chartTotal = await expensesPage.getExpensesPieChartValue(); + debugLog(`Total expenses from pie chart: ${chartTotal}`); + const tableTotal = await expensesPage.getTableItemisedExpensesValue(); + debugLog(`Total expenses from itemised table: ${tableTotal}`); + + await test.step('Compare total expenses values', async () => { + const dateRange = await datePicker.selectedDateText.textContent(); + expect(dateRange.includes(expectedDateRange)).toBe(true); + expect.soft(chartTotal).toBe(totalForPeriod); + expect(isWithinRoundingDrift(tableTotal, totalForPeriod, 0.01)).toBe(true); + }); + } + ); }); diff --git a/e2etests/tests/homepage-tests.spec.ts b/e2etests/tests/homepage-tests.spec.ts index 3d7a662ff..347c9e613 100644 --- a/e2etests/tests/homepage-tests.spec.ts +++ b/e2etests/tests/homepage-tests.spec.ts @@ -21,7 +21,7 @@ test.describe('[MPT-11464] Home Page Recommendations block tests', { tag: ['@ui' test( '[230550] Compare possible savings on home page with those on recommendations page', - { tag: '@p1' }, + { tag: ['@fast', '@p1'] }, async ({ homePage, recommendationsPage }) => { const homePageValue = await homePage.getRecommendationsPossibleSavingsValue(); await homePage.recommendationsBtn.click(); @@ -30,42 +30,45 @@ test.describe('[MPT-11464] Home Page Recommendations block tests', { tag: ['@ui' } ); - test('[230551] Verify Cost items displayed in the recommendations block match the sum total of items displayed on cards with savings', async ({ - homePage, - recommendationsPage, - }) => { - const homePageValue = await homePage.getRecommendationsCostValue(); - await homePage.recommendationsCostLink.click(); - expect.soft(await recommendationsPage.selectedComboBoxOption(recommendationsPage.categoriesSelect)).toEqual('Savings'); - expect.soft(await recommendationsPage.getTotalSumOfItemsFromSeeItemsButtons()).toBe(homePageValue); - }); + test( + '[230551] Verify Cost items displayed in the recommendations block match the sum total of items displayed on cards with savings', + { tag: ['@fast', '@p2'] }, + async ({ homePage, recommendationsPage }) => { + const homePageValue = await homePage.getRecommendationsCostValue(); + await homePage.recommendationsCostLink.click(); + expect.soft(await recommendationsPage.selectedComboBoxOption(recommendationsPage.categoriesSelect)).toEqual('Savings'); + expect.soft(await recommendationsPage.getTotalSumOfItemsFromSeeItemsButtons()).toBe(homePageValue); + } + ); - test('[230552] Verify Security items displayed in the recommendations block match the sum total of items displayed on cards in the security category', async ({ - homePage, - recommendationsPage, - }) => { - const homePageValue = await homePage.getRecommendationsSecurityValue(); - await homePage.recommendationsSecurityLink.click(); - expect.soft(await recommendationsPage.selectedComboBoxOption(recommendationsPage.categoriesSelect)).toEqual('Security'); - expect.soft(await recommendationsPage.getTotalSumOfItemsFromSeeItemsButtons()).toBe(homePageValue); - }); + test( + '[230552] Verify Security items displayed in the recommendations block match the sum total of items displayed on cards in the security category', + { tag: ['@fast', '@p2'] }, + async ({ homePage, recommendationsPage }) => { + const homePageValue = await homePage.getRecommendationsSecurityValue(); + await homePage.recommendationsSecurityLink.click(); + expect.soft(await recommendationsPage.selectedComboBoxOption(recommendationsPage.categoriesSelect)).toEqual('Security'); + expect.soft(await recommendationsPage.getTotalSumOfItemsFromSeeItemsButtons()).toBe(homePageValue); + } + ); - 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(); + test( + '[230553] Verify Critical items displayed in the recommendations block match the sum total of items displayed on cards with the critical status', + { tag: ['@fast', '@p2'] }, + 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; - } + 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); - }); + expect.soft(await recommendationsPage.selectedComboBoxOption(recommendationsPage.categoriesSelect)).toEqual('Critical'); + expect.soft(await recommendationsPage.getTotalSumOfItemsFromSeeItemsButtons()).toBe(homePageValue); + } + ); }); test.describe('[MPT-11958] Home Page Resource block tests', { tag: ['@ui', '@resources', '@homepage'] }, () => { @@ -81,16 +84,20 @@ test.describe('[MPT-11958] Home Page Resource block tests', { tag: ['@ui', '@res }); }); - test('[230838] Verify Top Resource block Resource link works correctly', async ({ homePage, resourcesPage }) => { - await test.step('Click on Top Resources button', async () => { - await homePage.clickTopResourcesBtn(); - await expect.soft(resourcesPage.heading).toBeVisible(); - }); - }); + test( + '[230838] Verify Top Resource block Resource link works correctly', + { tag: ['@fast', '@p2'] }, + async ({ homePage, resourcesPage }) => { + await test.step('Click on Top Resources button', async () => { + await homePage.clickTopResourcesBtn(); + await expect.soft(resourcesPage.heading).toBeVisible(); + }); + } + ); test( '[230839] Verify top Resource link navigates to the correct resource details page and last 30 days value match', - { tag: '@p1' }, + { tag: ['@fast', '@p1'] }, async ({ homePage, resourceDetailsPage, datePicker }) => { let homepageResourceTitle: string; let homePageExpenseValue: number; @@ -121,7 +128,7 @@ test.describe('[MPT-11958] Home Page Resource block tests', { tag: ['@ui', '@res } ); - test('[230842] Verify Top Resource Block displayed correctly', async ({ homePage }) => { + test('[230842] Verify Top Resource Block displayed correctly', { tag: ['@fast', '@p2'] }, async ({ homePage }) => { await test.step('Verify that the Top Resources section is displayed with 6 or fewer resources and include names for each', async () => { const count = await homePage.topResourcesAllLinks.count(); expect.soft(count).toBeLessThanOrEqual(6); @@ -155,31 +162,31 @@ test.describe('[MPT-12743] Home Page test for Pools requiring attention block', }); }); - test('[230922] Verify that Pools Requiring attention is empty when the are no qualifying pools', async ({ - homePage, - poolsPage, - mainMenu, - }) => { - await test.step('Remove limits from all pools if any', async () => { - await poolsPage.navigateToURL(); - await poolsPage.waitForAllProgressBarsToDisappear(); - await poolsPage.poolExpandMoreIcon.waitFor(); - if ((await poolsPage.getColumnBadgeText()) !== 'All') await poolsPage.selectAllColumns(); - await poolsPage.toggleExpandPool(); - await poolsPage.removeAllSubPoolMonthlyLimits(); - await poolsPage.toggleExpandPool(); - if ((await poolsPage.getOrganizationLimitValue()) !== 0) await poolsPage.editPoolMonthlyLimit(0); - await mainMenu.clickHomeBtn(); - }); - await test.step('Navigate to home page and verify Pools Requiring attention block is empty', async () => { - await expect.soft(homePage.poolsNoDataMessage).toBeVisible(); - expect.soft(await homePage.getPoolsBlockTotalValue()).toBe(0); - }); - }); + test( + '[230922] Verify that Pools Requiring attention is empty when the are no qualifying pools', + { tag: ['@fast', '@p2'] }, + async ({ homePage, poolsPage, mainMenu }) => { + await test.step('Remove limits from all pools if any', async () => { + await poolsPage.navigateToURL(); + await poolsPage.waitForAllProgressBarsToDisappear(); + await poolsPage.poolExpandMoreIcon.waitFor(); + if ((await poolsPage.getColumnBadgeText()) !== 'All') await poolsPage.selectAllColumns(); + await poolsPage.toggleExpandPool(); + await poolsPage.removeAllSubPoolMonthlyLimits(); + await poolsPage.toggleExpandPool(); + if ((await poolsPage.getOrganizationLimitValue()) !== 0) await poolsPage.editPoolMonthlyLimit(0); + await mainMenu.clickHomeBtn(); + }); + await test.step('Navigate to home page and verify Pools Requiring attention block is empty', async () => { + await expect.soft(homePage.poolsNoDataMessage).toBeVisible(); + expect.soft(await homePage.getPoolsBlockTotalValue()).toBe(0); + }); + } + ); test( '[230923] Verify that Pools Requiring attention shows Pool and Sub-pools that have exceeded their limit', - { tag: ['@p1'] }, + { tag: ['@fast', '@p1'] }, async ({ homePage, poolsPage, mainMenu }) => { let expenseValue: number; let subPoolExpenseValue: number; @@ -194,7 +201,10 @@ 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'); + 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); @@ -221,39 +231,39 @@ test.describe('[MPT-12743] Home Page test for Pools requiring attention block', } ); - test('[230924] Verify that Pools Requiring attention shows Pool and Sub-pools that are forecasted to overspend', async ({ - homePage, - poolsPage, - mainMenu, - }) => { - let expenseValue: number; - let forecastedValue: number; + test( + '[230924] Verify that Pools Requiring attention shows Pool and Sub-pools that are forecasted to overspend', + { tag: ['@fast', '@p2'] }, + async ({ homePage, poolsPage, mainMenu }) => { + let expenseValue: number; + let forecastedValue: number; - await test.step('Set monthly limit for a pool that is higher than expenses this month, but lower than forecast', async () => { - await poolsPage.navigateToURL(); - await homePage.waitForAllProgressBarsToDisappear(); - await poolsPage.poolExpandMoreIcon.waitFor(); - await poolsPage.selectAllColumns(); - expenseValue = await poolsPage.getExpensesThisMonth(); - forecastedValue = await poolsPage.getForecastThisMonth(); - const limitValue = Math.ceil(expenseValue / 0.99); - await poolsPage.toggleExpandPool(); - await poolsPage.removeAllSubPoolMonthlyLimits(); - await poolsPage.editPoolMonthlyLimit(limitValue); - }); - await test.step('Navigate to home page and verify Pools Requiring attention block', async () => { - await mainMenu.clickHomeBtn(); - await expect.soft(homePage.poolsNoDataMessage).toBeVisible(); - await homePage.clickPoolsBlockForecastedOverspendTab(); - expect.soft(await homePage.getPoolsBlockExpensesColumnValue(1)).toBe(expenseValue); - expect.soft(await homePage.getPoolsBlockForecastColumnValue(1)).toBe(forecastedValue); - expect - .soft(await homePage.getColorFromElement(homePage.poolsBlockExpensesColumn.locator('xpath=/div/div'))) - .toBe(homePage.successColor); - expect.soft(await homePage.getColorFromElement(homePage.poolsBlockForecastColumn.locator('span'))).toBe(homePage.warningColor); - expect.soft(await homePage.getPoolsBlockTotalValue()).toBe(1); - }); - }); + await test.step('Set monthly limit for a pool that is higher than expenses this month, but lower than forecast', async () => { + await poolsPage.navigateToURL(); + await homePage.waitForAllProgressBarsToDisappear(); + await poolsPage.poolExpandMoreIcon.waitFor(); + await poolsPage.selectAllColumns(); + expenseValue = await poolsPage.getExpensesThisMonth(); + forecastedValue = await poolsPage.getForecastThisMonth(); + const limitValue = Math.ceil(expenseValue / 0.99); + await poolsPage.toggleExpandPool(); + await poolsPage.removeAllSubPoolMonthlyLimits(); + await poolsPage.editPoolMonthlyLimit(limitValue); + }); + await test.step('Navigate to home page and verify Pools Requiring attention block', async () => { + await mainMenu.clickHomeBtn(); + await expect.soft(homePage.poolsNoDataMessage).toBeVisible(); + await homePage.clickPoolsBlockForecastedOverspendTab(); + expect.soft(await homePage.getPoolsBlockExpensesColumnValue(1)).toBe(expenseValue); + expect.soft(await homePage.getPoolsBlockForecastColumnValue(1)).toBe(forecastedValue); + expect + .soft(await homePage.getColorFromElement(homePage.poolsBlockExpensesColumn.locator('xpath=/div/div'))) + .toBe(homePage.successColor); + expect.soft(await homePage.getColorFromElement(homePage.poolsBlockForecastColumn.locator('span'))).toBe(homePage.warningColor); + expect.soft(await homePage.getPoolsBlockTotalValue()).toBe(1); + }); + } + ); }); test.describe('[MPT-18353] Home Page test for Policy Violation block', { tag: ['@ui', '@anomalies', '@homepage'] }, () => { @@ -269,57 +279,61 @@ test.describe('[MPT-18353] Home Page test for Policy Violation block', { tag: [' interceptAPI: { entries: apiInterceptions, failOnInterceptionMissing: true }, }); - test('[232876] Verify that Policy Violation block displays policy violations correctly', async ({ homePage }) => { - await test.step('Navigate to home page', async () => { - await homePage.page.clock.setFixedTime(new Date('2026-02-24T11:00:00Z')); - await homePage.navigateToURL(); - await homePage.waitForAllCanvases(); - }); + test( + '[232876] Verify that Policy Violation block displays policy violations correctly', + { tag: ['@fast', '@p2'] }, + async ({ homePage }) => { + await test.step('Navigate to home page', async () => { + await homePage.page.clock.setFixedTime(new Date('2026-02-24T11:00:00Z')); + await homePage.navigateToURL(); + await homePage.waitForAllCanvases(); + }); - const typeColumn = '//td[2]'; - const statusColumn = '//td[3]'; + const typeColumn = '//td[2]'; + const statusColumn = '//td[3]'; - await test.step('Verify that the Policy Violation block first page is displayed with correct data', async () => { - const NBSP = '\u00A0'; + await test.step('Verify that the Policy Violation block first page is displayed with correct data', async () => { + const NBSP = '\u00A0'; - await expect.soft(homePage.correlatedTaggingRow.locator(typeColumn)).toHaveText('Tagging'); - await expect.soft(homePage.correlatedTaggingRow.locator(statusColumn)).toHaveText('1 violation right now'); + await expect.soft(homePage.correlatedTaggingRow.locator(typeColumn)).toHaveText('Tagging'); + await expect.soft(homePage.correlatedTaggingRow.locator(statusColumn)).toHaveText('1 violation right now'); - await expect.soft(homePage.defaultExpenseAnomalyRow.locator(typeColumn)).toHaveText('Anomaly'); - await homePage.defaultExpenseAnomalyRow.locator(statusColumn).hover(); - await expect.soft(homePage.tooltip).toHaveText(`Average:${NBSP}$7,621.64Today:${NBSP}$63,376.15`); + await expect.soft(homePage.defaultExpenseAnomalyRow.locator(typeColumn)).toHaveText('Anomaly'); + await homePage.defaultExpenseAnomalyRow.locator(statusColumn).hover(); + await expect.soft(homePage.tooltip).toHaveText(`Average:${NBSP}$7,621.64Today:${NBSP}$63,376.15`); - await expect.soft(homePage.expiringBudgetRow.locator(`${typeColumn}`)).toHaveText('Quota/Budget'); - await expect.soft(homePage.expiringBudgetRow.locator(`${statusColumn}/div/div`)).toHaveText('$1,175.82'); - expect - .soft(await homePage.getColorFromElement(homePage.expiringBudgetRow.locator(`${statusColumn}/div/div`))) - .toBe(homePage.errorColor); + await expect.soft(homePage.expiringBudgetRow.locator(`${typeColumn}`)).toHaveText('Quota/Budget'); + await expect.soft(homePage.expiringBudgetRow.locator(`${statusColumn}/div/div`)).toHaveText('$1,175.82'); + expect + .soft(await homePage.getColorFromElement(homePage.expiringBudgetRow.locator(`${statusColumn}/div/div`))) + .toBe(homePage.errorColor); - await expect.soft(homePage.prohibitedTaggingRow.locator(typeColumn)).toHaveText('Tagging'); - await expect.soft(homePage.prohibitedTaggingRow.locator(statusColumn)).toHaveText('2 violations right now'); + await expect.soft(homePage.prohibitedTaggingRow.locator(typeColumn)).toHaveText('Tagging'); + await expect.soft(homePage.prohibitedTaggingRow.locator(statusColumn)).toHaveText('2 violations right now'); - await expect.soft(homePage.recurringBudgetRow.locator(`${typeColumn}`)).toHaveText('Quota/Budget'); - await expect.soft(homePage.recurringBudgetRow.locator(`${statusColumn}/div/div`)).toHaveText('$234,445.89'); - expect - .soft(await homePage.getColorFromElement(homePage.recurringBudgetRow.locator(`${statusColumn}/div/div`))) - .toBe(homePage.errorColor); + await expect.soft(homePage.recurringBudgetRow.locator(`${typeColumn}`)).toHaveText('Quota/Budget'); + await expect.soft(homePage.recurringBudgetRow.locator(`${statusColumn}/div/div`)).toHaveText('$234,445.89'); + expect + .soft(await homePage.getColorFromElement(homePage.recurringBudgetRow.locator(`${statusColumn}/div/div`))) + .toBe(homePage.errorColor); - await expect.soft(homePage.defaultResourceCountAnomalyRow).toBeHidden(); - }); + await expect.soft(homePage.defaultResourceCountAnomalyRow).toBeHidden(); + }); - await test.step('Verify second page of the Policy Violation block is displayed with correct data when clicking on the pagination button', async () => { - await homePage.policyViolationsNavigateNextBtn.click(); + await test.step('Verify second page of the Policy Violation block is displayed with correct data when clicking on the pagination button', async () => { + await homePage.policyViolationsNavigateNextBtn.click(); - await expect.soft(homePage.resourceQuotaRow.locator(`${typeColumn}`)).toHaveText('Quota/Budget'); - await expect.soft(homePage.resourceQuotaRow.locator(`${statusColumn}/div/div`)).toHaveText('478'); - expect - .soft(await homePage.getColorFromElement(homePage.resourceQuotaRow.locator(`${statusColumn}/div/div`))) - .toBe(homePage.errorColor); + await expect.soft(homePage.resourceQuotaRow.locator(`${typeColumn}`)).toHaveText('Quota/Budget'); + await expect.soft(homePage.resourceQuotaRow.locator(`${statusColumn}/div/div`)).toHaveText('478'); + expect + .soft(await homePage.getColorFromElement(homePage.resourceQuotaRow.locator(`${statusColumn}/div/div`))) + .toBe(homePage.errorColor); - await expect.soft(homePage.taggingRequiredRow.locator(`${typeColumn}`)).toHaveText('Tagging'); - await expect.soft(homePage.taggingRequiredRow.locator(`${statusColumn}`)).toHaveText('3119 violations right now'); + await expect.soft(homePage.taggingRequiredRow.locator(`${typeColumn}`)).toHaveText('Tagging'); + await expect.soft(homePage.taggingRequiredRow.locator(`${statusColumn}`)).toHaveText('3119 violations right now'); - await expect.soft(homePage.defaultResourceCountAnomalyRow).toBeHidden(); - }); - }); + await expect.soft(homePage.defaultResourceCountAnomalyRow).toBeHidden(); + }); + } + ); }); diff --git a/e2etests/tests/invitation-flow-tests.spec.ts b/e2etests/tests/invitation-flow-tests.spec.ts index 19e008a80..e17fdd678 100644 --- a/e2etests/tests/invitation-flow-tests.spec.ts +++ b/e2etests/tests/invitation-flow-tests.spec.ts @@ -25,7 +25,15 @@ test.describe('MPT-8230 Invitation Flow Tests for new users', { tag: ['@invitati test( '[229865] Invite new user to organisation, user accepts', { tag: '@p1' }, - async ({ header, mainMenu, usersPage, usersInvitePage, registerPage, pendingInvitationsPage, emailVerificationPage, baseRequest }) => { + async ({ header, + mainMenu, + usersPage, + usersInvitePage, + registerPage, + pendingInvitationsPage, + emailVerificationPage, + authRequest, + }) => { test.slow(); await test.step('Navigate to the invitation page', async () => { @@ -50,7 +58,7 @@ test.describe('MPT-8230 Invitation Flow Tests for new users', { tag: ['@invitati await test.step('Set verification code', async () => { await emailVerificationPage.waitForVerificationCodeResetTimeout(); - await baseRequest.setVerificationCode(invitationEmail, verificationCode); + await authRequest.setVerificationCode(invitationEmail, verificationCode); }); await test.step('Verify and accept invitation from email', async () => { @@ -69,359 +77,378 @@ test.describe('MPT-8230 Invitation Flow Tests for new users', { tag: ['@invitati } ); - test('[229866] Invite new user to organisation, but user declines', async ({ - header, - mainMenu, - usersPage, - usersInvitePage, - registerPage, - pendingInvitationsPage, - emailVerificationPage, - baseRequest, - }) => { - test.setTimeout(150000); - - await test.step('Navigate to the invitation page', async () => { - await mainMenu.clickUserManagement(); - await usersPage.clickInviteBtn(); - }); - - await test.step('Invite a new user to the organisation', async () => { - await usersInvitePage.inviteUser(invitationEmail); - await usersInvitePage.userInvitedAlert.waitFor(); - await usersInvitePage.userInvitedAlertCloseButton.click(); - }); - - await test.step('Sign out Admin user', async () => { - await header.signOut(); - }); - - await test.step('Sign up user', async () => { - await registerPage.navigateToRegistration(inviteLink); - await registerPage.registerUser('Test User', process.env.DEFAULT_USER_PASSWORD); - }); - - await test.step('Set verification code', async () => { - await emailVerificationPage.waitForVerificationCodeResetTimeout(); - await baseRequest.setVerificationCode(invitationEmail, verificationCode); - }); - - await test.step('Verify and decline invitation from email', async () => { - await emailVerificationPage.verifyCodeAndConfirm(verificationCode); - await emailVerificationPage.proceedToFinOps(); - await pendingInvitationsPage.declineInvite(); - }); - - await test.step('Assert no pending invitations message', async () => { - await expect(pendingInvitationsPage.noPendingInvitationsMessage).toBeVisible(); - }); - }); + test( + '[229866] Invite new user to organisation, but user declines', + { tag: '@p2' }, + async ({ header, + mainMenu, + usersPage, + usersInvitePage, + registerPage, + pendingInvitationsPage, + emailVerificationPage, + authRequest, + }) => { + test.setTimeout(150000); - test('[229867] Invite new user to organisation, who has previously declined @slow', async ({ - header, - loginPage, - mainMenu, - usersPage, - usersInvitePage, - registerPage, - pendingInvitationsPage, - emailVerificationPage, - baseRequest, - }) => { - test.setTimeout(120000); - - await test.step('Navigate to the invitation page', async () => { - await mainMenu.clickUserManagement(); - await usersPage.clickInviteBtn(); - }); - - await test.step('Invite a new user to the organisation', async () => { - await usersInvitePage.inviteUser(invitationEmail); - await usersInvitePage.userInvitedAlert.waitFor(); - await usersInvitePage.userInvitedAlertCloseButton.click(); - }); - - await test.step('Sign out Admin user', async () => { - await header.signOut(); - }); - - await test.step('Sign up user', async () => { - await registerPage.navigateToRegistration(inviteLink); - await registerPage.registerUser('Test User', process.env.DEFAULT_USER_PASSWORD); - }); - - await test.step('Set verification code', async () => { - await emailVerificationPage.waitForVerificationCodeResetTimeout(); - await baseRequest.setVerificationCode(invitationEmail, verificationCode); - }); - - await test.step('Verify and decline invitation from email', async () => { - await emailVerificationPage.verifyCodeAndConfirm(verificationCode); - await emailVerificationPage.proceedToFinOps(); - await pendingInvitationsPage.declineInvite(); - }); - - await test.step('Assert no pending invitations message', async () => { - await expect(pendingInvitationsPage.noPendingInvitationsMessage).toBeVisible(); - }); - - await test.step('Sign out user', async () => { - await header.signOut(); - }); - - await test.step('Log back in as admin', async () => { - await loginPage.loginWithoutNavigation(process.env.DEFAULT_USER_EMAIL, process.env.DEFAULT_USER_PASSWORD); - }); - await test.step('Navigate to the invitation page', async () => { - await mainMenu.clickUserManagement(); - await usersPage.clickInviteBtn(); - }); - - await test.step('Invite a new user to the organisation', async () => { - await usersInvitePage.inviteUser(invitationEmail); - await usersInvitePage.userInvitedAlert.waitFor(); - await usersInvitePage.userInvitedAlertCloseButton.click(); - }); - - await test.step('Sign out Admin user', async () => { - await header.signOut(); - }); - - await test.step('Login as new user', async () => { - await registerPage.navigateToRegistration(inviteLink); - await registerPage.clickAlreadyHaveAccountLink(); - await loginPage.loginWithPreFilledEmail(process.env.DEFAULT_USER_PASSWORD); - }); - - await test.step('Accept invitation', async () => { - await pendingInvitationsPage.acceptInvite(); - }); - - await test.step('Assert organization', async () => { - const orgName = header.getOrganizationNameForEnvironment(); - await expect(header.organizationSelect).toContainText(orgName); - }); - }); + await test.step('Navigate to the invitation page', async () => { + await mainMenu.clickUserManagement(); + await usersPage.clickInviteBtn(); + }); + + await test.step('Invite a new user to the organisation', async () => { + await usersInvitePage.inviteUser(invitationEmail); + await usersInvitePage.userInvitedAlert.waitFor(); + await usersInvitePage.userInvitedAlertCloseButton.click(); + }); + + await test.step('Sign out Admin user', async () => { + await header.signOut(); + }); + + await test.step('Sign up user', async () => { + await registerPage.navigateToRegistration(inviteLink); + await registerPage.registerUser('Test User', process.env.DEFAULT_USER_PASSWORD); + }); + + await test.step('Set verification code', async () => { + await emailVerificationPage.waitForVerificationCodeResetTimeout(); + await authRequest.setVerificationCode(invitationEmail, verificationCode); + }); + + await test.step('Verify and decline invitation from email', async () => { + await emailVerificationPage.verifyCodeAndConfirm(verificationCode); + await emailVerificationPage.proceedToFinOps(); + await pendingInvitationsPage.declineInvite(); + }); + + await test.step('Assert no pending invitations message', async () => { + await expect(pendingInvitationsPage.noPendingInvitationsMessage).toBeVisible(); + }); + } + ); + + test( + '[229867] Invite new user to organisation, who has previously declined @slow', + { tag: '@p2' }, + async ({ + header, + loginPage, + mainMenu, + usersPage, + usersInvitePage, + registerPage, + pendingInvitationsPage, + emailVerificationPage, + authRequest, + }) => { + test.setTimeout(120000); + + await test.step('Navigate to the invitation page', async () => { + await mainMenu.clickUserManagement(); + await usersPage.clickInviteBtn(); + }); + + await test.step('Invite a new user to the organisation', async () => { + await usersInvitePage.inviteUser(invitationEmail); + await usersInvitePage.userInvitedAlert.waitFor(); + await usersInvitePage.userInvitedAlertCloseButton.click(); + }); + + await test.step('Sign out Admin user', async () => { + await header.signOut(); + }); + + await test.step('Sign up user', async () => { + await registerPage.navigateToRegistration(inviteLink); + await registerPage.registerUser('Test User', process.env.DEFAULT_USER_PASSWORD); + }); + + await test.step('Set verification code', async () => { + await emailVerificationPage.waitForVerificationCodeResetTimeout(); + await authRequest.setVerificationCode(invitationEmail, verificationCode); + }); + + await test.step('Verify and decline invitation from email', async () => { + await emailVerificationPage.verifyCodeAndConfirm(verificationCode); + await emailVerificationPage.proceedToFinOps(); + await pendingInvitationsPage.declineInvite(); + }); + + await test.step('Assert no pending invitations message', async () => { + await expect(pendingInvitationsPage.noPendingInvitationsMessage).toBeVisible(); + }); + + await test.step('Sign out user', async () => { + await header.signOut(); + }); + + await test.step('Log back in as admin', async () => { + await loginPage.loginWithoutNavigation(process.env.DEFAULT_USER_EMAIL, process.env.DEFAULT_USER_PASSWORD); + }); + await test.step('Navigate to the invitation page', async () => { + await mainMenu.clickUserManagement(); + await usersPage.clickInviteBtn(); + }); + + await test.step('Invite a new user to the organisation', async () => { + await usersInvitePage.inviteUser(invitationEmail); + await usersInvitePage.userInvitedAlert.waitFor(); + await usersInvitePage.userInvitedAlertCloseButton.click(); + }); + + await test.step('Sign out Admin user', async () => { + await header.signOut(); + }); + + await test.step('Login as new user', async () => { + await registerPage.navigateToRegistration(inviteLink); + await registerPage.clickAlreadyHaveAccountLink(); + await loginPage.loginWithPreFilledEmail(process.env.DEFAULT_USER_PASSWORD); + }); + + await test.step('Accept invitation', async () => { + await pendingInvitationsPage.acceptInvite(); + }); + + await test.step('Assert organization', async () => { + const orgName = header.getOrganizationNameForEnvironment(); + await expect(header.organizationSelect).toContainText(orgName); + }); + } + ); }); test.describe('MPT-8229 Validate invitations in the settings', { tag: ['@invitation-flow', '@ui', '@slow'] }, () => { test.skip(process.env.USE_LIVE_DEMO === 'true', 'Live demo environment does not support invitation flow tests'); - test('[229868] Invitation is visible in Settings Tab @slow', async ({ - loginPage, - header, - mainMenu, - usersPage, - usersInvitePage, - registerPage, - settingsPage, - pendingInvitationsPage, - emailVerificationPage, - baseRequest, - }) => { - test.setTimeout(120000); - const poolName = usersPage.getPoolNameForEnvironment(); - - await test.step('Login as Admin user', async () => { - invitationEmail = generateRandomEmail(); - inviteLink = `${process.env.BASE_URL}/invited?email=${encodeURIComponent(invitationEmail)}`; - - await loginPage.login(process.env.DEFAULT_USER_EMAIL, process.env.DEFAULT_USER_PASSWORD); - await loginPage.waitForLoadingPageImgToDisappear(); - }); - - await test.step('Navigate to the invitation page', async () => { - await mainMenu.clickUserManagement(); - await usersPage.clickInviteBtn(); - }); - - await test.step('Invite a new user to the organisation', async () => { - await usersInvitePage.inviteUser(invitationEmail); - await usersInvitePage.userInvitedAlert.waitFor(); - await usersInvitePage.userInvitedAlertCloseButton.click(); - }); - - await test.step('Sign out Admin user', async () => { - await header.signOut(); - }); - - await test.step('Sign up user', async () => { - await registerPage.navigateToRegistration(inviteLink); - await registerPage.registerUser('Test User', process.env.DEFAULT_USER_PASSWORD); - }); - - await test.step('Set verification code', async () => { - await emailVerificationPage.waitForVerificationCodeResetTimeout(); - await baseRequest.setVerificationCode(invitationEmail, verificationCode); - }); - - await test.step('Verify and accept invitation from email', async () => { - await emailVerificationPage.verifyCodeAndConfirm(verificationCode); - await emailVerificationPage.proceedToFinOps(); - await pendingInvitationsPage.acceptInvite(); - }); - - await test.step('Verify no pending invitations in Settings tab', async () => { - await mainMenu.clickSettings(); - await settingsPage.clickInvitationsTab(); - await expect(settingsPage.page.getByText('No invitations pending')).toBeVisible(); - }); - - await test.step('Sign out user', async () => { - await header.signOut(); - }); - - await test.step('Log back in as admin', async () => { - await loginPage.loginWithoutNavigation(process.env.DEFAULT_USER_EMAIL, process.env.DEFAULT_USER_PASSWORD); - }); - await test.step('Navigate to the invitation page', async () => { - await mainMenu.clickUserManagement(); - await usersPage.clickInviteBtn(); - }); - - await test.step('Invite a existing user to the organisation', async () => { - await usersInvitePage.inviteUser(invitationEmail, 'Engineer', poolName); - await usersInvitePage.userInvitedAlert.waitFor(); - await usersInvitePage.userInvitedAlertCloseButton.click(); - }); - - await test.step('Sign out Admin user', async () => { - await header.signOut(); - }); - - await test.step('Login as new user', async () => { - await loginPage.loginWithoutNavigation(invitationEmail, process.env.DEFAULT_USER_PASSWORD); - }); - - await test.step('View invitation in Settings', async () => { - await settingsPage.navigateToURL(); - await settingsPage.clickInvitationsTab(); - await expect(settingsPage.page.getByText(`● Engineer at ${poolName}`)).toBeVisible(); - }); - }); + test( + '[229868] Invitation is visible in Settings Tab @slow', + { tag: '@p2' }, + async ({ + loginPage, + header, + mainMenu, + usersPage, + usersInvitePage, + registerPage, + settingsPage, + pendingInvitationsPage, + emailVerificationPage, + authRequest, + }) => { + test.setTimeout(120000); + const poolName = usersPage.getPoolNameForEnvironment(); + + await test.step('Login as Admin user', async () => { + invitationEmail = generateRandomEmail(); + inviteLink = `${process.env.BASE_URL}/invited?email=${encodeURIComponent(invitationEmail)}`; + + await loginPage.login(process.env.DEFAULT_USER_EMAIL, process.env.DEFAULT_USER_PASSWORD); + await loginPage.waitForLoadingPageImgToDisappear(); + }); + + await test.step('Navigate to the invitation page', async () => { + await mainMenu.clickUserManagement(); + await usersPage.clickInviteBtn(); + }); + + await test.step('Invite a new user to the organisation', async () => { + await usersInvitePage.inviteUser(invitationEmail); + await usersInvitePage.userInvitedAlert.waitFor(); + await usersInvitePage.userInvitedAlertCloseButton.click(); + }); + + await test.step('Sign out Admin user', async () => { + await header.signOut(); + }); + + await test.step('Sign up user', async () => { + await registerPage.navigateToRegistration(inviteLink); + await registerPage.registerUser('Test User', process.env.DEFAULT_USER_PASSWORD); + }); + + await test.step('Set verification code', async () => { + await emailVerificationPage.waitForVerificationCodeResetTimeout(); + await authRequest.setVerificationCode(invitationEmail, verificationCode); + }); + + await test.step('Verify and accept invitation from email', async () => { + await emailVerificationPage.verifyCodeAndConfirm(verificationCode); + await emailVerificationPage.proceedToFinOps(); + await pendingInvitationsPage.acceptInvite(); + }); + + await test.step('Verify no pending invitations in Settings tab', async () => { + await mainMenu.clickSettings(); + await settingsPage.clickInvitationsTab(); + await expect(settingsPage.page.getByText('No invitations pending')).toBeVisible(); + }); + + await test.step('Sign out user', async () => { + await header.signOut(); + }); + + await test.step('Log back in as admin', async () => { + await loginPage.loginWithoutNavigation(process.env.DEFAULT_USER_EMAIL, process.env.DEFAULT_USER_PASSWORD); + }); + await test.step('Navigate to the invitation page', async () => { + await mainMenu.clickUserManagement(); + await usersPage.clickInviteBtn(); + }); + + await test.step('Invite a existing user to the organisation', async () => { + await usersInvitePage.inviteUser(invitationEmail, 'Engineer', poolName); + await usersInvitePage.userInvitedAlert.waitFor(); + await usersInvitePage.userInvitedAlertCloseButton.click(); + }); + + await test.step('Sign out Admin user', async () => { + await header.signOut(); + }); + + await test.step('Login as new user', async () => { + await loginPage.loginWithoutNavigation(invitationEmail, process.env.DEFAULT_USER_PASSWORD); + }); + + await test.step('View invitation in Settings', async () => { + await settingsPage.navigateToURL(); + await settingsPage.clickInvitationsTab(); + await expect(settingsPage.page.getByText(`● Engineer at ${poolName}`)).toBeVisible(); + }); + } + ); }); test.describe('MPT-8231 Invitation Flow Tests for an existing user', { tag: ['@invitation-flow', '@ui', '@slow'] }, () => { test.skip(process.env.USE_LIVE_DEMO === 'true', 'Live demo environment does not support invitation flow tests'); - test('[229869] Invite existing user with a new role @slow', async ({ - header, - loginPage, - mainMenu, - usersPage, - usersInvitePage, - registerPage, - settingsPage, - pendingInvitationsPage, - emailVerificationPage, - baseRequest, - }) => { - test.setTimeout(120000); - const poolName = usersPage.getPoolNameForEnvironment(); - - await test.step('Login as Admin user', async () => { - invitationEmail = generateRandomEmail(); - inviteLink = `${process.env.BASE_URL}/invited?email=${encodeURIComponent(invitationEmail)}`; - - await loginPage.login(process.env.DEFAULT_USER_EMAIL, process.env.DEFAULT_USER_PASSWORD); - await loginPage.waitForLoadingPageImgToDisappear(); - }); - - await test.step('Navigate to the invitation page', async () => { - await mainMenu.clickUserManagement(); - await usersPage.clickInviteBtn(); - }); - - await test.step('Invite a new user to the organisation', async () => { - await usersInvitePage.inviteUser(invitationEmail); - await usersInvitePage.userInvitedAlert.waitFor(); - await usersInvitePage.userInvitedAlertCloseButton.click(); - }); - - await test.step('Sign out Admin user', async () => { - await header.signOut(); - }); - - await test.step('Sign up user', async () => { - await registerPage.navigateToRegistration(inviteLink); - await registerPage.registerUser('Test User', process.env.DEFAULT_USER_PASSWORD); - }); - - await test.step('Set verification code', async () => { - await emailVerificationPage.waitForVerificationCodeResetTimeout(); - await baseRequest.setVerificationCode(invitationEmail, verificationCode); - }); - - await test.step('Verify and accept invitation from email', async () => { - await emailVerificationPage.verifyCodeAndConfirm(verificationCode); - await emailVerificationPage.proceedToFinOps(); - await pendingInvitationsPage.acceptInvite(); - }); - - await test.step('Sign out user', async () => { - await header.signOut(); - }); - - await test.step('Log back in as admin', async () => { - await loginPage.loginWithoutNavigation(process.env.DEFAULT_USER_EMAIL, process.env.DEFAULT_USER_PASSWORD); - }); - await test.step('Navigate to the invitation page', async () => { - await mainMenu.clickUserManagement(); - await usersPage.clickInviteBtn(); - }); - - await test.step('Invite a existing user to the organisation', async () => { - await usersInvitePage.inviteUser(invitationEmail, 'Manager', poolName); - await usersInvitePage.userInvitedAlert.waitFor(); - await usersInvitePage.userInvitedAlertCloseButton.click(); - }); - - await test.step('Sign out Admin user', async () => { - await header.signOut(); - }); - - await test.step('Login as new user', async () => { - await loginPage.loginWithoutNavigation(invitationEmail, process.env.DEFAULT_USER_PASSWORD); - }); - - await test.step('View invitation in Settings', async () => { - await settingsPage.navigateToURL(); - await settingsPage.clickInvitationsTab(); - await expect(settingsPage.page.getByText(`● Manager at ${poolName} pool`)).toBeVisible(); - }); - }); -}); - -test.describe( - '[MPT-18378] Verify that an invitation is recorded in the events log', - { tag: ['@invitation-flow', '@ui', '@events'] }, - () => { - test.use({ restoreSession: true }); + test( + '[229869] Invite existing user with a new role @slow', + { tag: '@p2' }, + async ({ + header, + loginPage, + mainMenu, + usersPage, + usersInvitePage, + registerPage, + settingsPage, + pendingInvitationsPage, + emailVerificationPage, + authRequest, + }) => { + test.setTimeout(120000); + const poolName = usersPage.getPoolNameForEnvironment(); + + await test.step('Login as Admin user', async () => { + invitationEmail = generateRandomEmail(); + inviteLink = `${process.env.BASE_URL}/invited?email=${encodeURIComponent(invitationEmail)}`; + + await loginPage.login(process.env.DEFAULT_USER_EMAIL, process.env.DEFAULT_USER_PASSWORD); + await loginPage.waitForLoadingPageImgToDisappear(); + }); - test('[232868] Invite a new user and verify the event is logged', async ({ mainMenu, usersPage, usersInvitePage, eventsPage }) => { - invitationEmail = generateRandomEmail(); - let date: string; + await test.step('Navigate to the invitation page', async () => { + await mainMenu.clickUserManagement(); + await usersPage.clickInviteBtn(); + }); await test.step('Invite a new user to the organisation', async () => { - await usersPage.navigateToURL(); + await usersInvitePage.inviteUser(invitationEmail); + await usersInvitePage.userInvitedAlert.waitFor(); + await usersInvitePage.userInvitedAlertCloseButton.click(); + }); + + await test.step('Sign out Admin user', async () => { + await header.signOut(); + }); + + await test.step('Sign up user', async () => { + await registerPage.navigateToRegistration(inviteLink); + await registerPage.registerUser('Test User', process.env.DEFAULT_USER_PASSWORD); + }); + + await test.step('Set verification code', async () => { + await emailVerificationPage.waitForVerificationCodeResetTimeout(); + await authRequest.setVerificationCode(invitationEmail, verificationCode); + }); + + await test.step('Verify and accept invitation from email', async () => { + await emailVerificationPage.verifyCodeAndConfirm(verificationCode); + await emailVerificationPage.proceedToFinOps(); + await pendingInvitationsPage.acceptInvite(); + }); + + await test.step('Sign out user', async () => { + await header.signOut(); + }); + + await test.step('Log back in as admin', async () => { + await loginPage.loginWithoutNavigation(process.env.DEFAULT_USER_EMAIL, process.env.DEFAULT_USER_PASSWORD); + }); + await test.step('Navigate to the invitation page', async () => { + await mainMenu.clickUserManagement(); await usersPage.clickInviteBtn(); + }); - date = getCurrentUTCTimestamp(); - await usersInvitePage.inviteUser(invitationEmail); + await test.step('Invite a existing user to the organisation', async () => { + await usersInvitePage.inviteUser(invitationEmail, 'Manager', poolName); await usersInvitePage.userInvitedAlert.waitFor(); await usersInvitePage.userInvitedAlertCloseButton.click(); }); - await test.step('Navigate to Events page and verify invitation event is logged', async () => { - debugLog(`Current date: ${date}`); - await mainMenu.clickEvents(); - await eventsPage.filterByEventLevel('Info'); - const invitationEventText = await (await eventsPage.getEventByText(invitationEmail)).textContent(); - debugLog(`Invitation event text: ${invitationEventText}`); - expect(invitationEventText).toContain(date); - expect(invitationEventText.toLowerCase()).toContain(`employee ${invitationEmail} invited by`); - expect(invitationEventText).toContain(process.env.DEFAULT_USER_EMAIL); + await test.step('Sign out Admin user', async () => { + await header.signOut(); }); - }); + + await test.step('Login as new user', async () => { + await loginPage.loginWithoutNavigation(invitationEmail, process.env.DEFAULT_USER_PASSWORD); + }); + + await test.step('View invitation in Settings', async () => { + await settingsPage.navigateToURL(); + await settingsPage.clickInvitationsTab(); + await expect(settingsPage.page.getByText(`● Manager at ${poolName} pool`)).toBeVisible(); + }); + } + ); +}); + +test.describe( + '[MPT-18378] Verify that an invitation is recorded in the events log', + { tag: ['@invitation-flow', '@ui', '@events'] }, + () => { + test.use({ restoreSession: true }); + + test( + '[232868] Invite a new user and verify the event is logged', + { tag: '@p2' }, + async ({ mainMenu, usersPage, usersInvitePage, eventsPage }) => { + invitationEmail = generateRandomEmail(); + let date: string; + + await test.step('Invite a new user to the organisation', async () => { + await usersPage.navigateToURL(); + await usersPage.clickInviteBtn(); + + date = getCurrentUTCTimestamp(); + await usersInvitePage.inviteUser(invitationEmail); + await usersInvitePage.userInvitedAlert.waitFor(); + await usersInvitePage.userInvitedAlertCloseButton.click(); + }); + + await test.step('Navigate to Events page and verify invitation event is logged', async () => { + debugLog(`Current date: ${date}`); + await mainMenu.clickEvents(); + await eventsPage.filterByEventLevel('Info'); + const invitationEventText = await (await eventsPage.getEventByText(invitationEmail)).textContent(); + debugLog(`Invitation event text: ${invitationEventText}`); + expect(invitationEventText).toContain(date); + expect(invitationEventText.toLowerCase()).toContain(`employee ${invitationEmail} invited by`); + expect(invitationEventText).toContain(process.env.DEFAULT_USER_EMAIL); + }); + } + ); } ); diff --git a/e2etests/tests/perspective-tests.spec.ts b/e2etests/tests/perspective-tests.spec.ts index e4a587477..01d99017a 100644 --- a/e2etests/tests/perspective-tests.spec.ts +++ b/e2etests/tests/perspective-tests.spec.ts @@ -10,122 +10,125 @@ test.describe('[MPT-18579] Perspective Tests', { tag: ['@ui', '@resources', '@pe 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, - perspectivesPage, - }) => { - await resourcesPage.navigateToResourcesPageAndResetFilters(); - - const filter = 'Region'; - const filterOption = env !== 'test' ? 'eu-west-1' : 'West Europe'; - const categorizeBy = 'Resource type'; - 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 () => { - await resourcesPage.selectFilterByText(filter, filterOption); - await resourcesPage.clickExpensesTab(); - await resourcesPage.selectCategorizeBy(categorizeBy, false); - await resourcesPage.waitForAPIResponseByPartialTextMatch('breakdown_expenses', LARGE_DATA_TIMEOUT); - await resourcesPage.waitForCanvas(); - await resourcesPage.selectGroupByTag(groupByTag); - await resourcesPage.click(resourcesPage.savePerspectiveBtn); - }); - - await test.step('Verify perspective criteria matches those selected', async () => { - await expect.soft(resourcesPage.savePerspectiveBreakDownByValue).toHaveText('Expenses'); - await expect.soft(resourcesPage.savePerspectiveCategorizeByValue).toHaveText(categorizeBy); - await expect.soft(resourcesPage.savePerspectiveGroupByTypeValue).toContainText('Tag'); - await expect.soft(resourcesPage.savePerspectiveGroupByValue).toHaveText(groupByTag); - await expect.soft(resourcesPage.savePerspectiveFiltersValue).toContainText(filter); - await expect.soft(resourcesPage.savePerspectiveFiltersOptionValue).toContainText(filterOption); - }); - - let perspectiveBtn: Locator; - await test.step('Save perspective and verify it appears in the perspectives list', async () => { - await resourcesPage.savePerspective(perspectiveName); - await resourcesPage.click(resourcesPage.perspectivesBtn); - perspectiveBtn = await resourcesPage.getPerspectivesButtonByName(perspectiveName); - await expect(perspectiveBtn).toBeVisible(); - }); - - await test.step('Navigate to perspective page and validate the perspective in the table', async () => { - await resourcesPage.perspectivesSeeAllPerspectivesLink.click(); - const perspectiveRow = await perspectivesPage.getTableRowByPerspectiveName(perspectiveName); - await expect.soft(perspectiveRow.locator(perspectivesPage.breakdownByColumn)).toHaveText('Expenses'); - await expect.soft(perspectiveRow.locator(perspectivesPage.categorizeByColumn)).toHaveText(categorizeBy); - await expect.soft(perspectiveRow.locator(perspectivesPage.groupByColumn)).toContainText('Tag'); - await expect.soft(perspectiveRow.locator(perspectivesPage.groupByColumn)).toContainText(groupByTag); - await expect.soft(perspectiveRow.locator(perspectivesPage.filtersColumn)).toContainText(filter); - await expect.soft(perspectiveRow.locator(perspectivesPage.filtersColumn)).toContainText(filterOption); - }); - - await test.step('Return to the resources page and return to default view', async () => { + test( + '[232963] User can create an Expenses perspective and the chart options are saved and applied correctly', + { tag: '@p2' }, + async ({ resourcesPage, perspectivesPage }) => { await resourcesPage.navigateToResourcesPageAndResetFilters(); - await resourcesPage.selectCategorizeBy('Service'); - expect.soft(await resourcesPage.selectedComboBoxOption(resourcesPage.categorizeBySelect)).toBe('Service'); - await expect.soft(resourcesPage.clearIcon).toBeHidden(); - }); - - await test.step('Apply perspective and validate the chart options are applied correctly', async () => { - await resourcesPage.applyPerspective(perspectiveBtn); - const activeFilter = resourcesPage.getActiveFilter(); - await expect.soft(activeFilter).toHaveText(`${filter} (${filterOption})`); - - expect.soft(await resourcesPage.isAriaSelected(resourcesPage.tabExpensesBtn)).toBe(true); - expect.soft(await resourcesPage.selectedComboBoxOption(resourcesPage.categorizeBySelect)).toBe(categorizeBy); - await expect.soft(resourcesPage.selectedGroupByTagItem).toBeVisible(); - await expect.soft(resourcesPage.selectedGroupByTagKey).toContainText('Tag:'); - await expect.soft(resourcesPage.selectedGroupByTagValue).toHaveText(groupByTag); - }); - }); - test('[232964] User can create perspective for resource count and the perspective is saved and applied correctly', async ({ - resourcesPage, - }) => { - await resourcesPage.navigateToResourcesPageAndResetFilters(); - - const filter = 'Recommendations'; - const filterOption = 'With recommendations'; - const categorizeBy = 'Region'; - const perspectiveName = `Test Perspective ${new Date().getTime()}`; - - await test.step('Select options to save as a perspective', async () => { - await resourcesPage.selectFilterByText(filter, filterOption); - await resourcesPage.clickResourceCountTab(); - await resourcesPage.selectCategorizeBy(categorizeBy); - await resourcesPage.click(resourcesPage.savePerspectiveBtn); - }); - - await test.step('Verify perspective criteria matches those selected', async () => { - await expect.soft(resourcesPage.savePerspectiveBreakDownByValue).toHaveText('Resource count'); - await expect.soft(resourcesPage.savePerspectiveCategorizeByValue).toHaveText(categorizeBy); - await expect.soft(resourcesPage.savePerspectiveFiltersValue).toContainText(filter); - await expect.soft(resourcesPage.savePerspectiveFiltersOptionValue).toContainText(filterOption); - }); - - await test.step('Save perspective', async () => { - await resourcesPage.savePerspective(perspectiveName); - }); - - await test.step('Return to the resources page and return to default view', async () => { + const filter = 'Region'; + const filterOption = env !== 'test' ? 'eu-west-1' : 'West Europe'; + const categorizeBy = 'Resource type'; + 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 () => { + await resourcesPage.selectFilterByText(filter, filterOption); + await resourcesPage.clickExpensesTab(); + await resourcesPage.selectCategorizeBy(categorizeBy, false); + await resourcesPage.waitForAPIResponseByPartialTextMatch('breakdown_expenses', LARGE_DATA_TIMEOUT); + await resourcesPage.waitForCanvas(); + await resourcesPage.selectGroupByTag(groupByTag); + await resourcesPage.click(resourcesPage.savePerspectiveBtn); + }); + + await test.step('Verify perspective criteria matches those selected', async () => { + await expect.soft(resourcesPage.savePerspectiveBreakDownByValue).toHaveText('Expenses'); + await expect.soft(resourcesPage.savePerspectiveCategorizeByValue).toHaveText(categorizeBy); + await expect.soft(resourcesPage.savePerspectiveGroupByTypeValue).toContainText('Tag'); + await expect.soft(resourcesPage.savePerspectiveGroupByValue).toHaveText(groupByTag); + await expect.soft(resourcesPage.savePerspectiveFiltersValue).toContainText(filter); + await expect.soft(resourcesPage.savePerspectiveFiltersOptionValue).toContainText(filterOption); + }); + + let perspectiveBtn: Locator; + await test.step('Save perspective and verify it appears in the perspectives list', async () => { + await resourcesPage.savePerspective(perspectiveName); + await resourcesPage.click(resourcesPage.perspectivesBtn); + perspectiveBtn = await resourcesPage.getPerspectivesButtonByName(perspectiveName); + await expect(perspectiveBtn).toBeVisible(); + }); + + await test.step('Navigate to perspective page and validate the perspective in the table', async () => { + await resourcesPage.perspectivesSeeAllPerspectivesLink.click(); + const perspectiveRow = await perspectivesPage.getTableRowByPerspectiveName(perspectiveName); + await expect.soft(perspectiveRow.locator(perspectivesPage.breakdownByColumn)).toHaveText('Expenses'); + await expect.soft(perspectiveRow.locator(perspectivesPage.categorizeByColumn)).toHaveText(categorizeBy); + await expect.soft(perspectiveRow.locator(perspectivesPage.groupByColumn)).toContainText('Tag'); + await expect.soft(perspectiveRow.locator(perspectivesPage.groupByColumn)).toContainText(groupByTag); + await expect.soft(perspectiveRow.locator(perspectivesPage.filtersColumn)).toContainText(filter); + await expect.soft(perspectiveRow.locator(perspectivesPage.filtersColumn)).toContainText(filterOption); + }); + + await test.step('Return to the resources page and return to default view', async () => { + await resourcesPage.navigateToResourcesPageAndResetFilters(); + await resourcesPage.selectCategorizeBy('Service'); + expect.soft(await resourcesPage.selectedComboBoxOption(resourcesPage.categorizeBySelect)).toBe('Service'); + await expect.soft(resourcesPage.clearIcon).toBeHidden(); + }); + + await test.step('Apply perspective and validate the chart options are applied correctly', async () => { + await resourcesPage.applyPerspective(perspectiveBtn); + const activeFilter = resourcesPage.getActiveFilter(); + await expect.soft(activeFilter).toHaveText(`${filter} (${filterOption})`); + + expect.soft(await resourcesPage.isAriaSelected(resourcesPage.tabExpensesBtn)).toBe(true); + expect.soft(await resourcesPage.selectedComboBoxOption(resourcesPage.categorizeBySelect)).toBe(categorizeBy); + await expect.soft(resourcesPage.selectedGroupByTagItem).toBeVisible(); + await expect.soft(resourcesPage.selectedGroupByTagKey).toContainText('Tag:'); + await expect.soft(resourcesPage.selectedGroupByTagValue).toHaveText(groupByTag); + }); + } + ); + + test( + '[232964] User can create perspective for resource count and the perspective is saved and applied correctly', + { tag: '@p2' }, + async ({ resourcesPage }) => { await resourcesPage.navigateToResourcesPageAndResetFilters(); - await resourcesPage.selectCategorizeBy('Service'); - expect.soft(await resourcesPage.selectedComboBoxOption(resourcesPage.categorizeBySelect)).toBe('Service'); - await expect.soft(resourcesPage.clearIcon).toBeHidden(); - }); - await test.step('Apply perspective and validate the chart options are applied correctly', async () => { - await resourcesPage.applyPerspective(await resourcesPage.getPerspectivesButtonByName(perspectiveName)); - - const activeFilter = resourcesPage.getActiveFilter(); - await expect.soft(activeFilter).toHaveText(`${filter} (${filterOption})`); - expect.soft(await resourcesPage.isAriaSelected(resourcesPage.tabResourceCountBtn)).toBe(true); - expect.soft(await resourcesPage.selectedComboBoxOption(resourcesPage.categorizeBySelect)).toBe(categorizeBy); - }); - }); - - test('[232965] User can create a perspective a Tags chart is saved and applied correctly', async ({ resourcesPage }) => { + const filter = 'Recommendations'; + const filterOption = 'With recommendations'; + const categorizeBy = 'Region'; + const perspectiveName = `Test Perspective ${new Date().getTime()}`; + + await test.step('Select options to save as a perspective', async () => { + await resourcesPage.selectFilterByText(filter, filterOption); + await resourcesPage.clickResourceCountTab(); + await resourcesPage.selectCategorizeBy(categorizeBy); + await resourcesPage.click(resourcesPage.savePerspectiveBtn); + }); + + await test.step('Verify perspective criteria matches those selected', async () => { + await expect.soft(resourcesPage.savePerspectiveBreakDownByValue).toHaveText('Resource count'); + await expect.soft(resourcesPage.savePerspectiveCategorizeByValue).toHaveText(categorizeBy); + await expect.soft(resourcesPage.savePerspectiveFiltersValue).toContainText(filter); + await expect.soft(resourcesPage.savePerspectiveFiltersOptionValue).toContainText(filterOption); + }); + + await test.step('Save perspective', async () => { + await resourcesPage.savePerspective(perspectiveName); + }); + + await test.step('Return to the resources page and return to default view', async () => { + await resourcesPage.navigateToResourcesPageAndResetFilters(); + await resourcesPage.selectCategorizeBy('Service'); + expect.soft(await resourcesPage.selectedComboBoxOption(resourcesPage.categorizeBySelect)).toBe('Service'); + await expect.soft(resourcesPage.clearIcon).toBeHidden(); + }); + + await test.step('Apply perspective and validate the chart options are applied correctly', async () => { + await resourcesPage.applyPerspective(await resourcesPage.getPerspectivesButtonByName(perspectiveName)); + + const activeFilter = resourcesPage.getActiveFilter(); + await expect.soft(activeFilter).toHaveText(`${filter} (${filterOption})`); + expect.soft(await resourcesPage.isAriaSelected(resourcesPage.tabResourceCountBtn)).toBe(true); + expect.soft(await resourcesPage.selectedComboBoxOption(resourcesPage.categorizeBySelect)).toBe(categorizeBy); + }); + } + ); + + test('[232965] User can create a perspective a Tags chart is saved and applied correctly', { tag: '@p2' }, async ({ resourcesPage }) => { await resourcesPage.navigateToResourcesPageAndResetFilters(); const filter = 'Pool'; @@ -164,88 +167,94 @@ test.describe('[MPT-18579] Perspective Tests', { tag: ['@ui', '@resources', '@pe }); }); - test('[232966] User can create a perspective for the Meta chart and the perspective is saved and applied correctly', async ({ - resourcesPage, - }) => { - await resourcesPage.navigateToResourcesPageAndResetFilters(); - - const categorizeBy = 'OS'; - const breakdownType = 'Count'; - const perspectiveName = `Test Perspective ${new Date().getTime()}`; - - await test.step('Select options to save as a perspective', async () => { - await resourcesPage.clickMetaTab(); - await resourcesPage.selectMetaCategorizeBy(categorizeBy); - await resourcesPage.selectBreakdownType(breakdownType); - await resourcesPage.click(resourcesPage.savePerspectiveBtn); - }); - - await test.step('Verify perspective criteria matches those selected', async () => { - await expect.soft(resourcesPage.savePerspectiveBreakDownByValue).toHaveText('Meta'); - await expect.soft(resourcesPage.savePerspectiveCategorizeByValue).toHaveText(categorizeBy); - await expect.soft(resourcesPage.savePerspectiveNoFiltersValue).toHaveText('-'); - }); - - await test.step('Save perspective', async () => { - await resourcesPage.savePerspective(perspectiveName); - }); - - await test.step('Return to the resources page and return to default view', async () => { + test( + '[232966] User can create a perspective for the Meta chart and the perspective is saved and applied correctly', + { tag: '@p2' }, + async ({ resourcesPage }) => { await resourcesPage.navigateToResourcesPageAndResetFilters(); - await resourcesPage.selectCategorizeBy('Service'); - expect.soft(await resourcesPage.selectedComboBoxOption(resourcesPage.categorizeBySelect)).toBe('Service'); - await expect.soft(resourcesPage.clearIcon).toBeHidden(); - }); - await test.step('Apply perspective and validate the chart options are applied correctly', async () => { - await resourcesPage.applyPerspective(await resourcesPage.getPerspectivesButtonByName(perspectiveName)); + const categorizeBy = 'OS'; + const breakdownType = 'Count'; + const perspectiveName = `Test Perspective ${new Date().getTime()}`; - await expect.soft(resourcesPage.getActiveFilter()).toBeHidden(); - expect.soft(await resourcesPage.isAriaSelected(resourcesPage.tabMetaBtn)).toBe(true); - expect.soft(await resourcesPage.selectedComboBoxOption(resourcesPage.metaCategorizeBySelect)).toBe(categorizeBy); - }); - }); + await test.step('Select options to save as a perspective', async () => { + await resourcesPage.clickMetaTab(); + await resourcesPage.selectMetaCategorizeBy(categorizeBy); + await resourcesPage.selectBreakdownType(breakdownType); + await resourcesPage.click(resourcesPage.savePerspectiveBtn); + }); - 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}`); + await test.step('Verify perspective criteria matches those selected', async () => { + await expect.soft(resourcesPage.savePerspectiveBreakDownByValue).toHaveText('Meta'); + await expect.soft(resourcesPage.savePerspectiveCategorizeByValue).toHaveText(categorizeBy); + await expect.soft(resourcesPage.savePerspectiveNoFiltersValue).toHaveText('-'); + }); - await resourcesPage.navigateToResourcesPageAndResetFilters(); + await test.step('Save perspective', async () => { + await resourcesPage.savePerspective(perspectiveName); + }); - const perspectiveName = `Test Perspective ${new Date().getTime()}`; + await test.step('Return to the resources page and return to default view', async () => { + await resourcesPage.navigateToResourcesPageAndResetFilters(); + await resourcesPage.selectCategorizeBy('Service'); + expect.soft(await resourcesPage.selectedComboBoxOption(resourcesPage.categorizeBySelect)).toBe('Service'); + await expect.soft(resourcesPage.clearIcon).toBeHidden(); + }); + + await test.step('Apply perspective and validate the chart options are applied correctly', async () => { + await resourcesPage.applyPerspective(await resourcesPage.getPerspectivesButtonByName(perspectiveName)); + + await expect.soft(resourcesPage.getActiveFilter()).toBeHidden(); + expect.soft(await resourcesPage.isAriaSelected(resourcesPage.tabMetaBtn)).toBe(true); + expect.soft(await resourcesPage.selectedComboBoxOption(resourcesPage.metaCategorizeBySelect)).toBe(categorizeBy); + }); + } + ); + + test( + '[232967] User can create a perspective and delete it via the perspectives table', + { tag: '@p2' }, + async ({ resourcesPage, perspectivesPage }) => { + const tag = env !== 'test' ? 'Component' : 'devops-component'; + await perspectivesPage.navigateToURL(); + const initialPerspectivesCount = await perspectivesPage.getPerspectivesCount(); + debugLog(`Initial perspectives count: ${initialPerspectivesCount}`); - await test.step('Create and save a perspective', async () => { - await resourcesPage.clickExpensesTab(); - await resourcesPage.selectGroupByTag(tag); - await resourcesPage.click(resourcesPage.savePerspectiveBtn); - await resourcesPage.savePerspective(perspectiveName); - }); + await resourcesPage.navigateToResourcesPageAndResetFilters(); - await test.step('Navigate to perspectives page and delete the perspective', async () => { - await resourcesPage.click(resourcesPage.perspectivesBtn); - await resourcesPage.perspectivesSeeAllPerspectivesLink.click(); - await perspectivesPage.deletePerspective(perspectiveName); - }); + const perspectiveName = `Test Perspective ${new Date().getTime()}`; - await test.step('Validate the perspective is deleted and the perspectives count is updated', async () => { - await expect.soft(await perspectivesPage.getTableRowByPerspectiveName(perspectiveName)).toBeHidden(); - if (initialPerspectivesCount === 0) await expect.soft(perspectivesPage.noPerspectivesMessage).toBeVisible(); - }); + await test.step('Create and save a perspective', async () => { + await resourcesPage.clickExpensesTab(); + await resourcesPage.selectGroupByTag(tag); + await resourcesPage.click(resourcesPage.savePerspectiveBtn); + await resourcesPage.savePerspective(perspectiveName); + }); - await test.step('That the perspectives button is hidden if initial perspective count was zero', async () => { - await resourcesPage.navigateToURL(); - if (initialPerspectivesCount === 0) { - await expect.soft(resourcesPage.perspectivesBtn).toBeHidden(); - } else { + await test.step('Navigate to perspectives page and delete the perspective', async () => { await resourcesPage.click(resourcesPage.perspectivesBtn); - await expect.soft(await resourcesPage.getPerspectivesButtonByName(perspectiveName)).toBeHidden(); - } - }); - }); - - test('[232969] User can create a perspective with multiple filters', async ({ resourcesPage, perspectivesPage }) => { + await resourcesPage.perspectivesSeeAllPerspectivesLink.click(); + await perspectivesPage.deletePerspective(perspectiveName); + }); + + await test.step('Validate the perspective is deleted and the perspectives count is updated', async () => { + await expect.soft(await perspectivesPage.getTableRowByPerspectiveName(perspectiveName)).toBeHidden(); + if (initialPerspectivesCount === 0) await expect.soft(perspectivesPage.noPerspectivesMessage).toBeVisible(); + }); + + await test.step('That the perspectives button is hidden if initial perspective count was zero', async () => { + await resourcesPage.navigateToURL(); + if (initialPerspectivesCount === 0) { + await expect.soft(resourcesPage.perspectivesBtn).toBeHidden(); + } else { + await resourcesPage.click(resourcesPage.perspectivesBtn); + await expect.soft(await resourcesPage.getPerspectivesButtonByName(perspectiveName)).toBeHidden(); + } + }); + } + ); + + test('[232969] User can create a perspective with multiple filters', { tag: '@p2' }, async ({ resourcesPage, perspectivesPage }) => { await resourcesPage.navigateToResourcesPageAndResetFilters(); const filter1 = 'Region'; @@ -286,65 +295,68 @@ test.describe('[MPT-18579] Perspective Tests', { tag: ['@ui', '@resources', '@pe }); }); - test('[232970] Creating a perspective with an existing name shows a message stating that the original will be overwritten', async ({ - resourcesPage, - }) => { - await resourcesPage.navigateToResourcesPageAndResetFilters(); + test( + '[232970] Creating a perspective with an existing name shows a message stating that the original will be overwritten', + { tag: '@p2' }, + async ({ resourcesPage }) => { + await resourcesPage.navigateToResourcesPageAndResetFilters(); - const filter1 = 'Region'; - const filterOption1 = env !== 'test' ? 'eu-west-1' : 'West Europe'; - const filter2 = 'Recommendations'; - const filterOption2 = 'With recommendations'; - const perspectiveName = `Test Perspective ${new Date().getTime()}`; + const filter1 = 'Region'; + const filterOption1 = env !== 'test' ? 'eu-west-1' : 'West Europe'; + const filter2 = 'Recommendations'; + const filterOption2 = 'With recommendations'; + const perspectiveName = `Test Perspective ${new Date().getTime()}`; - await test.step('Create and save a perspective', async () => { - await resourcesPage.selectFilterByText(filter1, filterOption1); - await resourcesPage.click(resourcesPage.savePerspectiveBtn); - await resourcesPage.savePerspective(perspectiveName); - }); + await test.step('Create and save a perspective', async () => { + await resourcesPage.selectFilterByText(filter1, filterOption1); + await resourcesPage.click(resourcesPage.savePerspectiveBtn); + await resourcesPage.savePerspective(perspectiveName); + }); - await test.step('Return to the resources page and return to default view', async () => { + await test.step('Return to the resources page and return to default view', async () => { await resourcesPage.navigateToResourcesPageAndResetFilters(); - }); - - await test.step('Attempt to create another perspective with the same name and validate the overwrite message is displayed', async () => { - await resourcesPage.selectFilterByText(filter2, filterOption2); - await resourcesPage.click(resourcesPage.savePerspectiveBtn); - await resourcesPage.fillSavePerspectiveName(perspectiveName); - - await expect(resourcesPage.getPerspectiveOverwriteMessage(perspectiveName)).toBeVisible(); - }); - - await test.step('Save the perspective', async () => { - await resourcesPage.click(resourcesPage.savePerspectiveSaveBtn); - await resourcesPage.savePerspectiveSaveBtn.waitFor({ state: 'detached' }); - await resourcesPage.waitForAllProgressBarsToDisappear(); - }); - - await test.step('Reset filters and apply perspective to validate the updated filters are applied', async () => { - await resourcesPage.resetFilters(); - await resourcesPage.applyPerspective(await resourcesPage.getPerspectivesButtonByName(perspectiveName)); - - await expect(resourcesPage.getActiveFilter()).toHaveCount(1); - await expect.soft(resourcesPage.getActiveFilter().filter({ hasText: `${filter1} (${filterOption1})` })).toBeHidden(); - await expect.soft(resourcesPage.getActiveFilter().filter({ hasText: `${filter2} (${filterOption2})` })).toBeVisible(); - }); - }); - - test('[232968] No perspectives message is displayed and perspectives button is hidden if there are no perspectives', async ({ - resourcesPage, - perspectivesPage, - }) => { - await test.step('Navigate to perspectives page and delete all perspectives', async () => { - await perspectivesPage.navigateToURL(); - await perspectivesPage.waitForAllProgressBarsToDisappear(); - await perspectivesPage.deleteAllPerspectives(); - await expect.soft(perspectivesPage.noPerspectivesMessage).toBeVisible(); - }); - - await test.step('Navigate to resources page and validate perspectives button is hidden', async () => { - await resourcesPage.navigateToURL(); - await expect(resourcesPage.perspectivesBtn).toBeHidden(); - }); - }); + }); + + await test.step('Attempt to create another perspective with the same name and validate the overwrite message is displayed', async () => { + await resourcesPage.selectFilterByText(filter2, filterOption2); + await resourcesPage.click(resourcesPage.savePerspectiveBtn); + await resourcesPage.fillSavePerspectiveName(perspectiveName); + + await expect(resourcesPage.getPerspectiveOverwriteMessage(perspectiveName)).toBeVisible(); + }); + + await test.step('Save the perspective', async () => { + await resourcesPage.click(resourcesPage.savePerspectiveSaveBtn); + await resourcesPage.savePerspectiveSaveBtn.waitFor({ state: 'detached' }); + await resourcesPage.waitForAllProgressBarsToDisappear(); + }); + + await test.step('Reset filters and apply perspective to validate the updated filters are applied', async () => { + await resourcesPage.resetFilters(); + await resourcesPage.applyPerspective(await resourcesPage.getPerspectivesButtonByName(perspectiveName)); + + await expect(resourcesPage.getActiveFilter()).toHaveCount(1); + await expect.soft(resourcesPage.getActiveFilter().filter({ hasText: `${filter1} (${filterOption1})` })).toBeHidden(); + await expect.soft(resourcesPage.getActiveFilter().filter({ hasText: `${filter2} (${filterOption2})` })).toBeVisible(); + }); + } + ); + + test( + '[232968] No perspectives message is displayed and perspectives button is hidden if there are no perspectives', + { tag: '@p2' }, + async ({ resourcesPage, perspectivesPage }) => { + await test.step('Navigate to perspectives page and delete all perspectives', async () => { + await perspectivesPage.navigateToURL(); + await perspectivesPage.waitForAllProgressBarsToDisappear(); + await perspectivesPage.deleteAllPerspectives(); + await expect.soft(perspectivesPage.noPerspectivesMessage).toBeVisible(); + }); + + await test.step('Navigate to resources page and validate perspectives button is hidden', async () => { + await resourcesPage.navigateToURL(); + await expect(resourcesPage.perspectivesBtn).toBeHidden(); + }); + } + ); }); diff --git a/e2etests/tests/policies-tests.spec.ts b/e2etests/tests/policies-tests.spec.ts index 1e858ab77..e3777a9c5 100644 --- a/e2etests/tests/policies-tests.spec.ts +++ b/e2etests/tests/policies-tests.spec.ts @@ -43,10 +43,10 @@ test.describe('[MPT-16366] Policies Tests', { tag: ['@ui', '@policies'] }, () => }); }); - test('[232286] Verify that Sample data pop-up is visible when no policies exist', async ({ policiesPage }) => { + test('[232286] Verify that Sample data pop-up is visible when no policies exist', { tag: ['@fast', '@p2'] }, async ({ policiesPage }) => { await test.step('Ensure all policies are deleted', async () => { // eslint-disable-next-line playwright/no-conditional-in-test - if(!await policiesPage.realDataAddBtn.isVisible()) { + if (!(await policiesPage.realDataAddBtn.isVisible())) { await deleteAllPolicies(); await policiesPage.page.reload(); await policiesPage.waitForAllProgressBarsToDisappear(); @@ -65,7 +65,6 @@ test.describe('[MPT-16366] Policies Tests', { tag: ['@ui', '@policies'] }, () => // 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(); await policiesCreatePage.createResourcePolicy(policyName, resourceCount, policiesCreatePage.regionFilter, filterOption); @@ -94,100 +93,112 @@ test.describe('[MPT-16366] Policies Tests', { tag: ['@ui', '@policies'] }, () => }); }); - test('[232288] Verify that user can create a recurring budget policy', async ({ policiesPage, policiesCreatePage }) => { - const policyName = `Recurring Budget ${Date.now()}`; - const budgetAmount = 1000; - const formattedAmount = formatCurrency(budgetAmount); - const filterOption = 'Active'; - - await test.step('Create Recurring Budget Policy', async () => { - await policiesPage.navigateToCreatePolicy(); - await policiesCreatePage.createRecurringBudgetPolicy(policyName, budgetAmount, policiesCreatePage.activityFilter, filterOption); - }); - - const targetPolicyRow = policiesPage.table.locator(`//td[.="${policyName}"]/ancestor::tr`); - - await test.step('Verify that the new policy is displayed in the policies table', async () => { - await targetPolicyRow.waitFor(); - await expect.soft(targetPolicyRow.locator('xpath=/td[1]')).toHaveText(policyName); - await expect.soft(targetPolicyRow.locator('xpath=/td[3]')).toHaveText(`Current month expenses must not exceed ${formattedAmount}.`); - await expect.soft(targetPolicyRow.locator('xpath=/td[4]')).toContainText(`Activity: ${filterOption}`); - }); - - await test.step('Navigate to the created policy details page', async () => { - await policiesPage.click(targetPolicyRow.locator('//a')); - await policiesPage.policyDetailsDiv.waitFor(); - }); - - await test.step('Verify policy details', async () => { - await expect.soft(policiesPage.policyDetailsDiv).toContainText(`Name: ${policyName}`); - await expect.soft(policiesPage.policyDetailsDiv).toContainText('Type: Recurring budget'); - await expect.soft(policiesPage.policyDetailsDiv).toContainText(`Current month expenses budget: ${formattedAmount}`); - await expect.soft(policiesPage.policyDetailsDiv).toContainText(`Activity: ${filterOption}`); - }); - }); - - test('[232289] Verify that user can create an expiring budget policy', async ({ policiesPage, policiesCreatePage }) => { - const policyName = `Expiring Budget ${Date.now()}`; - const budgetAmount = 500; - const formattedAmount = formatCurrency(budgetAmount); - const startDate = `${(new Date().getMonth() + 1).toString().padStart(2, '0')}/${new Date().getDate().toString().padStart(2, '0')}/${new Date().getFullYear()} 12:00 AM`; - - await test.step('Create Expiring Budget Policy', async () => { - await policiesPage.navigateToCreatePolicy(); - await policiesCreatePage.createExpiringBudgetPolicy(policyName, budgetAmount); - }); - - const targetPolicyRow = policiesPage.table.locator(`//td[.="${policyName}"]/ancestor::tr`); - - await test.step('Verify that the new policy is displayed in the policies table', async () => { - await targetPolicyRow.waitFor(); - await expect.soft(targetPolicyRow.locator('//td[1]')).toHaveText(policyName); - await expect - .soft(targetPolicyRow.locator('//td[3]')) - .toHaveText(`Total expenses from ${startDate} must not exceed ${formattedAmount}.`); - await expect.soft(targetPolicyRow.locator('//td[4]')).toHaveText('-'); - }); - - await test.step('Navigate to the created policy details page', async () => { - await policiesPage.click(targetPolicyRow.locator('//a')); - await policiesPage.policyDetailsDiv.waitFor(); - }); - - await test.step('Verify policy details', async () => { - await expect.soft(policiesPage.policyDetailsDiv).toContainText(`Name: ${policyName}`); - await expect.soft(policiesPage.policyDetailsDiv).toContainText('Type: Expiring budget'); - await expect.soft(policiesPage.policyDetailsDiv).toContainText(`Start date: ${startDate}`); - await expect.soft(policiesPage.policyDetailsDiv).toContainText(`Budget: ${formattedAmount}`); - await expect.soft(policiesPage.policyDetailsDiv).not.toContainText('Filters:'); - }); - }); - - test('[232290] Verify that user can delete a policy from the policy details page', async ({ policiesPage, policiesCreatePage }) => { - const policyName = `Policy To Be Deleted ${Date.now()}`; - const resourceCount = 5; - - await test.step('Create a policy to be deleted', async () => { - await policiesPage.navigateToCreatePolicy(); - await policiesCreatePage.createResourcePolicy(policyName, resourceCount); - }); - - const targetPolicyRow = policiesPage.table.locator(`//td[.="${policyName}"]/ancestor::tr`); - - await test.step('Navigate to the created policy details page', async () => { - await targetPolicyRow.waitFor(); - await policiesPage.click(targetPolicyRow.locator('//a')); - await policiesPage.policyDetailsDiv.waitFor(); - }); - - await test.step('Delete the policy from the details page', async () => { - await policiesPage.deletePolicyFromDetailsPage(); - }); - - await test.step('Verify that the policy is deleted and no longer appears in the policies table', async () => { - await expect(targetPolicyRow).toBeHidden(); - }); - }); + test( + '[232288] Verify that user can create a recurring budget policy', + { tag: ['@fast', '@p2'] }, + async ({ policiesPage, policiesCreatePage }) => { + const policyName = `Recurring Budget ${Date.now()}`; + const budgetAmount = 1000; + const formattedAmount = formatCurrency(budgetAmount); + const filterOption = 'Active'; + + await test.step('Create Recurring Budget Policy', async () => { + await policiesPage.navigateToCreatePolicy(); + await policiesCreatePage.createRecurringBudgetPolicy(policyName, budgetAmount, policiesCreatePage.activityFilter, filterOption); + }); + + const targetPolicyRow = policiesPage.table.locator(`//td[.="${policyName}"]/ancestor::tr`); + + await test.step('Verify that the new policy is displayed in the policies table', async () => { + await targetPolicyRow.waitFor(); + await expect.soft(targetPolicyRow.locator('xpath=/td[1]')).toHaveText(policyName); + await expect.soft(targetPolicyRow.locator('xpath=/td[3]')).toHaveText(`Current month expenses must not exceed ${formattedAmount}.`); + await expect.soft(targetPolicyRow.locator('xpath=/td[4]')).toContainText(`Activity: ${filterOption}`); + }); + + await test.step('Navigate to the created policy details page', async () => { + await policiesPage.click(targetPolicyRow.locator('//a')); + await policiesPage.policyDetailsDiv.waitFor(); + }); + + await test.step('Verify policy details', async () => { + await expect.soft(policiesPage.policyDetailsDiv).toContainText(`Name: ${policyName}`); + await expect.soft(policiesPage.policyDetailsDiv).toContainText('Type: Recurring budget'); + await expect.soft(policiesPage.policyDetailsDiv).toContainText(`Current month expenses budget: ${formattedAmount}`); + await expect.soft(policiesPage.policyDetailsDiv).toContainText(`Activity: ${filterOption}`); + }); + } + ); + + test( + '[232289] Verify that user can create an expiring budget policy', + { tag: ['@fast', '@p2'] }, + async ({ policiesPage, policiesCreatePage }) => { + const policyName = `Expiring Budget ${Date.now()}`; + const budgetAmount = 500; + const formattedAmount = formatCurrency(budgetAmount); + const startDate = `${(new Date().getMonth() + 1).toString().padStart(2, '0')}/${new Date().getDate().toString().padStart(2, '0')}/${new Date().getFullYear()} 12:00 AM`; + + await test.step('Create Expiring Budget Policy', async () => { + await policiesPage.navigateToCreatePolicy(); + await policiesCreatePage.createExpiringBudgetPolicy(policyName, budgetAmount); + }); + + const targetPolicyRow = policiesPage.table.locator(`//td[.="${policyName}"]/ancestor::tr`); + + await test.step('Verify that the new policy is displayed in the policies table', async () => { + await targetPolicyRow.waitFor(); + await expect.soft(targetPolicyRow.locator('//td[1]')).toHaveText(policyName); + await expect + .soft(targetPolicyRow.locator('//td[3]')) + .toHaveText(`Total expenses from ${startDate} must not exceed ${formattedAmount}.`); + await expect.soft(targetPolicyRow.locator('//td[4]')).toHaveText('-'); + }); + + await test.step('Navigate to the created policy details page', async () => { + await policiesPage.click(targetPolicyRow.locator('//a')); + await policiesPage.policyDetailsDiv.waitFor(); + }); + + await test.step('Verify policy details', async () => { + await expect.soft(policiesPage.policyDetailsDiv).toContainText(`Name: ${policyName}`); + await expect.soft(policiesPage.policyDetailsDiv).toContainText('Type: Expiring budget'); + await expect.soft(policiesPage.policyDetailsDiv).toContainText(`Start date: ${startDate}`); + await expect.soft(policiesPage.policyDetailsDiv).toContainText(`Budget: ${formattedAmount}`); + await expect.soft(policiesPage.policyDetailsDiv).not.toContainText('Filters:'); + }); + } + ); + + test( + '[232290] Verify that user can delete a policy from the policy details page', + { tag: ['@fast', '@p2'] }, + async ({ policiesPage, policiesCreatePage }) => { + const policyName = `Policy To Be Deleted ${Date.now()}`; + const resourceCount = 5; + + await test.step('Create a policy to be deleted', async () => { + await policiesPage.navigateToCreatePolicy(); + await policiesCreatePage.createResourcePolicy(policyName, resourceCount); + }); + + const targetPolicyRow = policiesPage.table.locator(`//td[.="${policyName}"]/ancestor::tr`); + + await test.step('Navigate to the created policy details page', async () => { + await targetPolicyRow.waitFor(); + await policiesPage.click(targetPolicyRow.locator('//a')); + await policiesPage.policyDetailsDiv.waitFor(); + }); + + await test.step('Delete the policy from the details page', async () => { + await policiesPage.deletePolicyFromDetailsPage(); + }); + + await test.step('Verify that the policy is deleted and no longer appears in the policies table', async () => { + await expect(targetPolicyRow).toBeHidden(); + }); + } + ); }); test.describe('[MPT-16366] Mocked Policies Tests', { tag: ['@ui', '@policies'] }, () => { @@ -273,74 +284,100 @@ test.describe('[MPT-16366] Mocked Policies Tests', { tag: ['@ui', '@policies'] } }); }); - test('[232337] Verify that statuses are displayed correctly from each policy type when over and under limit', async ({ - policiesPage, - }) => { - expect(await policiesPage.getColorFromElement(policiesPage.resourceUnderLimitStatus)).toBe(policiesPage.successColor); - expect(await policiesPage.getColorFromElement(policiesPage.resourceOverLimitStatus)).toBe(policiesPage.errorColor); - expect(await policiesPage.getColorFromElement(policiesPage.recurringBudgetUnderLimitStatus)).toBe(policiesPage.successColor); - expect(await policiesPage.getColorFromElement(policiesPage.recurringBudgetOverLimitStatus)).toBe(policiesPage.errorColor); - expect(await policiesPage.getColorFromElement(policiesPage.expiringBudgetUnderLimitStatus)).toBe(policiesPage.successColor); - expect(await policiesPage.getColorFromElement(policiesPage.expiringBudgetOverLimitStatus)).toBe(policiesPage.errorColor); - }); - - test('[232338] Verify that expiring budget over limit displays violation in violation history table', async ({ policiesPage }) => { - await policiesPage.expiringBudgetOverLimitLink.click(); - await policiesPage.policyDetailsDiv.waitFor(); + test( + '[232337] Verify that statuses are displayed correctly from each policy type when over and under limit', + { tag: ['@fast', '@p2'] }, + async ({ policiesPage }) => { + expect(await policiesPage.getColorFromElement(policiesPage.resourceUnderLimitStatus)).toBe(policiesPage.successColor); + expect(await policiesPage.getColorFromElement(policiesPage.resourceOverLimitStatus)).toBe(policiesPage.errorColor); + expect(await policiesPage.getColorFromElement(policiesPage.recurringBudgetUnderLimitStatus)).toBe(policiesPage.successColor); + expect(await policiesPage.getColorFromElement(policiesPage.recurringBudgetOverLimitStatus)).toBe(policiesPage.errorColor); + expect(await policiesPage.getColorFromElement(policiesPage.expiringBudgetUnderLimitStatus)).toBe(policiesPage.successColor); + expect(await policiesPage.getColorFromElement(policiesPage.expiringBudgetOverLimitStatus)).toBe(policiesPage.errorColor); + } + ); + + test( + '[232338] Verify that expiring budget over limit displays violation in violation history table', + { tag: ['@fast', '@p2'] }, + async ({ policiesPage }) => { + await policiesPage.expiringBudgetOverLimitLink.click(); + await policiesPage.policyDetailsDiv.waitFor(); - const firstViolatedAtTableCell = policiesPage.table.locator('//td[1]'); - const firstBudgetActualExpensesTableCell = policiesPage.table.locator('//td[2]'); + const firstViolatedAtTableCell = policiesPage.table.locator('//td[1]'); + const firstBudgetActualExpensesTableCell = policiesPage.table.locator('//td[2]'); - await expect(policiesPage.policyViolationsHistoryHeading).toBeVisible(); - await expect(firstViolatedAtTableCell).toHaveText('01/08/2026 09:20 AM'); - await expect(firstBudgetActualExpensesTableCell).toHaveText('$1 ⟶ $48,646.2'); - }); + await expect(policiesPage.policyViolationsHistoryHeading).toBeVisible(); + await expect(firstViolatedAtTableCell).toHaveText('01/08/2026 09:20 AM'); + await expect(firstBudgetActualExpensesTableCell).toHaveText('$1 ⟶ $48,646.2'); + } + ); - test('[232339] Verify that expiring budget under limit shows no violations in violation history table', async ({ policiesPage }) => { - await policiesPage.expiringBudgetUnderLimitLink.click(); - await policiesPage.policyDetailsDiv.waitFor(); + test( + '[232339] Verify that expiring budget under limit shows no violations in violation history table', + { tag: ['@fast', '@p2'] }, + async ({ policiesPage }) => { + await policiesPage.expiringBudgetUnderLimitLink.click(); + await policiesPage.policyDetailsDiv.waitFor(); - await expect(policiesPage.policyViolationsHistoryHeading).toBeHidden(); - await expect(policiesPage.table).toBeHidden(); - }); + await expect(policiesPage.policyViolationsHistoryHeading).toBeHidden(); + await expect(policiesPage.table).toBeHidden(); + } + ); - test('[232340] Verify that recurring budget over limit displays violation in violation history table', async ({ policiesPage }) => { - await policiesPage.recurringBudgetOverLimitLink.click(); - await policiesPage.policyDetailsDiv.waitFor(); + test( + '[232340] Verify that recurring budget over limit displays violation in violation history table', + { tag: ['@fast', '@p2'] }, + async ({ policiesPage }) => { + await policiesPage.recurringBudgetOverLimitLink.click(); + await policiesPage.policyDetailsDiv.waitFor(); - const firstViolatedAtTableCell = policiesPage.table.locator('//td[1]'); - const firstBudgetActualExpensesTableCell = policiesPage.table.locator('//td[2]'); + const firstViolatedAtTableCell = policiesPage.table.locator('//td[1]'); + const firstBudgetActualExpensesTableCell = policiesPage.table.locator('//td[2]'); - await expect(policiesPage.policyViolationsHistoryHeading).toBeVisible(); - await expect(firstViolatedAtTableCell).toHaveText('01/08/2026 09:15 AM'); - await expect(firstBudgetActualExpensesTableCell).toHaveText('$10 ⟶ $48,646.2'); - }); + await expect(policiesPage.policyViolationsHistoryHeading).toBeVisible(); + await expect(firstViolatedAtTableCell).toHaveText('01/08/2026 09:15 AM'); + await expect(firstBudgetActualExpensesTableCell).toHaveText('$10 ⟶ $48,646.2'); + } + ); - test('[232341] Verify that recurring budget under limit shows no violations in violation history table', async ({ policiesPage }) => { - await policiesPage.recurringBudgetUnderLimitLink.click(); - await policiesPage.policyDetailsDiv.waitFor(); + test( + '[232341] Verify that recurring budget under limit shows no violations in violation history table', + { tag: ['@fast', '@p2'] }, + async ({ policiesPage }) => { + await policiesPage.recurringBudgetUnderLimitLink.click(); + await policiesPage.policyDetailsDiv.waitFor(); - await expect(policiesPage.policyViolationsHistoryHeading).toBeHidden(); - await expect(policiesPage.table).toBeHidden(); - }); + await expect(policiesPage.policyViolationsHistoryHeading).toBeHidden(); + await expect(policiesPage.table).toBeHidden(); + } + ); - test('[232342] Verify that resource quota over limit displays violation in violation history table', async ({ policiesPage }) => { - await policiesPage.resourceOverLimitLink.click(); - await policiesPage.policyDetailsDiv.waitFor(); + test( + '[232342] Verify that resource quota over limit displays violation in violation history table', + { tag: ['@fast', '@p2'] }, + async ({ policiesPage }) => { + await policiesPage.resourceOverLimitLink.click(); + await policiesPage.policyDetailsDiv.waitFor(); - const firstViolatedAtTableCell = policiesPage.table.locator('//td[1]'); - const firstQuotaActualResourceCountTableCell = policiesPage.table.locator('//td[2]'); + const firstViolatedAtTableCell = policiesPage.table.locator('//td[1]'); + const firstQuotaActualResourceCountTableCell = policiesPage.table.locator('//td[2]'); - await expect(policiesPage.policyViolationsHistoryHeading).toBeVisible(); - await expect(firstViolatedAtTableCell).toHaveText('01/08/2026 09:15 AM'); - await expect(firstQuotaActualResourceCountTableCell).toHaveText('1 ⟶ 3,012'); - }); + await expect(policiesPage.policyViolationsHistoryHeading).toBeVisible(); + await expect(firstViolatedAtTableCell).toHaveText('01/08/2026 09:15 AM'); + await expect(firstQuotaActualResourceCountTableCell).toHaveText('1 ⟶ 3,012'); + } + ); - test('[232343] Verify that resource quota under limit shows no violations in violation history table', async ({ policiesPage }) => { - await policiesPage.resourceUnderLimitLink.click(); - await policiesPage.policyDetailsDiv.waitFor(); + test( + '[232343] Verify that resource quota under limit shows no violations in violation history table', + { tag: ['@fast', '@p2'] }, + async ({ policiesPage }) => { + await policiesPage.resourceUnderLimitLink.click(); + await policiesPage.policyDetailsDiv.waitFor(); - await expect(policiesPage.policyViolationsHistoryHeading).toBeHidden(); - await expect(policiesPage.table).toBeHidden(); - }); + await expect(policiesPage.policyViolationsHistoryHeading).toBeHidden(); + await expect(policiesPage.table).toBeHidden(); + } + ); }); diff --git a/e2etests/tests/pools-tests.spec.ts b/e2etests/tests/pools-tests.spec.ts index 550867a1b..fe65bfe11 100644 --- a/e2etests/tests/pools-tests.spec.ts +++ b/e2etests/tests/pools-tests.spec.ts @@ -24,7 +24,7 @@ test.describe('[MPT-12743] Pools Tests', { tag: ['@ui', '@pools'] }, () => { await poolsPage.toggleExpandPool(); }); - test('[230911] Verify Pools page column selection', async ({ poolsPage }) => { + test('[230911] Verify Pools page column selection', { tag: ['@fast', '@p2'] }, async ({ poolsPage }) => { const defaultColumns = [ poolsPage.nameTableHeading, poolsPage.monthlyLimitTableHeading, @@ -81,57 +81,61 @@ test.describe('[MPT-12743] Pools Tests', { tag: ['@ui', '@pools'] }, () => { }); }); - test('[230912] Verify Organization limit, Pools Expenses and Forecast this month match totals in the table', async ({ poolsPage }) => { - // test.fail((await poolsPage.getPoolCount()) !== 1, `Expected 1 pool, but found ${await poolsPage.getPoolCount()}`); - - let organizationLimitValue: number; - let expensesThisMonthValue: number; - let forecastThisMonthValue: number; - - await test.step('Get Organization Limit, Expenses This Month, and Forecast This Month values', async () => { - organizationLimitValue = await poolsPage.getOrganizationLimitValue(); - expensesThisMonthValue = await poolsPage.getExpensesThisMonth(); - forecastThisMonthValue = await poolsPage.getForecastThisMonth(); - debugLog( - `Organization Limit: ${organizationLimitValue}, Expenses This Month: ${expensesThisMonthValue}, Forecast This Month: ${forecastThisMonthValue}` - ); - }); - - await test.step('Verify Organisation Limit matches the table', async () => { - if (organizationLimitValue === 0) { - debugLog('No organization limit set'); - await expect.soft(poolsPage.poolColumn2).toHaveText('-'); - } else { - const limit = await poolsPage.getPoolLimitFromTable(); - debugLog(`Organization Limit: ${organizationLimitValue}`); - expect.soft(limit).toEqual(organizationLimitValue); - } - }); - - await test.step('Verify Expenses This Month matches the table', async () => { - const expenses = await poolsPage.getExpensesThisMonthFromTable(); - expect.soft(expenses).toEqual(expensesThisMonthValue); - }); - - await test.step('Verify Forecast This Month matches the table', async () => { - const forecast = await poolsPage.getForecastThisMonthFromTable(); - expect.soft(forecast).toEqual(forecastThisMonthValue); - }); - - await test.step('Verify sub-pools expenses match total pool expenses', async () => { - const subPoolsExpenses = await poolsPage.sumSubPoolTotals('expenses this month'); - debugLog(`Sub-pools expenses: ${subPoolsExpenses}`); - expect.soft(isWithinRoundingDrift(subPoolsExpenses, expensesThisMonthValue, 0.0025)).toBe(true); - }); - - await test.step('Verify sub-pools forecast match total pool forecast', async () => { - const subPoolsForecast = await poolsPage.sumSubPoolTotals('forecast this month'); - debugLog(`Sub-pools forecast: ${subPoolsForecast}`); - expect.soft(isWithinRoundingDrift(subPoolsForecast, forecastThisMonthValue, 0.0025)).toBe(true); - }); - }); - - test('[230913] Verify Organisation Limit functionality - limit not set', async ({ poolsPage }) => { + test( + '[230912] Verify Organization limit, Pools Expenses and Forecast this month match totals in the table', + { tag: ['@fast', '@p2'] }, + async ({ poolsPage }) => { + // test.fail((await poolsPage.getPoolCount()) !== 1, `Expected 1 pool, but found ${await poolsPage.getPoolCount()}`); + + let organizationLimitValue: number; + let expensesThisMonthValue: number; + let forecastThisMonthValue: number; + + await test.step('Get Organization Limit, Expenses This Month, and Forecast This Month values', async () => { + organizationLimitValue = await poolsPage.getOrganizationLimitValue(); + expensesThisMonthValue = await poolsPage.getExpensesThisMonth(); + forecastThisMonthValue = await poolsPage.getForecastThisMonth(); + debugLog( + `Organization Limit: ${organizationLimitValue}, Expenses This Month: ${expensesThisMonthValue}, Forecast This Month: ${forecastThisMonthValue}` + ); + }); + + await test.step('Verify Organisation Limit matches the table', async () => { + if (organizationLimitValue === 0) { + debugLog('No organization limit set'); + await expect.soft(poolsPage.poolColumn2).toHaveText('-'); + } else { + const limit = await poolsPage.getPoolLimitFromTable(); + debugLog(`Organization Limit: ${organizationLimitValue}`); + expect.soft(limit).toEqual(organizationLimitValue); + } + }); + + await test.step('Verify Expenses This Month matches the table', async () => { + const expenses = await poolsPage.getExpensesThisMonthFromTable(); + expect.soft(expenses).toEqual(expensesThisMonthValue); + }); + + await test.step('Verify Forecast This Month matches the table', async () => { + const forecast = await poolsPage.getForecastThisMonthFromTable(); + expect.soft(forecast).toEqual(forecastThisMonthValue); + }); + + await test.step('Verify sub-pools expenses match total pool expenses', async () => { + const subPoolsExpenses = await poolsPage.sumSubPoolTotals('expenses this month'); + debugLog(`Sub-pools expenses: ${subPoolsExpenses}`); + expect.soft(isWithinRoundingDrift(subPoolsExpenses, expensesThisMonthValue, 0.0025)).toBe(true); + }); + + await test.step('Verify sub-pools forecast match total pool forecast', async () => { + const subPoolsForecast = await poolsPage.sumSubPoolTotals('forecast this month'); + debugLog(`Sub-pools forecast: ${subPoolsForecast}`); + expect.soft(isWithinRoundingDrift(subPoolsForecast, forecastThisMonthValue, 0.0025)).toBe(true); + }); + } + ); + + test('[230913] Verify Organisation Limit functionality - limit not set', { tag: ['@fast', '@p2'] }, async ({ poolsPage }) => { test.fail((await poolsPage.getPoolCount()) !== 1, `Expected 1 pool, but found ${await poolsPage.getPoolCount()}`); await test.step('Remove organisation limit if it is set.', async () => { @@ -155,124 +159,140 @@ test.describe('[MPT-12743] Pools Tests', { tag: ['@ui', '@pools'] }, () => { }); }); - test('[230914] Verify Organisation Limit functionality - expenses are less than 90% of limit', async ({ poolsPage }) => { - test.fail((await poolsPage.getPoolCount()) !== 1, `Expected 1 pool, but found ${await poolsPage.getPoolCount()}`); + test( + '[230914] Verify Organisation Limit functionality - expenses are less than 90% of limit', + { tag: ['@fast', '@p2'] }, + async ({ poolsPage }) => { + test.fail((await poolsPage.getPoolCount()) !== 1, `Expected 1 pool, but found ${await poolsPage.getPoolCount()}`); - const expensesThisMonth = await poolsPage.getExpensesThisMonth(); - test.skip(expensesThisMonth <= 100, 'Skipping test as it requires expenses to be greater than 100'); - const forecastThisMonth = await poolsPage.getForecastThisMonth(); - const organizationLimit = Math.ceil(expensesThisMonth / 0.89); //under 90% of the expenses + const expensesThisMonth = await poolsPage.getExpensesThisMonth(); + test.skip(expensesThisMonth <= 100, 'Skipping test as it requires expenses to be greater than 100'); + const forecastThisMonth = await poolsPage.getForecastThisMonth(); + const organizationLimit = Math.ceil(expensesThisMonth / 0.89); //under 90% of the expenses - await test.step('Set organization limit to an integer where the expenses are less than the 90% of the limit', async () => { - await poolsPage.editPoolMonthlyLimit(organizationLimit); - debugLog(`Set organization limit to ${organizationLimit}`); - }); + await test.step('Set organization limit to an integer where the expenses are less than the 90% of the limit', async () => { + await poolsPage.editPoolMonthlyLimit(organizationLimit); + debugLog(`Set organization limit to ${organizationLimit}`); + }); - await test.step('Assert Pools page elements displayed correctly when limit set', async () => { - await expect.soft(poolsPage.exceededLimitCard).toBeHidden(); - expect.soft(await poolsPage.getColorFromElement(poolsPage.expensesCard)).toBe(poolsPage.successColor); - await expect.soft(poolsPage.expensesThisMonthCheckIcon).toBeVisible(); + await test.step('Assert Pools page elements displayed correctly when limit set', async () => { + await expect.soft(poolsPage.exceededLimitCard).toBeHidden(); + expect.soft(await poolsPage.getColorFromElement(poolsPage.expensesCard)).toBe(poolsPage.successColor); + await expect.soft(poolsPage.expensesThisMonthCheckIcon).toBeVisible(); - if (forecastThisMonth > organizationLimit) { + if (forecastThisMonth > organizationLimit) { + expect.soft(await poolsPage.getColorFromElement(poolsPage.forecastCard)).toBe(poolsPage.errorColor); + await expect.soft(poolsPage.forecastThisMonthCancelIcon).toBeVisible(); + expect.soft(await poolsPage.poolTableRow.getAttribute('style')).toContain(`border-left: 4px solid ${poolsPage.warningColor};`); + expect.soft(await poolsPage.getColorFromElement(poolsPage.column4TextSpan)).toBe(poolsPage.warningColor); + } + expect.soft((await poolsPage.poolColumn2.textContent()).replace(/\D/g, '')).toBe(organizationLimit.toString()); + expect.soft(await poolsPage.getColorFromElement(poolsPage.column3TextDiv)).toBe(poolsPage.successColor); + }); + } + ); + + test( + '[230915] Verify Organisation Limit functionality - expenses are greater than 90% of limit', + { tag: ['@fast', '@p2'] }, + async ({ poolsPage }) => { + test.fail((await poolsPage.getPoolCount()) !== 1, `Expected 1 pool, but found ${await poolsPage.getPoolCount()}`); + + const expensesThisMonth = await poolsPage.getExpensesThisMonth(); + const organizationLimit = Math.ceil(expensesThisMonth / 0.91); + const forecastThisMonth = await poolsPage.getForecastThisMonth(); + + await test.step('Set organization limit to an integer where the expenses is more than the 90% of the limit', async () => { + await poolsPage.editPoolMonthlyLimit(organizationLimit); + debugLog(`Set organization limit to ${organizationLimit}`); + }); + + await test.step('Assert Pools page elements displayed correctly when limit set', async () => { + await expect.soft(poolsPage.exceededLimitCard).toBeHidden(); + expect.soft(await poolsPage.getColorFromElement(poolsPage.expensesCard)).toBe(poolsPage.warningColor); + await expect.soft(poolsPage.expensesThisMonthWarningIcon).toBeVisible(); + if (forecastThisMonth > organizationLimit) { + expect.soft(await poolsPage.getColorFromElement(poolsPage.forecastCard)).toBe(poolsPage.errorColor); + await expect.soft(poolsPage.forecastThisMonthCancelIcon).toBeVisible(); + expect.soft(await poolsPage.poolTableRow.getAttribute('style')).toContain(`border-left: 4px solid ${poolsPage.warningColor};`); + expect.soft(await poolsPage.getColorFromElement(poolsPage.column4TextSpan)).toBe(poolsPage.warningColor); + } else { + expect.soft(await poolsPage.getColorFromElement(poolsPage.forecastCard)).toBe(poolsPage.warningColor); + await expect.soft(poolsPage.forecastThisMonthWarningIcon).toBeVisible(); + expect.soft(await poolsPage.getColorFromElement(poolsPage.column4TextSpan)).toBe(poolsPage.infoColor); + } + expect.soft((await poolsPage.poolColumn2.textContent()).replace(/\D/g, '')).toBe(organizationLimit.toString()); + expect.soft(await poolsPage.getColorFromElement(poolsPage.column3TextDiv)).toBe(poolsPage.successColor); + }); + } + ); + + test( + '[230916] Verify Organisation Limit functionality - limit set lower than expenses this month', + { tag: ['@fast', '@p2'] }, + async ({ poolsPage }) => { + test.fail((await poolsPage.getPoolCount()) !== 1, `Expected 1 pool, but found ${await poolsPage.getPoolCount()}`); + + const expensesThisMonth = await poolsPage.getExpensesThisMonth(); + test.skip(expensesThisMonth <= 1, 'Skipping test as it requires expenses to be greater than 1'); + const organizationLimit: number = Math.round(expensesThisMonth - 1); + + await test.step('Set organization limit to an integer below the current expenses this month', async () => { + await poolsPage.editPoolMonthlyLimit(organizationLimit); + debugLog(`Set organization limit to ${organizationLimit}`); + }); + + await test.step('Assert Pools page elements displayed correctly when limit set below expenses this month', async () => { + const overLimit = await poolsPage.getSpentOverLimitValue(); + const calculatedOverLimit = parseFloat((expensesThisMonth - organizationLimit).toFixed(2)); + + expect.soft(overLimit).toBe(calculatedOverLimit); + expect.soft(await poolsPage.getExceededLimitValue()).toBe(1); + expect.soft(await poolsPage.getColorFromElement(poolsPage.exceededLimitCard)).toBe(poolsPage.errorColor); + await expect.soft(poolsPage.exceededLimitCancelIcon).toBeVisible(); + expect.soft(await poolsPage.getColorFromElement(poolsPage.expensesCard)).toBe(poolsPage.errorColor); + await expect.soft(poolsPage.expensesThisMonthCancelIcon).toBeVisible(); expect.soft(await poolsPage.getColorFromElement(poolsPage.forecastCard)).toBe(poolsPage.errorColor); await expect.soft(poolsPage.forecastThisMonthCancelIcon).toBeVisible(); - expect.soft(await poolsPage.poolTableRow.getAttribute('style')).toContain(`border-left: 4px solid ${poolsPage.warningColor};`); + expect.soft(await poolsPage.poolTableRow.getAttribute('style')).toContain(`border-left: 4px solid ${poolsPage.errorColor};`); + expect.soft(await poolsPage.getColorFromElement(poolsPage.column3TextSpan)).toBe(poolsPage.errorColor); expect.soft(await poolsPage.getColorFromElement(poolsPage.column4TextSpan)).toBe(poolsPage.warningColor); - } - expect.soft((await poolsPage.poolColumn2.textContent()).replace(/\D/g, '')).toBe(organizationLimit.toString()); - expect.soft(await poolsPage.getColorFromElement(poolsPage.column3TextDiv)).toBe(poolsPage.successColor); - }); - }); - - test('[230915] Verify Organisation Limit functionality - expenses are greater than 90% of limit', async ({ poolsPage }) => { - test.fail((await poolsPage.getPoolCount()) !== 1, `Expected 1 pool, but found ${await poolsPage.getPoolCount()}`); - - const expensesThisMonth = await poolsPage.getExpensesThisMonth(); - const organizationLimit = Math.ceil(expensesThisMonth / 0.91); - const forecastThisMonth = await poolsPage.getForecastThisMonth(); - - await test.step('Set organization limit to an integer where the expenses is more than the 90% of the limit', async () => { - await poolsPage.editPoolMonthlyLimit(organizationLimit); - debugLog(`Set organization limit to ${organizationLimit}`); - }); - - await test.step('Assert Pools page elements displayed correctly when limit set', async () => { - await expect.soft(poolsPage.exceededLimitCard).toBeHidden(); - expect.soft(await poolsPage.getColorFromElement(poolsPage.expensesCard)).toBe(poolsPage.warningColor); - await expect.soft(poolsPage.expensesThisMonthWarningIcon).toBeVisible(); - if(forecastThisMonth > organizationLimit){ + }); + } + ); + + test( + '[230917] Verify Organisation Limit functionality - limit set lower than forecast', + { tag: ['@fast', '@p2'] }, + async ({ poolsPage }) => { + const expensesThisMonth = await poolsPage.getExpensesThisMonth(); + const forecastThisMonth = await poolsPage.getForecastThisMonth(); + test.skip(expensesThisMonth <= 1, 'Skipping test as it requires expenses to be greater than 1'); + const organizationLimit: number = Math.round(forecastThisMonth - 1); + + await test.step('Set organization limit to an integer below the current forecast this month', async () => { + await poolsPage.editPoolMonthlyLimit(organizationLimit); + debugLog(`Set organization limit to ${organizationLimit}`); + }); + + await test.step('Assert Pools page elements displayed correctly when limit set below forecast this month', async () => { + await expect.soft(poolsPage.exceededLimitCard).toBeHidden(); expect.soft(await poolsPage.getColorFromElement(poolsPage.forecastCard)).toBe(poolsPage.errorColor); - await expect.soft(poolsPage.forecastThisMonthCancelIcon).toBeVisible(); + if (expensesThisMonth >= Math.round(organizationLimit * 0.9)) { + expect.soft(await poolsPage.getColorFromElement(poolsPage.expensesCard)).toBe(poolsPage.warningColor); + await expect.soft(poolsPage.expensesThisMonthWarningIcon).toBeVisible(); + } else { + expect.soft(await poolsPage.getColorFromElement(poolsPage.expensesCard)).toBe(poolsPage.successColor); + await expect.soft(poolsPage.expensesThisMonthCheckIcon).toBeVisible(); + } expect.soft(await poolsPage.poolTableRow.getAttribute('style')).toContain(`border-left: 4px solid ${poolsPage.warningColor};`); + expect.soft(await poolsPage.getColorFromElement(poolsPage.column3TextDiv)).toBe(poolsPage.successColor); expect.soft(await poolsPage.getColorFromElement(poolsPage.column4TextSpan)).toBe(poolsPage.warningColor); - } else { - expect.soft(await poolsPage.getColorFromElement(poolsPage.forecastCard)).toBe(poolsPage.warningColor); - await expect.soft(poolsPage.forecastThisMonthWarningIcon).toBeVisible(); - expect.soft(await poolsPage.getColorFromElement(poolsPage.column4TextSpan)).toBe(poolsPage.infoColor); - } - expect.soft((await poolsPage.poolColumn2.textContent()).replace(/\D/g, '')).toBe(organizationLimit.toString()); - expect.soft(await poolsPage.getColorFromElement(poolsPage.column3TextDiv)).toBe(poolsPage.successColor); - }); - }); + }); + } + ); - test('[230916] Verify Organisation Limit functionality - limit set lower than expenses this month', async ({ poolsPage }) => { - test.fail((await poolsPage.getPoolCount()) !== 1, `Expected 1 pool, but found ${await poolsPage.getPoolCount()}`); - - const expensesThisMonth = await poolsPage.getExpensesThisMonth(); - test.skip(expensesThisMonth <= 1, 'Skipping test as it requires expenses to be greater than 1'); - const organizationLimit: number = Math.round(expensesThisMonth - 1); - - await test.step('Set organization limit to an integer below the current expenses this month', async () => { - await poolsPage.editPoolMonthlyLimit(organizationLimit); - debugLog(`Set organization limit to ${organizationLimit}`); - }); - - await test.step('Assert Pools page elements displayed correctly when limit set below expenses this month', async () => { - const overLimit = await poolsPage.getSpentOverLimitValue(); - const calculatedOverLimit = parseFloat((expensesThisMonth - organizationLimit).toFixed(2)); - - expect.soft(overLimit).toBe(calculatedOverLimit); - expect.soft(await poolsPage.getExceededLimitValue()).toBe(1); - expect.soft(await poolsPage.getColorFromElement(poolsPage.exceededLimitCard)).toBe(poolsPage.errorColor); - await expect.soft(poolsPage.exceededLimitCancelIcon).toBeVisible(); - expect.soft(await poolsPage.getColorFromElement(poolsPage.expensesCard)).toBe(poolsPage.errorColor); - await expect.soft(poolsPage.expensesThisMonthCancelIcon).toBeVisible(); - expect.soft(await poolsPage.getColorFromElement(poolsPage.forecastCard)).toBe(poolsPage.errorColor); - await expect.soft(poolsPage.forecastThisMonthCancelIcon).toBeVisible(); - expect.soft(await poolsPage.poolTableRow.getAttribute('style')).toContain(`border-left: 4px solid ${poolsPage.errorColor};`); - expect.soft(await poolsPage.getColorFromElement(poolsPage.column3TextSpan)).toBe(poolsPage.errorColor); - expect.soft(await poolsPage.getColorFromElement(poolsPage.column4TextSpan)).toBe(poolsPage.warningColor); - }); - }); - - test('[230917] Verify Organisation Limit functionality - limit set lower than forecast', async ({ poolsPage }) => { - const expensesThisMonth = await poolsPage.getExpensesThisMonth(); - const forecastThisMonth = await poolsPage.getForecastThisMonth(); - test.skip(expensesThisMonth <= 1, 'Skipping test as it requires expenses to be greater than 1'); - const organizationLimit: number = Math.round(forecastThisMonth - 1); - - await test.step('Set organization limit to an integer below the current forecast this month', async () => { - await poolsPage.editPoolMonthlyLimit(organizationLimit); - debugLog(`Set organization limit to ${organizationLimit}`); - }); - - await test.step('Assert Pools page elements displayed correctly when limit set below forecast this month', async () => { - await expect.soft(poolsPage.exceededLimitCard).toBeHidden(); - expect.soft(await poolsPage.getColorFromElement(poolsPage.forecastCard)).toBe(poolsPage.errorColor); - if (expensesThisMonth >= Math.round(organizationLimit * 0.9)) { - expect.soft(await poolsPage.getColorFromElement(poolsPage.expensesCard)).toBe(poolsPage.warningColor); - await expect.soft(poolsPage.expensesThisMonthWarningIcon).toBeVisible(); - } else { - expect.soft(await poolsPage.getColorFromElement(poolsPage.expensesCard)).toBe(poolsPage.successColor); - await expect.soft(poolsPage.expensesThisMonthCheckIcon).toBeVisible(); - } - expect.soft(await poolsPage.poolTableRow.getAttribute('style')).toContain(`border-left: 4px solid ${poolsPage.warningColor};`); - expect.soft(await poolsPage.getColorFromElement(poolsPage.column3TextDiv)).toBe(poolsPage.successColor); - expect.soft(await poolsPage.getColorFromElement(poolsPage.column4TextSpan)).toBe(poolsPage.warningColor); - }); - }); - - test('[230918] Verify sub-pool monthly limit behaviour', async ({ poolsPage }) => { + test('[230918] Verify sub-pool monthly limit behaviour', { tag: ['@fast', '@p2'] }, async ({ poolsPage }) => { test.fail((await poolsPage.getPoolCount()) !== 1, `Expected 1 pool, but found ${await poolsPage.getPoolCount()}`); test.setTimeout(75000); @@ -312,7 +332,7 @@ test.describe('[MPT-12743] Pools Tests', { tag: ['@ui', '@pools'] }, () => { }); }); - test('[230919] Verify pool exceeded count and expand requiring attention', { tag: '@p1' }, async ({ poolsPage }) => { + test('[230919] Verify pool exceeded count and expand requiring attention', { tag: ['@fast', '@p1'] }, async ({ poolsPage }) => { // test.fail((await poolsPage.getPoolCount()) !== 1, `Expected 1 pool, but found ${await poolsPage.getPoolCount()}`); test.setTimeout(75000); @@ -356,42 +376,46 @@ test.describe('[MPT-12743] Pools Tests', { tag: ['@ui', '@pools'] }, () => { }); }); - test('[232865] Verify that updating limits of pools and sub-pools is recorded in the logs', async ({ poolsPage, eventsPage }) => { - test.setTimeout(60000); - let timestamp: string; - const randomNumber = Math.floor(Math.random() * 1_000); - - await test.step('Modify a pool limit and verify event log', async () => { - timestamp = getCurrentUTCTimestamp(); - await poolsPage.editPoolMonthlyLimit(randomNumber); - await eventsPage.navigateToURL(); - const poolEvent = eventsPage.getEventByMultipleTexts([ - `Pool ${getEnvironmentTestOrgName()}`, - `updated with parameters: limit: ${randomNumber}`, - ]); - const poolEventText = await poolEvent.textContent(); - debugLog(`Pool event log text: ${poolEventText}`); - expect.soft(poolEventText).toContain(`${timestamp} UTC`); - expect.soft(poolEventText).toContain(`limit: ${randomNumber},`); - expect.soft(poolEventText).toContain(`(${process.env.DEFAULT_USER_EMAIL})`); - }); - - await test.step('Add a sub-pool limit and verify event log', async () => { - await poolsPage.navigateToURL(); - timestamp = getCurrentUTCTimestamp(); - await poolsPage.toggleExpandPool(); - const subPoolName = await poolsPage.getSubPoolName(1); - await poolsPage.editSubPoolMonthlyLimit(randomNumber, true, 1, true); - await eventsPage.navigateToURL(); - const subPoolEvent = eventsPage.getEventByMultipleTexts([ - `Pool ${subPoolName}`, - `updated with parameters: name:`, - `limit: ${randomNumber},`, - ]); - const subPoolEventText = await subPoolEvent.textContent(); - debugLog(`Sub-pool event log text: ${subPoolEventText}`); - expect.soft(subPoolEventText).toContain(`${timestamp} UTC`); - expect.soft(subPoolEventText).toContain(`(${process.env.DEFAULT_USER_EMAIL})`); - }); - }); + test( + '[232865] Verify that updating limits of pools and sub-pools is recorded in the logs', + { tag: ['@fast', '@p2'] }, + async ({ poolsPage, eventsPage }) => { + test.setTimeout(60000); + let timestamp: string; + const randomNumber = Math.floor(Math.random() * 1_000); + + await test.step('Modify a pool limit and verify event log', async () => { + timestamp = getCurrentUTCTimestamp(); + await poolsPage.editPoolMonthlyLimit(randomNumber); + await eventsPage.navigateToURL(); + const poolEvent = eventsPage.getEventByMultipleTexts([ + `Pool ${getEnvironmentTestOrgName()}`, + `updated with parameters: limit: ${randomNumber}`, + ]); + const poolEventText = await poolEvent.textContent(); + debugLog(`Pool event log text: ${poolEventText}`); + expect.soft(poolEventText).toContain(`${timestamp} UTC`); + expect.soft(poolEventText).toContain(`limit: ${randomNumber},`); + expect.soft(poolEventText).toContain(`(${process.env.DEFAULT_USER_EMAIL})`); + }); + + await test.step('Add a sub-pool limit and verify event log', async () => { + await poolsPage.navigateToURL(); + timestamp = getCurrentUTCTimestamp(); + await poolsPage.toggleExpandPool(); + const subPoolName = await poolsPage.getSubPoolName(1); + await poolsPage.editSubPoolMonthlyLimit(randomNumber, true, 1, true); + await eventsPage.navigateToURL(); + const subPoolEvent = eventsPage.getEventByMultipleTexts([ + `Pool ${subPoolName}`, + `updated with parameters: name:`, + `limit: ${randomNumber},`, + ]); + const subPoolEventText = await subPoolEvent.textContent(); + debugLog(`Sub-pool event log text: ${subPoolEventText}`); + expect.soft(subPoolEventText).toContain(`${timestamp} UTC`); + expect.soft(subPoolEventText).toContain(`(${process.env.DEFAULT_USER_EMAIL})`); + }); + } + ); }); diff --git a/e2etests/tests/recommendations-tests.spec.ts b/e2etests/tests/recommendations-tests.spec.ts index 8bb8001a5..e393a1f00 100644 --- a/e2etests/tests/recommendations-tests.spec.ts +++ b/e2etests/tests/recommendations-tests.spec.ts @@ -18,7 +18,7 @@ test.describe('[MPT-11310] Recommendations page tests', { tag: ['@ui', '@recomme await recommendationsPage.selectCategory('All'); }); - test('[230511] Verify Card total savings match possible monthly savings', { tag: '@p1' }, async ({ recommendationsPage }) => { + test('[230511] Verify Card total savings match possible monthly savings', { tag: ['@fast', '@p1'] }, async ({ recommendationsPage }) => { let possibleMonthlySavings: number; let cardTotalSavings: number; @@ -37,7 +37,7 @@ test.describe('[MPT-11310] Recommendations page tests', { tag: ['@ui', '@recomme }); }); - test('[230597] Verify Data Source selection works correctly', async ({ recommendationsPage }) => { + test('[230597] Verify Data Source selection works correctly', { tag: ['@fast', '@p2'] }, async ({ recommendationsPage }) => { const dataSource = process.env.USE_LIVE_DEMO === 'true' ? 'Azure QA' : 'Marketplace (Dev)'; await test.step(`Select data source: ${dataSource}`, async () => { @@ -58,41 +58,42 @@ test.describe('[MPT-11310] Recommendations page tests', { tag: ['@ui', '@recomme }); //It appears that environments don't have the correct permissions to run S3 Duplicate checks, so marking this as FIXME for now. - test.fixme('[230513] Verify S3 Duplicate Possible monthly savings matches that on S3 Duplicate Finder page', async ({ - recommendationsPage, - s3DuplicateFinder, - }) => { - let captionText: string; - await test.step('Determine whether duplicate check run is completed', async () => { - captionText = await recommendationsPage.s3DuplicatesCaption.textContent(); - }); + test.fixme( + '[230513] Verify S3 Duplicate Possible monthly savings matches that on S3 Duplicate Finder page', + { tag: ['@fast', '@p2'] }, + async ({ recommendationsPage, s3DuplicateFinder }) => { + let captionText: string; + await test.step('Determine whether duplicate check run is completed', async () => { + captionText = await recommendationsPage.s3DuplicatesCaption.textContent(); + }); - if (captionText === 'No successfully completed checks') { - await test.step('Verify behaviour is correct if no completed duplicate checks', async () => { - debugLog('No successfully completed checks found.'); + if (captionText === 'No successfully completed checks') { + await test.step('Verify behaviour is correct if no completed duplicate checks', async () => { + debugLog('No successfully completed checks found.'); - // eslint-disable-next-line playwright/no-nested-step - await test.step('Verify S3 Duplicate Finder table shows no duplicate checks message', async () => { - await recommendationsPage.clickS3DuplicatesCard(); - await expect - .soft(s3DuplicateFinder.tableFirstRow) - .toHaveText('No duplicate checks, create a new one using the "Run check" button.'); + // eslint-disable-next-line playwright/no-nested-step + await test.step('Verify S3 Duplicate Finder table shows no duplicate checks message', async () => { + await recommendationsPage.clickS3DuplicatesCard(); + await expect + .soft(s3DuplicateFinder.tableFirstRow) + .toHaveText('No duplicate checks, create a new one using the "Run check" button.'); + }); }); + return; + } + await test.step('Compare S3 Duplicates possible savings matches total of savings on s3 Duplicate finder table', async () => { + const possibleMonthlySavings = await recommendationsPage.getS3DuplicateFinderPossibleMonthlySavingsValue(); + await recommendationsPage.clickS3DuplicatesCard(); + const s3DuplicateFinderPageSavings = await s3DuplicateFinder.getSavingsFromTable(); + + debugLog(`Possible Monthly Savings: ${possibleMonthlySavings}`); + debugLog(`S3 Duplicate Finder Page Savings: ${s3DuplicateFinderPageSavings}`); + expect(s3DuplicateFinderPageSavings).toBeCloseTo(possibleMonthlySavings, 0); }); - return; } - await test.step('Compare S3 Duplicates possible savings matches total of savings on s3 Duplicate finder table', async () => { - const possibleMonthlySavings = await recommendationsPage.getS3DuplicateFinderPossibleMonthlySavingsValue(); - await recommendationsPage.clickS3DuplicatesCard(); - const s3DuplicateFinderPageSavings = await s3DuplicateFinder.getSavingsFromTable(); - - debugLog(`Possible Monthly Savings: ${possibleMonthlySavings}`); - debugLog(`S3 Duplicate Finder Page Savings: ${s3DuplicateFinderPageSavings}`); - expect(s3DuplicateFinderPageSavings).toBeCloseTo(possibleMonthlySavings, 0); - }); - }); + ); - test('[230514] Verify Search functionality works correctly', async ({ recommendationsPage }) => { + test('[230514] Verify Search functionality works correctly', { tag: ['@fast', '@p2'] }, async ({ recommendationsPage }) => { await recommendationsPage.searchByName('Public'); await recommendationsPage.allCardHeadings.last().waitFor(); @@ -105,7 +106,7 @@ test.describe('[MPT-11310] Recommendations page tests', { tag: ['@ui', '@recomme test( '[230598] Verify only the correct applicable services are displayed for SWO Customisation', - { tag: '@p1' }, + { tag: ['@fast', '@p1'] }, async ({ recommendationsPage }) => { await test.step('Verify applicable services combo box options shows expected items', async () => { await recommendationsPage.applicableServices.click(); @@ -227,11 +228,15 @@ test.describe('[MPT-11310] Recommendations page tests', { tag: ['@ui', '@recomme ]; // eslint-disable-next-line playwright/expect-expect - test('[230515] Verify all expected cards are present when All category selected', async ({ recommendationsPage }) => { - await verifyCardsAndTable(recommendationsPage, 'All', allExpectedCardHeadings); - }); + test( + '[230515] Verify all expected cards are present when All category selected', + { tag: ['@fast', '@p2'] }, + async ({ recommendationsPage }) => { + await verifyCardsAndTable(recommendationsPage, 'All', allExpectedCardHeadings); + } + ); - test(`[231467] Verify no cards are displaying errors`, async ({ recommendationsPage }) => { + test(`[231467] Verify no cards are displaying errors`, { tag: ['@fast', '@p2'] }, async ({ recommendationsPage }) => { await recommendationsPage.selectCategory('All'); await recommendationsPage.allCardHeadings.last().waitFor(); @@ -249,117 +254,131 @@ test.describe('[MPT-11310] Recommendations page tests', { tag: ['@ui', '@recomme expect(errorCount, 'No cards should be displaying errors').toBe(0); }); // eslint-disable-next-line playwright/expect-expect - test('[230518] Verify all expected cards are present when Savings category selected', async ({ recommendationsPage }) => { - const expectedCardHeadings = [ - 'Abandoned Amazon S3 buckets', - 'Abandoned Images', - 'Abandoned instances', - 'Abandoned Kinesis Streams', - 'Abandoned Load Balancers', - 'Instances eligible for generation upgrade', - 'Instances for shutdown', - 'Instances with migration opportunities', - 'Instances with Spot (Preemptible) opportunities', - 'Instances with Subscription opportunities', - 'Intelligent Tiering', - 'Not attached Volumes', - 'Not deallocated Instances', - 'Obsolete IPs', - 'Obsolete snapshot chains', - 'Obsolete snapshots', - 'Reserved instances opportunities', - 'Snapshots with non-used Images', - 'Underutilized instances', - 'Underutilized RDS Instances', - ]; - await verifyCardsAndTable(recommendationsPage, 'Savings', expectedCardHeadings); - }); + test( + '[230518] Verify all expected cards are present when Savings category selected', + { tag: ['@fast', '@p2'] }, + async ({ recommendationsPage }) => { + const expectedCardHeadings = [ + 'Abandoned Amazon S3 buckets', + 'Abandoned Images', + 'Abandoned instances', + 'Abandoned Kinesis Streams', + 'Abandoned Load Balancers', + 'Instances eligible for generation upgrade', + 'Instances for shutdown', + 'Instances with migration opportunities', + 'Instances with Spot (Preemptible) opportunities', + 'Instances with Subscription opportunities', + 'Intelligent Tiering', + 'Not attached Volumes', + 'Not deallocated Instances', + 'Obsolete IPs', + 'Obsolete snapshot chains', + 'Obsolete snapshots', + 'Reserved instances opportunities', + 'Snapshots with non-used Images', + 'Underutilized instances', + 'Underutilized RDS Instances', + ]; + await verifyCardsAndTable(recommendationsPage, 'Savings', expectedCardHeadings); + } + ); // eslint-disable-next-line playwright/expect-expect - test('[230519] Verify all expected cards are present when Security category selected', async ({ recommendationsPage }) => { - const expectedCardHeadings = [ - 'IAM users with unused console access', - 'Inactive IAM users', - 'Resources with insecure Security Groups settings', - 'Public S3 buckets', - ]; - await verifyCardsAndTable(recommendationsPage, 'Security', expectedCardHeadings); - }); + test( + '[230519] Verify all expected cards are present when Security category selected', + { tag: ['@fast', '@p2'] }, + async ({ recommendationsPage }) => { + const expectedCardHeadings = [ + 'IAM users with unused console access', + 'Inactive IAM users', + 'Resources with insecure Security Groups settings', + 'Public S3 buckets', + ]; + await verifyCardsAndTable(recommendationsPage, 'Security', expectedCardHeadings); + } + ); - test('[230520] Verify all cards display critical icon when Critical category selected', async ({ recommendationsPage }) => { - let count: number; - let actualHeadings: string[]; - let criticalIconCount: number; + test( + '[230520] Verify all cards display critical icon when Critical category selected', + { tag: ['@fast', '@p2'] }, + async ({ recommendationsPage }) => { + let count: number; + let actualHeadings: string[]; + let criticalIconCount: number; - 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 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(); - debugLog(`Actual heading texts: ${actualHeadings}`); - debugLog(`Number of card headings found: ${count}`); - criticalIconCount = await recommendationsPage.allCriticalIcon.count(); - debugLog(`Number of critical icons found: ${criticalIconCount}`); - expect.soft(criticalIconCount).toBe(count); - }); + await recommendationsPage.allCardHeadings.last().waitFor(); + count = await recommendationsPage.allCardHeadings.count(); + actualHeadings = await recommendationsPage.allCardHeadings.allTextContents(); + debugLog(`Actual heading texts: ${actualHeadings}`); + debugLog(`Number of card headings found: ${count}`); + criticalIconCount = await recommendationsPage.allCriticalIcon.count(); + debugLog(`Number of critical icons found: ${criticalIconCount}`); + expect.soft(criticalIconCount).toBe(count); + }); - await test.step('Switch to table view and verify row count matches card count', async () => { - await recommendationsPage.clickTableButton(); - await recommendationsPage.allNameTableButtons.nth(criticalIconCount - 1).waitFor(); - expect.soft(await recommendationsPage.allNameTableButtons.count()).toBe(criticalIconCount); - }); + await test.step('Switch to table view and verify row count matches card count', async () => { + await recommendationsPage.clickTableButton(); + await recommendationsPage.allNameTableButtons.nth(criticalIconCount - 1).waitFor(); + expect.soft(await recommendationsPage.allNameTableButtons.count()).toBe(criticalIconCount); + }); - await test.step('Verify table row names match card headings', async () => { - const buttonNames = await recommendationsPage.allNameTableButtons.allTextContents(); - const expectedSorted = actualHeadings.map((t: string) => t.trim()).sort(); - const buttonNamesSorted = buttonNames.map((t: string) => t.trim()).sort(); - expect.soft(buttonNamesSorted).toEqual(expectedSorted); - }); + await test.step('Verify table row names match card headings', async () => { + const buttonNames = await recommendationsPage.allNameTableButtons.allTextContents(); + const expectedSorted = actualHeadings.map((t: string) => t.trim()).sort(); + const buttonNamesSorted = buttonNames.map((t: string) => t.trim()).sort(); + expect.soft(buttonNamesSorted).toEqual(expectedSorted); + }); - await test.step('Verify all table rows have Critical status', async () => { - const allStatuses = await recommendationsPage.statusColumn.allTextContents(); - for (const status of allStatuses) { - expect(status.trim()).toBe('Critical'); - } - }); - }); + await test.step('Verify all table rows have Critical status', async () => { + const allStatuses = await recommendationsPage.statusColumn.allTextContents(); + for (const status of allStatuses) { + expect(status.trim()).toBe('Critical'); + } + }); + } + ); - test('[230521] Verify that only cards with See Item buttons are displayed when Non-empty category selected', async ({ - recommendationsPage, - }) => { - let count: number; - let actualHeadings: string[]; - let seeAllBtnCount: number; + test( + '[230521] Verify that only cards with See Item buttons are displayed when Non-empty category selected', + { tag: ['@fast', '@p2'] }, + async ({ recommendationsPage }) => { + let count: number; + let actualHeadings: string[]; + let seeAllBtnCount: number; - await test.step('Select Non-empty category and verify every card has a See All button', async () => { - await recommendationsPage.selectCategory('Non-empty'); - await recommendationsPage.allCardHeadings.last().waitFor(); - count = await recommendationsPage.allCardHeadings.count(); - actualHeadings = await recommendationsPage.allCardHeadings.allTextContents(); - debugLog(`Actual heading texts: ${actualHeadings}`); - debugLog(`Number of card headings found: ${count}`); - seeAllBtnCount = await recommendationsPage.allSeeAllBtns.count(); - debugLog(`Number of See Item buttons found: ${seeAllBtnCount}`); - expect.soft(seeAllBtnCount).toBe(count); - }); + await test.step('Select Non-empty category and verify every card has a See All button', async () => { + await recommendationsPage.selectCategory('Non-empty'); + await recommendationsPage.allCardHeadings.last().waitFor(); + count = await recommendationsPage.allCardHeadings.count(); + actualHeadings = await recommendationsPage.allCardHeadings.allTextContents(); + debugLog(`Actual heading texts: ${actualHeadings}`); + debugLog(`Number of card headings found: ${count}`); + seeAllBtnCount = await recommendationsPage.allSeeAllBtns.count(); + debugLog(`Number of See Item buttons found: ${seeAllBtnCount}`); + expect.soft(seeAllBtnCount).toBe(count); + }); - await test.step('Switch to table view and verify row count matches card count', async () => { - await recommendationsPage.clickTableButton(); - await recommendationsPage.allNameTableButtons.nth(seeAllBtnCount - 1).waitFor(); - expect.soft(await recommendationsPage.allNameTableButtons.count()).toBe(seeAllBtnCount); - }); + await test.step('Switch to table view and verify row count matches card count', async () => { + await recommendationsPage.clickTableButton(); + await recommendationsPage.allNameTableButtons.nth(seeAllBtnCount - 1).waitFor(); + expect.soft(await recommendationsPage.allNameTableButtons.count()).toBe(seeAllBtnCount); + }); - await test.step('Verify table row names match card headings', async () => { - const buttonNames = await recommendationsPage.allNameTableButtons.allTextContents(); - const expectedSorted = actualHeadings.map((t: string) => t.trim()).sort(); - const buttonNamesSorted = buttonNames.map((t: string) => t.trim()).sort(); - expect(buttonNamesSorted).toEqual(expectedSorted); - }); - }); + await test.step('Verify table row names match card headings', async () => { + const buttonNames = await recommendationsPage.allNameTableButtons.allTextContents(); + const expectedSorted = actualHeadings.map((t: string) => t.trim()).sort(); + const buttonNamesSorted = buttonNames.map((t: string) => t.trim()).sort(); + expect(buttonNamesSorted).toEqual(expectedSorted); + }); + } + ); - test('[230523] Verify filtering by applicable service works correctly', async ({ recommendationsPage }) => { + test('[230523] Verify filtering by applicable service works correctly', { tag: ['@fast', '@p2'] }, async ({ recommendationsPage }) => { let count: number; let actualHeadings: string[]; let rdsCount: number; @@ -428,63 +447,64 @@ test.describe('[MPT-11310] Recommendations page tests', { tag: ['@ui', '@recomme ]; for (const cardName of cardEntries) { - test(`[230524] ${cardName}: Cards displaying possible savings, should match itemised modal total and table total`, async ({ - recommendationsPage, - }) => { - // Find this card’s full metadata at runtime - const allCardData = getCardMetaData(recommendationsPage); - const card = allCardData.find(c => c.name === cardName); - if (!card) throw new Error(`Card data not found for: ${cardName}`); - - const { savingsValue, countValue, seeAllBtn, tableLocator, modalColumnLocator } = card; - let isApproximate: boolean; - 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(); - isApproximate = value.includes('≈'); - debugLog(`${cardName} Card shows approximate savings: ${isApproximate}`); - } - - debugLog(`${cardName} Card Possible Savings: ${cardSavings}`); + test( + `[230524] ${cardName}: Cards displaying possible savings, should match itemised modal total and table total`, + { tag: ['@fast', '@p2'] }, + async ({ recommendationsPage }) => { + // Find this card’s full metadata at runtime + const allCardData = getCardMetaData(recommendationsPage); + const card = allCardData.find(c => c.name === cardName); + if (!card) throw new Error(`Card data not found for: ${cardName}`); + + const { savingsValue, countValue, seeAllBtn, tableLocator, modalColumnLocator } = card; + let isApproximate: boolean; + 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(); + isApproximate = value.includes('≈'); + debugLog(`${cardName} Card shows approximate savings: ${isApproximate}`); + } - if (cardSavings === 0 && !isApproximate) { - await test.step('Card savings is 0, check table and see-all button', async () => { - await expect.soft(seeAllBtn).toBeHidden(); - await recommendationsPage.clickTableButton(); - expect.soft(await recommendationsPage.getCurrencyValue(tableLocator)).toBe(0); - }); + debugLog(`${cardName} Card Possible Savings: ${cardSavings}`); + + if (cardSavings === 0 && !isApproximate) { + await test.step('Card savings is 0, check table and see-all button', async () => { + await expect.soft(seeAllBtn).toBeHidden(); + await recommendationsPage.clickTableButton(); + expect.soft(await recommendationsPage.getCurrencyValue(tableLocator)).toBe(0); + }); + } else { + await recommendationsPage.skipTestIfMoreThan100Items(seeAllBtn); + + const itemisedSavings = await recommendationsPage.getItemisedSavingsFromModal(seeAllBtn, modalColumnLocator); + + await test.step('Compare modal itemised total and card savings', async () => { + expect.soft(itemisedSavings).toBeCloseTo(cardSavings, 0); + }); + + await test.step('Compare modal and table savings', async () => { + await recommendationsPage.clickTableButton(); + await recommendationsPage.waitForAllProgressBarsToDisappear(); + const tableSavings = await recommendationsPage.getCurrencyValue(tableLocator); + debugLog(`${cardName} Table Savings: ${tableSavings}`); + expect.soft(isWithinRoundingDrift(cardSavings, tableSavings, 0.001)).toBe(true); // Allowable drift of 0.1% + }); + } } else { - await recommendationsPage.skipTestIfMoreThan100Items(seeAllBtn); - - const itemisedSavings = await recommendationsPage.getItemisedSavingsFromModal(seeAllBtn, modalColumnLocator); - - await test.step('Compare modal itemised total and card savings', async () => { - expect.soft(itemisedSavings).toBeCloseTo(cardSavings, 0); - }); - - await test.step('Compare modal and table savings', async () => { - await recommendationsPage.clickTableButton(); - await recommendationsPage.waitForAllProgressBarsToDisappear(); - const tableSavings = await recommendationsPage.getCurrencyValue(tableLocator); - debugLog(`${cardName} Table Savings: ${tableSavings}`); - expect.soft(isWithinRoundingDrift(cardSavings, tableSavings, 0.001)).toBe(true); // Allowable drift of 0.1% - }); + cardCount = await recommendationsPage.getCardCountValue(countValue); + debugLog(`${cardName} Card Count: ${cardCount}`); + const seeAllCount = await recommendationsPage.getItemCountFromSeeAllButton(seeAllBtn); + debugLog(`${cardName} See All Button Count: ${seeAllCount}`); + expect.soft(seeAllCount).toBeCloseTo(cardCount); } - } else { - cardCount = await recommendationsPage.getCardCountValue(countValue); - debugLog(`${cardName} Card Count: ${cardCount}`); - const seeAllCount = await recommendationsPage.getItemCountFromSeeAllButton(seeAllBtn); - debugLog(`${cardName} See All Button Count: ${seeAllCount}`); - expect.soft(seeAllCount).toBeCloseTo(cardCount); } - }); + ); } }); diff --git a/e2etests/tests/resources-tests.spec.ts b/e2etests/tests/resources-tests.spec.ts index 292f92975..daf3ff074 100644 --- a/e2etests/tests/resources-tests.spec.ts +++ b/e2etests/tests/resources-tests.spec.ts @@ -49,7 +49,7 @@ test.describe('[MPT-11957] Resources page tests', { tag: ['@ui', '@resources'] } }); }); - test('[230778] All expected filters are displayed', async ({ resourcesPage }) => { + test('[230778] All expected filters are displayed', { tag: ['@fast', '@p2'] }, async ({ resourcesPage }) => { await test.step('Click Show more filters button', async () => { await resourcesPage.clickShowMoreFilters(); await resourcesPage.showLessFiltersBtn.waitFor(); @@ -84,7 +84,7 @@ test.describe('[MPT-11957] Resources page tests', { tag: ['@ui', '@resources'] } }); }); - test('[230779] Verify table column selection', async ({ resourcesPage }) => { + test('[230779] Verify table column selection', { tag: ['@fast', '@p2'] }, async ({ resourcesPage }) => { const defaultColumns = [ resourcesPage.resourceTableHeading, resourcesPage.expensesTableHeading, @@ -151,37 +151,41 @@ test.describe('[MPT-11957] Resources page tests', { tag: ['@ui', '@resources'] } }); }); - test('[230780] Unfiltered Total expenses matches table itemised total', { tag: '@slow' }, async ({ resourcesPage, datePicker }) => { - test.setTimeout(1200000); - test.skip( - (await resourcesPage.getResourceCountValue()) > 5000, - 'Skipping test as resource count is greater than the tables maximum resources' - ); - - // if the current day of the month is the 1st, then there may be no data so the tests will need to change the date range to Last 7 days to have data to compare. - const currentDate = new Date(); - if (currentDate.getDate() === 1) { - await test.step('Set date range to Last 7 days to ensure there is data to compare', async () => { - await datePicker.selectLast7DaysDateRange(); - }); - } + test( + '[230780] Unfiltered Total expenses matches table itemised total', + { tag: ['@slow', '@p2'] }, + async ({ resourcesPage, datePicker }) => { + test.setTimeout(1200000); + test.skip( + (await resourcesPage.getResourceCountValue()) > 5000, + 'Skipping test as resource count is greater than the tables maximum resources' + ); + + // if the current day of the month is the 1st, then there may be no data so the tests will need to change the date range to Last 7 days to have data to compare. + const currentDate = new Date(); + if (currentDate.getDate() === 1) { + await test.step('Set date range to Last 7 days to ensure there is data to compare', async () => { + await datePicker.selectLast7DaysDateRange(); + }); + } - await test.step('Get total expenses value from resources page', async () => { - totalExpensesValue = await resourcesPage.getTotalExpensesValue(); - debugLog(`Total expenses value: ${totalExpensesValue}`); - }); - await test.step('get the sum of itemised expenses from table', async () => { - await resourcesPage.table.waitFor(); - itemisedTotal = await resourcesPage.sumCurrencyColumn(resourcesPage.tableExpensesValue, resourcesPage.navigateNextIcon); - debugLog(`Itemised total: ${itemisedTotal}`); - }); + await test.step('Get total expenses value from resources page', async () => { + totalExpensesValue = await resourcesPage.getTotalExpensesValue(); + debugLog(`Total expenses value: ${totalExpensesValue}`); + }); + await test.step('get the sum of itemised expenses from table', async () => { + await resourcesPage.table.waitFor(); + itemisedTotal = await resourcesPage.sumCurrencyColumn(resourcesPage.tableExpensesValue, resourcesPage.navigateNextIcon); + debugLog(`Itemised total: ${itemisedTotal}`); + }); - await test.step('Compare total expenses with itemised total', async () => { - expect.soft(isWithinRoundingDrift(totalExpensesValue, itemisedTotal, 0.005)).toBe(true); // 0.5% tolerance - }); - }); + await test.step('Compare total expenses with itemised total', async () => { + expect.soft(isWithinRoundingDrift(totalExpensesValue, itemisedTotal, 0.005)).toBe(true); // 0.5% tolerance + }); + } + ); - test('[230788] Filtered Total expenses matches table itemised total', { tag: '@slow' }, async ({ resourcesPage }) => { + test('[230788] Filtered Total expenses matches table itemised total', { tag: ['@slow', '@p2'] }, async ({ resourcesPage }) => { test.setTimeout(120000); let initialTotalExpensesValue: number; @@ -221,7 +225,7 @@ test.describe('[MPT-11957] Resources page tests', { tag: ['@ui', '@resources'] } test( '[230781] Total expenses matches table itemised total for date range set to last 7 days', - { tag: '@slow' }, + { tag: ['@slow', '@p2'] }, async ({ resourcesPage, datePicker }) => { test.setTimeout(120000); @@ -246,7 +250,7 @@ test.describe('[MPT-11957] Resources page tests', { tag: ['@ui', '@resources'] } } ); - test('[230782] Validate API default chart/table data for 7 days', async ({ resourcesPage, datePicker }) => { + test('[230782] Validate API default chart/table data for 7 days', { tag: ['@slow', '@p2'] }, async ({ resourcesPage, datePicker }) => { test.slow(); const { startDate, endDate } = getLast7DaysUnixRange(); @@ -327,7 +331,7 @@ test.describe('[MPT-11957] Resources page tests', { tag: ['@ui', '@resources'] } test( '[230783] Validate API data for the daily expenses chart by breakdown for 7 days', - { tag: '@slow' }, + { tag: ['@slow', '@p2'] }, async ({ resourcesPage, datePicker }) => { test.setTimeout(120000); const { startDate, endDate } = getLast7DaysUnixRange(); @@ -746,38 +750,42 @@ test.describe('[MPT-11957] Resources page mocked tests', { tag: ['@ui', '@resour }); }); - test('[230784] Verify default service daily expenses chart export with and without legend', { tag: '@p1' }, async ({ resourcesPage }) => { - test.fixme(process.env.CI === '1', 'Tests do not work in CI. It appears that the png comparison is unsupported on linux'); - let actualPath = path.resolve('tests', 'downloads', 'expenses-chart-export.png'); - let expectedPath = path.resolve('tests', 'expected', 'expected-expenses-chart-export.png'); - let diffPath = path.resolve('tests', 'downloads', 'diff-expenses-chart-export.png'); - let match: boolean; + test( + '[230784] Verify default service daily expenses chart export with and without legend', + { tag: ['@fast', '@p1'] }, + async ({ resourcesPage }) => { + test.fixme(process.env.CI === '1', 'Tests do not work in CI. It appears that the png comparison is unsupported on linux'); + let actualPath = path.resolve('tests', 'downloads', 'expenses-chart-export.png'); + let expectedPath = path.resolve('tests', 'expected', 'expected-expenses-chart-export.png'); + let diffPath = path.resolve('tests', 'downloads', 'diff-expenses-chart-export.png'); + let match: boolean; - await test.step('Verify the default chart is Service Daily Expenses with legend displayed', async () => { - expect.soft(await resourcesPage.selectedComboBoxOption(resourcesPage.categorizeBySelect)).toBe('Service'); - expect.soft(await resourcesPage.selectedComboBoxOption(resourcesPage.expensesSelect)).toBe('Daily'); - await expect.soft(resourcesPage.showLegend).toBeChecked(); - }); + await test.step('Verify the default chart is Service Daily Expenses with legend displayed', async () => { + expect.soft(await resourcesPage.selectedComboBoxOption(resourcesPage.categorizeBySelect)).toBe('Service'); + expect.soft(await resourcesPage.selectedComboBoxOption(resourcesPage.expensesSelect)).toBe('Daily'); + await expect.soft(resourcesPage.showLegend).toBeChecked(); + }); - await test.step('Download the chart with legend and compare with expected chart png', async () => { - await resourcesPage.downloadFile(resourcesPage.exportChartBtn, actualPath); - match = await comparePngImages(expectedPath, actualPath, diffPath); - expect.soft(match).toBe(true); - }); + await test.step('Download the chart with legend and compare with expected chart png', async () => { + await resourcesPage.downloadFile(resourcesPage.exportChartBtn, actualPath); + match = await comparePngImages(expectedPath, actualPath, diffPath); + expect.soft(match).toBe(true); + }); - actualPath = path.resolve('tests', 'downloads', 'expenses-chart-export-without-legend.png'); - expectedPath = path.resolve('tests', 'expected', 'expected-expenses-chart-export-without-legend.png'); - diffPath = path.resolve('tests', 'downloads', 'diff-expenses-chart-export-without-legend.png'); + actualPath = path.resolve('tests', 'downloads', 'expenses-chart-export-without-legend.png'); + expectedPath = path.resolve('tests', 'expected', 'expected-expenses-chart-export-without-legend.png'); + diffPath = path.resolve('tests', 'downloads', 'diff-expenses-chart-export-without-legend.png'); - await test.step('Toggle Show Legend and verify the chart without legend', async () => { - await resourcesPage.toggleShowLegend(); - await resourcesPage.downloadFile(resourcesPage.exportChartBtn, actualPath); - match = await comparePngImages(expectedPath, actualPath, diffPath); - expect.soft(match).toBe(true); - }); - }); + await test.step('Toggle Show Legend and verify the chart without legend', async () => { + await resourcesPage.toggleShowLegend(); + await resourcesPage.downloadFile(resourcesPage.exportChartBtn, actualPath); + match = await comparePngImages(expectedPath, actualPath, diffPath); + expect.soft(match).toBe(true); + }); + } + ); - test('[230785] Verify weekly and monthly expenses chart export', async ({ resourcesPage }) => { + test('[230785] Verify weekly and monthly expenses chart export', { tag: ['@fast', '@p2'] }, async ({ resourcesPage }) => { test.fixme(process.env.CI === '1', 'Tests do not work in CI. It appears that the png comparison is unsupported on linux'); let actualPath = path.resolve('tests', 'downloads', 'weekly-expenses-chart-export.png'); let expectedPath = path.resolve('tests', 'expected', 'expected-weekly-expenses-chart-export.png'); @@ -803,7 +811,7 @@ test.describe('[MPT-11957] Resources page mocked tests', { tag: ['@ui', '@resour }); }); - test('[230786] Verify expenses chart export with different categories', async ({ resourcesPage }) => { + test('[230786] Verify expenses chart export with different categories', { tag: ['@fast', '@p2'] }, async ({ resourcesPage }) => { test.fixme(process.env.CI === '1', 'Tests do not work in CI. It appears that the png comparison is unsupported on linux'); let actualPath = path.resolve('tests', 'downloads', 'region-expenses-chart-export.png'); let expectedPath = path.resolve('tests', 'expected', 'expected-region-expenses-chart-export.png'); @@ -895,7 +903,7 @@ test.describe('[MPT-11957] Resources page mocked tests', { tag: ['@ui', '@resour }); }); - test('[230787] Verify table grouping', async ({ resourcesPage }) => { + test('[230787] Verify table grouping', { tag: ['@fast', '@p2'] }, async ({ resourcesPage }) => { await test.step('Verify default grouping is None', async () => { await resourcesPage.table.waitFor(); await resourcesPage.table.scrollIntoViewIfNeeded(); diff --git a/e2etests/tests/ri-sp-coverage-test.spec.ts b/e2etests/tests/ri-sp-coverage-test.spec.ts index 95a3a0020..e10400735 100644 --- a/e2etests/tests/ri-sp-coverage-test.spec.ts +++ b/e2etests/tests/ri-sp-coverage-test.spec.ts @@ -75,34 +75,37 @@ test.describe('Mocked RI/SP coverage page test', { tag: ['@ui', '@risp-coverage' { url: `/v2/organizations/[^/]+/optimizations\\?limit=3&overview=true`, mock: OptimizationsLimitsResponse, - } - + }, ]; test.use({ restoreSession: true, interceptAPI: { entries: apiInterceptions, failOnInterceptionMissing: true } }); - test('[232683] Verify mocked table data is displayed on the RI/SP coverage page', async ({ recommendationsPage, riSpCoveragePage }) => { - let savingsValue: number; + test( + '[232683] Verify mocked table data is displayed on the RI/SP coverage page', + { tag: ['@fast', '@p2'] }, + async ({ recommendationsPage, riSpCoveragePage }) => { + let savingsValue: number; - await test.step('Navigate to the RI/SP coverage page from recommendations page', async () => { - await recommendationsPage.page.clock.setFixedTime(new Date('2026-02-10T00:00:00Z')); - await recommendationsPage.navigateToURL(); - savingsValue = await recommendationsPage.getSavedWithCommitmentsValue(); - await recommendationsPage.clickRI_SPCard(); - }); + await test.step('Navigate to the RI/SP coverage page from recommendations page', async () => { + await recommendationsPage.page.clock.setFixedTime(new Date('2026-02-10T00:00:00Z')); + await recommendationsPage.navigateToURL(); + savingsValue = await recommendationsPage.getSavedWithCommitmentsValue(); + await recommendationsPage.clickRI_SPCard(); + }); - await test.step('Verify that the RI SP breakdown table displays the mocked data', async () => { - await riSpCoveragePage.waitForAllCanvases(); - await riSpCoveragePage.table.waitFor(); - const tableSavingsValue = riSpCoveragePage.parseCurrencyValue(await riSpCoveragePage.targetSavingsTableCell.textContent()); + await test.step('Verify that the RI SP breakdown table displays the mocked data', async () => { + await riSpCoveragePage.waitForAllCanvases(); + await riSpCoveragePage.table.waitFor(); + const tableSavingsValue = riSpCoveragePage.parseCurrencyValue(await riSpCoveragePage.targetSavingsTableCell.textContent()); - await expect(riSpCoveragePage.targetSP_UsageTableCell).toContainText('2393.2 hours'); - await expect(riSpCoveragePage.targetRI_UsageTableCell).toContainText('662 hours'); - await expect(riSpCoveragePage.targetTotalUsageTableCell).toContainText('3979.2 hours'); - await expect(riSpCoveragePage.targetSP_ExpensesTableCell).toContainText('$3,557.07'); - await expect(riSpCoveragePage.targetRI_ExpensesTableCell).toContainText('$266.65'); - expect(tableSavingsValue).toBe(savingsValue); - await expect(riSpCoveragePage.targetTotalExpensesCell).toContainText('$10,238.65'); - }); - }); + await expect(riSpCoveragePage.targetSP_UsageTableCell).toContainText('2393.2 hours'); + await expect(riSpCoveragePage.targetRI_UsageTableCell).toContainText('662 hours'); + await expect(riSpCoveragePage.targetTotalUsageTableCell).toContainText('3979.2 hours'); + await expect(riSpCoveragePage.targetSP_ExpensesTableCell).toContainText('$3,557.07'); + await expect(riSpCoveragePage.targetRI_ExpensesTableCell).toContainText('$266.65'); + expect(tableSavingsValue).toBe(savingsValue); + await expect(riSpCoveragePage.targetTotalExpensesCell).toContainText('$10,238.65'); + }); + } + ); }); diff --git a/e2etests/tests/tagging-policy-tests.spec.ts b/e2etests/tests/tagging-policy-tests.spec.ts index e41db7423..1c5661793 100644 --- a/e2etests/tests/tagging-policy-tests.spec.ts +++ b/e2etests/tests/tagging-policy-tests.spec.ts @@ -32,130 +32,144 @@ test.describe('[MPT-17042] Tagging Policy Tests', { tag: ['@ui', '@tagging-polic }); }); - test('[232655] Verify that Sample data pop-up is visible when no policies exist', async ({ taggingPoliciesPage }) => { - await test.step('Ensure all policies are deleted', async () => { + test( + '[232655] Verify that Sample data pop-up is visible when no policies exist', + { tag: ['@fast', '@p2'] }, + async ({ taggingPoliciesPage }) => { + await test.step('Ensure all policies are deleted', async () => { + // eslint-disable-next-line playwright/no-conditional-in-test + if (!(await taggingPoliciesPage.addRealDataBtn.isVisible())) { + await deleteAllPolicies(); + await taggingPoliciesPage.page.reload(); + await taggingPoliciesPage.waitForAllProgressBarsToDisappear(); + } + }); + + await test.step('Verify Sample data pop-up visibility', async () => { + await expect(taggingPoliciesPage.addRealDataBtn).toBeVisible(); + }); + } + ); + + test( + '[232656] Verify that a user can create a required tagging policy', + { tag: ['@fast', '@p2'] }, + async ({ taggingPoliciesPage, taggingPoliciesCreatePage }) => { + const policyName = `Required Tag Policy ${Date.now()}`; // eslint-disable-next-line playwright/no-conditional-in-test - if (!(await taggingPoliciesPage.addRealDataBtn.isVisible())) { - await deleteAllPolicies(); - await taggingPoliciesPage.page.reload(); - await taggingPoliciesPage.waitForAllProgressBarsToDisappear(); - } - }); - - await test.step('Verify Sample data pop-up visibility', async () => { - await expect(taggingPoliciesPage.addRealDataBtn).toBeVisible(); - }); - }); - - test('[232656] Verify that a user can create a required tagging policy', async ({ taggingPoliciesPage, taggingPoliciesCreatePage }) => { - const policyName = `Required Tag Policy ${Date.now()}`; - // 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(); - await taggingPoliciesCreatePage.createTaggingPolicy(ETaggingPolicyType.requiredTag, policyName, tagName); - }); - - const targetPolicyRow = taggingPoliciesPage.table.locator(`//td[.="${policyName}"]/ancestor::tr`); - const date = `${(new Date().getMonth() + 1).toString().padStart(2, '0')}/${new Date().getDate().toString().padStart(2, '0')}/${new Date().getFullYear()} 12:00 AM`; - - await test.step('Verify that the required tagging policy is created', async () => { - await targetPolicyRow.waitFor(); - - await expect.soft(targetPolicyRow.locator('//td[1]')).toHaveText(policyName); - await expect.soft(targetPolicyRow.locator('//td[3]')).toHaveText(`The ${tagName} tag is required starting from ${date}.`); - }); - }); - - test('[232657] Verify that a user can create a prohibited tagging policy', async ({ taggingPoliciesPage, taggingPoliciesCreatePage }) => { - const policyName = `Prohibited Tag Policy ${Date.now()}`; - // eslint-disable-next-line playwright/no-conditional-in-test - const tagName = env !== 'test' ? 'Component' : 'devops-component'; - const filter = 'Activity'; - const filterOption = 'Active'; - - await test.step('Create prohibited Tagging Policy page', async () => { - await taggingPoliciesPage.navigateToCreateTaggingPolicy(); - await taggingPoliciesCreatePage.createTaggingPolicy( - ETaggingPolicyType.prohibitedTag, - policyName, - tagName, - undefined, - filter, - filterOption - ); - }); - - const targetPolicyRow = taggingPoliciesPage.table.locator(`//td[.="${policyName}"]/ancestor::tr`); - const date = `${(new Date().getMonth() + 1).toString().padStart(2, '0')}/${new Date().getDate().toString().padStart(2, '0')}/${new Date().getFullYear()} 12:00 AM`; - - await test.step('Verify that the prohibited tagging policy is created', async () => { - await targetPolicyRow.waitFor(); - - await expect.soft(targetPolicyRow.locator('//td[1]')).toHaveText(policyName); - await expect.soft(targetPolicyRow.locator('//td[3]')).toHaveText(`Tag ${tagName} is prohibited starting from ${date}.`); - await expect(targetPolicyRow.locator('//td[4]')).toHaveText(`${filter}: ${filterOption}`); - }); - }); - - test('[232658] Verify that a user can create a tags correlation tagging policy', async ({ - taggingPoliciesPage, - taggingPoliciesCreatePage, - }) => { - const policyName = `Correlated Tag Policy ${Date.now()}`; - // 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(); - await taggingPoliciesCreatePage.createTaggingPolicy(ETaggingPolicyType.tagsCorrelation, policyName, tagName, secondaryTagName); - }); - - const targetPolicyRow = taggingPoliciesPage.table.locator(`//td[.="${policyName}"]/ancestor::tr`); - const date = `${(new Date().getMonth() + 1).toString().padStart(2, '0')}/${new Date().getDate().toString().padStart(2, '0')}/${new Date().getFullYear()} 12:00 AM`; - - await test.step('Verify that the tags correlation tagging policy is created', async () => { - await targetPolicyRow.waitFor(); - - await expect.soft(targetPolicyRow.locator('//td[1]')).toHaveText(policyName); - await expect - .soft(targetPolicyRow.locator('//td[3]')) - .toHaveText(`Resources tagged with ${tagName} must be tagged with ${secondaryTagName} starting from ${date}.`); - }); - }); - - test('[232659] Verify that user can delete a policy from the tagging policy details page', async ({ - taggingPoliciesPage, - taggingPoliciesCreatePage, - }) => { - const policyName = `Policy To Be Deleted ${Date.now()}`; - // 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(); - await taggingPoliciesCreatePage.createTaggingPolicy(ETaggingPolicyType.requiredTag, policyName, tagName); - }); - - const targetPolicyRow = taggingPoliciesPage.table.locator(`//td[.="${policyName}"]/ancestor::tr`); - - await test.step('Navigate to the created policy details page', async () => { - await targetPolicyRow.waitFor(); - await taggingPoliciesPage.click(targetPolicyRow.locator('//a')); - await taggingPoliciesPage.policyDetailsDiv.waitFor(); - }); - - await test.step('Delete the policy from the details page', async () => { - await taggingPoliciesPage.deletePolicyFromDetailsPage(); - }); - - await test.step('Verify that the policy is deleted and no longer appears in the policies table', async () => { - await expect(targetPolicyRow).toBeHidden(); - }); - }); + const tagName = env !== 'test' ? 'Component' : 'devops-component'; + + await test.step('Create required Tagging Policy page', async () => { + await taggingPoliciesPage.navigateToCreateTaggingPolicy(); + await taggingPoliciesCreatePage.createTaggingPolicy(ETaggingPolicyType.requiredTag, policyName, tagName); + }); + + const targetPolicyRow = taggingPoliciesPage.table.locator(`//td[.="${policyName}"]/ancestor::tr`); + const date = `${(new Date().getMonth() + 1).toString().padStart(2, '0')}/${new Date().getDate().toString().padStart(2, '0')}/${new Date().getFullYear()} 12:00 AM`; + + await test.step('Verify that the required tagging policy is created', async () => { + await targetPolicyRow.waitFor(); + + await expect.soft(targetPolicyRow.locator('//td[1]')).toHaveText(policyName); + await expect.soft(targetPolicyRow.locator('//td[3]')).toHaveText(`The ${tagName} tag is required starting from ${date}.`); + }); + } + ); + + test( + '[232657] Verify that a user can create a prohibited tagging policy', + { tag: ['@fast', '@p2'] }, + async ({ taggingPoliciesPage, taggingPoliciesCreatePage }) => { + const policyName = `Prohibited Tag Policy ${Date.now()}`; + // eslint-disable-next-line playwright/no-conditional-in-test + const tagName = env !== 'test' ? 'Component' : 'devops-component'; + const filter = 'Activity'; + const filterOption = 'Active'; + + await test.step('Create prohibited Tagging Policy page', async () => { + await taggingPoliciesPage.navigateToCreateTaggingPolicy(); + await taggingPoliciesCreatePage.createTaggingPolicy( + ETaggingPolicyType.prohibitedTag, + policyName, + tagName, + undefined, + filter, + filterOption + ); + }); + + const targetPolicyRow = taggingPoliciesPage.table.locator(`//td[.="${policyName}"]/ancestor::tr`); + const date = `${(new Date().getMonth() + 1).toString().padStart(2, '0')}/${new Date().getDate().toString().padStart(2, '0')}/${new Date().getFullYear()} 12:00 AM`; + + await test.step('Verify that the prohibited tagging policy is created', async () => { + await targetPolicyRow.waitFor(); + + await expect.soft(targetPolicyRow.locator('//td[1]')).toHaveText(policyName); + await expect.soft(targetPolicyRow.locator('//td[3]')).toHaveText(`Tag ${tagName} is prohibited starting from ${date}.`); + await expect(targetPolicyRow.locator('//td[4]')).toHaveText(`${filter}: ${filterOption}`); + }); + } + ); + + test( + '[232658] Verify that a user can create a tags correlation tagging policy', + { tag: ['@fast', '@p2'] }, + async ({ taggingPoliciesPage, taggingPoliciesCreatePage }) => { + const policyName = `Correlated Tag Policy ${Date.now()}`; + // 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(); + await taggingPoliciesCreatePage.createTaggingPolicy(ETaggingPolicyType.tagsCorrelation, policyName, tagName, secondaryTagName); + }); + + const targetPolicyRow = taggingPoliciesPage.table.locator(`//td[.="${policyName}"]/ancestor::tr`); + const date = `${(new Date().getMonth() + 1).toString().padStart(2, '0')}/${new Date().getDate().toString().padStart(2, '0')}/${new Date().getFullYear()} 12:00 AM`; + + await test.step('Verify that the tags correlation tagging policy is created', async () => { + await targetPolicyRow.waitFor(); + + await expect.soft(targetPolicyRow.locator('//td[1]')).toHaveText(policyName); + await expect + .soft(targetPolicyRow.locator('//td[3]')) + .toHaveText(`Resources tagged with ${tagName} must be tagged with ${secondaryTagName} starting from ${date}.`); + }); + } + ); + + test( + '[232659] Verify that user can delete a policy from the tagging policy details page', + { tag: ['@fast', '@p2'] }, + async ({ taggingPoliciesPage, taggingPoliciesCreatePage }) => { + const policyName = `Policy To Be Deleted ${Date.now()}`; + // 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(); + await taggingPoliciesCreatePage.createTaggingPolicy(ETaggingPolicyType.requiredTag, policyName, tagName); + }); + + const targetPolicyRow = taggingPoliciesPage.table.locator(`//td[.="${policyName}"]/ancestor::tr`); + + await test.step('Navigate to the created policy details page', async () => { + await targetPolicyRow.waitFor(); + await taggingPoliciesPage.click(targetPolicyRow.locator('//a')); + await taggingPoliciesPage.policyDetailsDiv.waitFor(); + }); + + await test.step('Delete the policy from the details page', async () => { + await taggingPoliciesPage.deletePolicyFromDetailsPage(); + }); + + await test.step('Verify that the policy is deleted and no longer appears in the policies table', async () => { + await expect(targetPolicyRow).toBeHidden(); + }); + } + ); }); test.describe('[MPT-17042] Mocked Tagging Policies Tests', { tag: ['@ui', '@tagging-policies'] }, () => { @@ -181,20 +195,24 @@ test.describe('[MPT-17042] Mocked Tagging Policies Tests', { tag: ['@ui', '@tagg }); }); - test('[232660] Verify that tagging policies are displayed with the correct status', async ({ taggingPoliciesPage }) => { - const cancelIconXpath = '//*[@data-testid="CancelIcon"]'; - const checkCheckIconXpath = '//*[@data-testid="CheckCircleIcon"]'; - const correlatedTagStatus = taggingPoliciesPage.table.locator('(//a[contains(text(), "Correlated Tag")]/ancestor::tr/td[2]/div)[1]'); - const nonViolatingTagStatus = taggingPoliciesPage.table.locator('(//a[contains(text(), "Non-violating")]/ancestor::tr/td[2]/div)[1]'); - const prohibitedTagStatus = taggingPoliciesPage.table.locator('(//a[contains(text(), "Prohibited Tag")]/ancestor::tr/td[2]/div)[1]'); - const requiredTagStatus = taggingPoliciesPage.table.locator('(//a[contains(text(), "Required Tag")]/ancestor::tr/td[2]/div)[1]'); - - await expect.soft(correlatedTagStatus.locator(cancelIconXpath)).toBeVisible(); - await expect.soft(correlatedTagStatus).toHaveText('1 violation right now'); - await expect.soft(nonViolatingTagStatus.locator(checkCheckIconXpath)).toBeVisible(); - await expect.soft(prohibitedTagStatus.locator(cancelIconXpath)).toBeVisible(); - await expect.soft(prohibitedTagStatus).toHaveText('2 violations right now'); - await expect.soft(requiredTagStatus.locator(cancelIconXpath)).toBeVisible(); - await expect.soft(requiredTagStatus).toHaveText('3185 violations right now'); - }); + test( + '[232660] Verify that tagging policies are displayed with the correct status', + { tag: ['@fast', '@p2'] }, + async ({ taggingPoliciesPage }) => { + const cancelIconXpath = '//*[@data-testid="CancelIcon"]'; + const checkCheckIconXpath = '//*[@data-testid="CheckCircleIcon"]'; + const correlatedTagStatus = taggingPoliciesPage.table.locator('(//a[contains(text(), "Correlated Tag")]/ancestor::tr/td[2]/div)[1]'); + const nonViolatingTagStatus = taggingPoliciesPage.table.locator('(//a[contains(text(), "Non-violating")]/ancestor::tr/td[2]/div)[1]'); + const prohibitedTagStatus = taggingPoliciesPage.table.locator('(//a[contains(text(), "Prohibited Tag")]/ancestor::tr/td[2]/div)[1]'); + const requiredTagStatus = taggingPoliciesPage.table.locator('(//a[contains(text(), "Required Tag")]/ancestor::tr/td[2]/div)[1]'); + + await expect.soft(correlatedTagStatus.locator(cancelIconXpath)).toBeVisible(); + await expect.soft(correlatedTagStatus).toHaveText('1 violation right now'); + await expect.soft(nonViolatingTagStatus.locator(checkCheckIconXpath)).toBeVisible(); + await expect.soft(prohibitedTagStatus.locator(cancelIconXpath)).toBeVisible(); + await expect.soft(prohibitedTagStatus).toHaveText('2 violations right now'); + await expect.soft(requiredTagStatus.locator(cancelIconXpath)).toBeVisible(); + await expect.soft(requiredTagStatus).toHaveText('3185 violations right now'); + } + ); }); diff --git a/e2etests/utils/api-requests/interceptor.ts b/e2etests/utils/api-interceptor/interceptor.ts similarity index 100% rename from e2etests/utils/api-requests/interceptor.ts rename to e2etests/utils/api-interceptor/interceptor.ts diff --git a/e2etests/utils/api-requests/auth-request.ts b/e2etests/utils/api-requests/auth-request.ts deleted file mode 100644 index 3307da7d3..000000000 --- a/e2etests/utils/api-requests/auth-request.ts +++ /dev/null @@ -1,170 +0,0 @@ -import { APIResponse } from 'playwright'; -import { APIRequestContext } from '@playwright/test'; - -export class AuthRequest { - readonly request: APIRequestContext; - readonly userEndpoint: string; - readonly tokenEndpoint: string; - readonly verificationCodesEndpoint: string; - - /** - * Constructs an instance of AuthRequest. - * @param {APIRequestContext} request - The API request context. - */ - constructor(request: APIRequestContext) { - this.request = request; - const baseUrl = process.env.API_BASE_URL || ''; - this.userEndpoint = `${baseUrl}/auth/v2/users`; - this.tokenEndpoint = `${baseUrl}/auth/v2/tokens`; - this.verificationCodesEndpoint = `${baseUrl}/auth/v2/verification_codes`; - } - - /** - * Authorizes a user with the provided email and password. - * @param {string} email - The email address of the user. - * @param {string} password - The password of the user. - * @returns {Promise} A promise that resolves to the API response. - */ - async authorization(email: string, password: string): Promise { - return await this.request.post(this.tokenEndpoint, { - headers: { - 'Content-Type': 'application/json', - }, - data: { - email: email, - password: password, - }, - }); - } - - /** - * Gets an authorization token for the provided email and password. - * @param {string} email - The email address of the user. - * @param {string} password - The password of the user. - * @returns {Promise} A promise that resolves to the authorization token. - * @throws Will throw an error if the token generation fails. - */ - async getAuthorizationToken(email: string, password: string): Promise { - const response = await this.authorization(email, password); - if (response.status() !== 201) { - throw new Error('Failed to generate token'); - } - const body = await response.json(); - console.log(JSON.stringify(body)); - const { token } = await response.json(); - console.log(`Token: ${token}`); - return token; - } - - /** - * Gets users with the cluster secret. - * @param {string} [userID] - Optional user identifier. - * @returns {Promise} A promise that resolves to the API response. - */ - async getUsersWithClusterSecret(userID?: string): Promise { - let endpoint = this.userEndpoint; - if (userID) { - endpoint += `?user_id=${userID}`; - } - return await this.request.get(endpoint, { - headers: { - 'Content-Type': 'application/json', - Secret: `${process.env.CLUSTER_SECRET}`, - }, - }); - } - - /** - * Gets users with a bad cluster secret. - * @param {string} userID - The user identifier. - * @returns {Promise} A promise that resolves to the API response. - */ - async getUsersWithBadClusterSecret(userID: string): Promise { - return await this.request.get(`${this.userEndpoint}/${userID}`, { - headers: { - 'Content-Type': 'application/json', - Secret: 'bad-secret', - }, - }); - } - - /** - * Creates a new user with the provided email, password, and display name. - * @param {string} email - The email address of the user. - * @param {string} password - The password of the user. - * @param {string} displayName - The display name of the user. - * @returns {Promise} A promise that resolves to the API response. - * @throws Will throw an error if the user creation fails. - */ - async createUser(email: string, password: string, displayName: string): Promise { - const response = await this.request.post(this.userEndpoint, { - headers: { - 'Content-Type': 'application/json', - Secret: process.env.CLUSTER_SECRET, - }, - data: { - email, - display_name: displayName, - password, - verified: true, - }, - }); - - if (response.status() !== 201) { - throw new Error(`Failed to create user: Status ${response.status()}`); - } - return response; - } - - /** - * Sets a verification code for a user. - * This method sends a POST request to the verification codes endpoint with the provided email and code. - * It validates the presence of the `CLUSTER_SECRET` environment variable and handles errors if the request fails. - * - * @param {string} email - The email address of the user. - * @param {string} code - The verification code to be set for the user. - * @returns {Promise} A promise that resolves to the API response. - * @throws Will throw an error if the `CLUSTER_SECRET` is not defined or if the request fails. - */ - async setVerificationCode(email: string, code: string): Promise { - if (!process.env.CLUSTER_SECRET) { - throw new Error('CLUSTER_SECRET is not defined in the environment variables.'); - } - - const response = await this.request.post(this.verificationCodesEndpoint, { - headers: { - 'Content-Type': 'application/json', - Secret: process.env.CLUSTER_SECRET, - }, - data: { - email, - code, - }, - }); - const payload = JSON.parse(await response.text()); - if (response.status() !== 201) { - const reason = payload.error?.reason || 'Unknown error'; - throw new Error(`Failed to create verification code: Status ${response.status()} - Reason: ${reason}`); - } - return response; - } - - /** - * Deletes a user with the provided user ID. - * @param {string} userID - The user identifier. - * @returns {Promise} A promise that resolves when the user is deleted. - * @throws Will throw an error if the user deletion fails. - */ - async deleteUser(userID: string): Promise { - const response = await this.request.delete(`${this.userEndpoint}/${userID}`, { - headers: { - 'Content-Type': 'application/json', - Secret: `${process.env.CLUSTER_SECRET}`, - }, - }); - if (response.status() !== 204) { - throw new Error(`Failed to delete userID ${userID}`); - } - console.log(`UserID ${userID} deleted`); - } -} diff --git a/e2etests/utils/api-requests/base-request.ts b/e2etests/utils/api-requests/base-request.ts deleted file mode 100644 index db2f38af4..000000000 --- a/e2etests/utils/api-requests/base-request.ts +++ /dev/null @@ -1,195 +0,0 @@ -import { APIResponse } from 'playwright'; -import { APIRequestContext } from '@playwright/test'; -import fs from 'fs'; -import { debugLog } from '../debug-logging'; - -export class BaseRequest { - readonly request: APIRequestContext; - readonly userEndpoint: string; - readonly tokenEndpoint: string; - readonly verificationCodesEndpoint: string; - - /** - * Constructs an instance of AuthRequest. - * @param {APIRequestContext} request - The API request context. - */ - constructor(request: APIRequestContext) { - this.request = request; - const baseUrl = process.env.API_BASE_URL || ''; - this.userEndpoint = `${baseUrl}/auth/v2/users`; - this.tokenEndpoint = `${baseUrl}/auth/v2/tokens`; - this.verificationCodesEndpoint = `${baseUrl}/auth/v2/verification_codes`; - } - - /** - * Authorizes a user with the provided email and password. - * @param {string} email - The email address of the user. - * @param {string} password - The password of the user. - * @returns {Promise} A promise that resolves to the API response. - */ - async authorization(email: string, password: string): Promise { - return await this.request.post(this.tokenEndpoint, { - headers: { - 'Content-Type': 'application/json', - }, - data: { - email: email, - password: password, - }, - }); - } - - /** - * Gets an authorization token for the provided email and password. - * @param {string} email - The email address of the user. - * @param {string} password - The password of the user. - * @returns {Promise} A promise that resolves to the authorization token. - * @throws Will throw an error if the token generation fails. - */ - async getAuthorizationToken(email: string, password: string): Promise { - const response = await this.authorization(email, password); - if (response.status() !== 201) { - throw new Error('Failed to generate token'); - } - const body = await response.json(); - console.log(JSON.stringify(body)); - const { token } = await response.json(); - console.log(`Token: ${token}`); - return token; - } - - /** - * Saves the authorization response to a file. - * @param {string} email - The email address of the user. - * @param {string} password - The password of the user. - * @returns {Promise} A promise that resolves when the response is saved. - * @throws Will throw an error if the authorization fails. - */ - async saveAuthorizationResponse(email: string, password: string): Promise { - const response = await this.authorization(email, password); - if (response.status() !== 201) { - throw new Error('Failed to authorize user'); - } - const responseBody = await response.json(); - const userID = responseBody.user_id; // Assuming the response contains a user object with an id - - const filePath = `.cache/auth-response-${userID}.json`; - fs.writeFileSync(filePath, JSON.stringify(responseBody, null, 2)); - console.log(`Response saved to ${filePath}`); - } - - /** - * Gets users with the cluster secret. - * @param {string} [userID] - Optional user identifier. - * @returns {Promise} A promise that resolves to the API response. - */ - async getUsersWithClusterSecret(userID?: string): Promise { - let endpoint = this.userEndpoint; - if (userID) { - endpoint += `?user_id=${userID}`; - } - - debugLog(`Getting users from endpoint: ${endpoint}`); - - return await this.request.get(endpoint, { - headers: { - 'Content-Type': 'application/json', - Secret: `${process.env.CLUSTER_SECRET}`, - }, - }); - } - - /** - * Gets users with a bad cluster secret. - * @param {string} userID - The user identifier. - * @returns {Promise} A promise that resolves to the API response. - */ - async getUsersWithBadClusterSecret(userID: string): Promise { - return await this.request.get(`${this.userEndpoint}/${userID}`, { - headers: { - 'Content-Type': 'application/json', - Secret: 'bad-secret', - }, - }); - } - - /** - * Creates a new user with the provided email, password, and display name. - * @param {string} email - The email address of the user. - * @param {string} password - The password of the user. - * @param {string} displayName - The display name of the user. - * @returns {Promise} A promise that resolves to the API response. - * @throws Will throw an error if the user creation fails. - */ - async createUser(email: string, password: string, displayName: string): Promise { - const response = await this.request.post(this.userEndpoint, { - headers: { - 'Content-Type': 'application/json', - Secret: process.env.CLUSTER_SECRET, - }, - data: { - email, - display_name: displayName, - password, - verified: true, - }, - }); - - if (response.status() !== 201) { - throw new Error(`Failed to create user: Status ${response.status()}`); - } - return response; - } - - /** - * Sets a verification code for a user. - * This method sends a POST request to the verification codes endpoint with the provided email and code. - * It validates the presence of the `CLUSTER_SECRET` environment variable and handles errors if the request fails. - * - * @param {string} email - The email address of the user. - * @param {string} code - The verification code to be set for the user. - * @returns {Promise} A promise that resolves to the API response. - * @throws Will throw an error if the `CLUSTER_SECRET` is not defined or if the request fails. - */ - async setVerificationCode(email: string, code: string): Promise { - if (!process.env.CLUSTER_SECRET) { - throw new Error('CLUSTER_SECRET is not defined in the environment variables.'); - } - - const response = await this.request.post(this.verificationCodesEndpoint, { - headers: { - 'Content-Type': 'application/json', - Secret: process.env.CLUSTER_SECRET, - }, - data: { - email, - code, - }, - }); - const payload = JSON.parse(await response.text()); - if (response.status() !== 201) { - const reason = payload.error?.reason || 'Unknown error'; - throw new Error(`Failed to create verification code: Status ${response.status()} - Reason: ${reason}`); - } - return response; - } - - /** - * Deletes a user with the provided user ID. - * @param {string} userID - The user identifier. - * @returns {Promise} A promise that resolves when the user is deleted. - * @throws Will throw an error if the user deletion fails. - */ - async deleteUser(userID: string): Promise { - const response = await this.request.delete(`${this.userEndpoint}/${userID}`, { - headers: { - 'Content-Type': 'application/json', - Secret: `${process.env.CLUSTER_SECRET}`, - }, - }); - if (response.status() !== 204) { - throw new Error(`Failed to delete userID ${userID}`); - } - console.log(`UserID ${userID} deleted`); - } -} diff --git a/e2etests/utils/api-requests/organizations-request.ts b/e2etests/utils/api-requests/organizations-request.ts deleted file mode 100644 index 4c249d64e..000000000 --- a/e2etests/utils/api-requests/organizations-request.ts +++ /dev/null @@ -1,95 +0,0 @@ -import {APIRequestContext, Page} from "@playwright/test"; -import fs from "fs"; -import path from "path"; - -export class OrganizationsRequest { - readonly request: APIRequestContext; - readonly organizationsEndpoint: string; - - /** - * Constructs an instance of RestAPIRequest. - * @param {APIRequestContext} request - The API request context. - */ - constructor(request: APIRequestContext) { - this.request = request; - this.organizationsEndpoint = "/restapi/v2/organizations"; - } - - /** - * Creates an organization with the provided name and currency. - * @param {string} token - The authorization token. - * @param {string} name - The name of the organization. - * @param {string} [currency='USD'] - The currency of the organization. - * @returns {Promise} A promise that resolves to the response body as a string. - * @throws Will throw an error if the organization creation fails. - */ - async createOrganization(token: string, name: string, currency: string = 'USD'): Promise { - const response = await this.request.post(this.organizationsEndpoint, { - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${token}` - }, - data: { - name: name, - currency: currency - } - }); - - if (response.status() !== 201) { - throw new Error('Failed to create organization'); - } - const responseBody = await response.json(); - console.log(JSON.stringify(responseBody)); - return (JSON.stringify(responseBody)); - } - - /** - * Deletes an organization with the provided organization ID. - * @param {string} organizationId - The ID of the organization to delete. - * @returns {Promise} A promise that resolves to a confirmation message. - * @throws Will throw an error if the organization deletion fails. - */ - async deleteOrganization(organizationId: string): Promise { - const response = await this.request.delete(`${this.organizationsEndpoint}/${organizationId}`, { - headers: { - "Content-Type": "application/json", - Secret: `${process.env.CLUSTER_SECRET}` - } - }); - console.log(JSON.stringify(response)); - if (response.status() !== 204) { - throw new Error(`Failed to delete organization: ${organizationId}`); - } - return `Organization ${organizationId} deleted`; - } -} - -export function saveOrganizationId(organizationId: string): void { - fs.writeFile( - path.resolve(`.cache/organizationID.txt`), - `${organizationId}`, - "utf8", - function (err) { - if (err) { - return console.error(err); - } - console.log("File created!"); - }, - ); -} - -export async function fetchBreakdownExpenses( - page: Page, - breakdownBy: string, - selectCategorizeBy: (label: string) => Promise, - uiLabel: string -): Promise { - const [response] = await Promise.all([ - page.waitForResponse((resp) => - resp.url().includes(`/breakdown_expenses?breakdown_by=${breakdownBy}`) && resp.status() === 200 - ), - selectCategorizeBy(uiLabel), - ]); - - return response.json(); -} diff --git a/e2etests/utils/teardown-utils.ts b/e2etests/utils/teardown-utils.ts index 22559510b..7776e3258 100644 --- a/e2etests/utils/teardown-utils.ts +++ b/e2etests/utils/teardown-utils.ts @@ -7,7 +7,7 @@ import { PoolsResponse, TaggingPolicyResponse } from '../types/api-response.types'; -import { AuthRequest } from './api-requests/auth-request'; +import { AuthRequest } from '../api-requests/auth-request'; import { RestAPIRequest } from '../api-requests/restapi-request'; import { GetDatasourcesByOrganizationIDResponse } from '../types/GetDatasourcesByIDResponse'; import { getEnvironmentOpsAccountId, getEnvironmentOpsOrgId } from './environment-util';