diff --git a/.ibm/pipelines/openshift-ci-tests.sh b/.ibm/pipelines/openshift-ci-tests.sh index f30caf70dd..9df0637e34 100755 --- a/.ibm/pipelines/openshift-ci-tests.sh +++ b/.ibm/pipelines/openshift-ci-tests.sh @@ -135,4 +135,4 @@ main() { exit "${OVERALL_RESULT}" } -main +main \ No newline at end of file diff --git a/e2e-tests/playwright/e2e/plugins/notifications/filter-notification-severity-critical.spec.ts b/e2e-tests/playwright/e2e/plugins/notifications/filter-notification-severity-critical.spec.ts deleted file mode 100644 index ca94cb6f3b..0000000000 --- a/e2e-tests/playwright/e2e/plugins/notifications/filter-notification-severity-critical.spec.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { test } from "@playwright/test"; -import { UIhelper } from "../../../utils/ui-helper"; -import { Common } from "../../../utils/common"; -import RhdhNotficationsApi from "../../../support/api/notifications"; -import { Notifications } from "../../../support/api/notifications-api-structures"; -import { RhdhAuthApiHack } from "../../../support/api/rhdh-auth-api-hack"; - -import { NotificationPage } from "../../../support/pages/notifications"; - -test.describe("Filter critical notification tests", () => { - let uiHelper: UIhelper; - let common: Common; - let notificationPage: NotificationPage; - - test.beforeAll(async () => { - test.info().annotations.push({ - type: "component", - description: "integration", - }); - }); - - test.beforeEach(async ({ page }) => { - uiHelper = new UIhelper(page); - common = new Common(page); - notificationPage = new NotificationPage(page); - await common.loginAsKeycloakUser(); - await RhdhAuthApiHack.getToken(page); - }); - - test("Fiter notifcations by serverity - critical", async () => { - const r = (Math.random() + 1).toString(36).substring(7); - const severity = "critical"; - const notificationsApi = await RhdhNotficationsApi.build("test-token"); - // Used boradcast here, but we should use type: entity and entityRef: ["user:/"] - const notification: Notifications = { - recipients: { - type: "broadcast", - entityRef: [""], - }, - payload: { - title: `UI Notification Mark all as read ${severity}-${r}`, - description: `Test UI Notification Mark all as read ${severity}-${r}`, - severity: severity, - topic: `Testing UI Notification Mark all as read ${severity}-${r}`, - }, - }; - await notificationsApi.createNotification(notification); - await uiHelper.openSidebar("Notifications"); - await notificationPage.selectSeverity("Critical"); - await notificationPage.notificationContains( - `UI Notification Mark all as read ${severity}-${r}`, - ); - }); -}); diff --git a/e2e-tests/playwright/e2e/plugins/notifications/filter-notifications-by-severity.spec.ts b/e2e-tests/playwright/e2e/plugins/notifications/filter-notifications-by-severity.spec.ts new file mode 100644 index 0000000000..cebb2bd616 --- /dev/null +++ b/e2e-tests/playwright/e2e/plugins/notifications/filter-notifications-by-severity.spec.ts @@ -0,0 +1,49 @@ +import { test } from "@playwright/test"; +import { UIhelper } from "../../../utils/ui-helper"; +import { Common } from "../../../utils/common"; +import RhdhNotficationsApi from "../../../support/api/notifications"; +import { Notifications } from "../../../support/api/notifications-api-structures"; +import { NotificationPage } from "../../../support/pages/notifications"; + +test.describe("Filter critical notification tests", () => { + let uiHelper: UIhelper; + let common: Common; + let notificationPage: NotificationPage; + let apiToken: string; + + const severities = ["Critical", "High", "Normal", "Low"]; + + test.beforeEach(async ({ page }) => { + uiHelper = new UIhelper(page); + common = new Common(page); + notificationPage = new NotificationPage(page); + await common.loginAsKeycloakUser(); + apiToken = "test-token"; + }); + + for (const severity of severities) { + test(`Filter notifications by severity - ${severity}`, async () => { + const r = (Math.random() + 1).toString(36).substring(7); + const notificationsApi = await RhdhNotficationsApi.build(apiToken); + const notificationTitle = "UI Notification By Severity"; + const notification: Notifications = { + recipients: { + type: "broadcast", + entityRef: [""], + }, + payload: { + title: `${notificationTitle} ${severity}-${r}`, + description: `Test ${notificationTitle} ${severity}-${r}`, + severity: severity, + topic: `Testing ${notificationTitle} ${severity}-${r}`, + }, + }; + await notificationsApi.createNotification(notification); + await uiHelper.openSidebar("Notifications"); + await notificationPage.selectSeverity(severity); + await notificationPage.notificationContains( + `${notificationTitle} ${severity}-${r}`, + ); + }); + } +}); diff --git a/e2e-tests/playwright/e2e/plugins/notifications/mark-notifications.spec.ts b/e2e-tests/playwright/e2e/plugins/notifications/mark-notifications.spec.ts new file mode 100644 index 0000000000..7d4cee189a --- /dev/null +++ b/e2e-tests/playwright/e2e/plugins/notifications/mark-notifications.spec.ts @@ -0,0 +1,104 @@ +import { test } from "@playwright/test"; +import { UIhelper } from "../../../utils/ui-helper"; +import { Common } from "../../../utils/common"; +import RhdhNotificationsApi from "../../../support/api/notifications"; +import { Notifications } from "../../../support/api/notifications-api-structures"; +import { NotificationPage } from "../../../support/pages/notifications"; + +test.describe("Mark notification tests", () => { + let uiHelper: UIhelper; + let common: Common; + let notificationPage: NotificationPage; + let apiToken: string; + + test.beforeEach(async ({ page }) => { + uiHelper = new UIhelper(page); + common = new Common(page); + notificationPage = new NotificationPage(page); + await common.loginAsKeycloakUser(); + apiToken = "test-token"; + }); + + test("Mark notification as read", async () => { + const r = (Math.random() + 1).toString(36).substring(7); + const notificationsApi = await RhdhNotificationsApi.build(apiToken); + const notificationTitle = `UI Notification Mark as read`; + const notification: Notifications = { + recipients: { + type: "broadcast", + entityRef: [""], + }, + payload: { + title: `${notificationTitle}-${r}`, + description: `Test ${notificationTitle}-${r}`, + severity: "Normal", + topic: `Testing ${notificationTitle}-${r}`, + }, + }; + await notificationsApi.createNotification(notification); + await uiHelper.openSidebar("Notifications"); + await notificationPage.notificationContains(`${notificationTitle}-${r}`); + await notificationPage.markNotificationAsRead(`${notificationTitle}-${r}`); + await notificationPage.viewRead(); + await notificationPage.notificationContains( + RegExp(`${notificationTitle}-${r}.*(a few seconds ago)|(a minute ago)`), + ); + }); + + test("Mark notification as unread", async () => { + const r = (Math.random() + 1).toString(36).substring(7); + const notificationsApi = await RhdhNotificationsApi.build(apiToken); + const notificationTitle = `UI Notification Mark as unread`; + const notification: Notifications = { + recipients: { + type: "broadcast", + entityRef: [""], + }, + payload: { + title: `${notificationTitle}-${r}`, + description: `Test ${notificationTitle}-${r}`, + severity: "Normal", + topic: `Testing ${notificationTitle}-${r}`, + }, + }; + await notificationsApi.createNotification(notification); + await uiHelper.openSidebar("Notifications"); + await notificationPage.notificationContains(`${notificationTitle}-${r}`); + await notificationPage.markNotificationAsRead(`${notificationTitle}-${r}`); + await notificationPage.viewRead(); + await notificationPage.notificationContains( + RegExp(`${notificationTitle}-${r}.*(a few seconds ago)|(a minute ago)`), + ); + await notificationPage.markLastNotificationAsUnRead(); + await notificationPage.viewUnRead(); + await notificationPage.notificationContains( + RegExp(`${notificationTitle}-${r}.*(a few seconds ago)|(a minute ago)`), + ); + }); + + test("Mark notification as saved", async () => { + const r = (Math.random() + 1).toString(36).substring(7); + const notificationsApi = await RhdhNotificationsApi.build(apiToken); + const notificationTitle = `UI Notification Mark as saved`; + const notification: Notifications = { + recipients: { + type: "broadcast", + entityRef: [""], + }, + payload: { + title: `${notificationTitle}-${r}`, + description: `Test ${notificationTitle}-${r}`, + severity: "Normal", + topic: `Testing ${notificationTitle}-${r}`, + }, + }; + await notificationsApi.createNotification(notification); + await uiHelper.openSidebar("Notifications"); + await notificationPage.selectNotification(); + await notificationPage.saveSelected(); + await notificationPage.viewSaved(); + await notificationPage.notificationContains( + RegExp(`${notificationTitle}-${r}.*(a few seconds ago)|(a minute ago)`), + ); + }); +}); diff --git a/e2e-tests/playwright/e2e/plugins/orchestrator/greeting-workflow.spec.ts b/e2e-tests/playwright/e2e/plugins/orchestrator/greeting-workflow.spec.ts new file mode 100644 index 0000000000..6adfc29a6d --- /dev/null +++ b/e2e-tests/playwright/e2e/plugins/orchestrator/greeting-workflow.spec.ts @@ -0,0 +1,33 @@ +import { test } from "@playwright/test"; +import { UIhelper } from "../../../utils/ui-helper"; +import { Common } from "../../../utils/common"; +import { Orchestrator } from "../../../support/pages/orchestrator"; + +test.describe("Orchestrator greeting workflow tests", () => { + let uiHelper: UIhelper; + let common: Common; + let orchestrator: Orchestrator; + + test.beforeEach(async ({ page }) => { + uiHelper = new UIhelper(page); + common = new Common(page); + orchestrator = new Orchestrator(page); + await common.loginAsKeycloakUser(); + }); + + test("Greeting workflow execution and workflow tab validation", async () => { + await uiHelper.openSidebar("Orchestrator"); + await orchestrator.selectGreetingWorkflowItem(); + await orchestrator.runGreetingWorkflow(); + await uiHelper.openSidebar("Orchestrator"); + await orchestrator.validateGreetingWorkflow(); + }); + + test("Greeting workflow run details validation", async () => { + await uiHelper.openSidebar("Orchestrator"); + await orchestrator.selectGreetingWorkflowItem(); + await orchestrator.runGreetingWorkflow(); + await orchestrator.reRunGreetingWorkflow(); + await orchestrator.validateWorkflowRunsDetails(); + }); +}); diff --git a/e2e-tests/playwright/e2e/plugins/orchestrator/workflow-all-runs-validations.spec.ts b/e2e-tests/playwright/e2e/plugins/orchestrator/workflow-all-runs-validations.spec.ts new file mode 100644 index 0000000000..9280d42f83 --- /dev/null +++ b/e2e-tests/playwright/e2e/plugins/orchestrator/workflow-all-runs-validations.spec.ts @@ -0,0 +1,22 @@ +import { test } from "@playwright/test"; +import { UIhelper } from "../../../utils/ui-helper"; +import { Common } from "../../../utils/common"; +import { Orchestrator } from "../../../support/pages/orchestrator"; + +test.describe("Orchestrator Workflow Runs tests", () => { + let uiHelper: UIhelper; + let common: Common; + let orchestrator: Orchestrator; + + test.beforeEach(async ({ page }) => { + uiHelper = new UIhelper(page); + common = new Common(page); + orchestrator = new Orchestrator(page); + await common.loginAsKeycloakUser(); + }); + + test("Workflow All Runs Validation", async () => { + await uiHelper.openSidebar("Orchestrator"); + await orchestrator.validateWorkflowAllRuns(); + }); +}); diff --git a/e2e-tests/playwright/support/pages/notifications.ts b/e2e-tests/playwright/support/pages/notifications.ts index b08948383a..35a299f2e8 100644 --- a/e2e-tests/playwright/support/pages/notifications.ts +++ b/e2e-tests/playwright/support/pages/notifications.ts @@ -12,6 +12,12 @@ export class NotificationPage { async clickNotificationsNavBarItem() { await this.uiHelper.openSidebar("Notifications"); + await expect( + this.page.getByRole("table").filter({ hasText: "Rows per page" }), + ).toBeVisible(); + await expect( + this.page.getByTestId("loading-indicator").getByRole("img"), + ).toHaveCount(0); } async notificationContains(text: string | RegExp) { @@ -25,10 +31,6 @@ export class NotificationPage { await expect(row).toHaveCount(1); } - async notificationTextExists(text: string | RegExp) { - await expect(this.page.getByRole("cell", { name: text })).toHaveCount(1); - } - async clickNotificationHeadingLink(text: string | RegExp) { await this.page .getByRole("cell", { name: text, exact: true }) @@ -119,13 +121,9 @@ export class NotificationPage { async viewRead() { await this.page.getByLabel("View").click(); - if (`${process.env.MILESTONE}` == "5") { - await this.page - .getByRole("option", { name: "Read notifications", exact: true }) - .click(); - } else { - await this.page.getByRole("option", { name: "Marked as read" }).click(); - } + await this.page + .getByRole("option", { name: "Read notifications", exact: true }) + .click(); await expect( this.page.getByTestId("loading-indicator").getByRole("img"), ).toHaveCount(0); @@ -133,13 +131,9 @@ export class NotificationPage { async viewUnRead() { await this.page.getByLabel("View").click(); - if (`${process.env.MILESTONE}` == "5") { - await this.page - .getByRole("option", { name: "Unread notifications", exact: true }) - .click(); - } else { - await this.page.getByRole("option", { name: "New only" }).click(); - } + await this.page + .getByRole("option", { name: "Unread notifications", exact: true }) + .click(); await expect( this.page.getByTestId("loading-indicator").getByRole("img"), ).toHaveCount(0); diff --git a/e2e-tests/playwright/support/pages/orchestrator.ts b/e2e-tests/playwright/support/pages/orchestrator.ts index 96fa385e57..93b695d8e2 100644 --- a/e2e-tests/playwright/support/pages/orchestrator.ts +++ b/e2e-tests/playwright/support/pages/orchestrator.ts @@ -1,14 +1,11 @@ import { expect, type Page } from "@playwright/test"; -import { UIhelper } from "../../utils/ui-helper"; import Workflows from "./workflows"; export class Orchestrator { private readonly page: Page; - private readonly uiHelper: UIhelper; constructor(page: Page) { this.page = page; - this.uiHelper = new UIhelper(page); } async openWorkflowAlert() { @@ -20,15 +17,161 @@ export class Orchestrator { async closeWorkflowAlert() { await this.page.getByRole("alert").getByRole("button").nth(2).click(); } + async selectGreetingWorkflowItem() { + const workflowHeader = this.page.getByRole("heading", { + name: "Workflows", + }); + await expect(workflowHeader).toBeVisible(); + await expect(workflowHeader).toHaveText("Workflows"); + await expect(Workflows.workflowsTable(this.page)).toBeVisible(); + await this.page.getByRole("link", { name: "Greeting workflow" }).click(); + } - async selectUserOnboardingWorkflowItem() { + async runGreetingWorkflow(language = "English", status = "Completed") { + const runButton = this.page.getByRole("button", { name: "Run" }); + await expect(runButton).toBeVisible(); + await runButton.click(); + await this.page.getByLabel("Language").click(); + await this.page.getByRole("option", { name: language }).click(); + await this.page.getByRole("button", { name: "Next" }).click(); + await this.page.getByRole("button", { name: "Run" }).click(); + await expect(this.page.getByText(`${status}`, { exact: true })).toBeVisible( + { + timeout: 600000, + }, + ); + } + + async reRunGreetingWorkflow(language = "English", status = "Completed") { + await expect(this.page.getByText("Run again")).toBeVisible(); + await this.page.getByText("Run again").click(); + await this.page.getByLabel("Language").click(); + await this.page.getByRole("option", { name: language }).click(); + await this.page.getByRole("button", { name: "Next" }).click(); + await this.page.getByRole("button", { name: "Run" }).click(); + await expect(this.page.getByText(`${status}`, { exact: true })).toBeVisible( + { + timeout: 600000, + }, + ); + } + + async validateGreetingWorkflow() { + await this.page.getByRole("tab", { name: "Workflows" }).click(); const workflowHeader = this.page.getByRole("heading", { name: "Workflows", }); await expect(workflowHeader).toBeVisible(); await expect(workflowHeader).toHaveText("Workflows"); await expect(Workflows.workflowsTable(this.page)).toBeVisible(); - await this.page.getByRole("link", { name: "User Onboarding" }).click(); + await expect( + this.page.locator(`input[aria-label="Search"]`), + ).toHaveAttribute("placeholder", "Search"); + await expect( + this.page.getByRole("columnheader", { name: "Name", exact: true }), + ).toBeVisible(); + await expect( + this.page.getByRole("columnheader", { name: "Category", exact: true }), + ).toBeVisible(); + await expect( + this.page.getByRole("columnheader", { + name: "Workflow status", + exact: true, + }), + ).toBeVisible(); + + await expect( + this.page.getByRole("columnheader", { name: "Last run", exact: true }), + ).toBeVisible(); + await expect( + this.page.getByRole("columnheader", { + name: "Last run status", + exact: true, + }), + ).toBeVisible(); + await expect( + this.page.getByRole("columnheader", { name: "Actions", exact: true }), + ).toBeVisible(); + const workFlowRow = this.page.locator(`tr:has-text("Greeting workflow")`); + await expect(workFlowRow.locator("td").nth(0)).toHaveText( + "Greeting workflow", + ); + await expect(workFlowRow.locator("td").nth(1)).toHaveText("Infrastructure"); + await expect(workFlowRow.locator("td").nth(2)).toHaveText("Available"); + await expect(workFlowRow.locator("td").nth(3)).toHaveText( + /^\d{1,2}\/\d{1,2}\/\d{4}, \d{1,2}:\d{1,2}:\d{1,2} (AM|PM)$/, + ); + await expect(workFlowRow.locator("td").nth(4)).toHaveText("Completed"); + await expect(workFlowRow.locator("td").nth(5)).toHaveText( + "YAML based greeting workflow", + ); + await expect( + workFlowRow.getByRole("button", { name: "Run", exact: true }).first(), + ).toBeVisible(); + await expect( + workFlowRow.getByRole("button", { name: "View runs" }).first(), + ).toBeVisible(); + await expect( + workFlowRow.getByRole("button", { name: "View input schema" }).first(), + ).toBeVisible(); + } + + async validateWorkflowRunsDetails() { + await expect(this.page.getByText("Details")).toBeVisible(); + await expect(this.page.getByText("Results")).toBeVisible(); + await expect(this.page.getByText("Workflow progress")).toBeVisible(); + await expect( + this.page.locator("div").filter({ hasText: "Completed" }).first(), + ).toBeVisible(); + } + + async validateWorkflowAllRuns() { + await this.page.getByRole("tab", { name: "all runs" }).click(); + await expect( + this.page + .locator("tbody") + .getByRole("row") + .nth(0) + .getByRole("cell") + .nth(0), + ).toBeVisible(); + await expect(this.page.getByTestId("select").first()).toHaveAttribute( + "aria-label", + "Status", + ); + await this.page.getByTestId("select").first().click(); + + const statuses = [ + "All", + "Running", + "Failed", + "Completed", + "Aborted", + "Suspended", + ]; + for (const status of statuses) { + await expect(this.page.getByRole("option", { name: status })).toHaveText( + status, + ); + } + await this.page.getByRole("option", { name: "All" }).click(); + + const columnHeaders = [ + "ID", + "Workflow name", + "Run Status", + "Category", + "Started", + "Duration", + ]; + for (const columnHeader of columnHeaders) { + await expect( + this.page.getByRole("columnheader", { + name: columnHeader, + exact: true, + }), + ).toBeVisible(); + } } async getPageUrl() { @@ -56,42 +199,15 @@ export class Orchestrator { this.page.getByRole("button", { name: "Abort" }), ).toBeEnabled(); await this.page.getByRole("button", { name: "Abort" }).click(); - if (`${process.env.MILESTONE}` == "3") { - await expect( - this.page.getByRole("heading", { name: "Abort workflow", exact: true }), - ).toBeVisible(); - await expect( - this.page - .locator("div") - .filter({ - hasText: - /^Are you sure you want to abort this workflow instance\?$/, - }) - .first(), - ).toBeVisible(); - await this.page.getByRole("button", { name: "Ok" }).click(); - } else { - await expect( - this.page - .getByRole("dialog") - .locator("div") - .filter({ hasText: "Are you sure you want to" }) - .nth(2), - ).toBeVisible(); - await this.page.getByRole("button", { name: "Abort" }).click(); - } + await expect( + this.page + .getByRole("dialog") + .locator("div") + .filter({ hasText: "Are you sure you want to" }) + .nth(2), + ).toBeVisible(); + await this.page.getByRole("button", { name: "Abort" }).click(); await expect(this.page.getByText("Status Aborted")).toBeVisible(); - if (`${process.env.MILESTONE}` == "3") { - await expect( - this.page - .locator("b") - .filter({ - hasText: - /[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}/, - }) - .first(), - ).toBeVisible(); - } } async validateErrorPopup() { diff --git a/e2e-tests/playwright/support/pages/workflows.ts b/e2e-tests/playwright/support/pages/workflows.ts index 2f929d1d83..33e0ba12e7 100644 --- a/e2e-tests/playwright/support/pages/workflows.ts +++ b/e2e-tests/playwright/support/pages/workflows.ts @@ -1,10 +1,7 @@ import { Page } from "@playwright/test"; const workflowsTable = (page: Page) => - page - .locator("#root div") - .filter({ hasText: "WorkflowsNameCategoryLast" }) - .nth(2); + page.locator("#root div").filter({ hasText: "Workflows" }).nth(2); const WORKFLOWS = { workflowsTable, diff --git a/e2e-tests/playwright/utils/navbar.ts b/e2e-tests/playwright/utils/navbar.ts index fc15ac2454..651984ed1f 100644 --- a/e2e-tests/playwright/utils/navbar.ts +++ b/e2e-tests/playwright/utils/navbar.ts @@ -10,4 +10,5 @@ export type SidebarTabs = | "Docs" | "Clusters" | "Tech Radar" - | "Notifications"; + | "Notifications" + | "Orchestrator";