From 7b836b021d685221e688fb000fb138df4b4bac3f Mon Sep 17 00:00:00 2001 From: Oleksandr Andriienko Date: Wed, 5 Mar 2025 17:04:04 +0200 Subject: [PATCH 01/17] test(e2e): add sonarqube plugin test Signed-off-by: Oleksandr Andriienko --- .ibm/pipelines/resources/sonarqube/install.sh | 6 ++ .../pipelines/resources/sonarqube/values.yaml | 19 +++++ .../sonarkube-actions.spec.ts | 75 +++++++++++++++++++ 3 files changed, 100 insertions(+) create mode 100644 .ibm/pipelines/resources/sonarqube/install.sh create mode 100644 .ibm/pipelines/resources/sonarqube/values.yaml create mode 100644 e2e-tests/playwright/e2e/plugins/sonarkube-actions/sonarkube-actions.spec.ts diff --git a/.ibm/pipelines/resources/sonarqube/install.sh b/.ibm/pipelines/resources/sonarqube/install.sh new file mode 100644 index 0000000000..74c13e9c06 --- /dev/null +++ b/.ibm/pipelines/resources/sonarqube/install.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +helm repo add sonarqube https://SonarSource.github.io/helm-chart-sonarqube +helm repo update +kubectl create namespace sonarqube +helm upgrade --install -n sonarqube sonarqube sonarqube/sonarqube --set edition=developer -f values.yaml diff --git a/.ibm/pipelines/resources/sonarqube/values.yaml b/.ibm/pipelines/resources/sonarqube/values.yaml new file mode 100644 index 0000000000..5a7c79b70a --- /dev/null +++ b/.ibm/pipelines/resources/sonarqube/values.yaml @@ -0,0 +1,19 @@ +OpenShift: + enabled: true + serviceAccount: + create: true + route: + enabled: true + host: "sonarqube." + path: "/" + tls: + termination: edge + insecureEdgeTerminationPolicy: Redirect + +monitoringPasscode: "define_it" + +postgresql: + securityContext: + enabled: false + containerSecurityContext: + enabled: false \ No newline at end of file diff --git a/e2e-tests/playwright/e2e/plugins/sonarkube-actions/sonarkube-actions.spec.ts b/e2e-tests/playwright/e2e/plugins/sonarkube-actions/sonarkube-actions.spec.ts new file mode 100644 index 0000000000..0605be4d89 --- /dev/null +++ b/e2e-tests/playwright/e2e/plugins/sonarkube-actions/sonarkube-actions.spec.ts @@ -0,0 +1,75 @@ +import { test } from "@playwright/test"; +import { Common, setupBrowser } from "../../../utils/common"; +import { UIhelper } from "../../../utils/ui-helper"; +import { UI_HELPER_ELEMENTS } from "../../../support/pageObjects/global-obj"; +import { CatalogImport } from "../../../support/pages/catalog-import"; + +test.describe("Test SonarKube Actions plugin", () => { + let common: Common; + let uiHelper: UIhelper; + let catalogImport: CatalogImport; + let project: string; + let projectKey: string; + + const template = + "https://github.com/backstage/community-plugins/blob/main/workspaces/scaffolder-backend-module-sonarqube/plugins/scaffolder-backend-module-sonarqube/examples/templates/01-sonar-template.yaml"; + + test.beforeEach(async ({ browser, page }, testInfo) => { + page = (await setupBrowser(browser, testInfo)).page; + common = new Common(page); + uiHelper = new UIhelper(page); + catalogImport = new CatalogImport(page); + + await common.loginAsGuest(); + await uiHelper.clickLink({ ariaLabel: "Create..." }); + }); + + test("Creates kubernetes namespace", async ({ page }) => { + await uiHelper.clickButton("Register Existing Component"); + await catalogImport.registerExistingComponent(template, false); + await page.waitForTimeout(1000); + await uiHelper.clickLink({ ariaLabel: "Create..." }); + await common.waitForLoad(); + + await uiHelper.verifyHeading("Software Templates"); + await uiHelper.searchInputPlaceholder("Create a SonarQube project"); + await uiHelper.verifyText("Create a SonarQube project"); + + project = `test-sonarqube-actions-${Date.now()}`; + projectKey = `any-key-${Date.now()}`; + + await uiHelper.clickBtnInCard("Create a SonarQube project", "Choose"); + + await uiHelper.waitForTitle("Create a SonarQube project", 2); + await uiHelper.fillTextInputByLabel( + "Base URL *", + "https://sonarqube.apps.rosa.enptw-i8tb9-tkf.l9yc.p3.openshiftapps.com", + ); + await uiHelper.fillTextInputByLabel( + "Token *", + "sqa_ae44946cb513b0e7d8500d08d654257c2bb6fdd0", + ); + await uiHelper.fillTextInputByLabel("Name *", project); + await uiHelper.fillTextInputByLabel("Key *", projectKey); + await uiHelper.fillTextInputByLabel("Branch", "main"); + await uiHelper.clickButton("Review"); + await uiHelper.clickButton("Create"); + + await page.waitForSelector( + `${UI_HELPER_ELEMENTS.MuiTypography}:has-text("second")`, + ); + + // // Figure out how to use another browser tab with SonarQube service + // const context: BrowserContext = await browser.newContext(); + + // Open a new tab handler: force link to open in the same browser tab + // const [newPage] = await Promise.all([ + // context.waitForEvent("page"), + // uiHelper.clickButton("SonarQube project URL") + // ]); + + // // Wait for the new page with SonarQube service to load + // await newPage.waitForLoadState(); + // await page.waitForTimeout(15000); + }); +}); From 87cafcd2b208b05ba57371b766211472e2ebc675 Mon Sep 17 00:00:00 2001 From: Oleksandr Andriienko Date: Thu, 24 Jul 2025 00:17:08 +0300 Subject: [PATCH 02/17] fix(e2e): continue work on sonarqube test Signed-off-by: Oleksandr Andriienko --- .../sonarkube-actions.spec.ts | 48 ++++++++----------- e2e-tests/playwright/utils/ui-helper.ts | 27 +++++++++++ 2 files changed, 46 insertions(+), 29 deletions(-) diff --git a/e2e-tests/playwright/e2e/plugins/sonarkube-actions/sonarkube-actions.spec.ts b/e2e-tests/playwright/e2e/plugins/sonarkube-actions/sonarkube-actions.spec.ts index 0605be4d89..6f12537bc4 100644 --- a/e2e-tests/playwright/e2e/plugins/sonarkube-actions/sonarkube-actions.spec.ts +++ b/e2e-tests/playwright/e2e/plugins/sonarkube-actions/sonarkube-actions.spec.ts @@ -1,7 +1,6 @@ import { test } from "@playwright/test"; import { Common, setupBrowser } from "../../../utils/common"; import { UIhelper } from "../../../utils/ui-helper"; -import { UI_HELPER_ELEMENTS } from "../../../support/pageObjects/global-obj"; import { CatalogImport } from "../../../support/pages/catalog-import"; test.describe("Test SonarKube Actions plugin", () => { @@ -21,17 +20,17 @@ test.describe("Test SonarKube Actions plugin", () => { catalogImport = new CatalogImport(page); await common.loginAsGuest(); - await uiHelper.clickLink({ ariaLabel: "Create..." }); + await page.goto("/create"); }); test("Creates kubernetes namespace", async ({ page }) => { - await uiHelper.clickButton("Register Existing Component"); + await uiHelper.clickButton("Import an existing Git repository"); await catalogImport.registerExistingComponent(template, false); - await page.waitForTimeout(1000); - await uiHelper.clickLink({ ariaLabel: "Create..." }); + + await uiHelper.clickLink({ ariaLabel: "Self-service" }); await common.waitForLoad(); - await uiHelper.verifyHeading("Software Templates"); + await uiHelper.verifyHeading("Self-service"); await uiHelper.searchInputPlaceholder("Create a SonarQube project"); await uiHelper.verifyText("Create a SonarQube project"); @@ -41,35 +40,26 @@ test.describe("Test SonarKube Actions plugin", () => { await uiHelper.clickBtnInCard("Create a SonarQube project", "Choose"); await uiHelper.waitForTitle("Create a SonarQube project", 2); + await uiHelper.fillTextInputByLabel( "Base URL *", - "https://sonarqube.apps.rosa.enptw-i8tb9-tkf.l9yc.p3.openshiftapps.com", - ); - await uiHelper.fillTextInputByLabel( - "Token *", - "sqa_ae44946cb513b0e7d8500d08d654257c2bb6fdd0", + "https://sonarqube.apps.rosa.me4rm-zkeon-kfk.wq6f.p3.openshiftapps.com", ); - await uiHelper.fillTextInputByLabel("Name *", project); - await uiHelper.fillTextInputByLabel("Key *", projectKey); - await uiHelper.fillTextInputByLabel("Branch", "main"); - await uiHelper.clickButton("Review"); - await uiHelper.clickButton("Create"); - await page.waitForSelector( - `${UI_HELPER_ELEMENTS.MuiTypography}:has-text("second")`, - ); + await uiHelper.clickById("root_authParams__oneof_select"); + await uiHelper.selectDropDownOption("Username and Password"); - // // Figure out how to use another browser tab with SonarQube service - // const context: BrowserContext = await browser.newContext(); + await uiHelper.fillTextInputByLabel("Username *", "admin"); + await uiHelper.fillInputWithLabel("Password", "NewAdminPassword1@"); - // Open a new tab handler: force link to open in the same browser tab - // const [newPage] = await Promise.all([ - // context.waitForEvent("page"), - // uiHelper.clickButton("SonarQube project URL") - // ]); + await uiHelper.fillInputWithLabel("root_name", project); + await uiHelper.fillInputWithLabel("root_key", projectKey); + await uiHelper.fillTextInputByLabel("Branch", "main"); + await uiHelper.clickButton("Review"); + await page.waitForTimeout(5000); + await uiHelper.clickButton("Create"); + await uiHelper.clickLinkWithNewTab(/SonarQube project URL/i); - // // Wait for the new page with SonarQube service to load - // await newPage.waitForLoadState(); - // await page.waitForTimeout(15000); + await uiHelper.isLinkVisible(project); }); }); diff --git a/e2e-tests/playwright/utils/ui-helper.ts b/e2e-tests/playwright/utils/ui-helper.ts index 50d258bb45..3a938f8058 100644 --- a/e2e-tests/playwright/utils/ui-helper.ts +++ b/e2e-tests/playwright/utils/ui-helper.ts @@ -40,6 +40,10 @@ export class UIhelper { await this.page.fill(SEARCH_OBJECTS_COMPONENTS.ariaLabelSearch, searchText); } + async fillInputWithLabel(label: string, input: string) { + await this.page.locator(`input#${label}`).fill(input); + } + async pressTab() { await this.page.keyboard.press("Tab"); } @@ -51,6 +55,15 @@ export class UIhelper { await locator.check(); } + async clickLinkWithNewTab(name: string | RegExp) { + const [newPage] = await Promise.all([ + this.page.context().waitForEvent("page"), + this.page.getByRole("link", { name }).click(), + ]); + + await newPage.waitForLoadState(); + } + async clickButton( label: string | RegExp, options: { exact?: boolean; force?: boolean } = { @@ -692,6 +705,20 @@ export class UIhelper { await locator.click(); } + async selectDropDownOption(optionText: string) { + const dropdown = this.page.getByRole("listbox"); + await expect(dropdown).toBeVisible({ timeout: 5000 }); + + await this.page.waitForSelector(`li:has-text("${optionText}")`, { + timeout: 5000, + }); + + const optionItem = await this.page.waitForSelector( + `li:has-text("${optionText}")`, + ); + optionItem.click(); + } + async clickSpanByText(text: string) { await this.verifyText(text); await this.page.click(`span:has-text("${text}")`); From f4883d669146f87ca1905d6448602e47578e0475 Mon Sep 17 00:00:00 2001 From: Oleksandr Andriienko Date: Thu, 24 Jul 2025 14:05:47 +0300 Subject: [PATCH 03/17] feat(e2e): try to enable installation sonarqube Signed-off-by: Oleksandr Andriienko --- .ibm/pipelines/utils.sh | 12 ++++++++++++ e2e-tests/playwright.config.ts | 1 + .../sonarkube-actions.spec.ts | 0 3 files changed, 13 insertions(+) rename e2e-tests/playwright/e2e/plugins/{sonarkube-actions => sonarkube}/sonarkube-actions.spec.ts (100%) diff --git a/.ibm/pipelines/utils.sh b/.ibm/pipelines/utils.sh index 08bb7b6327..c9a16c1275 100755 --- a/.ibm/pipelines/utils.sh +++ b/.ibm/pipelines/utils.sh @@ -628,6 +628,17 @@ install_pipelines_operator() { fi } +install_sonar_qube() { + SONAR_QUBE_NS="sonarqube" + configure_namespace ${SONAR_QUBE_NS} + helm repo add sonarqube https://SonarSource.github.io/helm-chart-sonarqube || true + helm repo update + + helm upgrade --install -n ${SONAR_QUBE_NS} sonarqube sonarqube/sonarqube --set edition=developer -f "${DIR}/resources/sonarqube/values.yaml" +} + +## todo implement uninstall_sonar_qube + # Installs the Tekton Pipelines if not already installed (alternative of OpenShift Pipelines for Kubernetes clusters) install_tekton_pipelines() { DISPLAY_NAME="tekton-pipelines-webhook" @@ -663,6 +674,7 @@ delete_tekton_pipelines() { } cluster_setup_ocp_helm() { + install_sonar_qube install_pipelines_operator install_acm_ocp_operator install_crunchy_postgres_ocp_operator diff --git a/e2e-tests/playwright.config.ts b/e2e-tests/playwright.config.ts index 387cd63689..b89032abdb 100644 --- a/e2e-tests/playwright.config.ts +++ b/e2e-tests/playwright.config.ts @@ -65,6 +65,7 @@ export default defineConfig({ "**/playwright/e2e/verify-tls-config-health-check.spec.ts", "**/playwright/e2e/configuration-test/config-map.spec.ts", "**/playwright/e2e/plugins/tekton/tekton.spec.ts", + "**/playwright/e2e/plugins/sonarkube/sonarkube-actions.spec.ts", ], }, { diff --git a/e2e-tests/playwright/e2e/plugins/sonarkube-actions/sonarkube-actions.spec.ts b/e2e-tests/playwright/e2e/plugins/sonarkube/sonarkube-actions.spec.ts similarity index 100% rename from e2e-tests/playwright/e2e/plugins/sonarkube-actions/sonarkube-actions.spec.ts rename to e2e-tests/playwright/e2e/plugins/sonarkube/sonarkube-actions.spec.ts From 37685e3d70f1752c12b04de9efe54f73be8dcc57 Mon Sep 17 00:00:00 2001 From: Oleksandr Andriienko Date: Thu, 24 Jul 2025 15:18:38 +0300 Subject: [PATCH 04/17] feat(e2e): evaluate sonarkube url Signed-off-by: Oleksandr Andriienko --- .../e2e/plugins/sonarkube/sonarkube-actions.spec.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/e2e-tests/playwright/e2e/plugins/sonarkube/sonarkube-actions.spec.ts b/e2e-tests/playwright/e2e/plugins/sonarkube/sonarkube-actions.spec.ts index 6f12537bc4..6cc16b2a8b 100644 --- a/e2e-tests/playwright/e2e/plugins/sonarkube/sonarkube-actions.spec.ts +++ b/e2e-tests/playwright/e2e/plugins/sonarkube/sonarkube-actions.spec.ts @@ -41,9 +41,12 @@ test.describe("Test SonarKube Actions plugin", () => { await uiHelper.waitForTitle("Create a SonarQube project", 2); + const baseRHDHURL: string = process.env.BASE_URL; + const host: string = new URL(baseRHDHURL).hostname; + const domain = host.split(".").slice(1).join("."); await uiHelper.fillTextInputByLabel( "Base URL *", - "https://sonarqube.apps.rosa.me4rm-zkeon-kfk.wq6f.p3.openshiftapps.com", + `https://sonarqube.${domain}`, ); await uiHelper.clickById("root_authParams__oneof_select"); From 9b20bdc53a144e3a2643d55f505ae28453565281 Mon Sep 17 00:00:00 2001 From: Oleksandr Andriienko Date: Thu, 24 Jul 2025 16:50:18 +0300 Subject: [PATCH 05/17] feat(e2e): fix installation script Signed-off-by: Oleksandr Andriienko --- .ibm/pipelines/utils.sh | 6 +++++- .../e2e/plugins/sonarkube/sonarkube-actions.spec.ts | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.ibm/pipelines/utils.sh b/.ibm/pipelines/utils.sh index c9a16c1275..3a0cd19195 100755 --- a/.ibm/pipelines/utils.sh +++ b/.ibm/pipelines/utils.sh @@ -634,7 +634,11 @@ install_sonar_qube() { helm repo add sonarqube https://SonarSource.github.io/helm-chart-sonarqube || true helm repo update - helm upgrade --install -n ${SONAR_QUBE_NS} sonarqube sonarqube/sonarqube --set edition=developer -f "${DIR}/resources/sonarqube/values.yaml" + SONARQUBE_HOST="sonarqube.${K8S_CLUSTER_ROUTER_BASE}" + helm upgrade --install -n ${SONAR_QUBE_NS} sonarqube sonarqube/sonarqube \ + --set edition=developer \ + --set OpenShift.route.host="${SONARQUBE_HOST}" \ + -f "${DIR}/resources/sonarqube/values.yaml" } ## todo implement uninstall_sonar_qube diff --git a/e2e-tests/playwright/e2e/plugins/sonarkube/sonarkube-actions.spec.ts b/e2e-tests/playwright/e2e/plugins/sonarkube/sonarkube-actions.spec.ts index 6cc16b2a8b..15aeb68e47 100644 --- a/e2e-tests/playwright/e2e/plugins/sonarkube/sonarkube-actions.spec.ts +++ b/e2e-tests/playwright/e2e/plugins/sonarkube/sonarkube-actions.spec.ts @@ -26,6 +26,7 @@ test.describe("Test SonarKube Actions plugin", () => { test("Creates kubernetes namespace", async ({ page }) => { await uiHelper.clickButton("Import an existing Git repository"); await catalogImport.registerExistingComponent(template, false); + test.setTimeout(5000); await uiHelper.clickLink({ ariaLabel: "Self-service" }); await common.waitForLoad(); From c2221edf0703404c863f46b2f785fa99742b2abe Mon Sep 17 00:00:00 2001 From: Oleksandr Andriienko Date: Fri, 25 Jul 2025 00:32:34 +0300 Subject: [PATCH 06/17] feat(e2e): remove sonarkube test from skip list Signed-off-by: Oleksandr Andriienko --- e2e-tests/playwright.config.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/e2e-tests/playwright.config.ts b/e2e-tests/playwright.config.ts index b89032abdb..387cd63689 100644 --- a/e2e-tests/playwright.config.ts +++ b/e2e-tests/playwright.config.ts @@ -65,7 +65,6 @@ export default defineConfig({ "**/playwright/e2e/verify-tls-config-health-check.spec.ts", "**/playwright/e2e/configuration-test/config-map.spec.ts", "**/playwright/e2e/plugins/tekton/tekton.spec.ts", - "**/playwright/e2e/plugins/sonarkube/sonarkube-actions.spec.ts", ], }, { From 4e820cff76693d8ca073e6cf80dace39bf3ee0af Mon Sep 17 00:00:00 2001 From: Oleksandr Andriienko Date: Fri, 25 Jul 2025 03:12:51 +0300 Subject: [PATCH 07/17] feat(e2e): try to fix test failing Signed-off-by: Oleksandr Andriienko --- .../e2e/plugins/sonarkube/sonarkube-actions.spec.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/e2e-tests/playwright/e2e/plugins/sonarkube/sonarkube-actions.spec.ts b/e2e-tests/playwright/e2e/plugins/sonarkube/sonarkube-actions.spec.ts index 15aeb68e47..f836da8f0a 100644 --- a/e2e-tests/playwright/e2e/plugins/sonarkube/sonarkube-actions.spec.ts +++ b/e2e-tests/playwright/e2e/plugins/sonarkube/sonarkube-actions.spec.ts @@ -23,15 +23,16 @@ test.describe("Test SonarKube Actions plugin", () => { await page.goto("/create"); }); - test("Creates kubernetes namespace", async ({ page }) => { + test("Creates Sonarkube project", async ({ page }) => { await uiHelper.clickButton("Import an existing Git repository"); await catalogImport.registerExistingComponent(template, false); - test.setTimeout(5000); + // test.setTimeout(5000); - await uiHelper.clickLink({ ariaLabel: "Self-service" }); - await common.waitForLoad(); + // await uiHelper.clickLink({ ariaLabel: "Self-service" }); + // await common.waitForLoad(); + await uiHelper.openCatalogSidebar("Component"); - await uiHelper.verifyHeading("Self-service"); + // await uiHelper.verifyHeading("Self-service"); await uiHelper.searchInputPlaceholder("Create a SonarQube project"); await uiHelper.verifyText("Create a SonarQube project"); From e0cffcda09526d3debf1213074012b2da9894c6e Mon Sep 17 00:00:00 2001 From: Oleksandr Andriienko Date: Fri, 25 Jul 2025 10:54:40 +0300 Subject: [PATCH 08/17] feat(e2e): try with timeout Signed-off-by: Oleksandr Andriienko --- .../playwright/e2e/plugins/sonarkube/sonarkube-actions.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e-tests/playwright/e2e/plugins/sonarkube/sonarkube-actions.spec.ts b/e2e-tests/playwright/e2e/plugins/sonarkube/sonarkube-actions.spec.ts index f836da8f0a..0eb818c2bd 100644 --- a/e2e-tests/playwright/e2e/plugins/sonarkube/sonarkube-actions.spec.ts +++ b/e2e-tests/playwright/e2e/plugins/sonarkube/sonarkube-actions.spec.ts @@ -26,7 +26,7 @@ test.describe("Test SonarKube Actions plugin", () => { test("Creates Sonarkube project", async ({ page }) => { await uiHelper.clickButton("Import an existing Git repository"); await catalogImport.registerExistingComponent(template, false); - // test.setTimeout(5000); + test.setTimeout(5000); // await uiHelper.clickLink({ ariaLabel: "Self-service" }); // await common.waitForLoad(); From d7deff99469cebdfdf3bbe962e57b0bb81e64bd6 Mon Sep 17 00:00:00 2001 From: Oleksandr Andriienko Date: Fri, 25 Jul 2025 11:58:39 +0300 Subject: [PATCH 09/17] feat(e2e): add more time for processing entity Signed-off-by: Oleksandr Andriienko --- .../e2e/plugins/sonarkube/sonarkube-actions.spec.ts | 11 +++++------ e2e-tests/playwright/utils/ui-helper.ts | 4 ++++ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/e2e-tests/playwright/e2e/plugins/sonarkube/sonarkube-actions.spec.ts b/e2e-tests/playwright/e2e/plugins/sonarkube/sonarkube-actions.spec.ts index 0eb818c2bd..ee5e420364 100644 --- a/e2e-tests/playwright/e2e/plugins/sonarkube/sonarkube-actions.spec.ts +++ b/e2e-tests/playwright/e2e/plugins/sonarkube/sonarkube-actions.spec.ts @@ -26,13 +26,12 @@ test.describe("Test SonarKube Actions plugin", () => { test("Creates Sonarkube project", async ({ page }) => { await uiHelper.clickButton("Import an existing Git repository"); await catalogImport.registerExistingComponent(template, false); - test.setTimeout(5000); + // allow catalog to process entity from github + await uiHelper.sleep(10000); - // await uiHelper.clickLink({ ariaLabel: "Self-service" }); - // await common.waitForLoad(); - await uiHelper.openCatalogSidebar("Component"); - - // await uiHelper.verifyHeading("Self-service"); + await uiHelper.clickLink({ ariaLabel: "Self-service" }); + await common.waitForLoad(); + await uiHelper.waitForTitle("Self-service"); await uiHelper.searchInputPlaceholder("Create a SonarQube project"); await uiHelper.verifyText("Create a SonarQube project"); diff --git a/e2e-tests/playwright/utils/ui-helper.ts b/e2e-tests/playwright/utils/ui-helper.ts index 3a938f8058..382648ac97 100644 --- a/e2e-tests/playwright/utils/ui-helper.ts +++ b/e2e-tests/playwright/utils/ui-helper.ts @@ -365,6 +365,10 @@ export class UIhelper { await this.page.waitForSelector(`text=${text}`, { state: "detached" }); } + async sleep(timeoutMs: number) { + await this.page.waitForTimeout(timeoutMs); + } + async verifyText(text: string | RegExp, exact: boolean = true) { await this.verifyTextInLocator("", text, exact); } From 842bd3ce1ee49b081cc526abde730f77c9970a4e Mon Sep 17 00:00:00 2001 From: Oleksandr Andriienko Date: Fri, 25 Jul 2025 13:58:59 +0300 Subject: [PATCH 10/17] feat(e2e): enable sonarqube-scaffolder-module Signed-off-by: Oleksandr Andriienko --- .ibm/pipelines/value_files/values_showcase.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.ibm/pipelines/value_files/values_showcase.yaml b/.ibm/pipelines/value_files/values_showcase.yaml index cdfddc0d98..7f4dcb9837 100644 --- a/.ibm/pipelines/value_files/values_showcase.yaml +++ b/.ibm/pipelines/value_files/values_showcase.yaml @@ -12,6 +12,8 @@ global: # an optional `pluginConfig` with plugin-specific backstage configuration, and an optional `disabled` flag to disable/enable a plugin # listed in `includes` files. It also includes an `integrity` field that is used to verify the plugin package [integrity](https://w3c.github.io/webappsec-subresource-integrity/#integrity-metadata-description). plugins: + - package: ./dynamic-plugins/dist/backstage-community-plugin-scaffolder-backend-module-sonarqube-dynamic + disabled: false - package: oci://quay.io/gashcrumb/example-root-http-middleware:latest!internal-backstage-plugin-middleware-header-example-dynamic disabled: false pluginConfig: From 01d2ff5e3258543263f198b0aaabc0d3ff6acc86 Mon Sep 17 00:00:00 2001 From: Oleksandr Andriienko Date: Fri, 25 Jul 2025 14:55:31 +0300 Subject: [PATCH 11/17] temp Signed-off-by: Oleksandr Andriienko --- .ibm/pipelines/resources/sonarqube/values.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.ibm/pipelines/resources/sonarqube/values.yaml b/.ibm/pipelines/resources/sonarqube/values.yaml index 5a7c79b70a..a1be037c24 100644 --- a/.ibm/pipelines/resources/sonarqube/values.yaml +++ b/.ibm/pipelines/resources/sonarqube/values.yaml @@ -1,3 +1,10 @@ +account: + adminPassword: NewAdminPassword1@ + currentAdminPassword: admin + +community: + enabled: true + OpenShift: enabled: true serviceAccount: From f4c2b7a13f058436d4e04f465223ff4995eb099e Mon Sep 17 00:00:00 2001 From: Oleksandr Andriienko Date: Fri, 25 Jul 2025 15:20:48 +0300 Subject: [PATCH 12/17] feat(e2e): work with sonarqube helm Signed-off-by: Oleksandr Andriienko --- .ibm/pipelines/resources/sonarqube/values.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.ibm/pipelines/resources/sonarqube/values.yaml b/.ibm/pipelines/resources/sonarqube/values.yaml index a1be037c24..e3bbc6ad9f 100644 --- a/.ibm/pipelines/resources/sonarqube/values.yaml +++ b/.ibm/pipelines/resources/sonarqube/values.yaml @@ -2,9 +2,6 @@ account: adminPassword: NewAdminPassword1@ currentAdminPassword: admin -community: - enabled: true - OpenShift: enabled: true serviceAccount: From 9b50c37b95554e0ce617ab6166bf0f653bc0da2b Mon Sep 17 00:00:00 2001 From: Oleksandr Andriienko Date: Mon, 1 Sep 2025 05:44:38 +0300 Subject: [PATCH 13/17] fix(e2e): work on code review feedback Signed-off-by: Oleksandr Andriienko --- .ibm/pipelines/resources/sonarqube/install.sh | 46 +++++++++++++++++-- .../pipelines/resources/sonarqube/values.yaml | 3 ++ .ibm/pipelines/utils.sh | 19 ++++---- .../sonarkube/sonarkube-actions.spec.ts | 3 -- e2e-tests/playwright/utils/ui-helper.ts | 4 -- 5 files changed, 55 insertions(+), 20 deletions(-) mode change 100644 => 100755 .ibm/pipelines/resources/sonarqube/install.sh diff --git a/.ibm/pipelines/resources/sonarqube/install.sh b/.ibm/pipelines/resources/sonarqube/install.sh old mode 100644 new mode 100755 index 74c13e9c06..368a12930f --- a/.ibm/pipelines/resources/sonarqube/install.sh +++ b/.ibm/pipelines/resources/sonarqube/install.sh @@ -1,6 +1,46 @@ #!/bin/bash -helm repo add sonarqube https://SonarSource.github.io/helm-chart-sonarqube +# Defaults +NAMESPACE="sonarqube" +VALUES_FILE="$(dirname "$0")/values.yaml" +EDITION="developer" +HOST="" + +# Parse arguments +while [[ "$#" -gt 0 ]]; do + case $1 in + --namespace) NAMESPACE="$2"; shift ;; + --values) VALUES_FILE="$2"; shift ;; + --edition) EDITION="$2"; shift ;; + --host) HOST="$2"; shift ;; + *) echo "Unknown parameter passed: $1"; exit 1 ;; + esac + shift +done + +# Create namespace if it doesn't exist +kubectl get namespace "$NAMESPACE" > /dev/null || kubectl create namespace "$NAMESPACE" + +helm repo add sonarqube https://SonarSource.github.io/helm-chart-sonarqube --force-update helm repo update -kubectl create namespace sonarqube -helm upgrade --install -n sonarqube sonarqube sonarqube/sonarqube --set edition=developer -f values.yaml + +HELM_ARGS="--install -n ${NAMESPACE} sonarqube sonarqube/sonarqube" +HELM_ARGS="${HELM_ARGS} --set edition=${EDITION}" + +if [ -f "${VALUES_FILE}" ]; then + HELM_ARGS="${HELM_ARGS} -f ${VALUES_FILE}" +else + echo "Warning: Values file not found at ${VALUES_FILE}" +fi + +if [ -n "${HOST}" ]; then + HELM_ARGS="${HELM_ARGS} --set OpenShift.route.host=${HOST}" +fi + +HELM_ARGS="${HELM_ARGS} --set postgresql.image.repository=bitnami/postgresql" +HELM_ARGS="${HELM_ARGS} --set postgresql.image.tag=15.3.0" +# To use an external PostgreSQL, uncomment the following line +# HELM_ARGS="${HELM_ARGS} --set postgresql.enabled=false" + +# shellcheck disable=SC2086 +helm upgrade ${HELM_ARGS} diff --git a/.ibm/pipelines/resources/sonarqube/values.yaml b/.ibm/pipelines/resources/sonarqube/values.yaml index e3bbc6ad9f..6233986852 100644 --- a/.ibm/pipelines/resources/sonarqube/values.yaml +++ b/.ibm/pipelines/resources/sonarqube/values.yaml @@ -17,6 +17,9 @@ OpenShift: monitoringPasscode: "define_it" postgresql: + image: + repository: bitnami/postgresql + tag: 15.3.0 securityContext: enabled: false containerSecurityContext: diff --git a/.ibm/pipelines/utils.sh b/.ibm/pipelines/utils.sh index 3a0cd19195..3fcb54912f 100755 --- a/.ibm/pipelines/utils.sh +++ b/.ibm/pipelines/utils.sh @@ -629,16 +629,14 @@ install_pipelines_operator() { } install_sonar_qube() { - SONAR_QUBE_NS="sonarqube" - configure_namespace ${SONAR_QUBE_NS} - helm repo add sonarqube https://SonarSource.github.io/helm-chart-sonarqube || true - helm repo update - - SONARQUBE_HOST="sonarqube.${K8S_CLUSTER_ROUTER_BASE}" - helm upgrade --install -n ${SONAR_QUBE_NS} sonarqube sonarqube/sonarqube \ - --set edition=developer \ - --set OpenShift.route.host="${SONARQUBE_HOST}" \ - -f "${DIR}/resources/sonarqube/values.yaml" + local namespace="sonarqube" + local host="sonarqube.${K8S_CLUSTER_ROUTER_BASE}" + local values_file="${DIR}/resources/sonarqube/values.yaml" + + "${DIR}/resources/sonarqube/install.sh" \ + --namespace "$namespace" \ + --host "$host" \ + --values "$values_file" } ## todo implement uninstall_sonar_qube @@ -686,6 +684,7 @@ cluster_setup_ocp_helm() { } cluster_setup_ocp_operator() { + install_sonar_qube install_pipelines_operator install_acm_ocp_operator install_crunchy_postgres_ocp_operator diff --git a/e2e-tests/playwright/e2e/plugins/sonarkube/sonarkube-actions.spec.ts b/e2e-tests/playwright/e2e/plugins/sonarkube/sonarkube-actions.spec.ts index ee5e420364..bb3b7f5be7 100644 --- a/e2e-tests/playwright/e2e/plugins/sonarkube/sonarkube-actions.spec.ts +++ b/e2e-tests/playwright/e2e/plugins/sonarkube/sonarkube-actions.spec.ts @@ -26,8 +26,6 @@ test.describe("Test SonarKube Actions plugin", () => { test("Creates Sonarkube project", async ({ page }) => { await uiHelper.clickButton("Import an existing Git repository"); await catalogImport.registerExistingComponent(template, false); - // allow catalog to process entity from github - await uiHelper.sleep(10000); await uiHelper.clickLink({ ariaLabel: "Self-service" }); await common.waitForLoad(); @@ -60,7 +58,6 @@ test.describe("Test SonarKube Actions plugin", () => { await uiHelper.fillInputWithLabel("root_key", projectKey); await uiHelper.fillTextInputByLabel("Branch", "main"); await uiHelper.clickButton("Review"); - await page.waitForTimeout(5000); await uiHelper.clickButton("Create"); await uiHelper.clickLinkWithNewTab(/SonarQube project URL/i); diff --git a/e2e-tests/playwright/utils/ui-helper.ts b/e2e-tests/playwright/utils/ui-helper.ts index 382648ac97..3a938f8058 100644 --- a/e2e-tests/playwright/utils/ui-helper.ts +++ b/e2e-tests/playwright/utils/ui-helper.ts @@ -365,10 +365,6 @@ export class UIhelper { await this.page.waitForSelector(`text=${text}`, { state: "detached" }); } - async sleep(timeoutMs: number) { - await this.page.waitForTimeout(timeoutMs); - } - async verifyText(text: string | RegExp, exact: boolean = true) { await this.verifyTextInLocator("", text, exact); } From 2b6f51536c632d0ae341cc47fe3b352c562c1a74 Mon Sep 17 00:00:00 2001 From: Oleksandr Andriienko Date: Mon, 1 Sep 2025 11:49:53 +0300 Subject: [PATCH 14/17] feat(e2e): handle code review feedback Signed-off-by: Oleksandr Andriienko --- .../e2e/plugins/sonarkube/sonarkube-actions.spec.ts | 12 ++++++------ e2e-tests/playwright/utils/ui-helper.ts | 4 ---- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/e2e-tests/playwright/e2e/plugins/sonarkube/sonarkube-actions.spec.ts b/e2e-tests/playwright/e2e/plugins/sonarkube/sonarkube-actions.spec.ts index bb3b7f5be7..087c129247 100644 --- a/e2e-tests/playwright/e2e/plugins/sonarkube/sonarkube-actions.spec.ts +++ b/e2e-tests/playwright/e2e/plugins/sonarkube/sonarkube-actions.spec.ts @@ -23,7 +23,7 @@ test.describe("Test SonarKube Actions plugin", () => { await page.goto("/create"); }); - test("Creates Sonarkube project", async ({ page }) => { + test("Creates Sonarkube project", async () => { await uiHelper.clickButton("Import an existing Git repository"); await catalogImport.registerExistingComponent(template, false); @@ -43,20 +43,20 @@ test.describe("Test SonarKube Actions plugin", () => { const baseRHDHURL: string = process.env.BASE_URL; const host: string = new URL(baseRHDHURL).hostname; const domain = host.split(".").slice(1).join("."); - await uiHelper.fillTextInputByLabel( - "Base URL *", - `https://sonarqube.${domain}`, + await uiHelper.fillInputWithLabel( + "root_baseUrl", + `https://sonar.${domain}`, ); await uiHelper.clickById("root_authParams__oneof_select"); await uiHelper.selectDropDownOption("Username and Password"); - await uiHelper.fillTextInputByLabel("Username *", "admin"); + await uiHelper.fillInputWithLabel("root_authParams_username", "admin"); await uiHelper.fillInputWithLabel("Password", "NewAdminPassword1@"); await uiHelper.fillInputWithLabel("root_name", project); await uiHelper.fillInputWithLabel("root_key", projectKey); - await uiHelper.fillTextInputByLabel("Branch", "main"); + await uiHelper.fillInputWithLabel("root_branch", "main"); await uiHelper.clickButton("Review"); await uiHelper.clickButton("Create"); await uiHelper.clickLinkWithNewTab(/SonarQube project URL/i); diff --git a/e2e-tests/playwright/utils/ui-helper.ts b/e2e-tests/playwright/utils/ui-helper.ts index 3a938f8058..2385978ce4 100644 --- a/e2e-tests/playwright/utils/ui-helper.ts +++ b/e2e-tests/playwright/utils/ui-helper.ts @@ -20,10 +20,6 @@ export class UIhelper { return this.page.getByTestId("login-button").getByText(menuItem); } - async fillTextInputByLabel(label: string, text: string) { - await this.page.getByLabel(label).fill(text); - } - /** * Fills the search input with the provided text. * From f93777ded6e212a40a2f02f82ca78837d880b666 Mon Sep 17 00:00:00 2001 From: Oleksandr Andriienko Date: Mon, 1 Sep 2025 13:12:41 +0300 Subject: [PATCH 15/17] feat(e2e): handle code review feedback Signed-off-by: Oleksandr Andriienko --- .../sonarkube/sonarkube-actions.spec.ts | 6 ++++-- e2e-tests/playwright/utils/ui-helper.ts | 20 ++++--------------- 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/e2e-tests/playwright/e2e/plugins/sonarkube/sonarkube-actions.spec.ts b/e2e-tests/playwright/e2e/plugins/sonarkube/sonarkube-actions.spec.ts index 087c129247..885ac928dc 100644 --- a/e2e-tests/playwright/e2e/plugins/sonarkube/sonarkube-actions.spec.ts +++ b/e2e-tests/playwright/e2e/plugins/sonarkube/sonarkube-actions.spec.ts @@ -48,8 +48,10 @@ test.describe("Test SonarKube Actions plugin", () => { `https://sonar.${domain}`, ); - await uiHelper.clickById("root_authParams__oneof_select"); - await uiHelper.selectDropDownOption("Username and Password"); + await uiHelper.selectMuiBox( + "root_authParams__oneof_select", + "Username and Password", + ); await uiHelper.fillInputWithLabel("root_authParams_username", "admin"); await uiHelper.fillInputWithLabel("Password", "NewAdminPassword1@"); diff --git a/e2e-tests/playwright/utils/ui-helper.ts b/e2e-tests/playwright/utils/ui-helper.ts index 2385978ce4..fef97b3e68 100644 --- a/e2e-tests/playwright/utils/ui-helper.ts +++ b/e2e-tests/playwright/utils/ui-helper.ts @@ -341,8 +341,10 @@ export class UIhelper { await navLink.click(); } - async selectMuiBox(label: string, value: string) { - await this.page.click(`div[aria-label="${label}"]`); + async selectMuiBox(labelOrId: string, value: string) { + await this.page.click( + `div[aria-label="${labelOrId}"], div[id="${labelOrId}"]`, + ); const optionSelector = `li[role="option"]:has-text("${value}")`; await this.page.waitForSelector(optionSelector); await this.page.click(optionSelector); @@ -701,20 +703,6 @@ export class UIhelper { await locator.click(); } - async selectDropDownOption(optionText: string) { - const dropdown = this.page.getByRole("listbox"); - await expect(dropdown).toBeVisible({ timeout: 5000 }); - - await this.page.waitForSelector(`li:has-text("${optionText}")`, { - timeout: 5000, - }); - - const optionItem = await this.page.waitForSelector( - `li:has-text("${optionText}")`, - ); - optionItem.click(); - } - async clickSpanByText(text: string) { await this.verifyText(text); await this.page.click(`span:has-text("${text}")`); From 9fdd6e5aee3b53f9af01b64f2b7b1de4fcc873be Mon Sep 17 00:00:00 2001 From: Oleksandr Andriienko Date: Mon, 1 Sep 2025 13:17:56 +0300 Subject: [PATCH 16/17] feat(e2e): add sonarqube script's installation README file Signed-off-by: Oleksandr Andriienko --- .ibm/pipelines/resources/sonarqube/README.md | 43 ++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 .ibm/pipelines/resources/sonarqube/README.md diff --git a/.ibm/pipelines/resources/sonarqube/README.md b/.ibm/pipelines/resources/sonarqube/README.md new file mode 100644 index 0000000000..cd8d9bead0 --- /dev/null +++ b/.ibm/pipelines/resources/sonarqube/README.md @@ -0,0 +1,43 @@ +# SonarQube Installation Script + +This script installs a SonarQube instance in an OpenShift cluster using the official SonarQube Helm chart. + +## Prerequisites + +- `kubectl` or `oc` CLI installed and configured to connect to your OpenShift cluster. +- `helm` CLI installed. + +## Usage + +The `install.sh` script supports the following parameters: + +- `--namespace`: The namespace where SonarQube will be installed. (Default: `sonarqube`) +- `--values`: Path to a custom values file for Helm chart customization. (Default: `values.yaml` in the same directory) +- `--edition`: The SonarQube edition to install. (Default: `developer`) +- `--host`: The hostname for the OpenShift route. This is a mandatory parameter. + +### Example + +```shell +./install.sh --host sonar. +``` + +### Example with custom namespace + +```shell +./install.sh --namespace my-sonarqube --host sonar. +``` + +## Configuration + +The script uses a `values.yaml` file to configure the SonarQube Helm chart. You can modify this file to customize your SonarQube installation. For example, you can configure resource limits, persistence, and other chart values. + +### External PostgreSQL + +By default, the script installs a PostgreSQL database as part of the Helm release. To use an external PostgreSQL database, you can uncomment the following line in the `install.sh` script: + +```shell +# HELM_ARGS="${HELM_ARGS} --set postgresql.enabled=false" +``` + +You will also need to provide the connection details for your external database in the `values.yaml` file. From 35414afd95ac4f1f25f6a0b921f51dd6877dc6b1 Mon Sep 17 00:00:00 2001 From: Oleksandr Andriienko Date: Mon, 1 Sep 2025 14:59:16 +0300 Subject: [PATCH 17/17] feat(e2e): work on code review feedback Signed-off-by: Oleksandr Andriienko --- .../plugins/sonarkube/sonarkube-actions.spec.ts | 15 ++++++--------- e2e-tests/playwright/utils/ui-helper.ts | 8 ++++---- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/e2e-tests/playwright/e2e/plugins/sonarkube/sonarkube-actions.spec.ts b/e2e-tests/playwright/e2e/plugins/sonarkube/sonarkube-actions.spec.ts index 885ac928dc..c55774c0c0 100644 --- a/e2e-tests/playwright/e2e/plugins/sonarkube/sonarkube-actions.spec.ts +++ b/e2e-tests/playwright/e2e/plugins/sonarkube/sonarkube-actions.spec.ts @@ -43,22 +43,19 @@ test.describe("Test SonarKube Actions plugin", () => { const baseRHDHURL: string = process.env.BASE_URL; const host: string = new URL(baseRHDHURL).hostname; const domain = host.split(".").slice(1).join("."); - await uiHelper.fillInputWithLabel( - "root_baseUrl", - `https://sonar.${domain}`, - ); + await uiHelper.fillTextInputByLabel("Base URL", `https://sonar.${domain}`); await uiHelper.selectMuiBox( "root_authParams__oneof_select", "Username and Password", ); - await uiHelper.fillInputWithLabel("root_authParams_username", "admin"); - await uiHelper.fillInputWithLabel("Password", "NewAdminPassword1@"); + await uiHelper.fillTextInputByLabel("Username *", "admin"); + await uiHelper.fillTextInputByLabel("Password", "NewAdminPassword1@", true); - await uiHelper.fillInputWithLabel("root_name", project); - await uiHelper.fillInputWithLabel("root_key", projectKey); - await uiHelper.fillInputWithLabel("root_branch", "main"); + await uiHelper.fillTextInputByLabel("Name *", project, true); + await uiHelper.fillTextInputByLabel("Key *", projectKey); + await uiHelper.fillTextInputByLabel("Branch", "main"); await uiHelper.clickButton("Review"); await uiHelper.clickButton("Create"); await uiHelper.clickLinkWithNewTab(/SonarQube project URL/i); diff --git a/e2e-tests/playwright/utils/ui-helper.ts b/e2e-tests/playwright/utils/ui-helper.ts index fef97b3e68..d120ca9b50 100644 --- a/e2e-tests/playwright/utils/ui-helper.ts +++ b/e2e-tests/playwright/utils/ui-helper.ts @@ -20,6 +20,10 @@ export class UIhelper { return this.page.getByTestId("login-button").getByText(menuItem); } + async fillTextInputByLabel(label: string, text: string, exact?: boolean) { + await this.page.getByLabel(label, { exact }).fill(text); + } + /** * Fills the search input with the provided text. * @@ -36,10 +40,6 @@ export class UIhelper { await this.page.fill(SEARCH_OBJECTS_COMPONENTS.ariaLabelSearch, searchText); } - async fillInputWithLabel(label: string, input: string) { - await this.page.locator(`input#${label}`).fill(input); - } - async pressTab() { await this.page.keyboard.press("Tab"); }