From 3e555e18468ac810a6aeb693ed98a97308e7db93 Mon Sep 17 00:00:00 2001 From: Gustavo Lira Date: Wed, 3 Jul 2024 20:27:45 -0300 Subject: [PATCH] RHIDP-744 [Test Automation] Automate RHDH theme customization - E2E Refactor custom theme E2E test and update uihelper functions Improve error handling in UIhelper.ts Added try-catch to handle potential errors while scrolling elements into view during end-to-end testing in UIhelper.ts. A warning will now be logged in the console if an error occurs to aid debugging and troubleshooting. Signed-off-by: Gustavo Lira --- .../config_map/configmap-app-config-rhdh.yaml | 5 ++ e2e-tests/playwright/e2e/custom-theme.spec.ts | 60 +++++++++---------- .../playwright/e2e/github-happy-path.spec.ts | 7 ++- .../support/pageObjects/global-obj.ts | 4 ++ e2e-tests/playwright/utils/APIHelper.ts | 14 ++++- e2e-tests/playwright/utils/UIhelper.ts | 59 ++++++++++++++++-- .../utils/custom-theme/theme-verifier.ts | 60 +++++++++++++++++++ 7 files changed, 170 insertions(+), 39 deletions(-) create mode 100644 e2e-tests/playwright/utils/custom-theme/theme-verifier.ts diff --git a/.ibm/pipelines/resources/config_map/configmap-app-config-rhdh.yaml b/.ibm/pipelines/resources/config_map/configmap-app-config-rhdh.yaml index 520057004..2f1816dc7 100644 --- a/.ibm/pipelines/resources/config_map/configmap-app-config-rhdh.yaml +++ b/.ibm/pipelines/resources/config_map/configmap-app-config-rhdh.yaml @@ -16,6 +16,11 @@ data: headerColor1: "rgb(248, 248, 248)" headerColor2: "rgb(248, 248, 248)" navigationIndicatorColor: "rgb(255,95,21)" + dark: + primaryColor: '#ab75cf' + headerColor1: 'rgb(0, 0, 208)' + headerColor2: 'rgb(255, 246, 140)' + navigationIndicatorColor: 'rgb(244, 238, 169)' backend: auth: keys: diff --git a/e2e-tests/playwright/e2e/custom-theme.spec.ts b/e2e-tests/playwright/e2e/custom-theme.spec.ts index 80afd7674..5771917b4 100644 --- a/e2e-tests/playwright/e2e/custom-theme.spec.ts +++ b/e2e-tests/playwright/e2e/custom-theme.spec.ts @@ -1,50 +1,48 @@ -import { test, Page, expect } from '@playwright/test'; -import { UIhelper } from '../utils/UIhelper'; +import { test, Page, TestInfo } from '@playwright/test'; import { Common, setupBrowser } from '../utils/Common'; +import { ThemeVerifier } from '../utils/custom-theme/theme-verifier'; let page: Page; test.describe('CustomTheme should be applied', () => { let common: Common; - let uiHelper: UIhelper; + let themeVerifier: ThemeVerifier; test.beforeAll(async ({ browser }, testInfo) => { page = (await setupBrowser(browser, testInfo)).page; common = new Common(page); - uiHelper = new UIhelper(page); + themeVerifier = new ThemeVerifier(page); await common.loginAsGuest(); }); - /* eslint-disable-next-line no-empty-pattern */ - test('Verify that theme colors are applied and make screenshots', async ({} /* NOSONAR */, testInfo) => { - await uiHelper.openSidebar('Settings'); - - const header = await page.locator('header').first(); - await expect(header).toHaveCSS( - 'background-image', + // eslint-disable-next-line no-empty-pattern + test('Verify that theme light colors are applied and make screenshots', async ({}, testInfo: TestInfo) => { + await themeVerifier.setTheme('Light'); + await themeVerifier.verifyHeaderGradient( 'none, linear-gradient(90deg, rgb(248, 248, 248), rgb(248, 248, 248))', ); + await themeVerifier.verifyBorderLeftColor('rgb(255, 95, 21)'); + await themeVerifier.takeScreenshotAndAttach( + 'screenshots/custom-theme-light-inspection.png', + testInfo, + 'custom-theme-light-inspection', + ); + //await themeVerifier.verifyPrimaryColors('rgb(255, 95, 21)') //TODO: comment out when the primary color issue is fixed (RHIDP-3107) + }); - await page.screenshot({ - path: 'screenshots/cusotm-theme-inspection.png', - fullPage: true, - }); - - await testInfo.attach('cusotm-theme-inspection', { - path: 'screenshots/cusotm-theme-inspection.png', - }); - - await page.locator('[name=pin]').click(); - - await page.screenshot({ - path: 'screenshots/cusotm-theme-inspection-collapsed.png', - fullPage: true, - }); - await testInfo.attach('cusotm-theme-inspection-collapsed', { - path: 'screenshots/cusotm-theme-inspection-collapsed.png', - }); - - await common.signOut(); + // eslint-disable-next-line no-empty-pattern + test('Verify that theme dark colors are applied and make screenshots', async ({}, testInfo: TestInfo) => { + await themeVerifier.setTheme('Dark'); + await themeVerifier.verifyHeaderGradient( + 'none, linear-gradient(90deg, rgb(0, 0, 208), rgb(255, 246, 140))', + ); + await themeVerifier.verifyBorderLeftColor('rgb(244, 238, 169)'); + await themeVerifier.takeScreenshotAndAttach( + 'screenshots/custom-theme-dark-inspection.png', + testInfo, + 'custom-theme-dark-inspection', + ); + // await themeVerifier.verifyPrimaryColors('#ab75cf') //TODO: comment out when the primary color issue is fixed (RHIDP-3107) }); }); diff --git a/e2e-tests/playwright/e2e/github-happy-path.spec.ts b/e2e-tests/playwright/e2e/github-happy-path.spec.ts index a72f7fd80..05a720c33 100644 --- a/e2e-tests/playwright/e2e/github-happy-path.spec.ts +++ b/e2e-tests/playwright/e2e/github-happy-path.spec.ts @@ -115,19 +115,24 @@ test.describe.serial('GitHub Happy path', () => { }); test('Click on the arrows to verify that the next/previous/first/last pages of PRs are loaded', async () => { + console.log('Fetching all PRs from GitHub'); const allPRs = await BackstageShowcase.getGithubPRs('all', true); + console.log('Clicking on ALL button'); await uiHelper.clickButton('ALL', { force: true }); await backstageShowcase.verifyPRRows(allPRs, 0, 5); + console.log('Clicking on Next Page button'); await backstageShowcase.clickNextPage(); await backstageShowcase.verifyPRRows(allPRs, 5, 10); - // Calculate the starting index of the first PR on the last page, 5 PRs per page. const lastPagePRs = Math.floor((allPRs.length - 1) / 5) * 5; + + console.log('Clicking on Last Page button'); await backstageShowcase.clickLastPage(); await backstageShowcase.verifyPRRows(allPRs, lastPagePRs, allPRs.length); + console.log('Clicking on Previous Page button'); await backstageShowcase.clickPreviousPage(); await backstageShowcase.verifyPRRows(allPRs, lastPagePRs - 5, lastPagePRs); }); diff --git a/e2e-tests/playwright/support/pageObjects/global-obj.ts b/e2e-tests/playwright/support/pageObjects/global-obj.ts index 0e26b082e..7cf4d146b 100644 --- a/e2e-tests/playwright/support/pageObjects/global-obj.ts +++ b/e2e-tests/playwright/support/pageObjects/global-obj.ts @@ -5,10 +5,14 @@ export const waitsObjs = { export const UIhelperPO = { MuiButtonLabel: 'span[class^="MuiButton-label"]', + MuiToggleButtonLabel: 'span[class^="MuiToggleButton-label"]', MuiBoxLabel: 'div[class*="MuiBox-root"] label', MuiTableHead: 'th[class*="MuiTableCell-root"]', MuiTableCell: 'td[class*="MuiTableCell-root"]', MuiTableRow: 'tr[class*="MuiTableRow-root"]', + MuiTypographyColorPrimary: '.MuiTypography-colorPrimary', + MuiSwitchColorPrimary: '.MuiSwitch-colorPrimary', + MuiButtonTextPrimary: '.MuiButton-textPrimary', MuiCard: cardHeading => `//div[contains(@class,'MuiCardHeader-root') and descendant::*[text()='${cardHeading}']]/..`, MuiTable: 'table.MuiTable-root', diff --git a/e2e-tests/playwright/utils/APIHelper.ts b/e2e-tests/playwright/utils/APIHelper.ts index abac6365d..3618fe177 100644 --- a/e2e-tests/playwright/utils/APIHelper.ts +++ b/e2e-tests/playwright/utils/APIHelper.ts @@ -9,7 +9,7 @@ export class APIHelper { body?: string | object, ): Promise { const context = await request.newContext(); - const options = { + const options: any = { method: method, headers: { Accept: 'application/vnd.github+json', @@ -26,11 +26,21 @@ export class APIHelper { return response; } - static async getGithubPaginatedRequest(url, pageNo = 1, response = []) { + static async getGithubPaginatedRequest( + url: string, + pageNo = 1, + response: any[] = [], + ): Promise { const fullUrl = `${url}&page=${pageNo}`; const result = await this.githubRequest('GET', fullUrl); const body = await result.json(); + if (!Array.isArray(body)) { + throw new Error( + `Expected array but got ${typeof body}: ${JSON.stringify(body)}`, + ); + } + if (body.length === 0) { return response; } diff --git a/e2e-tests/playwright/utils/UIhelper.ts b/e2e-tests/playwright/utils/UIhelper.ts index 11699fe7b..2974783d1 100644 --- a/e2e-tests/playwright/utils/UIhelper.ts +++ b/e2e-tests/playwright/utils/UIhelper.ts @@ -3,7 +3,6 @@ import { UIhelperPO } from '../support/pageObjects/global-obj'; export class UIhelper { private page: Page; - private selectors: { [key: string]: string }; constructor(page: Page) { this.page = page; @@ -46,6 +45,15 @@ export class UIhelper { return button; } + async clickBtnByTitleIfNotPressed(title: string) { + const button = this.page.locator(`button[title="${title}"]`); + const isPressed = await button.getAttribute('aria-pressed'); + + if (isPressed === 'false') { + await button.click(); + } + } + async verifyDivHasText(divText: string) { await expect( this.page.locator(`div`).filter({ hasText: divText }), @@ -125,10 +133,25 @@ export class UIhelper { .locator(`tr>td`) .getByText(rowText, { exact: exact }) .first(); - await rowLocator.waitFor({ state: 'visible' }); - await rowLocator.waitFor({ state: 'attached' }); - await rowLocator.scrollIntoViewIfNeeded(); - await expect(rowLocator).toBeVisible(); + + try { + await rowLocator.waitFor({ state: 'visible', timeout: 10000 }); + await rowLocator.waitFor({ state: 'attached', timeout: 10000 }); + + try { + await rowLocator.scrollIntoViewIfNeeded(); + } catch (error) { + console.warn( + `Warning: Could not scroll element into view. Error: ${error.message}`, + ); + } + + await expect(rowLocator).toBeVisible(); + } catch (error) { + console.error( + `Error: Failed to verify row with text "${rowText}". Error: ${error.message}`, + ); + } } } @@ -247,4 +270,30 @@ export class UIhelper { const rowCount = await this.page.locator(rowSelector).count(); expect(rowCount).toBeGreaterThan(0); } + + // Function to convert hexadecimal to RGB or return RGB if it's already in RGB + toRgb(color: string): string { + if (color.startsWith('rgb')) { + return color; + } + + const bigint = parseInt(color.slice(1), 16); + const r = (bigint >> 16) & 255; + const g = (bigint >> 8) & 255; + const b = bigint & 255; + return `rgb(${r}, ${g}, ${b})`; + } + + async checkCssColor(page: Page, selector: string, expectedColor: string) { + const elements = await page.locator(selector); + const count = await elements.count(); + const expectedRgbColor = this.toRgb(expectedColor); + + for (let i = 0; i < count; i++) { + const color = await elements + .nth(i) + .evaluate(el => window.getComputedStyle(el).color); + expect(color).toBe(expectedRgbColor); + } + } } diff --git a/e2e-tests/playwright/utils/custom-theme/theme-verifier.ts b/e2e-tests/playwright/utils/custom-theme/theme-verifier.ts new file mode 100644 index 000000000..7bbfde467 --- /dev/null +++ b/e2e-tests/playwright/utils/custom-theme/theme-verifier.ts @@ -0,0 +1,60 @@ +import { Page, expect, TestInfo } from '@playwright/test'; +import { UIhelper } from '../UIhelper'; +import { UIhelperPO } from '../../support/pageObjects/global-obj'; + +export class ThemeVerifier { + private readonly page: Page; + private uiHelper: UIhelper; + + constructor(page: Page) { + this.page = page; + this.uiHelper = new UIhelper(page); + } + + async setTheme(theme: 'Light' | 'Dark') { + await this.uiHelper.openSidebar('Settings'); + await this.uiHelper.clickBtnByTitleIfNotPressed(`Select theme ${theme}`); + } + + async verifyHeaderGradient(expectedGradient: string) { + const header = await this.page.locator('main header'); + await expect(header).toHaveCSS('background-image', expectedGradient); + } + + async verifyBorderLeftColor(expectedColor: string) { + const locator = await this.page.locator("a[aria-label='Settings']"); + await expect(locator).toHaveCSS( + 'border-left', + `3px solid ${expectedColor}`, + ); + } + + async verifyPrimaryColors(colorPrimary: string) { + await this.uiHelper.checkCssColor( + this.page, + UIhelperPO.MuiTypographyColorPrimary, + colorPrimary, + ); + await this.uiHelper.checkCssColor( + this.page, + UIhelperPO.MuiSwitchColorPrimary, + colorPrimary, + ); + await this.uiHelper.openSidebar('Catalog'); + await this.uiHelper.checkCssColor( + this.page, + UIhelperPO.MuiButtonTextPrimary, + colorPrimary, + ); + await this.uiHelper.openSidebar('Settings'); + } + + async takeScreenshotAndAttach( + screenshotPath: string, + testInfo: TestInfo, + description: string, + ) { + await this.page.screenshot({ path: screenshotPath, fullPage: true }); + await testInfo.attach(description, { path: screenshotPath }); + } +}