Skip to content

Commit 72729f8

Browse files
authored
Merge branch 'guacsec:main' into tests/aibom_cbom
2 parents 78cb03f + 9e82125 commit 72729f8

File tree

14 files changed

+655
-2
lines changed

14 files changed

+655
-2
lines changed

client/src/app/components/UploadFiles.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
ModalHeader,
1515
MultipleFileUpload,
1616
MultipleFileUploadMain,
17+
type MultipleFileUploadProps,
1718
MultipleFileUploadStatus,
1819
MultipleFileUploadStatusItem,
1920
Spinner,
@@ -37,6 +38,10 @@ export interface IUploadFilesProps {
3738
handleRemoveUpload: (file: File) => void;
3839
extractSuccessMessage: (response: AxiosResponse) => string;
3940
extractErrorMessage: (error: AxiosError) => string;
41+
fileUploadProps?: Omit<
42+
MultipleFileUploadProps,
43+
"onFileDrop" | "dropzoneProps"
44+
>;
4045
}
4146

4247
export const UploadFiles: React.FC<IUploadFilesProps> = ({
@@ -45,6 +50,7 @@ export const UploadFiles: React.FC<IUploadFilesProps> = ({
4550
handleRemoveUpload,
4651
extractSuccessMessage,
4752
extractErrorMessage,
53+
fileUploadProps,
4854
}) => {
4955
const [showStatus, setShowStatus] = React.useState(false);
5056
const [statusIcon, setStatusIcon] = React.useState<
@@ -99,6 +105,7 @@ export const UploadFiles: React.FC<IUploadFilesProps> = ({
99105
onDropRejected: handleDropRejected,
100106
useFsAccessApi: false, // Required to make playwright work
101107
}}
108+
{...fileUploadProps}
102109
>
103110
<MultipleFileUploadMain
104111
titleIcon={<UploadIcon />}

client/src/app/pages/advisory-upload/advisory-upload.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export const AdvisoryUpload: React.FC = () => {
3737
</PageSection>
3838
<PageSection>
3939
<UploadFiles
40+
fileUploadProps={{ "aria-label": "advisory-uploader" }}
4041
uploads={uploads}
4142
handleUpload={handleUpload}
4243
handleRemoveUpload={handleRemoveUpload}

client/src/app/pages/sbom-upload/sbom-upload.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export const SbomUpload: React.FC = () => {
4141
</PageSection>
4242
<PageSection>
4343
<UploadFiles
44+
fileUploadProps={{ "aria-label": "sbom-uploader" }}
4445
uploads={uploads}
4546
handleUpload={handleUpload}
4647
handleRemoveUpload={handleRemoveUpload}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
nothing
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { expect as baseExpect } from "@playwright/test";
2+
import type { FileUpload } from "../pages/FileUpload";
3+
import type { MatcherResult } from "./types";
4+
5+
export interface FileUploadMatchers {
6+
toHaveSummaryUploadStatus(expectedStatus: {
7+
successfulFiles: number;
8+
totalFiles: number;
9+
}): Promise<MatcherResult>;
10+
toHaveItemUploadStatus(expectedStatus: {
11+
fileName: string;
12+
status: "success" | "danger";
13+
}): Promise<MatcherResult>;
14+
}
15+
16+
type FileUploadMatcherDefinitions = {
17+
readonly [K in keyof FileUploadMatchers]: (
18+
receiver: FileUpload,
19+
...args: Parameters<FileUploadMatchers[K]>
20+
) => Promise<MatcherResult>;
21+
};
22+
23+
export const fileUploadAssertions =
24+
baseExpect.extend<FileUploadMatcherDefinitions>({
25+
toHaveSummaryUploadStatus: async (
26+
fileUpload: FileUpload,
27+
expectedStatus: {
28+
successfulFiles: number;
29+
totalFiles: number;
30+
},
31+
): Promise<MatcherResult> => {
32+
try {
33+
await baseExpect(
34+
fileUpload._uploader.locator(
35+
".pf-v6-c-multiple-file-upload__status .pf-v6-c-expandable-section__toggle",
36+
),
37+
).toContainText(
38+
`${expectedStatus.successfulFiles} of ${expectedStatus.totalFiles} files uploaded`,
39+
);
40+
41+
return {
42+
pass: true,
43+
message: () => "Uploader has expected summary status",
44+
};
45+
} catch (error) {
46+
return {
47+
pass: false,
48+
message: () =>
49+
error instanceof Error ? error.message : String(error),
50+
};
51+
}
52+
},
53+
toHaveItemUploadStatus: async (
54+
fileUpload: FileUpload,
55+
expectedStatus: {
56+
fileName: string;
57+
status: "success" | "danger";
58+
},
59+
): Promise<MatcherResult> => {
60+
try {
61+
const statusItem = await fileUpload.getUploadStatusItem(
62+
expectedStatus.fileName,
63+
);
64+
await baseExpect(
65+
statusItem.locator(`.pf-v6-c-progress.pf-m-${expectedStatus.status}`),
66+
).toBeVisible();
67+
await baseExpect(
68+
statusItem.locator(".pf-v6-c-progress__status", { hasText: "100%" }),
69+
).toBeVisible();
70+
71+
return {
72+
pass: true,
73+
message: () => "Uploader has item with expected status",
74+
};
75+
} catch (error) {
76+
return {
77+
pass: false,
78+
message: () =>
79+
error instanceof Error ? error.message : String(error),
80+
};
81+
}
82+
},
83+
});

e2e/tests/ui/assertions/index.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,18 @@ import { toolbarAssertions, type ToolbarMatchers } from "./ToolbarMatchers";
1616
import type { DeletionConfirmDialog } from "../pages/ConfirmDialog";
1717
import { dialogAssertions, type DialogMatchers } from "./DialogMatchers";
1818

19+
import type { FileUpload } from "../pages/FileUpload";
20+
import {
21+
fileUploadAssertions,
22+
type FileUploadMatchers,
23+
} from "./FileUploadMatchers";
24+
1925
const merged = mergeExpects(
2026
tableAssertions,
2127
paginationAssertions,
2228
toolbarAssertions,
2329
dialogAssertions,
30+
fileUploadAssertions,
2431
// Add more custom assertions here
2532
);
2633

@@ -74,6 +81,14 @@ function typedExpect(
7481
> &
7582
DialogMatchers;
7683

84+
/**
85+
* Overload from FileUploadMatchers.ts
86+
*/
87+
function typedExpect(
88+
value: FileUpload,
89+
): Omit<ReturnType<typeof merged<FileUpload>>, keyof FileUploadMatchers> &
90+
FileUploadMatchers;
91+
7792
// Default overload
7893
function typedExpect<T>(value: T): ReturnType<typeof merged<T>>;
7994
function typedExpect<T>(value: T): unknown {

e2e/tests/ui/pages/FileUpload.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { expect, type Locator, type Page } from "@playwright/test";
2+
3+
export class FileUpload {
4+
private readonly _page: Page;
5+
_uploader: Locator;
6+
7+
private constructor(page: Page, uploader: Locator) {
8+
this._page = page;
9+
this._uploader = uploader;
10+
}
11+
12+
static async build(page: Page, ariaLabel: string) {
13+
const locator = page.locator(
14+
`div.pf-v6-c-multiple-file-upload[aria-label="${ariaLabel}"]`,
15+
);
16+
await expect(locator).toBeVisible();
17+
return new FileUpload(page, locator);
18+
}
19+
20+
async uploadFiles(filePaths: string[]) {
21+
const fileChooserPromise = this._page.waitForEvent("filechooser");
22+
await this._uploader
23+
.getByRole("button", { name: "Upload", exact: true })
24+
.click();
25+
const fileChooser = await fileChooserPromise;
26+
await fileChooser.setFiles(filePaths);
27+
}
28+
29+
async getUploadStatusItem(fileName: string) {
30+
const item = this._page
31+
.locator(".pf-v6-c-multiple-file-upload__status-item")
32+
.filter({
33+
has: this._page
34+
.locator(".pf-v6-c-progress__description")
35+
.filter({ hasText: fileName }),
36+
});
37+
await expect(item).toBeVisible();
38+
39+
return item;
40+
}
41+
}

e2e/tests/ui/pages/Navigation.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@ export class Navigation {
2222
| "Vulnerabilities"
2323
| "Packages"
2424
| "Advisories"
25-
| "Importers"
26-
| "Upload",
25+
| "Importers",
2726
) {
2827
// By default, we do not initialize navigation at "/"" where the Dashboard is located
2928
// This should help us to save some time loading pages as the Dashboard fetches too much data
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import type { Page } from "@playwright/test";
2+
import { FileUpload } from "../FileUpload";
3+
4+
export class AdvisoryUploadPage {
5+
private readonly _page: Page;
6+
7+
private constructor(page: Page) {
8+
this._page = page;
9+
}
10+
11+
static async buildFromBrowserPath(page: Page) {
12+
await page.goto("/advisories/upload");
13+
return new AdvisoryUploadPage(page);
14+
}
15+
16+
async getFileUploader() {
17+
return await FileUpload.build(this._page, "advisory-uploader");
18+
}
19+
}

0 commit comments

Comments
 (0)