From ddb170340f714c2b7a8edc0f1eeb72425440863c Mon Sep 17 00:00:00 2001 From: Pavel Sedlak Date: Sat, 18 Apr 2026 11:08:19 +0000 Subject: [PATCH] test(e2e): add SBOM and advisory upload BDD tests Add comprehensive BDD test coverage for SBOM and advisory upload workflows in a shared @uploads directory with a single reusable step file. Covers navigation, page layout, single/multi file upload, invalid file handling, and file removal scenarios. Implements TC-4004 Assisted-by: Claude Code --- .../assets/advisory/cve-2022-45787.json.bz2 | Bin 0 -> 47727 bytes .../assets/advisory/cve-2023-0044.json.bz2 | Bin 0 -> 2951 bytes .../features/@uploads/advisory-upload.feature | 65 +++++++ .../ui/features/@uploads/sbom-upload.feature | 65 +++++++ e2e/tests/ui/features/@uploads/upload.step.ts | 171 ++++++++++++++++++ e2e/tests/ui/pages/FileUpload.ts | 23 ++- .../advisory-upload/AdvisoryUploadPage.ts | 4 + .../ui/pages/sbom-upload/SBOMUploadPage.ts | 4 + 8 files changed, 325 insertions(+), 7 deletions(-) create mode 100644 e2e/tests/common/assets/advisory/cve-2022-45787.json.bz2 create mode 100644 e2e/tests/common/assets/advisory/cve-2023-0044.json.bz2 create mode 100644 e2e/tests/ui/features/@uploads/advisory-upload.feature create mode 100644 e2e/tests/ui/features/@uploads/sbom-upload.feature create mode 100644 e2e/tests/ui/features/@uploads/upload.step.ts diff --git a/e2e/tests/common/assets/advisory/cve-2022-45787.json.bz2 b/e2e/tests/common/assets/advisory/cve-2022-45787.json.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..935bddb0010f5fb57ab1b64e9915952686bbf9a7 GIT binary patch literal 47727 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 literal 0 HcmV?d00001 diff --git a/e2e/tests/ui/features/@uploads/advisory-upload.feature b/e2e/tests/ui/features/@uploads/advisory-upload.feature new file mode 100644 index 000000000..c8cc76b4e --- /dev/null +++ b/e2e/tests/ui/features/@uploads/advisory-upload.feature @@ -0,0 +1,65 @@ +Feature: Advisory Upload - Upload advisory documents to Trustify + As a Trustify user + I want to upload advisory documents + So that I can manage and track security advisories + + Background: Authentication + Given User is authenticated + + Scenario: Navigate to Upload Advisory page from advisory list + When User navigates to the advisory list page + When User clicks Upload Advisory button from the toolbar + Then The application navigates to the Upload Advisory page + And The upload page heading is "Upload Advisory" + And The breadcrumb shows "Advisories" link and "Upload Advisory" as active item + + Scenario: Verify Upload Advisory page layout + Given User is on the Upload Advisory page + Then The upload page heading is "Upload Advisory" + And The supported formats description mentions "CSAF" and "CVE" + And The upload area displays "Drag and drop files here" + And The upload area displays "Accepted file types: .json, .bz2" + And The Upload button is visible in the upload area + + Scenario Outline: Upload a valid advisory file + Given User is on the Upload Advisory page + When User uploads file "" from "" + Then The upload summary shows "1 of 1 files uploaded" + And The uploaded file "" shows "success" status + + Examples: + | fileName | filePath | + | cve-2022-45787.json.bz2 | /tests/common/assets/advisory/ | + | cve-2023-0044.json.bz2 | /tests/common/assets/advisory/ | + + Scenario: Upload an invalid advisory file + Given User is on the Upload Advisory page + When User uploads file "invalid-file.json" from "/tests/common/assets/" + Then The uploaded file "invalid-file.json" shows "danger" status + + Scenario: Reject file with unsupported extension + Given User is on the Upload Advisory page + When User uploads file "invalid-file.txt" from "/tests/common/assets/" + Then The unsupported file modal is displayed containing "invalid-file.txt" + + Scenario: Upload multiple advisory files simultaneously + Given User is on the Upload Advisory page + When User uploads files "cve-2022-45787.json.bz2, cve-2023-0044.json.bz2" from "/tests/common/assets/advisory/" + Then The upload summary shows "2 of 2 files uploaded" + And The uploaded file "cve-2022-45787.json.bz2" shows "success" status + And The uploaded file "cve-2023-0044.json.bz2" shows "success" status + + Scenario: Upload mix of valid and invalid files + Given User is on the Upload Advisory page + When User uploads file "cve-2022-45787.json.bz2" from "/tests/common/assets/advisory/" + When User uploads file "invalid-file.json" from "/tests/common/assets/" + Then The upload summary shows "1 of 2 files uploaded" + And The uploaded file "cve-2022-45787.json.bz2" shows "success" status + And The uploaded file "invalid-file.json" shows "danger" status + + Scenario: Remove file from upload list + Given User is on the Upload Advisory page + When User uploads files "cve-2022-45787.json.bz2, cve-2023-0044.json.bz2" from "/tests/common/assets/advisory/" + Then The upload summary shows "2 of 2 files uploaded" + When User removes file "cve-2022-45787.json.bz2" from the upload list + Then The upload summary shows "1 of 1 files uploaded" diff --git a/e2e/tests/ui/features/@uploads/sbom-upload.feature b/e2e/tests/ui/features/@uploads/sbom-upload.feature new file mode 100644 index 000000000..94cfc9c0a --- /dev/null +++ b/e2e/tests/ui/features/@uploads/sbom-upload.feature @@ -0,0 +1,65 @@ +Feature: SBOM Upload - Upload SBOM documents to Trustify + As a Trustify user + I want to upload SBOM documents + So that I can manage and analyze my software supply chain + + Background: Authentication + Given User is authenticated + + Scenario: Navigate to Upload SBOM page from SBOM list + When User navigates to the SBOM list page + When User clicks Upload SBOM action from the toolbar + Then The application navigates to the Upload SBOM page + And The upload page heading is "Upload SBOM" + And The breadcrumb shows "SBOMs" link and "Upload SBOM" as active item + + Scenario: Verify Upload SBOM page layout + Given User is on the Upload SBOM page + Then The upload page heading is "Upload SBOM" + And The supported formats description mentions "CycloneDX" and "SPDX" + And The upload area displays "Drag and drop files here" + And The upload area displays "Accepted file types: .json, .bz2" + And The Upload button is visible in the upload area + + Scenario Outline: Upload a valid SBOM file + Given User is on the Upload SBOM page + When User uploads file "" from "" + Then The upload summary shows "1 of 1 files uploaded" + And The uploaded file "" shows "success" status + + Examples: + | fileName | filePath | + | examples_sbom.json | /tests/common/assets/sbom/ | + | example_product_quarkus.json.bz2 | /tests/common/assets/sbom/ | + + Scenario: Upload an invalid SBOM file + Given User is on the Upload SBOM page + When User uploads file "invalid-file.json" from "/tests/common/assets/" + Then The uploaded file "invalid-file.json" shows "danger" status + + Scenario: Reject file with unsupported extension + Given User is on the Upload SBOM page + When User uploads file "invalid-file.txt" from "/tests/common/assets/" + Then The unsupported file modal is displayed containing "invalid-file.txt" + + Scenario: Upload multiple SBOM files simultaneously + Given User is on the Upload SBOM page + When User uploads files "examples_sbom.json, example_product_quarkus.json.bz2" from "/tests/common/assets/sbom/" + Then The upload summary shows "2 of 2 files uploaded" + And The uploaded file "examples_sbom.json" shows "success" status + And The uploaded file "example_product_quarkus.json.bz2" shows "success" status + + Scenario: Upload mix of valid and invalid files + Given User is on the Upload SBOM page + When User uploads file "examples_sbom.json" from "/tests/common/assets/sbom/" + When User uploads file "invalid-file.json" from "/tests/common/assets/" + Then The upload summary shows "1 of 2 files uploaded" + And The uploaded file "examples_sbom.json" shows "success" status + And The uploaded file "invalid-file.json" shows "danger" status + + Scenario: Remove file from upload list + Given User is on the Upload SBOM page + When User uploads files "examples_sbom.json, example_product_quarkus.json.bz2" from "/tests/common/assets/sbom/" + Then The upload summary shows "2 of 2 files uploaded" + When User removes file "examples_sbom.json" from the upload list + Then The upload summary shows "1 of 1 files uploaded" diff --git a/e2e/tests/ui/features/@uploads/upload.step.ts b/e2e/tests/ui/features/@uploads/upload.step.ts new file mode 100644 index 000000000..8a51efc74 --- /dev/null +++ b/e2e/tests/ui/features/@uploads/upload.step.ts @@ -0,0 +1,171 @@ +import { createBdd } from "playwright-bdd"; + +import { test } from "../../fixtures"; + +import { expect } from "../../assertions"; + +import { FileUpload } from "../../pages/FileUpload"; +import { resolveAssetPath } from "../../pages/Helpers"; +import { AdvisoryListPage } from "../../pages/advisory-list/AdvisoryListPage"; +import { AdvisoryUploadPage } from "../../pages/advisory-upload/AdvisoryUploadPage"; +import { SBOMUploadPage } from "../../pages/sbom-upload/SBOMUploadPage"; +import { SbomListPage } from "../../pages/sbom-list/SbomListPage"; + +export const { Given, When, Then } = createBdd(test); + +// --- Given (feature-specific) --- + +Given("User is on the Upload Advisory page", async ({ page }) => { + await AdvisoryUploadPage.buildFromBrowserPath(page); +}); + +Given("User is on the Upload SBOM page", async ({ page }) => { + await SBOMUploadPage.buildFromBrowserPath(page); +}); + +// --- When (feature-specific navigation) --- + +When("User navigates to the advisory list page", async ({ page }) => { + await AdvisoryListPage.build(page); +}); + +When("User navigates to the SBOM list page", async ({ page }) => { + await SbomListPage.build(page); +}); + +When( + "User clicks Upload Advisory button from the toolbar", + async ({ page }) => { + const toolbar = page.locator("[aria-label='advisory-toolbar']"); + await expect(toolbar).toBeVisible(); + await toolbar.getByRole("button", { name: "Upload Advisory" }).click(); + }, +); + +When("User clicks Upload SBOM action from the toolbar", async ({ page }) => { + const listPage = await SbomListPage.fromCurrentPage(page); + const toolbar = await listPage.getToolbar(); + await toolbar.clickKebabAction("Upload SBOM"); +}); + +// --- When (shared upload actions) --- + +When( + "User uploads file {string} from {string}", + async ({ page }, fileName: string, filePath: string) => { + const fileUploader = await FileUpload.build(page); + const resolvedPath = resolveAssetPath(filePath, fileName); + await fileUploader.uploadFiles([resolvedPath]); + }, +); + +When( + "User uploads files {string} from {string}", + async ({ page }, fileNamesCsv: string, filePath: string) => { + const fileUploader = await FileUpload.build(page); + const fileNames = fileNamesCsv.split(",").map((f) => f.trim()); + const resolvedPaths = fileNames.map((fn) => resolveAssetPath(filePath, fn)); + await fileUploader.uploadFiles(resolvedPaths); + }, +); + +When( + "User removes file {string} from the upload list", + async ({ page }, fileName: string) => { + const fileUploader = await FileUpload.build(page); + const statusItem = await fileUploader.getUploadStatusItem(fileName); + await statusItem.getByRole("button", { name: "Remove from list" }).click(); + }, +); + +// --- Then (shared assertions) --- + +Then( + "The application navigates to the Upload Advisory page", + async ({ page }) => { + await expect(page).toHaveURL(/\/advisories\/upload$/); + }, +); + +Then("The application navigates to the Upload SBOM page", async ({ page }) => { + await expect(page).toHaveURL(/\/sboms\/upload$/); +}); + +Then( + "The upload page heading is {string}", + async ({ page }, heading: string) => { + await expect( + page.getByRole("heading", { level: 1, name: heading }), + ).toBeVisible(); + }, +); + +Then( + "The breadcrumb shows {string} link and {string} as active item", + async ({ page }, linkText: string, activeText: string) => { + const breadcrumb = page.locator("nav[aria-label='Breadcrumb']"); + await expect( + breadcrumb.getByRole("link", { name: linkText }), + ).toBeVisible(); + await expect(breadcrumb.getByText(activeText)).toBeVisible(); + }, +); + +Then( + "The supported formats description mentions {string} and {string}", + async ({ page }, format1: string, format2: string) => { + const description = page.getByText("Upload a"); + await expect(description).toBeVisible(); + await expect(description).toContainText(format1); + await expect(description).toContainText(format2); + }, +); + +Then("The upload area displays {string}", async ({ page }, text: string) => { + await expect(page.getByText(text)).toBeVisible(); +}); + +Then("The Upload button is visible in the upload area", async ({ page }) => { + const fileUploader = await FileUpload.build(page); + await expect(fileUploader.getUploadButton()).toBeVisible(); +}); + +Then( + "The upload summary shows {string}", + async ({ page }, summaryText: string) => { + const match = summaryText.match(/(\d+) of (\d+) files uploaded/); + if (!match) { + throw new Error(`Invalid summary format: "${summaryText}"`); + } + const successfulFiles = Number(match[1]); + const totalFiles = Number(match[2]); + + const fileUploader = await FileUpload.build(page); + await expect(fileUploader).toHaveSummaryUploadStatus({ + successfulFiles, + totalFiles, + }); + }, +); + +Then( + "The uploaded file {string} shows {string} status", + async ({ page }, fileName: string, status: string) => { + const fileUploader = await FileUpload.build(page); + await expect(fileUploader).toHaveItemUploadStatus({ + fileName, + status: status as "success" | "danger", + }); + }, +); + +Then( + "The unsupported file modal is displayed containing {string}", + async ({ page }, fileName: string) => { + const modal = page.getByRole("dialog", { + name: "unsupported file upload attempted", + }); + await expect(modal).toBeVisible(); + await expect(modal).toContainText(fileName); + }, +); diff --git a/e2e/tests/ui/pages/FileUpload.ts b/e2e/tests/ui/pages/FileUpload.ts index 20f967168..238b506d5 100644 --- a/e2e/tests/ui/pages/FileUpload.ts +++ b/e2e/tests/ui/pages/FileUpload.ts @@ -9,19 +9,28 @@ export class FileUpload { this._uploader = uploader; } - static async build(page: Page, ariaLabel: string) { - const locator = page.locator( - `div.pf-v6-c-multiple-file-upload[aria-label="${ariaLabel}"]`, - ); + static async build(page: Page, ariaLabel?: string) { + const selector = ariaLabel + ? `div.pf-v6-c-multiple-file-upload[aria-label="${ariaLabel}"]` + : "div.pf-v6-c-multiple-file-upload"; + const locator = page.locator(selector); + if (!ariaLabel) { + await expect(locator).toHaveCount(1); + } await expect(locator).toBeVisible(); return new FileUpload(page, locator); } + getUploadButton() { + return this._uploader.getByRole("button", { + name: "Upload", + exact: true, + }); + } + async uploadFiles(filePaths: string[]) { const fileChooserPromise = this._page.waitForEvent("filechooser"); - await this._uploader - .getByRole("button", { name: "Upload", exact: true }) - .click(); + await this.getUploadButton().click(); const fileChooser = await fileChooserPromise; await fileChooser.setFiles(filePaths); } diff --git a/e2e/tests/ui/pages/advisory-upload/AdvisoryUploadPage.ts b/e2e/tests/ui/pages/advisory-upload/AdvisoryUploadPage.ts index ed995143f..51fbcc8de 100644 --- a/e2e/tests/ui/pages/advisory-upload/AdvisoryUploadPage.ts +++ b/e2e/tests/ui/pages/advisory-upload/AdvisoryUploadPage.ts @@ -13,6 +13,10 @@ export class AdvisoryUploadPage { return new AdvisoryUploadPage(page); } + static fromCurrentPage(page: Page) { + return new AdvisoryUploadPage(page); + } + async getFileUploader() { return await FileUpload.build(this._page, "advisory-uploader"); } diff --git a/e2e/tests/ui/pages/sbom-upload/SBOMUploadPage.ts b/e2e/tests/ui/pages/sbom-upload/SBOMUploadPage.ts index a02dacf0e..883e6ad5c 100644 --- a/e2e/tests/ui/pages/sbom-upload/SBOMUploadPage.ts +++ b/e2e/tests/ui/pages/sbom-upload/SBOMUploadPage.ts @@ -13,6 +13,10 @@ export class SBOMUploadPage { return new SBOMUploadPage(page); } + static fromCurrentPage(page: Page) { + return new SBOMUploadPage(page); + } + async getFileUploader() { return await FileUpload.build(this._page, "sbom-uploader"); }