Skip to content

Conversation

@carlosthe19916
Copy link
Collaborator

@carlosthe19916 carlosthe19916 commented Jan 25, 2026

Part of #889 (Phase 1)

  • Add tests to Upload SBOM
  • Add tests to Upload Advisory
  • Add FileUploadMatchers and FileUpload reusable components

Summary by Sourcery

Add reusable file upload page objects and assertions and use them to introduce end-to-end coverage for SBOM and advisory upload flows.

New Features:

  • Introduce a FileUpload page object and custom matchers to interact with and assert on the multi-file upload widget in UI tests.
  • Add shared upload test helpers to exercise parallel, sequential, removal, and invalid file upload scenarios for pages that use the file uploader.
  • Expose additional props on the UploadFiles component so callers can customize underlying PatternFly multiple file upload attributes such as ARIA labels.
  • Introduce dedicated SBOM and advisory upload page objects for navigation and file upload access in tests.

Enhancements:

  • Add ARIA labels to SBOM and advisory upload widgets to improve accessibility and enable more robust test targeting.
  • Extend the custom typedExpect wrapper to support FileUpload-specific matchers in UI tests.
  • Clean up navigation typing by removing the obsolete Upload entry from the navigation destinations.

Tests:

  • Add end-to-end tests for the SBOM upload page covering successful, failed, and invalid file upload behaviors using shared upload helpers.
  • Add end-to-end tests for the advisory upload page covering successful, failed, and invalid file upload behaviors using shared upload helpers.

Chores:

  • Add common invalid file fixtures for upload validation tests.

Signed-off-by: Carlos Feria <2582866+carlosthe19916@users.noreply.github.com>
Signed-off-by: Carlos Feria <2582866+carlosthe19916@users.noreply.github.com>
@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Jan 25, 2026

Reviewer's Guide

Adds reusable Playwright page objects, matchers, and helpers for testing SBOM and advisory file uploads, wires aria-labels into the shared UploadFiles React component so it can be targeted in tests, and introduces end-to-end coverage for success, error, removal, and invalid-file flows on both upload pages.

Sequence diagram for SBOM upload E2E test using FileUpload page object

sequenceDiagram
  actor Tester
  participant PlaywrightTest as Playwright_test
  participant SBOMUploadPage as SBOM_upload_page
  participant FileUpload as File_upload_page_object
  participant Browser as Browser_UI
  participant UploadFiles as UploadFiles_component
  participant Backend as Backend_API

  Tester->>PlaywrightTest: start_Sbom_upload_test()
  PlaywrightTest->>SBOMUploadPage: new_SBOMUploadPage(page)
  PlaywrightTest->>SBOMUploadPage: goto()
  SBOMUploadPage->>Browser: navigate_to_sbom_upload_url()

  PlaywrightTest->>FileUpload: new_FileUpload(page, ariaLabel_sbom_uploader)
  note over FileUpload,Browser: FileUpload locates uploader via aria-label sbom-uploader

  PlaywrightTest->>FileUpload: uploadFiles([sbom1.json, sbom2.json])
  FileUpload->>Browser: select_files_in_dropzone()
  Browser->>UploadFiles: onFileDrop(files)
  UploadFiles->>Backend: POST_upload_requests(files)
  Backend-->>UploadFiles: responses_per_file
  UploadFiles-->>Browser: update_status_items()

  PlaywrightTest->>FileUpload: waitForUploadComplete()
  FileUpload->>Browser: wait_for_status_items_complete()

  PlaywrightTest->>FileUpload: assertUploadSummary(2)
  FileUpload->>Browser: read_summary_counts()
  Browser-->>FileUpload: summary_data
  FileUpload-->>PlaywrightTest: summary_matches

  PlaywrightTest->>FileUpload: assertFileStatus(sbom1.json, success)
  FileUpload->>Browser: read_status_for_file(sbom1.json)
  Browser-->>FileUpload: status_success

  PlaywrightTest->>FileUpload: assertFileStatus(sbom2.json, success)
  FileUpload->>Browser: read_status_for_file(sbom2.json)
  Browser-->>FileUpload: status_success

  PlaywrightTest-->>Tester: report_test_passed()
Loading

Class diagram for updated UploadFiles component and new file upload test utilities

classDiagram

class UploadFiles {
  +uploads File[]
  +handleUpload(file File) Promise~void~
  +handleRemoveUpload(file File) void
  +extractSuccessMessage(response AxiosResponse) string
  +extractErrorMessage(error AxiosError) string
  +props MultipleFileUploadProps
}

class SbomUpload {
  +uploads File[]
  +handleUpload(file File) Promise~void~
  +handleRemoveUpload(file File) void
}

class AdvisoryUpload {
  +uploads File[]
  +handleUpload(file File) Promise~void~
  +handleRemoveUpload(file File) void
}

class MultipleFileUploadProps {
  +ariaLabel string
  +otherProps any
}

class FileUpload {
  +page Page
  +rootLocator Locator
  +uploadFiles(files string[]) Promise~void~
  +removeFile(fileName string) Promise~void~
  +waitForUploadComplete() Promise~void~
  +assertUploadSummary(expectedCount number) Promise~void~
  +assertFileStatus(fileName string, status string) Promise~void~
}

class SBOMUploadPage {
  +page Page
  +fileUpload FileUpload
  +goto() Promise~void~
}

class AdvisoryUploadPage {
  +page Page
  +fileUpload FileUpload
  +goto() Promise~void~
}

class UploadTestHelpers {
  +uploadValidFilesSequential(pageObject FileUpload, filePaths string[]) Promise~void~
  +uploadValidFilesParallel(pageObject FileUpload, filePaths string[]) Promise~void~
  +uploadInvalidFile(pageObject FileUpload, filePath string) Promise~void~
  +removeUploadedFile(pageObject FileUpload, fileName string) Promise~void~
}

class FileUploadMatchers {
  +toHaveUploadSummary(fileUpload FileUpload, expectedCount number) Promise~void~
  +toHaveFileStatus(fileUpload FileUpload, fileName string, status string) Promise~void~
}

SbomUpload --> UploadFiles : uses
AdvisoryUpload --> UploadFiles : uses
MultipleFileUploadProps <|-- UploadFiles : receives_subset

SBOMUploadPage *-- FileUpload : composes
AdvisoryUploadPage *-- FileUpload : composes

UploadTestHelpers --> FileUpload : operates_on
FileUploadMatchers --> FileUpload : asserts_on
Loading

File-Level Changes

Change Details Files
Extend custom Playwright expect typings to support FileUpload-specific matchers
  • Import FileUpload type and file upload assertions into the shared assertions index
  • Merge fileUploadAssertions into the existing merged expect extensions
  • Add an overloaded typedExpect signature for FileUpload values that exposes FileUploadMatchers
e2e/tests/ui/assertions/index.ts
Make UploadFiles React component more configurable for tests and accessibility
  • Import MultipleFileUploadProps type and add an optional props field to IUploadFilesProps, omitting onFileDrop and dropzoneProps
  • Thread the new props argument into the UploadFiles component function
  • Spread the passed props into the MultipleFileUpload component instance to allow custom attributes such as aria-label
client/src/app/components/UploadFiles.tsx
Tag SBOM and advisory upload widgets with specific aria-labels for Playwright targeting
  • Pass props with aria-label "advisory-uploader" into UploadFiles on the advisory-upload page
  • Pass props with aria-label "sbom-uploader" into UploadFiles on the sbom-upload page
client/src/app/pages/advisory-upload/advisory-upload.tsx
client/src/app/pages/sbom-upload/sbom-upload.tsx
Introduce reusable Playwright FileUpload page object and file-upload-specific custom matchers
  • Add FileUploadMatchers interface and FileUploadMatcherDefinitions wired into expect.extend for verifying summary and per-file upload status
  • Implement toHaveSummaryUploadStatus to assert the PatternFly status text for uploaded files
  • Implement toHaveItemUploadStatus to assert per-file success or danger progress state and 100% completion
  • Create a FileUpload page object that locates the upload widget by aria-label, triggers file selection via the Upload button, and finds individual status items by filename
e2e/tests/ui/assertions/FileUploadMatchers.ts
e2e/tests/ui/pages/FileUpload.ts
Add shared upload test helpers for parallel, sequential, removal, and invalid-file flows
  • Define UploadTestConfig abstraction that supplies a FileUpload for each test
  • Implement testUploadFilesParallel to upload a batch of files, then assert summary counts and per-file status via custom matchers
  • Implement testUploadFilesSequentially to upload files one at a time, updating and asserting the cumulative summary and item status after each upload
  • Implement testRemoveFiles to upload a set of files, then remove them sequentially while asserting that the summary counts update as expected
  • Implement testInvalidFileExtensions to upload invalid files, assert that the unsupported-file dialog appears, and that it lists each invalid filename
e2e/tests/ui/pages/common/upload-test-helpers.ts
Add SBOM and advisory upload page objects and E2E specs using shared helpers
  • Create AdvisoryUploadPage and SBOMUploadPage page objects that navigate to their respective upload routes and expose getFileUploader using the shared FileUpload builder
  • Add SBOM and advisory upload Playwright specs (contents not fully shown) that likely use upload-test-helpers with the new page objects and invalid/success fixture files to cover the upload scenarios described in the PR
  • Add invalid-file.json and invalid-file.txt fixtures for negative upload tests
e2e/tests/ui/pages/advisory-upload/AdvisoryUploadPage.ts
e2e/tests/ui/pages/sbom-upload/SBOMUploadPage.ts
e2e/tests/ui/pages/advisory-upload/upload.spec.ts
e2e/tests/ui/pages/sbom-upload/upload.spec.ts
e2e/tests/common/assets/invalid-file.json
e2e/tests/common/assets/invalid-file.txt
Tighten navigation helper to drop unused "Upload" primary nav option
  • Remove "Upload" from the union of allowed navigation targets, leaving "Importers" as the last option
e2e/tests/ui/pages/Navigation.ts

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@codecov
Copy link

codecov bot commented Jan 27, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 64.30%. Comparing base (7211ed4) to head (c35edbb).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #892      +/-   ##
==========================================
+ Coverage   63.65%   64.30%   +0.65%     
==========================================
  Files         184      187       +3     
  Lines        3260     3295      +35     
  Branches      742      746       +4     
==========================================
+ Hits         2075     2119      +44     
+ Misses        893      881      -12     
- Partials      292      295       +3     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@carlosthe19916 carlosthe19916 marked this pull request as ready for review January 27, 2026 09:16
Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • The UploadFiles component’s props prop is quite generically named and shadows the React convention; consider renaming it to something more explicit like uploaderProps or multipleFileUploadProps to make call sites clearer.
  • The custom matchers in FileUploadMatchers reach into the FileUpload page object’s _uploader field; exposing dedicated methods or accessors on FileUpload for the needed locators would keep the page-object abstraction cleaner and avoid relying on its internal structure.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `UploadFiles` component’s `props` prop is quite generically named and shadows the React convention; consider renaming it to something more explicit like `uploaderProps` or `multipleFileUploadProps` to make call sites clearer.
- The custom matchers in `FileUploadMatchers` reach into the `FileUpload` page object’s `_uploader` field; exposing dedicated methods or accessors on `FileUpload` for the needed locators would keep the page-object abstraction cleaner and avoid relying on its internal structure.

## Individual Comments

### Comment 1
<location> `e2e/tests/ui/pages/common/upload-test-helpers.ts:52-61` </location>
<code_context>
+    }
+  });
+
+export const testUploadFilesSequentially = (
+  testName: string,
+  {
+    files,
+    getConfig,
+  }: {
+    files: {
+      path: string;
+      status: "success" | "danger";
+    }[];
+    getConfig: ({ page }: { page: Page }) => Promise<UploadTestConfig>;
+  },
+) =>
+  test(`Upload sequentially- ${testName}`, async ({ page }) => {
+    const config = await getConfig({ page });
+    const fileUploader = config.fileUploader;
+
+    let successfulFilesCount = 0;
+    for (let index = 0; index < files.length; index++) {
+      const file = files[index];
+      if (file.status === "success") {
+        successfulFilesCount++;
+      }
+
+      // Upload file
+      await fileUploader.uploadFiles([file.path]);
+
+      // Summary status
+      if (successfulFilesCount > 0) {
+        await expect(fileUploader).toHaveSummaryUploadStatus({
+          totalFiles: index + 1,
</code_context>

<issue_to_address>
**suggestion (testing):** Sequential upload helper doesn't assert summary status when there are no successful files yet

In `testUploadFilesSequentially`, the summary is only asserted when `successfulFilesCount > 0`, so we never validate the summary when all processed files have failed so far. Please also cover the `successfulFilesCount === 0` case (e.g., assert `0 of X files uploaded` or that the summary is hidden, depending on the intended UX) to make this behavior explicit in the test.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Signed-off-by: Carlos Feria <2582866+carlosthe19916@users.noreply.github.com>
Copy link
Contributor

@mrrajan mrrajan left a comment

Choose a reason for hiding this comment

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

Thanks for the PR @carlosthe19916 I have requested some changes and added a suggestion for AdvisoryUploadPage.ts and SBOMUploadPage.ts files.

): Promise<MatcherResult> => {
try {
await baseExpect(
fileUpload._uploader.locator(
Copy link
Contributor

Choose a reason for hiding this comment

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

Private field _uploader from FileUpload class directly accessed here. Either Please make it public or create and access it with getUploader() method.

Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of having separate Upload Page class for advisory and sbom, shall we leverage FileUpload.ts file with the methods buildFromBrowserPath and getFileUploader parameterized with endpoints and locator?

await login(page);
});

testUploadFilesSequentially("CSAF file (.bz2)", {
Copy link
Contributor

Choose a reason for hiding this comment

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

Change CSAF file to SBOM file

testUploadFilesSequentially,
} from "../common/upload-test-helpers";
import { SBOMUploadPage } from "./SBOMUploadPage";

Copy link
Contributor

Choose a reason for hiding this comment

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

Hardcoded test data file path can be extracted to constants and referenced for the directory path.

Suggested change
const TEST_FILES = {
QUARKUS_BOM: path.join(__dirname, "../../../common/dataset/sbom/quarkus-bom-2.13.8.Final-redhat-00004.json.bz2"),
UBI9_MINIMAL: path.join(__dirname, "../../../common/dataset/sbom/ubi9-minimal-9.3-1361.json.bz2"),
INVALID_JSON: path.join(__dirname, "../../../common/assets/invalid-file.json"),
INVALID_TXT: path.join(__dirname, "../../../common/assets/invalid-file.txt"),
};

testUploadFilesSequentially,
} from "../common/upload-test-helpers";
import { AdvisoryUploadPage } from "./AdvisoryUploadPage";

Copy link
Contributor

Choose a reason for hiding this comment

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

Same as SBOM file path suggestion. Export the directory path into constants.

statusItem.locator(`.pf-v6-c-progress.pf-m-${expectedStatus.status}`),
).toBeVisible();
await baseExpect(
statusItem.locator(".pf-v6-c-progress__status", { hasText: "100%" }),
Copy link
Contributor

Choose a reason for hiding this comment

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

Shall we move the locators of success | danger and upload percent elemet to page objects and use get methods for assertion?

For example, add getUploadProgressStatus method in FileUpload.ts as,

 async getUploadProgressStatus(fileName: string) {                                                                                                                                          
    const item = await this.getUploadStatusItem(fileName);                                                                                                                                                         
    return item.locator(".pf-v6-c-progress__status");                                                                                                                                                              
  }  

Similarly,

 async getUploadProgressBar(fileName: string, expectedStatus: string) {                                                                                                                                          
    const item = await this.getUploadStatusItem(fileName);                                                                                                                                                         
    return item.locator(`.pf-v6-c-progress.pf-m-${expectedStatus.status}`);                                                                                                                                                              
  }  

And call them for assertion as,

await baseExpect(
          await fileUpload.getUploadProgressBar(expectedStatus.fileName)
        ).toBeVisible();

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

2 participants