-
Notifications
You must be signed in to change notification settings - Fork 27
Expand file tree
/
Copy pathDetailsPage.ts
More file actions
288 lines (261 loc) · 9.2 KB
/
DetailsPage.ts
File metadata and controls
288 lines (261 loc) · 9.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
import { expect, type Locator, type Page } from "@playwright/test";
/**
* Describes the Details of an Entity. E.g. SBOM Details Page.
* Generally based on https://www.patternfly.org/extensions/component-groups/details-page/
*/
export class DetailsPage {
page: Page;
constructor(page: Page) {
this.page = page;
}
async selectTab(tabName: string) {
const tab = this.page.locator("button[role='tab']", { hasText: tabName });
await expect(tab).toBeVisible();
await tab.click();
}
async clickOnPageAction(actionName: string) {
await this.page.getByRole("button", { name: "Actions" }).click();
await this.page.getByRole("menuitem", { name: actionName }).click();
}
async openActionsMenu() {
const actionsButton = this.page.getByRole("button", { name: "Actions" });
await actionsButton.click();
await expect(actionsButton).toHaveAttribute("aria-expanded", "true");
}
async clickOnPageButton(buttonName: string) {
await this.page.getByRole("button", { name: buttonName }).click();
}
async verifyPageHeader(header: string) {
await expect(this.page.getByRole("heading")).toContainText(header);
}
async verifyActionIsAvailable(actionName: string) {
await this.openActionsMenu();
await expect(
this.page.getByRole("menuitem", { name: actionName }),
).toBeVisible();
}
async verifyButtonIsVisible(button: string) {
await expect(this.page.getByRole("button", { name: button })).toBeVisible();
}
async verifyPanelIsVisible(panel: string) {
await expect(
this.page
.locator(".pf-v6-c-card__title-text")
.filter({ hasText: panel })
.first(),
).toBeVisible();
}
async verifyTabIsSelected(tabName: string) {
await expect(this.page.getByRole("tab", { name: tabName })).toHaveAttribute(
"aria-selected",
"true",
);
}
async verifyTabIsVisible(tabName: string) {
await expect(this.page.getByRole("tab", { name: tabName })).toBeVisible();
}
async verifyTabIsNotVisible(tabName: string) {
await expect(this.page.getByRole("tab", { name: tabName })).toHaveCount(0);
}
//Wait for Loading Spinner to detach from the DOM
async waitForData() {
const spinner = this.page.locator(`xpath=(//table//tbody)[1]`);
await spinner.waitFor({ state: "visible", timeout: 5000 });
}
//Verifies the Page loads with data
async verifyDataAvailable() {
await expect(
this.page.locator(
`xpath=//div[(.='No data available to be shown here.')]`,
),
).toHaveCount(0);
}
//Verifies the Vulnerability counts from summary to table
async verifyVulnerabilityPanelcount() {
const pieVulnSevLabel = `xpath=//*[name()='svg']/*[name()='g']//*[name()='tspan']`;
const totalVuln = `xpath=//*[name()='svg']/*[name()='text']//*[name()='tspan'][1]`;
const totalVulnElement = this.page.locator(totalVuln);
await totalVulnElement.waitFor({ state: "visible", timeout: 5000 });
const totalVulnPanel = await totalVulnElement.textContent();
const panelVulnSev = await this.getCountFromLabels(pieVulnSevLabel, ":");
const sumPanelVulnSev = await Object.values(panelVulnSev).reduce(
(sum, value) => sum + value,
0,
);
const tableVulnSev = await this.getCVSSCountFromVulnTable();
let mismatch = false;
await expect(
// biome-ignore lint/style/noNonNullAssertion: allowed
parseInt(totalVulnPanel!, 10),
`Total Vulnerabilities count ${totalVulnPanel} mismatches with sum of individual ${sumPanelVulnSev}`,
).toEqual(sumPanelVulnSev);
for (const severity in tableVulnSev) {
if (panelVulnSev[severity] !== undefined) {
if (panelVulnSev[severity] !== tableVulnSev[severity]) {
console.log(
`${severity} count mismatch. Summary panel count ${panelVulnSev[severity]}, Rows count ${tableVulnSev[severity]}`,
);
mismatch = true;
}
}
}
await expect(mismatch, "Panel count mismatches to table count").not.toBe(
true,
);
}
/**
* Get all the Elements matching to the @param labelLocator and retrieves the textContext of each element
* Splits the text with @param delimiter
* @returns the mutable object { [key: 0th_element ]: 1st_element }
*/
async getCountFromLabels(
labelLocator: string,
delimiter: string,
): Promise<{ [key: string]: number }> {
const elements = await this.page.locator(labelLocator).all();
const vulnLabelCount: { [key: string]: number } = {};
for (const element of elements) {
const innerText = await element.textContent();
const labelArr = await innerText?.split(delimiter);
if (labelArr) {
vulnLabelCount[labelArr[0].trim().toString()] = parseInt(
labelArr[1].trim(),
10,
);
}
}
return vulnLabelCount;
}
/**
* Retrieves the CVSS value from each row of Vulnerability table
* @returns Count of each CVSS type in { [key: severity ]: count }
*/
async getCVSSCountFromVulnTable(): Promise<{ [key: string]: number }> {
let nextPage = true;
const counts = {
Unknown: 0,
None: 0,
Low: 0,
Medium: 0,
High: 0,
Critical: 0,
};
const nextButton = await this.page.locator(
`xpath=(//section[@id='vulnerabilities-tab-section']//button[@data-action='next'])[1]`,
);
const noOfRows = await this.page.locator(
`xpath=//section[@id="vulnerabilities-tab-section"]//button[@id="pagination-id-top-toggle"]`,
);
if (await noOfRows.isEnabled()) {
noOfRows.click();
await this.page.getByRole("menuitem", { name: "100 per page" }).click();
}
while (nextPage) {
for (const cvssType of Object.keys(counts) as Array<
keyof typeof counts
>) {
const cvssLocator = await this.page
.locator(`xpath=//td[@data-label='CVSS']//div[.='${cvssType}']`)
.all();
counts[cvssType] += await cvssLocator.length;
}
nextPage = await nextButton.isEnabled();
if (nextPage) {
await nextButton.click();
}
}
return counts;
}
/**
* Generates a random String with length of 6
*/
public randomString(length: number = 6): string {
const chars = "abcdefghijklmnopqrstuvwxyz0123456789";
return Array.from(
{ length },
() => chars[Math.floor(Math.random() * chars.length)],
).join("");
}
/**
* Generates randomized labels for SBOM list page testing
*/
public generateLabels(): string {
return `label_${this.randomString()}, key_${this.randomString(3)}=qe_${this.randomString(3)}`;
}
/**
* To click on Edit labels button on Details page
*/
async editLabelsDetailsPage() {
await this.selectTab(`Info`);
await this.page.getByRole("button", { name: "Edit" }).click();
}
/**
* To add labels to edit model window
* @param labels List of labels to add to the entity
*/
async addLabels(labelList: string) {
const labels = labelList.split(",").map((label) => label.trim());
await this.page.getByText("Edit labels").isVisible();
for (const label of labels) {
await this.page.getByPlaceholder("Add label").fill(label);
await this.page.getByPlaceholder("Add label").press("Enter");
}
await this.page.getByLabel("submit").click();
}
/**
* To verify the given labels exist with the Entity
* @param labels List of expected labels
* @param entity Entity name to which the Labels needs to be verified
* @param parentElem Parent element to identify the Labels element - Defaults to List page table rows
*/
async verifyLabels(
labelList: string,
entity: string = "",
parentElem: Locator | undefined = undefined,
) {
const labels = labelList.split(",").map((label) => label.trim());
if (!parentElem) {
parentElem = this.page.locator(`xpath=//td[.='${entity}']/parent::tr/td`);
}
const moreElem = parentElem.getByRole("button", { name: "more" });
if (await moreElem.isVisible({ timeout: 2000 })) {
await moreElem.click();
}
// Wait for Edit label modal window to close with retry
const editLabels = this.page.getByText("Edit labels");
if (await editLabels.isVisible({ timeout: 1000 })) {
await editLabels.waitFor({ state: "hidden", timeout: 5000 });
}
// Wait for labels container to be stable
const labelContainer = parentElem.locator(
`xpath=//ul[@aria-label='Label group category']`,
);
await expect(labelContainer).toBeVisible({ timeout: 5000 });
let max_attempts = 0;
let labelFound = true;
let labelExpected = [...labels];
while (max_attempts < 3 && labelFound) {
max_attempts++;
for (const label of labels) {
try {
const labelUI = await parentElem.locator(
`xpath=//ul[@aria-label='Label group category']/li[.='${label}']`,
);
await expect(labelUI).toBeVisible({ timeout: 2000 });
labelExpected = labelExpected.filter(
(labelTemp) => labelTemp !== label,
);
} catch {
await this.page.waitForTimeout(500);
}
}
if (labelExpected.length === 0) {
labelFound = false;
}
}
expect(
labelExpected.length,
`Labels missing from the given list: ${labelExpected.join(", ")}`,
).toBe(0);
}
}