diff --git a/package.json b/package.json index 79d9094..0cc5b83 100644 --- a/package.json +++ b/package.json @@ -6,11 +6,13 @@ "scripts": { "openapi": "openapi-ts -f ./config/openapi-ts.config.ts", "codegen": "npx playwright codegen http://localhost:8080/", - "test": "npx bddgen && npx playwright test --project='chromium'", - "test:api": "npx bddgen && npx playwright test --project='api'", - "test:ui": "npx bddgen && npx playwright test --project='chromium'", - "test:ui:trace": "npx bddgen && npx playwright test --trace on", + "test": "npx bddgen && npx playwright test --project='api' --project='chromium' --project='playwright'", + "test:api": "npx playwright test --project='api'", + "test:ui": "npx bddgen && npx playwright test --project='chromium' --project='playwright'", + "test:ui:trace": "npx bddgen && npx playwright test --project='chromium' --project='playwright' --trace on", "test:ui:host": "npx bddgen && npx playwright test --ui-host 127.0.0.1", + "test:playwright": "npx playwright test --project='playwright'", + "test:playwright:trace": "npx playwright test --project='playwright' --trace on", "format:check": "prettier --check './**/*.{ts,js,json}'", "format:fix": "prettier --write './**/*.{ts,js,json}'" }, diff --git a/playwright.config.ts b/playwright.config.ts index 8aae734..942bb0c 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -72,6 +72,16 @@ export default defineConfig({ dependencies: ["setup-ui-data"], }, + { + name: "playwright", + testDir: "./tests/ui/pages", + testMatch: "*.spec.ts", + use: { + ...devices["Desktop Chrome"], + ...DESKTOP_CONFIG, + }, + }, + { name: "setup-ui-data", testDir: "./tests/ui/dependencies", diff --git a/tests/ui/helpers/Auth.ts b/tests/ui/helpers/Auth.ts index 1ccfe43..b6787a2 100644 --- a/tests/ui/helpers/Auth.ts +++ b/tests/ui/helpers/Auth.ts @@ -1,4 +1,4 @@ -import { Page } from "@playwright/test"; +import { expect, Page } from "@playwright/test"; export const login = async (page: Page) => { let shouldLogin = process.env.TRUSTIFY_AUTH_ENABLED; @@ -7,12 +7,12 @@ export const login = async (page: Page) => { let userName = process.env.TRUSTIFY_AUTH_USER ?? "admin"; let userPassword = process.env.TRUSTIFY_AUTH_PASSWORD ?? "admin"; - await page.goto("/"); + await page.goto("/upload"); await page.fill('input[name="username"]:visible', userName); await page.fill('input[name="password"]:visible', userPassword); await page.keyboard.press("Enter"); - await page.waitForSelector("text=Dashboard"); // Ensure login was successful + await expect(page.getByRole("heading", { name: "Upload" })).toHaveCount(1); // Ensure login was successful } }; diff --git a/tests/ui/helpers/ToolbarTable.ts b/tests/ui/helpers/ToolbarTable.ts index 49e51b3..3369691 100644 --- a/tests/ui/helpers/ToolbarTable.ts +++ b/tests/ui/helpers/ToolbarTable.ts @@ -1,6 +1,6 @@ import { expect, Page } from "@playwright/test"; export class ToolbarTable { - private _page: Page; + private readonly _page: Page; private _tableName: string; constructor(page: Page, tableName: string) { diff --git a/tests/ui/pages/DetailsPageLayout.ts b/tests/ui/pages/DetailsPageLayout.ts new file mode 100644 index 0000000..ad344ae --- /dev/null +++ b/tests/ui/pages/DetailsPageLayout.ts @@ -0,0 +1,43 @@ +import { expect, 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/tests/ui/pages/Helpers.ts b/tests/ui/pages/Helpers.ts new file mode 100644 index 0000000..bd7b4b1 --- /dev/null +++ b/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/tests/ui/pages/LabelsModal.ts b/tests/ui/pages/LabelsModal.ts new file mode 100644 index 0000000..fd8b3c6 --- /dev/null +++ b/tests/ui/pages/LabelsModal.ts @@ -0,0 +1,44 @@ +import { Locator, Page } from "playwright-core"; +import { expect } from "playwright/test"; + +export class LabelsModal { + private readonly _page: Page; + private _dialog: Locator; + + private constructor(page: Page, dialog: Locator) { + this._page = page; + 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/tests/ui/pages/Navigation.ts b/tests/ui/pages/Navigation.ts new file mode 100644 index 0000000..cedd817 --- /dev/null +++ b/tests/ui/pages/Navigation.ts @@ -0,0 +1,33 @@ +import { 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/tests/ui/pages/Pagination.ts b/tests/ui/pages/Pagination.ts new file mode 100644 index 0000000..bdcdfd4 --- /dev/null +++ b/tests/ui/pages/Pagination.ts @@ -0,0 +1,90 @@ +import { expect, Locator, Page } from "@playwright/test"; +import { 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: number) { + 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 fistPageButton = this._pagination.locator( + "button[data-action='first']" + ); + await expect(fistPageButton).toBeVisible(); + await expect(fistPageButton).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(fistPageButton).toBeVisible(); + await expect(fistPageButton).not.toBeDisabled(); + + // Moving back to the first page + await fistPageButton.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/tests/ui/pages/Table.ts b/tests/ui/pages/Table.ts new file mode 100644 index 0000000..d70e750 --- /dev/null +++ b/tests/ui/pages/Table.ts @@ -0,0 +1,92 @@ +import { expect, Locator, 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/tests/ui/pages/Toolbar.ts b/tests/ui/pages/Toolbar.ts new file mode 100644 index 0000000..865e84c --- /dev/null +++ b/tests/ui/pages/Toolbar.ts @@ -0,0 +1,109 @@ +import { expect, Locator, 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/tests/ui/pages/advisory-details/AdvisoryDetailsPage.ts b/tests/ui/pages/advisory-details/AdvisoryDetailsPage.ts new file mode 100644 index 0000000..e935e74 --- /dev/null +++ b/tests/ui/pages/advisory-details/AdvisoryDetailsPage.ts @@ -0,0 +1,34 @@ +import { expect, Page } from "@playwright/test"; +import { DetailsPageLayout } from "../DetailsPageLayout"; +import { Navigation } from "../Navigation"; +import { AdvisoryListPage } from "../advisory-list/AdvisoryListPage"; + +export class AdvisoryDetailsPage { + private readonly _page: Page; + _layout: DetailsPageLayout; + + private constructor(page: Page, layout: DetailsPageLayout) { + this._page = page; + 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/tests/ui/pages/advisory-details/info/info.spec.ts b/tests/ui/pages/advisory-details/info/info.spec.ts new file mode 100644 index 0000000..c1ad9eb --- /dev/null +++ b/tests/ui/pages/advisory-details/info/info.spec.ts @@ -0,0 +1,45 @@ +// @ts-check + +import { expect, test } from "@playwright/test"; + +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/tests/ui/pages/advisory-details/vulnerabilities/VulnerabilitiesTab.ts b/tests/ui/pages/advisory-details/vulnerabilities/VulnerabilitiesTab.ts new file mode 100644 index 0000000..fa5bb6e --- /dev/null +++ b/tests/ui/pages/advisory-details/vulnerabilities/VulnerabilitiesTab.ts @@ -0,0 +1,37 @@ +import { 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(top: boolean = true) { + return await Pagination.build( + this._page, + `vulnerability-table-pagination-${top ? "top" : "bottom"}` + ); + } +} diff --git a/tests/ui/pages/advisory-details/vulnerabilities/columns.spec.ts b/tests/ui/pages/advisory-details/vulnerabilities/columns.spec.ts new file mode 100644 index 0000000..a198f84 --- /dev/null +++ b/tests/ui/pages/advisory-details/vulnerabilities/columns.spec.ts @@ -0,0 +1,43 @@ +// @ts-check + +import { expect, test } from "@playwright/test"; + +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/tests/ui/pages/advisory-details/vulnerabilities/filter.spec.ts b/tests/ui/pages/advisory-details/vulnerabilities/filter.spec.ts new file mode 100644 index 0000000..5d17778 --- /dev/null +++ b/tests/ui/pages/advisory-details/vulnerabilities/filter.spec.ts @@ -0,0 +1,17 @@ +// @ts-check + +import { test } from "@playwright/test"; + +import { login } from "../../../helpers/Auth"; +import { VulnerabilitiesTab } from "./VulnerabilitiesTab"; + +// Tables does not have filters +test.describe.skip("Filter validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test("Filters", async ({ page }) => { + await VulnerabilitiesTab.build(page, "CVE-2024-26308"); + }); +}); diff --git a/tests/ui/pages/advisory-details/vulnerabilities/pagination.spec.ts b/tests/ui/pages/advisory-details/vulnerabilities/pagination.spec.ts new file mode 100644 index 0000000..a3a5194 --- /dev/null +++ b/tests/ui/pages/advisory-details/vulnerabilities/pagination.spec.ts @@ -0,0 +1,35 @@ +// @ts-check + +import { test } from "@playwright/test"; + +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/tests/ui/pages/advisory-details/vulnerabilities/sort.spec.ts b/tests/ui/pages/advisory-details/vulnerabilities/sort.spec.ts new file mode 100644 index 0000000..92c12f0 --- /dev/null +++ b/tests/ui/pages/advisory-details/vulnerabilities/sort.spec.ts @@ -0,0 +1,31 @@ +// @ts-check + +import { test } from "@playwright/test"; + +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/tests/ui/pages/advisory-list/AdvisoryListPage.ts b/tests/ui/pages/advisory-list/AdvisoryListPage.ts new file mode 100644 index 0000000..ffd721a --- /dev/null +++ b/tests/ui/pages/advisory-list/AdvisoryListPage.ts @@ -0,0 +1,35 @@ +import { 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/tests/ui/pages/advisory-list/columns.spec.ts b/tests/ui/pages/advisory-list/columns.spec.ts new file mode 100644 index 0000000..b72aa7a --- /dev/null +++ b/tests/ui/pages/advisory-list/columns.spec.ts @@ -0,0 +1,41 @@ +// @ts-check + +import { expect, test } from "@playwright/test"; + +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/tests/ui/pages/advisory-list/filter.spec.ts b/tests/ui/pages/advisory-list/filter.spec.ts new file mode 100644 index 0000000..566ac96 --- /dev/null +++ b/tests/ui/pages/advisory-list/filter.spec.ts @@ -0,0 +1,34 @@ +// @ts-check + +import { test } from "@playwright/test"; + +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", "08/01/2024", "08/03/2024"); + 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/tests/ui/pages/advisory-list/pagination.spec.ts b/tests/ui/pages/advisory-list/pagination.spec.ts new file mode 100644 index 0000000..18f6fb9 --- /dev/null +++ b/tests/ui/pages/advisory-list/pagination.spec.ts @@ -0,0 +1,28 @@ +// @ts-check + +import { test } from "@playwright/test"; + +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/tests/ui/pages/advisory-list/sort.spec.ts b/tests/ui/pages/advisory-list/sort.spec.ts new file mode 100644 index 0000000..300fd57 --- /dev/null +++ b/tests/ui/pages/advisory-list/sort.spec.ts @@ -0,0 +1,31 @@ +// @ts-check + +import { test } from "@playwright/test"; + +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/tests/ui/pages/package-details/PackageDetailsPage.ts b/tests/ui/pages/package-details/PackageDetailsPage.ts new file mode 100644 index 0000000..6716797 --- /dev/null +++ b/tests/ui/pages/package-details/PackageDetailsPage.ts @@ -0,0 +1,35 @@ +import { expect, Page } from "@playwright/test"; +import { DetailsPageLayout } from "../DetailsPageLayout"; +import { Navigation } from "../Navigation"; +import { VulnerabilityListPage } from "../vulnerability-list/VulnerabilityListPage"; +import { PackageListPage } from "../package-list/PackageListPage"; + +export class PackageDetailsPage { + private readonly _page: Page; + _layout: DetailsPageLayout; + + private constructor(page: Page, layout: DetailsPageLayout) { + this._page = page; + 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/tests/ui/pages/package-details/info/info.spec.ts b/tests/ui/pages/package-details/info/info.spec.ts new file mode 100644 index 0000000..408f9f6 --- /dev/null +++ b/tests/ui/pages/package-details/info/info.spec.ts @@ -0,0 +1,17 @@ +// @ts-check + +import { test } from "@playwright/test"; + +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 + }); +}); diff --git a/tests/ui/pages/package-details/sboms/SbomsTab.ts b/tests/ui/pages/package-details/sboms/SbomsTab.ts new file mode 100644 index 0000000..34aee5c --- /dev/null +++ b/tests/ui/pages/package-details/sboms/SbomsTab.ts @@ -0,0 +1,37 @@ +import { 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/tests/ui/pages/package-details/sboms/columns.spec.ts b/tests/ui/pages/package-details/sboms/columns.spec.ts new file mode 100644 index 0000000..004287c --- /dev/null +++ b/tests/ui/pages/package-details/sboms/columns.spec.ts @@ -0,0 +1,38 @@ +// @ts-check + +import { expect, test } from "@playwright/test"; + +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/tests/ui/pages/package-details/sboms/filter.spec.ts b/tests/ui/pages/package-details/sboms/filter.spec.ts new file mode 100644 index 0000000..315657b --- /dev/null +++ b/tests/ui/pages/package-details/sboms/filter.spec.ts @@ -0,0 +1,17 @@ +// @ts-check + +import { test } from "@playwright/test"; + +import { login } from "../../../helpers/Auth"; +import { SbomsTab } from "./SbomsTab"; + +// Table does not have filters +test.describe.skip("Filter validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test("Filters", async ({ page }) => { + await SbomsTab.build(page, "keycloak-core"); + }); +}); diff --git a/tests/ui/pages/package-details/sboms/pagination.spec.ts b/tests/ui/pages/package-details/sboms/pagination.spec.ts new file mode 100644 index 0000000..5db8d09 --- /dev/null +++ b/tests/ui/pages/package-details/sboms/pagination.spec.ts @@ -0,0 +1,29 @@ +// @ts-check + +import { test } from "@playwright/test"; + +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/tests/ui/pages/package-details/sboms/sort.spec.ts b/tests/ui/pages/package-details/sboms/sort.spec.ts new file mode 100644 index 0000000..13db16a --- /dev/null +++ b/tests/ui/pages/package-details/sboms/sort.spec.ts @@ -0,0 +1,28 @@ +// @ts-check + +import { test } from "@playwright/test"; + +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/tests/ui/pages/package-details/vulnerabilities/VulnerabilitiesTab.ts b/tests/ui/pages/package-details/vulnerabilities/VulnerabilitiesTab.ts new file mode 100644 index 0000000..5c69462 --- /dev/null +++ b/tests/ui/pages/package-details/vulnerabilities/VulnerabilitiesTab.ts @@ -0,0 +1,37 @@ +import { 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/tests/ui/pages/package-details/vulnerabilities/columns.spec.ts b/tests/ui/pages/package-details/vulnerabilities/columns.spec.ts new file mode 100644 index 0000000..75f349c --- /dev/null +++ b/tests/ui/pages/package-details/vulnerabilities/columns.spec.ts @@ -0,0 +1,36 @@ +// @ts-check + +import { expect, test } from "@playwright/test"; + +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/tests/ui/pages/package-details/vulnerabilities/filter.spec.ts b/tests/ui/pages/package-details/vulnerabilities/filter.spec.ts new file mode 100644 index 0000000..8874412 --- /dev/null +++ b/tests/ui/pages/package-details/vulnerabilities/filter.spec.ts @@ -0,0 +1,17 @@ +// @ts-check + +import { test } from "@playwright/test"; + +import { login } from "../../../helpers/Auth"; +import { VulnerabilitiesTab } from "./VulnerabilitiesTab"; + +// Table does not have filters +test.describe.skip("Filter validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test("Filters", async ({ page }) => { + await VulnerabilitiesTab.build(page, "keycloak-core"); + }); +}); diff --git a/tests/ui/pages/package-details/vulnerabilities/pagination.spec.ts b/tests/ui/pages/package-details/vulnerabilities/pagination.spec.ts new file mode 100644 index 0000000..a9b0ab4 --- /dev/null +++ b/tests/ui/pages/package-details/vulnerabilities/pagination.spec.ts @@ -0,0 +1,35 @@ +// @ts-check + +import { test } from "@playwright/test"; + +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/tests/ui/pages/package-details/vulnerabilities/sort.spec.ts b/tests/ui/pages/package-details/vulnerabilities/sort.spec.ts new file mode 100644 index 0000000..da23a18 --- /dev/null +++ b/tests/ui/pages/package-details/vulnerabilities/sort.spec.ts @@ -0,0 +1,31 @@ +// @ts-check + +import { test } from "@playwright/test"; + +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/tests/ui/pages/package-list/PackageListPage.ts b/tests/ui/pages/package-list/PackageListPage.ts new file mode 100644 index 0000000..c5374d7 --- /dev/null +++ b/tests/ui/pages/package-list/PackageListPage.ts @@ -0,0 +1,35 @@ +import { 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/tests/ui/pages/package-list/columns.spec.ts b/tests/ui/pages/package-list/columns.spec.ts new file mode 100644 index 0000000..04008a4 --- /dev/null +++ b/tests/ui/pages/package-list/columns.spec.ts @@ -0,0 +1,59 @@ +// @ts-check + +import { expect, test } from "@playwright/test"; + +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/tests/ui/pages/package-list/filter.spec.ts b/tests/ui/pages/package-list/filter.spec.ts new file mode 100644 index 0000000..e8a711f --- /dev/null +++ b/tests/ui/pages/package-list/filter.spec.ts @@ -0,0 +1,34 @@ +// @ts-check + +import { test } from "@playwright/test"; + +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/tests/ui/pages/package-list/pagination.spec.ts b/tests/ui/pages/package-list/pagination.spec.ts new file mode 100644 index 0000000..979b5e9 --- /dev/null +++ b/tests/ui/pages/package-list/pagination.spec.ts @@ -0,0 +1,28 @@ +// @ts-check + +import { test } from "@playwright/test"; + +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/tests/ui/pages/package-list/sort.spec.ts b/tests/ui/pages/package-list/sort.spec.ts new file mode 100644 index 0000000..49bf7f7 --- /dev/null +++ b/tests/ui/pages/package-list/sort.spec.ts @@ -0,0 +1,29 @@ +// @ts-check + +import { test } from "@playwright/test"; + +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/tests/ui/pages/sbom-details/SbomDetailsPage.ts b/tests/ui/pages/sbom-details/SbomDetailsPage.ts new file mode 100644 index 0000000..f17bff9 --- /dev/null +++ b/tests/ui/pages/sbom-details/SbomDetailsPage.ts @@ -0,0 +1,34 @@ +import { expect, Page } from "@playwright/test"; +import { DetailsPageLayout } from "../DetailsPageLayout"; +import { Navigation } from "../Navigation"; +import { SbomListPage } from "../sbom-list/SbomListPage"; + +export class SbomDetailsPage { + private readonly _page: Page; + _layout: DetailsPageLayout; + + private constructor(page: Page, layout: DetailsPageLayout) { + this._page = page; + 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/tests/ui/pages/sbom-details/actions.spec.ts b/tests/ui/pages/sbom-details/actions.spec.ts new file mode 100644 index 0000000..6ce85cd --- /dev/null +++ b/tests/ui/pages/sbom-details/actions.spec.ts @@ -0,0 +1,32 @@ +// @ts-check + +import { expect, test } from "@playwright/test"; + +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/tests/ui/pages/sbom-details/info/info.spec.ts b/tests/ui/pages/sbom-details/info/info.spec.ts new file mode 100644 index 0000000..0406670 --- /dev/null +++ b/tests/ui/pages/sbom-details/info/info.spec.ts @@ -0,0 +1,44 @@ +// @ts-check + +import { expect, test } from "@playwright/test"; + +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/tests/ui/pages/sbom-details/packages/PackagesTab.ts b/tests/ui/pages/sbom-details/packages/PackagesTab.ts new file mode 100644 index 0000000..41ec7fb --- /dev/null +++ b/tests/ui/pages/sbom-details/packages/PackagesTab.ts @@ -0,0 +1,37 @@ +import { 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/tests/ui/pages/sbom-details/packages/columns.spec.ts b/tests/ui/pages/sbom-details/packages/columns.spec.ts new file mode 100644 index 0000000..6c6f4dc --- /dev/null +++ b/tests/ui/pages/sbom-details/packages/columns.spec.ts @@ -0,0 +1,65 @@ +// @ts-check + +import { expect, test } from "@playwright/test"; + +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) + ).toContainText("Apache-2.0"); + await expect( + table._table.locator(`td[data-label="Licenses"]`).nth(1) + ).toContainText("NOASSERTION"); + + // 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/tests/ui/pages/sbom-details/packages/filter.spec.ts b/tests/ui/pages/sbom-details/packages/filter.spec.ts new file mode 100644 index 0000000..643d99e --- /dev/null +++ b/tests/ui/pages/sbom-details/packages/filter.spec.ts @@ -0,0 +1,32 @@ +// @ts-check + +import { test } from "@playwright/test"; + +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/tests/ui/pages/sbom-details/packages/pagination.spec.ts b/tests/ui/pages/sbom-details/packages/pagination.spec.ts new file mode 100644 index 0000000..54de917 --- /dev/null +++ b/tests/ui/pages/sbom-details/packages/pagination.spec.ts @@ -0,0 +1,28 @@ +// @ts-check + +import { test } from "@playwright/test"; + +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/tests/ui/pages/sbom-details/packages/sort.spec.ts b/tests/ui/pages/sbom-details/packages/sort.spec.ts new file mode 100644 index 0000000..1ef167f --- /dev/null +++ b/tests/ui/pages/sbom-details/packages/sort.spec.ts @@ -0,0 +1,28 @@ +// @ts-check + +import { test } from "@playwright/test"; + +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/tests/ui/pages/sbom-details/vulnerabilities/VulnerabilitiesTab.ts b/tests/ui/pages/sbom-details/vulnerabilities/VulnerabilitiesTab.ts new file mode 100644 index 0000000..197223b --- /dev/null +++ b/tests/ui/pages/sbom-details/vulnerabilities/VulnerabilitiesTab.ts @@ -0,0 +1,37 @@ +import { 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/tests/ui/pages/sbom-details/vulnerabilities/columns.spec.ts b/tests/ui/pages/sbom-details/vulnerabilities/columns.spec.ts new file mode 100644 index 0000000..328e3a8 --- /dev/null +++ b/tests/ui/pages/sbom-details/vulnerabilities/columns.spec.ts @@ -0,0 +1,70 @@ +// @ts-check + +import { expect, test } from "@playwright/test"; + +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/tests/ui/pages/sbom-details/vulnerabilities/donutchart.spec.ts b/tests/ui/pages/sbom-details/vulnerabilities/donutchart.spec.ts new file mode 100644 index 0000000..e4a2c0f --- /dev/null +++ b/tests/ui/pages/sbom-details/vulnerabilities/donutchart.spec.ts @@ -0,0 +1,23 @@ +// @ts-check + +import { expect, test } from "@playwright/test"; + +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: 9"); + await expect(page.locator("#legend-labels-3")).toContainText("Low: 0"); + await expect(page.locator("#legend-labels-4")).toContainText("None: 1"); + await expect(page.locator("#legend-labels-5")).toContainText("Unknown: 0"); + }); +}); diff --git a/tests/ui/pages/sbom-details/vulnerabilities/filter.spec.ts b/tests/ui/pages/sbom-details/vulnerabilities/filter.spec.ts new file mode 100644 index 0000000..25ae770 --- /dev/null +++ b/tests/ui/pages/sbom-details/vulnerabilities/filter.spec.ts @@ -0,0 +1,33 @@ +// @ts-check + +import { test } from "@playwright/test"; + +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/tests/ui/pages/sbom-details/vulnerabilities/pagination.spec.ts b/tests/ui/pages/sbom-details/vulnerabilities/pagination.spec.ts new file mode 100644 index 0000000..aa636f5 --- /dev/null +++ b/tests/ui/pages/sbom-details/vulnerabilities/pagination.spec.ts @@ -0,0 +1,31 @@ +// @ts-check + +import { test } from "@playwright/test"; + +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/tests/ui/pages/sbom-details/vulnerabilities/sort.spec.ts b/tests/ui/pages/sbom-details/vulnerabilities/sort.spec.ts new file mode 100644 index 0000000..304d621 --- /dev/null +++ b/tests/ui/pages/sbom-details/vulnerabilities/sort.spec.ts @@ -0,0 +1,31 @@ +// @ts-check + +import { test } from "@playwright/test"; + +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/tests/ui/pages/sbom-list/SbomListPage.ts b/tests/ui/pages/sbom-list/SbomListPage.ts new file mode 100644 index 0000000..c85a548 --- /dev/null +++ b/tests/ui/pages/sbom-list/SbomListPage.ts @@ -0,0 +1,35 @@ +import { 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/tests/ui/pages/sbom-list/actions.spec.ts b/tests/ui/pages/sbom-list/actions.spec.ts new file mode 100644 index 0000000..845c6a8 --- /dev/null +++ b/tests/ui/pages/sbom-list/actions.spec.ts @@ -0,0 +1,85 @@ +// @ts-check + +import { expect, test } from "@playwright/test"; + +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/tests/ui/pages/sbom-list/columns.spec.ts b/tests/ui/pages/sbom-list/columns.spec.ts new file mode 100644 index 0000000..3b30946 --- /dev/null +++ b/tests/ui/pages/sbom-list/columns.spec.ts @@ -0,0 +1,57 @@ +// @ts-check + +import { expect, test } from "@playwright/test"; + +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: 9, + }, + { + severity: "none", + count: 1, + }, + ]; + + 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/tests/ui/pages/sbom-list/filter.spec.ts b/tests/ui/pages/sbom-list/filter.spec.ts new file mode 100644 index 0000000..cd11a9a --- /dev/null +++ b/tests/ui/pages/sbom-list/filter.spec.ts @@ -0,0 +1,38 @@ +// @ts-check + +import { test } from "@playwright/test"; + +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/tests/ui/pages/sbom-list/pagination.spec.ts b/tests/ui/pages/sbom-list/pagination.spec.ts new file mode 100644 index 0000000..7a3b270 --- /dev/null +++ b/tests/ui/pages/sbom-list/pagination.spec.ts @@ -0,0 +1,28 @@ +// @ts-check + +import { test } from "@playwright/test"; + +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/tests/ui/pages/sbom-list/sort.spec.ts b/tests/ui/pages/sbom-list/sort.spec.ts new file mode 100644 index 0000000..540eb93 --- /dev/null +++ b/tests/ui/pages/sbom-list/sort.spec.ts @@ -0,0 +1,28 @@ +// @ts-check + +import { test } from "@playwright/test"; + +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/tests/ui/pages/vulnerability-details/VulnerabilityDetailsPage.ts b/tests/ui/pages/vulnerability-details/VulnerabilityDetailsPage.ts new file mode 100644 index 0000000..dd707e2 --- /dev/null +++ b/tests/ui/pages/vulnerability-details/VulnerabilityDetailsPage.ts @@ -0,0 +1,36 @@ +import { expect, Page } from "@playwright/test"; +import { DetailsPageLayout } from "../DetailsPageLayout"; +import { Navigation } from "../Navigation"; +import { VulnerabilityListPage } from "../vulnerability-list/VulnerabilityListPage"; + +export class VulnerabilityDetailsPage { + private readonly _page: Page; + _layout: DetailsPageLayout; + + private constructor(page: Page, layout: DetailsPageLayout) { + this._page = page; + 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/tests/ui/pages/vulnerability-details/advisories/AdvisoriesTab.ts b/tests/ui/pages/vulnerability-details/advisories/AdvisoriesTab.ts new file mode 100644 index 0000000..9d60e31 --- /dev/null +++ b/tests/ui/pages/vulnerability-details/advisories/AdvisoriesTab.ts @@ -0,0 +1,40 @@ +import { 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/tests/ui/pages/vulnerability-details/advisories/columns.spec.ts b/tests/ui/pages/vulnerability-details/advisories/columns.spec.ts new file mode 100644 index 0000000..3033218 --- /dev/null +++ b/tests/ui/pages/vulnerability-details/advisories/columns.spec.ts @@ -0,0 +1,48 @@ +// @ts-check + +import { expect, test } from "@playwright/test"; + +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 ids = await table._table + .locator(`td[data-label="ID"]`) + .allInnerTexts(); + const idIndex = ids.indexOf("CVE-2023-2976"); + expect(idIndex).not.toBe(-1); + + // Name + await expect( + table._table.locator(`td[data-label="ID"]`).nth(idIndex) + ).toContainText("CVE-2023-2976"); + + // Title + await expect( + table._table.locator(`td[data-label="Title"]`).nth(idIndex) + ).toContainText("guava: insecure temporary directory creation"); + + // Type + await expect( + table._table.locator(`td[data-label="Type"]`).nth(idIndex) + ).toContainText("csaf"); + + // Revision + await expect( + table._table.locator(`td[data-label="Revision"]`).nth(idIndex) + ).toContainText("Nov 14, 2023"); + + // Vulnerabilities + await expect( + table._table.locator(`td[data-label="Vulnerabilities"]`).nth(idIndex) + ).toContainText("1"); + }); +}); diff --git a/tests/ui/pages/vulnerability-details/advisories/filter.spec.ts b/tests/ui/pages/vulnerability-details/advisories/filter.spec.ts new file mode 100644 index 0000000..219afeb --- /dev/null +++ b/tests/ui/pages/vulnerability-details/advisories/filter.spec.ts @@ -0,0 +1,17 @@ +// @ts-check + +import { test } from "@playwright/test"; + +import { login } from "../../../helpers/Auth"; +import { AdvisoriesTab } from "./AdvisoriesTab"; + +// Table does not have filters +test.describe.skip("Filter validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test("Filters", async ({ page }) => { + await AdvisoriesTab.build(page, "CVE-2023-2976"); + }); +}); diff --git a/tests/ui/pages/vulnerability-details/advisories/pagination.spec.ts b/tests/ui/pages/vulnerability-details/advisories/pagination.spec.ts new file mode 100644 index 0000000..6f44686 --- /dev/null +++ b/tests/ui/pages/vulnerability-details/advisories/pagination.spec.ts @@ -0,0 +1,29 @@ +// @ts-check + +import { test } from "@playwright/test"; + +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/tests/ui/pages/vulnerability-details/advisories/sort.spec.ts b/tests/ui/pages/vulnerability-details/advisories/sort.spec.ts new file mode 100644 index 0000000..dd6739c --- /dev/null +++ b/tests/ui/pages/vulnerability-details/advisories/sort.spec.ts @@ -0,0 +1,28 @@ +// @ts-check + +import { test } from "@playwright/test"; + +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/tests/ui/pages/vulnerability-details/info/info.spec.ts b/tests/ui/pages/vulnerability-details/info/info.spec.ts new file mode 100644 index 0000000..2c12e8b --- /dev/null +++ b/tests/ui/pages/vulnerability-details/info/info.spec.ts @@ -0,0 +1,17 @@ +// @ts-check + +import { test } from "@playwright/test"; + +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/tests/ui/pages/vulnerability-details/sboms/SbomsTab.ts b/tests/ui/pages/vulnerability-details/sboms/SbomsTab.ts new file mode 100644 index 0000000..c118fc9 --- /dev/null +++ b/tests/ui/pages/vulnerability-details/sboms/SbomsTab.ts @@ -0,0 +1,40 @@ +import { 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/tests/ui/pages/vulnerability-details/sboms/columns.spec.ts b/tests/ui/pages/vulnerability-details/sboms/columns.spec.ts new file mode 100644 index 0000000..a0a8100 --- /dev/null +++ b/tests/ui/pages/vulnerability-details/sboms/columns.spec.ts @@ -0,0 +1,68 @@ +// @ts-check + +import { expect, test } from "@playwright/test"; + +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/tests/ui/pages/vulnerability-details/sboms/filter.spec.ts b/tests/ui/pages/vulnerability-details/sboms/filter.spec.ts new file mode 100644 index 0000000..8237876 --- /dev/null +++ b/tests/ui/pages/vulnerability-details/sboms/filter.spec.ts @@ -0,0 +1,17 @@ +// @ts-check + +import { test } from "@playwright/test"; + +import { login } from "../../../helpers/Auth"; +import { SbomsTab } from "./SbomsTab"; + +// Table does not have filters +test.describe.skip("Filter validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test("Filters", async ({ page }) => { + await SbomsTab.build(page, "CVE-2023-2976"); + }); +}); diff --git a/tests/ui/pages/vulnerability-details/sboms/pagination.spec.ts b/tests/ui/pages/vulnerability-details/sboms/pagination.spec.ts new file mode 100644 index 0000000..0abdb61 --- /dev/null +++ b/tests/ui/pages/vulnerability-details/sboms/pagination.spec.ts @@ -0,0 +1,29 @@ +// @ts-check + +import { test } from "@playwright/test"; + +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/tests/ui/pages/vulnerability-details/sboms/sort.spec.ts b/tests/ui/pages/vulnerability-details/sboms/sort.spec.ts new file mode 100644 index 0000000..6afbda7 --- /dev/null +++ b/tests/ui/pages/vulnerability-details/sboms/sort.spec.ts @@ -0,0 +1,28 @@ +// @ts-check + +import { test } from "@playwright/test"; + +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/tests/ui/pages/vulnerability-list/VulnerabilityListPage.ts b/tests/ui/pages/vulnerability-list/VulnerabilityListPage.ts new file mode 100644 index 0000000..453626b --- /dev/null +++ b/tests/ui/pages/vulnerability-list/VulnerabilityListPage.ts @@ -0,0 +1,35 @@ +import { 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/tests/ui/pages/vulnerability-list/columns.spec.ts b/tests/ui/pages/vulnerability-list/columns.spec.ts new file mode 100644 index 0000000..9cc9421 --- /dev/null +++ b/tests/ui/pages/vulnerability-list/columns.spec.ts @@ -0,0 +1,29 @@ +// @ts-check + +import { expect, test } from "@playwright/test"; + +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/tests/ui/pages/vulnerability-list/filter.spec.ts b/tests/ui/pages/vulnerability-list/filter.spec.ts new file mode 100644 index 0000000..104322a --- /dev/null +++ b/tests/ui/pages/vulnerability-list/filter.spec.ts @@ -0,0 +1,38 @@ +// @ts-check + +import { test } from "@playwright/test"; + +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", "None"]); + 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/tests/ui/pages/vulnerability-list/pagination.spec.ts b/tests/ui/pages/vulnerability-list/pagination.spec.ts new file mode 100644 index 0000000..1cfb2fc --- /dev/null +++ b/tests/ui/pages/vulnerability-list/pagination.spec.ts @@ -0,0 +1,28 @@ +// @ts-check + +import { test } from "@playwright/test"; + +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/tests/ui/pages/vulnerability-list/sort.spec.ts b/tests/ui/pages/vulnerability-list/sort.spec.ts new file mode 100644 index 0000000..75d2a2c --- /dev/null +++ b/tests/ui/pages/vulnerability-list/sort.spec.ts @@ -0,0 +1,31 @@ +// @ts-check + +import { test } from "@playwright/test"; + +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); + }); +});