diff --git a/.devcontainer/template.json b/.devcontainer/template.json index 4e8bb6d0c..c01c95141 100644 --- a/.devcontainer/template.json +++ b/.devcontainer/template.json @@ -1,6 +1,8 @@ { "name": "playwright", - "image": "mcr.microsoft.com/playwright:v1.55.0-jammy", + "build": { + "dockerfile": "Dockerfile" + }, "workspaceFolder": "/workspaces/trustify-ui/e2e", "runArgs": [ "--privileged", @@ -27,7 +29,8 @@ "customizations": { "vscode": { "extensions": [ - "ms-playwright.playwright" + "ms-playwright.playwright", + "alexkrechik.cucumberautocomplete" ] } } diff --git a/.env b/.env index 0a3d759d2..64197d16b 100644 --- a/.env +++ b/.env @@ -7,4 +7,4 @@ PLAYWRIGHT_IMAGE=mcr.microsoft.com/playwright PLAYWRIGHT_VERSION=v1.55.0 UBUNTU_VERSION_ALIAS=-jammy PLAYWRIGHT_PORT=5000 -PLAYWRIGHT_HOME=/home/pwuser \ No newline at end of file +PLAYWRIGHT_HOME=/home/pwuser diff --git a/e2e/tests/ui/pages/DetailsPageLayout.ts b/e2e/tests/ui/pages/DetailsPageLayout.ts new file mode 100644 index 000000000..3101216c6 --- /dev/null +++ b/e2e/tests/ui/pages/DetailsPageLayout.ts @@ -0,0 +1,43 @@ +import { expect, type Page } from "@playwright/test"; + +export class DetailsPageLayout { + private readonly _page: Page; + + private constructor(page: Page) { + this._page = page; + } + + static async build(page: Page) { + await expect(page.locator("nav[aria-label='Breadcrumb']")).toBeVisible(); + return new DetailsPageLayout(page); + } + + async selectTab(tabName: string) { + const tab = this._page.locator("button[role='tab']", { hasText: tabName }); + await expect(tab).toBeVisible(); + await tab.click(); + } + + async clickOnPageAction(actionName: string) { + await this._page.getByRole("button", { name: "Actions" }).click(); + await this._page.getByRole("menuitem", { name: actionName }).click(); + } + + async verifyPageHeader(header: string) { + await expect(this._page.getByRole("heading")).toContainText(header); + } + + async verifyTabIsSelected(tabName: string) { + await expect( + this._page.getByRole("tab", { name: tabName }), + ).toHaveAttribute("aria-selected", "true"); + } + + async verifyTabIsVisible(tabName: string) { + await expect(this._page.getByRole("tab", { name: tabName })).toBeVisible(); + } + + async verifyTabIsNotVisible(tabName: string) { + await expect(this._page.getByRole("tab", { name: tabName })).toHaveCount(0); + } +} diff --git a/e2e/tests/ui/pages/Helpers.ts b/e2e/tests/ui/pages/Helpers.ts new file mode 100644 index 000000000..338ffacf6 --- /dev/null +++ b/e2e/tests/ui/pages/Helpers.ts @@ -0,0 +1,23 @@ +import { expect } from "@playwright/test"; + +export const sortArray = (arr: string[], asc: boolean) => { + let sorted = [...arr].sort((a, b) => + a.localeCompare(b, "en", { numeric: true }), + ); + if (!asc) { + sorted = sorted.reverse(); + } + const isSorted = arr.every((val, i) => val === sorted[i]); + return { + isSorted, + sorted, + }; +}; + +export const expectSort = (arr: string[], asc: boolean) => { + const { isSorted, sorted } = sortArray(arr, asc); + expect( + isSorted, + `Received: ${arr.join(", ")} \nExpected: ${sorted.join(", ")}`, + ).toBe(true); +}; diff --git a/e2e/tests/ui/pages/LabelsModal.ts b/e2e/tests/ui/pages/LabelsModal.ts new file mode 100644 index 000000000..7f1a8159f --- /dev/null +++ b/e2e/tests/ui/pages/LabelsModal.ts @@ -0,0 +1,42 @@ +import type { Locator, Page } from "playwright-core"; +import { expect } from "playwright/test"; + +export class LabelsModal { + private _dialog: Locator; + + private constructor(_page: Page, dialog: Locator) { + this._dialog = dialog; + } + + static async build(page: Page) { + const dialog = page.getByRole("dialog"); + await expect(dialog).toBeVisible(); + return new LabelsModal(page, dialog); + } + + async clickSave() { + await this._dialog.locator("button[aria-label='submit']").click(); + await expect(this._dialog).not.toBeVisible(); + } + + async addLabels(labels: string[]) { + const inputText = this._dialog.getByPlaceholder("Add label"); + + for (const label of labels) { + await inputText.click(); + await inputText.fill(label); + await inputText.press("Enter"); + } + } + + async removeLabels(labels: string[]) { + for (const label of labels) { + await this._dialog + .locator(".pf-v6-c-label-group__list-item", { + hasText: label, + }) + .locator("button") + .click(); + } + } +} diff --git a/e2e/tests/ui/pages/Navigation.ts b/e2e/tests/ui/pages/Navigation.ts new file mode 100644 index 000000000..f9a7fd66e --- /dev/null +++ b/e2e/tests/ui/pages/Navigation.ts @@ -0,0 +1,33 @@ +import type { Page } from "playwright-core"; + +/** + * Used to navigate to different pages + */ +export class Navigation { + private readonly _page: Page; + + private constructor(page: Page) { + this._page = page; + } + + static async build(page: Page) { + return new Navigation(page); + } + + async goToSidebar( + menu: + | "Dashboard" + | "Search" + | "SBOMs" + | "Vulnerabilities" + | "Packages" + | "Advisories" + | "Importers" + | "Upload", + ) { + // By default, we do not initialize navigation at "/"" where the Dashboard is located + // This should help us to save some time loading pages as the Dashboard fetches too much data + await this._page.goto("/upload"); + await this._page.getByRole("link", { name: menu }).click(); + } +} diff --git a/e2e/tests/ui/pages/Pagination.ts b/e2e/tests/ui/pages/Pagination.ts new file mode 100644 index 000000000..cad53b77a --- /dev/null +++ b/e2e/tests/ui/pages/Pagination.ts @@ -0,0 +1,90 @@ +import { expect, type Locator, type Page } from "@playwright/test"; +import type { Table } from "./Table"; + +export class Pagination { + private readonly _page: Page; + _pagination: Locator; + + private constructor(page: Page, pagination: Locator) { + this._page = page; + this._pagination = pagination; + } + + static async build(page: Page, paginationId: string) { + const pagination = page.locator(`#${paginationId}`); + await expect(pagination).toBeVisible(); + return new Pagination(page, pagination); + } + + /** + * Selects Number of rows per page on the table + * @param perPage Number of rows + */ + async selectItemsPerPage(perPage: 10 | 20 | 50 | 100) { + await this._pagination + .locator(`//button[@aria-haspopup='listbox']`) + .click(); + await this._page + .getByRole("menuitem", { name: `${perPage} per page` }) + .click(); + + await expect(this._pagination.locator("input")).toHaveValue("1"); + } + + async validatePagination() { + // Verify next buttons are enabled as there are more than 11 rows present + const nextPageButton = this._pagination.locator( + "button[data-action='next']", + ); + await expect(nextPageButton).toBeVisible(); + await expect(nextPageButton).not.toBeDisabled(); + + // Verify that previous buttons are disabled being on the first page + const prevPageButton = this._pagination.locator( + "button[data-action='previous']", + ); + await expect(prevPageButton).toBeVisible(); + await expect(prevPageButton).toBeDisabled(); + + // Verify that navigation button to last page is enabled + const lastPageButton = this._pagination.locator( + "button[data-action='last']", + ); + await expect(lastPageButton).toBeVisible(); + await expect(lastPageButton).not.toBeDisabled(); + + // Verify that navigation button to first page is disabled being on the first page + const firstPageButton = this._pagination.locator( + "button[data-action='first']", + ); + await expect(firstPageButton).toBeVisible(); + await expect(firstPageButton).toBeDisabled(); + + // Navigate to next page + await nextPageButton.click(); + + // Verify that previous buttons are enabled after moving to next page + await expect(prevPageButton).toBeVisible(); + await expect(prevPageButton).not.toBeDisabled(); + + // Verify that navigation button to first page is enabled after moving to next page + await expect(firstPageButton).toBeVisible(); + await expect(firstPageButton).not.toBeDisabled(); + + // Moving back to the first page + await firstPageButton.click(); + } + + async validateItemsPerPage(columnName: string, table: Table) { + // Verify that only 10 items are displayed + await this.selectItemsPerPage(10); + await table.validateNumberOfRows({ equal: 10 }, columnName); + + // Verify that items less than or equal to 20 and greater than 10 are displayed + await this.selectItemsPerPage(20); + await table.validateNumberOfRows( + { greaterThan: 10, lessThan: 21 }, + columnName, + ); + } +} diff --git a/e2e/tests/ui/pages/Table.ts b/e2e/tests/ui/pages/Table.ts new file mode 100644 index 000000000..d7024ee64 --- /dev/null +++ b/e2e/tests/ui/pages/Table.ts @@ -0,0 +1,92 @@ +import { expect, type Locator, type Page } from "@playwright/test"; + +export class Table { + private readonly _page: Page; + _table: Locator; + + private constructor(page: Page, table: Locator) { + this._page = page; + this._table = table; + } + + /** + * @param page + * @param tableAriaLabel the unique aria-label that corresponds to the DOM element that contains the Table. E.g.
+ * @returns a new instance of a Toolbar + */ + static async build(page: Page, tableAriaLabel: string) { + const table = page.locator(`table[aria-label="${tableAriaLabel}"]`); + await expect(table).toBeVisible(); + + const result = new Table(page, table); + await result.waitUntilDataIsLoaded(); + return result; + } + + async waitUntilDataIsLoaded() { + const rows = this._table.locator( + 'xpath=//tbody[not(@aria-label="Table loading")]', + ); + await expect(rows.first()).toBeVisible(); + + const rowsCount = await rows.count(); + expect(rowsCount).toBeGreaterThanOrEqual(1); + } + + async clickSortBy(columnName: string) { + await this._table + .getByRole("button", { name: columnName, exact: true }) + .click(); + await this.waitUntilDataIsLoaded(); + } + + async clickAction(actionName: string, rowIndex: number) { + await this._table + .locator(`button[aria-label="Kebab toggle"]`) + .nth(rowIndex) + .click(); + + await this._page.getByRole("menuitem", { name: actionName }).click(); + } + + async verifyTableIsSortedBy(columnName: string, asc: boolean = true) { + await expect( + this._table.getByRole("columnheader", { name: columnName }), + ).toHaveAttribute("aria-sort", asc ? "ascending" : "descending"); + } + + async verifyColumnContainsText(columnName: string, expectedValue: string) { + await expect( + this._table.locator(`td[data-label="${columnName}"]`, { + hasText: expectedValue, + }), + ).toBeVisible(); + } + + async verifyTableHasNoData() { + await expect( + this._table.locator(`tbody[aria-label="Table empty"]`), + ).toBeVisible(); + } + + async validateNumberOfRows( + expectedRows: { + equal?: number; + greaterThan?: number; + lessThan?: number; + }, + columnName: string, + ) { + const rows = this._table.locator(`td[data-label="${columnName}"]`); + + if (expectedRows.equal) { + expect(await rows.count()).toBe(expectedRows.equal); + } + if (expectedRows.greaterThan) { + expect(await rows.count()).toBeGreaterThan(expectedRows.greaterThan); + } + if (expectedRows.lessThan) { + expect(await rows.count()).toBeLessThan(expectedRows.lessThan); + } + } +} diff --git a/e2e/tests/ui/pages/Toolbar.ts b/e2e/tests/ui/pages/Toolbar.ts new file mode 100644 index 000000000..2a3927ae6 --- /dev/null +++ b/e2e/tests/ui/pages/Toolbar.ts @@ -0,0 +1,109 @@ +import { expect, type Locator, type Page } from "@playwright/test"; + +export class Toolbar { + private readonly _page: Page; + _toolbar: Locator; + + private constructor(page: Page, toolbar: Locator) { + this._page = page; + this._toolbar = toolbar; + } + + /** + * @param page + * @param toolbarAriaLabel the unique aria-label that corresponds to the DOM element that contains the Toolbar. E.g.
+ * @returns a new instance of a Toolbar + */ + static async build(page: Page, toolbarAriaLabel: string) { + const toolbar = page.locator(`[aria-label="${toolbarAriaLabel}"]`); + await expect(toolbar).toBeVisible(); + return new Toolbar(page, toolbar); + } + + /** + * Selects the main filter to be applied + * @param filterName the name of the filter as rendered in the UI + */ + async selectFilter(filterName: string) { + await this._toolbar + .locator(".pf-m-toggle-group button.pf-v6-c-menu-toggle") + .click(); + await this._page.getByRole("menuitem", { name: filterName }).click(); + } + + private async assertFilterHasLabels( + filterName: string, + filterValue: string | string[], + ) { + await expect( + this._toolbar.locator(".pf-m-label-group", { hasText: filterName }), + ).toBeVisible(); + + const labels = Array.isArray(filterValue) ? filterValue : [filterValue]; + for (const label of labels) { + await expect( + this._toolbar.locator(".pf-m-label-group", { hasText: label }), + ).toBeVisible(); + } + } + + async applyTextFilter(filterName: string, filterValue: string) { + await this.selectFilter(filterName); + + await this._toolbar.getByRole("textbox").fill(filterValue); + await this._page.keyboard.press("Enter"); + + await this.assertFilterHasLabels(filterName, filterValue); + } + + async applyDateRangeFilter( + filterName: string, + fromDate: string, + toDate: string, + ) { + await this.selectFilter(filterName); + + await this._toolbar + .locator("input[aria-label='Interval start']") + .fill(fromDate); + await this._toolbar + .locator("input[aria-label='Interval end']") + .fill(toDate); + + await this.assertFilterHasLabels(filterName, [fromDate, toDate]); + } + + async applyMultiSelectFilter(filterName: string, selections: string[]) { + await this.selectFilter(filterName); + + for (const option of selections) { + const inputText = this._toolbar.locator( + "input[aria-label='Type to filter']", + ); + await inputText.clear(); + await inputText.fill(option); + + const dropdownOption = this._page.getByRole("menuitem", { name: option }); + await expect(dropdownOption).toBeVisible(); + await dropdownOption.click(); + } + + await this.assertFilterHasLabels(filterName, selections); + } + + async applyLabelsFilter(filterName: string, labels: string[]) { + await this.selectFilter(filterName); + + for (const label of labels) { + await this._toolbar + .locator("input[aria-label='select-autocomplete-listbox']") + .fill(label); + + const dropdownOption = this._page.getByRole("option", { name: label }); + await expect(dropdownOption).toBeVisible(); + await dropdownOption.click(); + } + + await this.assertFilterHasLabels(filterName, labels); + } +} diff --git a/e2e/tests/ui/pages/advisory-details/AdvisoryDetailsPage.ts b/e2e/tests/ui/pages/advisory-details/AdvisoryDetailsPage.ts new file mode 100644 index 000000000..771c6c0d4 --- /dev/null +++ b/e2e/tests/ui/pages/advisory-details/AdvisoryDetailsPage.ts @@ -0,0 +1,32 @@ +import type { Page } from "@playwright/test"; +import { DetailsPageLayout } from "../DetailsPageLayout"; +import { Navigation } from "../Navigation"; +import { AdvisoryListPage } from "../advisory-list/AdvisoryListPage"; + +export class AdvisoryDetailsPage { + _layout: DetailsPageLayout; + + private constructor(_page: Page, layout: DetailsPageLayout) { + this._layout = layout; + } + + static async build(page: Page, advisoryID: string) { + const navigation = await Navigation.build(page); + await navigation.goToSidebar("Advisories"); + + const listPage = await AdvisoryListPage.build(page); + const toolbar = await listPage.getToolbar(); + const table = await listPage.getTable(); + + await toolbar.applyTextFilter("Filter text", advisoryID); + await table.waitUntilDataIsLoaded(); + await table.verifyColumnContainsText("ID", advisoryID); + + await page.getByRole("link", { name: advisoryID, exact: true }).click(); + + const layout = await DetailsPageLayout.build(page); + await layout.verifyPageHeader(advisoryID); + + return new AdvisoryDetailsPage(page, layout); + } +} diff --git a/e2e/tests/ui/pages/advisory-details/info/info.spec.ts b/e2e/tests/ui/pages/advisory-details/info/info.spec.ts new file mode 100644 index 000000000..fb4528169 --- /dev/null +++ b/e2e/tests/ui/pages/advisory-details/info/info.spec.ts @@ -0,0 +1,46 @@ +// @ts-check + +import { expect } from "@playwright/test"; + +import { test } from "../../../fixtures"; +import { login } from "../../../helpers/Auth"; +import { AdvisoryDetailsPage } from "../AdvisoryDetailsPage"; +import { LabelsModal } from "../../LabelsModal"; + +test.describe("Info Tab validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test("Labels", async ({ page }) => { + await AdvisoryDetailsPage.build(page, "CVE-2024-26308"); + + const labels = ["color=red", "production"]; + + // Open Edit Labels Modal + await page.getByRole("button", { name: "Edit" }).click(); + + let labelsModal = await LabelsModal.build(page); + await labelsModal.addLabels(labels); + await labelsModal.clickSave(); + + for (const label of labels) { + await expect( + page.locator(".pf-v6-c-label-group__list-item", { hasText: label }), + ).toHaveCount(1); + } + + // Clean labels added previously + await page.getByRole("button", { name: "Edit" }).click(); + + labelsModal = await LabelsModal.build(page); + await labelsModal.removeLabels(labels); + await labelsModal.clickSave(); + + for (const label of labels) { + await expect( + page.locator(".pf-v6-c-label-group__list-item", { hasText: label }), + ).toHaveCount(0); + } + }); +}); diff --git a/e2e/tests/ui/pages/advisory-details/vulnerabilities/VulnerabilitiesTab.ts b/e2e/tests/ui/pages/advisory-details/vulnerabilities/VulnerabilitiesTab.ts new file mode 100644 index 000000000..3ab068efa --- /dev/null +++ b/e2e/tests/ui/pages/advisory-details/vulnerabilities/VulnerabilitiesTab.ts @@ -0,0 +1,37 @@ +import type { Page } from "@playwright/test"; +import { AdvisoryDetailsPage } from "../AdvisoryDetailsPage"; +import { Toolbar } from "../../Toolbar"; +import { Table } from "../../Table"; +import { Pagination } from "../../Pagination"; + +export class VulnerabilitiesTab { + private readonly _page: Page; + _detailsPage: AdvisoryDetailsPage; + + private constructor(page: Page, layout: AdvisoryDetailsPage) { + this._page = page; + this._detailsPage = layout; + } + + static async build(page: Page, packageName: string) { + const detailsPage = await AdvisoryDetailsPage.build(page, packageName); + await detailsPage._layout.selectTab("Vulnerabilities"); + + return new VulnerabilitiesTab(page, detailsPage); + } + + async getToolbar() { + return await Toolbar.build(this._page, "vulnerability toolbar"); + } + + async getTable() { + return await Table.build(this._page, "vulnerability table"); + } + + async getPagination(location: "top" | "bottom" = "top") { + return await Pagination.build( + this._page, + `vulnerability-table-pagination-${location}`, + ); + } +} diff --git a/e2e/tests/ui/pages/advisory-details/vulnerabilities/columns.spec.ts b/e2e/tests/ui/pages/advisory-details/vulnerabilities/columns.spec.ts new file mode 100644 index 000000000..54f7e43e1 --- /dev/null +++ b/e2e/tests/ui/pages/advisory-details/vulnerabilities/columns.spec.ts @@ -0,0 +1,44 @@ +// @ts-check + +import { expect } from "@playwright/test"; + +import { test } from "../../../fixtures"; +import { login } from "../../../helpers/Auth"; +import { VulnerabilitiesTab } from "./VulnerabilitiesTab"; + +test.describe("Columns validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test("Columns", async ({ page }) => { + const vulnerabilitiesTab = await VulnerabilitiesTab.build( + page, + "CVE-2024-26308", + ); + const table = await vulnerabilitiesTab.getTable(); + + const ids = await table._table + .locator(`td[data-label="ID"]`) + .allInnerTexts(); + const idIndex = ids.indexOf("CVE-2024-26308"); + expect(idIndex).not.toBe(-1); + + // Name + await expect( + table._table.locator(`td[data-label="ID"]`).nth(idIndex), + ).toContainText("CVE-2024-26308"); + + // Title + await expect( + table._table.locator(`td[data-label="Title"]`).nth(idIndex), + ).toContainText( + "Apache Commons Compress: OutOfMemoryError unpacking broken Pack200 file", + ); + + // CWE + await expect( + table._table.locator(`td[data-label="CWE"]`).nth(idIndex), + ).toContainText("CWE-770"); + }); +}); diff --git a/e2e/tests/ui/pages/advisory-details/vulnerabilities/pagination.spec.ts b/e2e/tests/ui/pages/advisory-details/vulnerabilities/pagination.spec.ts new file mode 100644 index 000000000..dff610544 --- /dev/null +++ b/e2e/tests/ui/pages/advisory-details/vulnerabilities/pagination.spec.ts @@ -0,0 +1,34 @@ +// @ts-check + +import { test } from "../../../fixtures"; +import { login } from "../../../helpers/Auth"; +import { VulnerabilitiesTab } from "./VulnerabilitiesTab"; + +// Number of items less than 10, cannot tests pagination +test.describe.skip("Pagination validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test("Navigation button validations", async ({ page }) => { + const vulnerabilitiesTab = await VulnerabilitiesTab.build( + page, + "CVE-2024-26308", + ); + const pagination = await vulnerabilitiesTab.getPagination(); + + await pagination.validatePagination(); + }); + + test("Items per page validations", async ({ page }) => { + const vulnerabilitiesTab = await VulnerabilitiesTab.build( + page, + "CVE-2024-26308", + ); + + const pagination = await vulnerabilitiesTab.getPagination(); + const table = await vulnerabilitiesTab.getTable(); + + await pagination.validateItemsPerPage("ID", table); + }); +}); diff --git a/e2e/tests/ui/pages/advisory-details/vulnerabilities/sort.spec.ts b/e2e/tests/ui/pages/advisory-details/vulnerabilities/sort.spec.ts new file mode 100644 index 000000000..ff39c15dc --- /dev/null +++ b/e2e/tests/ui/pages/advisory-details/vulnerabilities/sort.spec.ts @@ -0,0 +1,30 @@ +// @ts-check + +import { test } from "../../../fixtures"; +import { login } from "../../../helpers/Auth"; +import { VulnerabilitiesTab } from "./VulnerabilitiesTab"; +import { expectSort } from "../../Helpers"; + +test.describe("Sort validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test("Sort", async ({ page }) => { + const vulnerabilitiesTab = await VulnerabilitiesTab.build( + page, + "CVE-2024-26308", + ); + const table = await vulnerabilitiesTab.getTable(); + + const columnNameSelector = table._table.locator(`td[data-label="ID"]`); + + const ascList = await columnNameSelector.allInnerTexts(); + expectSort(ascList, true); + + // Reverse sorting + await table.clickSortBy("ID"); + const descList = await columnNameSelector.allInnerTexts(); + expectSort(descList, false); + }); +}); diff --git a/e2e/tests/ui/pages/advisory-list/AdvisoryListPage.ts b/e2e/tests/ui/pages/advisory-list/AdvisoryListPage.ts new file mode 100644 index 000000000..54d5cf30a --- /dev/null +++ b/e2e/tests/ui/pages/advisory-list/AdvisoryListPage.ts @@ -0,0 +1,35 @@ +import type { Page } from "@playwright/test"; +import { Navigation } from "../Navigation"; +import { Toolbar } from "../Toolbar"; +import { Table } from "../Table"; +import { Pagination } from "../Pagination"; + +export class AdvisoryListPage { + private readonly _page: Page; + + private constructor(page: Page) { + this._page = page; + } + + static async build(page: Page) { + const navigation = await Navigation.build(page); + await navigation.goToSidebar("Advisories"); + + return new AdvisoryListPage(page); + } + + async getToolbar() { + return await Toolbar.build(this._page, "advisory-toolbar"); + } + + async getTable() { + return await Table.build(this._page, "advisory-table"); + } + + async getPagination(top: boolean = true) { + return await Pagination.build( + this._page, + `advisory-table-pagination-${top ? "top" : "bottom"}`, + ); + } +} diff --git a/e2e/tests/ui/pages/advisory-list/columns.spec.ts b/e2e/tests/ui/pages/advisory-list/columns.spec.ts new file mode 100644 index 000000000..7d1ef125f --- /dev/null +++ b/e2e/tests/ui/pages/advisory-list/columns.spec.ts @@ -0,0 +1,42 @@ +// @ts-check + +import { expect } from "@playwright/test"; + +import { test } from "../../fixtures"; +import { login } from "../../helpers/Auth"; +import { AdvisoryListPage } from "./AdvisoryListPage"; + +test.describe("Columns validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test("Columns", async ({ page }) => { + const listPage = await AdvisoryListPage.build(page); + + const toolbar = await listPage.getToolbar(); + const table = await listPage.getTable(); + + // Full search + await toolbar.applyTextFilter("Filter text", "CVE-2024-26308"); + await table.waitUntilDataIsLoaded(); + + // ID + await table.verifyColumnContainsText("ID", "CVE-2024-26308"); + + // Title + await expect(table._table.locator(`td[data-label="Title"]`)).toContainText( + "Apache Commons Compress: OutOfMemoryError unpacking broken Pack200 file", + ); + + // Type + await expect(table._table.locator(`td[data-label="Type"]`)).toContainText( + "cve", + ); + + // Labels + await expect(table._table.locator(`td[data-label="Labels"]`)).toContainText( + "type=cve", + ); + }); +}); diff --git a/e2e/tests/ui/pages/advisory-list/filter.spec.ts b/e2e/tests/ui/pages/advisory-list/filter.spec.ts new file mode 100644 index 000000000..1351a884e --- /dev/null +++ b/e2e/tests/ui/pages/advisory-list/filter.spec.ts @@ -0,0 +1,33 @@ +// @ts-check + +import { test } from "../../fixtures"; +import { login } from "../../helpers/Auth"; +import { AdvisoryListPage } from "./AdvisoryListPage"; + +test.describe("Filter validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test("Filters", async ({ page }) => { + const listPage = await AdvisoryListPage.build(page); + + const toolbar = await listPage.getToolbar(); + const table = await listPage.getTable(); + + // Full search + await toolbar.applyTextFilter("Filter text", "CVE-2024-26308"); + await table.waitUntilDataIsLoaded(); + await table.verifyColumnContainsText("ID", "CVE-2024-26308"); + + // Date filter + await toolbar.applyDateRangeFilter("Revision", "03/26/2025", "03/28/2025"); + await table.waitUntilDataIsLoaded(); + await table.verifyColumnContainsText("ID", "CVE-2024-26308"); + + // Labels filter + await toolbar.applyLabelsFilter("Label", ["type=cve"]); + await table.waitUntilDataIsLoaded(); + await table.verifyColumnContainsText("ID", "CVE-2024-26308"); + }); +}); diff --git a/e2e/tests/ui/pages/advisory-list/pagination.spec.ts b/e2e/tests/ui/pages/advisory-list/pagination.spec.ts new file mode 100644 index 000000000..2fea59397 --- /dev/null +++ b/e2e/tests/ui/pages/advisory-list/pagination.spec.ts @@ -0,0 +1,27 @@ +// @ts-check + +import { test } from "../../fixtures"; +import { login } from "../../helpers/Auth"; +import { AdvisoryListPage } from "./AdvisoryListPage"; + +test.describe("Pagination validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test("Navigation button validations", async ({ page }) => { + const listPage = await AdvisoryListPage.build(page); + + const pagination = await listPage.getPagination(); + await pagination.validatePagination(); + }); + + test("Items per page validations", async ({ page }) => { + const listPage = await AdvisoryListPage.build(page); + + const pagination = await listPage.getPagination(); + const table = await listPage.getTable(); + + await pagination.validateItemsPerPage("ID", table); + }); +}); diff --git a/e2e/tests/ui/pages/advisory-list/sort.spec.ts b/e2e/tests/ui/pages/advisory-list/sort.spec.ts new file mode 100644 index 000000000..570e36591 --- /dev/null +++ b/e2e/tests/ui/pages/advisory-list/sort.spec.ts @@ -0,0 +1,30 @@ +// @ts-check + +import { test } from "../../fixtures"; +import { login } from "../../helpers/Auth"; +import { expectSort } from "../Helpers"; +import { AdvisoryListPage } from "./AdvisoryListPage"; + +test.describe("Sort validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + // TODO: enable after https://github.com/trustification/trustify/issues/1810 is fixed + test.skip("Sort", async ({ page }) => { + const listPage = await AdvisoryListPage.build(page); + const table = await listPage.getTable(); + + const columnNameSelector = table._table.locator(`td[data-label="ID"]`); + + // ID Asc + await table.clickSortBy("ID"); + const ascList = await columnNameSelector.allInnerTexts(); + expectSort(ascList, true); + + // ID Desc + await table.clickSortBy("ID"); + const descList = await columnNameSelector.allInnerTexts(); + expectSort(descList, false); + }); +}); diff --git a/e2e/tests/ui/pages/package-details/PackageDetailsPage.ts b/e2e/tests/ui/pages/package-details/PackageDetailsPage.ts new file mode 100644 index 000000000..90a248dfc --- /dev/null +++ b/e2e/tests/ui/pages/package-details/PackageDetailsPage.ts @@ -0,0 +1,32 @@ +import type { Page } from "@playwright/test"; +import { DetailsPageLayout } from "../DetailsPageLayout"; +import { Navigation } from "../Navigation"; +import { PackageListPage } from "../package-list/PackageListPage"; + +export class PackageDetailsPage { + _layout: DetailsPageLayout; + + private constructor(_page: Page, layout: DetailsPageLayout) { + this._layout = layout; + } + + static async build(page: Page, packageName: string) { + const navigation = await Navigation.build(page); + await navigation.goToSidebar("Packages"); + + const listPage = await PackageListPage.build(page); + const toolbar = await listPage.getToolbar(); + const table = await listPage.getTable(); + + await toolbar.applyTextFilter("Filter text", packageName); + await table.waitUntilDataIsLoaded(); + await table.verifyColumnContainsText("Name", packageName); + + await page.getByRole("link", { name: packageName, exact: true }).click(); + + const layout = await DetailsPageLayout.build(page); + await layout.verifyPageHeader(packageName); + + return new PackageDetailsPage(page, layout); + } +} diff --git a/e2e/tests/ui/pages/package-details/info/info.spec.ts b/e2e/tests/ui/pages/package-details/info/info.spec.ts new file mode 100644 index 000000000..5690703fa --- /dev/null +++ b/e2e/tests/ui/pages/package-details/info/info.spec.ts @@ -0,0 +1,18 @@ +// @ts-check + +import { expect, test } from "../../../fixtures"; +import { login } from "../../../helpers/Auth"; +import { PackageDetailsPage } from "../PackageDetailsPage"; + +test.describe("Info Tab validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test("Info", async ({ page }) => { + await PackageDetailsPage.build(page, "keycloak-core"); + + // Verify version + await expect(page.getByText("version: 18.0.6.redhat-00001")).toHaveCount(1); + }); +}); diff --git a/e2e/tests/ui/pages/package-details/sboms/SbomsTab.ts b/e2e/tests/ui/pages/package-details/sboms/SbomsTab.ts new file mode 100644 index 000000000..cc956cbb5 --- /dev/null +++ b/e2e/tests/ui/pages/package-details/sboms/SbomsTab.ts @@ -0,0 +1,37 @@ +import type { Page } from "@playwright/test"; +import { PackageDetailsPage } from "../PackageDetailsPage"; +import { Toolbar } from "../../Toolbar"; +import { Table } from "../../Table"; +import { Pagination } from "../../Pagination"; + +export class SbomsTab { + private readonly _page: Page; + _detailsPage: PackageDetailsPage; + + private constructor(page: Page, layout: PackageDetailsPage) { + this._page = page; + this._detailsPage = layout; + } + + static async build(page: Page, packageName: string) { + const detailsPage = await PackageDetailsPage.build(page, packageName); + await detailsPage._layout.selectTab("SBOMs using package"); + + return new SbomsTab(page, detailsPage); + } + + async getToolbar() { + return await Toolbar.build(this._page, "SBOM toolbar"); + } + + async getTable() { + return await Table.build(this._page, "SBOM table"); + } + + async getPagination(top: boolean = true) { + return await Pagination.build( + this._page, + `sbom-table-pagination-${top ? "top" : "bottom"}`, + ); + } +} diff --git a/e2e/tests/ui/pages/package-details/sboms/columns.spec.ts b/e2e/tests/ui/pages/package-details/sboms/columns.spec.ts new file mode 100644 index 000000000..933419cbc --- /dev/null +++ b/e2e/tests/ui/pages/package-details/sboms/columns.spec.ts @@ -0,0 +1,39 @@ +// @ts-check + +import { expect } from "@playwright/test"; + +import { test } from "../../../fixtures"; +import { login } from "../../../helpers/Auth"; +import { SbomsTab } from "./SbomsTab"; + +test.describe("Columns validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test("Columns", async ({ page }) => { + const sbomTab = await SbomsTab.build(page, "keycloak-core"); + const table = await sbomTab.getTable(); + + const ids = await table._table + .locator(`td[data-label="Name"]`) + .allInnerTexts(); + const idIndex = ids.indexOf("quarkus-bom"); + expect(idIndex).not.toBe(-1); + + // Name + await expect( + table._table.locator(`td[data-label="Name"]`).nth(idIndex), + ).toContainText("quarkus-bom"); + + // Version + await expect( + table._table.locator(`td[data-label="Version"]`).nth(idIndex), + ).toContainText("2.13.8.Final-redhat-00004"); + + // Supplier + await expect( + table._table.locator(`td[data-label="Supplier"]`).nth(idIndex), + ).toContainText("Organization: Red Hat"); + }); +}); diff --git a/e2e/tests/ui/pages/package-details/sboms/pagination.spec.ts b/e2e/tests/ui/pages/package-details/sboms/pagination.spec.ts new file mode 100644 index 000000000..757c2f081 --- /dev/null +++ b/e2e/tests/ui/pages/package-details/sboms/pagination.spec.ts @@ -0,0 +1,28 @@ +// @ts-check + +import { test } from "../../../fixtures"; +import { login } from "../../../helpers/Auth"; +import { SbomsTab } from "./SbomsTab"; + +// Number of items less than 10, cannot tests pagination +test.describe.skip("Pagination validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test("Navigation button validations", async ({ page }) => { + const sbomTab = await SbomsTab.build(page, "keycloak-core"); + const pagination = await sbomTab.getPagination(); + + await pagination.validatePagination(); + }); + + test("Items per page validations", async ({ page }) => { + const sbomTab = await SbomsTab.build(page, "keycloak-core"); + + const pagination = await sbomTab.getPagination(); + const table = await sbomTab.getTable(); + + await pagination.validateItemsPerPage("Name", table); + }); +}); diff --git a/e2e/tests/ui/pages/package-details/sboms/sort.spec.ts b/e2e/tests/ui/pages/package-details/sboms/sort.spec.ts new file mode 100644 index 000000000..835c9a6ee --- /dev/null +++ b/e2e/tests/ui/pages/package-details/sboms/sort.spec.ts @@ -0,0 +1,27 @@ +// @ts-check + +import { test } from "../../../fixtures"; +import { login } from "../../../helpers/Auth"; +import { SbomsTab } from "./SbomsTab"; +import { expectSort } from "../../Helpers"; + +test.describe("Sort validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test("Sort", async ({ page }) => { + const sbomTab = await SbomsTab.build(page, "keycloak-core"); + const table = await sbomTab.getTable(); + + const columnNameSelector = table._table.locator(`td[data-label="Name"]`); + + const ascList = await columnNameSelector.allInnerTexts(); + expectSort(ascList, true); + + // Reverse sorting + await table.clickSortBy("Name"); + const descList = await columnNameSelector.allInnerTexts(); + expectSort(descList, false); + }); +}); diff --git a/e2e/tests/ui/pages/package-details/vulnerabilities/VulnerabilitiesTab.ts b/e2e/tests/ui/pages/package-details/vulnerabilities/VulnerabilitiesTab.ts new file mode 100644 index 000000000..7c41ea60c --- /dev/null +++ b/e2e/tests/ui/pages/package-details/vulnerabilities/VulnerabilitiesTab.ts @@ -0,0 +1,37 @@ +import type { Page } from "@playwright/test"; +import { PackageDetailsPage } from "../PackageDetailsPage"; +import { Toolbar } from "../../Toolbar"; +import { Table } from "../../Table"; +import { Pagination } from "../../Pagination"; + +export class VulnerabilitiesTab { + private readonly _page: Page; + _detailsPage: PackageDetailsPage; + + private constructor(page: Page, layout: PackageDetailsPage) { + this._page = page; + this._detailsPage = layout; + } + + static async build(page: Page, packageName: string) { + const detailsPage = await PackageDetailsPage.build(page, packageName); + await detailsPage._layout.selectTab("Vulnerabilities"); + + return new VulnerabilitiesTab(page, detailsPage); + } + + async getToolbar() { + return await Toolbar.build(this._page, "vulnerability toolbar"); + } + + async getTable() { + return await Table.build(this._page, "vulnerability table"); + } + + async getPagination(top: boolean = true) { + return await Pagination.build( + this._page, + `vulnerability-table-pagination-${top ? "top" : "bottom"}`, + ); + } +} diff --git a/e2e/tests/ui/pages/package-details/vulnerabilities/columns.spec.ts b/e2e/tests/ui/pages/package-details/vulnerabilities/columns.spec.ts new file mode 100644 index 000000000..cb4c6deab --- /dev/null +++ b/e2e/tests/ui/pages/package-details/vulnerabilities/columns.spec.ts @@ -0,0 +1,37 @@ +// @ts-check + +import { expect } from "@playwright/test"; + +import { test } from "../../../fixtures"; +import { login } from "../../../helpers/Auth"; +import { VulnerabilitiesTab } from "./VulnerabilitiesTab"; + +test.describe("Columns validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test("Columns", async ({ page }) => { + const vulnerabilitiesTab = await VulnerabilitiesTab.build( + page, + "keycloak-core", + ); + const table = await vulnerabilitiesTab.getTable(); + + const ids = await table._table + .locator(`td[data-label="ID"]`) + .allInnerTexts(); + const idIndex = ids.indexOf("CVE-2023-1664"); + expect(idIndex).not.toBe(-1); + + // Name + await expect( + table._table.locator(`td[data-label="ID"]`).nth(idIndex), + ).toContainText("CVE-2023-1664"); + + // Title + await expect( + table._table.locator(`td[data-label="CVSS"]`).nth(idIndex), + ).toContainText("Medium(6.5)"); + }); +}); diff --git a/e2e/tests/ui/pages/package-details/vulnerabilities/pagination.spec.ts b/e2e/tests/ui/pages/package-details/vulnerabilities/pagination.spec.ts new file mode 100644 index 000000000..e2bc25a95 --- /dev/null +++ b/e2e/tests/ui/pages/package-details/vulnerabilities/pagination.spec.ts @@ -0,0 +1,34 @@ +// @ts-check + +import { test } from "../../../fixtures"; +import { login } from "../../../helpers/Auth"; +import { VulnerabilitiesTab } from "./VulnerabilitiesTab"; + +// Number of items less than 10, cannot tests pagination +test.describe.skip("Pagination validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test("Navigation button validations", async ({ page }) => { + const vulnerabilitiesTab = await VulnerabilitiesTab.build( + page, + "keycloak-core", + ); + const pagination = await vulnerabilitiesTab.getPagination(); + + await pagination.validatePagination(); + }); + + test("Items per page validations", async ({ page }) => { + const vulnerabilitiesTab = await VulnerabilitiesTab.build( + page, + "keycloak-core", + ); + + const pagination = await vulnerabilitiesTab.getPagination(); + const table = await vulnerabilitiesTab.getTable(); + + await pagination.validateItemsPerPage("ID", table); + }); +}); diff --git a/e2e/tests/ui/pages/package-details/vulnerabilities/sort.spec.ts b/e2e/tests/ui/pages/package-details/vulnerabilities/sort.spec.ts new file mode 100644 index 000000000..edf653494 --- /dev/null +++ b/e2e/tests/ui/pages/package-details/vulnerabilities/sort.spec.ts @@ -0,0 +1,30 @@ +// @ts-check + +import { test } from "../../../fixtures"; +import { login } from "../../../helpers/Auth"; +import { VulnerabilitiesTab } from "./VulnerabilitiesTab"; +import { expectSort } from "../../Helpers"; + +test.describe("Sort validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test("Sort", async ({ page }) => { + const vulnerabilitiesTab = await VulnerabilitiesTab.build( + page, + "keycloak-core", + ); + const table = await vulnerabilitiesTab.getTable(); + + const columnNameSelector = table._table.locator(`td[data-label="ID"]`); + + const ascList = await columnNameSelector.allInnerTexts(); + expectSort(ascList, true); + + // Reverse sorting + await table.clickSortBy("ID"); + const descList = await columnNameSelector.allInnerTexts(); + expectSort(descList, false); + }); +}); diff --git a/e2e/tests/ui/pages/package-list/PackageListPage.ts b/e2e/tests/ui/pages/package-list/PackageListPage.ts new file mode 100644 index 000000000..705ca07e9 --- /dev/null +++ b/e2e/tests/ui/pages/package-list/PackageListPage.ts @@ -0,0 +1,35 @@ +import type { Page } from "@playwright/test"; +import { Navigation } from "../Navigation"; +import { Toolbar } from "../Toolbar"; +import { Table } from "../Table"; +import { Pagination } from "../Pagination"; + +export class PackageListPage { + private readonly _page: Page; + + private constructor(page: Page) { + this._page = page; + } + + static async build(page: Page) { + const navigation = await Navigation.build(page); + await navigation.goToSidebar("Packages"); + + return new PackageListPage(page); + } + + async getToolbar() { + return await Toolbar.build(this._page, "package-toolbar"); + } + + async getTable() { + return await Table.build(this._page, "Package table"); + } + + async getPagination(top: boolean = true) { + return await Pagination.build( + this._page, + `package-table-pagination-${top ? "top" : "bottom"}`, + ); + } +} diff --git a/e2e/tests/ui/pages/package-list/columns.spec.ts b/e2e/tests/ui/pages/package-list/columns.spec.ts new file mode 100644 index 000000000..12feea2de --- /dev/null +++ b/e2e/tests/ui/pages/package-list/columns.spec.ts @@ -0,0 +1,60 @@ +// @ts-check + +import { expect } from "@playwright/test"; + +import { test } from "../../fixtures"; +import { login } from "../../helpers/Auth"; +import { PackageListPage } from "./PackageListPage"; + +test.describe("Columns validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test("Columns", async ({ page }) => { + const listPage = await PackageListPage.build(page); + + const toolbar = await listPage.getToolbar(); + const table = await listPage.getTable(); + + // Full search + await toolbar.applyTextFilter("Filter text", "keycloak-core"); + await table.waitUntilDataIsLoaded(); + await table.verifyColumnContainsText("Name", "keycloak-core"); + + // Namespace + await expect( + table._table.locator(`td[data-label="Namespace"]`), + ).toContainText("org.keycloak"); + + // Version + await expect( + table._table.locator(`td[data-label="Version"]`), + ).toContainText("18.0.6.redhat-00001"); + + // Type + await expect(table._table.locator(`td[data-label="Type"]`)).toContainText( + "maven", + ); + + // Qualifiers + await expect( + table._table.locator(`td[data-label="Qualifiers"]`), + ).toContainText("type=jar"); + await expect( + table._table.locator(`td[data-label="Qualifiers"]`), + ).toContainText("repository_url=https://maven.repository.redhat.com/ga/"); + + // Vulnerabilities + await expect( + table._table + .locator(`td[data-label="Vulnerabilities"]`) + .locator("div[aria-label='total']"), + ).toContainText("1"); + await expect( + table._table + .locator(`td[data-label="Vulnerabilities"]`) + .locator("div[aria-label='medium']"), + ).toContainText("1"); + }); +}); diff --git a/e2e/tests/ui/pages/package-list/filter.spec.ts b/e2e/tests/ui/pages/package-list/filter.spec.ts new file mode 100644 index 000000000..332f4e94a --- /dev/null +++ b/e2e/tests/ui/pages/package-list/filter.spec.ts @@ -0,0 +1,33 @@ +// @ts-check + +import { test } from "../../fixtures"; +import { login } from "../../helpers/Auth"; +import { PackageListPage } from "./PackageListPage"; + +test.describe("Filter validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test("Filters", async ({ page }) => { + const listPage = await PackageListPage.build(page); + + const toolbar = await listPage.getToolbar(); + const table = await listPage.getTable(); + + // Full search + await toolbar.applyTextFilter("Filter text", "keycloak-core"); + await table.waitUntilDataIsLoaded(); + await table.verifyColumnContainsText("Name", "keycloak-core"); + + // Type filter + await toolbar.applyMultiSelectFilter("Type", ["Maven", "RPM"]); + await table.waitUntilDataIsLoaded(); + await table.verifyColumnContainsText("Name", "keycloak-core"); + + // Architecture + await toolbar.applyMultiSelectFilter("Architecture", ["S390", "No Arch"]); + await table.waitUntilDataIsLoaded(); + await table.verifyTableHasNoData(); + }); +}); diff --git a/e2e/tests/ui/pages/package-list/pagination.spec.ts b/e2e/tests/ui/pages/package-list/pagination.spec.ts new file mode 100644 index 000000000..131689590 --- /dev/null +++ b/e2e/tests/ui/pages/package-list/pagination.spec.ts @@ -0,0 +1,27 @@ +// @ts-check + +import { test } from "../../fixtures"; +import { login } from "../../helpers/Auth"; +import { PackageListPage } from "./PackageListPage"; + +test.describe("Pagination validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test("Navigation button validations", async ({ page }) => { + const listPage = await PackageListPage.build(page); + const pagination = await listPage.getPagination(); + + await pagination.validatePagination(); + }); + + test("Items per page validations", async ({ page }) => { + const listPage = await PackageListPage.build(page); + + const pagination = await listPage.getPagination(); + const table = await listPage.getTable(); + + await pagination.validateItemsPerPage("Name", table); + }); +}); diff --git a/e2e/tests/ui/pages/package-list/sort.spec.ts b/e2e/tests/ui/pages/package-list/sort.spec.ts new file mode 100644 index 000000000..c685b775d --- /dev/null +++ b/e2e/tests/ui/pages/package-list/sort.spec.ts @@ -0,0 +1,28 @@ +// @ts-check + +import { test } from "../../fixtures"; +import { login } from "../../helpers/Auth"; +import { expectSort } from "../Helpers"; +import { PackageListPage } from "./PackageListPage"; + +test.describe("Sort validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test("Sort", async ({ page }) => { + const listPage = await PackageListPage.build(page); + const table = await listPage.getTable(); + + const columnNameSelector = table._table.locator(`td[data-label="ID"]`); + + // ID Asc + const ascList = await columnNameSelector.allInnerTexts(); + expectSort(ascList, true); + + // ID Desc + await table.clickSortBy("Name"); + const descList = await columnNameSelector.allInnerTexts(); + expectSort(descList, false); + }); +}); diff --git a/e2e/tests/ui/pages/sbom-details/SbomDetailsPage.ts b/e2e/tests/ui/pages/sbom-details/SbomDetailsPage.ts new file mode 100644 index 000000000..029cc5769 --- /dev/null +++ b/e2e/tests/ui/pages/sbom-details/SbomDetailsPage.ts @@ -0,0 +1,32 @@ +import type { Page } from "@playwright/test"; +import { DetailsPageLayout } from "../DetailsPageLayout"; +import { Navigation } from "../Navigation"; +import { SbomListPage } from "../sbom-list/SbomListPage"; + +export class SbomDetailsPage { + _layout: DetailsPageLayout; + + private constructor(_page: Page, layout: DetailsPageLayout) { + this._layout = layout; + } + + static async build(page: Page, sbomName: string) { + const navigation = await Navigation.build(page); + await navigation.goToSidebar("SBOMs"); + + const listPage = await SbomListPage.build(page); + const toolbar = await listPage.getToolbar(); + const table = await listPage.getTable(); + + await toolbar.applyTextFilter("Filter text", sbomName); + await table.waitUntilDataIsLoaded(); + await table.verifyColumnContainsText("Name", sbomName); + + await page.getByRole("link", { name: sbomName, exact: true }).click(); + + const layout = await DetailsPageLayout.build(page); + await layout.verifyPageHeader(sbomName); + + return new SbomDetailsPage(page, layout); + } +} diff --git a/e2e/tests/ui/pages/sbom-details/actions.spec.ts b/e2e/tests/ui/pages/sbom-details/actions.spec.ts new file mode 100644 index 000000000..45961dc5c --- /dev/null +++ b/e2e/tests/ui/pages/sbom-details/actions.spec.ts @@ -0,0 +1,33 @@ +// @ts-check + +import { expect } from "@playwright/test"; + +import { test } from "../../fixtures"; +import { login } from "../../helpers/Auth"; +import { SbomDetailsPage } from "./SbomDetailsPage"; + +test.describe("Action validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test("Actions - Download SBOM", async ({ page }) => { + const detailsPage = await SbomDetailsPage.build(page, "quarkus-bom"); + + const downloadPromise = page.waitForEvent("download"); + await detailsPage._layout.clickOnPageAction("Download SBOM"); + const download = await downloadPromise; + const filename = download.suggestedFilename(); + expect(filename).toEqual("quarkus-bom.json"); + }); + + test("Actions - Download License Report", async ({ page }) => { + const detailsPage = await SbomDetailsPage.build(page, "quarkus-bom"); + + const downloadPromise = page.waitForEvent("download"); + await detailsPage._layout.clickOnPageAction("Download License Report"); + const download = await downloadPromise; + const filename = download.suggestedFilename(); + expect(filename).toContain("quarkus-bom"); + }); +}); diff --git a/e2e/tests/ui/pages/sbom-details/info/info.spec.ts b/e2e/tests/ui/pages/sbom-details/info/info.spec.ts new file mode 100644 index 000000000..372de55a7 --- /dev/null +++ b/e2e/tests/ui/pages/sbom-details/info/info.spec.ts @@ -0,0 +1,45 @@ +// @ts-check + +import { expect } from "@playwright/test"; + +import { test } from "../../../fixtures"; +import { login } from "../../../helpers/Auth"; +import { SbomDetailsPage } from "../SbomDetailsPage"; +import { LabelsModal } from "../../LabelsModal"; + +test.describe("Info Tab validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test("Labels", async ({ page }) => { + await SbomDetailsPage.build(page, "quarkus-bom"); + const labels = ["color=red", "production"]; + + // Open Edit Labels Modal + await page.getByRole("button", { name: "Edit" }).click(); + + let labelsModal = await LabelsModal.build(page); + await labelsModal.addLabels(labels); + await labelsModal.clickSave(); + + for (const label of labels) { + await expect( + page.locator(".pf-v6-c-label-group__list-item", { hasText: label }), + ).toHaveCount(1); + } + + // Clean labels added previously + await page.getByRole("button", { name: "Edit" }).click(); + + labelsModal = await LabelsModal.build(page); + await labelsModal.removeLabels(labels); + await labelsModal.clickSave(); + + for (const label of labels) { + await expect( + page.locator(".pf-v6-c-label-group__list-item", { hasText: label }), + ).toHaveCount(0); + } + }); +}); diff --git a/e2e/tests/ui/pages/sbom-details/packages/PackagesTab.ts b/e2e/tests/ui/pages/sbom-details/packages/PackagesTab.ts new file mode 100644 index 000000000..26198a7bd --- /dev/null +++ b/e2e/tests/ui/pages/sbom-details/packages/PackagesTab.ts @@ -0,0 +1,37 @@ +import type { Page } from "@playwright/test"; +import { SbomDetailsPage } from "../SbomDetailsPage"; +import { Toolbar } from "../../Toolbar"; +import { Table } from "../../Table"; +import { Pagination } from "../../Pagination"; + +export class PackagesTab { + private readonly _page: Page; + _detailsPage: SbomDetailsPage; + + private constructor(page: Page, layout: SbomDetailsPage) { + this._page = page; + this._detailsPage = layout; + } + + static async build(page: Page, sbomName: string) { + const detailsPage = await SbomDetailsPage.build(page, sbomName); + await detailsPage._layout.selectTab("Packages"); + + return new PackagesTab(page, detailsPage); + } + + async getToolbar() { + return await Toolbar.build(this._page, "Package toolbar"); + } + + async getTable() { + return await Table.build(this._page, "Package table"); + } + + async getPagination(top: boolean = true) { + return await Pagination.build( + this._page, + `package-table-pagination-${top ? "top" : "bottom"}`, + ); + } +} diff --git a/e2e/tests/ui/pages/sbom-details/packages/columns.spec.ts b/e2e/tests/ui/pages/sbom-details/packages/columns.spec.ts new file mode 100644 index 000000000..e2b21f3bb --- /dev/null +++ b/e2e/tests/ui/pages/sbom-details/packages/columns.spec.ts @@ -0,0 +1,72 @@ +// @ts-check + +import { expect } from "@playwright/test"; + +import { test } from "../../../fixtures"; +import { login } from "../../../helpers/Auth"; +import { PackagesTab } from "./PackagesTab"; + +test.describe("Columns validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test("Columns", async ({ page }) => { + const packageTab = await PackagesTab.build(page, "quarkus-bom"); + + const toolbar = await packageTab.getToolbar(); + const table = await packageTab.getTable(); + + // Full search + await toolbar.applyTextFilter("Filter text", "commons-compress"); + await table.waitUntilDataIsLoaded(); + await table.verifyColumnContainsText("Name", "commons-compress"); + + // Name + await expect(table._table.locator(`td[data-label="Name"]`)).toContainText( + "commons-compress", + ); + + // Version + await expect( + table._table.locator(`td[data-label="Version"]`), + ).toContainText("1.21.0.redhat-00001"); + + // Vulnerabilities + await expect( + table._table + .locator(`td[data-label="Vulnerabilities"]`) + .locator("div[aria-label='total']"), + ).toContainText("1"); + + // Licenses + await expect( + table._table.locator(`td[data-label="Licenses"]`), + ).toContainText("2 Licenses"); + + await table._table.locator(`td[data-label="Licenses"]`).click(); + + await expect( + table._table + .locator(`td[data-label="Licenses"]`) + .nth(1) + .locator("ul > li", { hasText: "Apache-2.0" }), + ).toBeVisible(); + await expect( + table._table + .locator(`td[data-label="Licenses"]`) + .nth(1) + .locator("ul > li", { hasText: "NOASSERTION" }), + ).toBeVisible(); + + // PURL + await expect(table._table.locator(`td[data-label="PURLs"]`)).toContainText( + "pkg:maven/org.apache.commons/commons-compress@1.21.0.redhat-00001?repository_url=https://maven.repository.redhat.com/ga/&type=jar", + ); + + // CPE + await expect(table._table.locator(`td[data-label="CPEs"]`)).toContainText( + "0 CPEs", + ); + }); +}); diff --git a/e2e/tests/ui/pages/sbom-details/packages/filter.spec.ts b/e2e/tests/ui/pages/sbom-details/packages/filter.spec.ts new file mode 100644 index 000000000..f26ee5758 --- /dev/null +++ b/e2e/tests/ui/pages/sbom-details/packages/filter.spec.ts @@ -0,0 +1,31 @@ +// @ts-check + +import { test } from "../../../fixtures"; +import { login } from "../../../helpers/Auth"; +import { PackagesTab } from "./PackagesTab"; + +test.describe("Filter validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test("Filters", async ({ page }) => { + const packageTab = await PackagesTab.build(page, "quarkus-bom"); + + const toolbar = await packageTab.getToolbar(); + const table = await packageTab.getTable(); + + // Full search + await toolbar.applyTextFilter("Filter text", "commons-compress"); + await table.waitUntilDataIsLoaded(); + await table.verifyColumnContainsText("Name", "commons-compress"); + + // Labels filter + await toolbar.applyMultiSelectFilter("License", [ + "Apache-2.0", + "NOASSERTION", + ]); + await table.waitUntilDataIsLoaded(); + await table.verifyColumnContainsText("Name", "commons-compress"); + }); +}); diff --git a/e2e/tests/ui/pages/sbom-details/packages/pagination.spec.ts b/e2e/tests/ui/pages/sbom-details/packages/pagination.spec.ts new file mode 100644 index 000000000..fd56de05b --- /dev/null +++ b/e2e/tests/ui/pages/sbom-details/packages/pagination.spec.ts @@ -0,0 +1,27 @@ +// @ts-check + +import { test } from "../../../fixtures"; +import { login } from "../../../helpers/Auth"; +import { PackagesTab } from "./PackagesTab"; + +test.describe("Pagination validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test("Navigation button validations", async ({ page }) => { + const packageTab = await PackagesTab.build(page, "quarkus-bom"); + const pagination = await packageTab.getPagination(); + + await pagination.validatePagination(); + }); + + test("Items per page validations", async ({ page }) => { + const packageTab = await PackagesTab.build(page, "quarkus-bom"); + + const pagination = await packageTab.getPagination(); + const table = await packageTab.getTable(); + + await pagination.validateItemsPerPage("Name", table); + }); +}); diff --git a/e2e/tests/ui/pages/sbom-details/packages/sort.spec.ts b/e2e/tests/ui/pages/sbom-details/packages/sort.spec.ts new file mode 100644 index 000000000..d7574f164 --- /dev/null +++ b/e2e/tests/ui/pages/sbom-details/packages/sort.spec.ts @@ -0,0 +1,27 @@ +// @ts-check + +import { test } from "../../../fixtures"; +import { login } from "../../../helpers/Auth"; +import { PackagesTab } from "./PackagesTab"; +import { expectSort } from "../../Helpers"; + +test.describe("Sort validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test("Sort", async ({ page }) => { + const packageTab = await PackagesTab.build(page, "quarkus-bom"); + const table = await packageTab.getTable(); + + const columnNameSelector = table._table.locator(`td[data-label="Name"]`); + + const ascList = await columnNameSelector.allInnerTexts(); + expectSort(ascList, true); + + // Reverse sorting + await table.clickSortBy("Name"); + const descList = await columnNameSelector.allInnerTexts(); + expectSort(descList, false); + }); +}); diff --git a/e2e/tests/ui/pages/sbom-details/vulnerabilities/VulnerabilitiesTab.ts b/e2e/tests/ui/pages/sbom-details/vulnerabilities/VulnerabilitiesTab.ts new file mode 100644 index 000000000..c1e9a8ec4 --- /dev/null +++ b/e2e/tests/ui/pages/sbom-details/vulnerabilities/VulnerabilitiesTab.ts @@ -0,0 +1,37 @@ +import type { Page } from "@playwright/test"; +import { SbomDetailsPage } from "../SbomDetailsPage"; +import { Toolbar } from "../../Toolbar"; +import { Table } from "../../Table"; +import { Pagination } from "../../Pagination"; + +export class VulnerabilitiesTab { + private readonly _page: Page; + _detailsPage: SbomDetailsPage; + + private constructor(page: Page, layout: SbomDetailsPage) { + this._page = page; + this._detailsPage = layout; + } + + static async build(page: Page, sbomName: string) { + const detailsPage = await SbomDetailsPage.build(page, sbomName); + await detailsPage._layout.selectTab("Vulnerabilities"); + + return new VulnerabilitiesTab(page, detailsPage); + } + + async getToolbar() { + return await Toolbar.build(this._page, "Vulnerability toolbar"); + } + + async getTable() { + return await Table.build(this._page, "Vulnerability table"); + } + + async getPagination(top: boolean = true) { + return await Pagination.build( + this._page, + `vulnerability-table-pagination-${top ? "top" : "bottom"}`, + ); + } +} diff --git a/e2e/tests/ui/pages/sbom-details/vulnerabilities/columns.spec.ts b/e2e/tests/ui/pages/sbom-details/vulnerabilities/columns.spec.ts new file mode 100644 index 000000000..a32a4c825 --- /dev/null +++ b/e2e/tests/ui/pages/sbom-details/vulnerabilities/columns.spec.ts @@ -0,0 +1,71 @@ +// @ts-check + +import { expect } from "@playwright/test"; + +import { test } from "../../../fixtures"; +import { login } from "../../../helpers/Auth"; +import { VulnerabilitiesTab } from "./VulnerabilitiesTab"; + +test.describe("Columns validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test("Columns", async ({ page }) => { + const vulnerabilityTab = await VulnerabilitiesTab.build( + page, + "quarkus-bom", + ); + + const table = await vulnerabilityTab.getTable(); + + const ids = await table._table + .locator(`td[data-label="Id"]`) + .allInnerTexts(); + const idIndex = ids.indexOf("CVE-2023-4853"); + expect(idIndex).not.toBe(-1); + + // Name + await expect( + table._table.locator(`td[data-label="Id"]`).nth(idIndex), + ).toContainText("CVE-2023-4853"); + + // Description + await expect( + table._table.locator(`td[data-label="Description"]`).nth(idIndex), + ).toContainText("quarkus: HTTP security policy bypass"); + + // Vulnerabilities + await expect( + table._table.locator(`td[data-label="CVSS"]`).nth(idIndex), + ).toContainText("High(8.1)"); + + // Affected dependencies + await expect( + table._table + .locator(`td[data-label="Affected dependencies"]`) + .nth(idIndex), + ).toContainText("3"); + + await table._table + .locator(`td[data-label="Affected dependencies"]`) + .nth(idIndex) + .click(); + + await expect( + table._table + .locator(`td[data-label="Affected dependencies"]`) + .nth(idIndex + 1), + ).toContainText("quarkus-undertow"); + await expect( + table._table + .locator(`td[data-label="Affected dependencies"]`) + .nth(idIndex + 1), + ).toContainText("quarkus-keycloak-authorization"); + await expect( + table._table + .locator(`td[data-label="Affected dependencies"]`) + .nth(idIndex + 1), + ).toContainText("quarkus-vertx-http"); + }); +}); diff --git a/e2e/tests/ui/pages/sbom-details/vulnerabilities/donutchart.spec.ts b/e2e/tests/ui/pages/sbom-details/vulnerabilities/donutchart.spec.ts new file mode 100644 index 000000000..4922cb46c --- /dev/null +++ b/e2e/tests/ui/pages/sbom-details/vulnerabilities/donutchart.spec.ts @@ -0,0 +1,24 @@ +// @ts-check + +import { expect } from "@playwright/test"; + +import { test } from "../../../fixtures"; +import { login } from "../../../helpers/Auth"; +import { VulnerabilitiesTab } from "./VulnerabilitiesTab"; + +test.describe("DonutChart validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test("Vulnerabilities", async ({ page }) => { + await VulnerabilitiesTab.build(page, "quarkus-bom"); + + await expect(page.locator("#legend-labels-0")).toContainText("Critical: 0"); + await expect(page.locator("#legend-labels-1")).toContainText("High: 1"); + await expect(page.locator("#legend-labels-2")).toContainText("Medium: 10"); + await expect(page.locator("#legend-labels-3")).toContainText("Low: 0"); + await expect(page.locator("#legend-labels-4")).toContainText("None: 0"); + await expect(page.locator("#legend-labels-5")).toContainText("Unknown: 0"); + }); +}); diff --git a/e2e/tests/ui/pages/sbom-details/vulnerabilities/filter.spec.ts b/e2e/tests/ui/pages/sbom-details/vulnerabilities/filter.spec.ts new file mode 100644 index 000000000..e785a06f5 --- /dev/null +++ b/e2e/tests/ui/pages/sbom-details/vulnerabilities/filter.spec.ts @@ -0,0 +1,32 @@ +// @ts-check + +import { test } from "../../../fixtures"; +import { login } from "../../../helpers/Auth"; +import { VulnerabilitiesTab } from "./VulnerabilitiesTab"; + +// Table has no filters +test.describe.skip("Filter validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test.skip("Filters", async ({ page }) => { + const vulnerabilityTab = await VulnerabilitiesTab.build( + page, + "quarkus-bom", + ); + + const toolbar = await vulnerabilityTab.getToolbar(); + const table = await vulnerabilityTab.getTable(); + + // Full search + await toolbar.applyTextFilter("Filter text", "CVE-2023-4853"); + await table.waitUntilDataIsLoaded(); + await table.verifyColumnContainsText("Id", "CVE-2023-4853"); + + // Labels filter + await toolbar.applyMultiSelectFilter("Severity", ["High"]); + await table.waitUntilDataIsLoaded(); + await table.verifyColumnContainsText("Id", "CVE-2023-4853"); + }); +}); diff --git a/e2e/tests/ui/pages/sbom-details/vulnerabilities/pagination.spec.ts b/e2e/tests/ui/pages/sbom-details/vulnerabilities/pagination.spec.ts new file mode 100644 index 000000000..3d2460a00 --- /dev/null +++ b/e2e/tests/ui/pages/sbom-details/vulnerabilities/pagination.spec.ts @@ -0,0 +1,30 @@ +// @ts-check + +import { test } from "../../../fixtures"; +import { login } from "../../../helpers/Auth"; +import { VulnerabilitiesTab } from "./VulnerabilitiesTab"; + +test.describe("Pagination validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test("Navigation button validations", async ({ page }) => { + const vulnerabilityTab = await VulnerabilitiesTab.build( + page, + "quarkus-bom", + ); + const pagination = await vulnerabilityTab.getPagination(); + + await pagination.validatePagination(); + }); + + test("Items per page validations", async ({ page }) => { + const packageTab = await VulnerabilitiesTab.build(page, "quarkus-bom"); + + const pagination = await packageTab.getPagination(); + const table = await packageTab.getTable(); + + await pagination.validateItemsPerPage("Id", table); + }); +}); diff --git a/e2e/tests/ui/pages/sbom-details/vulnerabilities/sort.spec.ts b/e2e/tests/ui/pages/sbom-details/vulnerabilities/sort.spec.ts new file mode 100644 index 000000000..adee61606 --- /dev/null +++ b/e2e/tests/ui/pages/sbom-details/vulnerabilities/sort.spec.ts @@ -0,0 +1,30 @@ +// @ts-check + +import { test } from "../../../fixtures"; +import { login } from "../../../helpers/Auth"; +import { VulnerabilitiesTab } from "./VulnerabilitiesTab"; +import { expectSort } from "../../Helpers"; + +test.describe("Sort validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test("Sort", async ({ page }) => { + const vulnerabilityTab = await VulnerabilitiesTab.build( + page, + "quarkus-bom", + ); + const table = await vulnerabilityTab.getTable(); + + const columnNameSelector = table._table.locator(`td[data-label="Id"]`); + + const ascList = await columnNameSelector.allInnerTexts(); + expectSort(ascList, true); + + // Reverse sorting + await table.clickSortBy("Id"); + const descList = await columnNameSelector.allInnerTexts(); + expectSort(descList, false); + }); +}); diff --git a/e2e/tests/ui/pages/sbom-list/SbomListPage.ts b/e2e/tests/ui/pages/sbom-list/SbomListPage.ts new file mode 100644 index 000000000..fd9fd6a8c --- /dev/null +++ b/e2e/tests/ui/pages/sbom-list/SbomListPage.ts @@ -0,0 +1,35 @@ +import type { Page } from "@playwright/test"; +import { Navigation } from "../Navigation"; +import { Toolbar } from "../Toolbar"; +import { Table } from "../Table"; +import { Pagination } from "../Pagination"; + +export class SbomListPage { + private readonly _page: Page; + + private constructor(page: Page) { + this._page = page; + } + + static async build(page: Page) { + const navigation = await Navigation.build(page); + await navigation.goToSidebar("SBOMs"); + + return new SbomListPage(page); + } + + async getToolbar() { + return await Toolbar.build(this._page, "sbom-toolbar"); + } + + async getTable() { + return await Table.build(this._page, "sbom-table"); + } + + async getPagination(top: boolean = true) { + return await Pagination.build( + this._page, + `sbom-table-pagination-${top ? "top" : "bottom"}`, + ); + } +} diff --git a/e2e/tests/ui/pages/sbom-list/actions.spec.ts b/e2e/tests/ui/pages/sbom-list/actions.spec.ts new file mode 100644 index 000000000..33a5c8c0a --- /dev/null +++ b/e2e/tests/ui/pages/sbom-list/actions.spec.ts @@ -0,0 +1,86 @@ +// @ts-check + +import { expect } from "@playwright/test"; + +import { test } from "../../fixtures"; +import { login } from "../../helpers/Auth"; +import { LabelsModal } from "../LabelsModal"; +import { SbomListPage } from "./SbomListPage"; + +test.describe("Action validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test("Actions - Download SBOM", async ({ page }) => { + const listPage = await SbomListPage.build(page); + const table = await listPage.getTable(); + + const sbomNames = await table._table + .locator(`td[data-label="Name"]`) + .allInnerTexts(); + + const downloadPromise = page.waitForEvent("download"); + await table.clickAction("Download SBOM", 0); + const download = await downloadPromise; + const filename = download.suggestedFilename(); + expect(filename).toEqual(`${sbomNames[0]}.json`); + }); + + test("Actions - Download License Report", async ({ page }) => { + const listPage = await SbomListPage.build(page); + const table = await listPage.getTable(); + + const sbomNames = await table._table + .locator(`td[data-label="Name"]`) + .allInnerTexts(); + + const downloadPromise = page.waitForEvent("download"); + await table.clickAction("Download License Report", 0); + const download = await downloadPromise; + const filename = download.suggestedFilename(); + expect(filename).toContain(sbomNames[0]); + }); + + test("Actions - Edit Labels", async ({ page }) => { + const listPage = await SbomListPage.build(page); + const table = await listPage.getTable(); + + const labels = ["color=red", "production"]; + await table.clickAction("Edit labels", 0); + + let labelsModal = await LabelsModal.build(page); + + // Add labels + await labelsModal.addLabels(labels); + await labelsModal.clickSave(); + + // Verify labels were added + await table.waitUntilDataIsLoaded(); + for (const label of labels) { + await expect( + table._table + .locator(`td[data-label="Labels"]`) + .first() + .locator(".pf-v6-c-label", { hasText: label }), + ).toHaveCount(1); + } + + // Clean labels added previously + await table.clickAction("Edit labels", 0); + + labelsModal = await LabelsModal.build(page); + await labelsModal.removeLabels(labels); + await labelsModal.clickSave(); + + await table.waitUntilDataIsLoaded(); + for (const label of labels) { + await expect( + table._table + .locator(`td[data-label="Labels"]`) + .first() + .locator(".pf-v6-c-label", { hasText: label }), + ).toHaveCount(0); + } + }); +}); diff --git a/e2e/tests/ui/pages/sbom-list/columns.spec.ts b/e2e/tests/ui/pages/sbom-list/columns.spec.ts new file mode 100644 index 000000000..7fe5780ad --- /dev/null +++ b/e2e/tests/ui/pages/sbom-list/columns.spec.ts @@ -0,0 +1,54 @@ +// @ts-check + +import { expect } from "@playwright/test"; + +import { test } from "../../fixtures"; +import { login } from "../../helpers/Auth"; +import { SbomListPage } from "./SbomListPage"; + +test.describe("Columns validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test("Vulnerabilities", async ({ page }) => { + const listPage = await SbomListPage.build(page); + + const toolbar = await listPage.getToolbar(); + const table = await listPage.getTable(); + + // Full search + await toolbar.applyTextFilter("Filter text", "quarkus-bom"); + await table.waitUntilDataIsLoaded(); + await table.verifyColumnContainsText("Name", "quarkus-bom"); + + // Total Vulnerabilities + await expect( + table._table + .locator(`td[data-label="Vulnerabilities"]`) + .locator("div[aria-label='total']", { hasText: "11" }), + ).toHaveCount(1); + + // Severities + const expectedVulnerabilities = [ + { + severity: "high", + count: 1, + }, + { + severity: "medium", + count: 10, + }, + ]; + + for (const expectedVulnerability of expectedVulnerabilities) { + await expect( + table._table + .locator(`td[data-label="Vulnerabilities"]`) + .locator(`div[aria-label="${expectedVulnerability.severity}"]`, { + hasText: expectedVulnerability.count.toString(), + }), + ).toHaveCount(1); + } + }); +}); diff --git a/e2e/tests/ui/pages/sbom-list/filter.spec.ts b/e2e/tests/ui/pages/sbom-list/filter.spec.ts new file mode 100644 index 000000000..7fa0c4564 --- /dev/null +++ b/e2e/tests/ui/pages/sbom-list/filter.spec.ts @@ -0,0 +1,37 @@ +// @ts-check + +import { test } from "../../fixtures"; +import { login } from "../../helpers/Auth"; +import { SbomListPage } from "./SbomListPage"; + +test.describe("Filter validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test("Filters", async ({ page }) => { + const listPage = await SbomListPage.build(page); + + const toolbar = await listPage.getToolbar(); + const table = await listPage.getTable(); + + // Full search + await toolbar.applyTextFilter("Filter text", "quarkus"); + await table.waitUntilDataIsLoaded(); + await table.verifyColumnContainsText("Name", "quarkus-bom"); + + // Date filter + await toolbar.applyDateRangeFilter( + "Created on", + "11/21/2023", + "11/23/2023", + ); + await table.waitUntilDataIsLoaded(); + await table.verifyColumnContainsText("Name", "quarkus-bom"); + + // Labels filter + await toolbar.applyLabelsFilter("Label", ["type=spdx"]); + await table.waitUntilDataIsLoaded(); + await table.verifyColumnContainsText("Name", "quarkus-bom"); + }); +}); diff --git a/e2e/tests/ui/pages/sbom-list/pagination.spec.ts b/e2e/tests/ui/pages/sbom-list/pagination.spec.ts new file mode 100644 index 000000000..be9174b0e --- /dev/null +++ b/e2e/tests/ui/pages/sbom-list/pagination.spec.ts @@ -0,0 +1,27 @@ +// @ts-check + +import { test } from "../../fixtures"; +import { login } from "../../helpers/Auth"; +import { SbomListPage } from "./SbomListPage"; + +test.describe("Pagination validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test("Navigation button validations", async ({ page }) => { + const listPage = await SbomListPage.build(page); + const pagination = await listPage.getPagination(); + + await pagination.validatePagination(); + }); + + test("Items per page validations", async ({ page }) => { + const listPage = await SbomListPage.build(page); + + const pagination = await listPage.getPagination(); + const table = await listPage.getTable(); + + await pagination.validateItemsPerPage("Name", table); + }); +}); diff --git a/e2e/tests/ui/pages/sbom-list/sort.spec.ts b/e2e/tests/ui/pages/sbom-list/sort.spec.ts new file mode 100644 index 000000000..555b7c92a --- /dev/null +++ b/e2e/tests/ui/pages/sbom-list/sort.spec.ts @@ -0,0 +1,27 @@ +// @ts-check + +import { test } from "../../fixtures"; +import { login } from "../../helpers/Auth"; +import { expectSort } from "../Helpers"; +import { SbomListPage } from "./SbomListPage"; + +test.describe("Sort validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test("Sort", async ({ page }) => { + const listPage = await SbomListPage.build(page); + const table = await listPage.getTable(); + + const columnNameSelector = table._table.locator(`td[data-label="Name"]`); + + const ascList = await columnNameSelector.allInnerTexts(); + expectSort(ascList, true); + + // Reverse sorting + await table.clickSortBy("Name"); + const desList = await columnNameSelector.allInnerTexts(); + expectSort(desList, false); + }); +}); diff --git a/e2e/tests/ui/pages/vulnerability-details/VulnerabilityDetailsPage.ts b/e2e/tests/ui/pages/vulnerability-details/VulnerabilityDetailsPage.ts new file mode 100644 index 000000000..dd30ec034 --- /dev/null +++ b/e2e/tests/ui/pages/vulnerability-details/VulnerabilityDetailsPage.ts @@ -0,0 +1,34 @@ +import type { Page } from "@playwright/test"; +import { DetailsPageLayout } from "../DetailsPageLayout"; +import { Navigation } from "../Navigation"; +import { VulnerabilityListPage } from "../vulnerability-list/VulnerabilityListPage"; + +export class VulnerabilityDetailsPage { + _layout: DetailsPageLayout; + + private constructor(_page: Page, layout: DetailsPageLayout) { + this._layout = layout; + } + + static async build(page: Page, vulnerabilityID: string) { + const navigation = await Navigation.build(page); + await navigation.goToSidebar("Vulnerabilities"); + + const listPage = await VulnerabilityListPage.build(page); + const toolbar = await listPage.getToolbar(); + const table = await listPage.getTable(); + + await toolbar.applyTextFilter("Filter text", vulnerabilityID); + await table.waitUntilDataIsLoaded(); + await table.verifyColumnContainsText("ID", vulnerabilityID); + + await page + .getByRole("link", { name: vulnerabilityID, exact: true }) + .click(); + + const layout = await DetailsPageLayout.build(page); + await layout.verifyPageHeader(vulnerabilityID); + + return new VulnerabilityDetailsPage(page, layout); + } +} diff --git a/e2e/tests/ui/pages/vulnerability-details/advisories/AdvisoriesTab.ts b/e2e/tests/ui/pages/vulnerability-details/advisories/AdvisoriesTab.ts new file mode 100644 index 000000000..597625c7a --- /dev/null +++ b/e2e/tests/ui/pages/vulnerability-details/advisories/AdvisoriesTab.ts @@ -0,0 +1,40 @@ +import type { Page } from "@playwright/test"; +import { VulnerabilityDetailsPage } from "../VulnerabilityDetailsPage"; +import { Toolbar } from "../../Toolbar"; +import { Table } from "../../Table"; +import { Pagination } from "../../Pagination"; + +export class AdvisoriesTab { + private readonly _page: Page; + _detailsPage: VulnerabilityDetailsPage; + + private constructor(page: Page, layout: VulnerabilityDetailsPage) { + this._page = page; + this._detailsPage = layout; + } + + static async build(page: Page, vulnerabilityID: string) { + const detailsPage = await VulnerabilityDetailsPage.build( + page, + vulnerabilityID, + ); + await detailsPage._layout.selectTab("Related Advisories"); + + return new AdvisoriesTab(page, detailsPage); + } + + async getToolbar() { + return await Toolbar.build(this._page, "Advisory toolbar"); + } + + async getTable() { + return await Table.build(this._page, "Advisory table"); + } + + async getPagination(top: boolean = true) { + return await Pagination.build( + this._page, + `advisory-table-pagination-${top ? "top" : "bottom"}`, + ); + } +} diff --git a/e2e/tests/ui/pages/vulnerability-details/advisories/columns.spec.ts b/e2e/tests/ui/pages/vulnerability-details/advisories/columns.spec.ts new file mode 100644 index 000000000..9ee0b564a --- /dev/null +++ b/e2e/tests/ui/pages/vulnerability-details/advisories/columns.spec.ts @@ -0,0 +1,49 @@ +// @ts-check + +import { expect } from "@playwright/test"; + +import { test } from "../../../fixtures"; +import { login } from "../../../helpers/Auth"; +import { AdvisoriesTab } from "./AdvisoriesTab"; + +test.describe("Columns validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test("Columns", async ({ page }) => { + const advisoriesTab = await AdvisoriesTab.build(page, "CVE-2023-2976"); + const table = await advisoriesTab.getTable(); + + const types = await table._table + .locator(`td[data-label="Type"]`) + .allInnerTexts(); + const rowIndex = types.indexOf("csaf"); + expect(rowIndex).not.toBe(-1); + + // Name + await expect( + table._table.locator(`td[data-label="ID"]`).nth(rowIndex), + ).toContainText("CVE-2023-2976"); + + // Title + await expect( + table._table.locator(`td[data-label="Title"]`).nth(rowIndex), + ).toContainText("guava: insecure temporary directory creation"); + + // Type + await expect( + table._table.locator(`td[data-label="Type"]`).nth(rowIndex), + ).toContainText("csaf"); + + // Revision + await expect( + table._table.locator(`td[data-label="Revision"]`).nth(rowIndex), + ).toContainText("Nov 14, 2023"); + + // Vulnerabilities + await expect( + table._table.locator(`td[data-label="Vulnerabilities"]`).nth(rowIndex), + ).toContainText("1"); + }); +}); diff --git a/e2e/tests/ui/pages/vulnerability-details/advisories/pagination.spec.ts b/e2e/tests/ui/pages/vulnerability-details/advisories/pagination.spec.ts new file mode 100644 index 000000000..6ebd78a94 --- /dev/null +++ b/e2e/tests/ui/pages/vulnerability-details/advisories/pagination.spec.ts @@ -0,0 +1,28 @@ +// @ts-check + +import { test } from "../../../fixtures"; +import { login } from "../../../helpers/Auth"; +import { AdvisoriesTab } from "./AdvisoriesTab"; + +// Number of items less than 10, cannot tests pagination +test.describe.skip("Pagination validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test("Navigation button validations", async ({ page }) => { + const advisoriesTab = await AdvisoriesTab.build(page, "CVE-2023-2976"); + const pagination = await advisoriesTab.getPagination(); + + await pagination.validatePagination(); + }); + + test("Items per page validations", async ({ page }) => { + const sbomTab = await AdvisoriesTab.build(page, "CVE-2023-2976"); + + const pagination = await sbomTab.getPagination(); + const table = await sbomTab.getTable(); + + await pagination.validateItemsPerPage("ID", table); + }); +}); diff --git a/e2e/tests/ui/pages/vulnerability-details/advisories/sort.spec.ts b/e2e/tests/ui/pages/vulnerability-details/advisories/sort.spec.ts new file mode 100644 index 000000000..1538614e0 --- /dev/null +++ b/e2e/tests/ui/pages/vulnerability-details/advisories/sort.spec.ts @@ -0,0 +1,27 @@ +// @ts-check + +import { test } from "../../../fixtures"; +import { login } from "../../../helpers/Auth"; +import { AdvisoriesTab } from "./AdvisoriesTab"; +import { expectSort } from "../../Helpers"; + +test.describe("Sort validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test("Sort", async ({ page }) => { + const advisoryTab = await AdvisoriesTab.build(page, "CVE-2023-2976"); + const table = await advisoryTab.getTable(); + + const columnNameSelector = table._table.locator(`td[data-label="ID"]`); + + const ascList = await columnNameSelector.allInnerTexts(); + expectSort(ascList, true); + + // Reverse sorting + await table.clickSortBy("ID"); + const descList = await columnNameSelector.allInnerTexts(); + expectSort(descList, false); + }); +}); diff --git a/e2e/tests/ui/pages/vulnerability-details/info/info.spec.ts b/e2e/tests/ui/pages/vulnerability-details/info/info.spec.ts new file mode 100644 index 000000000..b1773b317 --- /dev/null +++ b/e2e/tests/ui/pages/vulnerability-details/info/info.spec.ts @@ -0,0 +1,16 @@ +// @ts-check + +import { test } from "../../../fixtures"; +import { login } from "../../../helpers/Auth"; +import { VulnerabilityDetailsPage } from "../VulnerabilityDetailsPage"; + +test.describe("Info Tab validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test("Info", async ({ page }) => { + await VulnerabilityDetailsPage.build(page, "CVE-2023-2976"); + // Verify + }); +}); diff --git a/e2e/tests/ui/pages/vulnerability-details/sboms/SbomsTab.ts b/e2e/tests/ui/pages/vulnerability-details/sboms/SbomsTab.ts new file mode 100644 index 000000000..8f21c6465 --- /dev/null +++ b/e2e/tests/ui/pages/vulnerability-details/sboms/SbomsTab.ts @@ -0,0 +1,40 @@ +import type { Page } from "@playwright/test"; +import { VulnerabilityDetailsPage } from "../VulnerabilityDetailsPage"; +import { Toolbar } from "../../Toolbar"; +import { Table } from "../../Table"; +import { Pagination } from "../../Pagination"; + +export class SbomsTab { + private readonly _page: Page; + _detailsPage: VulnerabilityDetailsPage; + + private constructor(page: Page, layout: VulnerabilityDetailsPage) { + this._page = page; + this._detailsPage = layout; + } + + static async build(page: Page, vulnerabilityID: string) { + const detailsPage = await VulnerabilityDetailsPage.build( + page, + vulnerabilityID, + ); + await detailsPage._layout.selectTab("Related SBOMs"); + + return new SbomsTab(page, detailsPage); + } + + async getToolbar() { + return await Toolbar.build(this._page, "Sbom toolbar"); + } + + async getTable() { + return await Table.build(this._page, "Sbom table"); + } + + async getPagination(top: boolean = true) { + return await Pagination.build( + this._page, + `sbom-table-pagination-${top ? "top" : "bottom"}`, + ); + } +} diff --git a/e2e/tests/ui/pages/vulnerability-details/sboms/columns.spec.ts b/e2e/tests/ui/pages/vulnerability-details/sboms/columns.spec.ts new file mode 100644 index 000000000..65d7400d6 --- /dev/null +++ b/e2e/tests/ui/pages/vulnerability-details/sboms/columns.spec.ts @@ -0,0 +1,69 @@ +// @ts-check + +import { expect } from "@playwright/test"; + +import { test } from "../../../fixtures"; +import { login } from "../../../helpers/Auth"; +import { SbomsTab } from "./SbomsTab"; + +test.describe("Columns validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test("Columns", async ({ page }) => { + const sbomTab = await SbomsTab.build(page, "CVE-2023-2976"); + const table = await sbomTab.getTable(); + + const ids = await table._table + .locator(`td[data-label="Name"]`) + .allInnerTexts(); + const idIndex = ids.indexOf("quarkus-bom"); + expect(idIndex).not.toBe(-1); + + // Name + await expect( + table._table.locator(`td[data-label="Name"]`).nth(idIndex), + ).toContainText("quarkus-bom"); + + // Version + await expect( + table._table.locator(`td[data-label="Version"]`).nth(idIndex), + ).toContainText("2.13.8.Final-redhat-00004"); + + // Status + await expect( + table._table.locator(`td[data-label="Status"]`).nth(idIndex), + ).toContainText("Affected"); + + // Dependencies + await expect( + table._table.locator(`td[data-label="Dependencies"]`).nth(idIndex), + ).toContainText("1"); + + await table._table + .locator(`td[data-label="Dependencies"]`) + .nth(idIndex) + .click(); + + await expect( + table._table.locator(`td[data-label="Dependencies"]`).nth(idIndex + 1), + ).toContainText("com.google.guava"); + await expect( + table._table + .locator(`td[data-label="Dependencies"]`) + .nth(idIndex + 1) + .getByRole("link", { name: "guava" }), + ).toHaveCount(1); + + // Status + await expect( + table._table.locator(`td[data-label="Supplier"]`).nth(idIndex), + ).toContainText("Organization: Red Hat"); + + // Status + await expect( + table._table.locator(`td[data-label="Created on"]`).nth(idIndex), + ).toContainText("Nov 22, 2023"); + }); +}); diff --git a/e2e/tests/ui/pages/vulnerability-details/sboms/pagination.spec.ts b/e2e/tests/ui/pages/vulnerability-details/sboms/pagination.spec.ts new file mode 100644 index 000000000..83a988907 --- /dev/null +++ b/e2e/tests/ui/pages/vulnerability-details/sboms/pagination.spec.ts @@ -0,0 +1,28 @@ +// @ts-check + +import { test } from "../../../fixtures"; +import { login } from "../../../helpers/Auth"; +import { SbomsTab } from "./SbomsTab"; + +// Number of items less than 10, cannot tests pagination +test.describe.skip("Pagination validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test("Navigation button validations", async ({ page }) => { + const sbomTab = await SbomsTab.build(page, "CVE-2023-2976"); + const pagination = await sbomTab.getPagination(); + + await pagination.validatePagination(); + }); + + test("Items per page validations", async ({ page }) => { + const sbomTab = await SbomsTab.build(page, "CVE-2023-2976"); + + const pagination = await sbomTab.getPagination(); + const table = await sbomTab.getTable(); + + await pagination.validateItemsPerPage("Name", table); + }); +}); diff --git a/e2e/tests/ui/pages/vulnerability-details/sboms/sort.spec.ts b/e2e/tests/ui/pages/vulnerability-details/sboms/sort.spec.ts new file mode 100644 index 000000000..041b09e9c --- /dev/null +++ b/e2e/tests/ui/pages/vulnerability-details/sboms/sort.spec.ts @@ -0,0 +1,27 @@ +// @ts-check + +import { test } from "../../../fixtures"; +import { login } from "../../../helpers/Auth"; +import { SbomsTab } from "./SbomsTab"; +import { expectSort } from "../../Helpers"; + +test.describe("Sort validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test("Sort", async ({ page }) => { + const sbomTab = await SbomsTab.build(page, "CVE-2023-2976"); + const table = await sbomTab.getTable(); + + const columnNameSelector = table._table.locator(`td[data-label="Name"]`); + + const ascList = await columnNameSelector.allInnerTexts(); + expectSort(ascList, true); + + // Reverse sorting + await table.clickSortBy("Name"); + const descList = await columnNameSelector.allInnerTexts(); + expectSort(descList, false); + }); +}); diff --git a/e2e/tests/ui/pages/vulnerability-list/VulnerabilityListPage.ts b/e2e/tests/ui/pages/vulnerability-list/VulnerabilityListPage.ts new file mode 100644 index 000000000..b994f08f2 --- /dev/null +++ b/e2e/tests/ui/pages/vulnerability-list/VulnerabilityListPage.ts @@ -0,0 +1,35 @@ +import type { Page } from "@playwright/test"; +import { Navigation } from "../Navigation"; +import { Toolbar } from "../Toolbar"; +import { Table } from "../Table"; +import { Pagination } from "../Pagination"; + +export class VulnerabilityListPage { + private readonly _page: Page; + + private constructor(page: Page) { + this._page = page; + } + + static async build(page: Page) { + const navigation = await Navigation.build(page); + await navigation.goToSidebar("Vulnerabilities"); + + return new VulnerabilityListPage(page); + } + + async getToolbar() { + return await Toolbar.build(this._page, "vulnerability-toolbar"); + } + + async getTable() { + return await Table.build(this._page, "Vulnerability table"); + } + + async getPagination(top: boolean = true) { + return await Pagination.build( + this._page, + `vulnerability-table-pagination-${top ? "top" : "bottom"}`, + ); + } +} diff --git a/e2e/tests/ui/pages/vulnerability-list/columns.spec.ts b/e2e/tests/ui/pages/vulnerability-list/columns.spec.ts new file mode 100644 index 000000000..081e1395a --- /dev/null +++ b/e2e/tests/ui/pages/vulnerability-list/columns.spec.ts @@ -0,0 +1,30 @@ +// @ts-check + +import { expect } from "@playwright/test"; + +import { test } from "../../fixtures"; +import { login } from "../../helpers/Auth"; +import { VulnerabilityListPage } from "./VulnerabilityListPage"; + +test.describe("Columns validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test("Impacted SBOMs", async ({ page }) => { + const listPage = await VulnerabilityListPage.build(page); + + const toolbar = await listPage.getToolbar(); + const table = await listPage.getTable(); + + // Full search + await toolbar.applyTextFilter("Filter text", "CVE-2024-26308"); + await table.waitUntilDataIsLoaded(); + await table.verifyColumnContainsText("ID", "CVE-2024-26308"); + + // Impacted SBOMs + await expect( + table._table.locator(`td[data-label="Impacted SBOMs"]`), + ).toContainText("1"); + }); +}); diff --git a/e2e/tests/ui/pages/vulnerability-list/filter.spec.ts b/e2e/tests/ui/pages/vulnerability-list/filter.spec.ts new file mode 100644 index 000000000..a9a2c8e12 --- /dev/null +++ b/e2e/tests/ui/pages/vulnerability-list/filter.spec.ts @@ -0,0 +1,37 @@ +// @ts-check + +import { test } from "../../fixtures"; +import { login } from "../../helpers/Auth"; +import { VulnerabilityListPage } from "./VulnerabilityListPage"; + +test.describe("Filter validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test("Filters", async ({ page }) => { + const listPage = await VulnerabilityListPage.build(page); + + const toolbar = await listPage.getToolbar(); + const table = await listPage.getTable(); + + // Full search + await toolbar.applyTextFilter("Filter text", "CVE-2024-26308"); + await table.waitUntilDataIsLoaded(); + await table.verifyColumnContainsText("ID", "CVE-2024-26308"); + + // Severity filter + await toolbar.applyMultiSelectFilter("CVSS", ["Unknown", "Medium"]); + await table.waitUntilDataIsLoaded(); + await table.verifyColumnContainsText("ID", "CVE-2024-26308"); + + // Date filter + await toolbar.applyDateRangeFilter( + "Created on", + "02/18/2024", + "02/20/2024", + ); + await table.waitUntilDataIsLoaded(); + await table.verifyColumnContainsText("ID", "CVE-2024-26308"); + }); +}); diff --git a/e2e/tests/ui/pages/vulnerability-list/pagination.spec.ts b/e2e/tests/ui/pages/vulnerability-list/pagination.spec.ts new file mode 100644 index 000000000..802ad1fe1 --- /dev/null +++ b/e2e/tests/ui/pages/vulnerability-list/pagination.spec.ts @@ -0,0 +1,27 @@ +// @ts-check + +import { test } from "../../fixtures"; +import { login } from "../../helpers/Auth"; +import { VulnerabilityListPage } from "./VulnerabilityListPage"; + +test.describe("Pagination validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test("Navigation button validations", async ({ page }) => { + const listPage = await VulnerabilityListPage.build(page); + const pagination = await listPage.getPagination(); + + await pagination.validatePagination(); + }); + + test("Items per page validations", async ({ page }) => { + const listPage = await VulnerabilityListPage.build(page); + + const pagination = await listPage.getPagination(); + const table = await listPage.getTable(); + + await pagination.validateItemsPerPage("ID", table); + }); +}); diff --git a/e2e/tests/ui/pages/vulnerability-list/sort.spec.ts b/e2e/tests/ui/pages/vulnerability-list/sort.spec.ts new file mode 100644 index 000000000..b2d4b5d29 --- /dev/null +++ b/e2e/tests/ui/pages/vulnerability-list/sort.spec.ts @@ -0,0 +1,30 @@ +// @ts-check + +import { test } from "../../fixtures"; +import { login } from "../../helpers/Auth"; +import { expectSort } from "../Helpers"; +import { VulnerabilityListPage } from "./VulnerabilityListPage"; + +test.describe("Sort validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + // TODO enable after https://github.com/trustification/trustify/issues/1811 is fixed + test.skip("Sort", async ({ page }) => { + const listPage = await VulnerabilityListPage.build(page); + const table = await listPage.getTable(); + + const columnNameSelector = table._table.locator(`td[data-label="ID"]`); + + // ID Asc + await table.clickSortBy("ID"); + const ascList = await columnNameSelector.allInnerTexts(); + expectSort(ascList, true); + + // ID Desc + await table.clickSortBy("ID"); + const desList = await columnNameSelector.allInnerTexts(); + expectSort(desList, false); + }); +});