Skip to content
This repository was archived by the owner on Sep 11, 2025. It is now read-only.
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
37 changes: 37 additions & 0 deletions tests/ui/features/@upload/assets/test-upload-001.spdx.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"spdxVersion": "SPDX-2.3",
"dataLicense": "CC0-1.0",
"SPDXID": "SPDXRef-SBOM-Upload-001",
"name": "test-upload-001",
"documentNamespace": "trustify-tests",
"creationInfo": {
"creators": ["trustify-tests contributors"],
"created": "2025-04-29T12:34:56Z"
},
"packages": [
{
"name": "test-upload-package-001",
"SPDXID": "SPDXRef-Package-test-upload-001",
"versionInfo": "0.0.1",
"downloadLocation": "NOASSERTION",
"sourceInfo": "written by hand",
"licenseConcluded": "NONE",
"licenseDeclared": "NOASSERTION",
"copyrightText": "NOASSERTION",
"externalRefs": [
{
"referenceCategory": "PACKAGE_MANAGER",
"referenceLocator": "pkg:trustify-tests/upload/tt-upload@0.0.1?fakeQuali=fier",
"referenceType": "purl"
}
]
}
],
"relationships": [
{
"spdxElementId": "SPDXRef-SBOM-Upload-001",
"relatedSpdxElement": "SPDXRef-Package-test-upload-001",
"relationshipType": "DESCRIBES"
}
]
}
37 changes: 37 additions & 0 deletions tests/ui/features/@upload/assets/test-upload-002.spdx.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"spdxVersion": "SPDX-2.3",
"dataLicense": "CC0-1.0",
"SPDXID": "SPDXRef-SBOM-Upload-002",
"name": "test-upload-002",
"documentNamespace": "trustify-tests",
"creationInfo": {
"creators": ["trustify-tests contributors"],
"created": "2025-04-29T12:34:56Z"
},
"packages": [
{
"name": "test-upload-package-002",
"SPDXID": "SPDXRef-Package-test-upload-002",
"versionInfo": "0.0.2",
"downloadLocation": "NOASSERTION",
"sourceInfo": "written by hand",
"licenseConcluded": "NONE",
"licenseDeclared": "NOASSERTION",
"copyrightText": "NOASSERTION",
"externalRefs": [
{
"referenceCategory": "PACKAGE_MANAGER",
"referenceLocator": "pkg:trustify-tests/upload/tt-upload@0.0.2?fakeQuali=fier",
"referenceType": "purl"
}
]
}
],
"relationships": [
{
"spdxElementId": "SPDXRef-SBOM-Upload-002",
"relatedSpdxElement": "SPDXRef-Package-test-upload-002",
"relationshipType": "DESCRIBES"
}
]
}
37 changes: 37 additions & 0 deletions tests/ui/features/@upload/assets/test-upload-003.spdx.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"spdxVersion": "SPDX-2.3",
"dataLicense": "CC0-1.0",
"SPDXID": "SPDXRef-SBOM-Upload-003",
"name": "test-upload-003",
"documentNamespace": "trustify-tests",
"creationInfo": {
"creators": ["trustify-tests contributors"],
"created": "2025-04-29T12:34:56Z"
},
"packages": [
{
"name": "test-upload-package-003",
"SPDXID": "SPDXRef-Package-test-upload-003",
"versionInfo": "0.0.3",
"downloadLocation": "NOASSERTION",
"sourceInfo": "written by hand",
"licenseConcluded": "NONE",
"licenseDeclared": "NOASSERTION",
"copyrightText": "NOASSERTION",
"externalRefs": [
{
"referenceCategory": "PACKAGE_MANAGER",
"referenceLocator": "pkg:trustify-tests/upload/tt-upload@0.0.3?fakeQuali=fier",
"referenceType": "purl"
}
]
}
],
"relationships": [
{
"spdxElementId": "SPDXRef-SBOM-Upload-003",
"relatedSpdxElement": "SPDXRef-Package-test-upload-003",
"relationshipType": "DESCRIBES"
}
]
}
Binary file not shown.
33 changes: 33 additions & 0 deletions tests/ui/features/@upload/assets/test-upload-004.cdx.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"bomFormat": "CycloneDX",
"specVersion": "1.3",
"serialNumber": "urn:uuid:00000000-1111-2222-4444-555500000004",
"version": 1,
"metadata": {
"timestamp": "2023-02-27T23:15:50-08:00",
"tools": [
{
"vendor": "trustify-tests",
"name": "contributors",
"version": "1"
}
],
"component": {
"bom-ref": "0040000000000001",
"type": "container",
"name": "test-upload-container-004",
"version": "0.0.4",
"purl": "pkg:trustify-tests/upload/tt-upload-cont@0.0.4"
}
},
"components": [
{
"bom-ref": "0040000000000002",
"type": "application",
"name": "test-upload-package-004",
"version": "0.0.4",
"cpe": "cpe:2.3:a:trustify-tests/upload:tt-upload:0.0.4:*:*:*:*:*:*:*",
"purl": "pkg:trustify-tests/upload/tt-upload@0.0.4"
}
]
}
33 changes: 33 additions & 0 deletions tests/ui/features/@upload/assets/test-upload-005.cdx.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"bomFormat": "CycloneDX",
"specVersion": "1.3",
"serialNumber": "urn:uuid:00000000-1111-2222-4444-555500000005",
"version": 1,
"metadata": {
"timestamp": "2023-02-27T23:15:50-08:00",
"tools": [
{
"vendor": "trustify-tests",
"name": "contributors",
"version": "1"
}
],
"component": {
"bom-ref": "0050000000000001",
"type": "container",
"name": "test-upload-container-005",
"version": "0.0.5",
"purl": "pkg:trustify-tests/upload/tt-upload-cont@0.0.5"
}
},
"components": [
{
"bom-ref": "0050000000000002",
"type": "application",
"name": "test-upload-package-005",
"version": "0.0.5",
"cpe": "cpe:2.3:a:trustify-tests/upload:tt-upload:0.0.5:*:*:*:*:*:*:*",
"purl": "pkg:trustify-tests/upload/tt-upload@0.0.5"
}
]
}
33 changes: 33 additions & 0 deletions tests/ui/features/@upload/assets/test-upload-006.cdx.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"bomFormat": "CycloneDX",
"specVersion": "1.3",
"serialNumber": "urn:uuid:00000000-1111-2222-4444-555500000006",
"version": 1,
"metadata": {
"timestamp": "2023-02-27T23:15:50-08:00",
"tools": [
{
"vendor": "trustify-tests",
"name": "contributors",
"version": "1"
}
],
"component": {
"bom-ref": "0060000000000001",
"type": "container",
"name": "test-upload-container-006",
"version": "0.0.6",
"purl": "pkg:trustify-tests/upload/tt-upload-cont@0.0.6"
}
},
"components": [
{
"bom-ref": "0060000000000002",
"type": "application",
"name": "test-upload-package-006",
"version": "0.0.6",
"cpe": "cpe:2.3:a:trustify-tests/upload:tt-upload:0.0.6:*:*:*:*:*:*:*",
"purl": "pkg:trustify-tests/upload/tt-upload@0.0.6"
}
]
}
Binary file not shown.
23 changes: 23 additions & 0 deletions tests/ui/features/@upload/sbom-upload.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
Feature: Upload SBOM

Background:
Given User is authenticated

Scenario Outline: Verify Upload SBOM page content
When User visits Upload page
Then SBOM upload tab is selected
And Drag and drop instructions are visible
And Upload button is present
And Accepted file types are described

Scenario Outline: Upload single SBOM
Given User visits Upload page
When User uploads "single SBOM"
Then Total uploaded count is shown for "single SBOM"
And Results of uploading "single SBOM" are visible

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For your consideration - should we also check if the SBOM is present in the DB? And should we check for both the file and the DB record, as they are two separate things? I think the former would be much harder to check for in a deployed instance, though, so maybe it's out of scope for now.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah i would consider these out of scope as these checks would need access to and knowledge of internal structures.
What could be better fit here is confirming upload/ingestion by search/viewing details of the sboms e.g. the SBOM Explorer/Details view.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, we should have the steps for that implemented, so that should be easy to do.

Scenario Outline: Upload multiple SBOMs
Given User visits Upload page
When User uploads "multiple SBOMs"
Then Total uploaded count is shown for "multiple SBOMs"
And Results of uploading "multiple SBOMs" are visible
126 changes: 126 additions & 0 deletions tests/ui/features/@upload/sbom-upload.step.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import fs from "fs";
import path from "path";
import { expect, test } from "@playwright/test";
import { createBdd } from "playwright-bdd";

export const { Given, When, Then, Step } = createBdd();

const MENU_UPLOAD = "Upload";
const HEADER_UPLOAD = "Upload";
const BUTTON_UPLOAD = "Upload";
const TAB_SBOM = "SBOM";
const DRAG_INSTRUCTIONS = "Drag and drop files here or";
const ACCEPTED_TYPES_DESC = "Accepted file types:";

const TIMEOUT_PAGELOAD_IMPORT = 2_000;
const TIMEOUT_UPLOAD_DONE = 90_000;

const SBOM_SET = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about keeping these SBOMs out of the tests/common/assets/sbom folder. I actually prefer it your way, but I'm afraid that if we start doing this, eventually everyone will be doing it their own way and it's going to introduce chaos into the project structure. If we keep all of the SBOMs in one place, it's easier to keep track and do mass operations over them. Would be nice to get other people's opinion here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason i went with placing them next to test and not in common/assets ... as they are not 'common', they should NOT be uploaded e.g. during initial ingestion or such - they are specific meant only for this test of Upload functionality.

"single SBOM": ["test-upload-001.spdx.json"],
"multiple SBOMs": [
"test-upload-002.spdx.json",
"test-upload-003.spdx.json.bz2",
"test-upload-004.cdx.json",
"test-upload-005.cdx.json",
"test-upload-006.cdx.json.bz2",
],
};

const visitUploadPage = async ({ page }) => {
await page.goto("/importers"); // dont care which page here, importers is lot faster than Dashboard
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this necessary, actually? Isn't the side menu always visible?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Menu on the left is not visible if there is no page loaded at start ... which seems there is not if auth is not enabled (so e.g. in dev/local or ci or such).

await page.getByRole("link", { name: MENU_UPLOAD }).click();

const header = page.locator(`xpath=(//h1[text()="${HEADER_UPLOAD}"])`);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a soft suggestion, but in general I would try not to rely on xpaths and CSS selectors if not necessary. You can let Playwright do the work, e.g. page.getByRole("heading"). That way we don't have to worry about paths and selectors changing as much.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will look into this, thanks!

await header.waitFor({ state: "visible", timeout: TIMEOUT_PAGELOAD_IMPORT });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if I understand this - is this a timeout for loading the page? Why specifically change it here?

};
Step("User visits Upload page", visitUploadPage);

function assetsPath(files: string[]): string[] {
return files.map((e) => path.join(__dirname, "assets", e));
}

// PAGE CONTENT

Then("SBOM upload tab is selected", async ({ page }) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already have this function here. I'd prefer not duplicating these, if possible.

async verifyTabIsSelected(tabName: string) {

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, i've missed it will switch to it.

await expect(page.getByRole("tab", { name: TAB_SBOM })).toHaveAttribute(
"aria-selected",
"true"
);
});

Then("Drag and drop instructions are visible", async ({ page }) => {
await expect(
page.getByLabel("SBOM").getByText(DRAG_INSTRUCTIONS)
).toBeVisible();
});

Then("Upload button is present", async ({ page }) => {
await expect(page.getByRole("button", { name: "Upload" })).toBeVisible();
});

Then("Accepted file types are described", async ({ page }) => {
expect(page.getByLabel("SBOM").getByText(ACCEPTED_TYPES_DESC)).toBeVisible();
});

// FILE UPLOAD

When("User uploads {string}", async ({ page }, data_set_key) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another soft suggestion - it would be easier to read without having to scroll up if instead of data_set_key the parameter was called something like option or sbom_subset.

const files = assetsPath(SBOM_SET[data_set_key]);

const fileChooserPromise = page.waitForEvent("filechooser");
await page.getByRole("button", { name: BUTTON_UPLOAD, exact: true }).click();
const fileChooser = await fileChooserPromise;

await fileChooser.setFiles(files);
});

// FILE UPLOAD RESULTS

Then(
"Total uploaded count is shown for {string}",
async ({ page }, data_set_key) => {
test.setTimeout(TIMEOUT_UPLOAD_DONE);
const count = SBOM_SET[data_set_key].length;

await expect(
page.locator(
"#upload-sbom-tab-content .pf-v6-c-multiple-file-upload__status-progress"
)
).toContainText(`${count} of ${count} files uploaded`, {
timeout: TIMEOUT_UPLOAD_DONE,
});
}
);

Then(
"Results of uploading {string} are visible",
async ({ page }, data_set_key) => {
const individual_results = page.locator(
".pf-v6-c-expandable-section__content .pf-v6-c-multiple-file-upload__status-item"
);

// expect correct count of individual result entries
await expect(individual_results).toHaveCount(SBOM_SET[data_set_key].length);

// now upload itself may take a while so increase overall test timeout
test.setTimeout(TIMEOUT_UPLOAD_DONE);

// expect name of each sbom to be in the list of results
await expect(
individual_results.locator(
".pf-v6-c-multiple-file-upload__status-item-progress-text"
)
).toContainText(SBOM_SET[data_set_key], {
timeout: TIMEOUT_UPLOAD_DONE,
});

// expect result for each sbom to be present and have 100% state
await expect(
individual_results.locator(
".pf-v6-c-progress__status .pf-v6-c-progress__measure"
)
).toContainText(new Array(SBOM_SET[data_set_key].length).fill("100%"), {
timeout: TIMEOUT_UPLOAD_DONE,
});
}
);
Loading