Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file not shown.
Binary file not shown.
65 changes: 65 additions & 0 deletions e2e/tests/ui/features/@uploads/advisory-upload.feature
Original file line number Diff line number Diff line change
@@ -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 "<fileName>" from "<filePath>"
Then The upload summary shows "1 of 1 files uploaded"
And The uploaded file "<fileName>" 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"
65 changes: 65 additions & 0 deletions e2e/tests/ui/features/@uploads/sbom-upload.feature
Original file line number Diff line number Diff line change
@@ -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 "<fileName>" from "<filePath>"
Then The upload summary shows "1 of 1 files uploaded"
And The uploaded file "<fileName>" 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"
171 changes: 171 additions & 0 deletions e2e/tests/ui/features/@uploads/upload.step.ts
Original file line number Diff line number Diff line change
@@ -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);
},
);
23 changes: 16 additions & 7 deletions e2e/tests/ui/pages/FileUpload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
4 changes: 4 additions & 0 deletions e2e/tests/ui/pages/advisory-upload/AdvisoryUploadPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
Expand Down
4 changes: 4 additions & 0 deletions e2e/tests/ui/pages/sbom-upload/SBOMUploadPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
Expand Down
Loading