From 8420a7fefb8b561e6c8b2f0c6f77f84059b14923 Mon Sep 17 00:00:00 2001 From: Carlos Feria <2582866+carlosthe19916@users.noreply.github.com> Date: Mon, 23 Jun 2025 09:45:56 +0200 Subject: [PATCH 01/23] save changes Signed-off-by: Carlos Feria <2582866+carlosthe19916@users.noreply.github.com> --- package.json | 6 ++--- playwright.config.ts | 27 ++++++++++++------- .../@sbom-explorer/sbom-explorer.feature | 4 +-- tests/ui/pages/example.spec.ts | 22 +++++++++++++++ tests/ui/steps/auth.ts | 2 +- 5 files changed, 46 insertions(+), 15 deletions(-) create mode 100644 tests/ui/pages/example.spec.ts diff --git a/package.json b/package.json index 79d9094..3cf440c 100644 --- a/package.json +++ b/package.json @@ -6,10 +6,10 @@ "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": "npx bddgen && npx playwright test --project='api' --project='chromium'", + "test:api": "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:ui:trace": "npx bddgen && npx playwright test --project='chromium' --trace on", "test:ui:host": "npx bddgen && npx playwright test --ui-host 127.0.0.1", "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..da70833 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,10 +1,16 @@ import { defineConfig, devices } from "@playwright/test"; -import { defineBddConfig } from "playwright-bdd"; +import { defineBddProject } from "playwright-bdd"; -const testDir = defineBddConfig({ - features: ["tests/**/features/@*/*.feature"], - steps: ["tests/**/features/**/*.step.ts", "tests/**/steps/**/*.ts"], -}); +const createBddProjectConfig = (name: string) => { + return defineBddProject({ + name, + + featuresRoot: "tests/ui/features", + features: ["tests/**/features/@*/*.feature"], + steps: ["tests/**/features/**/*.step.ts", "tests/**/steps/**/*.ts"], + outputDir: "tests/.features-gen", + }); +}; /** * Read environment variables from file. @@ -21,7 +27,9 @@ const DESKTOP_CONFIG = { * See https://playwright.dev/docs/test-configuration. */ export default defineConfig({ - testDir, + // testDir, + testDir: "./tests", + testIgnore: ["*.setup.ts", "*.teardown.ts"], /* Run tests in files in parallel */ fullyParallel: true, /* Fail the build on CI if you accidentally left test.only in the source code. */ @@ -46,16 +54,17 @@ export default defineConfig({ /* Configure projects for major browsers */ projects: [ { - name: "chromium", + ...createBddProjectConfig("chromium"), use: { ...devices["Desktop Chrome"], ...DESKTOP_CONFIG, }, dependencies: ["setup-ui-data"], + testDir: "./tests/", }, { - name: "firefox", + ...createBddProjectConfig("firefox"), use: { ...devices["Desktop Firefox"], ...DESKTOP_CONFIG, @@ -64,7 +73,7 @@ export default defineConfig({ }, { - name: "webkit", + ...createBddProjectConfig("webkit"), use: { ...devices["Desktop Safari"], ...DESKTOP_CONFIG, diff --git a/tests/ui/features/@sbom-explorer/sbom-explorer.feature b/tests/ui/features/@sbom-explorer/sbom-explorer.feature index ba93907..f6586b4 100644 --- a/tests/ui/features/@sbom-explorer/sbom-explorer.feature +++ b/tests/ui/features/@sbom-explorer/sbom-explorer.feature @@ -1,6 +1,6 @@ Feature: SBOM Explorer - View SBOM details - Background: Authentication - Given User is authenticated + # Background: Authentication + # Given User is authenticated Scenario Outline: View SBOM Overview Given An ingested "" SBOM "" is available diff --git a/tests/ui/pages/example.spec.ts b/tests/ui/pages/example.spec.ts new file mode 100644 index 0000000..7399a02 --- /dev/null +++ b/tests/ui/pages/example.spec.ts @@ -0,0 +1,22 @@ +// @ts-check + +import { expect, test } from "@playwright/test"; + +test("has title", async ({ page }) => { + await page.goto("https://playwright.dev/"); + + // Expect a title "to contain" a substring. + await expect(page).toHaveTitle(/Playwright/); +}); + +test("get started link", async ({ page }) => { + await page.goto("https://playwright.dev/"); + + // Click the get started link. + await page.getByRole("link", { name: "Get started" }).click(); + + // Expects page to have a heading with the name of Installation. + await expect( + page.getByRole("heading", { name: "Installation" }) + ).toBeVisible(); +}); diff --git a/tests/ui/steps/auth.ts b/tests/ui/steps/auth.ts index 1ad2159..8ff0977 100644 --- a/tests/ui/steps/auth.ts +++ b/tests/ui/steps/auth.ts @@ -4,5 +4,5 @@ import { login } from "../helpers/Auth"; export const { Given, When, Then } = createBdd(); Given("User is authenticated", async ({ page }) => { - await login(page); + // await login(page); }); From 40d2aa97d377cd761ea1b3b9b403489a175722f0 Mon Sep 17 00:00:00 2001 From: Carlos Feria <2582866+carlosthe19916@users.noreply.github.com> Date: Mon, 23 Jun 2025 14:00:26 +0200 Subject: [PATCH 02/23] Save working example --- playwright.config.ts | 38 ++++++++++--------- .../@sbom-explorer/sbom-explorer.feature | 4 +- tests/ui/pages/example.spec.ts | 1 + tests/ui/steps/auth.ts | 2 +- 4 files changed, 24 insertions(+), 21 deletions(-) diff --git a/playwright.config.ts b/playwright.config.ts index da70833..a2af7e1 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,16 +1,10 @@ import { defineConfig, devices } from "@playwright/test"; -import { defineBddProject } from "playwright-bdd"; +import { defineBddConfig } from "playwright-bdd"; -const createBddProjectConfig = (name: string) => { - return defineBddProject({ - name, - - featuresRoot: "tests/ui/features", - features: ["tests/**/features/@*/*.feature"], - steps: ["tests/**/features/**/*.step.ts", "tests/**/steps/**/*.ts"], - outputDir: "tests/.features-gen", - }); -}; +const testDir = defineBddConfig({ + features: ["tests/**/features/@*/*.feature"], + steps: ["tests/**/features/**/*.step.ts", "tests/**/steps/**/*.ts"], +}); /** * Read environment variables from file. @@ -27,9 +21,7 @@ const DESKTOP_CONFIG = { * See https://playwright.dev/docs/test-configuration. */ export default defineConfig({ - // testDir, - testDir: "./tests", - testIgnore: ["*.setup.ts", "*.teardown.ts"], + testDir, /* Run tests in files in parallel */ fullyParallel: true, /* Fail the build on CI if you accidentally left test.only in the source code. */ @@ -54,17 +46,16 @@ export default defineConfig({ /* Configure projects for major browsers */ projects: [ { - ...createBddProjectConfig("chromium"), + name: "chromium", use: { ...devices["Desktop Chrome"], ...DESKTOP_CONFIG, }, dependencies: ["setup-ui-data"], - testDir: "./tests/", }, { - ...createBddProjectConfig("firefox"), + name: "firefox", use: { ...devices["Desktop Firefox"], ...DESKTOP_CONFIG, @@ -73,7 +64,7 @@ export default defineConfig({ }, { - ...createBddProjectConfig("webkit"), + name: "webkit", use: { ...devices["Desktop Safari"], ...DESKTOP_CONFIG, @@ -81,6 +72,17 @@ export default defineConfig({ dependencies: ["setup-ui-data"], }, + { + name: "vanilla", + testDir: "./tests/ui/pages", + testMatch: "*.spec.ts", + dependencies: ["setup-ui-data"], + use: { + ...devices["Desktop Chrome"], + ...DESKTOP_CONFIG, + }, + }, + { name: "setup-ui-data", testDir: "./tests/ui/dependencies", diff --git a/tests/ui/features/@sbom-explorer/sbom-explorer.feature b/tests/ui/features/@sbom-explorer/sbom-explorer.feature index f6586b4..ba93907 100644 --- a/tests/ui/features/@sbom-explorer/sbom-explorer.feature +++ b/tests/ui/features/@sbom-explorer/sbom-explorer.feature @@ -1,6 +1,6 @@ Feature: SBOM Explorer - View SBOM details - # Background: Authentication - # Given User is authenticated + Background: Authentication + Given User is authenticated Scenario Outline: View SBOM Overview Given An ingested "" SBOM "" is available diff --git a/tests/ui/pages/example.spec.ts b/tests/ui/pages/example.spec.ts index 7399a02..95a3917 100644 --- a/tests/ui/pages/example.spec.ts +++ b/tests/ui/pages/example.spec.ts @@ -7,6 +7,7 @@ test("has title", async ({ page }) => { // Expect a title "to contain" a substring. await expect(page).toHaveTitle(/Playwright/); + await expect(page).toHaveTitle(/Playwright/); }); test("get started link", async ({ page }) => { diff --git a/tests/ui/steps/auth.ts b/tests/ui/steps/auth.ts index 8ff0977..1ad2159 100644 --- a/tests/ui/steps/auth.ts +++ b/tests/ui/steps/auth.ts @@ -4,5 +4,5 @@ import { login } from "../helpers/Auth"; export const { Given, When, Then } = createBdd(); Given("User is authenticated", async ({ page }) => { - // await login(page); + await login(page); }); From f7badbe04475081a56f51f431079c52a3a5925ea Mon Sep 17 00:00:00 2001 From: Carlos Feria <2582866+carlosthe19916@users.noreply.github.com> Date: Wed, 25 Jun 2025 11:02:40 +0200 Subject: [PATCH 03/23] Save composition basic behavior --- .../@sbom-explorer/sbom-explorer.step.ts | 98 +++++--- .../vulnerability-explorer.step.ts | 75 ++++-- tests/ui/helpers/Constants.ts | 31 +++ tests/ui/helpers/DetailsPage.ts | 117 --------- tests/ui/helpers/Navigation.ts | 28 +++ tests/ui/helpers/Pagination.ts | 158 ++++++++++++ tests/ui/helpers/SbomDetailsPage.ts | 111 +++++++++ tests/ui/helpers/SearchPage.ts | 27 -- tests/ui/helpers/Table.ts | 55 +++++ tests/ui/helpers/Toolbar.ts | 92 +++++++ tests/ui/helpers/ToolbarTable.ts | 232 ------------------ tests/ui/pages/example.spec.ts | 23 -- tests/ui/pages/sbom-list/filter.spec.ts | 39 +++ 13 files changed, 631 insertions(+), 455 deletions(-) create mode 100644 tests/ui/helpers/Constants.ts create mode 100644 tests/ui/helpers/Navigation.ts create mode 100644 tests/ui/helpers/Pagination.ts create mode 100644 tests/ui/helpers/SbomDetailsPage.ts delete mode 100644 tests/ui/helpers/SearchPage.ts create mode 100644 tests/ui/helpers/Table.ts create mode 100644 tests/ui/helpers/Toolbar.ts delete mode 100644 tests/ui/helpers/ToolbarTable.ts delete mode 100644 tests/ui/pages/example.spec.ts create mode 100644 tests/ui/pages/sbom-list/filter.spec.ts diff --git a/tests/ui/features/@sbom-explorer/sbom-explorer.step.ts b/tests/ui/features/@sbom-explorer/sbom-explorer.step.ts index 190f061..7212e5f 100644 --- a/tests/ui/features/@sbom-explorer/sbom-explorer.step.ts +++ b/tests/ui/features/@sbom-explorer/sbom-explorer.step.ts @@ -1,8 +1,12 @@ import { createBdd } from "playwright-bdd"; import { expect } from "playwright/test"; import { DetailsPage } from "../../helpers/DetailsPage"; -import { ToolbarTable } from "../../helpers/ToolbarTable"; -import { SearchPage } from "../../helpers/SearchPage"; +import { Toolbar } from "../../helpers/Toolbar"; +import { Table } from "../../helpers/Table"; +import { Pagination } from "../../helpers/Pagination"; +import { Navigation } from "../../helpers/Navigation"; +import { SbomDetailsPage } from "../../helpers/SbomDetailsPage"; +import { SBOMListPage } from "../../helpers/Constants"; export const { Given, When, Then } = createBdd(); @@ -12,8 +16,14 @@ const VULN_TABLE_NAME = "Vulnerability table"; Given( "An ingested {string} SBOM {string} is available", async ({ page }, _sbomType, sbomName) => { - const searchPage = new SearchPage(page); - await searchPage.dedicatedSearch("SBOMs", sbomName); + const navigation = await Navigation.build(page); + await navigation.goToSidebar("SBOMs"); + + const toolbar = await Toolbar.build(page, SBOMListPage.toolbarAriaLabel); + await toolbar.applyTextFilter(SBOMListPage.filters.filterText, sbomName); + + const table = await Table.build(page, SBOMListPage.tableAriaLabel); + await table.verifyTableHasData(); } ); @@ -46,48 +56,66 @@ Then( Then( "The Package table is sorted by {string}", async ({ page }, columnName) => { - const toolbarTable = new ToolbarTable(page, PACKAGE_TABLE_NAME); - await toolbarTable.verifyTableIsSortedBy(columnName); + const table = await Table.build(page, PACKAGE_TABLE_NAME); + await table.verifyTableIsSortedBy(columnName); } ); Then("Search by FilterText {string}", async ({ page }, filterText) => { - const toolbarTable = new ToolbarTable(page, PACKAGE_TABLE_NAME); - await toolbarTable.filterByText(filterText); + const toolbar = await Toolbar.build(page, "Package toolbar"); + await toolbar.applyTextFilter("Filter text", filterText); }); Then( "The Package table total results is {int}", async ({ page }, totalResults) => { - const toolbarTable = new ToolbarTable(page, PACKAGE_TABLE_NAME); - await toolbarTable.verifyPaginationHasTotalResults(totalResults); + const table = await Table.build(page, PACKAGE_TABLE_NAME); + if (totalResults > 0) { + await table.verifyTableHasData(); + } else { + await table.verifyTableHasNoData(); + } + + const pagination = await Pagination.build( + page, + "package-table-pagination-top" + ); + const total = await pagination.getTotalNumberOfItems(); + expect(total).toBe(totalResults); } ); Then( - "The Package table total results is greather than {int}", + "The Package table total results is greater than {int}", async ({ page }, totalResults) => { - const toolbarTable = new ToolbarTable(page, PACKAGE_TABLE_NAME); - await toolbarTable.verifyPaginationHasTotalResultsGreatherThan( - totalResults + const pagination = await Pagination.build( + page, + "package-table-pagination-top" ); + const total = await pagination.getTotalNumberOfItems(); + expect(total).toBeGreaterThan(totalResults); } ); Then( "The {string} column of the Package table table contains {string}", async ({ page }, columnName, expectedValue) => { - const toolbarTable = new ToolbarTable(page, PACKAGE_TABLE_NAME); - await toolbarTable.verifyColumnContainsText(columnName, expectedValue); + const table = await Table.build(page, PACKAGE_TABLE_NAME); + await table.verifyColumnContainsText(columnName, expectedValue); } ); Given( "An ingested {string} SBOM {string} containing Vulnerabilities", async ({ page }, _sbomType, sbomName) => { - const searchPage = new SearchPage(page); - await searchPage.dedicatedSearch("SBOMs", sbomName); - const element = await page.locator( + const navigation = await Navigation.build(page); + await navigation.goToSidebar("SBOMs"); + + const toolbar = await Toolbar.build(page, SBOMListPage.toolbarAriaLabel); + await toolbar.applyTextFilter(SBOMListPage.filters.filterText, sbomName); + + const table = await Table.build(page, SBOMListPage.tableAriaLabel); + const element = table._table.locator( `xpath=(//tr[contains(.,'${sbomName}')]/td[@data-label='Vulnerabilities']/div)[1]` ); await expect(element, "SBOM have no vulnerabilities").toHaveText( @@ -115,24 +143,24 @@ Then( Then( "Vulnerability Risk Profile shows summary of vulnerabilities", async ({ page }) => { - const detailsPage = new DetailsPage(page); - await detailsPage.verifyVulnerabilityPanelcount(); + const sbomDetailsPage = new SbomDetailsPage(page); + await sbomDetailsPage.verifyVulnerabilityPanelcount(); } ); Then( "SBOM Name {string} should be visible inside the tab", async ({ page }, sbomName) => { - const panelSbomName = await page.locator( + const panelSbomName = page.locator( `xpath=//section[@id='refVulnerabilitiesSection']//dt[contains(.,'Name')]/following-sibling::dd` ); await panelSbomName.isVisible(); - await expect(await panelSbomName.textContent()).toEqual(sbomName); + expect(await panelSbomName.textContent()).toEqual(sbomName); } ); Then("SBOM Version should be visible inside the tab", async ({ page }) => { - const panelSBOMVersion = await page.locator( + const panelSBOMVersion = page.locator( `xpath=//section[@id='refVulnerabilitiesSection']//dt[contains(.,'Version')]/following-sibling::dd` ); await panelSBOMVersion.isVisible(); @@ -141,7 +169,7 @@ Then("SBOM Version should be visible inside the tab", async ({ page }) => { Then( "SBOM Creation date should be visible inside the tab", async ({ page }) => { - const panelSBOMVersion = await page.locator( + const panelSBOMVersion = page.locator( `xpath=//section[@id='refVulnerabilitiesSection']//dt[contains(.,'Creation date')]/following-sibling::dd` ); await panelSBOMVersion.isVisible(); @@ -151,19 +179,23 @@ Then( Then( "List of related Vulnerabilities should be sorted by {string} in descending order", async ({ page }, columnName) => { - const toolbarTable = new ToolbarTable(page, VULN_TABLE_NAME); - await toolbarTable.verifyTableIsSortedBy(columnName, false); + const table = await Table.build(page, VULN_TABLE_NAME); + await table.verifyTableIsSortedBy(columnName, false); } ); Then("Pagination of Vulnerabilities list works", async ({ page }) => { - const toolbarTable = new ToolbarTable(page, VULN_TABLE_NAME); - const vulnTableTopPagination = `xpath=//div[@id="vulnerability-table-pagination-top"]`; - await toolbarTable.verifyPagination(vulnTableTopPagination); + const pagination = await Pagination.build( + page, + "vulnerability-table-pagination-top" + ); + await pagination.verifyPagination(); }); Then("Pagination of Packages list works", async ({ page }) => { - const toolbarTable = new ToolbarTable(page, PACKAGE_TABLE_NAME); - const vulnTableTopPagination = `xpath=//div[@id="package-table-pagination-top"]`; - await toolbarTable.verifyPagination(vulnTableTopPagination); + const pagination = await Pagination.build( + page, + "package-table-pagination-top" + ); + await pagination.verifyPagination(); }); diff --git a/tests/ui/features/@vulnerability-explorer/vulnerability-explorer.step.ts b/tests/ui/features/@vulnerability-explorer/vulnerability-explorer.step.ts index 4334a26..457fde9 100644 --- a/tests/ui/features/@vulnerability-explorer/vulnerability-explorer.step.ts +++ b/tests/ui/features/@vulnerability-explorer/vulnerability-explorer.step.ts @@ -1,7 +1,11 @@ -import { createBdd } from "playwright-bdd"; -import { ToolbarTable } from "../../helpers/ToolbarTable"; -import { SearchPage } from "../../helpers/SearchPage"; import { expect } from "@playwright/test"; +import { createBdd } from "playwright-bdd"; + +import { Navigation } from "../../helpers/Navigation"; +import { Pagination } from "../../helpers/Pagination"; +import { Table } from "../../helpers/Table"; +import { Toolbar } from "../../helpers/Toolbar"; +import { VulnerabilityListPage } from "../../helpers/Constants"; export const { Given, When, Then } = createBdd(); @@ -11,8 +15,21 @@ const ADVISORY_TABLE_NAME = "Advisory table"; Given( "User visits Vulnerability details Page of {string}", async ({ page }, vulnerabilityID) => { - const searchPage = new SearchPage(page); - await searchPage.dedicatedSearch("Vulnerabilities", vulnerabilityID); + const navigation = await Navigation.build(page); + await navigation.goToSidebar("Vulnerabilities"); + + const toolbar = await Toolbar.build( + page, + VulnerabilityListPage.tableAriaLabel + ); + await toolbar.applyTextFilter( + VulnerabilityListPage.filters.filterText, + vulnerabilityID + ); + + const table = await Table.build(page, "Vulnerability table"); + await table.verifyTableHasData(); + await page.getByRole("link", { name: vulnerabilityID }).click(); } ); @@ -69,33 +86,39 @@ Then( // SBOMS Then("The SBOMs table is sorted by {string}", async ({ page }, columnName) => { - const toolbarTable = new ToolbarTable(page, SBOM_TABLE_NAME); - await toolbarTable.verifyTableIsSortedBy(columnName); + const table = await Table.build(page, SBOM_TABLE_NAME); + await table.verifyTableIsSortedBy(columnName); }); Then( "The SBOMs table total results is {int}", async ({ page }, totalResults) => { - const toolbarTable = new ToolbarTable(page, SBOM_TABLE_NAME); - await toolbarTable.verifyPaginationHasTotalResults(totalResults); + const pagination = await Pagination.build( + page, + "sbom-table-pagination-top" + ); + const total = await pagination.getTotalPagesFromNavigation(); + expect(total).toBe(totalResults); } ); Then( "The SBOMs table total results is greather than {int}", async ({ page }, totalResults) => { - const toolbarTable = new ToolbarTable(page, SBOM_TABLE_NAME); - await toolbarTable.verifyPaginationHasTotalResultsGreatherThan( - totalResults + const pagination = await Pagination.build( + page, + "sbom-table-pagination-top" ); + const total = await pagination.getTotalPagesFromNavigation(); + expect(total).toBeGreaterThan(totalResults); } ); Then( "The {string} column of the SBOM table contains {string}", async ({ page }, columnName, expectedValue) => { - const toolbarTable = new ToolbarTable(page, SBOM_TABLE_NAME); - await toolbarTable.verifyColumnContainsText(columnName, expectedValue); + const table = await Table.build(page, SBOM_TABLE_NAME); + await table.verifyColumnContainsText(columnName, expectedValue); } ); @@ -108,33 +131,39 @@ Then("User selects the Tabs {string}", async ({ page }, tabName) => { Then( "The Advisory table is sorted by {string}", async ({ page }, columnName) => { - const toolbarTable = new ToolbarTable(page, ADVISORY_TABLE_NAME); - await toolbarTable.verifyTableIsSortedBy(columnName); + const table = await Table.build(page, ADVISORY_TABLE_NAME); + await table.verifyTableIsSortedBy(columnName); } ); Then( "The Advisory table total results is {int}", async ({ page }, totalResults) => { - const toolbarTable = new ToolbarTable(page, ADVISORY_TABLE_NAME); - await toolbarTable.verifyPaginationHasTotalResults(totalResults); + const pagination = await Pagination.build( + page, + "advisory-table-pagination-top" + ); + const total = await pagination.getTotalPagesFromNavigation(); + expect(total).toBe(totalResults); } ); Then( "The Advisory table total results is greather than {int}", async ({ page }, totalResults) => { - const toolbarTable = new ToolbarTable(page, ADVISORY_TABLE_NAME); - await toolbarTable.verifyPaginationHasTotalResultsGreatherThan( - totalResults + const pagination = await Pagination.build( + page, + "advisory-table-pagination-top" ); + const total = await pagination.getTotalPagesFromNavigation(); + expect(total).toBeGreaterThan(totalResults); } ); Then( "The {string} column of the Advisory table contains {string}", async ({ page }, columnName, expectedValue) => { - const toolbarTable = new ToolbarTable(page, ADVISORY_TABLE_NAME); - await toolbarTable.verifyColumnContainsText(columnName, expectedValue); + const table = await Table.build(page, ADVISORY_TABLE_NAME); + await table.verifyColumnContainsText(columnName, expectedValue); } ); diff --git a/tests/ui/helpers/Constants.ts b/tests/ui/helpers/Constants.ts new file mode 100644 index 0000000..fe8ef6b --- /dev/null +++ b/tests/ui/helpers/Constants.ts @@ -0,0 +1,31 @@ +export type SidebarMenu = + | "Dashboard" + | "Search" + | "SBOMs" + | "Vulnerabilities" + | "Packages" + | "Advisories" + | "Importers" + | "Upload"; + +export const SBOMListPage = { + toolbarAriaLabel: "sbom-toolbar", + tableAriaLabel: "sbom-table", + paginationIdTop: "sbom-table-pagination-top", + paginationIdBottom: "sbom-table-pagination-bottom", + filters: { + filterText: "Filter text", + createdOn: "Created on", + label: "Label", + }, +}; + +export const VulnerabilityListPage = { + toolbarAriaLabel: "vulnerability-toolbar", + tableAriaLabel: "vulnerability-table", + paginationIdTop: "vulnerability-table-pagination-top", + paginationIdBottom: "vulnerability-table-pagination-bottom", + filters: { + filterText: "Filter text", + }, +}; diff --git a/tests/ui/helpers/DetailsPage.ts b/tests/ui/helpers/DetailsPage.ts index 4bb06e8..8365e6d 100644 --- a/tests/ui/helpers/DetailsPage.ts +++ b/tests/ui/helpers/DetailsPage.ts @@ -1,9 +1,5 @@ import { expect, Page } from "@playwright/test"; -/** - * Describes the Details of an Entity. E.g. SBOM Details Page. - * Generally based on https://www.patternfly.org/extensions/component-groups/details-page/ - */ export class DetailsPage { page: Page; @@ -40,117 +36,4 @@ export class DetailsPage { async verifyTabIsNotVisible(tabName: string) { await expect(this.page.getByRole("tab", { name: tabName })).toHaveCount(0); } - - //Wait for Loading Spinner to detach from the DOM - async waitForData() { - const spinner = this.page.locator(`xpath=(//table//tbody)[1]`); - await spinner.waitFor({ state: "visible", timeout: 5000 }); - } - - //Verifies the Page loads with data - async verifyDataAvailable() { - await expect( - this.page.locator( - `xpath=//div[(.='No data available to be shown here.')]` - ) - ).toHaveCount(0); - } - - //Verifies the Vulnerability counts from summary to table - async verifyVulnerabilityPanelcount() { - const pieVulnSevLabel = `xpath=//*[name()='svg']/*[name()='g']//*[name()='tspan']`; - const totalVuln = `xpath=//*[name()='svg']/*[name()='text']//*[name()='tspan'][1]`; - const totalVulnElement = this.page.locator(totalVuln); - await totalVulnElement.waitFor({ state: "visible", timeout: 5000 }); - const totalVulnPanel = await totalVulnElement.textContent(); - const panelVulnSev = await this.getCountFromLabels(pieVulnSevLabel, ":"); - const sumPanelVulnSev = await Object.values(panelVulnSev).reduce( - (sum, value) => sum + value, - 0 - ); - const tableVulnSev = await this.getCVSSCountFromVulnTable(); - let mismatch = false; - await expect( - parseInt(totalVulnPanel!, 10), - `Total Vulnerabilities count ${totalVulnPanel} mismatches with sum of individual ${sumPanelVulnSev}` - ).toEqual(sumPanelVulnSev); - - for (const severity in tableVulnSev) { - if (panelVulnSev[severity] !== undefined) { - if (panelVulnSev[severity] !== tableVulnSev[severity]) { - console.log( - `${severity} count mismatch. Summary panel count ${panelVulnSev[severity]}, Rows count ${tableVulnSev[severity]}` - ); - mismatch = true; - } - } - } - await expect(mismatch, "Panel count mismatches to table count").not.toBe( - true - ); - } - - /** - * Get all the Elements matching to the @param labelLocator and retrieves the textContext of each element - * Splits the text with @param delimiter - * @returns the mutable object { [key: 0th_element ]: 1st_element } - */ - async getCountFromLabels( - labelLocator: string, - delimiter: string - ): Promise<{ [key: string]: number }> { - let elements = await this.page.locator(labelLocator).all(); - let vulnLabelCount = {}; - for (let element of elements) { - let innerText = await element.textContent(); - let labelArr = await innerText!.split(delimiter); - vulnLabelCount[labelArr[0].trim().toString()] = parseInt( - labelArr[1].trim(), - 10 - ); - } - return vulnLabelCount; - } - - /** - * Retrieves the CVSS value from each row of Vulnerability table - * @returns Count of each CVSS type in { [key: severity ]: count } - */ - - async getCVSSCountFromVulnTable(): Promise<{ [key: string]: number }> { - let nextPage = true; - const counts = { - Unknown: 0, - None: 0, - Low: 0, - Medium: 0, - High: 0, - Critical: 0, - }; - const nextButton = await this.page.locator( - `xpath=(//section[@id='refVulnerabilitiesSection']//button[@data-action='next'])[1]` - ); - - const noOfRows = await this.page.locator( - `xpath=//section[@id="refVulnerabilitiesSection"]//button[@id="pagination-id-top-toggle"]` - ); - if (await noOfRows.isEnabled()) { - noOfRows.click(); - await this.page.getByRole("menuitem", { name: "100 per page" }).click(); - } - - while (nextPage) { - for (let cvssType in counts) { - let cvssLocator = await this.page - .locator(`xpath=//td[@data-label='CVSS']//div[.='${cvssType}']`) - .all(); - counts[cvssType] += await cvssLocator.length; - } - nextPage = await nextButton.isEnabled(); - if (nextPage) { - await nextButton.click(); - } - } - return counts; - } } diff --git a/tests/ui/helpers/Navigation.ts b/tests/ui/helpers/Navigation.ts new file mode 100644 index 0000000..62e573b --- /dev/null +++ b/tests/ui/helpers/Navigation.ts @@ -0,0 +1,28 @@ +import { Page } from "playwright-core"; + +export class Navigation { + 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" + ) { + await this.page.goto("/upload"); + await this.page.getByRole("link", { name: menu }).click(); + } +} diff --git a/tests/ui/helpers/Pagination.ts b/tests/ui/helpers/Pagination.ts new file mode 100644 index 0000000..50dfdd4 --- /dev/null +++ b/tests/ui/helpers/Pagination.ts @@ -0,0 +1,158 @@ +import { expect, Locator, Page } from "@playwright/test"; + +export class Pagination { + private _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); + } + + /** + * Retrieves the Total page count from pagination + * @returns total count from pagination text + */ + async getTotalNumberOfItems(): Promise { + const totalResultsText = await this._pagination + .locator(".pf-v6-c-pagination__total-items") + .locator("b") + .nth(1) + .textContent(); + return totalResultsText ? parseInt(totalResultsText) : null; + } + + /** + * Retrieves the Total page count from pagination + * @returns total count from pagination text + */ + async getTotalPagesFromNavigation(): Promise { + const navTotal = this._pagination.locator( + `xpath=//span[contains(@class,'form-control')]/following-sibling::span` + ); + const totalPages = await navTotal.textContent(); + return parseInt(totalPages!.replace("of", "").trim(), 10); + } + + /** + * Selects Number of rows per page on the table + * @param perPage Number of rows + */ + async selectPerPage(perPage: string) { + await this._pagination + .locator(`//button[@aria-haspopup='listbox']`) + .click(); + await this._page.getByRole("menuitem", { name: perPage }).click(); + } + + /** + * Verifies the pagination count against per page selection + * Example, SBOM Explorer - Vulnerabilities and Packages section top and bottom sections. + * Parent Element for Vulnerabilities section top Pagination `//div[@id="vulnerability-table-pagination-top"]` + * And bottom section `//div[@id="vulnerability-table-pagination-bottom"]` + */ + async verifyPagination() { + const perPageValues = [10, 20, 50, 100]; + const totalItems = await this.getTotalNumberOfItems(); + expect(totalItems).not.toBeNull(); + + for (const value of perPageValues) { + const firstPage = this._pagination.getByRole("button", { + name: "Go to first page", + }); + if (await firstPage.isEnabled()) { + await firstPage.click(); + } + let expectedPagecount = Math.trunc(totalItems! / value); + let remainingRows = totalItems! % value; + if (remainingRows > 0) { + expectedPagecount += 1; + } + await this.selectPerPage(value + " per page"); + + // Wait for page to load + await expect( + this._pagination.locator(".pf-v6-c-pagination__nav") + ).toContainText(`of ${expectedPagecount}`, { timeout: 15000 }); + + await this.navigateToEachPageVerifyRowsCount( + expectedPagecount, + value, + remainingRows + ); + } + } + + /** + * Verifies Number of rows on the table equals to or lesser than the row count given + * @param rowsCount Number of rows + */ + async verifyPerPageToRowCount(rowsCount: number) { + // Bug: https://issues.redhat.com/browse/TC-2353 + // expect(this._page.locator("table > tbody > tr").count()).toEqual(rowsCount); + } + + /** + * Navigates to Each page with Next button and verify the rows count + * @param pageCount Number of Pages expected + * @param perPageRows Number of rows expected per page + * @param remainingRows Number of rows in Last Page + */ + async navigateToEachPageVerifyRowsCount( + pageCount: number, + perPageRows: number, + remainingRows: number + ) { + const nextButton = this._pagination.getByLabel("Go to next page"); + let expMinCount = 1; + let expMaxCount = perPageRows; + if (pageCount === 1) { + expMaxCount = remainingRows; + } + for (let i = 1; i < pageCount; i++) { + // Wait for page to load + await expect( + this._pagination.locator("input[aria-label='Current page']") + ).toHaveValue(`${i}`, { timeout: 30000 }); + + await this.verifyRowsCounterPagination(expMinCount, expMaxCount); + await this.verifyPerPageToRowCount(perPageRows); + await nextButton.isEnabled(); + await nextButton.click(); + + expMinCount += perPageRows; + if (i === pageCount - 1) { + expMaxCount = expMaxCount + remainingRows; + } else { + expMaxCount += perPageRows; + } + } + if (remainingRows > 0) { + await this.verifyPerPageToRowCount(remainingRows); + await this.verifyRowsCounterPagination(expMinCount, expMaxCount); + } + await nextButton.isDisabled(); + } + + /** + * @param expMinCount Expected Min count on the counter + * @param expMaxCount Expected Max count on the counter + */ + async verifyRowsCounterPagination(expMinCount: number, expMaxCount: number) { + const pageCounter = this._pagination.locator( + `xpath=//button//b[contains (.,"-")]` + ); + const counterText = await pageCounter.textContent(); + let [min, max] = counterText! + .split("-") + .map((value) => parseInt(value.trim())); + expect(min).toEqual(expMinCount); + expect(max).toEqual(expMaxCount); + } +} diff --git a/tests/ui/helpers/SbomDetailsPage.ts b/tests/ui/helpers/SbomDetailsPage.ts new file mode 100644 index 0000000..ac778cd --- /dev/null +++ b/tests/ui/helpers/SbomDetailsPage.ts @@ -0,0 +1,111 @@ +import { expect, Page } from "@playwright/test"; + +/** + * Describes the Details of an Entity. E.g. SBOM Details Page. + * Generally based on https://www.patternfly.org/extensions/component-groups/details-page/ + */ +export class SbomDetailsPage { + page: Page; + + constructor(page: Page) { + this.page = page; + } + + //Verifies the Vulnerability counts from summary to table + async verifyVulnerabilityPanelcount() { + const pieVulnSevLabel = `xpath=//*[name()='svg']/*[name()='g']//*[name()='tspan']`; + const totalVuln = `xpath=//*[name()='svg']/*[name()='text']//*[name()='tspan'][1]`; + const totalVulnElement = this.page.locator(totalVuln); + await totalVulnElement.waitFor({ state: "visible", timeout: 5000 }); + const totalVulnPanel = await totalVulnElement.textContent(); + const panelVulnSev = await this.getCountFromLabels(pieVulnSevLabel, ":"); + const sumPanelVulnSev = await Object.values(panelVulnSev).reduce( + (sum, value) => sum + value, + 0 + ); + const tableVulnSev = await this.getCVSSCountFromVulnTable(); + let mismatch = false; + await expect( + parseInt(totalVulnPanel!, 10), + `Total Vulnerabilities count ${totalVulnPanel} mismatches with sum of individual ${sumPanelVulnSev}` + ).toEqual(sumPanelVulnSev); + + for (const severity in tableVulnSev) { + if (panelVulnSev[severity] !== undefined) { + if (panelVulnSev[severity] !== tableVulnSev[severity]) { + console.log( + `${severity} count mismatch. Summary panel count ${panelVulnSev[severity]}, Rows count ${tableVulnSev[severity]}` + ); + mismatch = true; + } + } + } + await expect(mismatch, "Panel count mismatches to table count").not.toBe( + true + ); + } + + /** + * Get all the Elements matching to the @param labelLocator and retrieves the textContext of each element + * Splits the text with @param delimiter + * @returns the mutable object { [key: 0th_element ]: 1st_element } + */ + async getCountFromLabels( + labelLocator: string, + delimiter: string + ): Promise<{ [key: string]: number }> { + let elements = await this.page.locator(labelLocator).all(); + let vulnLabelCount = {}; + for (let element of elements) { + let innerText = await element.textContent(); + let labelArr = await innerText!.split(delimiter); + vulnLabelCount[labelArr[0].trim().toString()] = parseInt( + labelArr[1].trim(), + 10 + ); + } + return vulnLabelCount; + } + + /** + * Retrieves the CVSS value from each row of Vulnerability table + * @returns Count of each CVSS type in { [key: severity ]: count } + */ + + async getCVSSCountFromVulnTable(): Promise<{ [key: string]: number }> { + let nextPage = true; + const counts = { + Unknown: 0, + None: 0, + Low: 0, + Medium: 0, + High: 0, + Critical: 0, + }; + const nextButton = await this.page.locator( + `xpath=(//section[@id='refVulnerabilitiesSection']//button[@data-action='next'])[1]` + ); + + const noOfRows = await this.page.locator( + `xpath=//section[@id="refVulnerabilitiesSection"]//button[@id="pagination-id-top-toggle"]` + ); + if (await noOfRows.isEnabled()) { + noOfRows.click(); + await this.page.getByRole("menuitem", { name: "100 per page" }).click(); + } + + while (nextPage) { + for (let cvssType in counts) { + let cvssLocator = await this.page + .locator(`xpath=//td[@data-label='CVSS']//div[.='${cvssType}']`) + .all(); + counts[cvssType] += await cvssLocator.length; + } + nextPage = await nextButton.isEnabled(); + if (nextPage) { + await nextButton.click(); + } + } + return counts; + } +} diff --git a/tests/ui/helpers/SearchPage.ts b/tests/ui/helpers/SearchPage.ts deleted file mode 100644 index cf82a0d..0000000 --- a/tests/ui/helpers/SearchPage.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { expect, Page } from "@playwright/test"; -import { DetailsPage } from "./DetailsPage"; - -export class SearchPage { - page: Page; - - constructor(page: Page) { - this.page = page; - } - - /** - * Navigates to given menu option and filters data - * @param menu Option from Vertical navigation menu - * @param data Search data to filter - */ - async dedicatedSearch(menu: string, data: string) { - await this.page.goto("/"); - await this.page.getByRole("link", { name: menu }).click(); - const detailsPage = new DetailsPage(this.page); - await detailsPage.waitForData(); - await detailsPage.verifyDataAvailable(); - await this.page.getByPlaceholder("Search").click(); - await this.page.getByPlaceholder("Search").fill(data); - await this.page.getByPlaceholder("Search").press("Enter"); - await detailsPage.verifyDataAvailable(); - } -} diff --git a/tests/ui/helpers/Table.ts b/tests/ui/helpers/Table.ts new file mode 100644 index 0000000..6763571 --- /dev/null +++ b/tests/ui/helpers/Table.ts @@ -0,0 +1,55 @@ +import { expect, Locator, Page } from "@playwright/test"; + +export class Table { + private _page: Page; + _table: Locator; + + private constructor(page: Page, table: Locator) { + this._page = page; + this._table = table; + } + + /** + * @param page + * @param toolbarAriaLabel the unique aria-label that correspont 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({ timeout: 5000 }); + return new Table(page, table); + } + + 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 verifyTableHasData() { + const rows = this._table.locator( + 'xpath=//tbody[not(@aria-label="Table loading") and not(@aria-label="Table error") and not(@aria-label="Table empty")]' + ); + + // Wait for at least one to appear (or a specific one) + await expect(rows.first()).toBeVisible(); + + // Now safely count + const rowsCount = await rows.count(); + expect(rowsCount).toBeGreaterThanOrEqual(1); + } + + async verifyTableHasNoData() { + await expect( + this._table.locator(`tbody[aria-label="Table empty"]`) + ).toBeVisible(); + } +} diff --git a/tests/ui/helpers/Toolbar.ts b/tests/ui/helpers/Toolbar.ts new file mode 100644 index 0000000..83c30c9 --- /dev/null +++ b/tests/ui/helpers/Toolbar.ts @@ -0,0 +1,92 @@ +import { expect, Locator, Page } from "@playwright/test"; + +export class Toolbar { + private _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 correspont to the DOM element that contains the Toobar. 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({ timeout: 5000 }); + 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("[aria-label='filter-toggle']").click(); + this._page + .locator("[aria-label='filter-menu']") + .getByText(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 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/helpers/ToolbarTable.ts b/tests/ui/helpers/ToolbarTable.ts deleted file mode 100644 index 49e51b3..0000000 --- a/tests/ui/helpers/ToolbarTable.ts +++ /dev/null @@ -1,232 +0,0 @@ -import { expect, Page } from "@playwright/test"; -export class ToolbarTable { - private _page: Page; - private _tableName: string; - - constructor(page: Page, tableName: string) { - this._page = page; - this._tableName = tableName; - } - - async verifyPaginationHasTotalResults(totalResults: number) { - const paginationTop = this._page.locator("#pagination-id-top-toggle"); - await expect(paginationTop.locator("b").nth(1)).toHaveText( - `${totalResults}` - ); - } - - async verifyPaginationHasTotalResultsGreatherThan( - totalResults: number, - include: boolean = false - ) { - const paginationTop = this._page.locator("#pagination-id-top-toggle"); - const totalResultsText = await paginationTop - .locator("b") - .nth(1) - .textContent(); - if (include) { - expect(Number(totalResultsText)).toBeGreaterThanOrEqual(totalResults); - } else { - expect(Number(totalResultsText)).toBeGreaterThan(totalResults); - } - } - - async filterByText(filterText: string) { - const input = this._page.locator("#search-input"); - await input.fill(filterText); - await input.press("Enter"); - } - - async verifyTableIsSortedBy(columnName: string, asc: boolean = true) { - const table = this.getTable(); - await expect( - table.getByRole("columnheader", { name: columnName }) - ).toHaveAttribute("aria-sort", asc ? "ascending" : "descending"); - } - - async verifyColumnContainsText(columnName: any, expectedValue: any) { - const table = this.getTable(); - await expect(table.locator(`td[data-label="${columnName}"]`)).toContainText( - expectedValue - ); - } - - /** - * Verifies the pagination count against per page selection - * @param parentElem required to identify the pagination across sections - * Example, SBOM Explorer - Vulnerabilities and Packages section top and bottom sections. - * Parent Element for Vulnerabilities section top Pagination `//div[@id="vulnerability-table-pagination-top"]` - * And bottom section `//div[@id="vulnerability-table-pagination-bottom"]` - */ - async verifyPagination(parentElem: string) { - const section = this._page.locator(parentElem); - const perPageValues = [10, 20, 50, 100]; - const totalRows = await this.getTotalRowsFromPagination(parentElem); - for (const value of perPageValues) { - const firstPage = section.getByRole("button", { - name: "Go to first page", - }); - if (await firstPage.isEnabled()) { - await firstPage.click(); - } - let expectedPagecount = Math.trunc(totalRows / value); - let remainingRows = totalRows % value; - if (remainingRows > 0) { - expectedPagecount += 1; - } - await this.selectPerPage(parentElem, value + " per page"); - const progressBar = this._page.getByRole("gridcell", { - name: "Loading...", - }); - await progressBar.waitFor({ state: "hidden", timeout: 5000 }); - const actualPageCount = - await this.getTotalPagesFromNavigation(parentElem); - await expect(actualPageCount, "Page count mismatches").toEqual( - expectedPagecount - ); - await this.navigateToEachPageVerifyRowsCount( - parentElem, - expectedPagecount, - value, - remainingRows - ); - } - } - - /** - * Retrieves the Total page count from pagination - * @param parentElem required to identify the pagination across sections - * @returns total count from pagination text - */ - async getTotalPagesFromNavigation(parentElem: string): Promise { - const section = this._page.locator(parentElem); - const navTotal = await section.locator( - `xpath=//span[contains(@class,'form-control')]/following-sibling::span` - ); - const totalPages = await navTotal.textContent(); - return parseInt(totalPages!.replace("of", "").trim(), 10); - } - - /** - * Retrieves the Total Row count from pagination - * @param parentElem required to differentiate top and bottom pagination - * @returns total row count from pagination dropdown - */ - async getTotalRowsFromPagination(parentElem: string): Promise { - const tableError = this._page.locator( - `xpath=(//tbody[@aria-label="Table error"])[1]` - ); - if (await tableError.isVisible()) { - await expect(tableError, "No Data available").not.toBeVisible(); - } - const progressBar = this._page.getByRole("gridcell", { - name: "Loading...", - }); - await progressBar.waitFor({ state: "hidden", timeout: 5000 }); - const pagination = this._page.locator(parentElem); - const totalResultsText = await pagination - .locator(`xpath=//button//b[not(contains (.,'-'))]`) - .textContent(); - return parseInt(totalResultsText!.trim(), 10); - } - - /** - * Selects Number of rows per page on the table - * @param perPage Number of rows - */ - async selectPerPage(parentElem: string, perPage: string) { - const pagination = this._page.locator(parentElem); - await pagination.locator(`//button[@aria-haspopup='listbox']`).click(); - await this._page.getByRole("menuitem", { name: perPage }).click(); - } - - /** - * Verifies Number of rows on the table equals to or lesser than the row count given - * @param rowsCount Number of rows - */ - async verifyPerPageToRowCount(rowsCount: number) { - const table = this.getTable(); - const rows = await table.locator(`xpath=//tbody/tr`); - const tabRows = await rows.count(); - // Bug: https://issues.redhat.com/browse/TC-2353 - await expect(tabRows).toEqual(rowsCount); - } - - /** - * Navigates to Each page with Next button and verify the rows count - * @param parentElem required to identify the pagination across sections - * @param pageCount Number of Pages expected - * @param perPageRows Number of rows expected per page - * @param remainingRows Number of rows in Last Page - */ - async navigateToEachPageVerifyRowsCount( - parentElem: string, - pageCount: number, - perPageRows: number, - remainingRows: number - ) { - const section = this._page.locator(parentElem); - const nextButton = await section.getByLabel("Go to next page"); - let expMinCount = 1; - let expMaxCount = perPageRows; - if (pageCount === 1) { - expMaxCount = remainingRows; - } - for (let i = 1; i < pageCount; i++) { - await this.verifyRowsCounterPagination( - parentElem, - expMinCount, - expMaxCount - ); - await this.verifyPerPageToRowCount(perPageRows); - await nextButton.isEnabled(); - await nextButton.click(); - const progressBar = this._page.getByRole("gridcell", { - name: "Loading...", - }); - await progressBar.waitFor({ state: "hidden", timeout: 5000 }); - expMinCount += perPageRows; - if (i === pageCount - 1) { - expMaxCount = expMaxCount + remainingRows; - } else { - expMaxCount += perPageRows; - } - } - if (remainingRows > 0) { - await this.verifyPerPageToRowCount(remainingRows); - await this.verifyRowsCounterPagination( - parentElem, - expMinCount, - expMaxCount - ); - } - await nextButton.isDisabled(); - } - - /** - * - * @param parentElem required to differentiate top and bottom pagination - * @param expMinCount Expected Min count on the counter - * @param expMaxCount Expected Max count on the counter - */ - async verifyRowsCounterPagination( - parentElem: string, - expMinCount: number, - expMaxCount: number - ) { - const pagination = this._page.locator(parentElem); - const pageCounter = await pagination.locator( - `xpath=//button//b[contains (.,"-")]` - ); - const counterText = await pageCounter.textContent(); - let [min, max] = counterText! - .split("-") - .map((value) => parseInt(value.trim())); - await expect(min).toEqual(expMinCount); - await expect(max).toEqual(expMaxCount); - } - - private getTable() { - return this._page.locator(`table[aria-label="${this._tableName}"]`); - } -} diff --git a/tests/ui/pages/example.spec.ts b/tests/ui/pages/example.spec.ts deleted file mode 100644 index 95a3917..0000000 --- a/tests/ui/pages/example.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -// @ts-check - -import { expect, test } from "@playwright/test"; - -test("has title", async ({ page }) => { - await page.goto("https://playwright.dev/"); - - // Expect a title "to contain" a substring. - await expect(page).toHaveTitle(/Playwright/); - await expect(page).toHaveTitle(/Playwright/); -}); - -test("get started link", async ({ page }) => { - await page.goto("https://playwright.dev/"); - - // Click the get started link. - await page.getByRole("link", { name: "Get started" }).click(); - - // Expects page to have a heading with the name of Installation. - await expect( - page.getByRole("heading", { name: "Installation" }) - ).toBeVisible(); -}); 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..e4ed171 --- /dev/null +++ b/tests/ui/pages/sbom-list/filter.spec.ts @@ -0,0 +1,39 @@ +// @ts-check + +import { test } from "@playwright/test"; + +import { login } from "../../helpers/Auth"; +import { SBOMListPage } from "../../helpers/Constants"; +import { Navigation } from "../../helpers/Navigation"; +import { Table } from "../../helpers/Table"; +import { Toolbar } from "../../helpers/Toolbar"; + +test.describe("Filter validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test("Filters", async ({ page }) => { + const navigation = await Navigation.build(page); + await navigation.goToSidebar("SBOMs"); + + const toolbar = await Toolbar.build(page, SBOMListPage.toolbarAriaLabel); + const table = await Table.build(page, SBOMListPage.tableAriaLabel); + + // Full search + await toolbar.applyTextFilter(SBOMListPage.filters.filterText, "quarkus"); + await table.verifyColumnContainsText("Name", "quarkus-bom"); + + // Date filter + await toolbar.applyDateRangeFilter( + SBOMListPage.filters.createdOn, + "11/21/2023", + "11/23/2023" + ); + await table.verifyColumnContainsText("Name", "quarkus-bom"); + + // Labels filter + await toolbar.applyLabelsFilter(SBOMListPage.filters.label, ["type=spdx"]); + await table.verifyColumnContainsText("Name", "quarkus-bom"); + }); +}); From 59297c9a9816c09db68439a32aa436540bcc6364 Mon Sep 17 00:00:00 2001 From: Carlos Feria <2582866+carlosthe19916@users.noreply.github.com> Date: Wed, 25 Jun 2025 15:52:52 +0200 Subject: [PATCH 04/23] save changes Signed-off-by: Carlos Feria <2582866+carlosthe19916@users.noreply.github.com> --- .../features/@sbom-explorer/sbom-explorer.feature | 8 ++++---- tests/ui/helpers/Navigation.ts | 13 +++++++++---- tests/ui/helpers/SbomDetailsPage.ts | 10 +++++----- tests/ui/helpers/Table.ts | 4 ++-- tests/ui/helpers/Toolbar.ts | 2 +- 5 files changed, 21 insertions(+), 16 deletions(-) diff --git a/tests/ui/features/@sbom-explorer/sbom-explorer.feature b/tests/ui/features/@sbom-explorer/sbom-explorer.feature index ba93907..7f8642d 100644 --- a/tests/ui/features/@sbom-explorer/sbom-explorer.feature +++ b/tests/ui/features/@sbom-explorer/sbom-explorer.feature @@ -1,7 +1,7 @@ Feature: SBOM Explorer - View SBOM details Background: Authentication Given User is authenticated - + Scenario Outline: View SBOM Overview Given An ingested "" SBOM "" is available When User visits SBOM details Page of "" @@ -14,7 +14,7 @@ Feature: SBOM Explorer - View SBOM details Examples: | sbomName | | quarkus-bom | - + Scenario Outline: View SBOM Info (Metadata) Given An ingested "" SBOM "" is available When User visits SBOM details Page of "" @@ -28,7 +28,7 @@ Feature: SBOM Explorer - View SBOM details Examples: | sbomName | | quarkus-bom | - + Scenario Outline: Downloading SBOM file Given An ingested "" SBOM "" is available When User visits SBOM details Page of "" @@ -56,7 +56,7 @@ Feature: SBOM Explorer - View SBOM details Then The Package table total results is 0 When User clear all filters - Then The Package table total results is greather than 1 + Then The Package table total results is greater than 1 Examples: | sbomType | sbomName | packageName | diff --git a/tests/ui/helpers/Navigation.ts b/tests/ui/helpers/Navigation.ts index 62e573b..a106db6 100644 --- a/tests/ui/helpers/Navigation.ts +++ b/tests/ui/helpers/Navigation.ts @@ -1,10 +1,13 @@ import { Page } from "playwright-core"; +/** + * Used to navigate to different pages + */ export class Navigation { - page: Page; + private _page: Page; private constructor(page: Page) { - this.page = page; + this._page = page; } static async build(page: Page) { @@ -22,7 +25,9 @@ export class Navigation { | "Importers" | "Upload" ) { - await this.page.goto("/upload"); - await this.page.getByRole("link", { name: menu }).click(); + // 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/helpers/SbomDetailsPage.ts b/tests/ui/helpers/SbomDetailsPage.ts index ac778cd..108f90d 100644 --- a/tests/ui/helpers/SbomDetailsPage.ts +++ b/tests/ui/helpers/SbomDetailsPage.ts @@ -58,7 +58,7 @@ export class SbomDetailsPage { let vulnLabelCount = {}; for (let element of elements) { let innerText = await element.textContent(); - let labelArr = await innerText!.split(delimiter); + let labelArr = innerText!.split(delimiter); vulnLabelCount[labelArr[0].trim().toString()] = parseInt( labelArr[1].trim(), 10 @@ -82,15 +82,15 @@ export class SbomDetailsPage { High: 0, Critical: 0, }; - const nextButton = await this.page.locator( + const nextButton = this.page.locator( `xpath=(//section[@id='refVulnerabilitiesSection']//button[@data-action='next'])[1]` ); - const noOfRows = await this.page.locator( + const noOfRows = this.page.locator( `xpath=//section[@id="refVulnerabilitiesSection"]//button[@id="pagination-id-top-toggle"]` ); if (await noOfRows.isEnabled()) { - noOfRows.click(); + await noOfRows.click(); await this.page.getByRole("menuitem", { name: "100 per page" }).click(); } @@ -99,7 +99,7 @@ export class SbomDetailsPage { let cvssLocator = await this.page .locator(`xpath=//td[@data-label='CVSS']//div[.='${cvssType}']`) .all(); - counts[cvssType] += await cvssLocator.length; + counts[cvssType] += cvssLocator.length; } nextPage = await nextButton.isEnabled(); if (nextPage) { diff --git a/tests/ui/helpers/Table.ts b/tests/ui/helpers/Table.ts index 6763571..692b011 100644 --- a/tests/ui/helpers/Table.ts +++ b/tests/ui/helpers/Table.ts @@ -11,12 +11,12 @@ export class Table { /** * @param page - * @param toolbarAriaLabel the unique aria-label that correspont to the DOM element that contains the Table. E.g.
+ * @param tableAriaLabel the unique aria-label that correspont 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({ timeout: 5000 }); + await expect(table).toBeVisible(); return new Table(page, table); } diff --git a/tests/ui/helpers/Toolbar.ts b/tests/ui/helpers/Toolbar.ts index 83c30c9..593b26b 100644 --- a/tests/ui/helpers/Toolbar.ts +++ b/tests/ui/helpers/Toolbar.ts @@ -26,7 +26,7 @@ export class Toolbar { */ async selectFilter(filterName: string) { await this._toolbar.locator("[aria-label='filter-toggle']").click(); - this._page + await this._page .locator("[aria-label='filter-menu']") .getByText(filterName) .click(); From 2f6cbcd1d4de135b49db8b80623bc4fd02910245 Mon Sep 17 00:00:00 2001 From: Carlos Feria <2582866+carlosthe19916@users.noreply.github.com> Date: Wed, 25 Jun 2025 16:37:16 +0200 Subject: [PATCH 05/23] save changes Signed-off-by: Carlos Feria <2582866+carlosthe19916@users.noreply.github.com> --- tests/ui/helpers/Toolbar.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/tests/ui/helpers/Toolbar.ts b/tests/ui/helpers/Toolbar.ts index 593b26b..04a52d5 100644 --- a/tests/ui/helpers/Toolbar.ts +++ b/tests/ui/helpers/Toolbar.ts @@ -25,11 +25,8 @@ export class Toolbar { * @param filterName the name of the filter as rendered in the UI */ async selectFilter(filterName: string) { - await this._toolbar.locator("[aria-label='filter-toggle']").click(); - await this._page - .locator("[aria-label='filter-menu']") - .getByText(filterName) - .click(); + 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( From 5df00fc8bd474c882788a06ea7df9b573a614168 Mon Sep 17 00:00:00 2001 From: Carlos Feria <2582866+carlosthe19916@users.noreply.github.com> Date: Fri, 27 Jun 2025 10:22:08 +0200 Subject: [PATCH 06/23] sbom list fully tested Signed-off-by: Carlos Feria <2582866+carlosthe19916@users.noreply.github.com> --- package.json | 2 + playwright.config.ts | 7 +- .../@sbom-explorer/sbom-explorer.feature | 8 +- .../@sbom-explorer/sbom-explorer.step.ts | 98 +++----- .../vulnerability-explorer.step.ts | 75 ++---- tests/ui/helpers/DetailsPage.ts | 117 +++++++++ tests/ui/helpers/Pagination.ts | 158 ------------ tests/ui/helpers/SbomDetailsPage.ts | 111 --------- tests/ui/helpers/SearchPage.ts | 27 ++ tests/ui/helpers/Table.ts | 55 ----- tests/ui/helpers/ToolbarTable.ts | 232 ++++++++++++++++++ tests/ui/{helpers => pages}/Constants.ts | 8 + tests/ui/pages/DetailsPage.ts | 38 +++ tests/ui/{helpers => pages}/Navigation.ts | 0 tests/ui/pages/Pagination.ts | 90 +++++++ tests/ui/pages/Table.ts | 104 ++++++++ tests/ui/{helpers => pages}/Toolbar.ts | 6 +- tests/ui/pages/sbom-list/actions.spec.ts | 107 ++++++++ tests/ui/pages/sbom-list/columns.spec.ts | 64 +++++ tests/ui/pages/sbom-list/filter.spec.ts | 15 +- tests/ui/pages/sbom-list/pagination.spec.ts | 36 +++ tests/ui/pages/sbom-list/sort.spec.ts | 31 +++ 22 files changed, 934 insertions(+), 455 deletions(-) delete mode 100644 tests/ui/helpers/Pagination.ts delete mode 100644 tests/ui/helpers/SbomDetailsPage.ts create mode 100644 tests/ui/helpers/SearchPage.ts delete mode 100644 tests/ui/helpers/Table.ts create mode 100644 tests/ui/helpers/ToolbarTable.ts rename tests/ui/{helpers => pages}/Constants.ts (77%) create mode 100644 tests/ui/pages/DetailsPage.ts rename tests/ui/{helpers => pages}/Navigation.ts (100%) create mode 100644 tests/ui/pages/Pagination.ts create mode 100644 tests/ui/pages/Table.ts rename tests/ui/{helpers => pages}/Toolbar.ts (94%) create mode 100644 tests/ui/pages/sbom-list/actions.spec.ts create mode 100644 tests/ui/pages/sbom-list/columns.spec.ts create mode 100644 tests/ui/pages/sbom-list/pagination.spec.ts create mode 100644 tests/ui/pages/sbom-list/sort.spec.ts diff --git a/package.json b/package.json index 3cf440c..ddfb976 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,8 @@ "test:ui": "npx bddgen && npx playwright test --project='chromium'", "test:ui:trace": "npx bddgen && npx playwright test --project='chromium' --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 a2af7e1..42255bb 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -73,14 +73,17 @@ export default defineConfig({ }, { - name: "vanilla", + name: "playwright", testDir: "./tests/ui/pages", testMatch: "*.spec.ts", - dependencies: ["setup-ui-data"], use: { ...devices["Desktop Chrome"], ...DESKTOP_CONFIG, }, + // timeout: 120_000, + // expect: { + // timeout: 20_000, + // }, }, { diff --git a/tests/ui/features/@sbom-explorer/sbom-explorer.feature b/tests/ui/features/@sbom-explorer/sbom-explorer.feature index 7f8642d..ba93907 100644 --- a/tests/ui/features/@sbom-explorer/sbom-explorer.feature +++ b/tests/ui/features/@sbom-explorer/sbom-explorer.feature @@ -1,7 +1,7 @@ Feature: SBOM Explorer - View SBOM details Background: Authentication Given User is authenticated - + Scenario Outline: View SBOM Overview Given An ingested "" SBOM "" is available When User visits SBOM details Page of "" @@ -14,7 +14,7 @@ Feature: SBOM Explorer - View SBOM details Examples: | sbomName | | quarkus-bom | - + Scenario Outline: View SBOM Info (Metadata) Given An ingested "" SBOM "" is available When User visits SBOM details Page of "" @@ -28,7 +28,7 @@ Feature: SBOM Explorer - View SBOM details Examples: | sbomName | | quarkus-bom | - + Scenario Outline: Downloading SBOM file Given An ingested "" SBOM "" is available When User visits SBOM details Page of "" @@ -56,7 +56,7 @@ Feature: SBOM Explorer - View SBOM details Then The Package table total results is 0 When User clear all filters - Then The Package table total results is greater than 1 + Then The Package table total results is greather than 1 Examples: | sbomType | sbomName | packageName | diff --git a/tests/ui/features/@sbom-explorer/sbom-explorer.step.ts b/tests/ui/features/@sbom-explorer/sbom-explorer.step.ts index 7212e5f..190f061 100644 --- a/tests/ui/features/@sbom-explorer/sbom-explorer.step.ts +++ b/tests/ui/features/@sbom-explorer/sbom-explorer.step.ts @@ -1,12 +1,8 @@ import { createBdd } from "playwright-bdd"; import { expect } from "playwright/test"; import { DetailsPage } from "../../helpers/DetailsPage"; -import { Toolbar } from "../../helpers/Toolbar"; -import { Table } from "../../helpers/Table"; -import { Pagination } from "../../helpers/Pagination"; -import { Navigation } from "../../helpers/Navigation"; -import { SbomDetailsPage } from "../../helpers/SbomDetailsPage"; -import { SBOMListPage } from "../../helpers/Constants"; +import { ToolbarTable } from "../../helpers/ToolbarTable"; +import { SearchPage } from "../../helpers/SearchPage"; export const { Given, When, Then } = createBdd(); @@ -16,14 +12,8 @@ const VULN_TABLE_NAME = "Vulnerability table"; Given( "An ingested {string} SBOM {string} is available", async ({ page }, _sbomType, sbomName) => { - const navigation = await Navigation.build(page); - await navigation.goToSidebar("SBOMs"); - - const toolbar = await Toolbar.build(page, SBOMListPage.toolbarAriaLabel); - await toolbar.applyTextFilter(SBOMListPage.filters.filterText, sbomName); - - const table = await Table.build(page, SBOMListPage.tableAriaLabel); - await table.verifyTableHasData(); + const searchPage = new SearchPage(page); + await searchPage.dedicatedSearch("SBOMs", sbomName); } ); @@ -56,66 +46,48 @@ Then( Then( "The Package table is sorted by {string}", async ({ page }, columnName) => { - const table = await Table.build(page, PACKAGE_TABLE_NAME); - await table.verifyTableIsSortedBy(columnName); + const toolbarTable = new ToolbarTable(page, PACKAGE_TABLE_NAME); + await toolbarTable.verifyTableIsSortedBy(columnName); } ); Then("Search by FilterText {string}", async ({ page }, filterText) => { - const toolbar = await Toolbar.build(page, "Package toolbar"); - await toolbar.applyTextFilter("Filter text", filterText); + const toolbarTable = new ToolbarTable(page, PACKAGE_TABLE_NAME); + await toolbarTable.filterByText(filterText); }); Then( "The Package table total results is {int}", async ({ page }, totalResults) => { - const table = await Table.build(page, PACKAGE_TABLE_NAME); - if (totalResults > 0) { - await table.verifyTableHasData(); - } else { - await table.verifyTableHasNoData(); - } - - const pagination = await Pagination.build( - page, - "package-table-pagination-top" - ); - const total = await pagination.getTotalNumberOfItems(); - expect(total).toBe(totalResults); + const toolbarTable = new ToolbarTable(page, PACKAGE_TABLE_NAME); + await toolbarTable.verifyPaginationHasTotalResults(totalResults); } ); Then( - "The Package table total results is greater than {int}", + "The Package table total results is greather than {int}", async ({ page }, totalResults) => { - const pagination = await Pagination.build( - page, - "package-table-pagination-top" + const toolbarTable = new ToolbarTable(page, PACKAGE_TABLE_NAME); + await toolbarTable.verifyPaginationHasTotalResultsGreatherThan( + totalResults ); - const total = await pagination.getTotalNumberOfItems(); - expect(total).toBeGreaterThan(totalResults); } ); Then( "The {string} column of the Package table table contains {string}", async ({ page }, columnName, expectedValue) => { - const table = await Table.build(page, PACKAGE_TABLE_NAME); - await table.verifyColumnContainsText(columnName, expectedValue); + const toolbarTable = new ToolbarTable(page, PACKAGE_TABLE_NAME); + await toolbarTable.verifyColumnContainsText(columnName, expectedValue); } ); Given( "An ingested {string} SBOM {string} containing Vulnerabilities", async ({ page }, _sbomType, sbomName) => { - const navigation = await Navigation.build(page); - await navigation.goToSidebar("SBOMs"); - - const toolbar = await Toolbar.build(page, SBOMListPage.toolbarAriaLabel); - await toolbar.applyTextFilter(SBOMListPage.filters.filterText, sbomName); - - const table = await Table.build(page, SBOMListPage.tableAriaLabel); - const element = table._table.locator( + const searchPage = new SearchPage(page); + await searchPage.dedicatedSearch("SBOMs", sbomName); + const element = await page.locator( `xpath=(//tr[contains(.,'${sbomName}')]/td[@data-label='Vulnerabilities']/div)[1]` ); await expect(element, "SBOM have no vulnerabilities").toHaveText( @@ -143,24 +115,24 @@ Then( Then( "Vulnerability Risk Profile shows summary of vulnerabilities", async ({ page }) => { - const sbomDetailsPage = new SbomDetailsPage(page); - await sbomDetailsPage.verifyVulnerabilityPanelcount(); + const detailsPage = new DetailsPage(page); + await detailsPage.verifyVulnerabilityPanelcount(); } ); Then( "SBOM Name {string} should be visible inside the tab", async ({ page }, sbomName) => { - const panelSbomName = page.locator( + const panelSbomName = await page.locator( `xpath=//section[@id='refVulnerabilitiesSection']//dt[contains(.,'Name')]/following-sibling::dd` ); await panelSbomName.isVisible(); - expect(await panelSbomName.textContent()).toEqual(sbomName); + await expect(await panelSbomName.textContent()).toEqual(sbomName); } ); Then("SBOM Version should be visible inside the tab", async ({ page }) => { - const panelSBOMVersion = page.locator( + const panelSBOMVersion = await page.locator( `xpath=//section[@id='refVulnerabilitiesSection']//dt[contains(.,'Version')]/following-sibling::dd` ); await panelSBOMVersion.isVisible(); @@ -169,7 +141,7 @@ Then("SBOM Version should be visible inside the tab", async ({ page }) => { Then( "SBOM Creation date should be visible inside the tab", async ({ page }) => { - const panelSBOMVersion = page.locator( + const panelSBOMVersion = await page.locator( `xpath=//section[@id='refVulnerabilitiesSection']//dt[contains(.,'Creation date')]/following-sibling::dd` ); await panelSBOMVersion.isVisible(); @@ -179,23 +151,19 @@ Then( Then( "List of related Vulnerabilities should be sorted by {string} in descending order", async ({ page }, columnName) => { - const table = await Table.build(page, VULN_TABLE_NAME); - await table.verifyTableIsSortedBy(columnName, false); + const toolbarTable = new ToolbarTable(page, VULN_TABLE_NAME); + await toolbarTable.verifyTableIsSortedBy(columnName, false); } ); Then("Pagination of Vulnerabilities list works", async ({ page }) => { - const pagination = await Pagination.build( - page, - "vulnerability-table-pagination-top" - ); - await pagination.verifyPagination(); + const toolbarTable = new ToolbarTable(page, VULN_TABLE_NAME); + const vulnTableTopPagination = `xpath=//div[@id="vulnerability-table-pagination-top"]`; + await toolbarTable.verifyPagination(vulnTableTopPagination); }); Then("Pagination of Packages list works", async ({ page }) => { - const pagination = await Pagination.build( - page, - "package-table-pagination-top" - ); - await pagination.verifyPagination(); + const toolbarTable = new ToolbarTable(page, PACKAGE_TABLE_NAME); + const vulnTableTopPagination = `xpath=//div[@id="package-table-pagination-top"]`; + await toolbarTable.verifyPagination(vulnTableTopPagination); }); diff --git a/tests/ui/features/@vulnerability-explorer/vulnerability-explorer.step.ts b/tests/ui/features/@vulnerability-explorer/vulnerability-explorer.step.ts index 457fde9..4334a26 100644 --- a/tests/ui/features/@vulnerability-explorer/vulnerability-explorer.step.ts +++ b/tests/ui/features/@vulnerability-explorer/vulnerability-explorer.step.ts @@ -1,11 +1,7 @@ -import { expect } from "@playwright/test"; import { createBdd } from "playwright-bdd"; - -import { Navigation } from "../../helpers/Navigation"; -import { Pagination } from "../../helpers/Pagination"; -import { Table } from "../../helpers/Table"; -import { Toolbar } from "../../helpers/Toolbar"; -import { VulnerabilityListPage } from "../../helpers/Constants"; +import { ToolbarTable } from "../../helpers/ToolbarTable"; +import { SearchPage } from "../../helpers/SearchPage"; +import { expect } from "@playwright/test"; export const { Given, When, Then } = createBdd(); @@ -15,21 +11,8 @@ const ADVISORY_TABLE_NAME = "Advisory table"; Given( "User visits Vulnerability details Page of {string}", async ({ page }, vulnerabilityID) => { - const navigation = await Navigation.build(page); - await navigation.goToSidebar("Vulnerabilities"); - - const toolbar = await Toolbar.build( - page, - VulnerabilityListPage.tableAriaLabel - ); - await toolbar.applyTextFilter( - VulnerabilityListPage.filters.filterText, - vulnerabilityID - ); - - const table = await Table.build(page, "Vulnerability table"); - await table.verifyTableHasData(); - + const searchPage = new SearchPage(page); + await searchPage.dedicatedSearch("Vulnerabilities", vulnerabilityID); await page.getByRole("link", { name: vulnerabilityID }).click(); } ); @@ -86,39 +69,33 @@ Then( // SBOMS Then("The SBOMs table is sorted by {string}", async ({ page }, columnName) => { - const table = await Table.build(page, SBOM_TABLE_NAME); - await table.verifyTableIsSortedBy(columnName); + const toolbarTable = new ToolbarTable(page, SBOM_TABLE_NAME); + await toolbarTable.verifyTableIsSortedBy(columnName); }); Then( "The SBOMs table total results is {int}", async ({ page }, totalResults) => { - const pagination = await Pagination.build( - page, - "sbom-table-pagination-top" - ); - const total = await pagination.getTotalPagesFromNavigation(); - expect(total).toBe(totalResults); + const toolbarTable = new ToolbarTable(page, SBOM_TABLE_NAME); + await toolbarTable.verifyPaginationHasTotalResults(totalResults); } ); Then( "The SBOMs table total results is greather than {int}", async ({ page }, totalResults) => { - const pagination = await Pagination.build( - page, - "sbom-table-pagination-top" + const toolbarTable = new ToolbarTable(page, SBOM_TABLE_NAME); + await toolbarTable.verifyPaginationHasTotalResultsGreatherThan( + totalResults ); - const total = await pagination.getTotalPagesFromNavigation(); - expect(total).toBeGreaterThan(totalResults); } ); Then( "The {string} column of the SBOM table contains {string}", async ({ page }, columnName, expectedValue) => { - const table = await Table.build(page, SBOM_TABLE_NAME); - await table.verifyColumnContainsText(columnName, expectedValue); + const toolbarTable = new ToolbarTable(page, SBOM_TABLE_NAME); + await toolbarTable.verifyColumnContainsText(columnName, expectedValue); } ); @@ -131,39 +108,33 @@ Then("User selects the Tabs {string}", async ({ page }, tabName) => { Then( "The Advisory table is sorted by {string}", async ({ page }, columnName) => { - const table = await Table.build(page, ADVISORY_TABLE_NAME); - await table.verifyTableIsSortedBy(columnName); + const toolbarTable = new ToolbarTable(page, ADVISORY_TABLE_NAME); + await toolbarTable.verifyTableIsSortedBy(columnName); } ); Then( "The Advisory table total results is {int}", async ({ page }, totalResults) => { - const pagination = await Pagination.build( - page, - "advisory-table-pagination-top" - ); - const total = await pagination.getTotalPagesFromNavigation(); - expect(total).toBe(totalResults); + const toolbarTable = new ToolbarTable(page, ADVISORY_TABLE_NAME); + await toolbarTable.verifyPaginationHasTotalResults(totalResults); } ); Then( "The Advisory table total results is greather than {int}", async ({ page }, totalResults) => { - const pagination = await Pagination.build( - page, - "advisory-table-pagination-top" + const toolbarTable = new ToolbarTable(page, ADVISORY_TABLE_NAME); + await toolbarTable.verifyPaginationHasTotalResultsGreatherThan( + totalResults ); - const total = await pagination.getTotalPagesFromNavigation(); - expect(total).toBeGreaterThan(totalResults); } ); Then( "The {string} column of the Advisory table contains {string}", async ({ page }, columnName, expectedValue) => { - const table = await Table.build(page, ADVISORY_TABLE_NAME); - await table.verifyColumnContainsText(columnName, expectedValue); + const toolbarTable = new ToolbarTable(page, ADVISORY_TABLE_NAME); + await toolbarTable.verifyColumnContainsText(columnName, expectedValue); } ); diff --git a/tests/ui/helpers/DetailsPage.ts b/tests/ui/helpers/DetailsPage.ts index 8365e6d..4bb06e8 100644 --- a/tests/ui/helpers/DetailsPage.ts +++ b/tests/ui/helpers/DetailsPage.ts @@ -1,5 +1,9 @@ import { expect, Page } from "@playwright/test"; +/** + * Describes the Details of an Entity. E.g. SBOM Details Page. + * Generally based on https://www.patternfly.org/extensions/component-groups/details-page/ + */ export class DetailsPage { page: Page; @@ -36,4 +40,117 @@ export class DetailsPage { async verifyTabIsNotVisible(tabName: string) { await expect(this.page.getByRole("tab", { name: tabName })).toHaveCount(0); } + + //Wait for Loading Spinner to detach from the DOM + async waitForData() { + const spinner = this.page.locator(`xpath=(//table//tbody)[1]`); + await spinner.waitFor({ state: "visible", timeout: 5000 }); + } + + //Verifies the Page loads with data + async verifyDataAvailable() { + await expect( + this.page.locator( + `xpath=//div[(.='No data available to be shown here.')]` + ) + ).toHaveCount(0); + } + + //Verifies the Vulnerability counts from summary to table + async verifyVulnerabilityPanelcount() { + const pieVulnSevLabel = `xpath=//*[name()='svg']/*[name()='g']//*[name()='tspan']`; + const totalVuln = `xpath=//*[name()='svg']/*[name()='text']//*[name()='tspan'][1]`; + const totalVulnElement = this.page.locator(totalVuln); + await totalVulnElement.waitFor({ state: "visible", timeout: 5000 }); + const totalVulnPanel = await totalVulnElement.textContent(); + const panelVulnSev = await this.getCountFromLabels(pieVulnSevLabel, ":"); + const sumPanelVulnSev = await Object.values(panelVulnSev).reduce( + (sum, value) => sum + value, + 0 + ); + const tableVulnSev = await this.getCVSSCountFromVulnTable(); + let mismatch = false; + await expect( + parseInt(totalVulnPanel!, 10), + `Total Vulnerabilities count ${totalVulnPanel} mismatches with sum of individual ${sumPanelVulnSev}` + ).toEqual(sumPanelVulnSev); + + for (const severity in tableVulnSev) { + if (panelVulnSev[severity] !== undefined) { + if (panelVulnSev[severity] !== tableVulnSev[severity]) { + console.log( + `${severity} count mismatch. Summary panel count ${panelVulnSev[severity]}, Rows count ${tableVulnSev[severity]}` + ); + mismatch = true; + } + } + } + await expect(mismatch, "Panel count mismatches to table count").not.toBe( + true + ); + } + + /** + * Get all the Elements matching to the @param labelLocator and retrieves the textContext of each element + * Splits the text with @param delimiter + * @returns the mutable object { [key: 0th_element ]: 1st_element } + */ + async getCountFromLabels( + labelLocator: string, + delimiter: string + ): Promise<{ [key: string]: number }> { + let elements = await this.page.locator(labelLocator).all(); + let vulnLabelCount = {}; + for (let element of elements) { + let innerText = await element.textContent(); + let labelArr = await innerText!.split(delimiter); + vulnLabelCount[labelArr[0].trim().toString()] = parseInt( + labelArr[1].trim(), + 10 + ); + } + return vulnLabelCount; + } + + /** + * Retrieves the CVSS value from each row of Vulnerability table + * @returns Count of each CVSS type in { [key: severity ]: count } + */ + + async getCVSSCountFromVulnTable(): Promise<{ [key: string]: number }> { + let nextPage = true; + const counts = { + Unknown: 0, + None: 0, + Low: 0, + Medium: 0, + High: 0, + Critical: 0, + }; + const nextButton = await this.page.locator( + `xpath=(//section[@id='refVulnerabilitiesSection']//button[@data-action='next'])[1]` + ); + + const noOfRows = await this.page.locator( + `xpath=//section[@id="refVulnerabilitiesSection"]//button[@id="pagination-id-top-toggle"]` + ); + if (await noOfRows.isEnabled()) { + noOfRows.click(); + await this.page.getByRole("menuitem", { name: "100 per page" }).click(); + } + + while (nextPage) { + for (let cvssType in counts) { + let cvssLocator = await this.page + .locator(`xpath=//td[@data-label='CVSS']//div[.='${cvssType}']`) + .all(); + counts[cvssType] += await cvssLocator.length; + } + nextPage = await nextButton.isEnabled(); + if (nextPage) { + await nextButton.click(); + } + } + return counts; + } } diff --git a/tests/ui/helpers/Pagination.ts b/tests/ui/helpers/Pagination.ts deleted file mode 100644 index 50dfdd4..0000000 --- a/tests/ui/helpers/Pagination.ts +++ /dev/null @@ -1,158 +0,0 @@ -import { expect, Locator, Page } from "@playwright/test"; - -export class Pagination { - private _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); - } - - /** - * Retrieves the Total page count from pagination - * @returns total count from pagination text - */ - async getTotalNumberOfItems(): Promise { - const totalResultsText = await this._pagination - .locator(".pf-v6-c-pagination__total-items") - .locator("b") - .nth(1) - .textContent(); - return totalResultsText ? parseInt(totalResultsText) : null; - } - - /** - * Retrieves the Total page count from pagination - * @returns total count from pagination text - */ - async getTotalPagesFromNavigation(): Promise { - const navTotal = this._pagination.locator( - `xpath=//span[contains(@class,'form-control')]/following-sibling::span` - ); - const totalPages = await navTotal.textContent(); - return parseInt(totalPages!.replace("of", "").trim(), 10); - } - - /** - * Selects Number of rows per page on the table - * @param perPage Number of rows - */ - async selectPerPage(perPage: string) { - await this._pagination - .locator(`//button[@aria-haspopup='listbox']`) - .click(); - await this._page.getByRole("menuitem", { name: perPage }).click(); - } - - /** - * Verifies the pagination count against per page selection - * Example, SBOM Explorer - Vulnerabilities and Packages section top and bottom sections. - * Parent Element for Vulnerabilities section top Pagination `//div[@id="vulnerability-table-pagination-top"]` - * And bottom section `//div[@id="vulnerability-table-pagination-bottom"]` - */ - async verifyPagination() { - const perPageValues = [10, 20, 50, 100]; - const totalItems = await this.getTotalNumberOfItems(); - expect(totalItems).not.toBeNull(); - - for (const value of perPageValues) { - const firstPage = this._pagination.getByRole("button", { - name: "Go to first page", - }); - if (await firstPage.isEnabled()) { - await firstPage.click(); - } - let expectedPagecount = Math.trunc(totalItems! / value); - let remainingRows = totalItems! % value; - if (remainingRows > 0) { - expectedPagecount += 1; - } - await this.selectPerPage(value + " per page"); - - // Wait for page to load - await expect( - this._pagination.locator(".pf-v6-c-pagination__nav") - ).toContainText(`of ${expectedPagecount}`, { timeout: 15000 }); - - await this.navigateToEachPageVerifyRowsCount( - expectedPagecount, - value, - remainingRows - ); - } - } - - /** - * Verifies Number of rows on the table equals to or lesser than the row count given - * @param rowsCount Number of rows - */ - async verifyPerPageToRowCount(rowsCount: number) { - // Bug: https://issues.redhat.com/browse/TC-2353 - // expect(this._page.locator("table > tbody > tr").count()).toEqual(rowsCount); - } - - /** - * Navigates to Each page with Next button and verify the rows count - * @param pageCount Number of Pages expected - * @param perPageRows Number of rows expected per page - * @param remainingRows Number of rows in Last Page - */ - async navigateToEachPageVerifyRowsCount( - pageCount: number, - perPageRows: number, - remainingRows: number - ) { - const nextButton = this._pagination.getByLabel("Go to next page"); - let expMinCount = 1; - let expMaxCount = perPageRows; - if (pageCount === 1) { - expMaxCount = remainingRows; - } - for (let i = 1; i < pageCount; i++) { - // Wait for page to load - await expect( - this._pagination.locator("input[aria-label='Current page']") - ).toHaveValue(`${i}`, { timeout: 30000 }); - - await this.verifyRowsCounterPagination(expMinCount, expMaxCount); - await this.verifyPerPageToRowCount(perPageRows); - await nextButton.isEnabled(); - await nextButton.click(); - - expMinCount += perPageRows; - if (i === pageCount - 1) { - expMaxCount = expMaxCount + remainingRows; - } else { - expMaxCount += perPageRows; - } - } - if (remainingRows > 0) { - await this.verifyPerPageToRowCount(remainingRows); - await this.verifyRowsCounterPagination(expMinCount, expMaxCount); - } - await nextButton.isDisabled(); - } - - /** - * @param expMinCount Expected Min count on the counter - * @param expMaxCount Expected Max count on the counter - */ - async verifyRowsCounterPagination(expMinCount: number, expMaxCount: number) { - const pageCounter = this._pagination.locator( - `xpath=//button//b[contains (.,"-")]` - ); - const counterText = await pageCounter.textContent(); - let [min, max] = counterText! - .split("-") - .map((value) => parseInt(value.trim())); - expect(min).toEqual(expMinCount); - expect(max).toEqual(expMaxCount); - } -} diff --git a/tests/ui/helpers/SbomDetailsPage.ts b/tests/ui/helpers/SbomDetailsPage.ts deleted file mode 100644 index 108f90d..0000000 --- a/tests/ui/helpers/SbomDetailsPage.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { expect, Page } from "@playwright/test"; - -/** - * Describes the Details of an Entity. E.g. SBOM Details Page. - * Generally based on https://www.patternfly.org/extensions/component-groups/details-page/ - */ -export class SbomDetailsPage { - page: Page; - - constructor(page: Page) { - this.page = page; - } - - //Verifies the Vulnerability counts from summary to table - async verifyVulnerabilityPanelcount() { - const pieVulnSevLabel = `xpath=//*[name()='svg']/*[name()='g']//*[name()='tspan']`; - const totalVuln = `xpath=//*[name()='svg']/*[name()='text']//*[name()='tspan'][1]`; - const totalVulnElement = this.page.locator(totalVuln); - await totalVulnElement.waitFor({ state: "visible", timeout: 5000 }); - const totalVulnPanel = await totalVulnElement.textContent(); - const panelVulnSev = await this.getCountFromLabels(pieVulnSevLabel, ":"); - const sumPanelVulnSev = await Object.values(panelVulnSev).reduce( - (sum, value) => sum + value, - 0 - ); - const tableVulnSev = await this.getCVSSCountFromVulnTable(); - let mismatch = false; - await expect( - parseInt(totalVulnPanel!, 10), - `Total Vulnerabilities count ${totalVulnPanel} mismatches with sum of individual ${sumPanelVulnSev}` - ).toEqual(sumPanelVulnSev); - - for (const severity in tableVulnSev) { - if (panelVulnSev[severity] !== undefined) { - if (panelVulnSev[severity] !== tableVulnSev[severity]) { - console.log( - `${severity} count mismatch. Summary panel count ${panelVulnSev[severity]}, Rows count ${tableVulnSev[severity]}` - ); - mismatch = true; - } - } - } - await expect(mismatch, "Panel count mismatches to table count").not.toBe( - true - ); - } - - /** - * Get all the Elements matching to the @param labelLocator and retrieves the textContext of each element - * Splits the text with @param delimiter - * @returns the mutable object { [key: 0th_element ]: 1st_element } - */ - async getCountFromLabels( - labelLocator: string, - delimiter: string - ): Promise<{ [key: string]: number }> { - let elements = await this.page.locator(labelLocator).all(); - let vulnLabelCount = {}; - for (let element of elements) { - let innerText = await element.textContent(); - let labelArr = innerText!.split(delimiter); - vulnLabelCount[labelArr[0].trim().toString()] = parseInt( - labelArr[1].trim(), - 10 - ); - } - return vulnLabelCount; - } - - /** - * Retrieves the CVSS value from each row of Vulnerability table - * @returns Count of each CVSS type in { [key: severity ]: count } - */ - - async getCVSSCountFromVulnTable(): Promise<{ [key: string]: number }> { - let nextPage = true; - const counts = { - Unknown: 0, - None: 0, - Low: 0, - Medium: 0, - High: 0, - Critical: 0, - }; - const nextButton = this.page.locator( - `xpath=(//section[@id='refVulnerabilitiesSection']//button[@data-action='next'])[1]` - ); - - const noOfRows = this.page.locator( - `xpath=//section[@id="refVulnerabilitiesSection"]//button[@id="pagination-id-top-toggle"]` - ); - if (await noOfRows.isEnabled()) { - await noOfRows.click(); - await this.page.getByRole("menuitem", { name: "100 per page" }).click(); - } - - while (nextPage) { - for (let cvssType in counts) { - let cvssLocator = await this.page - .locator(`xpath=//td[@data-label='CVSS']//div[.='${cvssType}']`) - .all(); - counts[cvssType] += cvssLocator.length; - } - nextPage = await nextButton.isEnabled(); - if (nextPage) { - await nextButton.click(); - } - } - return counts; - } -} diff --git a/tests/ui/helpers/SearchPage.ts b/tests/ui/helpers/SearchPage.ts new file mode 100644 index 0000000..cf82a0d --- /dev/null +++ b/tests/ui/helpers/SearchPage.ts @@ -0,0 +1,27 @@ +import { expect, Page } from "@playwright/test"; +import { DetailsPage } from "./DetailsPage"; + +export class SearchPage { + page: Page; + + constructor(page: Page) { + this.page = page; + } + + /** + * Navigates to given menu option and filters data + * @param menu Option from Vertical navigation menu + * @param data Search data to filter + */ + async dedicatedSearch(menu: string, data: string) { + await this.page.goto("/"); + await this.page.getByRole("link", { name: menu }).click(); + const detailsPage = new DetailsPage(this.page); + await detailsPage.waitForData(); + await detailsPage.verifyDataAvailable(); + await this.page.getByPlaceholder("Search").click(); + await this.page.getByPlaceholder("Search").fill(data); + await this.page.getByPlaceholder("Search").press("Enter"); + await detailsPage.verifyDataAvailable(); + } +} diff --git a/tests/ui/helpers/Table.ts b/tests/ui/helpers/Table.ts deleted file mode 100644 index 692b011..0000000 --- a/tests/ui/helpers/Table.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { expect, Locator, Page } from "@playwright/test"; - -export class Table { - private _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 correspont 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(); - return new Table(page, table); - } - - 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 verifyTableHasData() { - const rows = this._table.locator( - 'xpath=//tbody[not(@aria-label="Table loading") and not(@aria-label="Table error") and not(@aria-label="Table empty")]' - ); - - // Wait for at least one to appear (or a specific one) - await expect(rows.first()).toBeVisible(); - - // Now safely count - const rowsCount = await rows.count(); - expect(rowsCount).toBeGreaterThanOrEqual(1); - } - - async verifyTableHasNoData() { - await expect( - this._table.locator(`tbody[aria-label="Table empty"]`) - ).toBeVisible(); - } -} diff --git a/tests/ui/helpers/ToolbarTable.ts b/tests/ui/helpers/ToolbarTable.ts new file mode 100644 index 0000000..49e51b3 --- /dev/null +++ b/tests/ui/helpers/ToolbarTable.ts @@ -0,0 +1,232 @@ +import { expect, Page } from "@playwright/test"; +export class ToolbarTable { + private _page: Page; + private _tableName: string; + + constructor(page: Page, tableName: string) { + this._page = page; + this._tableName = tableName; + } + + async verifyPaginationHasTotalResults(totalResults: number) { + const paginationTop = this._page.locator("#pagination-id-top-toggle"); + await expect(paginationTop.locator("b").nth(1)).toHaveText( + `${totalResults}` + ); + } + + async verifyPaginationHasTotalResultsGreatherThan( + totalResults: number, + include: boolean = false + ) { + const paginationTop = this._page.locator("#pagination-id-top-toggle"); + const totalResultsText = await paginationTop + .locator("b") + .nth(1) + .textContent(); + if (include) { + expect(Number(totalResultsText)).toBeGreaterThanOrEqual(totalResults); + } else { + expect(Number(totalResultsText)).toBeGreaterThan(totalResults); + } + } + + async filterByText(filterText: string) { + const input = this._page.locator("#search-input"); + await input.fill(filterText); + await input.press("Enter"); + } + + async verifyTableIsSortedBy(columnName: string, asc: boolean = true) { + const table = this.getTable(); + await expect( + table.getByRole("columnheader", { name: columnName }) + ).toHaveAttribute("aria-sort", asc ? "ascending" : "descending"); + } + + async verifyColumnContainsText(columnName: any, expectedValue: any) { + const table = this.getTable(); + await expect(table.locator(`td[data-label="${columnName}"]`)).toContainText( + expectedValue + ); + } + + /** + * Verifies the pagination count against per page selection + * @param parentElem required to identify the pagination across sections + * Example, SBOM Explorer - Vulnerabilities and Packages section top and bottom sections. + * Parent Element for Vulnerabilities section top Pagination `//div[@id="vulnerability-table-pagination-top"]` + * And bottom section `//div[@id="vulnerability-table-pagination-bottom"]` + */ + async verifyPagination(parentElem: string) { + const section = this._page.locator(parentElem); + const perPageValues = [10, 20, 50, 100]; + const totalRows = await this.getTotalRowsFromPagination(parentElem); + for (const value of perPageValues) { + const firstPage = section.getByRole("button", { + name: "Go to first page", + }); + if (await firstPage.isEnabled()) { + await firstPage.click(); + } + let expectedPagecount = Math.trunc(totalRows / value); + let remainingRows = totalRows % value; + if (remainingRows > 0) { + expectedPagecount += 1; + } + await this.selectPerPage(parentElem, value + " per page"); + const progressBar = this._page.getByRole("gridcell", { + name: "Loading...", + }); + await progressBar.waitFor({ state: "hidden", timeout: 5000 }); + const actualPageCount = + await this.getTotalPagesFromNavigation(parentElem); + await expect(actualPageCount, "Page count mismatches").toEqual( + expectedPagecount + ); + await this.navigateToEachPageVerifyRowsCount( + parentElem, + expectedPagecount, + value, + remainingRows + ); + } + } + + /** + * Retrieves the Total page count from pagination + * @param parentElem required to identify the pagination across sections + * @returns total count from pagination text + */ + async getTotalPagesFromNavigation(parentElem: string): Promise { + const section = this._page.locator(parentElem); + const navTotal = await section.locator( + `xpath=//span[contains(@class,'form-control')]/following-sibling::span` + ); + const totalPages = await navTotal.textContent(); + return parseInt(totalPages!.replace("of", "").trim(), 10); + } + + /** + * Retrieves the Total Row count from pagination + * @param parentElem required to differentiate top and bottom pagination + * @returns total row count from pagination dropdown + */ + async getTotalRowsFromPagination(parentElem: string): Promise { + const tableError = this._page.locator( + `xpath=(//tbody[@aria-label="Table error"])[1]` + ); + if (await tableError.isVisible()) { + await expect(tableError, "No Data available").not.toBeVisible(); + } + const progressBar = this._page.getByRole("gridcell", { + name: "Loading...", + }); + await progressBar.waitFor({ state: "hidden", timeout: 5000 }); + const pagination = this._page.locator(parentElem); + const totalResultsText = await pagination + .locator(`xpath=//button//b[not(contains (.,'-'))]`) + .textContent(); + return parseInt(totalResultsText!.trim(), 10); + } + + /** + * Selects Number of rows per page on the table + * @param perPage Number of rows + */ + async selectPerPage(parentElem: string, perPage: string) { + const pagination = this._page.locator(parentElem); + await pagination.locator(`//button[@aria-haspopup='listbox']`).click(); + await this._page.getByRole("menuitem", { name: perPage }).click(); + } + + /** + * Verifies Number of rows on the table equals to or lesser than the row count given + * @param rowsCount Number of rows + */ + async verifyPerPageToRowCount(rowsCount: number) { + const table = this.getTable(); + const rows = await table.locator(`xpath=//tbody/tr`); + const tabRows = await rows.count(); + // Bug: https://issues.redhat.com/browse/TC-2353 + await expect(tabRows).toEqual(rowsCount); + } + + /** + * Navigates to Each page with Next button and verify the rows count + * @param parentElem required to identify the pagination across sections + * @param pageCount Number of Pages expected + * @param perPageRows Number of rows expected per page + * @param remainingRows Number of rows in Last Page + */ + async navigateToEachPageVerifyRowsCount( + parentElem: string, + pageCount: number, + perPageRows: number, + remainingRows: number + ) { + const section = this._page.locator(parentElem); + const nextButton = await section.getByLabel("Go to next page"); + let expMinCount = 1; + let expMaxCount = perPageRows; + if (pageCount === 1) { + expMaxCount = remainingRows; + } + for (let i = 1; i < pageCount; i++) { + await this.verifyRowsCounterPagination( + parentElem, + expMinCount, + expMaxCount + ); + await this.verifyPerPageToRowCount(perPageRows); + await nextButton.isEnabled(); + await nextButton.click(); + const progressBar = this._page.getByRole("gridcell", { + name: "Loading...", + }); + await progressBar.waitFor({ state: "hidden", timeout: 5000 }); + expMinCount += perPageRows; + if (i === pageCount - 1) { + expMaxCount = expMaxCount + remainingRows; + } else { + expMaxCount += perPageRows; + } + } + if (remainingRows > 0) { + await this.verifyPerPageToRowCount(remainingRows); + await this.verifyRowsCounterPagination( + parentElem, + expMinCount, + expMaxCount + ); + } + await nextButton.isDisabled(); + } + + /** + * + * @param parentElem required to differentiate top and bottom pagination + * @param expMinCount Expected Min count on the counter + * @param expMaxCount Expected Max count on the counter + */ + async verifyRowsCounterPagination( + parentElem: string, + expMinCount: number, + expMaxCount: number + ) { + const pagination = this._page.locator(parentElem); + const pageCounter = await pagination.locator( + `xpath=//button//b[contains (.,"-")]` + ); + const counterText = await pageCounter.textContent(); + let [min, max] = counterText! + .split("-") + .map((value) => parseInt(value.trim())); + await expect(min).toEqual(expMinCount); + await expect(max).toEqual(expMaxCount); + } + + private getTable() { + return this._page.locator(`table[aria-label="${this._tableName}"]`); + } +} diff --git a/tests/ui/helpers/Constants.ts b/tests/ui/pages/Constants.ts similarity index 77% rename from tests/ui/helpers/Constants.ts rename to tests/ui/pages/Constants.ts index fe8ef6b..904bd0e 100644 --- a/tests/ui/helpers/Constants.ts +++ b/tests/ui/pages/Constants.ts @@ -29,3 +29,11 @@ export const VulnerabilityListPage = { filterText: "Filter text", }, }; + +export const isSorted = (arr: string[], asc: boolean) => { + let sorted = [...arr].sort((a, b) => a.localeCompare(b)); + if (!asc) { + sorted = sorted.reverse(); + } + return arr.every((val, i) => val === sorted[i]); +}; diff --git a/tests/ui/pages/DetailsPage.ts b/tests/ui/pages/DetailsPage.ts new file mode 100644 index 0000000..a457d34 --- /dev/null +++ b/tests/ui/pages/DetailsPage.ts @@ -0,0 +1,38 @@ +import { expect, Page } from "@playwright/test"; + +export class DetailsPage { + private _page: Page; + + constructor(page: Page) { + this._page = 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/helpers/Navigation.ts b/tests/ui/pages/Navigation.ts similarity index 100% rename from tests/ui/helpers/Navigation.ts rename to tests/ui/pages/Navigation.ts diff --git a/tests/ui/pages/Pagination.ts b/tests/ui/pages/Pagination.ts new file mode 100644 index 0000000..61bb7cd --- /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 _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..ae1c1f4 --- /dev/null +++ b/tests/ui/pages/Table.ts @@ -0,0 +1,104 @@ +import { expect, Locator, Page } from "@playwright/test"; + +type SortAria = "ascending" | "descending"; + +const getSortFromAria = (value: SortAria): boolean => { + return value === "ascending" ? true : false; +}; + +export class Table { + private _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 correspont 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) { + const column = this._table.getByRole("columnheader", { name: columnName }); + + await expect(column).toHaveAttribute("aria-sort"); + const currentSort = (await column.getAttribute("aria-sort")) as SortAria; + + // Apply sort + await this._table.getByRole("button", { name: columnName }).click(); + await this.verifyTableIsSortedBy(columnName, !getSortFromAria(currentSort)); + + 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/helpers/Toolbar.ts b/tests/ui/pages/Toolbar.ts similarity index 94% rename from tests/ui/helpers/Toolbar.ts rename to tests/ui/pages/Toolbar.ts index 04a52d5..2c0d151 100644 --- a/tests/ui/helpers/Toolbar.ts +++ b/tests/ui/pages/Toolbar.ts @@ -16,7 +16,7 @@ export class Toolbar { */ static async build(page: Page, toolbarAriaLabel: string) { const toolbar = page.locator(`[aria-label="${toolbarAriaLabel}"]`); - await expect(toolbar).toBeVisible({ timeout: 5000 }); + await expect(toolbar).toBeVisible(); return new Toolbar(page, toolbar); } @@ -25,7 +25,9 @@ export class Toolbar { * @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._toolbar + .locator(".pf-m-toggle-group button.pf-v6-c-menu-toggle") + .click(); await this._page.getByRole("menuitem", { name: filterName }).click(); } 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..e9e3ef8 --- /dev/null +++ b/tests/ui/pages/sbom-list/actions.spec.ts @@ -0,0 +1,107 @@ +// @ts-check + +import { expect, test } from "@playwright/test"; + +import { login } from "../../helpers/Auth"; +import { SBOMListPage } from "../Constants"; +import { Navigation } from "../Navigation"; +import { Table } from "../Table"; + +test.describe("Action validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + + const navigation = await Navigation.build(page); + await navigation.goToSidebar("SBOMs"); + }); + + test("Actions - Download SBOM", async ({ page }) => { + const table = await Table.build(page, SBOMListPage.tableAriaLabel); + + 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 table = await Table.build(page, SBOMListPage.tableAriaLabel); + + 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 labels = ["color=red", "production"]; + + const table = await Table.build(page, SBOMListPage.tableAriaLabel); + await table.clickAction("Edit labels", 0); + + // Verify Modal is open + const dialog = page.getByRole("dialog"); + expect(dialog).toBeVisible(); + + const saveLabels = async () => { + await dialog.locator("button[aria-label='submit']").click(); + await expect(dialog).not.toBeVisible(); + }; + + // Add labels + const inputText = dialog.getByPlaceholder("Add label"); + + for (const label of labels) { + await inputText.click(); + await inputText.fill(label); + await inputText.press("Enter"); + } + + await saveLabels(); + + // 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); + + for (const label of labels) { + await dialog + .locator(".pf-v6-c-label-group__list-item", { + hasText: label, + }) + .locator("button") + .click(); + } + + await saveLabels(); + 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..d833900 --- /dev/null +++ b/tests/ui/pages/sbom-list/columns.spec.ts @@ -0,0 +1,64 @@ +// @ts-check + +import { expect, test } from "@playwright/test"; + +import { login } from "../../helpers/Auth"; +import { SBOMListPage } from "../Constants"; +import { Navigation } from "../Navigation"; +import { Table } from "../Table"; +import { Toolbar } from "../Toolbar"; + +test.describe("Columns validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + + const navigation = await Navigation.build(page); + await navigation.goToSidebar("SBOMs"); + }); + + test("Vulnerabilities", async ({ page }) => { + const toolbar = await Toolbar.build(page, SBOMListPage.toolbarAriaLabel); + const table = await Table.build(page, SBOMListPage.tableAriaLabel); + + // Full search + await toolbar.applyTextFilter( + SBOMListPage.filters.filterText, + "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 index e4ed171..550ae8e 100644 --- a/tests/ui/pages/sbom-list/filter.spec.ts +++ b/tests/ui/pages/sbom-list/filter.spec.ts @@ -3,25 +3,26 @@ import { test } from "@playwright/test"; import { login } from "../../helpers/Auth"; -import { SBOMListPage } from "../../helpers/Constants"; -import { Navigation } from "../../helpers/Navigation"; -import { Table } from "../../helpers/Table"; -import { Toolbar } from "../../helpers/Toolbar"; +import { SBOMListPage } from "../Constants"; +import { Navigation } from "../Navigation"; +import { Table } from "../Table"; +import { Toolbar } from "../Toolbar"; test.describe("Filter validations", { tag: "@tier1" }, () => { test.beforeEach(async ({ page }) => { await login(page); - }); - test("Filters", async ({ page }) => { const navigation = await Navigation.build(page); await navigation.goToSidebar("SBOMs"); + }); + test("Filters", async ({ page }) => { const toolbar = await Toolbar.build(page, SBOMListPage.toolbarAriaLabel); const table = await Table.build(page, SBOMListPage.tableAriaLabel); // Full search await toolbar.applyTextFilter(SBOMListPage.filters.filterText, "quarkus"); + await table.waitUntilDataIsLoaded(); await table.verifyColumnContainsText("Name", "quarkus-bom"); // Date filter @@ -30,10 +31,12 @@ test.describe("Filter validations", { tag: "@tier1" }, () => { "11/21/2023", "11/23/2023" ); + await table.waitUntilDataIsLoaded(); await table.verifyColumnContainsText("Name", "quarkus-bom"); // Labels filter await toolbar.applyLabelsFilter(SBOMListPage.filters.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..43b006a --- /dev/null +++ b/tests/ui/pages/sbom-list/pagination.spec.ts @@ -0,0 +1,36 @@ +// @ts-check + +import { test } from "@playwright/test"; + +import { login } from "../../helpers/Auth"; +import { Navigation } from "../Navigation"; +import { SBOMListPage } from "../Constants"; +import { Table } from "../Table"; +import { Pagination } from "../Pagination"; + +test.describe("Pagination validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + + const navigation = await Navigation.build(page); + await navigation.goToSidebar("SBOMs"); + }); + + test("Navigation button validations", async ({ page }) => { + const pagination = await Pagination.build( + page, + SBOMListPage.paginationIdTop + ); + await pagination.validatePagination(); + }); + + test("Items per page validations", async ({ page }) => { + const pagination = await Pagination.build( + page, + SBOMListPage.paginationIdTop + ); + + const table = await Table.build(page, SBOMListPage.tableAriaLabel); + 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..d7702d2 --- /dev/null +++ b/tests/ui/pages/sbom-list/sort.spec.ts @@ -0,0 +1,31 @@ +// @ts-check + +import { expect, test } from "@playwright/test"; + +import { login } from "../../helpers/Auth"; +import { Navigation } from "../Navigation"; +import { isSorted, SBOMListPage } from "../Constants"; +import { Table } from "../Table"; + +test.describe("Sort validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + + const navigation = await Navigation.build(page); + await navigation.goToSidebar("SBOMs"); + }); + + test("Sort", async ({ page }) => { + const table = await Table.build(page, SBOMListPage.tableAriaLabel); + const columnNameSelector = table._table.locator(`td[data-label="Name"]`); + + const namesAsc = await columnNameSelector.allInnerTexts(); + expect(isSorted(namesAsc, true)).toBe(true); + + // Reverse sorting + await table.clickSortBy("Name"); + const namesDesc = await columnNameSelector.allInnerTexts(); + + expect(isSorted(namesDesc, false)).toBe(true); + }); +}); From 59907cfbd1f8acdb426c66837a44c656b0e84622 Mon Sep 17 00:00:00 2001 From: Carlos Feria <2582866+carlosthe19916@users.noreply.github.com> Date: Fri, 27 Jun 2025 11:10:23 +0200 Subject: [PATCH 07/23] add playwright vanilla to executions Signed-off-by: Carlos Feria <2582866+carlosthe19916@users.noreply.github.com> --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index ddfb976..0cc5b83 100644 --- a/package.json +++ b/package.json @@ -6,10 +6,10 @@ "scripts": { "openapi": "openapi-ts -f ./config/openapi-ts.config.ts", "codegen": "npx playwright codegen http://localhost:8080/", - "test": "npx bddgen && npx playwright test --project='api' --project='chromium'", + "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'", - "test:ui:trace": "npx bddgen && npx playwright test --project='chromium' --trace on", + "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", From 83f1121ea66d9c5239e609ff71d8ec8fa07bb2d1 Mon Sep 17 00:00:00 2001 From: Carlos Feria <2582866+carlosthe19916@users.noreply.github.com> Date: Fri, 27 Jun 2025 12:03:12 +0200 Subject: [PATCH 08/23] add vulnerability list tests Signed-off-by: Carlos Feria <2582866+carlosthe19916@users.noreply.github.com> --- tests/ui/pages/Constants.ts | 4 +- tests/ui/pages/Table.ts | 8 --- tests/ui/pages/Toolbar.ts | 18 +++++++ .../pages/vulnerability-list/columns.spec.ts | 39 ++++++++++++++ .../pages/vulnerability-list/filter.spec.ts | 51 +++++++++++++++++++ .../vulnerability-list/pagination.spec.ts | 36 +++++++++++++ .../ui/pages/vulnerability-list/sort.spec.ts | 32 ++++++++++++ 7 files changed, 179 insertions(+), 9 deletions(-) create mode 100644 tests/ui/pages/vulnerability-list/columns.spec.ts create mode 100644 tests/ui/pages/vulnerability-list/filter.spec.ts create mode 100644 tests/ui/pages/vulnerability-list/pagination.spec.ts create mode 100644 tests/ui/pages/vulnerability-list/sort.spec.ts diff --git a/tests/ui/pages/Constants.ts b/tests/ui/pages/Constants.ts index 904bd0e..e748efb 100644 --- a/tests/ui/pages/Constants.ts +++ b/tests/ui/pages/Constants.ts @@ -22,11 +22,13 @@ export const SBOMListPage = { export const VulnerabilityListPage = { toolbarAriaLabel: "vulnerability-toolbar", - tableAriaLabel: "vulnerability-table", + tableAriaLabel: "Vulnerability table", paginationIdTop: "vulnerability-table-pagination-top", paginationIdBottom: "vulnerability-table-pagination-bottom", filters: { filterText: "Filter text", + severity: "CVSS", + createdOn: "Created on", }, }; diff --git a/tests/ui/pages/Table.ts b/tests/ui/pages/Table.ts index ae1c1f4..696285f 100644 --- a/tests/ui/pages/Table.ts +++ b/tests/ui/pages/Table.ts @@ -40,15 +40,7 @@ export class Table { } async clickSortBy(columnName: string) { - const column = this._table.getByRole("columnheader", { name: columnName }); - - await expect(column).toHaveAttribute("aria-sort"); - const currentSort = (await column.getAttribute("aria-sort")) as SortAria; - - // Apply sort await this._table.getByRole("button", { name: columnName }).click(); - await this.verifyTableIsSortedBy(columnName, !getSortFromAria(currentSort)); - await this.waitUntilDataIsLoaded(); } diff --git a/tests/ui/pages/Toolbar.ts b/tests/ui/pages/Toolbar.ts index 2c0d151..04fb18b 100644 --- a/tests/ui/pages/Toolbar.ts +++ b/tests/ui/pages/Toolbar.ts @@ -73,6 +73,24 @@ export class Toolbar { 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); 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..03756e0 --- /dev/null +++ b/tests/ui/pages/vulnerability-list/columns.spec.ts @@ -0,0 +1,39 @@ +// @ts-check + +import { expect, test } from "@playwright/test"; + +import { login } from "../../helpers/Auth"; +import { VulnerabilityListPage } from "../Constants"; +import { Navigation } from "../Navigation"; +import { Table } from "../Table"; +import { Toolbar } from "../Toolbar"; + +test.describe("Columns validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + + const navigation = await Navigation.build(page); + await navigation.goToSidebar("Vulnerabilities"); + }); + + test("Impacted SBOMs", async ({ page }) => { + const toolbar = await Toolbar.build( + page, + VulnerabilityListPage.toolbarAriaLabel + ); + const table = await Table.build(page, VulnerabilityListPage.tableAriaLabel); + + // Full search + await toolbar.applyTextFilter( + VulnerabilityListPage.filters.filterText, + "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..ad283b3 --- /dev/null +++ b/tests/ui/pages/vulnerability-list/filter.spec.ts @@ -0,0 +1,51 @@ +// @ts-check + +import { test } from "@playwright/test"; + +import { login } from "../../helpers/Auth"; +import { VulnerabilityListPage } from "../Constants"; +import { Navigation } from "../Navigation"; +import { Table } from "../Table"; +import { Toolbar } from "../Toolbar"; + +test.describe("Filter validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + + const navigation = await Navigation.build(page); + await navigation.goToSidebar("Vulnerabilities"); + }); + + test("Filters", async ({ page }) => { + const toolbar = await Toolbar.build( + page, + VulnerabilityListPage.toolbarAriaLabel + ); + const table = await Table.build(page, VulnerabilityListPage.tableAriaLabel); + + // Full search + await toolbar.applyTextFilter( + VulnerabilityListPage.filters.filterText, + "CVE-2024-26308" + ); + await table.waitUntilDataIsLoaded(); + await table.verifyColumnContainsText("ID", "CVE-2024-26308"); + + // Severity filter + await toolbar.applyMultiSelectFilter( + VulnerabilityListPage.filters.severity, + ["Unknown", "None"] + ); + await table.waitUntilDataIsLoaded(); + await table.verifyColumnContainsText("ID", "CVE-2024-26308"); + + // Date filter + await toolbar.applyDateRangeFilter( + VulnerabilityListPage.filters.createdOn, + "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..a9af48a --- /dev/null +++ b/tests/ui/pages/vulnerability-list/pagination.spec.ts @@ -0,0 +1,36 @@ +// @ts-check + +import { test } from "@playwright/test"; + +import { login } from "../../helpers/Auth"; +import { VulnerabilityListPage } from "../Constants"; +import { Navigation } from "../Navigation"; +import { Pagination } from "../Pagination"; +import { Table } from "../Table"; + +test.describe("Pagination validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + + const navigation = await Navigation.build(page); + await navigation.goToSidebar("Vulnerabilities"); + }); + + test("Navigation button validations", async ({ page }) => { + const pagination = await Pagination.build( + page, + VulnerabilityListPage.paginationIdTop + ); + await pagination.validatePagination(); + }); + + test("Items per page validations", async ({ page }) => { + const pagination = await Pagination.build( + page, + VulnerabilityListPage.paginationIdTop + ); + + const table = await Table.build(page, VulnerabilityListPage.tableAriaLabel); + 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..a37f093 --- /dev/null +++ b/tests/ui/pages/vulnerability-list/sort.spec.ts @@ -0,0 +1,32 @@ +// @ts-check + +import { expect, test } from "@playwright/test"; + +import { login } from "../../helpers/Auth"; +import { Navigation } from "../Navigation"; +import { isSorted, VulnerabilityListPage } from "../Constants"; +import { Table } from "../Table"; + +test.describe("Sort validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + + const navigation = await Navigation.build(page); + await navigation.goToSidebar("Vulnerabilities"); + }); + + test("Sort", async ({ page }) => { + const table = await Table.build(page, VulnerabilityListPage.tableAriaLabel); + const columnNameSelector = table._table.locator(`td[data-label="ID"]`); + + // ID Asc + await table.clickSortBy("ID"); + const namesAsc = await columnNameSelector.allInnerTexts(); + expect(isSorted(namesAsc, true)).toBe(true); + + // ID Desc + await table.clickSortBy("ID"); + const namesDesc = await columnNameSelector.allInnerTexts(); + expect(isSorted(namesDesc, false)).toBe(true); + }); +}); From 8a478ce6e63dee8117658f4cb8a06f52a1b1783b Mon Sep 17 00:00:00 2001 From: Carlos Feria <2582866+carlosthe19916@users.noreply.github.com> Date: Fri, 27 Jun 2025 13:47:31 +0200 Subject: [PATCH 09/23] add package list tests Signed-off-by: Carlos Feria <2582866+carlosthe19916@users.noreply.github.com> --- tests/ui/helpers/Auth.ts | 6 +- tests/ui/pages/package-list/columns.spec.ts | 62 +++++++++++++++++++ tests/ui/pages/package-list/filter.spec.ts | 47 ++++++++++++++ .../ui/pages/package-list/pagination.spec.ts | 36 +++++++++++ tests/ui/pages/package-list/sort.spec.ts | 31 ++++++++++ 5 files changed, 179 insertions(+), 3 deletions(-) create mode 100644 tests/ui/pages/package-list/columns.spec.ts create mode 100644 tests/ui/pages/package-list/filter.spec.ts create mode 100644 tests/ui/pages/package-list/pagination.spec.ts create mode 100644 tests/ui/pages/package-list/sort.spec.ts 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/pages/package-list/columns.spec.ts b/tests/ui/pages/package-list/columns.spec.ts new file mode 100644 index 0000000..d5d9940 --- /dev/null +++ b/tests/ui/pages/package-list/columns.spec.ts @@ -0,0 +1,62 @@ +// @ts-check + +import { expect, test } from "@playwright/test"; + +import { login } from "../../helpers/Auth"; +import { PackageListPage } from "../Constants"; +import { Navigation } from "../Navigation"; +import { Table } from "../Table"; +import { Toolbar } from "../Toolbar"; + +test.describe("Columns validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + + const navigation = await Navigation.build(page); + await navigation.goToSidebar("Packages"); + }); + + test("Columns", async ({ page }) => { + const toolbar = await Toolbar.build(page, PackageListPage.toolbarAriaLabel); + const table = await Table.build(page, PackageListPage.tableAriaLabel); + + // Full search + await toolbar.applyTextFilter( + PackageListPage.filters.filterText, + "keycloak-core" + ); + await table.waitUntilDataIsLoaded(); + await table.verifyColumnContainsText("Name", "keycloak-core"); + + await expect( + table._table.locator(`td[data-label="Namespace"]`) + ).toContainText("org.keycloak"); + + await expect( + table._table.locator(`td[data-label="Version"]`) + ).toContainText("18.0.6.redhat-00001"); + + await expect(table._table.locator(`td[data-label="Type"]`)).toContainText( + "maven" + ); + + 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..5763ebb --- /dev/null +++ b/tests/ui/pages/package-list/filter.spec.ts @@ -0,0 +1,47 @@ +// @ts-check + +import { test } from "@playwright/test"; + +import { login } from "../../helpers/Auth"; +import { PackageListPage } from "../Constants"; +import { Navigation } from "../Navigation"; +import { Table } from "../Table"; +import { Toolbar } from "../Toolbar"; + +test.describe("Filter validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + + const navigation = await Navigation.build(page); + await navigation.goToSidebar("Packages"); + }); + + test("Filters", async ({ page }) => { + const toolbar = await Toolbar.build(page, PackageListPage.toolbarAriaLabel); + const table = await Table.build(page, PackageListPage.tableAriaLabel); + + // Full search + await toolbar.applyTextFilter( + PackageListPage.filters.filterText, + "keycloak-core" + ); + await table.waitUntilDataIsLoaded(); + await table.verifyColumnContainsText("Name", "keycloak-core"); + + // Type filter + await toolbar.applyMultiSelectFilter(PackageListPage.filters.type, [ + "Maven", + "RPM", + ]); + await table.waitUntilDataIsLoaded(); + await table.verifyColumnContainsText("Name", "keycloak-core"); + + // Architecture + await toolbar.applyMultiSelectFilter(PackageListPage.filters.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..1830db9 --- /dev/null +++ b/tests/ui/pages/package-list/pagination.spec.ts @@ -0,0 +1,36 @@ +// @ts-check + +import { test } from "@playwright/test"; + +import { login } from "../../helpers/Auth"; +import { PackageListPage } from "../Constants"; +import { Navigation } from "../Navigation"; +import { Pagination } from "../Pagination"; +import { Table } from "../Table"; + +test.describe("Pagination validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + + const navigation = await Navigation.build(page); + await navigation.goToSidebar("Packages"); + }); + + test("Navigation button validations", async ({ page }) => { + const pagination = await Pagination.build( + page, + PackageListPage.paginationIdTop + ); + await pagination.validatePagination(); + }); + + test("Items per page validations", async ({ page }) => { + const pagination = await Pagination.build( + page, + PackageListPage.paginationIdTop + ); + + const table = await Table.build(page, PackageListPage.tableAriaLabel); + 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..35f3f9c --- /dev/null +++ b/tests/ui/pages/package-list/sort.spec.ts @@ -0,0 +1,31 @@ +// @ts-check + +import { expect, test } from "@playwright/test"; + +import { login } from "../../helpers/Auth"; +import { Navigation } from "../Navigation"; +import { isSorted, PackageListPage } from "../Constants"; +import { Table } from "../Table"; + +test.describe("Sort validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + + const navigation = await Navigation.build(page); + await navigation.goToSidebar("Packages"); + }); + + test("Sort", async ({ page }) => { + const table = await Table.build(page, PackageListPage.tableAriaLabel); + const columnNameSelector = table._table.locator(`td[data-label="ID"]`); + + // ID Asc + const namesAsc = await columnNameSelector.allInnerTexts(); + expect(isSorted(namesAsc, true)).toBe(true); + + // ID Desc + await table.clickSortBy("Name"); + const namesDesc = await columnNameSelector.allInnerTexts(); + expect(isSorted(namesDesc, false)).toBe(true); + }); +}); From 5f22c2b5011e443f29cbe0aa9fe9ce26263145dc Mon Sep 17 00:00:00 2001 From: Carlos Feria <2582866+carlosthe19916@users.noreply.github.com> Date: Fri, 27 Jun 2025 13:47:59 +0200 Subject: [PATCH 10/23] add package list tests Signed-off-by: Carlos Feria <2582866+carlosthe19916@users.noreply.github.com> --- tests/ui/pages/Constants.ts | 12 ++++++++++++ tests/ui/pages/Table.ts | 4 +++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/tests/ui/pages/Constants.ts b/tests/ui/pages/Constants.ts index e748efb..df061fa 100644 --- a/tests/ui/pages/Constants.ts +++ b/tests/ui/pages/Constants.ts @@ -32,6 +32,18 @@ export const VulnerabilityListPage = { }, }; +export const PackageListPage = { + toolbarAriaLabel: "package-toolbar", + tableAriaLabel: "Package table", + paginationIdTop: "package-table-pagination-top", + paginationIdBottom: "package-table-pagination-bottom", + filters: { + filterText: "Filter text", + type: "Type", + architecture: "Architecture", + }, +}; + export const isSorted = (arr: string[], asc: boolean) => { let sorted = [...arr].sort((a, b) => a.localeCompare(b)); if (!asc) { diff --git a/tests/ui/pages/Table.ts b/tests/ui/pages/Table.ts index 696285f..ea203a7 100644 --- a/tests/ui/pages/Table.ts +++ b/tests/ui/pages/Table.ts @@ -40,7 +40,9 @@ export class Table { } async clickSortBy(columnName: string) { - await this._table.getByRole("button", { name: columnName }).click(); + await this._table + .getByRole("button", { name: columnName, exact: true }) + .click(); await this.waitUntilDataIsLoaded(); } From 594884888ccfa3d9a45ee022c53b571f59232955 Mon Sep 17 00:00:00 2001 From: Carlos Feria <2582866+carlosthe19916@users.noreply.github.com> Date: Fri, 27 Jun 2025 14:08:40 +0200 Subject: [PATCH 11/23] add advisory tests Signed-off-by: Carlos Feria <2582866+carlosthe19916@users.noreply.github.com> --- tests/ui/pages/advisory-list/columns.spec.ts | 46 +++++++++++++++++ tests/ui/pages/advisory-list/filter.spec.ts | 50 +++++++++++++++++++ .../ui/pages/advisory-list/pagination.spec.ts | 36 +++++++++++++ tests/ui/pages/advisory-list/sort.spec.ts | 33 ++++++++++++ 4 files changed, 165 insertions(+) create mode 100644 tests/ui/pages/advisory-list/columns.spec.ts create mode 100644 tests/ui/pages/advisory-list/filter.spec.ts create mode 100644 tests/ui/pages/advisory-list/pagination.spec.ts create mode 100644 tests/ui/pages/advisory-list/sort.spec.ts 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..a02d078 --- /dev/null +++ b/tests/ui/pages/advisory-list/columns.spec.ts @@ -0,0 +1,46 @@ +// @ts-check + +import { expect, test } from "@playwright/test"; + +import { login } from "../../helpers/Auth"; +import { AdvisoryListPage } from "../Constants"; +import { Navigation } from "../Navigation"; +import { Table } from "../Table"; +import { Toolbar } from "../Toolbar"; + +test.describe("Columns validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + + const navigation = await Navigation.build(page); + await navigation.goToSidebar("Advisories"); + }); + + test("Columns", async ({ page }) => { + const toolbar = await Toolbar.build( + page, + AdvisoryListPage.toolbarAriaLabel + ); + const table = await Table.build(page, AdvisoryListPage.tableAriaLabel); + + // Full search + await toolbar.applyTextFilter( + AdvisoryListPage.filters.filterText, + "CVE-2024-26308" + ); + await table.waitUntilDataIsLoaded(); + await table.verifyColumnContainsText("ID", "CVE-2024-26308"); + + await expect(table._table.locator(`td[data-label="Title"]`)).toContainText( + "Apache Commons Compress: OutOfMemoryError unpacking broken Pack200 file" + ); + + await expect(table._table.locator(`td[data-label="Type"]`)).toContainText( + "cve" + ); + + 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..9e435bd --- /dev/null +++ b/tests/ui/pages/advisory-list/filter.spec.ts @@ -0,0 +1,50 @@ +// @ts-check + +import { test } from "@playwright/test"; + +import { login } from "../../helpers/Auth"; +import { AdvisoryListPage } from "../Constants"; +import { Navigation } from "../Navigation"; +import { Table } from "../Table"; +import { Toolbar } from "../Toolbar"; + +test.describe("Filter validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + + const navigation = await Navigation.build(page); + await navigation.goToSidebar("Advisories"); + }); + + test("Filters", async ({ page }) => { + const toolbar = await Toolbar.build( + page, + AdvisoryListPage.toolbarAriaLabel + ); + const table = await Table.build(page, AdvisoryListPage.tableAriaLabel); + + // Full search + await toolbar.applyTextFilter( + AdvisoryListPage.filters.filterText, + "CVE-2024-26308" + ); + await table.waitUntilDataIsLoaded(); + await table.verifyColumnContainsText("ID", "CVE-2024-26308"); + + // Date filter + await toolbar.applyDateRangeFilter( + AdvisoryListPage.filters.revision, + "08/01/2024", + "08/03/2024" + ); + await table.waitUntilDataIsLoaded(); + await table.verifyColumnContainsText("ID", "CVE-2024-26308"); + + // Labels filter + await toolbar.applyLabelsFilter(AdvisoryListPage.filters.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..dd7e8a0 --- /dev/null +++ b/tests/ui/pages/advisory-list/pagination.spec.ts @@ -0,0 +1,36 @@ +// @ts-check + +import { test } from "@playwright/test"; + +import { login } from "../../helpers/Auth"; +import { AdvisoryListPage } from "../Constants"; +import { Navigation } from "../Navigation"; +import { Pagination } from "../Pagination"; +import { Table } from "../Table"; + +test.describe("Pagination validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + + const navigation = await Navigation.build(page); + await navigation.goToSidebar("Advisories"); + }); + + test("Navigation button validations", async ({ page }) => { + const pagination = await Pagination.build( + page, + AdvisoryListPage.paginationIdTop + ); + await pagination.validatePagination(); + }); + + test("Items per page validations", async ({ page }) => { + const pagination = await Pagination.build( + page, + AdvisoryListPage.paginationIdTop + ); + + const table = await Table.build(page, AdvisoryListPage.tableAriaLabel); + 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..1deb2e7 --- /dev/null +++ b/tests/ui/pages/advisory-list/sort.spec.ts @@ -0,0 +1,33 @@ +// @ts-check + +import { expect, test } from "@playwright/test"; + +import { login } from "../../helpers/Auth"; +import { Navigation } from "../Navigation"; +import { isSorted, AdvisoryListPage } from "../Constants"; +import { Table } from "../Table"; + +test.describe("Sort validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + + const navigation = await Navigation.build(page); + await navigation.goToSidebar("Advisories"); + }); + + // skipped until it is fixed in the backend. It is bug, fix it + test.skip("Sort", async ({ page }) => { + const table = await Table.build(page, AdvisoryListPage.tableAriaLabel); + const columnNameSelector = table._table.locator(`td[data-label="ID"]`); + + // ID Asc + await table.clickSortBy("ID"); + const namesAsc = await columnNameSelector.allInnerTexts(); + expect(isSorted(namesAsc, true), "").toBe(true); + + // ID Desc + await table.clickSortBy("ID"); + const namesDesc = await columnNameSelector.allInnerTexts(); + expect(isSorted(namesDesc, false)).toBe(true); + }); +}); From ecf3f2380c71dc14decaac98b33a9044911c595c Mon Sep 17 00:00:00 2001 From: Carlos Feria <2582866+carlosthe19916@users.noreply.github.com> Date: Fri, 27 Jun 2025 14:13:48 +0200 Subject: [PATCH 12/23] add advisory tests Signed-off-by: Carlos Feria <2582866+carlosthe19916@users.noreply.github.com> --- tests/ui/pages/Constants.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/ui/pages/Constants.ts b/tests/ui/pages/Constants.ts index df061fa..006a9c4 100644 --- a/tests/ui/pages/Constants.ts +++ b/tests/ui/pages/Constants.ts @@ -44,6 +44,18 @@ export const PackageListPage = { }, }; +export const AdvisoryListPage = { + toolbarAriaLabel: "advisory-toolbar", + tableAriaLabel: "advisory-table", + paginationIdTop: "advisory-table-pagination-top", + paginationIdBottom: "advisory-table-pagination-bottom", + filters: { + filterText: "Filter text", + revision: "Revision", + label: "Label", + }, +}; + export const isSorted = (arr: string[], asc: boolean) => { let sorted = [...arr].sort((a, b) => a.localeCompare(b)); if (!asc) { From cd80755150ccf79d2596a9f53c74c2ebadb83a5d Mon Sep 17 00:00:00 2001 From: Carlos Feria <2582866+carlosthe19916@users.noreply.github.com> Date: Fri, 27 Jun 2025 16:04:04 +0200 Subject: [PATCH 13/23] add sbom info tab tests Signed-off-by: Carlos Feria <2582866+carlosthe19916@users.noreply.github.com> --- .../{DetailsPage.ts => DetailsPageLayout.ts} | 12 +++-- tests/ui/pages/LabelsModal.ts | 47 +++++++++++++++++++ .../ui/pages/sbom-details/SbomDetailsPage.ts | 35 ++++++++++++++ tests/ui/pages/sbom-details/actions.spec.ts | 32 +++++++++++++ tests/ui/pages/sbom-details/info/info.spec.ts | 44 +++++++++++++++++ tests/ui/pages/sbom-list/actions.spec.ts | 34 +++----------- 6 files changed, 174 insertions(+), 30 deletions(-) rename tests/ui/pages/{DetailsPage.ts => DetailsPageLayout.ts} (76%) create mode 100644 tests/ui/pages/LabelsModal.ts create mode 100644 tests/ui/pages/sbom-details/SbomDetailsPage.ts create mode 100644 tests/ui/pages/sbom-details/actions.spec.ts create mode 100644 tests/ui/pages/sbom-details/info/info.spec.ts diff --git a/tests/ui/pages/DetailsPage.ts b/tests/ui/pages/DetailsPageLayout.ts similarity index 76% rename from tests/ui/pages/DetailsPage.ts rename to tests/ui/pages/DetailsPageLayout.ts index a457d34..c758d46 100644 --- a/tests/ui/pages/DetailsPage.ts +++ b/tests/ui/pages/DetailsPageLayout.ts @@ -1,12 +1,18 @@ -import { expect, Page } from "@playwright/test"; +import { expect, Locator, Page } from "@playwright/test"; -export class DetailsPage { +export class DetailsPageLayout { private _page: Page; + _detailsPage: Locator; - constructor(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(); diff --git a/tests/ui/pages/LabelsModal.ts b/tests/ui/pages/LabelsModal.ts new file mode 100644 index 0000000..34120be --- /dev/null +++ b/tests/ui/pages/LabelsModal.ts @@ -0,0 +1,47 @@ +import { Locator, Page } from "playwright-core"; +import { expect } from "playwright/test"; + +/** + * Used to navigate to different pages + */ +export class LabelsModal { + private _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"); + 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/sbom-details/SbomDetailsPage.ts b/tests/ui/pages/sbom-details/SbomDetailsPage.ts new file mode 100644 index 0000000..b4dff85 --- /dev/null +++ b/tests/ui/pages/sbom-details/SbomDetailsPage.ts @@ -0,0 +1,35 @@ +import { expect, Page } from "@playwright/test"; +import { SBOMListPage } from "../Constants"; +import { DetailsPageLayout } from "../DetailsPageLayout"; +import { Navigation } from "../Navigation"; +import { Table } from "../Table"; +import { Toolbar } from "../Toolbar"; + +export class SbomDetailsPage { + private _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 toolbar = await Toolbar.build(page, SBOMListPage.toolbarAriaLabel); + const table = await Table.build(page, SBOMListPage.tableAriaLabel); + + await toolbar.applyTextFilter(SBOMListPage.filters.filterText, sbomName); + await table.waitUntilDataIsLoaded(); + await table.verifyColumnContainsText("Name", sbomName); + + await page.getByRole("link", { name: "quarkus-bom", exact: true }).click(); + + const layout = await DetailsPageLayout.build(page); + await expect(page.getByRole("heading", { name: sbomName })).toBeVisible(); + + 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-list/actions.spec.ts b/tests/ui/pages/sbom-list/actions.spec.ts index e9e3ef8..1c58810 100644 --- a/tests/ui/pages/sbom-list/actions.spec.ts +++ b/tests/ui/pages/sbom-list/actions.spec.ts @@ -6,6 +6,7 @@ import { login } from "../../helpers/Auth"; import { SBOMListPage } from "../Constants"; import { Navigation } from "../Navigation"; import { Table } from "../Table"; +import { LabelsModal } from "../LabelsModal"; test.describe("Action validations", { tag: "@tier1" }, () => { test.beforeEach(async ({ page }) => { @@ -49,25 +50,11 @@ test.describe("Action validations", { tag: "@tier1" }, () => { const table = await Table.build(page, SBOMListPage.tableAriaLabel); await table.clickAction("Edit labels", 0); - // Verify Modal is open - const dialog = page.getByRole("dialog"); - expect(dialog).toBeVisible(); - - const saveLabels = async () => { - await dialog.locator("button[aria-label='submit']").click(); - await expect(dialog).not.toBeVisible(); - }; + let labelsModal = await LabelsModal.build(page); // Add labels - const inputText = dialog.getByPlaceholder("Add label"); - - for (const label of labels) { - await inputText.click(); - await inputText.fill(label); - await inputText.press("Enter"); - } - - await saveLabels(); + await labelsModal.addLabels(labels); + await labelsModal.clickSave(); // Verify labels were added await table.waitUntilDataIsLoaded(); @@ -83,18 +70,11 @@ test.describe("Action validations", { tag: "@tier1" }, () => { // Clean labels added previously await table.clickAction("Edit labels", 0); - for (const label of labels) { - await dialog - .locator(".pf-v6-c-label-group__list-item", { - hasText: label, - }) - .locator("button") - .click(); - } + labelsModal = await LabelsModal.build(page); + await labelsModal.removeLabels(labels); + await labelsModal.clickSave(); - await saveLabels(); await table.waitUntilDataIsLoaded(); - for (const label of labels) { await expect( table._table From 6f70e2ec28f37fed8662d2e2c3ed57a7a15c65b7 Mon Sep 17 00:00:00 2001 From: Carlos Feria <2582866+carlosthe19916@users.noreply.github.com> Date: Sat, 28 Jun 2025 07:23:58 +0200 Subject: [PATCH 14/23] save details working test Signed-off-by: Carlos Feria <2582866+carlosthe19916@users.noreply.github.com> --- tests/ui/pages/Constants.ts | 21 ++++- tests/ui/pages/DetailsPageLayout.ts | 3 +- tests/ui/pages/LabelsModal.ts | 5 +- tests/ui/pages/Navigation.ts | 2 +- tests/ui/pages/Table.ts | 6 -- .../pages/advisory-list/AdvisoryListPage.ts | 16 ++++ tests/ui/pages/advisory-list/columns.spec.ts | 8 +- tests/ui/pages/advisory-list/filter.spec.ts | 12 +-- .../ui/pages/advisory-list/pagination.spec.ts | 8 +- tests/ui/pages/advisory-list/sort.spec.ts | 4 +- tests/ui/pages/package-list/columns.spec.ts | 11 ++- tests/ui/pages/package-list/filter.spec.ts | 12 +-- .../ui/pages/package-list/pagination.spec.ts | 8 +- tests/ui/pages/package-list/sort.spec.ts | 4 +- .../ui/pages/sbom-details/SbomDetailsPage.ts | 8 +- .../sbom-details/package/columns.spec.ts | 78 +++++++++++++++++++ .../pages/sbom-details/package/filter.spec.ts | 46 +++++++++++ .../sbom-details/package/pagination.spec.ts | 46 +++++++++++ .../pages/sbom-details/package/sort.spec.ts | 34 ++++++++ tests/ui/pages/sbom-list/actions.spec.ts | 8 +- tests/ui/pages/sbom-list/columns.spec.ts | 8 +- tests/ui/pages/sbom-list/filter.spec.ts | 12 +-- tests/ui/pages/sbom-list/pagination.spec.ts | 8 +- tests/ui/pages/sbom-list/sort.spec.ts | 4 +- .../pages/vulnerability-list/columns.spec.ts | 8 +- .../pages/vulnerability-list/filter.spec.ts | 12 +-- .../vulnerability-list/pagination.spec.ts | 8 +- .../ui/pages/vulnerability-list/sort.spec.ts | 4 +- 28 files changed, 315 insertions(+), 89 deletions(-) create mode 100644 tests/ui/pages/advisory-list/AdvisoryListPage.ts create mode 100644 tests/ui/pages/sbom-details/package/columns.spec.ts create mode 100644 tests/ui/pages/sbom-details/package/filter.spec.ts create mode 100644 tests/ui/pages/sbom-details/package/pagination.spec.ts create mode 100644 tests/ui/pages/sbom-details/package/sort.spec.ts diff --git a/tests/ui/pages/Constants.ts b/tests/ui/pages/Constants.ts index 006a9c4..ee22b01 100644 --- a/tests/ui/pages/Constants.ts +++ b/tests/ui/pages/Constants.ts @@ -8,7 +8,7 @@ export type SidebarMenu = | "Importers" | "Upload"; -export const SBOMListPage = { +export const ListPage_SBOM = { toolbarAriaLabel: "sbom-toolbar", tableAriaLabel: "sbom-table", paginationIdTop: "sbom-table-pagination-top", @@ -20,7 +20,7 @@ export const SBOMListPage = { }, }; -export const VulnerabilityListPage = { +export const ListPage_Vulnerability = { toolbarAriaLabel: "vulnerability-toolbar", tableAriaLabel: "Vulnerability table", paginationIdTop: "vulnerability-table-pagination-top", @@ -32,7 +32,7 @@ export const VulnerabilityListPage = { }, }; -export const PackageListPage = { +export const ListPage_Package = { toolbarAriaLabel: "package-toolbar", tableAriaLabel: "Package table", paginationIdTop: "package-table-pagination-top", @@ -44,7 +44,7 @@ export const PackageListPage = { }, }; -export const AdvisoryListPage = { +export const ListPage_Advisory = { toolbarAriaLabel: "advisory-toolbar", tableAriaLabel: "advisory-table", paginationIdTop: "advisory-table-pagination-top", @@ -56,6 +56,19 @@ export const AdvisoryListPage = { }, }; +export const DetailsPage_SBOM = { + packagesTab: { + toolbarAriaLabel: "Package toolbar", + tableAriaLabel: "Package table", + paginationIdTop: "package-table-pagination-top", + paginationIdBottom: "package-table-pagination-bottom", + filters: { + filterText: "Filter text", + license: "License", + }, + }, +}; + export const isSorted = (arr: string[], asc: boolean) => { let sorted = [...arr].sort((a, b) => a.localeCompare(b)); if (!asc) { diff --git a/tests/ui/pages/DetailsPageLayout.ts b/tests/ui/pages/DetailsPageLayout.ts index c758d46..e4f2eeb 100644 --- a/tests/ui/pages/DetailsPageLayout.ts +++ b/tests/ui/pages/DetailsPageLayout.ts @@ -1,8 +1,7 @@ -import { expect, Locator, Page } from "@playwright/test"; +import { expect, Page } from "@playwright/test"; export class DetailsPageLayout { private _page: Page; - _detailsPage: Locator; private constructor(page: Page) { this._page = page; diff --git a/tests/ui/pages/LabelsModal.ts b/tests/ui/pages/LabelsModal.ts index 34120be..4c6775a 100644 --- a/tests/ui/pages/LabelsModal.ts +++ b/tests/ui/pages/LabelsModal.ts @@ -1,9 +1,6 @@ import { Locator, Page } from "playwright-core"; import { expect } from "playwright/test"; -/** - * Used to navigate to different pages - */ export class LabelsModal { private _page: Page; private _dialog: Locator; @@ -15,7 +12,7 @@ export class LabelsModal { static async build(page: Page) { const dialog = page.getByRole("dialog"); - expect(dialog).toBeVisible(); + await expect(dialog).toBeVisible(); return new LabelsModal(page, dialog); } diff --git a/tests/ui/pages/Navigation.ts b/tests/ui/pages/Navigation.ts index a106db6..0f5cb20 100644 --- a/tests/ui/pages/Navigation.ts +++ b/tests/ui/pages/Navigation.ts @@ -25,7 +25,7 @@ export class Navigation { | "Importers" | "Upload" ) { - // By default we do not initialize navigation at "/"" where the Dashboard is located + // 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/Table.ts b/tests/ui/pages/Table.ts index ea203a7..c325117 100644 --- a/tests/ui/pages/Table.ts +++ b/tests/ui/pages/Table.ts @@ -1,11 +1,5 @@ import { expect, Locator, Page } from "@playwright/test"; -type SortAria = "ascending" | "descending"; - -const getSortFromAria = (value: SortAria): boolean => { - return value === "ascending" ? true : false; -}; - export class Table { private _page: Page; _table: Locator; diff --git a/tests/ui/pages/advisory-list/AdvisoryListPage.ts b/tests/ui/pages/advisory-list/AdvisoryListPage.ts new file mode 100644 index 0000000..d870d0c --- /dev/null +++ b/tests/ui/pages/advisory-list/AdvisoryListPage.ts @@ -0,0 +1,16 @@ +import { expect, Page } from "@playwright/test"; + +export class AdvisoryListPage { + private _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 AdvisoryListPage(page); + } + + +} diff --git a/tests/ui/pages/advisory-list/columns.spec.ts b/tests/ui/pages/advisory-list/columns.spec.ts index a02d078..80290fd 100644 --- a/tests/ui/pages/advisory-list/columns.spec.ts +++ b/tests/ui/pages/advisory-list/columns.spec.ts @@ -3,7 +3,7 @@ import { expect, test } from "@playwright/test"; import { login } from "../../helpers/Auth"; -import { AdvisoryListPage } from "../Constants"; +import { ListPage_Advisory } from "../Constants"; import { Navigation } from "../Navigation"; import { Table } from "../Table"; import { Toolbar } from "../Toolbar"; @@ -19,13 +19,13 @@ test.describe("Columns validations", { tag: "@tier1" }, () => { test("Columns", async ({ page }) => { const toolbar = await Toolbar.build( page, - AdvisoryListPage.toolbarAriaLabel + ListPage_Advisory.toolbarAriaLabel ); - const table = await Table.build(page, AdvisoryListPage.tableAriaLabel); + const table = await Table.build(page, ListPage_Advisory.tableAriaLabel); // Full search await toolbar.applyTextFilter( - AdvisoryListPage.filters.filterText, + ListPage_Advisory.filters.filterText, "CVE-2024-26308" ); await table.waitUntilDataIsLoaded(); diff --git a/tests/ui/pages/advisory-list/filter.spec.ts b/tests/ui/pages/advisory-list/filter.spec.ts index 9e435bd..d530ed2 100644 --- a/tests/ui/pages/advisory-list/filter.spec.ts +++ b/tests/ui/pages/advisory-list/filter.spec.ts @@ -3,7 +3,7 @@ import { test } from "@playwright/test"; import { login } from "../../helpers/Auth"; -import { AdvisoryListPage } from "../Constants"; +import { ListPage_Advisory } from "../Constants"; import { Navigation } from "../Navigation"; import { Table } from "../Table"; import { Toolbar } from "../Toolbar"; @@ -19,13 +19,13 @@ test.describe("Filter validations", { tag: "@tier1" }, () => { test("Filters", async ({ page }) => { const toolbar = await Toolbar.build( page, - AdvisoryListPage.toolbarAriaLabel + ListPage_Advisory.toolbarAriaLabel ); - const table = await Table.build(page, AdvisoryListPage.tableAriaLabel); + const table = await Table.build(page, ListPage_Advisory.tableAriaLabel); // Full search await toolbar.applyTextFilter( - AdvisoryListPage.filters.filterText, + ListPage_Advisory.filters.filterText, "CVE-2024-26308" ); await table.waitUntilDataIsLoaded(); @@ -33,7 +33,7 @@ test.describe("Filter validations", { tag: "@tier1" }, () => { // Date filter await toolbar.applyDateRangeFilter( - AdvisoryListPage.filters.revision, + ListPage_Advisory.filters.revision, "08/01/2024", "08/03/2024" ); @@ -41,7 +41,7 @@ test.describe("Filter validations", { tag: "@tier1" }, () => { await table.verifyColumnContainsText("ID", "CVE-2024-26308"); // Labels filter - await toolbar.applyLabelsFilter(AdvisoryListPage.filters.label, [ + await toolbar.applyLabelsFilter(ListPage_Advisory.filters.label, [ "type=cve", ]); await table.waitUntilDataIsLoaded(); diff --git a/tests/ui/pages/advisory-list/pagination.spec.ts b/tests/ui/pages/advisory-list/pagination.spec.ts index dd7e8a0..90278e1 100644 --- a/tests/ui/pages/advisory-list/pagination.spec.ts +++ b/tests/ui/pages/advisory-list/pagination.spec.ts @@ -3,7 +3,7 @@ import { test } from "@playwright/test"; import { login } from "../../helpers/Auth"; -import { AdvisoryListPage } from "../Constants"; +import { ListPage_Advisory } from "../Constants"; import { Navigation } from "../Navigation"; import { Pagination } from "../Pagination"; import { Table } from "../Table"; @@ -19,7 +19,7 @@ test.describe("Pagination validations", { tag: "@tier1" }, () => { test("Navigation button validations", async ({ page }) => { const pagination = await Pagination.build( page, - AdvisoryListPage.paginationIdTop + ListPage_Advisory.paginationIdTop ); await pagination.validatePagination(); }); @@ -27,10 +27,10 @@ test.describe("Pagination validations", { tag: "@tier1" }, () => { test("Items per page validations", async ({ page }) => { const pagination = await Pagination.build( page, - AdvisoryListPage.paginationIdTop + ListPage_Advisory.paginationIdTop ); - const table = await Table.build(page, AdvisoryListPage.tableAriaLabel); + const table = await Table.build(page, ListPage_Advisory.tableAriaLabel); 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 index 1deb2e7..23886af 100644 --- a/tests/ui/pages/advisory-list/sort.spec.ts +++ b/tests/ui/pages/advisory-list/sort.spec.ts @@ -3,8 +3,8 @@ import { expect, test } from "@playwright/test"; import { login } from "../../helpers/Auth"; +import { isSorted, ListPage_Advisory } from "../Constants"; import { Navigation } from "../Navigation"; -import { isSorted, AdvisoryListPage } from "../Constants"; import { Table } from "../Table"; test.describe("Sort validations", { tag: "@tier1" }, () => { @@ -17,7 +17,7 @@ test.describe("Sort validations", { tag: "@tier1" }, () => { // skipped until it is fixed in the backend. It is bug, fix it test.skip("Sort", async ({ page }) => { - const table = await Table.build(page, AdvisoryListPage.tableAriaLabel); + const table = await Table.build(page, ListPage_Advisory.tableAriaLabel); const columnNameSelector = table._table.locator(`td[data-label="ID"]`); // ID Asc diff --git a/tests/ui/pages/package-list/columns.spec.ts b/tests/ui/pages/package-list/columns.spec.ts index d5d9940..5b5b5f4 100644 --- a/tests/ui/pages/package-list/columns.spec.ts +++ b/tests/ui/pages/package-list/columns.spec.ts @@ -3,7 +3,7 @@ import { expect, test } from "@playwright/test"; import { login } from "../../helpers/Auth"; -import { PackageListPage } from "../Constants"; +import { ListPage_Package } from "../Constants"; import { Navigation } from "../Navigation"; import { Table } from "../Table"; import { Toolbar } from "../Toolbar"; @@ -17,12 +17,15 @@ test.describe("Columns validations", { tag: "@tier1" }, () => { }); test("Columns", async ({ page }) => { - const toolbar = await Toolbar.build(page, PackageListPage.toolbarAriaLabel); - const table = await Table.build(page, PackageListPage.tableAriaLabel); + const toolbar = await Toolbar.build( + page, + ListPage_Package.toolbarAriaLabel + ); + const table = await Table.build(page, ListPage_Package.tableAriaLabel); // Full search await toolbar.applyTextFilter( - PackageListPage.filters.filterText, + ListPage_Package.filters.filterText, "keycloak-core" ); await table.waitUntilDataIsLoaded(); diff --git a/tests/ui/pages/package-list/filter.spec.ts b/tests/ui/pages/package-list/filter.spec.ts index 5763ebb..3ab7a88 100644 --- a/tests/ui/pages/package-list/filter.spec.ts +++ b/tests/ui/pages/package-list/filter.spec.ts @@ -3,7 +3,7 @@ import { test } from "@playwright/test"; import { login } from "../../helpers/Auth"; -import { PackageListPage } from "../Constants"; +import { ListPage_Package } from "../Constants"; import { Navigation } from "../Navigation"; import { Table } from "../Table"; import { Toolbar } from "../Toolbar"; @@ -17,19 +17,19 @@ test.describe("Filter validations", { tag: "@tier1" }, () => { }); test("Filters", async ({ page }) => { - const toolbar = await Toolbar.build(page, PackageListPage.toolbarAriaLabel); - const table = await Table.build(page, PackageListPage.tableAriaLabel); + const toolbar = await Toolbar.build(page, ListPage_Package.toolbarAriaLabel); + const table = await Table.build(page, ListPage_Package.tableAriaLabel); // Full search await toolbar.applyTextFilter( - PackageListPage.filters.filterText, + ListPage_Package.filters.filterText, "keycloak-core" ); await table.waitUntilDataIsLoaded(); await table.verifyColumnContainsText("Name", "keycloak-core"); // Type filter - await toolbar.applyMultiSelectFilter(PackageListPage.filters.type, [ + await toolbar.applyMultiSelectFilter(ListPage_Package.filters.type, [ "Maven", "RPM", ]); @@ -37,7 +37,7 @@ test.describe("Filter validations", { tag: "@tier1" }, () => { await table.verifyColumnContainsText("Name", "keycloak-core"); // Architecture - await toolbar.applyMultiSelectFilter(PackageListPage.filters.architecture, [ + await toolbar.applyMultiSelectFilter(ListPage_Package.filters.architecture, [ "S390", "No Arch", ]); diff --git a/tests/ui/pages/package-list/pagination.spec.ts b/tests/ui/pages/package-list/pagination.spec.ts index 1830db9..d32f249 100644 --- a/tests/ui/pages/package-list/pagination.spec.ts +++ b/tests/ui/pages/package-list/pagination.spec.ts @@ -3,7 +3,7 @@ import { test } from "@playwright/test"; import { login } from "../../helpers/Auth"; -import { PackageListPage } from "../Constants"; +import { ListPage_Package } from "../Constants"; import { Navigation } from "../Navigation"; import { Pagination } from "../Pagination"; import { Table } from "../Table"; @@ -19,7 +19,7 @@ test.describe("Pagination validations", { tag: "@tier1" }, () => { test("Navigation button validations", async ({ page }) => { const pagination = await Pagination.build( page, - PackageListPage.paginationIdTop + ListPage_Package.paginationIdTop ); await pagination.validatePagination(); }); @@ -27,10 +27,10 @@ test.describe("Pagination validations", { tag: "@tier1" }, () => { test("Items per page validations", async ({ page }) => { const pagination = await Pagination.build( page, - PackageListPage.paginationIdTop + ListPage_Package.paginationIdTop ); - const table = await Table.build(page, PackageListPage.tableAriaLabel); + const table = await Table.build(page, ListPage_Package.tableAriaLabel); 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 index 35f3f9c..1d75dd0 100644 --- a/tests/ui/pages/package-list/sort.spec.ts +++ b/tests/ui/pages/package-list/sort.spec.ts @@ -3,8 +3,8 @@ import { expect, test } from "@playwright/test"; import { login } from "../../helpers/Auth"; +import { isSorted, ListPage_Package } from "../Constants"; import { Navigation } from "../Navigation"; -import { isSorted, PackageListPage } from "../Constants"; import { Table } from "../Table"; test.describe("Sort validations", { tag: "@tier1" }, () => { @@ -16,7 +16,7 @@ test.describe("Sort validations", { tag: "@tier1" }, () => { }); test("Sort", async ({ page }) => { - const table = await Table.build(page, PackageListPage.tableAriaLabel); + const table = await Table.build(page, ListPage_Package.tableAriaLabel); const columnNameSelector = table._table.locator(`td[data-label="ID"]`); // ID Asc diff --git a/tests/ui/pages/sbom-details/SbomDetailsPage.ts b/tests/ui/pages/sbom-details/SbomDetailsPage.ts index b4dff85..460c361 100644 --- a/tests/ui/pages/sbom-details/SbomDetailsPage.ts +++ b/tests/ui/pages/sbom-details/SbomDetailsPage.ts @@ -1,5 +1,5 @@ import { expect, Page } from "@playwright/test"; -import { SBOMListPage } from "../Constants"; +import { ListPage_SBOM } from "../Constants"; import { DetailsPageLayout } from "../DetailsPageLayout"; import { Navigation } from "../Navigation"; import { Table } from "../Table"; @@ -18,10 +18,10 @@ export class SbomDetailsPage { const navigation = await Navigation.build(page); await navigation.goToSidebar("SBOMs"); - const toolbar = await Toolbar.build(page, SBOMListPage.toolbarAriaLabel); - const table = await Table.build(page, SBOMListPage.tableAriaLabel); + const toolbar = await Toolbar.build(page, ListPage_SBOM.toolbarAriaLabel); + const table = await Table.build(page, ListPage_SBOM.tableAriaLabel); - await toolbar.applyTextFilter(SBOMListPage.filters.filterText, sbomName); + await toolbar.applyTextFilter(ListPage_SBOM.filters.filterText, sbomName); await table.waitUntilDataIsLoaded(); await table.verifyColumnContainsText("Name", sbomName); diff --git a/tests/ui/pages/sbom-details/package/columns.spec.ts b/tests/ui/pages/sbom-details/package/columns.spec.ts new file mode 100644 index 0000000..efbd195 --- /dev/null +++ b/tests/ui/pages/sbom-details/package/columns.spec.ts @@ -0,0 +1,78 @@ +// @ts-check + +import { expect, test } from "@playwright/test"; + +import { login } from "../../../helpers/Auth"; +import { DetailsPage_SBOM } from "../../Constants"; +import { Table } from "../../Table"; +import { Toolbar } from "../../Toolbar"; +import { SbomDetailsPage } from "../SbomDetailsPage"; + +test.describe("Columns validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test("Vulnerabilities", async ({ page }) => { + const detailsPage = await SbomDetailsPage.build(page, "quarkus-bom"); + await detailsPage._layout.selectTab("Packages"); + + const toolbar = await Toolbar.build( + page, + DetailsPage_SBOM.packagesTab.toolbarAriaLabel + ); + const table = await Table.build( + page, + DetailsPage_SBOM.packagesTab.tableAriaLabel + ); + + // Full search + await toolbar.applyTextFilter( + DetailsPage_SBOM.packagesTab.filters.filterText, + "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/package/filter.spec.ts b/tests/ui/pages/sbom-details/package/filter.spec.ts new file mode 100644 index 0000000..c0839fa --- /dev/null +++ b/tests/ui/pages/sbom-details/package/filter.spec.ts @@ -0,0 +1,46 @@ +// @ts-check + +import { test } from "@playwright/test"; + +import { login } from "../../../helpers/Auth"; +import { DetailsPage_SBOM } from "../../Constants"; +import { Navigation } from "../../Navigation"; +import { Table } from "../../Table"; +import { Toolbar } from "../../Toolbar"; +import { SbomDetailsPage } from "../SbomDetailsPage"; + +test.describe("Filter validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test("Filters", async ({ page }) => { + const detailsPage = await SbomDetailsPage.build(page, "quarkus-bom"); + await detailsPage._layout.selectTab("Packages"); + + const toolbar = await Toolbar.build( + page, + DetailsPage_SBOM.packagesTab.toolbarAriaLabel + ); + const table = await Table.build( + page, + DetailsPage_SBOM.packagesTab.tableAriaLabel + ); + + // Full search + await toolbar.applyTextFilter( + DetailsPage_SBOM.packagesTab.filters.filterText, + "commons-compress" + ); + await table.waitUntilDataIsLoaded(); + await table.verifyColumnContainsText("Name", "commons-compress"); + + // Labels filter + await toolbar.applyMultiSelectFilter( + DetailsPage_SBOM.packagesTab.filters.license, + ["Apache-2.0", "NOASSERTION"] + ); + await table.waitUntilDataIsLoaded(); + await table.verifyColumnContainsText("Name", "commons-compress"); + }); +}); diff --git a/tests/ui/pages/sbom-details/package/pagination.spec.ts b/tests/ui/pages/sbom-details/package/pagination.spec.ts new file mode 100644 index 0000000..5002517 --- /dev/null +++ b/tests/ui/pages/sbom-details/package/pagination.spec.ts @@ -0,0 +1,46 @@ +// @ts-check + +import { test } from "@playwright/test"; + +import { login } from "../../../helpers/Auth"; +import { DetailsPage_SBOM } from "../../Constants"; +import { Navigation } from "../../Navigation"; +import { Pagination } from "../../Pagination"; +import { Table } from "../../Table"; +import { SbomDetailsPage } from "../SbomDetailsPage"; + +test.describe("Pagination validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + + const navigation = await Navigation.build(page); + await navigation.goToSidebar("SBOMs"); + }); + + test("Navigation button validations", async ({ page }) => { + const detailsPage = await SbomDetailsPage.build(page, "quarkus-bom"); + await detailsPage._layout.selectTab("Packages"); + + const pagination = await Pagination.build( + page, + DetailsPage_SBOM.packagesTab.paginationIdTop + ); + await pagination.validatePagination(); + }); + + test("Items per page validations", async ({ page }) => { + const detailsPage = await SbomDetailsPage.build(page, "quarkus-bom"); + await detailsPage._layout.selectTab("Packages"); + + const pagination = await Pagination.build( + page, + DetailsPage_SBOM.packagesTab.paginationIdTop + ); + + const table = await Table.build( + page, + DetailsPage_SBOM.packagesTab.tableAriaLabel + ); + await pagination.validateItemsPerPage("Name", table); + }); +}); diff --git a/tests/ui/pages/sbom-details/package/sort.spec.ts b/tests/ui/pages/sbom-details/package/sort.spec.ts new file mode 100644 index 0000000..fd22432 --- /dev/null +++ b/tests/ui/pages/sbom-details/package/sort.spec.ts @@ -0,0 +1,34 @@ +// @ts-check + +import { expect, test } from "@playwright/test"; + +import { login } from "../../../helpers/Auth"; +import { DetailsPage_SBOM, isSorted } from "../../Constants"; +import { Table } from "../../Table"; +import { SbomDetailsPage } from "../SbomDetailsPage"; + +test.describe("Sort validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test("Sort", async ({ page }) => { + const detailsPage = await SbomDetailsPage.build(page, "quarkus-bom"); + await detailsPage._layout.selectTab("Packages"); + + const table = await Table.build( + page, + DetailsPage_SBOM.packagesTab.tableAriaLabel + ); + const columnNameSelector = table._table.locator(`td[data-label="Name"]`); + + const namesAsc = await columnNameSelector.allInnerTexts(); + expect(isSorted(namesAsc, true)).toBe(true); + + // Reverse sorting + await table.clickSortBy("Name"); + const namesDesc = await columnNameSelector.allInnerTexts(); + + expect(isSorted(namesDesc, false)).toBe(true); + }); +}); diff --git a/tests/ui/pages/sbom-list/actions.spec.ts b/tests/ui/pages/sbom-list/actions.spec.ts index 1c58810..a660c79 100644 --- a/tests/ui/pages/sbom-list/actions.spec.ts +++ b/tests/ui/pages/sbom-list/actions.spec.ts @@ -3,7 +3,7 @@ import { expect, test } from "@playwright/test"; import { login } from "../../helpers/Auth"; -import { SBOMListPage } from "../Constants"; +import { ListPage_SBOM } from "../Constants"; import { Navigation } from "../Navigation"; import { Table } from "../Table"; import { LabelsModal } from "../LabelsModal"; @@ -17,7 +17,7 @@ test.describe("Action validations", { tag: "@tier1" }, () => { }); test("Actions - Download SBOM", async ({ page }) => { - const table = await Table.build(page, SBOMListPage.tableAriaLabel); + const table = await Table.build(page, ListPage_SBOM.tableAriaLabel); const sbomNames = await table._table .locator(`td[data-label="Name"]`) @@ -31,7 +31,7 @@ test.describe("Action validations", { tag: "@tier1" }, () => { }); test("Actions - Download License Report", async ({ page }) => { - const table = await Table.build(page, SBOMListPage.tableAriaLabel); + const table = await Table.build(page, ListPage_SBOM.tableAriaLabel); const sbomNames = await table._table .locator(`td[data-label="Name"]`) @@ -47,7 +47,7 @@ test.describe("Action validations", { tag: "@tier1" }, () => { test("Actions - Edit Labels", async ({ page }) => { const labels = ["color=red", "production"]; - const table = await Table.build(page, SBOMListPage.tableAriaLabel); + const table = await Table.build(page, ListPage_SBOM.tableAriaLabel); await table.clickAction("Edit labels", 0); let labelsModal = await LabelsModal.build(page); diff --git a/tests/ui/pages/sbom-list/columns.spec.ts b/tests/ui/pages/sbom-list/columns.spec.ts index d833900..78d101d 100644 --- a/tests/ui/pages/sbom-list/columns.spec.ts +++ b/tests/ui/pages/sbom-list/columns.spec.ts @@ -3,7 +3,7 @@ import { expect, test } from "@playwright/test"; import { login } from "../../helpers/Auth"; -import { SBOMListPage } from "../Constants"; +import { ListPage_SBOM } from "../Constants"; import { Navigation } from "../Navigation"; import { Table } from "../Table"; import { Toolbar } from "../Toolbar"; @@ -17,12 +17,12 @@ test.describe("Columns validations", { tag: "@tier1" }, () => { }); test("Vulnerabilities", async ({ page }) => { - const toolbar = await Toolbar.build(page, SBOMListPage.toolbarAriaLabel); - const table = await Table.build(page, SBOMListPage.tableAriaLabel); + const toolbar = await Toolbar.build(page, ListPage_SBOM.toolbarAriaLabel); + const table = await Table.build(page, ListPage_SBOM.tableAriaLabel); // Full search await toolbar.applyTextFilter( - SBOMListPage.filters.filterText, + ListPage_SBOM.filters.filterText, "quarkus-bom" ); await table.waitUntilDataIsLoaded(); diff --git a/tests/ui/pages/sbom-list/filter.spec.ts b/tests/ui/pages/sbom-list/filter.spec.ts index 550ae8e..4d4b24c 100644 --- a/tests/ui/pages/sbom-list/filter.spec.ts +++ b/tests/ui/pages/sbom-list/filter.spec.ts @@ -3,7 +3,7 @@ import { test } from "@playwright/test"; import { login } from "../../helpers/Auth"; -import { SBOMListPage } from "../Constants"; +import { ListPage_SBOM } from "../Constants"; import { Navigation } from "../Navigation"; import { Table } from "../Table"; import { Toolbar } from "../Toolbar"; @@ -17,17 +17,17 @@ test.describe("Filter validations", { tag: "@tier1" }, () => { }); test("Filters", async ({ page }) => { - const toolbar = await Toolbar.build(page, SBOMListPage.toolbarAriaLabel); - const table = await Table.build(page, SBOMListPage.tableAriaLabel); + const toolbar = await Toolbar.build(page, ListPage_SBOM.toolbarAriaLabel); + const table = await Table.build(page, ListPage_SBOM.tableAriaLabel); // Full search - await toolbar.applyTextFilter(SBOMListPage.filters.filterText, "quarkus"); + await toolbar.applyTextFilter(ListPage_SBOM.filters.filterText, "quarkus"); await table.waitUntilDataIsLoaded(); await table.verifyColumnContainsText("Name", "quarkus-bom"); // Date filter await toolbar.applyDateRangeFilter( - SBOMListPage.filters.createdOn, + ListPage_SBOM.filters.createdOn, "11/21/2023", "11/23/2023" ); @@ -35,7 +35,7 @@ test.describe("Filter validations", { tag: "@tier1" }, () => { await table.verifyColumnContainsText("Name", "quarkus-bom"); // Labels filter - await toolbar.applyLabelsFilter(SBOMListPage.filters.label, ["type=spdx"]); + await toolbar.applyLabelsFilter(ListPage_SBOM.filters.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 index 43b006a..f67f255 100644 --- a/tests/ui/pages/sbom-list/pagination.spec.ts +++ b/tests/ui/pages/sbom-list/pagination.spec.ts @@ -4,7 +4,7 @@ import { test } from "@playwright/test"; import { login } from "../../helpers/Auth"; import { Navigation } from "../Navigation"; -import { SBOMListPage } from "../Constants"; +import { ListPage_SBOM } from "../Constants"; import { Table } from "../Table"; import { Pagination } from "../Pagination"; @@ -19,7 +19,7 @@ test.describe("Pagination validations", { tag: "@tier1" }, () => { test("Navigation button validations", async ({ page }) => { const pagination = await Pagination.build( page, - SBOMListPage.paginationIdTop + ListPage_SBOM.paginationIdTop ); await pagination.validatePagination(); }); @@ -27,10 +27,10 @@ test.describe("Pagination validations", { tag: "@tier1" }, () => { test("Items per page validations", async ({ page }) => { const pagination = await Pagination.build( page, - SBOMListPage.paginationIdTop + ListPage_SBOM.paginationIdTop ); - const table = await Table.build(page, SBOMListPage.tableAriaLabel); + const table = await Table.build(page, ListPage_SBOM.tableAriaLabel); 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 index d7702d2..e13eae8 100644 --- a/tests/ui/pages/sbom-list/sort.spec.ts +++ b/tests/ui/pages/sbom-list/sort.spec.ts @@ -4,7 +4,7 @@ import { expect, test } from "@playwright/test"; import { login } from "../../helpers/Auth"; import { Navigation } from "../Navigation"; -import { isSorted, SBOMListPage } from "../Constants"; +import { isSorted, ListPage_SBOM } from "../Constants"; import { Table } from "../Table"; test.describe("Sort validations", { tag: "@tier1" }, () => { @@ -16,7 +16,7 @@ test.describe("Sort validations", { tag: "@tier1" }, () => { }); test("Sort", async ({ page }) => { - const table = await Table.build(page, SBOMListPage.tableAriaLabel); + const table = await Table.build(page, ListPage_SBOM.tableAriaLabel); const columnNameSelector = table._table.locator(`td[data-label="Name"]`); const namesAsc = await columnNameSelector.allInnerTexts(); diff --git a/tests/ui/pages/vulnerability-list/columns.spec.ts b/tests/ui/pages/vulnerability-list/columns.spec.ts index 03756e0..19e0612 100644 --- a/tests/ui/pages/vulnerability-list/columns.spec.ts +++ b/tests/ui/pages/vulnerability-list/columns.spec.ts @@ -3,7 +3,7 @@ import { expect, test } from "@playwright/test"; import { login } from "../../helpers/Auth"; -import { VulnerabilityListPage } from "../Constants"; +import { ListPage_Vulnerability } from "../Constants"; import { Navigation } from "../Navigation"; import { Table } from "../Table"; import { Toolbar } from "../Toolbar"; @@ -19,13 +19,13 @@ test.describe("Columns validations", { tag: "@tier1" }, () => { test("Impacted SBOMs", async ({ page }) => { const toolbar = await Toolbar.build( page, - VulnerabilityListPage.toolbarAriaLabel + ListPage_Vulnerability.toolbarAriaLabel ); - const table = await Table.build(page, VulnerabilityListPage.tableAriaLabel); + const table = await Table.build(page, ListPage_Vulnerability.tableAriaLabel); // Full search await toolbar.applyTextFilter( - VulnerabilityListPage.filters.filterText, + ListPage_Vulnerability.filters.filterText, "CVE-2024-26308" ); await table.waitUntilDataIsLoaded(); diff --git a/tests/ui/pages/vulnerability-list/filter.spec.ts b/tests/ui/pages/vulnerability-list/filter.spec.ts index ad283b3..01304b4 100644 --- a/tests/ui/pages/vulnerability-list/filter.spec.ts +++ b/tests/ui/pages/vulnerability-list/filter.spec.ts @@ -3,7 +3,7 @@ import { test } from "@playwright/test"; import { login } from "../../helpers/Auth"; -import { VulnerabilityListPage } from "../Constants"; +import { ListPage_Vulnerability } from "../Constants"; import { Navigation } from "../Navigation"; import { Table } from "../Table"; import { Toolbar } from "../Toolbar"; @@ -19,13 +19,13 @@ test.describe("Filter validations", { tag: "@tier1" }, () => { test("Filters", async ({ page }) => { const toolbar = await Toolbar.build( page, - VulnerabilityListPage.toolbarAriaLabel + ListPage_Vulnerability.toolbarAriaLabel ); - const table = await Table.build(page, VulnerabilityListPage.tableAriaLabel); + const table = await Table.build(page, ListPage_Vulnerability.tableAriaLabel); // Full search await toolbar.applyTextFilter( - VulnerabilityListPage.filters.filterText, + ListPage_Vulnerability.filters.filterText, "CVE-2024-26308" ); await table.waitUntilDataIsLoaded(); @@ -33,7 +33,7 @@ test.describe("Filter validations", { tag: "@tier1" }, () => { // Severity filter await toolbar.applyMultiSelectFilter( - VulnerabilityListPage.filters.severity, + ListPage_Vulnerability.filters.severity, ["Unknown", "None"] ); await table.waitUntilDataIsLoaded(); @@ -41,7 +41,7 @@ test.describe("Filter validations", { tag: "@tier1" }, () => { // Date filter await toolbar.applyDateRangeFilter( - VulnerabilityListPage.filters.createdOn, + ListPage_Vulnerability.filters.createdOn, "02/18/2024", "02/20/2024" ); diff --git a/tests/ui/pages/vulnerability-list/pagination.spec.ts b/tests/ui/pages/vulnerability-list/pagination.spec.ts index a9af48a..5942640 100644 --- a/tests/ui/pages/vulnerability-list/pagination.spec.ts +++ b/tests/ui/pages/vulnerability-list/pagination.spec.ts @@ -3,7 +3,7 @@ import { test } from "@playwright/test"; import { login } from "../../helpers/Auth"; -import { VulnerabilityListPage } from "../Constants"; +import { ListPage_Vulnerability } from "../Constants"; import { Navigation } from "../Navigation"; import { Pagination } from "../Pagination"; import { Table } from "../Table"; @@ -19,7 +19,7 @@ test.describe("Pagination validations", { tag: "@tier1" }, () => { test("Navigation button validations", async ({ page }) => { const pagination = await Pagination.build( page, - VulnerabilityListPage.paginationIdTop + ListPage_Vulnerability.paginationIdTop ); await pagination.validatePagination(); }); @@ -27,10 +27,10 @@ test.describe("Pagination validations", { tag: "@tier1" }, () => { test("Items per page validations", async ({ page }) => { const pagination = await Pagination.build( page, - VulnerabilityListPage.paginationIdTop + ListPage_Vulnerability.paginationIdTop ); - const table = await Table.build(page, VulnerabilityListPage.tableAriaLabel); + const table = await Table.build(page, ListPage_Vulnerability.tableAriaLabel); 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 index a37f093..21d9723 100644 --- a/tests/ui/pages/vulnerability-list/sort.spec.ts +++ b/tests/ui/pages/vulnerability-list/sort.spec.ts @@ -4,7 +4,7 @@ import { expect, test } from "@playwright/test"; import { login } from "../../helpers/Auth"; import { Navigation } from "../Navigation"; -import { isSorted, VulnerabilityListPage } from "../Constants"; +import { isSorted, ListPage_Vulnerability } from "../Constants"; import { Table } from "../Table"; test.describe("Sort validations", { tag: "@tier1" }, () => { @@ -16,7 +16,7 @@ test.describe("Sort validations", { tag: "@tier1" }, () => { }); test("Sort", async ({ page }) => { - const table = await Table.build(page, VulnerabilityListPage.tableAriaLabel); + const table = await Table.build(page, ListPage_Vulnerability.tableAriaLabel); const columnNameSelector = table._table.locator(`td[data-label="ID"]`); // ID Asc From 5fa7690744e08f5e688296b7d39e140390022882 Mon Sep 17 00:00:00 2001 From: Carlos Feria <2582866+carlosthe19916@users.noreply.github.com> Date: Sat, 28 Jun 2025 07:59:49 +0200 Subject: [PATCH 15/23] Reuse ListPage --- tests/ui/pages/Constants.ts | 58 ------------------- .../pages/advisory-list/AdvisoryListPage.ts | 25 +++++++- tests/ui/pages/advisory-list/columns.spec.ts | 27 ++++----- tests/ui/pages/advisory-list/filter.spec.ts | 32 +++------- .../ui/pages/advisory-list/pagination.spec.ts | 24 +++----- tests/ui/pages/advisory-list/sort.spec.ts | 12 ++-- .../ui/pages/package-list/PackageListPage.ts | 35 +++++++++++ tests/ui/pages/package-list/columns.spec.ts | 26 ++++----- tests/ui/pages/package-list/filter.spec.ts | 29 +++------- .../ui/pages/package-list/pagination.spec.ts | 24 +++----- tests/ui/pages/package-list/sort.spec.ts | 12 ++-- .../pages/sbom-details/package/filter.spec.ts | 1 - tests/ui/pages/sbom-list/SbomListPage.ts | 35 +++++++++++ tests/ui/pages/sbom-list/actions.spec.ts | 18 +++--- tests/ui/pages/sbom-list/columns.spec.ts | 19 ++---- tests/ui/pages/sbom-list/filter.spec.ts | 20 +++---- tests/ui/pages/sbom-list/pagination.spec.ts | 24 +++----- tests/ui/pages/sbom-list/sort.spec.ts | 12 ++-- .../VulnerabilityListPage.ts | 35 +++++++++++ .../pages/vulnerability-list/columns.spec.ts | 22 ++----- .../pages/vulnerability-list/filter.spec.ts | 29 +++------- .../vulnerability-list/pagination.spec.ts | 24 +++----- .../ui/pages/vulnerability-list/sort.spec.ts | 12 ++-- 23 files changed, 252 insertions(+), 303 deletions(-) create mode 100644 tests/ui/pages/package-list/PackageListPage.ts create mode 100644 tests/ui/pages/sbom-list/SbomListPage.ts create mode 100644 tests/ui/pages/vulnerability-list/VulnerabilityListPage.ts diff --git a/tests/ui/pages/Constants.ts b/tests/ui/pages/Constants.ts index ee22b01..a565b53 100644 --- a/tests/ui/pages/Constants.ts +++ b/tests/ui/pages/Constants.ts @@ -1,61 +1,3 @@ -export type SidebarMenu = - | "Dashboard" - | "Search" - | "SBOMs" - | "Vulnerabilities" - | "Packages" - | "Advisories" - | "Importers" - | "Upload"; - -export const ListPage_SBOM = { - toolbarAriaLabel: "sbom-toolbar", - tableAriaLabel: "sbom-table", - paginationIdTop: "sbom-table-pagination-top", - paginationIdBottom: "sbom-table-pagination-bottom", - filters: { - filterText: "Filter text", - createdOn: "Created on", - label: "Label", - }, -}; - -export const ListPage_Vulnerability = { - toolbarAriaLabel: "vulnerability-toolbar", - tableAriaLabel: "Vulnerability table", - paginationIdTop: "vulnerability-table-pagination-top", - paginationIdBottom: "vulnerability-table-pagination-bottom", - filters: { - filterText: "Filter text", - severity: "CVSS", - createdOn: "Created on", - }, -}; - -export const ListPage_Package = { - toolbarAriaLabel: "package-toolbar", - tableAriaLabel: "Package table", - paginationIdTop: "package-table-pagination-top", - paginationIdBottom: "package-table-pagination-bottom", - filters: { - filterText: "Filter text", - type: "Type", - architecture: "Architecture", - }, -}; - -export const ListPage_Advisory = { - toolbarAriaLabel: "advisory-toolbar", - tableAriaLabel: "advisory-table", - paginationIdTop: "advisory-table-pagination-top", - paginationIdBottom: "advisory-table-pagination-bottom", - filters: { - filterText: "Filter text", - revision: "Revision", - label: "Label", - }, -}; - export const DetailsPage_SBOM = { packagesTab: { toolbarAriaLabel: "Package toolbar", diff --git a/tests/ui/pages/advisory-list/AdvisoryListPage.ts b/tests/ui/pages/advisory-list/AdvisoryListPage.ts index d870d0c..ffd721a 100644 --- a/tests/ui/pages/advisory-list/AdvisoryListPage.ts +++ b/tests/ui/pages/advisory-list/AdvisoryListPage.ts @@ -1,16 +1,35 @@ -import { expect, Page } from "@playwright/test"; +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 _page: Page; + 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(); + 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 index 80290fd..b72aa7a 100644 --- a/tests/ui/pages/advisory-list/columns.spec.ts +++ b/tests/ui/pages/advisory-list/columns.spec.ts @@ -3,42 +3,37 @@ import { expect, test } from "@playwright/test"; import { login } from "../../helpers/Auth"; -import { ListPage_Advisory } from "../Constants"; -import { Navigation } from "../Navigation"; -import { Table } from "../Table"; -import { Toolbar } from "../Toolbar"; +import { AdvisoryListPage } from "./AdvisoryListPage"; test.describe("Columns validations", { tag: "@tier1" }, () => { test.beforeEach(async ({ page }) => { await login(page); - - const navigation = await Navigation.build(page); - await navigation.goToSidebar("Advisories"); }); test("Columns", async ({ page }) => { - const toolbar = await Toolbar.build( - page, - ListPage_Advisory.toolbarAriaLabel - ); - const table = await Table.build(page, ListPage_Advisory.tableAriaLabel); + const listPage = await AdvisoryListPage.build(page); + + const toolbar = await listPage.getToolbar(); + const table = await listPage.getTable(); // Full search - await toolbar.applyTextFilter( - ListPage_Advisory.filters.filterText, - "CVE-2024-26308" - ); + 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 index d530ed2..566ac96 100644 --- a/tests/ui/pages/advisory-list/filter.spec.ts +++ b/tests/ui/pages/advisory-list/filter.spec.ts @@ -3,47 +3,31 @@ import { test } from "@playwright/test"; import { login } from "../../helpers/Auth"; -import { ListPage_Advisory } from "../Constants"; -import { Navigation } from "../Navigation"; -import { Table } from "../Table"; -import { Toolbar } from "../Toolbar"; +import { AdvisoryListPage } from "./AdvisoryListPage"; test.describe("Filter validations", { tag: "@tier1" }, () => { test.beforeEach(async ({ page }) => { await login(page); - - const navigation = await Navigation.build(page); - await navigation.goToSidebar("Advisories"); }); test("Filters", async ({ page }) => { - const toolbar = await Toolbar.build( - page, - ListPage_Advisory.toolbarAriaLabel - ); - const table = await Table.build(page, ListPage_Advisory.tableAriaLabel); + const listPage = await AdvisoryListPage.build(page); + + const toolbar = await listPage.getToolbar(); + const table = await listPage.getTable(); // Full search - await toolbar.applyTextFilter( - ListPage_Advisory.filters.filterText, - "CVE-2024-26308" - ); + await toolbar.applyTextFilter("Filter text", "CVE-2024-26308"); await table.waitUntilDataIsLoaded(); await table.verifyColumnContainsText("ID", "CVE-2024-26308"); // Date filter - await toolbar.applyDateRangeFilter( - ListPage_Advisory.filters.revision, - "08/01/2024", - "08/03/2024" - ); + 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(ListPage_Advisory.filters.label, [ - "type=cve", - ]); + 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 index 90278e1..18f6fb9 100644 --- a/tests/ui/pages/advisory-list/pagination.spec.ts +++ b/tests/ui/pages/advisory-list/pagination.spec.ts @@ -3,34 +3,26 @@ import { test } from "@playwright/test"; import { login } from "../../helpers/Auth"; -import { ListPage_Advisory } from "../Constants"; -import { Navigation } from "../Navigation"; -import { Pagination } from "../Pagination"; -import { Table } from "../Table"; +import { AdvisoryListPage } from "./AdvisoryListPage"; test.describe("Pagination validations", { tag: "@tier1" }, () => { test.beforeEach(async ({ page }) => { await login(page); - - const navigation = await Navigation.build(page); - await navigation.goToSidebar("Advisories"); }); test("Navigation button validations", async ({ page }) => { - const pagination = await Pagination.build( - page, - ListPage_Advisory.paginationIdTop - ); + const listPage = await AdvisoryListPage.build(page); + + const pagination = await listPage.getPagination(); await pagination.validatePagination(); }); test("Items per page validations", async ({ page }) => { - const pagination = await Pagination.build( - page, - ListPage_Advisory.paginationIdTop - ); + const listPage = await AdvisoryListPage.build(page); + + const pagination = await listPage.getPagination(); + const table = await listPage.getTable(); - const table = await Table.build(page, ListPage_Advisory.tableAriaLabel); 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 index 23886af..7ea8cc6 100644 --- a/tests/ui/pages/advisory-list/sort.spec.ts +++ b/tests/ui/pages/advisory-list/sort.spec.ts @@ -3,21 +3,19 @@ import { expect, test } from "@playwright/test"; import { login } from "../../helpers/Auth"; -import { isSorted, ListPage_Advisory } from "../Constants"; -import { Navigation } from "../Navigation"; -import { Table } from "../Table"; +import { isSorted } from "../Constants"; +import { AdvisoryListPage } from "./AdvisoryListPage"; test.describe("Sort validations", { tag: "@tier1" }, () => { test.beforeEach(async ({ page }) => { await login(page); - - const navigation = await Navigation.build(page); - await navigation.goToSidebar("Advisories"); }); // skipped until it is fixed in the backend. It is bug, fix it test.skip("Sort", async ({ page }) => { - const table = await Table.build(page, ListPage_Advisory.tableAriaLabel); + const listPage = await AdvisoryListPage.build(page); + const table = await listPage.getTable(); + const columnNameSelector = table._table.locator(`td[data-label="ID"]`); // ID Asc 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 index 5b5b5f4..04008a4 100644 --- a/tests/ui/pages/package-list/columns.spec.ts +++ b/tests/ui/pages/package-list/columns.spec.ts @@ -3,46 +3,40 @@ import { expect, test } from "@playwright/test"; import { login } from "../../helpers/Auth"; -import { ListPage_Package } from "../Constants"; -import { Navigation } from "../Navigation"; -import { Table } from "../Table"; -import { Toolbar } from "../Toolbar"; +import { PackageListPage } from "./PackageListPage"; test.describe("Columns validations", { tag: "@tier1" }, () => { test.beforeEach(async ({ page }) => { await login(page); - - const navigation = await Navigation.build(page); - await navigation.goToSidebar("Packages"); }); test("Columns", async ({ page }) => { - const toolbar = await Toolbar.build( - page, - ListPage_Package.toolbarAriaLabel - ); - const table = await Table.build(page, ListPage_Package.tableAriaLabel); + const listPage = await PackageListPage.build(page); + + const toolbar = await listPage.getToolbar(); + const table = await listPage.getTable(); // Full search - await toolbar.applyTextFilter( - ListPage_Package.filters.filterText, - "keycloak-core" - ); + 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"); diff --git a/tests/ui/pages/package-list/filter.spec.ts b/tests/ui/pages/package-list/filter.spec.ts index 3ab7a88..e8a711f 100644 --- a/tests/ui/pages/package-list/filter.spec.ts +++ b/tests/ui/pages/package-list/filter.spec.ts @@ -3,44 +3,31 @@ import { test } from "@playwright/test"; import { login } from "../../helpers/Auth"; -import { ListPage_Package } from "../Constants"; -import { Navigation } from "../Navigation"; -import { Table } from "../Table"; -import { Toolbar } from "../Toolbar"; +import { PackageListPage } from "./PackageListPage"; test.describe("Filter validations", { tag: "@tier1" }, () => { test.beforeEach(async ({ page }) => { await login(page); - - const navigation = await Navigation.build(page); - await navigation.goToSidebar("Packages"); }); test("Filters", async ({ page }) => { - const toolbar = await Toolbar.build(page, ListPage_Package.toolbarAriaLabel); - const table = await Table.build(page, ListPage_Package.tableAriaLabel); + const listPage = await PackageListPage.build(page); + + const toolbar = await listPage.getToolbar(); + const table = await listPage.getTable(); // Full search - await toolbar.applyTextFilter( - ListPage_Package.filters.filterText, - "keycloak-core" - ); + await toolbar.applyTextFilter("Filter text", "keycloak-core"); await table.waitUntilDataIsLoaded(); await table.verifyColumnContainsText("Name", "keycloak-core"); // Type filter - await toolbar.applyMultiSelectFilter(ListPage_Package.filters.type, [ - "Maven", - "RPM", - ]); + await toolbar.applyMultiSelectFilter("Type", ["Maven", "RPM"]); await table.waitUntilDataIsLoaded(); await table.verifyColumnContainsText("Name", "keycloak-core"); // Architecture - await toolbar.applyMultiSelectFilter(ListPage_Package.filters.architecture, [ - "S390", - "No Arch", - ]); + 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 index d32f249..979b5e9 100644 --- a/tests/ui/pages/package-list/pagination.spec.ts +++ b/tests/ui/pages/package-list/pagination.spec.ts @@ -3,34 +3,26 @@ import { test } from "@playwright/test"; import { login } from "../../helpers/Auth"; -import { ListPage_Package } from "../Constants"; -import { Navigation } from "../Navigation"; -import { Pagination } from "../Pagination"; -import { Table } from "../Table"; +import { PackageListPage } from "./PackageListPage"; test.describe("Pagination validations", { tag: "@tier1" }, () => { test.beforeEach(async ({ page }) => { await login(page); - - const navigation = await Navigation.build(page); - await navigation.goToSidebar("Packages"); }); test("Navigation button validations", async ({ page }) => { - const pagination = await Pagination.build( - page, - ListPage_Package.paginationIdTop - ); + const listPage = await PackageListPage.build(page); + const pagination = await listPage.getPagination(); + await pagination.validatePagination(); }); test("Items per page validations", async ({ page }) => { - const pagination = await Pagination.build( - page, - ListPage_Package.paginationIdTop - ); + const listPage = await PackageListPage.build(page); + + const pagination = await listPage.getPagination(); + const table = await listPage.getTable(); - const table = await Table.build(page, ListPage_Package.tableAriaLabel); 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 index 1d75dd0..f6c79d3 100644 --- a/tests/ui/pages/package-list/sort.spec.ts +++ b/tests/ui/pages/package-list/sort.spec.ts @@ -3,20 +3,18 @@ import { expect, test } from "@playwright/test"; import { login } from "../../helpers/Auth"; -import { isSorted, ListPage_Package } from "../Constants"; -import { Navigation } from "../Navigation"; -import { Table } from "../Table"; +import { isSorted } from "../Constants"; +import { PackageListPage } from "./PackageListPage"; test.describe("Sort validations", { tag: "@tier1" }, () => { test.beforeEach(async ({ page }) => { await login(page); - - const navigation = await Navigation.build(page); - await navigation.goToSidebar("Packages"); }); test("Sort", async ({ page }) => { - const table = await Table.build(page, ListPage_Package.tableAriaLabel); + const listPage = await PackageListPage.build(page); + const table = await listPage.getTable(); + const columnNameSelector = table._table.locator(`td[data-label="ID"]`); // ID Asc diff --git a/tests/ui/pages/sbom-details/package/filter.spec.ts b/tests/ui/pages/sbom-details/package/filter.spec.ts index c0839fa..a52a96d 100644 --- a/tests/ui/pages/sbom-details/package/filter.spec.ts +++ b/tests/ui/pages/sbom-details/package/filter.spec.ts @@ -4,7 +4,6 @@ import { test } from "@playwright/test"; import { login } from "../../../helpers/Auth"; import { DetailsPage_SBOM } from "../../Constants"; -import { Navigation } from "../../Navigation"; import { Table } from "../../Table"; import { Toolbar } from "../../Toolbar"; import { SbomDetailsPage } from "../SbomDetailsPage"; 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 index a660c79..845c6a8 100644 --- a/tests/ui/pages/sbom-list/actions.spec.ts +++ b/tests/ui/pages/sbom-list/actions.spec.ts @@ -3,21 +3,17 @@ import { expect, test } from "@playwright/test"; import { login } from "../../helpers/Auth"; -import { ListPage_SBOM } from "../Constants"; -import { Navigation } from "../Navigation"; -import { Table } from "../Table"; import { LabelsModal } from "../LabelsModal"; +import { SbomListPage } from "./SbomListPage"; test.describe("Action validations", { tag: "@tier1" }, () => { test.beforeEach(async ({ page }) => { await login(page); - - const navigation = await Navigation.build(page); - await navigation.goToSidebar("SBOMs"); }); test("Actions - Download SBOM", async ({ page }) => { - const table = await Table.build(page, ListPage_SBOM.tableAriaLabel); + const listPage = await SbomListPage.build(page); + const table = await listPage.getTable(); const sbomNames = await table._table .locator(`td[data-label="Name"]`) @@ -31,7 +27,8 @@ test.describe("Action validations", { tag: "@tier1" }, () => { }); test("Actions - Download License Report", async ({ page }) => { - const table = await Table.build(page, ListPage_SBOM.tableAriaLabel); + const listPage = await SbomListPage.build(page); + const table = await listPage.getTable(); const sbomNames = await table._table .locator(`td[data-label="Name"]`) @@ -45,9 +42,10 @@ test.describe("Action validations", { tag: "@tier1" }, () => { }); test("Actions - Edit Labels", async ({ page }) => { - const labels = ["color=red", "production"]; + const listPage = await SbomListPage.build(page); + const table = await listPage.getTable(); - const table = await Table.build(page, ListPage_SBOM.tableAriaLabel); + const labels = ["color=red", "production"]; await table.clickAction("Edit labels", 0); let labelsModal = await LabelsModal.build(page); diff --git a/tests/ui/pages/sbom-list/columns.spec.ts b/tests/ui/pages/sbom-list/columns.spec.ts index 78d101d..3b30946 100644 --- a/tests/ui/pages/sbom-list/columns.spec.ts +++ b/tests/ui/pages/sbom-list/columns.spec.ts @@ -3,28 +3,21 @@ import { expect, test } from "@playwright/test"; import { login } from "../../helpers/Auth"; -import { ListPage_SBOM } from "../Constants"; -import { Navigation } from "../Navigation"; -import { Table } from "../Table"; -import { Toolbar } from "../Toolbar"; +import { SbomListPage } from "./SbomListPage"; test.describe("Columns validations", { tag: "@tier1" }, () => { test.beforeEach(async ({ page }) => { await login(page); - - const navigation = await Navigation.build(page); - await navigation.goToSidebar("SBOMs"); }); test("Vulnerabilities", async ({ page }) => { - const toolbar = await Toolbar.build(page, ListPage_SBOM.toolbarAriaLabel); - const table = await Table.build(page, ListPage_SBOM.tableAriaLabel); + const listPage = await SbomListPage.build(page); + + const toolbar = await listPage.getToolbar(); + const table = await listPage.getTable(); // Full search - await toolbar.applyTextFilter( - ListPage_SBOM.filters.filterText, - "quarkus-bom" - ); + await toolbar.applyTextFilter("Filter text", "quarkus-bom"); await table.waitUntilDataIsLoaded(); await table.verifyColumnContainsText("Name", "quarkus-bom"); diff --git a/tests/ui/pages/sbom-list/filter.spec.ts b/tests/ui/pages/sbom-list/filter.spec.ts index 4d4b24c..cd11a9a 100644 --- a/tests/ui/pages/sbom-list/filter.spec.ts +++ b/tests/ui/pages/sbom-list/filter.spec.ts @@ -3,31 +3,27 @@ import { test } from "@playwright/test"; import { login } from "../../helpers/Auth"; -import { ListPage_SBOM } from "../Constants"; -import { Navigation } from "../Navigation"; -import { Table } from "../Table"; -import { Toolbar } from "../Toolbar"; +import { SbomListPage } from "./SbomListPage"; test.describe("Filter validations", { tag: "@tier1" }, () => { test.beforeEach(async ({ page }) => { await login(page); - - const navigation = await Navigation.build(page); - await navigation.goToSidebar("SBOMs"); }); test("Filters", async ({ page }) => { - const toolbar = await Toolbar.build(page, ListPage_SBOM.toolbarAriaLabel); - const table = await Table.build(page, ListPage_SBOM.tableAriaLabel); + const listPage = await SbomListPage.build(page); + + const toolbar = await listPage.getToolbar(); + const table = await listPage.getTable(); // Full search - await toolbar.applyTextFilter(ListPage_SBOM.filters.filterText, "quarkus"); + await toolbar.applyTextFilter("Filter text", "quarkus"); await table.waitUntilDataIsLoaded(); await table.verifyColumnContainsText("Name", "quarkus-bom"); // Date filter await toolbar.applyDateRangeFilter( - ListPage_SBOM.filters.createdOn, + "Created on", "11/21/2023", "11/23/2023" ); @@ -35,7 +31,7 @@ test.describe("Filter validations", { tag: "@tier1" }, () => { await table.verifyColumnContainsText("Name", "quarkus-bom"); // Labels filter - await toolbar.applyLabelsFilter(ListPage_SBOM.filters.label, ["type=spdx"]); + 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 index f67f255..7a3b270 100644 --- a/tests/ui/pages/sbom-list/pagination.spec.ts +++ b/tests/ui/pages/sbom-list/pagination.spec.ts @@ -3,34 +3,26 @@ import { test } from "@playwright/test"; import { login } from "../../helpers/Auth"; -import { Navigation } from "../Navigation"; -import { ListPage_SBOM } from "../Constants"; -import { Table } from "../Table"; -import { Pagination } from "../Pagination"; +import { SbomListPage } from "./SbomListPage"; test.describe("Pagination validations", { tag: "@tier1" }, () => { test.beforeEach(async ({ page }) => { await login(page); - - const navigation = await Navigation.build(page); - await navigation.goToSidebar("SBOMs"); }); test("Navigation button validations", async ({ page }) => { - const pagination = await Pagination.build( - page, - ListPage_SBOM.paginationIdTop - ); + const listPage = await SbomListPage.build(page); + const pagination = await listPage.getPagination(); + await pagination.validatePagination(); }); test("Items per page validations", async ({ page }) => { - const pagination = await Pagination.build( - page, - ListPage_SBOM.paginationIdTop - ); + const listPage = await SbomListPage.build(page); + + const pagination = await listPage.getPagination(); + const table = await listPage.getTable(); - const table = await Table.build(page, ListPage_SBOM.tableAriaLabel); 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 index e13eae8..bbc86c0 100644 --- a/tests/ui/pages/sbom-list/sort.spec.ts +++ b/tests/ui/pages/sbom-list/sort.spec.ts @@ -3,20 +3,18 @@ import { expect, test } from "@playwright/test"; import { login } from "../../helpers/Auth"; -import { Navigation } from "../Navigation"; -import { isSorted, ListPage_SBOM } from "../Constants"; -import { Table } from "../Table"; +import { isSorted } from "../Constants"; +import { SbomListPage } from "./SbomListPage"; test.describe("Sort validations", { tag: "@tier1" }, () => { test.beforeEach(async ({ page }) => { await login(page); - - const navigation = await Navigation.build(page); - await navigation.goToSidebar("SBOMs"); }); test("Sort", async ({ page }) => { - const table = await Table.build(page, ListPage_SBOM.tableAriaLabel); + const listPage = await SbomListPage.build(page); + const table = await listPage.getTable(); + const columnNameSelector = table._table.locator(`td[data-label="Name"]`); const namesAsc = await columnNameSelector.allInnerTexts(); 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 index 19e0612..9cc9421 100644 --- a/tests/ui/pages/vulnerability-list/columns.spec.ts +++ b/tests/ui/pages/vulnerability-list/columns.spec.ts @@ -3,31 +3,21 @@ import { expect, test } from "@playwright/test"; import { login } from "../../helpers/Auth"; -import { ListPage_Vulnerability } from "../Constants"; -import { Navigation } from "../Navigation"; -import { Table } from "../Table"; -import { Toolbar } from "../Toolbar"; +import { VulnerabilityListPage } from "./VulnerabilityListPage"; test.describe("Columns validations", { tag: "@tier1" }, () => { test.beforeEach(async ({ page }) => { await login(page); - - const navigation = await Navigation.build(page); - await navigation.goToSidebar("Vulnerabilities"); }); test("Impacted SBOMs", async ({ page }) => { - const toolbar = await Toolbar.build( - page, - ListPage_Vulnerability.toolbarAriaLabel - ); - const table = await Table.build(page, ListPage_Vulnerability.tableAriaLabel); + const listPage = await VulnerabilityListPage.build(page); + + const toolbar = await listPage.getToolbar(); + const table = await listPage.getTable(); // Full search - await toolbar.applyTextFilter( - ListPage_Vulnerability.filters.filterText, - "CVE-2024-26308" - ); + await toolbar.applyTextFilter("Filter text", "CVE-2024-26308"); await table.waitUntilDataIsLoaded(); await table.verifyColumnContainsText("ID", "CVE-2024-26308"); diff --git a/tests/ui/pages/vulnerability-list/filter.spec.ts b/tests/ui/pages/vulnerability-list/filter.spec.ts index 01304b4..104322a 100644 --- a/tests/ui/pages/vulnerability-list/filter.spec.ts +++ b/tests/ui/pages/vulnerability-list/filter.spec.ts @@ -3,45 +3,32 @@ import { test } from "@playwright/test"; import { login } from "../../helpers/Auth"; -import { ListPage_Vulnerability } from "../Constants"; -import { Navigation } from "../Navigation"; -import { Table } from "../Table"; -import { Toolbar } from "../Toolbar"; +import { VulnerabilityListPage } from "./VulnerabilityListPage"; test.describe("Filter validations", { tag: "@tier1" }, () => { test.beforeEach(async ({ page }) => { await login(page); - - const navigation = await Navigation.build(page); - await navigation.goToSidebar("Vulnerabilities"); }); test("Filters", async ({ page }) => { - const toolbar = await Toolbar.build( - page, - ListPage_Vulnerability.toolbarAriaLabel - ); - const table = await Table.build(page, ListPage_Vulnerability.tableAriaLabel); + const listPage = await VulnerabilityListPage.build(page); + + const toolbar = await listPage.getToolbar(); + const table = await listPage.getTable(); // Full search - await toolbar.applyTextFilter( - ListPage_Vulnerability.filters.filterText, - "CVE-2024-26308" - ); + await toolbar.applyTextFilter("Filter text", "CVE-2024-26308"); await table.waitUntilDataIsLoaded(); await table.verifyColumnContainsText("ID", "CVE-2024-26308"); // Severity filter - await toolbar.applyMultiSelectFilter( - ListPage_Vulnerability.filters.severity, - ["Unknown", "None"] - ); + await toolbar.applyMultiSelectFilter("CVSS", ["Unknown", "None"]); await table.waitUntilDataIsLoaded(); await table.verifyColumnContainsText("ID", "CVE-2024-26308"); // Date filter await toolbar.applyDateRangeFilter( - ListPage_Vulnerability.filters.createdOn, + "Created on", "02/18/2024", "02/20/2024" ); diff --git a/tests/ui/pages/vulnerability-list/pagination.spec.ts b/tests/ui/pages/vulnerability-list/pagination.spec.ts index 5942640..1cfb2fc 100644 --- a/tests/ui/pages/vulnerability-list/pagination.spec.ts +++ b/tests/ui/pages/vulnerability-list/pagination.spec.ts @@ -3,34 +3,26 @@ import { test } from "@playwright/test"; import { login } from "../../helpers/Auth"; -import { ListPage_Vulnerability } from "../Constants"; -import { Navigation } from "../Navigation"; -import { Pagination } from "../Pagination"; -import { Table } from "../Table"; +import { VulnerabilityListPage } from "./VulnerabilityListPage"; test.describe("Pagination validations", { tag: "@tier1" }, () => { test.beforeEach(async ({ page }) => { await login(page); - - const navigation = await Navigation.build(page); - await navigation.goToSidebar("Vulnerabilities"); }); test("Navigation button validations", async ({ page }) => { - const pagination = await Pagination.build( - page, - ListPage_Vulnerability.paginationIdTop - ); + const listPage = await VulnerabilityListPage.build(page); + const pagination = await listPage.getPagination(); + await pagination.validatePagination(); }); test("Items per page validations", async ({ page }) => { - const pagination = await Pagination.build( - page, - ListPage_Vulnerability.paginationIdTop - ); + const listPage = await VulnerabilityListPage.build(page); + + const pagination = await listPage.getPagination(); + const table = await listPage.getTable(); - const table = await Table.build(page, ListPage_Vulnerability.tableAriaLabel); 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 index 21d9723..d7544cb 100644 --- a/tests/ui/pages/vulnerability-list/sort.spec.ts +++ b/tests/ui/pages/vulnerability-list/sort.spec.ts @@ -3,20 +3,18 @@ import { expect, test } from "@playwright/test"; import { login } from "../../helpers/Auth"; -import { Navigation } from "../Navigation"; -import { isSorted, ListPage_Vulnerability } from "../Constants"; -import { Table } from "../Table"; +import { isSorted } from "../Constants"; +import { VulnerabilityListPage } from "./VulnerabilityListPage"; test.describe("Sort validations", { tag: "@tier1" }, () => { test.beforeEach(async ({ page }) => { await login(page); - - const navigation = await Navigation.build(page); - await navigation.goToSidebar("Vulnerabilities"); }); test("Sort", async ({ page }) => { - const table = await Table.build(page, ListPage_Vulnerability.tableAriaLabel); + const listPage = await VulnerabilityListPage.build(page); + const table = await listPage.getTable(); + const columnNameSelector = table._table.locator(`td[data-label="ID"]`); // ID Asc From 1e92bc37861f4ece746df814e8d8c4f2690b5bf0 Mon Sep 17 00:00:00 2001 From: Carlos Feria <2582866+carlosthe19916@users.noreply.github.com> Date: Sat, 28 Jun 2025 08:12:55 +0200 Subject: [PATCH 16/23] make sbom packge tab reusable --- tests/ui/helpers/ToolbarTable.ts | 2 +- tests/ui/pages/Constants.ts | 13 ------- tests/ui/pages/DetailsPageLayout.ts | 2 +- tests/ui/pages/LabelsModal.ts | 2 +- tests/ui/pages/Navigation.ts | 2 +- tests/ui/pages/Pagination.ts | 2 +- tests/ui/pages/Table.ts | 2 +- tests/ui/pages/Toolbar.ts | 2 +- .../ui/pages/sbom-details/SbomDetailsPage.ts | 13 +++---- .../pages/sbom-details/package/PackageTab.ts | 37 +++++++++++++++++++ .../sbom-details/package/columns.spec.ts | 23 +++--------- .../pages/sbom-details/package/filter.spec.ts | 31 +++++----------- .../sbom-details/package/pagination.spec.ts | 34 ++++------------- .../pages/sbom-details/package/sort.spec.ts | 13 ++----- 14 files changed, 76 insertions(+), 102 deletions(-) create mode 100644 tests/ui/pages/sbom-details/package/PackageTab.ts 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/Constants.ts b/tests/ui/pages/Constants.ts index a565b53..afc2e55 100644 --- a/tests/ui/pages/Constants.ts +++ b/tests/ui/pages/Constants.ts @@ -1,16 +1,3 @@ -export const DetailsPage_SBOM = { - packagesTab: { - toolbarAriaLabel: "Package toolbar", - tableAriaLabel: "Package table", - paginationIdTop: "package-table-pagination-top", - paginationIdBottom: "package-table-pagination-bottom", - filters: { - filterText: "Filter text", - license: "License", - }, - }, -}; - export const isSorted = (arr: string[], asc: boolean) => { let sorted = [...arr].sort((a, b) => a.localeCompare(b)); if (!asc) { diff --git a/tests/ui/pages/DetailsPageLayout.ts b/tests/ui/pages/DetailsPageLayout.ts index e4f2eeb..ad344ae 100644 --- a/tests/ui/pages/DetailsPageLayout.ts +++ b/tests/ui/pages/DetailsPageLayout.ts @@ -1,7 +1,7 @@ import { expect, Page } from "@playwright/test"; export class DetailsPageLayout { - private _page: Page; + private readonly _page: Page; private constructor(page: Page) { this._page = page; diff --git a/tests/ui/pages/LabelsModal.ts b/tests/ui/pages/LabelsModal.ts index 4c6775a..fd8b3c6 100644 --- a/tests/ui/pages/LabelsModal.ts +++ b/tests/ui/pages/LabelsModal.ts @@ -2,7 +2,7 @@ import { Locator, Page } from "playwright-core"; import { expect } from "playwright/test"; export class LabelsModal { - private _page: Page; + private readonly _page: Page; private _dialog: Locator; private constructor(page: Page, dialog: Locator) { diff --git a/tests/ui/pages/Navigation.ts b/tests/ui/pages/Navigation.ts index 0f5cb20..cedd817 100644 --- a/tests/ui/pages/Navigation.ts +++ b/tests/ui/pages/Navigation.ts @@ -4,7 +4,7 @@ import { Page } from "playwright-core"; * Used to navigate to different pages */ export class Navigation { - private _page: Page; + private readonly _page: Page; private constructor(page: Page) { this._page = page; diff --git a/tests/ui/pages/Pagination.ts b/tests/ui/pages/Pagination.ts index 61bb7cd..bdcdfd4 100644 --- a/tests/ui/pages/Pagination.ts +++ b/tests/ui/pages/Pagination.ts @@ -2,7 +2,7 @@ import { expect, Locator, Page } from "@playwright/test"; import { Table } from "./Table"; export class Pagination { - private _page: Page; + private readonly _page: Page; _pagination: Locator; private constructor(page: Page, pagination: Locator) { diff --git a/tests/ui/pages/Table.ts b/tests/ui/pages/Table.ts index c325117..5f02463 100644 --- a/tests/ui/pages/Table.ts +++ b/tests/ui/pages/Table.ts @@ -1,7 +1,7 @@ import { expect, Locator, Page } from "@playwright/test"; export class Table { - private _page: Page; + private readonly _page: Page; _table: Locator; private constructor(page: Page, table: Locator) { diff --git a/tests/ui/pages/Toolbar.ts b/tests/ui/pages/Toolbar.ts index 04fb18b..7479759 100644 --- a/tests/ui/pages/Toolbar.ts +++ b/tests/ui/pages/Toolbar.ts @@ -1,7 +1,7 @@ import { expect, Locator, Page } from "@playwright/test"; export class Toolbar { - private _page: Page; + private readonly _page: Page; _toolbar: Locator; private constructor(page: Page, toolbar: Locator) { diff --git a/tests/ui/pages/sbom-details/SbomDetailsPage.ts b/tests/ui/pages/sbom-details/SbomDetailsPage.ts index 460c361..4ab1072 100644 --- a/tests/ui/pages/sbom-details/SbomDetailsPage.ts +++ b/tests/ui/pages/sbom-details/SbomDetailsPage.ts @@ -1,12 +1,10 @@ import { expect, Page } from "@playwright/test"; -import { ListPage_SBOM } from "../Constants"; import { DetailsPageLayout } from "../DetailsPageLayout"; import { Navigation } from "../Navigation"; -import { Table } from "../Table"; -import { Toolbar } from "../Toolbar"; +import { SbomListPage } from "../sbom-list/SbomListPage"; export class SbomDetailsPage { - private _page: Page; + private readonly _page: Page; _layout: DetailsPageLayout; private constructor(page: Page, layout: DetailsPageLayout) { @@ -18,10 +16,11 @@ export class SbomDetailsPage { const navigation = await Navigation.build(page); await navigation.goToSidebar("SBOMs"); - const toolbar = await Toolbar.build(page, ListPage_SBOM.toolbarAriaLabel); - const table = await Table.build(page, ListPage_SBOM.tableAriaLabel); + const listPage = await SbomListPage.build(page); + const toolbar = await listPage.getToolbar(); + const table = await listPage.getTable(); - await toolbar.applyTextFilter(ListPage_SBOM.filters.filterText, sbomName); + await toolbar.applyTextFilter("Filter text", sbomName); await table.waitUntilDataIsLoaded(); await table.verifyColumnContainsText("Name", sbomName); diff --git a/tests/ui/pages/sbom-details/package/PackageTab.ts b/tests/ui/pages/sbom-details/package/PackageTab.ts new file mode 100644 index 0000000..eef9a47 --- /dev/null +++ b/tests/ui/pages/sbom-details/package/PackageTab.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 PackageTab { + 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 PackageTab(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/package/columns.spec.ts b/tests/ui/pages/sbom-details/package/columns.spec.ts index efbd195..10e5cb1 100644 --- a/tests/ui/pages/sbom-details/package/columns.spec.ts +++ b/tests/ui/pages/sbom-details/package/columns.spec.ts @@ -3,10 +3,7 @@ import { expect, test } from "@playwright/test"; import { login } from "../../../helpers/Auth"; -import { DetailsPage_SBOM } from "../../Constants"; -import { Table } from "../../Table"; -import { Toolbar } from "../../Toolbar"; -import { SbomDetailsPage } from "../SbomDetailsPage"; +import { PackageTab } from "./PackageTab"; test.describe("Columns validations", { tag: "@tier1" }, () => { test.beforeEach(async ({ page }) => { @@ -14,23 +11,13 @@ test.describe("Columns validations", { tag: "@tier1" }, () => { }); test("Vulnerabilities", async ({ page }) => { - const detailsPage = await SbomDetailsPage.build(page, "quarkus-bom"); - await detailsPage._layout.selectTab("Packages"); + const packageTab = await PackageTab.build(page, "quarkus-bom"); - const toolbar = await Toolbar.build( - page, - DetailsPage_SBOM.packagesTab.toolbarAriaLabel - ); - const table = await Table.build( - page, - DetailsPage_SBOM.packagesTab.tableAriaLabel - ); + const toolbar = await packageTab.getToolbar(); + const table = await packageTab.getTable(); // Full search - await toolbar.applyTextFilter( - DetailsPage_SBOM.packagesTab.filters.filterText, - "commons-compress" - ); + await toolbar.applyTextFilter("Filter text", "commons-compress"); await table.waitUntilDataIsLoaded(); await table.verifyColumnContainsText("Name", "commons-compress"); diff --git a/tests/ui/pages/sbom-details/package/filter.spec.ts b/tests/ui/pages/sbom-details/package/filter.spec.ts index a52a96d..04e94b8 100644 --- a/tests/ui/pages/sbom-details/package/filter.spec.ts +++ b/tests/ui/pages/sbom-details/package/filter.spec.ts @@ -3,10 +3,7 @@ import { test } from "@playwright/test"; import { login } from "../../../helpers/Auth"; -import { DetailsPage_SBOM } from "../../Constants"; -import { Table } from "../../Table"; -import { Toolbar } from "../../Toolbar"; -import { SbomDetailsPage } from "../SbomDetailsPage"; +import { PackageTab } from "./PackageTab"; test.describe("Filter validations", { tag: "@tier1" }, () => { test.beforeEach(async ({ page }) => { @@ -14,31 +11,21 @@ test.describe("Filter validations", { tag: "@tier1" }, () => { }); test("Filters", async ({ page }) => { - const detailsPage = await SbomDetailsPage.build(page, "quarkus-bom"); - await detailsPage._layout.selectTab("Packages"); + const packageTab = await PackageTab.build(page, "quarkus-bom"); - const toolbar = await Toolbar.build( - page, - DetailsPage_SBOM.packagesTab.toolbarAriaLabel - ); - const table = await Table.build( - page, - DetailsPage_SBOM.packagesTab.tableAriaLabel - ); + const toolbar = await packageTab.getToolbar(); + const table = await packageTab.getTable(); // Full search - await toolbar.applyTextFilter( - DetailsPage_SBOM.packagesTab.filters.filterText, - "commons-compress" - ); + await toolbar.applyTextFilter("Filter text", "commons-compress"); await table.waitUntilDataIsLoaded(); await table.verifyColumnContainsText("Name", "commons-compress"); // Labels filter - await toolbar.applyMultiSelectFilter( - DetailsPage_SBOM.packagesTab.filters.license, - ["Apache-2.0", "NOASSERTION"] - ); + 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/package/pagination.spec.ts b/tests/ui/pages/sbom-details/package/pagination.spec.ts index 5002517..444534b 100644 --- a/tests/ui/pages/sbom-details/package/pagination.spec.ts +++ b/tests/ui/pages/sbom-details/package/pagination.spec.ts @@ -3,44 +3,26 @@ import { test } from "@playwright/test"; import { login } from "../../../helpers/Auth"; -import { DetailsPage_SBOM } from "../../Constants"; -import { Navigation } from "../../Navigation"; -import { Pagination } from "../../Pagination"; -import { Table } from "../../Table"; -import { SbomDetailsPage } from "../SbomDetailsPage"; +import { PackageTab } from "./PackageTab"; test.describe("Pagination validations", { tag: "@tier1" }, () => { test.beforeEach(async ({ page }) => { await login(page); - - const navigation = await Navigation.build(page); - await navigation.goToSidebar("SBOMs"); }); test("Navigation button validations", async ({ page }) => { - const detailsPage = await SbomDetailsPage.build(page, "quarkus-bom"); - await detailsPage._layout.selectTab("Packages"); + const packageTab = await PackageTab.build(page, "quarkus-bom"); + const pagination = await packageTab.getPagination(); - const pagination = await Pagination.build( - page, - DetailsPage_SBOM.packagesTab.paginationIdTop - ); await pagination.validatePagination(); }); test("Items per page validations", async ({ page }) => { - const detailsPage = await SbomDetailsPage.build(page, "quarkus-bom"); - await detailsPage._layout.selectTab("Packages"); - - const pagination = await Pagination.build( - page, - DetailsPage_SBOM.packagesTab.paginationIdTop - ); - - const table = await Table.build( - page, - DetailsPage_SBOM.packagesTab.tableAriaLabel - ); + const packageTab = await PackageTab.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/package/sort.spec.ts b/tests/ui/pages/sbom-details/package/sort.spec.ts index fd22432..7d262b3 100644 --- a/tests/ui/pages/sbom-details/package/sort.spec.ts +++ b/tests/ui/pages/sbom-details/package/sort.spec.ts @@ -3,9 +3,8 @@ import { expect, test } from "@playwright/test"; import { login } from "../../../helpers/Auth"; -import { DetailsPage_SBOM, isSorted } from "../../Constants"; -import { Table } from "../../Table"; -import { SbomDetailsPage } from "../SbomDetailsPage"; +import { PackageTab } from "./PackageTab"; +import { isSorted } from "../../Constants"; test.describe("Sort validations", { tag: "@tier1" }, () => { test.beforeEach(async ({ page }) => { @@ -13,13 +12,9 @@ test.describe("Sort validations", { tag: "@tier1" }, () => { }); test("Sort", async ({ page }) => { - const detailsPage = await SbomDetailsPage.build(page, "quarkus-bom"); - await detailsPage._layout.selectTab("Packages"); + const packageTab = await PackageTab.build(page, "quarkus-bom"); + const table = await packageTab.getTable(); - const table = await Table.build( - page, - DetailsPage_SBOM.packagesTab.tableAriaLabel - ); const columnNameSelector = table._table.locator(`td[data-label="Name"]`); const namesAsc = await columnNameSelector.allInnerTexts(); From 3f52c69f18bd1a109178e52758827c7433217835 Mon Sep 17 00:00:00 2001 From: Carlos Feria <2582866+carlosthe19916@users.noreply.github.com> Date: Sat, 28 Jun 2025 09:19:55 +0200 Subject: [PATCH 17/23] Add vulnerability table tests --- tests/ui/pages/Constants.ts | 22 +++++- tests/ui/pages/advisory-list/sort.spec.ts | 14 ++-- tests/ui/pages/package-list/sort.spec.ts | 12 ++-- .../pages/sbom-details/package/sort.spec.ts | 13 ++-- .../vulnerability/VulnerabilityTab.ts | 37 ++++++++++ .../vulnerability/columns.spec.ts | 67 +++++++++++++++++++ .../sbom-details/vulnerability/filter.spec.ts | 30 +++++++++ .../vulnerability/pagination.spec.ts | 28 ++++++++ .../sbom-details/vulnerability/sort.spec.ts | 28 ++++++++ tests/ui/pages/sbom-list/sort.spec.ts | 13 ++-- .../ui/pages/vulnerability-list/sort.spec.ts | 15 +++-- 11 files changed, 242 insertions(+), 37 deletions(-) create mode 100644 tests/ui/pages/sbom-details/vulnerability/VulnerabilityTab.ts create mode 100644 tests/ui/pages/sbom-details/vulnerability/columns.spec.ts create mode 100644 tests/ui/pages/sbom-details/vulnerability/filter.spec.ts create mode 100644 tests/ui/pages/sbom-details/vulnerability/pagination.spec.ts create mode 100644 tests/ui/pages/sbom-details/vulnerability/sort.spec.ts diff --git a/tests/ui/pages/Constants.ts b/tests/ui/pages/Constants.ts index afc2e55..bd7b4b1 100644 --- a/tests/ui/pages/Constants.ts +++ b/tests/ui/pages/Constants.ts @@ -1,7 +1,23 @@ -export const isSorted = (arr: string[], asc: boolean) => { - let sorted = [...arr].sort((a, b) => a.localeCompare(b)); +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(); } - return arr.every((val, i) => val === sorted[i]); + 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/advisory-list/sort.spec.ts b/tests/ui/pages/advisory-list/sort.spec.ts index 7ea8cc6..f625bc9 100644 --- a/tests/ui/pages/advisory-list/sort.spec.ts +++ b/tests/ui/pages/advisory-list/sort.spec.ts @@ -1,9 +1,9 @@ // @ts-check -import { expect, test } from "@playwright/test"; +import { test } from "@playwright/test"; import { login } from "../../helpers/Auth"; -import { isSorted } from "../Constants"; +import { expectSort } from "../Constants"; import { AdvisoryListPage } from "./AdvisoryListPage"; test.describe("Sort validations", { tag: "@tier1" }, () => { @@ -11,7 +11,7 @@ test.describe("Sort validations", { tag: "@tier1" }, () => { await login(page); }); - // skipped until it is fixed in the backend. It is bug, fix it + // TODO test.skip("Sort", async ({ page }) => { const listPage = await AdvisoryListPage.build(page); const table = await listPage.getTable(); @@ -20,12 +20,12 @@ test.describe("Sort validations", { tag: "@tier1" }, () => { // ID Asc await table.clickSortBy("ID"); - const namesAsc = await columnNameSelector.allInnerTexts(); - expect(isSorted(namesAsc, true), "").toBe(true); + const ascList = await columnNameSelector.allInnerTexts(); + expectSort(ascList, true); // ID Desc await table.clickSortBy("ID"); - const namesDesc = await columnNameSelector.allInnerTexts(); - expect(isSorted(namesDesc, false)).toBe(true); + const descList = await columnNameSelector.allInnerTexts(); + expectSort(descList, false); }); }); diff --git a/tests/ui/pages/package-list/sort.spec.ts b/tests/ui/pages/package-list/sort.spec.ts index f6c79d3..b3fb1a0 100644 --- a/tests/ui/pages/package-list/sort.spec.ts +++ b/tests/ui/pages/package-list/sort.spec.ts @@ -1,9 +1,9 @@ // @ts-check -import { expect, test } from "@playwright/test"; +import { test } from "@playwright/test"; import { login } from "../../helpers/Auth"; -import { isSorted } from "../Constants"; +import { expectSort } from "../Constants"; import { PackageListPage } from "./PackageListPage"; test.describe("Sort validations", { tag: "@tier1" }, () => { @@ -18,12 +18,12 @@ test.describe("Sort validations", { tag: "@tier1" }, () => { const columnNameSelector = table._table.locator(`td[data-label="ID"]`); // ID Asc - const namesAsc = await columnNameSelector.allInnerTexts(); - expect(isSorted(namesAsc, true)).toBe(true); + const ascList = await columnNameSelector.allInnerTexts(); + expectSort(ascList, true); // ID Desc await table.clickSortBy("Name"); - const namesDesc = await columnNameSelector.allInnerTexts(); - expect(isSorted(namesDesc, false)).toBe(true); + const descList = await columnNameSelector.allInnerTexts(); + expectSort(descList, false); }); }); diff --git a/tests/ui/pages/sbom-details/package/sort.spec.ts b/tests/ui/pages/sbom-details/package/sort.spec.ts index 7d262b3..532d1ef 100644 --- a/tests/ui/pages/sbom-details/package/sort.spec.ts +++ b/tests/ui/pages/sbom-details/package/sort.spec.ts @@ -1,10 +1,10 @@ // @ts-check -import { expect, test } from "@playwright/test"; +import { test } from "@playwright/test"; import { login } from "../../../helpers/Auth"; import { PackageTab } from "./PackageTab"; -import { isSorted } from "../../Constants"; +import { expectSort } from "../../Constants"; test.describe("Sort validations", { tag: "@tier1" }, () => { test.beforeEach(async ({ page }) => { @@ -17,13 +17,12 @@ test.describe("Sort validations", { tag: "@tier1" }, () => { const columnNameSelector = table._table.locator(`td[data-label="Name"]`); - const namesAsc = await columnNameSelector.allInnerTexts(); - expect(isSorted(namesAsc, true)).toBe(true); + const ascList = await columnNameSelector.allInnerTexts(); + expectSort(ascList, true); // Reverse sorting await table.clickSortBy("Name"); - const namesDesc = await columnNameSelector.allInnerTexts(); - - expect(isSorted(namesDesc, false)).toBe(true); + const descList = await columnNameSelector.allInnerTexts(); + expectSort(descList, false); }); }); diff --git a/tests/ui/pages/sbom-details/vulnerability/VulnerabilityTab.ts b/tests/ui/pages/sbom-details/vulnerability/VulnerabilityTab.ts new file mode 100644 index 0000000..3c5a274 --- /dev/null +++ b/tests/ui/pages/sbom-details/vulnerability/VulnerabilityTab.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 VulnerabilityTab { + 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 VulnerabilityTab(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/vulnerability/columns.spec.ts b/tests/ui/pages/sbom-details/vulnerability/columns.spec.ts new file mode 100644 index 0000000..2b7b9ee --- /dev/null +++ b/tests/ui/pages/sbom-details/vulnerability/columns.spec.ts @@ -0,0 +1,67 @@ +// @ts-check + +import { expect, test } from "@playwright/test"; + +import { login } from "../../../helpers/Auth"; +import { VulnerabilityTab } from "./VulnerabilityTab"; + +test.describe("Columns validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test("Columns", async ({ page }) => { + const vulnerabilityTab = await VulnerabilityTab.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/vulnerability/filter.spec.ts b/tests/ui/pages/sbom-details/vulnerability/filter.spec.ts new file mode 100644 index 0000000..ca45940 --- /dev/null +++ b/tests/ui/pages/sbom-details/vulnerability/filter.spec.ts @@ -0,0 +1,30 @@ +// @ts-check + +import { test } from "@playwright/test"; + +import { login } from "../../../helpers/Auth"; +import { VulnerabilityTab } from "./VulnerabilityTab"; + +test.describe("Filter validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + // Currently tab has no filters + test.skip("Filters", async ({ page }) => { + const vulnerabilityTab = await VulnerabilityTab.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/vulnerability/pagination.spec.ts b/tests/ui/pages/sbom-details/vulnerability/pagination.spec.ts new file mode 100644 index 0000000..a064597 --- /dev/null +++ b/tests/ui/pages/sbom-details/vulnerability/pagination.spec.ts @@ -0,0 +1,28 @@ +// @ts-check + +import { test } from "@playwright/test"; + +import { login } from "../../../helpers/Auth"; +import { VulnerabilityTab } from "./VulnerabilityTab"; + +test.describe("Pagination validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test("Navigation button validations", async ({ page }) => { + const vulnerabilityTab = await VulnerabilityTab.build(page, "quarkus-bom"); + const pagination = await vulnerabilityTab.getPagination(); + + await pagination.validatePagination(); + }); + + test("Items per page validations", async ({ page }) => { + const packageTab = await VulnerabilityTab.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/vulnerability/sort.spec.ts b/tests/ui/pages/sbom-details/vulnerability/sort.spec.ts new file mode 100644 index 0000000..45bccc6 --- /dev/null +++ b/tests/ui/pages/sbom-details/vulnerability/sort.spec.ts @@ -0,0 +1,28 @@ +// @ts-check + +import { test } from "@playwright/test"; + +import { login } from "../../../helpers/Auth"; +import { VulnerabilityTab } from "./VulnerabilityTab"; +import { expectSort } from "../../Constants"; + +test.describe("Sort validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test("Sort", async ({ page }) => { + const vulnerabilityTab = await VulnerabilityTab.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/sort.spec.ts b/tests/ui/pages/sbom-list/sort.spec.ts index bbc86c0..48afdee 100644 --- a/tests/ui/pages/sbom-list/sort.spec.ts +++ b/tests/ui/pages/sbom-list/sort.spec.ts @@ -1,9 +1,9 @@ // @ts-check -import { expect, test } from "@playwright/test"; +import { test } from "@playwright/test"; import { login } from "../../helpers/Auth"; -import { isSorted } from "../Constants"; +import { expectSort } from "../Constants"; import { SbomListPage } from "./SbomListPage"; test.describe("Sort validations", { tag: "@tier1" }, () => { @@ -17,13 +17,12 @@ test.describe("Sort validations", { tag: "@tier1" }, () => { const columnNameSelector = table._table.locator(`td[data-label="Name"]`); - const namesAsc = await columnNameSelector.allInnerTexts(); - expect(isSorted(namesAsc, true)).toBe(true); + const ascList = await columnNameSelector.allInnerTexts(); + expectSort(ascList, true); // Reverse sorting await table.clickSortBy("Name"); - const namesDesc = await columnNameSelector.allInnerTexts(); - - expect(isSorted(namesDesc, false)).toBe(true); + const desList = await columnNameSelector.allInnerTexts(); + expectSort(desList, false); }); }); diff --git a/tests/ui/pages/vulnerability-list/sort.spec.ts b/tests/ui/pages/vulnerability-list/sort.spec.ts index d7544cb..326bf36 100644 --- a/tests/ui/pages/vulnerability-list/sort.spec.ts +++ b/tests/ui/pages/vulnerability-list/sort.spec.ts @@ -1,9 +1,9 @@ // @ts-check -import { expect, test } from "@playwright/test"; +import { test } from "@playwright/test"; import { login } from "../../helpers/Auth"; -import { isSorted } from "../Constants"; +import { expectSort } from "../Constants"; import { VulnerabilityListPage } from "./VulnerabilityListPage"; test.describe("Sort validations", { tag: "@tier1" }, () => { @@ -11,7 +11,8 @@ test.describe("Sort validations", { tag: "@tier1" }, () => { await login(page); }); - test("Sort", async ({ page }) => { + // TODO + test.skip("Sort", async ({ page }) => { const listPage = await VulnerabilityListPage.build(page); const table = await listPage.getTable(); @@ -19,12 +20,12 @@ test.describe("Sort validations", { tag: "@tier1" }, () => { // ID Asc await table.clickSortBy("ID"); - const namesAsc = await columnNameSelector.allInnerTexts(); - expect(isSorted(namesAsc, true)).toBe(true); + const ascList = await columnNameSelector.allInnerTexts(); + expectSort(ascList, true); // ID Desc await table.clickSortBy("ID"); - const namesDesc = await columnNameSelector.allInnerTexts(); - expect(isSorted(namesDesc, false)).toBe(true); + const desList = await columnNameSelector.allInnerTexts(); + expectSort(desList, false); }); }); From 6c5e8d8b90cb887ed6507f3c0cad9a3437a8acd4 Mon Sep 17 00:00:00 2001 From: Carlos Feria <2582866+carlosthe19916@users.noreply.github.com> Date: Sat, 28 Jun 2025 09:28:13 +0200 Subject: [PATCH 18/23] Add sbom vulnerability donut chart test --- .../vulnerability/donutchart.spec.ts | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 tests/ui/pages/sbom-details/vulnerability/donutchart.spec.ts diff --git a/tests/ui/pages/sbom-details/vulnerability/donutchart.spec.ts b/tests/ui/pages/sbom-details/vulnerability/donutchart.spec.ts new file mode 100644 index 0000000..7494122 --- /dev/null +++ b/tests/ui/pages/sbom-details/vulnerability/donutchart.spec.ts @@ -0,0 +1,23 @@ +// @ts-check + +import { expect, test } from "@playwright/test"; + +import { login } from "../../../helpers/Auth"; +import { VulnerabilityTab } from "./VulnerabilityTab"; + +test.describe("DonutChart validations", { tag: "@tier1" }, () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test("Vulnerabilities", async ({ page }) => { + await VulnerabilityTab.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"); + }); +}); From 4b2f4b6f90b08ec3cbc875d5e649997f83090009 Mon Sep 17 00:00:00 2001 From: Carlos Feria <2582866+carlosthe19916@users.noreply.github.com> Date: Sat, 28 Jun 2025 14:07:25 +0200 Subject: [PATCH 19/23] Add more tests --- .../package-details/PackageDetailsPage.ts | 37 ++++++++++ .../pages/package-details/info/info.spec.ts | 17 +++++ .../pages/package-details/sboms/SbomsTab.ts | 37 ++++++++++ .../package-details/sboms/columns.spec.ts | 38 +++++++++++ .../package-details/sboms/filter.spec.ts | 17 +++++ .../package-details/sboms/pagination.spec.ts | 30 ++++++++ .../pages/package-details/sboms/sort.spec.ts | 28 ++++++++ .../vulnerabilities/VulnerabilitiesTab.ts | 37 ++++++++++ .../vulnerabilities/columns.spec.ts | 36 ++++++++++ .../vulnerabilities/filter.spec.ts | 17 +++++ .../vulnerabilities/pagination.spec.ts | 36 ++++++++++ .../vulnerabilities/sort.spec.ts | 28 ++++++++ .../ui/pages/sbom-details/SbomDetailsPage.ts | 2 +- .../PackageTab.ts => packages/PackagesTab.ts} | 4 +- .../{package => packages}/columns.spec.ts | 6 +- .../{package => packages}/filter.spec.ts | 4 +- .../{package => packages}/pagination.spec.ts | 6 +- .../{package => packages}/sort.spec.ts | 4 +- .../VulnerabilitiesTab.ts} | 4 +- .../columns.spec.ts | 4 +- .../donutchart.spec.ts | 4 +- .../filter.spec.ts | 4 +- .../pagination.spec.ts | 6 +- .../sort.spec.ts | 4 +- .../VulnerabilityDetailsPage.ts | 38 +++++++++++ .../advisories/AdvisoriesTab.ts | 37 ++++++++++ .../advisories/columns.spec.ts | 48 +++++++++++++ .../advisories/filter.spec.ts | 17 +++++ .../advisories/pagination.spec.ts | 30 ++++++++ .../advisories/sort.spec.ts | 28 ++++++++ .../vulnerability-details/info/info.spec.ts | 17 +++++ .../vulnerability-details/sboms/SbomsTab.ts | 37 ++++++++++ .../sboms/columns.spec.ts | 68 +++++++++++++++++++ .../sboms/filter.spec.ts | 17 +++++ .../sboms/pagination.spec.ts | 30 ++++++++ .../vulnerability-details/sboms/sort.spec.ts | 28 ++++++++ 36 files changed, 779 insertions(+), 26 deletions(-) create mode 100644 tests/ui/pages/package-details/PackageDetailsPage.ts create mode 100644 tests/ui/pages/package-details/info/info.spec.ts create mode 100644 tests/ui/pages/package-details/sboms/SbomsTab.ts create mode 100644 tests/ui/pages/package-details/sboms/columns.spec.ts create mode 100644 tests/ui/pages/package-details/sboms/filter.spec.ts create mode 100644 tests/ui/pages/package-details/sboms/pagination.spec.ts create mode 100644 tests/ui/pages/package-details/sboms/sort.spec.ts create mode 100644 tests/ui/pages/package-details/vulnerabilities/VulnerabilitiesTab.ts create mode 100644 tests/ui/pages/package-details/vulnerabilities/columns.spec.ts create mode 100644 tests/ui/pages/package-details/vulnerabilities/filter.spec.ts create mode 100644 tests/ui/pages/package-details/vulnerabilities/pagination.spec.ts create mode 100644 tests/ui/pages/package-details/vulnerabilities/sort.spec.ts rename tests/ui/pages/sbom-details/{package/PackageTab.ts => packages/PackagesTab.ts} (92%) rename tests/ui/pages/sbom-details/{package => packages}/columns.spec.ts (91%) rename tests/ui/pages/sbom-details/{package => packages}/filter.spec.ts (87%) rename tests/ui/pages/sbom-details/{package => packages}/pagination.spec.ts (77%) rename tests/ui/pages/sbom-details/{package => packages}/sort.spec.ts (86%) rename tests/ui/pages/sbom-details/{vulnerability/VulnerabilityTab.ts => vulnerabilities/VulnerabilitiesTab.ts} (91%) rename tests/ui/pages/sbom-details/{vulnerability => vulnerabilities}/columns.spec.ts (92%) rename tests/ui/pages/sbom-details/{vulnerability => vulnerabilities}/donutchart.spec.ts (87%) rename tests/ui/pages/sbom-details/{vulnerability => vulnerabilities}/filter.spec.ts (85%) rename tests/ui/pages/sbom-details/{vulnerability => vulnerabilities}/pagination.spec.ts (74%) rename tests/ui/pages/sbom-details/{vulnerability => vulnerabilities}/sort.spec.ts (83%) create mode 100644 tests/ui/pages/vulnerability-details/VulnerabilityDetailsPage.ts create mode 100644 tests/ui/pages/vulnerability-details/advisories/AdvisoriesTab.ts create mode 100644 tests/ui/pages/vulnerability-details/advisories/columns.spec.ts create mode 100644 tests/ui/pages/vulnerability-details/advisories/filter.spec.ts create mode 100644 tests/ui/pages/vulnerability-details/advisories/pagination.spec.ts create mode 100644 tests/ui/pages/vulnerability-details/advisories/sort.spec.ts create mode 100644 tests/ui/pages/vulnerability-details/info/info.spec.ts create mode 100644 tests/ui/pages/vulnerability-details/sboms/SbomsTab.ts create mode 100644 tests/ui/pages/vulnerability-details/sboms/columns.spec.ts create mode 100644 tests/ui/pages/vulnerability-details/sboms/filter.spec.ts create mode 100644 tests/ui/pages/vulnerability-details/sboms/pagination.spec.ts create mode 100644 tests/ui/pages/vulnerability-details/sboms/sort.spec.ts diff --git a/tests/ui/pages/package-details/PackageDetailsPage.ts b/tests/ui/pages/package-details/PackageDetailsPage.ts new file mode 100644 index 0000000..978703a --- /dev/null +++ b/tests/ui/pages/package-details/PackageDetailsPage.ts @@ -0,0 +1,37 @@ +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 expect( + page.getByRole("heading", { name: packageName }) + ).toBeVisible(); + + 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..c177226 --- /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("Labels", 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..bda8013 --- /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"; + +// Tables 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..d73294f --- /dev/null +++ b/tests/ui/pages/package-details/sboms/pagination.spec.ts @@ -0,0 +1,30 @@ +// @ts-check + +import { test } from "@playwright/test"; + +import { login } from "../../../helpers/Auth"; +import { SbomsTab } from "./SbomsTab"; + +// Number of items in table are below single page to be able to test +// Inf a Vulnerability that contains significant number of impacted SBOMs +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..b1a5614 --- /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 "../../Constants"; + +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..493b463 --- /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"; + +// 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, "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..eb3aba0 --- /dev/null +++ b/tests/ui/pages/package-details/vulnerabilities/pagination.spec.ts @@ -0,0 +1,36 @@ +// @ts-check + +import { test } from "@playwright/test"; + +import { login } from "../../../helpers/Auth"; +import { VulnerabilitiesTab } from "./VulnerabilitiesTab"; + +// Number of items in table are below single page to be able to test +// Inf a Vulnerability that contains significant number of impacted SBOMs +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..828549b --- /dev/null +++ b/tests/ui/pages/package-details/vulnerabilities/sort.spec.ts @@ -0,0 +1,28 @@ +// @ts-check + +import { test } from "@playwright/test"; + +import { login } from "../../../helpers/Auth"; +import { VulnerabilitiesTab } from "./VulnerabilitiesTab"; +import { expectSort } from "../../Constants"; + +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/sbom-details/SbomDetailsPage.ts b/tests/ui/pages/sbom-details/SbomDetailsPage.ts index 4ab1072..7de9357 100644 --- a/tests/ui/pages/sbom-details/SbomDetailsPage.ts +++ b/tests/ui/pages/sbom-details/SbomDetailsPage.ts @@ -24,7 +24,7 @@ export class SbomDetailsPage { await table.waitUntilDataIsLoaded(); await table.verifyColumnContainsText("Name", sbomName); - await page.getByRole("link", { name: "quarkus-bom", exact: true }).click(); + await page.getByRole("link", { name: sbomName, exact: true }).click(); const layout = await DetailsPageLayout.build(page); await expect(page.getByRole("heading", { name: sbomName })).toBeVisible(); diff --git a/tests/ui/pages/sbom-details/package/PackageTab.ts b/tests/ui/pages/sbom-details/packages/PackagesTab.ts similarity index 92% rename from tests/ui/pages/sbom-details/package/PackageTab.ts rename to tests/ui/pages/sbom-details/packages/PackagesTab.ts index eef9a47..41ec7fb 100644 --- a/tests/ui/pages/sbom-details/package/PackageTab.ts +++ b/tests/ui/pages/sbom-details/packages/PackagesTab.ts @@ -4,7 +4,7 @@ import { Toolbar } from "../../Toolbar"; import { Table } from "../../Table"; import { Pagination } from "../../Pagination"; -export class PackageTab { +export class PackagesTab { private readonly _page: Page; _detailsPage: SbomDetailsPage; @@ -17,7 +17,7 @@ export class PackageTab { const detailsPage = await SbomDetailsPage.build(page, sbomName); await detailsPage._layout.selectTab("Packages"); - return new PackageTab(page, detailsPage); + return new PackagesTab(page, detailsPage); } async getToolbar() { diff --git a/tests/ui/pages/sbom-details/package/columns.spec.ts b/tests/ui/pages/sbom-details/packages/columns.spec.ts similarity index 91% rename from tests/ui/pages/sbom-details/package/columns.spec.ts rename to tests/ui/pages/sbom-details/packages/columns.spec.ts index 10e5cb1..6c6f4dc 100644 --- a/tests/ui/pages/sbom-details/package/columns.spec.ts +++ b/tests/ui/pages/sbom-details/packages/columns.spec.ts @@ -3,15 +3,15 @@ import { expect, test } from "@playwright/test"; import { login } from "../../../helpers/Auth"; -import { PackageTab } from "./PackageTab"; +import { PackagesTab } from "./PackagesTab"; test.describe("Columns validations", { tag: "@tier1" }, () => { test.beforeEach(async ({ page }) => { await login(page); }); - test("Vulnerabilities", async ({ page }) => { - const packageTab = await PackageTab.build(page, "quarkus-bom"); + test("Columns", async ({ page }) => { + const packageTab = await PackagesTab.build(page, "quarkus-bom"); const toolbar = await packageTab.getToolbar(); const table = await packageTab.getTable(); diff --git a/tests/ui/pages/sbom-details/package/filter.spec.ts b/tests/ui/pages/sbom-details/packages/filter.spec.ts similarity index 87% rename from tests/ui/pages/sbom-details/package/filter.spec.ts rename to tests/ui/pages/sbom-details/packages/filter.spec.ts index 04e94b8..643d99e 100644 --- a/tests/ui/pages/sbom-details/package/filter.spec.ts +++ b/tests/ui/pages/sbom-details/packages/filter.spec.ts @@ -3,7 +3,7 @@ import { test } from "@playwright/test"; import { login } from "../../../helpers/Auth"; -import { PackageTab } from "./PackageTab"; +import { PackagesTab } from "./PackagesTab"; test.describe("Filter validations", { tag: "@tier1" }, () => { test.beforeEach(async ({ page }) => { @@ -11,7 +11,7 @@ test.describe("Filter validations", { tag: "@tier1" }, () => { }); test("Filters", async ({ page }) => { - const packageTab = await PackageTab.build(page, "quarkus-bom"); + const packageTab = await PackagesTab.build(page, "quarkus-bom"); const toolbar = await packageTab.getToolbar(); const table = await packageTab.getTable(); diff --git a/tests/ui/pages/sbom-details/package/pagination.spec.ts b/tests/ui/pages/sbom-details/packages/pagination.spec.ts similarity index 77% rename from tests/ui/pages/sbom-details/package/pagination.spec.ts rename to tests/ui/pages/sbom-details/packages/pagination.spec.ts index 444534b..54de917 100644 --- a/tests/ui/pages/sbom-details/package/pagination.spec.ts +++ b/tests/ui/pages/sbom-details/packages/pagination.spec.ts @@ -3,7 +3,7 @@ import { test } from "@playwright/test"; import { login } from "../../../helpers/Auth"; -import { PackageTab } from "./PackageTab"; +import { PackagesTab } from "./PackagesTab"; test.describe("Pagination validations", { tag: "@tier1" }, () => { test.beforeEach(async ({ page }) => { @@ -11,14 +11,14 @@ test.describe("Pagination validations", { tag: "@tier1" }, () => { }); test("Navigation button validations", async ({ page }) => { - const packageTab = await PackageTab.build(page, "quarkus-bom"); + 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 PackageTab.build(page, "quarkus-bom"); + const packageTab = await PackagesTab.build(page, "quarkus-bom"); const pagination = await packageTab.getPagination(); const table = await packageTab.getTable(); diff --git a/tests/ui/pages/sbom-details/package/sort.spec.ts b/tests/ui/pages/sbom-details/packages/sort.spec.ts similarity index 86% rename from tests/ui/pages/sbom-details/package/sort.spec.ts rename to tests/ui/pages/sbom-details/packages/sort.spec.ts index 532d1ef..4ce3e10 100644 --- a/tests/ui/pages/sbom-details/package/sort.spec.ts +++ b/tests/ui/pages/sbom-details/packages/sort.spec.ts @@ -3,7 +3,7 @@ import { test } from "@playwright/test"; import { login } from "../../../helpers/Auth"; -import { PackageTab } from "./PackageTab"; +import { PackagesTab } from "./PackagesTab"; import { expectSort } from "../../Constants"; test.describe("Sort validations", { tag: "@tier1" }, () => { @@ -12,7 +12,7 @@ test.describe("Sort validations", { tag: "@tier1" }, () => { }); test("Sort", async ({ page }) => { - const packageTab = await PackageTab.build(page, "quarkus-bom"); + const packageTab = await PackagesTab.build(page, "quarkus-bom"); const table = await packageTab.getTable(); const columnNameSelector = table._table.locator(`td[data-label="Name"]`); diff --git a/tests/ui/pages/sbom-details/vulnerability/VulnerabilityTab.ts b/tests/ui/pages/sbom-details/vulnerabilities/VulnerabilitiesTab.ts similarity index 91% rename from tests/ui/pages/sbom-details/vulnerability/VulnerabilityTab.ts rename to tests/ui/pages/sbom-details/vulnerabilities/VulnerabilitiesTab.ts index 3c5a274..197223b 100644 --- a/tests/ui/pages/sbom-details/vulnerability/VulnerabilityTab.ts +++ b/tests/ui/pages/sbom-details/vulnerabilities/VulnerabilitiesTab.ts @@ -4,7 +4,7 @@ import { Toolbar } from "../../Toolbar"; import { Table } from "../../Table"; import { Pagination } from "../../Pagination"; -export class VulnerabilityTab { +export class VulnerabilitiesTab { private readonly _page: Page; _detailsPage: SbomDetailsPage; @@ -17,7 +17,7 @@ export class VulnerabilityTab { const detailsPage = await SbomDetailsPage.build(page, sbomName); await detailsPage._layout.selectTab("Vulnerabilities"); - return new VulnerabilityTab(page, detailsPage); + return new VulnerabilitiesTab(page, detailsPage); } async getToolbar() { diff --git a/tests/ui/pages/sbom-details/vulnerability/columns.spec.ts b/tests/ui/pages/sbom-details/vulnerabilities/columns.spec.ts similarity index 92% rename from tests/ui/pages/sbom-details/vulnerability/columns.spec.ts rename to tests/ui/pages/sbom-details/vulnerabilities/columns.spec.ts index 2b7b9ee..428818b 100644 --- a/tests/ui/pages/sbom-details/vulnerability/columns.spec.ts +++ b/tests/ui/pages/sbom-details/vulnerabilities/columns.spec.ts @@ -3,7 +3,7 @@ import { expect, test } from "@playwright/test"; import { login } from "../../../helpers/Auth"; -import { VulnerabilityTab } from "./VulnerabilityTab"; +import { VulnerabilitiesTab } from "./VulnerabilitiesTab"; test.describe("Columns validations", { tag: "@tier1" }, () => { test.beforeEach(async ({ page }) => { @@ -11,7 +11,7 @@ test.describe("Columns validations", { tag: "@tier1" }, () => { }); test("Columns", async ({ page }) => { - const vulnerabilityTab = await VulnerabilityTab.build(page, "quarkus-bom"); + const vulnerabilityTab = await VulnerabilitiesTab.build(page, "quarkus-bom"); const table = await vulnerabilityTab.getTable(); diff --git a/tests/ui/pages/sbom-details/vulnerability/donutchart.spec.ts b/tests/ui/pages/sbom-details/vulnerabilities/donutchart.spec.ts similarity index 87% rename from tests/ui/pages/sbom-details/vulnerability/donutchart.spec.ts rename to tests/ui/pages/sbom-details/vulnerabilities/donutchart.spec.ts index 7494122..e4a2c0f 100644 --- a/tests/ui/pages/sbom-details/vulnerability/donutchart.spec.ts +++ b/tests/ui/pages/sbom-details/vulnerabilities/donutchart.spec.ts @@ -3,7 +3,7 @@ import { expect, test } from "@playwright/test"; import { login } from "../../../helpers/Auth"; -import { VulnerabilityTab } from "./VulnerabilityTab"; +import { VulnerabilitiesTab } from "./VulnerabilitiesTab"; test.describe("DonutChart validations", { tag: "@tier1" }, () => { test.beforeEach(async ({ page }) => { @@ -11,7 +11,7 @@ test.describe("DonutChart validations", { tag: "@tier1" }, () => { }); test("Vulnerabilities", async ({ page }) => { - await VulnerabilityTab.build(page, "quarkus-bom"); + 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"); diff --git a/tests/ui/pages/sbom-details/vulnerability/filter.spec.ts b/tests/ui/pages/sbom-details/vulnerabilities/filter.spec.ts similarity index 85% rename from tests/ui/pages/sbom-details/vulnerability/filter.spec.ts rename to tests/ui/pages/sbom-details/vulnerabilities/filter.spec.ts index ca45940..b5629c5 100644 --- a/tests/ui/pages/sbom-details/vulnerability/filter.spec.ts +++ b/tests/ui/pages/sbom-details/vulnerabilities/filter.spec.ts @@ -3,7 +3,7 @@ import { test } from "@playwright/test"; import { login } from "../../../helpers/Auth"; -import { VulnerabilityTab } from "./VulnerabilityTab"; +import { VulnerabilitiesTab } from "./VulnerabilitiesTab"; test.describe("Filter validations", { tag: "@tier1" }, () => { test.beforeEach(async ({ page }) => { @@ -12,7 +12,7 @@ test.describe("Filter validations", { tag: "@tier1" }, () => { // Currently tab has no filters test.skip("Filters", async ({ page }) => { - const vulnerabilityTab = await VulnerabilityTab.build(page, "quarkus-bom"); + const vulnerabilityTab = await VulnerabilitiesTab.build(page, "quarkus-bom"); const toolbar = await vulnerabilityTab.getToolbar(); const table = await vulnerabilityTab.getTable(); diff --git a/tests/ui/pages/sbom-details/vulnerability/pagination.spec.ts b/tests/ui/pages/sbom-details/vulnerabilities/pagination.spec.ts similarity index 74% rename from tests/ui/pages/sbom-details/vulnerability/pagination.spec.ts rename to tests/ui/pages/sbom-details/vulnerabilities/pagination.spec.ts index a064597..e02ba6c 100644 --- a/tests/ui/pages/sbom-details/vulnerability/pagination.spec.ts +++ b/tests/ui/pages/sbom-details/vulnerabilities/pagination.spec.ts @@ -3,7 +3,7 @@ import { test } from "@playwright/test"; import { login } from "../../../helpers/Auth"; -import { VulnerabilityTab } from "./VulnerabilityTab"; +import { VulnerabilitiesTab } from "./VulnerabilitiesTab"; test.describe("Pagination validations", { tag: "@tier1" }, () => { test.beforeEach(async ({ page }) => { @@ -11,14 +11,14 @@ test.describe("Pagination validations", { tag: "@tier1" }, () => { }); test("Navigation button validations", async ({ page }) => { - const vulnerabilityTab = await VulnerabilityTab.build(page, "quarkus-bom"); + 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 VulnerabilityTab.build(page, "quarkus-bom"); + const packageTab = await VulnerabilitiesTab.build(page, "quarkus-bom"); const pagination = await packageTab.getPagination(); const table = await packageTab.getTable(); diff --git a/tests/ui/pages/sbom-details/vulnerability/sort.spec.ts b/tests/ui/pages/sbom-details/vulnerabilities/sort.spec.ts similarity index 83% rename from tests/ui/pages/sbom-details/vulnerability/sort.spec.ts rename to tests/ui/pages/sbom-details/vulnerabilities/sort.spec.ts index 45bccc6..0d951bb 100644 --- a/tests/ui/pages/sbom-details/vulnerability/sort.spec.ts +++ b/tests/ui/pages/sbom-details/vulnerabilities/sort.spec.ts @@ -3,7 +3,7 @@ import { test } from "@playwright/test"; import { login } from "../../../helpers/Auth"; -import { VulnerabilityTab } from "./VulnerabilityTab"; +import { VulnerabilitiesTab } from "./VulnerabilitiesTab"; import { expectSort } from "../../Constants"; test.describe("Sort validations", { tag: "@tier1" }, () => { @@ -12,7 +12,7 @@ test.describe("Sort validations", { tag: "@tier1" }, () => { }); test("Sort", async ({ page }) => { - const vulnerabilityTab = await VulnerabilityTab.build(page, "quarkus-bom"); + const vulnerabilityTab = await VulnerabilitiesTab.build(page, "quarkus-bom"); const table = await vulnerabilityTab.getTable(); const columnNameSelector = table._table.locator(`td[data-label="Id"]`); diff --git a/tests/ui/pages/vulnerability-details/VulnerabilityDetailsPage.ts b/tests/ui/pages/vulnerability-details/VulnerabilityDetailsPage.ts new file mode 100644 index 0000000..8c9e638 --- /dev/null +++ b/tests/ui/pages/vulnerability-details/VulnerabilityDetailsPage.ts @@ -0,0 +1,38 @@ +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 expect( + page.getByRole("heading", { name: vulnerabilityID }) + ).toBeVisible(); + + 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..f054fc7 --- /dev/null +++ b/tests/ui/pages/vulnerability-details/advisories/AdvisoriesTab.ts @@ -0,0 +1,37 @@ +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..c832884 --- /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"; + +// Tables 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..132b200 --- /dev/null +++ b/tests/ui/pages/vulnerability-details/advisories/pagination.spec.ts @@ -0,0 +1,30 @@ +// @ts-check + +import { test } from "@playwright/test"; + +import { login } from "../../../helpers/Auth"; +import { AdvisoriesTab } from "./AdvisoriesTab"; + +// Number of items in table are below single page to be able to test +// Inf a Vulnerability that contains significant number of impacted SBOMs +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..1c133b0 --- /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 "../../Constants"; + +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..25d059b --- /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("Labels", 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..b08d4c1 --- /dev/null +++ b/tests/ui/pages/vulnerability-details/sboms/SbomsTab.ts @@ -0,0 +1,37 @@ +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..47c03d0 --- /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"; + +// Tables 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..0fe691f --- /dev/null +++ b/tests/ui/pages/vulnerability-details/sboms/pagination.spec.ts @@ -0,0 +1,30 @@ +// @ts-check + +import { test } from "@playwright/test"; + +import { login } from "../../../helpers/Auth"; +import { SbomsTab } from "./SbomsTab"; + +// Number of items in table are below single page to be able to test +// Inf a Vulnerability that contains significant number of impacted SBOMs +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..6786c19 --- /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 "../../Constants"; + +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); + }); +}); From 72c4faa94772ee85a10dcfba4e69ed886d5ebee6 Mon Sep 17 00:00:00 2001 From: Carlos Feria <2582866+carlosthe19916@users.noreply.github.com> Date: Sat, 28 Jun 2025 14:19:58 +0200 Subject: [PATCH 20/23] Add advisory tests --- .../advisory-details/AdvisoryDetailsPage.ts | 34 ++++++++++++++ .../pages/advisory-details/info/info.spec.ts | 45 +++++++++++++++++++ .../vulnerabilities/VulnerabilitiesTab.ts | 37 +++++++++++++++ .../vulnerabilities/columns.spec.ts | 43 ++++++++++++++++++ .../vulnerabilities/filter.spec.ts | 17 +++++++ .../vulnerabilities/pagination.spec.ts | 36 +++++++++++++++ .../vulnerabilities/sort.spec.ts | 31 +++++++++++++ .../pages/package-details/info/info.spec.ts | 2 +- .../vulnerabilities/sort.spec.ts | 5 ++- .../vulnerabilities/columns.spec.ts | 5 ++- .../vulnerabilities/filter.spec.ts | 5 ++- .../vulnerabilities/pagination.spec.ts | 5 ++- .../sbom-details/vulnerabilities/sort.spec.ts | 5 ++- .../advisories/AdvisoriesTab.ts | 5 ++- .../vulnerability-details/info/info.spec.ts | 2 +- .../vulnerability-details/sboms/SbomsTab.ts | 5 ++- 16 files changed, 273 insertions(+), 9 deletions(-) create mode 100644 tests/ui/pages/advisory-details/AdvisoryDetailsPage.ts create mode 100644 tests/ui/pages/advisory-details/info/info.spec.ts create mode 100644 tests/ui/pages/advisory-details/vulnerabilities/VulnerabilitiesTab.ts create mode 100644 tests/ui/pages/advisory-details/vulnerabilities/columns.spec.ts create mode 100644 tests/ui/pages/advisory-details/vulnerabilities/filter.spec.ts create mode 100644 tests/ui/pages/advisory-details/vulnerabilities/pagination.spec.ts create mode 100644 tests/ui/pages/advisory-details/vulnerabilities/sort.spec.ts diff --git a/tests/ui/pages/advisory-details/AdvisoryDetailsPage.ts b/tests/ui/pages/advisory-details/AdvisoryDetailsPage.ts new file mode 100644 index 0000000..e159f8c --- /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 expect(page.getByRole("heading", { name: advisoryID })).toBeVisible(); + + 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..f62bb81 --- /dev/null +++ b/tests/ui/pages/advisory-details/vulnerabilities/pagination.spec.ts @@ -0,0 +1,36 @@ +// @ts-check + +import { test } from "@playwright/test"; + +import { login } from "../../../helpers/Auth"; +import { VulnerabilitiesTab } from "./VulnerabilitiesTab"; + +// Number of items in table are below single page to be able to test +// Inf a Vulnerability that contains significant number of impacted SBOMs +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..6124727 --- /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 "../../Constants"; + +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/package-details/info/info.spec.ts b/tests/ui/pages/package-details/info/info.spec.ts index c177226..408f9f6 100644 --- a/tests/ui/pages/package-details/info/info.spec.ts +++ b/tests/ui/pages/package-details/info/info.spec.ts @@ -10,7 +10,7 @@ test.describe("Info Tab validations", { tag: "@tier1" }, () => { await login(page); }); - test("Labels", async ({ page }) => { + test("Info", async ({ page }) => { await PackageDetailsPage.build(page, "keycloak-core"); // Verify }); diff --git a/tests/ui/pages/package-details/vulnerabilities/sort.spec.ts b/tests/ui/pages/package-details/vulnerabilities/sort.spec.ts index 828549b..fb61e85 100644 --- a/tests/ui/pages/package-details/vulnerabilities/sort.spec.ts +++ b/tests/ui/pages/package-details/vulnerabilities/sort.spec.ts @@ -12,7 +12,10 @@ test.describe("Sort validations", { tag: "@tier1" }, () => { }); test("Sort", async ({ page }) => { - const vulnerabilitiesTab = await VulnerabilitiesTab.build(page, "keycloak-core"); + const vulnerabilitiesTab = await VulnerabilitiesTab.build( + page, + "keycloak-core" + ); const table = await vulnerabilitiesTab.getTable(); const columnNameSelector = table._table.locator(`td[data-label="ID"]`); diff --git a/tests/ui/pages/sbom-details/vulnerabilities/columns.spec.ts b/tests/ui/pages/sbom-details/vulnerabilities/columns.spec.ts index 428818b..328e3a8 100644 --- a/tests/ui/pages/sbom-details/vulnerabilities/columns.spec.ts +++ b/tests/ui/pages/sbom-details/vulnerabilities/columns.spec.ts @@ -11,7 +11,10 @@ test.describe("Columns validations", { tag: "@tier1" }, () => { }); test("Columns", async ({ page }) => { - const vulnerabilityTab = await VulnerabilitiesTab.build(page, "quarkus-bom"); + const vulnerabilityTab = await VulnerabilitiesTab.build( + page, + "quarkus-bom" + ); const table = await vulnerabilityTab.getTable(); diff --git a/tests/ui/pages/sbom-details/vulnerabilities/filter.spec.ts b/tests/ui/pages/sbom-details/vulnerabilities/filter.spec.ts index b5629c5..20ce0a4 100644 --- a/tests/ui/pages/sbom-details/vulnerabilities/filter.spec.ts +++ b/tests/ui/pages/sbom-details/vulnerabilities/filter.spec.ts @@ -12,7 +12,10 @@ test.describe("Filter validations", { tag: "@tier1" }, () => { // Currently tab has no filters test.skip("Filters", async ({ page }) => { - const vulnerabilityTab = await VulnerabilitiesTab.build(page, "quarkus-bom"); + const vulnerabilityTab = await VulnerabilitiesTab.build( + page, + "quarkus-bom" + ); const toolbar = await vulnerabilityTab.getToolbar(); const table = await vulnerabilityTab.getTable(); diff --git a/tests/ui/pages/sbom-details/vulnerabilities/pagination.spec.ts b/tests/ui/pages/sbom-details/vulnerabilities/pagination.spec.ts index e02ba6c..aa636f5 100644 --- a/tests/ui/pages/sbom-details/vulnerabilities/pagination.spec.ts +++ b/tests/ui/pages/sbom-details/vulnerabilities/pagination.spec.ts @@ -11,7 +11,10 @@ test.describe("Pagination validations", { tag: "@tier1" }, () => { }); test("Navigation button validations", async ({ page }) => { - const vulnerabilityTab = await VulnerabilitiesTab.build(page, "quarkus-bom"); + const vulnerabilityTab = await VulnerabilitiesTab.build( + page, + "quarkus-bom" + ); const pagination = await vulnerabilityTab.getPagination(); await pagination.validatePagination(); diff --git a/tests/ui/pages/sbom-details/vulnerabilities/sort.spec.ts b/tests/ui/pages/sbom-details/vulnerabilities/sort.spec.ts index 0d951bb..ea3482d 100644 --- a/tests/ui/pages/sbom-details/vulnerabilities/sort.spec.ts +++ b/tests/ui/pages/sbom-details/vulnerabilities/sort.spec.ts @@ -12,7 +12,10 @@ test.describe("Sort validations", { tag: "@tier1" }, () => { }); test("Sort", async ({ page }) => { - const vulnerabilityTab = await VulnerabilitiesTab.build(page, "quarkus-bom"); + const vulnerabilityTab = await VulnerabilitiesTab.build( + page, + "quarkus-bom" + ); const table = await vulnerabilityTab.getTable(); const columnNameSelector = table._table.locator(`td[data-label="Id"]`); diff --git a/tests/ui/pages/vulnerability-details/advisories/AdvisoriesTab.ts b/tests/ui/pages/vulnerability-details/advisories/AdvisoriesTab.ts index f054fc7..9d60e31 100644 --- a/tests/ui/pages/vulnerability-details/advisories/AdvisoriesTab.ts +++ b/tests/ui/pages/vulnerability-details/advisories/AdvisoriesTab.ts @@ -14,7 +14,10 @@ export class AdvisoriesTab { } static async build(page: Page, vulnerabilityID: string) { - const detailsPage = await VulnerabilityDetailsPage.build(page, vulnerabilityID); + const detailsPage = await VulnerabilityDetailsPage.build( + page, + vulnerabilityID + ); await detailsPage._layout.selectTab("Related Advisories"); return new AdvisoriesTab(page, detailsPage); diff --git a/tests/ui/pages/vulnerability-details/info/info.spec.ts b/tests/ui/pages/vulnerability-details/info/info.spec.ts index 25d059b..2c12e8b 100644 --- a/tests/ui/pages/vulnerability-details/info/info.spec.ts +++ b/tests/ui/pages/vulnerability-details/info/info.spec.ts @@ -10,7 +10,7 @@ test.describe("Info Tab validations", { tag: "@tier1" }, () => { await login(page); }); - test("Labels", async ({ 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 index b08d4c1..c118fc9 100644 --- a/tests/ui/pages/vulnerability-details/sboms/SbomsTab.ts +++ b/tests/ui/pages/vulnerability-details/sboms/SbomsTab.ts @@ -14,7 +14,10 @@ export class SbomsTab { } static async build(page: Page, vulnerabilityID: string) { - const detailsPage = await VulnerabilityDetailsPage.build(page, vulnerabilityID); + const detailsPage = await VulnerabilityDetailsPage.build( + page, + vulnerabilityID + ); await detailsPage._layout.selectTab("Related SBOMs"); return new SbomsTab(page, detailsPage); From 610ab3898fe0d1ee60c21ee83d122e0cd0e157e1 Mon Sep 17 00:00:00 2001 From: Carlos Feria <2582866+carlosthe19916@users.noreply.github.com> Date: Mon, 30 Jun 2025 10:17:26 +0200 Subject: [PATCH 21/23] Refactor a bit the code --- playwright.config.ts | 4 ---- tests/ui/pages/Table.ts | 2 +- tests/ui/pages/Toolbar.ts | 2 +- tests/ui/pages/advisory-details/AdvisoryDetailsPage.ts | 2 +- .../pages/advisory-details/vulnerabilities/pagination.spec.ts | 3 +-- tests/ui/pages/advisory-list/sort.spec.ts | 4 ++-- tests/ui/pages/package-details/PackageDetailsPage.ts | 4 +--- tests/ui/pages/package-details/sboms/filter.spec.ts | 2 +- tests/ui/pages/package-details/sboms/pagination.spec.ts | 3 +-- tests/ui/pages/package-details/vulnerabilities/filter.spec.ts | 2 +- .../pages/package-details/vulnerabilities/pagination.spec.ts | 3 +-- tests/ui/pages/sbom-details/SbomDetailsPage.ts | 2 +- tests/ui/pages/sbom-details/vulnerabilities/filter.spec.ts | 4 ++-- .../pages/vulnerability-details/VulnerabilityDetailsPage.ts | 4 +--- .../ui/pages/vulnerability-details/advisories/filter.spec.ts | 2 +- .../pages/vulnerability-details/advisories/pagination.spec.ts | 3 +-- tests/ui/pages/vulnerability-details/sboms/filter.spec.ts | 2 +- tests/ui/pages/vulnerability-details/sboms/pagination.spec.ts | 3 +-- tests/ui/pages/vulnerability-list/sort.spec.ts | 4 ++-- 19 files changed, 21 insertions(+), 34 deletions(-) diff --git a/playwright.config.ts b/playwright.config.ts index 42255bb..942bb0c 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -80,10 +80,6 @@ export default defineConfig({ ...devices["Desktop Chrome"], ...DESKTOP_CONFIG, }, - // timeout: 120_000, - // expect: { - // timeout: 20_000, - // }, }, { diff --git a/tests/ui/pages/Table.ts b/tests/ui/pages/Table.ts index 5f02463..d70e750 100644 --- a/tests/ui/pages/Table.ts +++ b/tests/ui/pages/Table.ts @@ -11,7 +11,7 @@ export class Table { /** * @param page - * @param tableAriaLabel the unique aria-label that correspont to the DOM element that contains the Table. E.g.
+ * @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) { diff --git a/tests/ui/pages/Toolbar.ts b/tests/ui/pages/Toolbar.ts index 7479759..865e84c 100644 --- a/tests/ui/pages/Toolbar.ts +++ b/tests/ui/pages/Toolbar.ts @@ -11,7 +11,7 @@ export class Toolbar { /** * @param page - * @param toolbarAriaLabel the unique aria-label that correspont to the DOM element that contains the Toobar. E.g.
+ * @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) { diff --git a/tests/ui/pages/advisory-details/AdvisoryDetailsPage.ts b/tests/ui/pages/advisory-details/AdvisoryDetailsPage.ts index e159f8c..e935e74 100644 --- a/tests/ui/pages/advisory-details/AdvisoryDetailsPage.ts +++ b/tests/ui/pages/advisory-details/AdvisoryDetailsPage.ts @@ -27,7 +27,7 @@ export class AdvisoryDetailsPage { await page.getByRole("link", { name: advisoryID, exact: true }).click(); const layout = await DetailsPageLayout.build(page); - await expect(page.getByRole("heading", { name: advisoryID })).toBeVisible(); + await layout.verifyPageHeader(advisoryID); return new AdvisoryDetailsPage(page, layout); } diff --git a/tests/ui/pages/advisory-details/vulnerabilities/pagination.spec.ts b/tests/ui/pages/advisory-details/vulnerabilities/pagination.spec.ts index f62bb81..a3a5194 100644 --- a/tests/ui/pages/advisory-details/vulnerabilities/pagination.spec.ts +++ b/tests/ui/pages/advisory-details/vulnerabilities/pagination.spec.ts @@ -5,8 +5,7 @@ import { test } from "@playwright/test"; import { login } from "../../../helpers/Auth"; import { VulnerabilitiesTab } from "./VulnerabilitiesTab"; -// Number of items in table are below single page to be able to test -// Inf a Vulnerability that contains significant number of impacted SBOMs +// Number of items less than 10, cannot tests pagination test.describe.skip("Pagination validations", { tag: "@tier1" }, () => { test.beforeEach(async ({ page }) => { await login(page); diff --git a/tests/ui/pages/advisory-list/sort.spec.ts b/tests/ui/pages/advisory-list/sort.spec.ts index f625bc9..a5d92df 100644 --- a/tests/ui/pages/advisory-list/sort.spec.ts +++ b/tests/ui/pages/advisory-list/sort.spec.ts @@ -11,8 +11,8 @@ test.describe("Sort validations", { tag: "@tier1" }, () => { await login(page); }); - // TODO - test.skip("Sort", async ({ page }) => { + // TODO: enable after https://github.com/trustification/trustify/issues/1810 is fixed + test("Sort", async ({ page }) => { const listPage = await AdvisoryListPage.build(page); const table = await listPage.getTable(); diff --git a/tests/ui/pages/package-details/PackageDetailsPage.ts b/tests/ui/pages/package-details/PackageDetailsPage.ts index 978703a..6716797 100644 --- a/tests/ui/pages/package-details/PackageDetailsPage.ts +++ b/tests/ui/pages/package-details/PackageDetailsPage.ts @@ -28,9 +28,7 @@ export class PackageDetailsPage { await page.getByRole("link", { name: packageName, exact: true }).click(); const layout = await DetailsPageLayout.build(page); - await expect( - page.getByRole("heading", { name: packageName }) - ).toBeVisible(); + await layout.verifyPageHeader(packageName); return new PackageDetailsPage(page, layout); } diff --git a/tests/ui/pages/package-details/sboms/filter.spec.ts b/tests/ui/pages/package-details/sboms/filter.spec.ts index bda8013..315657b 100644 --- a/tests/ui/pages/package-details/sboms/filter.spec.ts +++ b/tests/ui/pages/package-details/sboms/filter.spec.ts @@ -5,7 +5,7 @@ import { test } from "@playwright/test"; import { login } from "../../../helpers/Auth"; import { SbomsTab } from "./SbomsTab"; -// Tables does not have filters +// Table does not have filters test.describe.skip("Filter validations", { tag: "@tier1" }, () => { test.beforeEach(async ({ page }) => { await login(page); diff --git a/tests/ui/pages/package-details/sboms/pagination.spec.ts b/tests/ui/pages/package-details/sboms/pagination.spec.ts index d73294f..5db8d09 100644 --- a/tests/ui/pages/package-details/sboms/pagination.spec.ts +++ b/tests/ui/pages/package-details/sboms/pagination.spec.ts @@ -5,8 +5,7 @@ import { test } from "@playwright/test"; import { login } from "../../../helpers/Auth"; import { SbomsTab } from "./SbomsTab"; -// Number of items in table are below single page to be able to test -// Inf a Vulnerability that contains significant number of impacted SBOMs +// Number of items less than 10, cannot tests pagination test.describe.skip("Pagination validations", { tag: "@tier1" }, () => { test.beforeEach(async ({ page }) => { await login(page); diff --git a/tests/ui/pages/package-details/vulnerabilities/filter.spec.ts b/tests/ui/pages/package-details/vulnerabilities/filter.spec.ts index 493b463..8874412 100644 --- a/tests/ui/pages/package-details/vulnerabilities/filter.spec.ts +++ b/tests/ui/pages/package-details/vulnerabilities/filter.spec.ts @@ -5,7 +5,7 @@ import { test } from "@playwright/test"; import { login } from "../../../helpers/Auth"; import { VulnerabilitiesTab } from "./VulnerabilitiesTab"; -// Tables does not have filters +// Table does not have filters test.describe.skip("Filter validations", { tag: "@tier1" }, () => { test.beforeEach(async ({ page }) => { await login(page); diff --git a/tests/ui/pages/package-details/vulnerabilities/pagination.spec.ts b/tests/ui/pages/package-details/vulnerabilities/pagination.spec.ts index eb3aba0..a9b0ab4 100644 --- a/tests/ui/pages/package-details/vulnerabilities/pagination.spec.ts +++ b/tests/ui/pages/package-details/vulnerabilities/pagination.spec.ts @@ -5,8 +5,7 @@ import { test } from "@playwright/test"; import { login } from "../../../helpers/Auth"; import { VulnerabilitiesTab } from "./VulnerabilitiesTab"; -// Number of items in table are below single page to be able to test -// Inf a Vulnerability that contains significant number of impacted SBOMs +// Number of items less than 10, cannot tests pagination test.describe.skip("Pagination validations", { tag: "@tier1" }, () => { test.beforeEach(async ({ page }) => { await login(page); diff --git a/tests/ui/pages/sbom-details/SbomDetailsPage.ts b/tests/ui/pages/sbom-details/SbomDetailsPage.ts index 7de9357..f17bff9 100644 --- a/tests/ui/pages/sbom-details/SbomDetailsPage.ts +++ b/tests/ui/pages/sbom-details/SbomDetailsPage.ts @@ -27,7 +27,7 @@ export class SbomDetailsPage { await page.getByRole("link", { name: sbomName, exact: true }).click(); const layout = await DetailsPageLayout.build(page); - await expect(page.getByRole("heading", { name: sbomName })).toBeVisible(); + await layout.verifyPageHeader(sbomName); return new SbomDetailsPage(page, layout); } diff --git a/tests/ui/pages/sbom-details/vulnerabilities/filter.spec.ts b/tests/ui/pages/sbom-details/vulnerabilities/filter.spec.ts index 20ce0a4..25ae770 100644 --- a/tests/ui/pages/sbom-details/vulnerabilities/filter.spec.ts +++ b/tests/ui/pages/sbom-details/vulnerabilities/filter.spec.ts @@ -5,12 +5,12 @@ import { test } from "@playwright/test"; import { login } from "../../../helpers/Auth"; import { VulnerabilitiesTab } from "./VulnerabilitiesTab"; -test.describe("Filter validations", { tag: "@tier1" }, () => { +// Table has no filters +test.describe.skip("Filter validations", { tag: "@tier1" }, () => { test.beforeEach(async ({ page }) => { await login(page); }); - // Currently tab has no filters test.skip("Filters", async ({ page }) => { const vulnerabilityTab = await VulnerabilitiesTab.build( page, diff --git a/tests/ui/pages/vulnerability-details/VulnerabilityDetailsPage.ts b/tests/ui/pages/vulnerability-details/VulnerabilityDetailsPage.ts index 8c9e638..dd707e2 100644 --- a/tests/ui/pages/vulnerability-details/VulnerabilityDetailsPage.ts +++ b/tests/ui/pages/vulnerability-details/VulnerabilityDetailsPage.ts @@ -29,9 +29,7 @@ export class VulnerabilityDetailsPage { .click(); const layout = await DetailsPageLayout.build(page); - await expect( - page.getByRole("heading", { name: vulnerabilityID }) - ).toBeVisible(); + await layout.verifyPageHeader(vulnerabilityID); return new VulnerabilityDetailsPage(page, layout); } diff --git a/tests/ui/pages/vulnerability-details/advisories/filter.spec.ts b/tests/ui/pages/vulnerability-details/advisories/filter.spec.ts index c832884..219afeb 100644 --- a/tests/ui/pages/vulnerability-details/advisories/filter.spec.ts +++ b/tests/ui/pages/vulnerability-details/advisories/filter.spec.ts @@ -5,7 +5,7 @@ import { test } from "@playwright/test"; import { login } from "../../../helpers/Auth"; import { AdvisoriesTab } from "./AdvisoriesTab"; -// Tables does not have filters +// Table does not have filters test.describe.skip("Filter validations", { tag: "@tier1" }, () => { test.beforeEach(async ({ page }) => { await login(page); diff --git a/tests/ui/pages/vulnerability-details/advisories/pagination.spec.ts b/tests/ui/pages/vulnerability-details/advisories/pagination.spec.ts index 132b200..6f44686 100644 --- a/tests/ui/pages/vulnerability-details/advisories/pagination.spec.ts +++ b/tests/ui/pages/vulnerability-details/advisories/pagination.spec.ts @@ -5,8 +5,7 @@ import { test } from "@playwright/test"; import { login } from "../../../helpers/Auth"; import { AdvisoriesTab } from "./AdvisoriesTab"; -// Number of items in table are below single page to be able to test -// Inf a Vulnerability that contains significant number of impacted SBOMs +// Number of items less than 10, cannot tests pagination test.describe.skip("Pagination validations", { tag: "@tier1" }, () => { test.beforeEach(async ({ page }) => { await login(page); diff --git a/tests/ui/pages/vulnerability-details/sboms/filter.spec.ts b/tests/ui/pages/vulnerability-details/sboms/filter.spec.ts index 47c03d0..8237876 100644 --- a/tests/ui/pages/vulnerability-details/sboms/filter.spec.ts +++ b/tests/ui/pages/vulnerability-details/sboms/filter.spec.ts @@ -5,7 +5,7 @@ import { test } from "@playwright/test"; import { login } from "../../../helpers/Auth"; import { SbomsTab } from "./SbomsTab"; -// Tables does not have filters +// Table does not have filters test.describe.skip("Filter validations", { tag: "@tier1" }, () => { test.beforeEach(async ({ page }) => { await login(page); diff --git a/tests/ui/pages/vulnerability-details/sboms/pagination.spec.ts b/tests/ui/pages/vulnerability-details/sboms/pagination.spec.ts index 0fe691f..0abdb61 100644 --- a/tests/ui/pages/vulnerability-details/sboms/pagination.spec.ts +++ b/tests/ui/pages/vulnerability-details/sboms/pagination.spec.ts @@ -5,8 +5,7 @@ import { test } from "@playwright/test"; import { login } from "../../../helpers/Auth"; import { SbomsTab } from "./SbomsTab"; -// Number of items in table are below single page to be able to test -// Inf a Vulnerability that contains significant number of impacted SBOMs +// Number of items less than 10, cannot tests pagination test.describe.skip("Pagination validations", { tag: "@tier1" }, () => { test.beforeEach(async ({ page }) => { await login(page); diff --git a/tests/ui/pages/vulnerability-list/sort.spec.ts b/tests/ui/pages/vulnerability-list/sort.spec.ts index 326bf36..ef8469f 100644 --- a/tests/ui/pages/vulnerability-list/sort.spec.ts +++ b/tests/ui/pages/vulnerability-list/sort.spec.ts @@ -11,8 +11,8 @@ test.describe("Sort validations", { tag: "@tier1" }, () => { await login(page); }); - // TODO - test.skip("Sort", async ({ page }) => { + // TODO enable after https://github.com/trustification/trustify/issues/1811 is fixed + test("Sort", async ({ page }) => { const listPage = await VulnerabilityListPage.build(page); const table = await listPage.getTable(); From 180b3de6d1fa40e307dc0935d62254dfc378c69b Mon Sep 17 00:00:00 2001 From: Carlos Feria <2582866+carlosthe19916@users.noreply.github.com> Date: Mon, 30 Jun 2025 10:59:10 +0200 Subject: [PATCH 22/23] Skip tests that need fixes on rest api --- tests/ui/pages/advisory-list/sort.spec.ts | 2 +- tests/ui/pages/vulnerability-list/sort.spec.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ui/pages/advisory-list/sort.spec.ts b/tests/ui/pages/advisory-list/sort.spec.ts index a5d92df..728bfea 100644 --- a/tests/ui/pages/advisory-list/sort.spec.ts +++ b/tests/ui/pages/advisory-list/sort.spec.ts @@ -12,7 +12,7 @@ test.describe("Sort validations", { tag: "@tier1" }, () => { }); // TODO: enable after https://github.com/trustification/trustify/issues/1810 is fixed - test("Sort", async ({ page }) => { + test.skip("Sort", async ({ page }) => { const listPage = await AdvisoryListPage.build(page); const table = await listPage.getTable(); diff --git a/tests/ui/pages/vulnerability-list/sort.spec.ts b/tests/ui/pages/vulnerability-list/sort.spec.ts index ef8469f..6c0ec3d 100644 --- a/tests/ui/pages/vulnerability-list/sort.spec.ts +++ b/tests/ui/pages/vulnerability-list/sort.spec.ts @@ -12,7 +12,7 @@ test.describe("Sort validations", { tag: "@tier1" }, () => { }); // TODO enable after https://github.com/trustification/trustify/issues/1811 is fixed - test("Sort", async ({ page }) => { + test.skip("Sort", async ({ page }) => { const listPage = await VulnerabilityListPage.build(page); const table = await listPage.getTable(); From b5a2b7a024411992fec1a3249dc61a6895bf88b9 Mon Sep 17 00:00:00 2001 From: Carlos Feria <2582866+carlosthe19916@users.noreply.github.com> Date: Mon, 30 Jun 2025 11:10:24 +0200 Subject: [PATCH 23/23] Rename constants by helpers --- tests/ui/pages/{Constants.ts => Helpers.ts} | 0 tests/ui/pages/advisory-details/vulnerabilities/sort.spec.ts | 2 +- tests/ui/pages/advisory-list/sort.spec.ts | 2 +- tests/ui/pages/package-details/sboms/sort.spec.ts | 2 +- tests/ui/pages/package-details/vulnerabilities/sort.spec.ts | 2 +- tests/ui/pages/package-list/sort.spec.ts | 2 +- tests/ui/pages/sbom-details/packages/sort.spec.ts | 2 +- tests/ui/pages/sbom-details/vulnerabilities/sort.spec.ts | 2 +- tests/ui/pages/sbom-list/sort.spec.ts | 2 +- tests/ui/pages/vulnerability-details/advisories/sort.spec.ts | 2 +- tests/ui/pages/vulnerability-details/sboms/sort.spec.ts | 2 +- tests/ui/pages/vulnerability-list/sort.spec.ts | 2 +- 12 files changed, 11 insertions(+), 11 deletions(-) rename tests/ui/pages/{Constants.ts => Helpers.ts} (100%) diff --git a/tests/ui/pages/Constants.ts b/tests/ui/pages/Helpers.ts similarity index 100% rename from tests/ui/pages/Constants.ts rename to tests/ui/pages/Helpers.ts diff --git a/tests/ui/pages/advisory-details/vulnerabilities/sort.spec.ts b/tests/ui/pages/advisory-details/vulnerabilities/sort.spec.ts index 6124727..92c12f0 100644 --- a/tests/ui/pages/advisory-details/vulnerabilities/sort.spec.ts +++ b/tests/ui/pages/advisory-details/vulnerabilities/sort.spec.ts @@ -4,7 +4,7 @@ import { test } from "@playwright/test"; import { login } from "../../../helpers/Auth"; import { VulnerabilitiesTab } from "./VulnerabilitiesTab"; -import { expectSort } from "../../Constants"; +import { expectSort } from "../../Helpers"; test.describe("Sort validations", { tag: "@tier1" }, () => { test.beforeEach(async ({ page }) => { diff --git a/tests/ui/pages/advisory-list/sort.spec.ts b/tests/ui/pages/advisory-list/sort.spec.ts index 728bfea..300fd57 100644 --- a/tests/ui/pages/advisory-list/sort.spec.ts +++ b/tests/ui/pages/advisory-list/sort.spec.ts @@ -3,7 +3,7 @@ import { test } from "@playwright/test"; import { login } from "../../helpers/Auth"; -import { expectSort } from "../Constants"; +import { expectSort } from "../Helpers"; import { AdvisoryListPage } from "./AdvisoryListPage"; test.describe("Sort validations", { tag: "@tier1" }, () => { diff --git a/tests/ui/pages/package-details/sboms/sort.spec.ts b/tests/ui/pages/package-details/sboms/sort.spec.ts index b1a5614..13db16a 100644 --- a/tests/ui/pages/package-details/sboms/sort.spec.ts +++ b/tests/ui/pages/package-details/sboms/sort.spec.ts @@ -4,7 +4,7 @@ import { test } from "@playwright/test"; import { login } from "../../../helpers/Auth"; import { SbomsTab } from "./SbomsTab"; -import { expectSort } from "../../Constants"; +import { expectSort } from "../../Helpers"; test.describe("Sort validations", { tag: "@tier1" }, () => { test.beforeEach(async ({ page }) => { diff --git a/tests/ui/pages/package-details/vulnerabilities/sort.spec.ts b/tests/ui/pages/package-details/vulnerabilities/sort.spec.ts index fb61e85..da23a18 100644 --- a/tests/ui/pages/package-details/vulnerabilities/sort.spec.ts +++ b/tests/ui/pages/package-details/vulnerabilities/sort.spec.ts @@ -4,7 +4,7 @@ import { test } from "@playwright/test"; import { login } from "../../../helpers/Auth"; import { VulnerabilitiesTab } from "./VulnerabilitiesTab"; -import { expectSort } from "../../Constants"; +import { expectSort } from "../../Helpers"; test.describe("Sort validations", { tag: "@tier1" }, () => { test.beforeEach(async ({ page }) => { diff --git a/tests/ui/pages/package-list/sort.spec.ts b/tests/ui/pages/package-list/sort.spec.ts index b3fb1a0..49bf7f7 100644 --- a/tests/ui/pages/package-list/sort.spec.ts +++ b/tests/ui/pages/package-list/sort.spec.ts @@ -3,7 +3,7 @@ import { test } from "@playwright/test"; import { login } from "../../helpers/Auth"; -import { expectSort } from "../Constants"; +import { expectSort } from "../Helpers"; import { PackageListPage } from "./PackageListPage"; test.describe("Sort validations", { tag: "@tier1" }, () => { diff --git a/tests/ui/pages/sbom-details/packages/sort.spec.ts b/tests/ui/pages/sbom-details/packages/sort.spec.ts index 4ce3e10..1ef167f 100644 --- a/tests/ui/pages/sbom-details/packages/sort.spec.ts +++ b/tests/ui/pages/sbom-details/packages/sort.spec.ts @@ -4,7 +4,7 @@ import { test } from "@playwright/test"; import { login } from "../../../helpers/Auth"; import { PackagesTab } from "./PackagesTab"; -import { expectSort } from "../../Constants"; +import { expectSort } from "../../Helpers"; test.describe("Sort validations", { tag: "@tier1" }, () => { test.beforeEach(async ({ page }) => { diff --git a/tests/ui/pages/sbom-details/vulnerabilities/sort.spec.ts b/tests/ui/pages/sbom-details/vulnerabilities/sort.spec.ts index ea3482d..304d621 100644 --- a/tests/ui/pages/sbom-details/vulnerabilities/sort.spec.ts +++ b/tests/ui/pages/sbom-details/vulnerabilities/sort.spec.ts @@ -4,7 +4,7 @@ import { test } from "@playwright/test"; import { login } from "../../../helpers/Auth"; import { VulnerabilitiesTab } from "./VulnerabilitiesTab"; -import { expectSort } from "../../Constants"; +import { expectSort } from "../../Helpers"; test.describe("Sort validations", { tag: "@tier1" }, () => { test.beforeEach(async ({ page }) => { diff --git a/tests/ui/pages/sbom-list/sort.spec.ts b/tests/ui/pages/sbom-list/sort.spec.ts index 48afdee..540eb93 100644 --- a/tests/ui/pages/sbom-list/sort.spec.ts +++ b/tests/ui/pages/sbom-list/sort.spec.ts @@ -3,7 +3,7 @@ import { test } from "@playwright/test"; import { login } from "../../helpers/Auth"; -import { expectSort } from "../Constants"; +import { expectSort } from "../Helpers"; import { SbomListPage } from "./SbomListPage"; test.describe("Sort validations", { tag: "@tier1" }, () => { diff --git a/tests/ui/pages/vulnerability-details/advisories/sort.spec.ts b/tests/ui/pages/vulnerability-details/advisories/sort.spec.ts index 1c133b0..dd6739c 100644 --- a/tests/ui/pages/vulnerability-details/advisories/sort.spec.ts +++ b/tests/ui/pages/vulnerability-details/advisories/sort.spec.ts @@ -4,7 +4,7 @@ import { test } from "@playwright/test"; import { login } from "../../../helpers/Auth"; import { AdvisoriesTab } from "./AdvisoriesTab"; -import { expectSort } from "../../Constants"; +import { expectSort } from "../../Helpers"; test.describe("Sort validations", { tag: "@tier1" }, () => { test.beforeEach(async ({ page }) => { diff --git a/tests/ui/pages/vulnerability-details/sboms/sort.spec.ts b/tests/ui/pages/vulnerability-details/sboms/sort.spec.ts index 6786c19..6afbda7 100644 --- a/tests/ui/pages/vulnerability-details/sboms/sort.spec.ts +++ b/tests/ui/pages/vulnerability-details/sboms/sort.spec.ts @@ -4,7 +4,7 @@ import { test } from "@playwright/test"; import { login } from "../../../helpers/Auth"; import { SbomsTab } from "./SbomsTab"; -import { expectSort } from "../../Constants"; +import { expectSort } from "../../Helpers"; test.describe("Sort validations", { tag: "@tier1" }, () => { test.beforeEach(async ({ page }) => { diff --git a/tests/ui/pages/vulnerability-list/sort.spec.ts b/tests/ui/pages/vulnerability-list/sort.spec.ts index 6c0ec3d..75d2a2c 100644 --- a/tests/ui/pages/vulnerability-list/sort.spec.ts +++ b/tests/ui/pages/vulnerability-list/sort.spec.ts @@ -3,7 +3,7 @@ import { test } from "@playwright/test"; import { login } from "../../helpers/Auth"; -import { expectSort } from "../Constants"; +import { expectSort } from "../Helpers"; import { VulnerabilityListPage } from "./VulnerabilityListPage"; test.describe("Sort validations", { tag: "@tier1" }, () => {