Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions e2etests/pages/cloud-accounts-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ export class CloudAccountsPage extends BasePage {

}

async navigateToCloudAccountsPage(): Promise<void> {
await this.navigateToURL();
await this.waitForAllProgressBarsToDisappear();
await this.allCloudAccountLinks.last().waitFor();
}

/**
* Clicks the Add button on the Cloud Accounts page.
* @returns {Promise<void>}
Expand Down
57 changes: 56 additions & 1 deletion e2etests/pages/events-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,68 @@ import {BasePage} from "./base-page";
*/
export class EventsPage extends BasePage {
readonly heading: Locator;
readonly allBtn: Locator;
readonly infoBtn: Locator;
readonly warningBtn: Locator;
readonly errorBtn: Locator;
readonly noEventsMessage: Locator;


/**
* Initializes a new instance of the EventsPage class.
* @param {Page} page - The Playwright page object.
*/
constructor(page: Page) {
super(page, '/events');
this.heading = this.page.getByTestId('lbl_events');
this.heading = this.main.getByTestId('lbl_events');
this.allBtn = this.main.getByTestId('event_lvl_all');
this.infoBtn = this.main.getByTestId('event_lvl_info');
this.warningBtn = this.main.getByTestId('event_lvl_warning');
this.errorBtn = this.main.getByTestId('event_lvl_error');
this.noEventsMessage = this.main.getByText('There are no events for selected criteria. Please try to change the filters.');
}

async filterByEventLevel(level: 'All' | 'Info' | 'Warning' | 'Error'): Promise<void> {
switch (level) {
case 'All':
await this.clickButtonIfNotActive(this.allBtn);
break;
case 'Info':
await this.clickButtonIfNotActive(this.infoBtn);
break;
case 'Warning':
await this.clickButtonIfNotActive(this.warningBtn);
break;
case 'Error':
await this.clickButtonIfNotActive(this.errorBtn);
break;
}
await this.waitForAllProgressBarsToDisappear();
}

async getEventByText(text: string): Promise<Locator> {
return this.main.locator(`//p[contains(text(), "${text}")]`)
}

/**
* Gets an event by matching text using a RegExp pattern.
* @param {RegExp} pattern - The regular expression pattern to match.
* @returns {Locator} A locator for the event matching the pattern.
*/
getEventByPattern(pattern: RegExp): Locator {
return this.main.getByText(pattern);
}

/**
* Gets an event by matching multiple text conditions.
* @returns {Locator} A locator for the event matching both text conditions.
* @param text
*/
getEventByMultipleTexts(text: string[]): Locator {
let xpath = '//p';
for (const t of text) {
xpath += `[contains(text(), "${t}")]`;
}
return this.main.locator(xpath).first();
}
}
9 changes: 9 additions & 0 deletions e2etests/pages/main-menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,4 +143,13 @@ export class MainMenu extends BasePage {
async clickSettings(): Promise<void> {
await this.settingsBtn.click();
}

/**
* Clicks the Events button.
* This method is used to navigate to the Events page.
* @returns {Promise<void>} A promise that resolves when the Events button is clicked.
*/
async clickEvents(): Promise<void> {
await this.eventsBtn.click();
}
}
29 changes: 26 additions & 3 deletions e2etests/pages/pools-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export class PoolsPage extends BasePage {
readonly expensesThisMonthToggle: Locator;
readonly forecastToggle: Locator;
readonly ownerToggle: Locator;
readonly firstSubItem: Locator
readonly firstSubItem: Locator;

readonly nameTableHeading: Locator;
readonly monthlyLimitTableHeading: Locator;
Expand Down Expand Up @@ -136,8 +136,7 @@ export class PoolsPage extends BasePage {

this.firstSubItem = this.table.locator('//tr[@data-test-id="row_1"]');
this.domainOutlinedIcon = this.getByAnyTestId('DomainOutlinedIcon', this.table);
this.poolExpandMoreIcon = this.domainOutlinedIcon.locator(
'xpath=/ancestor::td//*[@data-testid="ExpandMoreIcon"]');
this.poolExpandMoreIcon = this.domainOutlinedIcon.locator('xpath=/ancestor::td//*[@data-testid="ExpandMoreIcon"]');
this.navigateNextIcon = this.getByAnyTestId('NavigateNextIcon', this.main);

//Edit pools side modal
Expand Down Expand Up @@ -334,6 +333,30 @@ export class PoolsPage extends BasePage {
}
}

/**
* Retrieves the name of a specific sub-pool from the table.
*
* This method adjusts the provided 1-based index to zero-based indexing,
* locates the sub-pool name element, and returns the trimmed text content.
*
* @param {number} [index=1] - The 1-based index of the sub-pool to retrieve the name for (default is 1).
* @returns {Promise<string>} The trimmed name of the sub-pool.
*
* @example
* // Get the first sub-pool name
* const firstName = await poolsPage.getSubPoolName();
*
* @example
* // Get the third sub-pool name
* const thirdName = await poolsPage.getSubPoolName(3);
*/
async getSubPoolName(index: number = 1): Promise<string> {
index = index - 1; // Adjust index to be zero-based
const locator = this.subPoolNameColumn.nth(index);
const text = await locator.textContent();
return text.trim();
}

/**
* Retrieves the monthly limit for a specific sub-pool from the table.
* Adjusts the index to be zero-based, retrieves the sub-pool name for logging,
Expand Down
2 changes: 2 additions & 0 deletions e2etests/setup/global-teardown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
getDatasourceIdByNameViaOpsAPI,
getSubPoolIdsContainingName,
} from '../utils/teardown-utils';
import { debugLog } from '../utils/debug-logging';

async function globalTeardown() {

Expand All @@ -37,6 +38,7 @@ async function globalTeardown() {

if (subPoolIds.length > 1) {
const marketplaceDevId = await getDatasourceIdByNameViaOpsAPI(restAPIRequest, dataSourceName);
debugLog('Orphaned Sub-pools found for Marketplace (Dev), proceeding to disconnect data source, delete sub-pools and reconnect data source to clean them up');
await disconnectDataSource(restAPIRequest, token, marketplaceDevId);
await deleteSubPoolsByName(restAPIRequest, token, dataSourceName);
await connectDataSource(restAPIRequest, token, dataSourceName);
Expand Down
113 changes: 88 additions & 25 deletions e2etests/tests/cloud-accounts-tests.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,12 @@ import {
OrganizationsResponse,
OrganizationThemeSettingsResponse,
} from '../mocks/cloud-accounts-page.mocks';
import { getCurrentUTCTimestamp } from '../utils/date-range-utils';

test.describe('Cloud Accounts Tests', { tag: ['@ui', '@cloudaccounts'] }, () => {
test.describe.configure({ mode: 'default' });
test.describe('Cloud Accounts Tests', { tag: ['@ui', '@cloud-accounts'] }, () => {
test.describe.configure({ mode: 'serial' });
test.use({ restoreSession: true });

test.beforeEach('Login admin user', async ({ cloudAccountsPage }) => {
await test.step('Login admin user', async () => {
await cloudAccountsPage.navigateToURL();
await cloudAccountsPage.waitForAllProgressBarsToDisappear();
await cloudAccountsPage.allCloudAccountLinks.last().waitFor();
});
});

//TODO: The first datasource has not been configured correctly in the environment. The test will need to be changed to use Marketplace (Dev) which is
// the test datasource that we can configure without external dependencies.
test.fixme(
Expand All @@ -38,6 +31,7 @@ test.describe('Cloud Accounts Tests', { tag: ['@ui', '@cloudaccounts'] }, () =>
let dataSourceResponse: DataSourceBillingResponse;
const now = Math.floor(Date.now() / 1000);
const secondsIn24Hours = 86400;
await cloudAccountsPage.navigateToCloudAccountsPage();

await test.step('Fetch Data Source Response for first account', async () => {
const [response] = await Promise.all([fetchDataSourceResponse(page), cloudAccountsPage.clickCloudAccountLink(1)]);
Expand All @@ -64,7 +58,9 @@ test.describe('Cloud Accounts Tests', { tag: ['@ui', '@cloudaccounts'] }, () =>

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)';

await test.step(`Disconnect ${awsAccountName} if connected`, async () => {
await cloudAccountsPage.disconnectIfConnectedCloudAccountByName(awsAccountName);
});
Expand All @@ -86,7 +82,9 @@ test.describe('Cloud Accounts Tests', { tag: ['@ui', '@cloudaccounts'] }, () =>
{ tag: '@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();
const awsAccountName = 'Marketplace (Dev)';

await test.step(`Disconnect ${awsAccountName} if connected`, async () => {
await cloudAccountsPage.disconnectIfConnectedCloudAccountByName(awsAccountName);
});
Expand All @@ -106,7 +104,9 @@ test.describe('Cloud Accounts Tests', { tag: ['@ui', '@cloudaccounts'] }, () =>

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);
});
Expand All @@ -129,7 +129,10 @@ test.describe('Cloud Accounts Tests', { tag: ['@ui', '@cloudaccounts'] }, () =>
}) => {
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();
Expand All @@ -142,9 +145,11 @@ test.describe('Cloud Accounts Tests', { tag: ['@ui', '@cloudaccounts'] }, () =>
});

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.';
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();
Expand All @@ -162,17 +167,16 @@ test.describe('Cloud Accounts Tests', { tag: ['@ui', '@cloudaccounts'] }, () =>
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);
]);
responseStatus = response.status();
debugLog(`API Response status: ${responseStatus}`);
expect(responseStatus).toBe(200);
});
});

});

test.describe('Mocked Cloud Accounts Tests', { tag: ['@ui', '@cloudaccounts'] }, () => {
test.describe.configure({ mode: 'default' });
test.describe('Mocked Cloud Accounts Tests', { tag: ['@ui', '@cloud-accounts'] }, () => {
test.describe.configure({ mode: 'serial' });

const apiInterceptions: InterceptionEntry[] = [
{
Expand Down Expand Up @@ -224,13 +228,8 @@ test.describe('Mocked Cloud Accounts Tests', { tag: ['@ui', '@cloudaccounts'] },
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('Login admin user', async () => {
await cloudAccountsPage.navigateToURL();
await cloudAccountsPage.waitForAllProgressBarsToDisappear();
await cloudAccountsPage.allCloudAccountLinks.last().waitFor();
});

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();
});
Expand All @@ -251,4 +250,68 @@ test.describe('Mocked Cloud Accounts Tests', { tag: ['@ui', '@cloudaccounts'] },
await expect.soft(cloudAccountsPage.sideModalSecondaryAlert).toHaveText(permissionsMessage);
});
});
});
});


test.describe(
'[MPT-18378] Verify Cloud Account actions are recorded correctly in the events log',
{ tag: ['@ui', '@cloud-accounts', '@events'] },
() => {
test.describe.configure({ mode: 'serial' });
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}`);
expect.soft(eventText).toContain(`${timestamp} UTC`);
});

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 creationEvent = eventsPage.getEventByMultipleTexts([`Cloud account ${awsAccountName}`, 'created']);
await expect.soft(creationEvent).toBeVisible();

const eventText = await creationEvent.textContent();
debugLog(`Creation event text: ${eventText}`);
expect.soft(eventText).toContain(`${timestamp} UTC`);

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}`);
expect.soft(poolEventText).toContain(`${timestamp} UTC`);
expect.soft(poolEventText).toContain(process.env.DEFAULT_USER_EMAIL);
});
});
}
);
1 change: 0 additions & 1 deletion e2etests/tests/homepage-tests.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,6 @@ test.describe('[MPT-12743] Home Page test for Pools requiring attention block',
homePage,
poolsPage,
}) => {
test.describe.configure({ mode: 'serial' }); // The test is state dependent, so it should not run in parallel with other resource tests.
await test.step('Navigate to home page', async () => {
await homePage.navigateToURL();
await homePage.waitForAllProgressBarsToDisappear();
Expand Down
Loading