@red-hat-developer-hub/e2e-test-utils 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/dist/playwright/helpers/auth-token.d.ts +5 -0
  2. package/dist/playwright/helpers/auth-token.d.ts.map +1 -0
  3. package/dist/playwright/helpers/auth-token.js +37 -0
  4. package/dist/playwright/helpers/catalog-api-helper.d.ts +15 -0
  5. package/dist/playwright/helpers/catalog-api-helper.d.ts.map +1 -0
  6. package/dist/playwright/helpers/catalog-api-helper.js +63 -0
  7. package/dist/playwright/helpers/catalog-api-helper.test.d.ts +2 -0
  8. package/dist/playwright/helpers/catalog-api-helper.test.d.ts.map +1 -0
  9. package/dist/playwright/helpers/catalog-api-helper.test.js +66 -0
  10. package/dist/playwright/helpers/common.d.ts +0 -1
  11. package/dist/playwright/helpers/common.d.ts.map +1 -1
  12. package/dist/playwright/helpers/common.js +0 -4
  13. package/dist/playwright/helpers/index.d.ts +3 -0
  14. package/dist/playwright/helpers/index.d.ts.map +1 -1
  15. package/dist/playwright/helpers/index.js +3 -0
  16. package/dist/playwright/helpers/notifications-api-helper.d.ts +32 -0
  17. package/dist/playwright/helpers/notifications-api-helper.d.ts.map +1 -0
  18. package/dist/playwright/helpers/notifications-api-helper.js +35 -0
  19. package/dist/playwright/helpers/ui-helper.d.ts +2 -0
  20. package/dist/playwright/helpers/ui-helper.d.ts.map +1 -1
  21. package/dist/playwright/helpers/ui-helper.js +7 -10
  22. package/dist/playwright/page-objects/global-obj.d.ts +4 -1
  23. package/dist/playwright/page-objects/global-obj.d.ts.map +1 -1
  24. package/dist/playwright/page-objects/global-obj.js +4 -1
  25. package/dist/playwright/page-objects/global-obj.test.d.ts +2 -0
  26. package/dist/playwright/page-objects/global-obj.test.d.ts.map +1 -0
  27. package/dist/playwright/page-objects/global-obj.test.js +14 -0
  28. package/dist/playwright/pages/notifications.d.ts +14 -3
  29. package/dist/playwright/pages/notifications.d.ts.map +1 -1
  30. package/dist/playwright/pages/notifications.js +168 -48
  31. package/package.json +1 -1
@@ -0,0 +1,5 @@
1
+ import { type Page } from "@playwright/test";
2
+ import { UIhelper } from "./ui-helper.js";
3
+ /** Obtain a bearer token from the logged-in browser session. */
4
+ export declare function getSessionAuthToken(page: Page, uiHelper: UIhelper, baseUrl: string): Promise<string>;
5
+ //# sourceMappingURL=auth-token.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-token.d.ts","sourceRoot":"","sources":["../../../src/playwright/helpers/auth-token.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAErD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE1C,gEAAgE;AAChE,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,CAAC,CAwCjB"}
@@ -0,0 +1,37 @@
1
+ import { expect } from "@playwright/test";
2
+ import { AuthApiHelper } from "./auth-api-helper.js";
3
+ /** Obtain a bearer token from the logged-in browser session. */
4
+ export async function getSessionAuthToken(page, uiHelper, baseUrl) {
5
+ const authApiHelper = new AuthApiHelper(page);
6
+ const readToken = async () => {
7
+ try {
8
+ const value = await authApiHelper.getToken();
9
+ return value?.length > 0 ? value : undefined;
10
+ }
11
+ catch {
12
+ return undefined;
13
+ }
14
+ };
15
+ const existingToken = await readToken();
16
+ if (existingToken) {
17
+ return existingToken;
18
+ }
19
+ await page.goto(baseUrl);
20
+ await uiHelper.waitForAppReady();
21
+ let token = "";
22
+ await expect
23
+ .poll(async () => {
24
+ const value = await readToken();
25
+ if (value) {
26
+ token = value;
27
+ return true;
28
+ }
29
+ return false;
30
+ }, {
31
+ message: "Token should be retrieved after session is established",
32
+ timeout: 30_000,
33
+ intervals: [2_000],
34
+ })
35
+ .toBe(true);
36
+ return token;
37
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Static helper for catalog entity lookups via the RHDH catalog API.
3
+ * Used by event-driven discovery tests that poll for ingest completion.
4
+ */
5
+ export declare class CatalogApiHelper {
6
+ private static context;
7
+ private static getContext;
8
+ static dispose(): Promise<void>;
9
+ static entityExists(baseUrl: string, token: string, kind: string, name: string, namespace?: string): Promise<boolean>;
10
+ static getEntity(baseUrl: string, token: string, kind: string, name: string, namespace?: string): Promise<any>;
11
+ static getEntityDescription(baseUrl: string, token: string, kind: string, name: string, namespace?: string): Promise<string | undefined>;
12
+ static getGroupEntity(baseUrl: string, token: string, groupName: string): Promise<any>;
13
+ static getGroupMembers(baseUrl: string, token: string, groupName: string): Promise<string[]>;
14
+ }
15
+ //# sourceMappingURL=catalog-api-helper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog-api-helper.d.ts","sourceRoot":"","sources":["../../../src/playwright/helpers/catalog-api-helper.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAC,OAAO,CAAgC;mBAEjC,UAAU;WASlB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;WAOxB,YAAY,CACvB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,SAAS,SAAY,GACpB,OAAO,CAAC,OAAO,CAAC;WAYN,SAAS,CACpB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,SAAS,SAAY,GAEpB,OAAO,CAAC,GAAG,CAAC;WAmBF,oBAAoB,CAC/B,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,SAAS,SAAY,GACpB,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;WAKjB,cAAc,CACzB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,GAEhB,OAAO,CAAC,GAAG,CAAC;WAIF,eAAe,CAC1B,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,EAAE,CAAC;CAcrB"}
@@ -0,0 +1,63 @@
1
+ import { request } from "@playwright/test";
2
+ /**
3
+ * Static helper for catalog entity lookups via the RHDH catalog API.
4
+ * Used by event-driven discovery tests that poll for ingest completion.
5
+ */
6
+ export class CatalogApiHelper {
7
+ static context;
8
+ static async getContext() {
9
+ if (!this.context) {
10
+ this.context = await request.newContext({
11
+ ignoreHTTPSErrors: true,
12
+ });
13
+ }
14
+ return this.context;
15
+ }
16
+ static async dispose() {
17
+ if (this.context) {
18
+ await this.context.dispose();
19
+ this.context = undefined;
20
+ }
21
+ }
22
+ static async entityExists(baseUrl, token, kind, name, namespace = "default") {
23
+ try {
24
+ await this.getEntity(baseUrl, token, kind, name, namespace);
25
+ return true;
26
+ }
27
+ catch (error) {
28
+ if (error instanceof Error && error.message.includes("404")) {
29
+ return false;
30
+ }
31
+ throw error;
32
+ }
33
+ }
34
+ static async getEntity(baseUrl, token, kind, name, namespace = "default") {
35
+ const context = await this.getContext();
36
+ const url = `${baseUrl}/api/catalog/entities/by-name/${kind.toLowerCase()}/${namespace}/${name}`;
37
+ const response = await context.get(url, {
38
+ headers: {
39
+ Authorization: `Bearer ${token}`,
40
+ },
41
+ });
42
+ if (!response.ok()) {
43
+ throw new Error(`Failed to get ${kind} entity "${name}": ${response.status()} ${response.statusText()}`);
44
+ }
45
+ return await response.json();
46
+ }
47
+ static async getEntityDescription(baseUrl, token, kind, name, namespace = "default") {
48
+ const entity = await this.getEntity(baseUrl, token, kind, name, namespace);
49
+ return entity.metadata?.description;
50
+ }
51
+ static async getGroupEntity(baseUrl, token, groupName) {
52
+ return this.getEntity(baseUrl, token, "group", groupName);
53
+ }
54
+ static async getGroupMembers(baseUrl, token, groupName) {
55
+ const groupEntity = await CatalogApiHelper.getGroupEntity(baseUrl, token, groupName);
56
+ const members = groupEntity.relations
57
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
58
+ ?.filter((r) => r.type === "hasMember")
59
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
60
+ .map((r) => r.targetRef.split("/")[1]) || [];
61
+ return members;
62
+ }
63
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=catalog-api-helper.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog-api-helper.test.d.ts","sourceRoot":"","sources":["../../../src/playwright/helpers/catalog-api-helper.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,66 @@
1
+ import assert from "node:assert";
2
+ import { afterEach, describe, it, mock } from "node:test";
3
+ import { request } from "@playwright/test";
4
+ import { CatalogApiHelper } from "./catalog-api-helper.js";
5
+ const baseUrl = "https://rhdh.example.com";
6
+ const token = "test-token";
7
+ describe("CatalogApiHelper", () => {
8
+ afterEach(async () => {
9
+ await CatalogApiHelper.dispose();
10
+ mock.restoreAll();
11
+ });
12
+ it("entityExists returns false when catalog responds with 404", async () => {
13
+ mock.method(request, "newContext", async () => ({
14
+ get: async () => ({
15
+ ok: () => false,
16
+ status: () => 404,
17
+ statusText: () => "Not Found",
18
+ }),
19
+ dispose: async () => { },
20
+ }));
21
+ const exists = await CatalogApiHelper.entityExists(baseUrl, token, "component", "missing-entity");
22
+ assert.strictEqual(exists, false);
23
+ });
24
+ it("entityExists returns true when entity is found", async () => {
25
+ mock.method(request, "newContext", async () => ({
26
+ get: async () => ({
27
+ ok: () => true,
28
+ status: () => 200,
29
+ statusText: () => "OK",
30
+ json: async () => ({ metadata: { name: "my-entity" } }),
31
+ }),
32
+ dispose: async () => { },
33
+ }));
34
+ const exists = await CatalogApiHelper.entityExists(baseUrl, token, "component", "my-entity");
35
+ assert.strictEqual(exists, true);
36
+ });
37
+ it("entityExists rethrows non-404 errors", async () => {
38
+ mock.method(request, "newContext", async () => ({
39
+ get: async () => ({
40
+ ok: () => false,
41
+ status: () => 500,
42
+ statusText: () => "Internal Server Error",
43
+ }),
44
+ dispose: async () => { },
45
+ }));
46
+ await assert.rejects(() => CatalogApiHelper.entityExists(baseUrl, token, "component", "my-entity"), /500/);
47
+ });
48
+ it("getEntityDescription returns metadata.description", async () => {
49
+ mock.method(request, "newContext", async () => ({
50
+ get: async (url) => {
51
+ assert.match(url, /\/api\/catalog\/entities\/by-name\/component\/default\/my-entity$/);
52
+ return {
53
+ ok: () => true,
54
+ status: () => 200,
55
+ statusText: () => "OK",
56
+ json: async () => ({
57
+ metadata: { description: "updated description" },
58
+ }),
59
+ };
60
+ },
61
+ dispose: async () => { },
62
+ }));
63
+ const description = await CatalogApiHelper.getEntityDescription(baseUrl, token, "component", "my-entity");
64
+ assert.strictEqual(description, "updated description");
65
+ });
66
+ });
@@ -14,7 +14,6 @@ export declare class LoginHelper {
14
14
  private handleGithubPopupReauth;
15
15
  googleSignIn(email: string): Promise<void>;
16
16
  checkAndClickOnGHloginPopup(force?: boolean): Promise<void>;
17
- getButtonSelector(label: string): string;
18
17
  getLoginBtnSelector(): string;
19
18
  clickOnGHloginPopup(): Promise<void>;
20
19
  getGitHub2FAOTP(userid: string): string;
@@ -1 +1 @@
1
- {"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../../src/playwright/helpers/common.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAG1C,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAOhE,qBAAa,WAAW;IACtB,IAAI,EAAE,IAAI,CAAC;IACX,QAAQ,EAAE,QAAQ,CAAC;gBAEP,IAAI,EAAE,IAAI;IAKhB,YAAY;IAcZ,OAAO;YAMC,aAAa;IA2CrB,eAAe,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;IAO7D,mBAAmB,CACvB,MAAM,GAAE,MAAkC,EAC1C,QAAQ,GAAE,MAAkC;IAWxC,iBAAiB,CACrB,MAAM,GAAE,MAA+C;IAoDnD,4BAA4B;YASpB,uBAAuB;IAgB/B,YAAY,CAAC,KAAK,EAAE,MAAM;IA2B1B,2BAA2B,CAAC,KAAK,UAAQ;IAU/C,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAIxC,mBAAmB,IAAI,MAAM;IAIvB,mBAAmB;IAgBzB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;IAevC,eAAe,IAAI,MAAM;IAKnB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;YAqCxC,sBAAsB;IAoD9B,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;IAYjE,2BAA2B,CAC/B,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM;IAYb,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;CA2C7D;AAED,wBAAsB,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ;;;GAWtE"}
1
+ {"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../../src/playwright/helpers/common.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAG1C,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAMhE,qBAAa,WAAW;IACtB,IAAI,EAAE,IAAI,CAAC;IACX,QAAQ,EAAE,QAAQ,CAAC;gBAEP,IAAI,EAAE,IAAI;IAKhB,YAAY;IAcZ,OAAO;YAMC,aAAa;IA2CrB,eAAe,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;IAO7D,mBAAmB,CACvB,MAAM,GAAE,MAAkC,EAC1C,QAAQ,GAAE,MAAkC;IAWxC,iBAAiB,CACrB,MAAM,GAAE,MAA+C;IAoDnD,4BAA4B;YASpB,uBAAuB;IAgB/B,YAAY,CAAC,KAAK,EAAE,MAAM;IA2B1B,2BAA2B,CAAC,KAAK,UAAQ;IAU/C,mBAAmB,IAAI,MAAM;IAIvB,mBAAmB;IAgBzB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;IAevC,eAAe,IAAI,MAAM;IAKnB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;YAqCxC,sBAAsB;IAoD9B,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;IAYjE,2BAA2B,CAC/B,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM;IAYb,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;CA2C7D;AAED,wBAAsB,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ;;;GAWtE"}
@@ -2,7 +2,6 @@ import { UIhelper } from "./ui-helper.js";
2
2
  import { authenticator } from "otplib";
3
3
  import { test, expect } from "@playwright/test";
4
4
  import { SETTINGS_PAGE_COMPONENTS } from "../page-objects/page-obj.js";
5
- import { UI_HELPER_ELEMENTS } from "../page-objects/global-obj.js";
6
5
  import * as path from "path";
7
6
  import * as fs from "fs";
8
7
  import { DEFAULT_USERS } from "../../deployment/keycloak/constants.js";
@@ -169,9 +168,6 @@ export class LoginHelper {
169
168
  throw error;
170
169
  }
171
170
  }
172
- getButtonSelector(label) {
173
- return `${UI_HELPER_ELEMENTS.MuiButtonLabel}:has-text("${label}")`;
174
- }
175
171
  getLoginBtnSelector() {
176
172
  return 'MuiListItem-root li.MuiListItem-root button.MuiButton-root:has(span.MuiButton-label:text("Log in"))';
177
173
  }
@@ -4,4 +4,7 @@ export { LoginHelper, setupBrowser } from "./common.js";
4
4
  export { UIhelper } from "./ui-helper.js";
5
5
  export { RbacApiHelper, Policy, Role, Response } from "./rbac-api-helper.js";
6
6
  export { AuthApiHelper } from "./auth-api-helper.js";
7
+ export { getSessionAuthToken } from "./auth-token.js";
8
+ export { CatalogApiHelper } from "./catalog-api-helper.js";
9
+ export { RhdhNotificationsApi, type NotificationPayload, type NotificationRecipients, type NotificationRequest, type NotificationSeverity, type BroadcastRecipients, type EntityRecipients, } from "./notifications-api-helper.js";
7
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/playwright/helpers/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAC7E,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/playwright/helpers/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAC7E,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EACL,oBAAoB,EACpB,KAAK,mBAAmB,EACxB,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,GACtB,MAAM,+BAA+B,CAAC"}
@@ -4,3 +4,6 @@ export { LoginHelper, setupBrowser } from "./common.js";
4
4
  export { UIhelper } from "./ui-helper.js";
5
5
  export { RbacApiHelper, Response } from "./rbac-api-helper.js";
6
6
  export { AuthApiHelper } from "./auth-api-helper.js";
7
+ export { getSessionAuthToken } from "./auth-token.js";
8
+ export { CatalogApiHelper } from "./catalog-api-helper.js";
9
+ export { RhdhNotificationsApi, } from "./notifications-api-helper.js";
@@ -0,0 +1,32 @@
1
+ import { APIResponse } from "@playwright/test";
2
+ export type NotificationSeverity = "critical" | "high" | "normal" | "low";
3
+ export interface NotificationPayload {
4
+ title: string;
5
+ description: string;
6
+ severity: NotificationSeverity;
7
+ topic: string;
8
+ }
9
+ export type BroadcastRecipients = {
10
+ type: "broadcast";
11
+ };
12
+ export type EntityRecipients = {
13
+ type: "entity";
14
+ entityRef: string | string[];
15
+ excludeEntityRef?: string | string[];
16
+ };
17
+ export type NotificationRecipients = BroadcastRecipients | EntityRecipients;
18
+ export interface NotificationRequest {
19
+ recipients: NotificationRecipients;
20
+ payload: NotificationPayload;
21
+ }
22
+ export declare class RhdhNotificationsApi {
23
+ private readonly token;
24
+ private readonly apiUrl;
25
+ private readonly authHeader;
26
+ private myContext;
27
+ private constructor();
28
+ static build(token: string): Promise<RhdhNotificationsApi>;
29
+ createNotification(notification: NotificationRequest): Promise<APIResponse>;
30
+ markAllNotificationsAsRead(): Promise<APIResponse>;
31
+ }
32
+ //# sourceMappingURL=notifications-api-helper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notifications-api-helper.d.ts","sourceRoot":"","sources":["../../../src/playwright/helpers/notifications-api-helper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,WAAW,EAAW,MAAM,kBAAkB,CAAC;AAE3E,MAAM,MAAM,oBAAoB,GAAG,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;AAE1E,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,EAAE,WAAW,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,QAAQ,CAAC;IACf,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC7B,gBAAgB,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;CACtC,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG,mBAAmB,GAAG,gBAAgB,CAAC;AAE5E,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,sBAAsB,CAAC;IACnC,OAAO,EAAE,mBAAmB,CAAC;CAC9B;AAED,qBAAa,oBAAoB;IAUX,OAAO,CAAC,QAAQ,CAAC,KAAK;IAT1C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAuC;IAE9D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAGzB;IAEF,OAAO,CAAC,SAAS,CAAqB;IAEtC,OAAO;WAOa,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAS1D,kBAAkB,CAC7B,YAAY,EAAE,mBAAmB,GAChC,OAAO,CAAC,WAAW,CAAC;IAIV,0BAA0B,IAAI,OAAO,CAAC,WAAW,CAAC;CAQhE"}
@@ -0,0 +1,35 @@
1
+ import { request } from "@playwright/test";
2
+ export class RhdhNotificationsApi {
3
+ token;
4
+ apiUrl = `${process.env.RHDH_BASE_URL}/api/`;
5
+ /* eslint-disable @typescript-eslint/naming-convention */
6
+ authHeader;
7
+ /* eslint-enable @typescript-eslint/naming-convention */
8
+ myContext;
9
+ constructor(token) {
10
+ this.token = token;
11
+ this.authHeader = {
12
+ Accept: "application/json",
13
+ Authorization: `Bearer ${this.token}`,
14
+ };
15
+ }
16
+ static async build(token) {
17
+ const instance = new RhdhNotificationsApi(token);
18
+ instance.myContext = await request.newContext({
19
+ baseURL: instance.apiUrl,
20
+ extraHTTPHeaders: instance.authHeader,
21
+ });
22
+ return instance;
23
+ }
24
+ async createNotification(notification) {
25
+ return await this.myContext.post("notifications", { data: notification });
26
+ }
27
+ async markAllNotificationsAsRead() {
28
+ return await this.myContext.patch("notifications", {
29
+ data: {
30
+ ids: [],
31
+ read: true,
32
+ },
33
+ });
34
+ }
35
+ }
@@ -4,6 +4,8 @@ export declare class UIhelper {
4
4
  private page;
5
5
  constructor(page: Page);
6
6
  waitForLoad(timeout?: number): Promise<void>;
7
+ /** Wait for MUI and BUI loading indicators to clear. Alias for {@link waitForLoad}. */
8
+ waitForAppReady(timeout?: number): Promise<void>;
7
9
  /**
8
10
  * Closes the quickstart drawer when the "Hide" button is visible (RHDH quickstart plugin),
9
11
  * so it does not cover catalog or other UI under test.
@@ -1 +1 @@
1
- {"version":3,"file":"ui-helper.d.ts","sourceRoot":"","sources":["../../../src/playwright/helpers/ui-helper.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ui-helper.d.ts","sourceRoot":"","sources":["../../../src/playwright/helpers/ui-helper.ts"],"names":[],"mappings":"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"}
@@ -14,6 +14,10 @@ export class UIhelper {
14
14
  });
15
15
  }
16
16
  }
17
+ /** Wait for MUI and BUI loading indicators to clear. Alias for {@link waitForLoad}. */
18
+ async waitForAppReady(timeout = 120000) {
19
+ await this.waitForLoad(timeout);
20
+ }
17
21
  /**
18
22
  * Closes the quickstart drawer when the "Hide" button is visible (RHDH quickstart plugin),
19
23
  * so it does not cover catalog or other UI under test.
@@ -70,17 +74,10 @@ export class UIhelper {
70
74
  exact: true,
71
75
  force: false,
72
76
  }) {
73
- const selector = `${UI_HELPER_ELEMENTS.MuiButtonLabel}`;
74
77
  const button = this.page
75
- .locator(selector)
76
- .getByText(label, { exact: options.exact })
78
+ .getByRole("button", { name: label, exact: options.exact })
77
79
  .first();
78
- if (options?.force) {
79
- await button.click({ force: true });
80
- }
81
- else {
82
- await button.click();
83
- }
80
+ await button.click({ force: options.force });
84
81
  return button;
85
82
  }
86
83
  async clickBtnByTitleIfNotPressed(title) {
@@ -218,7 +215,7 @@ export class UIhelper {
218
215
  .locator(`nav a:has-text("${navBarText}")`)
219
216
  .first();
220
217
  await navLink.waitFor({ state: "visible", timeout: 15_000 });
221
- await navLink.dispatchEvent("click");
218
+ await navLink.click();
222
219
  }
223
220
  async openCatalogSidebar(kind) {
224
221
  await this.openSidebar("Catalog");
@@ -1,9 +1,12 @@
1
1
  export declare const WAIT_OBJECTS: {
2
2
  MuiLinearProgress: string;
3
3
  MuiCircularProgress: string;
4
+ /** Backstage UI (BUI) — additive so MUI and BUI apps both settle before assertions */
5
+ progressBar: string;
6
+ buttonSpinner: string;
7
+ alertSpinner: string;
4
8
  };
5
9
  export declare const UI_HELPER_ELEMENTS: {
6
- MuiButtonLabel: string;
7
10
  MuiToggleButtonLabel: string;
8
11
  MuiBoxLabel: string;
9
12
  MuiTableHead: string;
@@ -1 +1 @@
1
- {"version":3,"file":"global-obj.d.ts","sourceRoot":"","sources":["../../../src/playwright/page-objects/global-obj.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,YAAY;;;CAGxB,CAAC;AAEF,eAAO,MAAM,kBAAkB;;;;;;;;;;2BAWN,MAAM;4BAEL,MAAM;;;;;;;sBAQZ,MAAM;CACzB,CAAC"}
1
+ {"version":3,"file":"global-obj.d.ts","sourceRoot":"","sources":["../../../src/playwright/page-objects/global-obj.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,YAAY;;;IAGvB,sFAAsF;;;;CAIvF,CAAC;AAEF,eAAO,MAAM,kBAAkB;;;;;;;;;2BASN,MAAM;4BAEL,MAAM;;;;;;;sBAQZ,MAAM;CACzB,CAAC"}
@@ -1,9 +1,12 @@
1
1
  export const WAIT_OBJECTS = {
2
2
  MuiLinearProgress: 'div[class*="MuiLinearProgress-root"]',
3
3
  MuiCircularProgress: '[class*="MuiCircularProgress-root"]',
4
+ /** Backstage UI (BUI) — additive so MUI and BUI apps both settle before assertions */
5
+ progressBar: '[role="progressbar"]',
6
+ buttonSpinner: ".bui-ButtonSpinner, .bui-ButtonIconSpinner",
7
+ alertSpinner: ".bui-AlertSpinner",
4
8
  };
5
9
  export const UI_HELPER_ELEMENTS = {
6
- MuiButtonLabel: 'span[class^="MuiButton-label"],button[class*="MuiButton-root"]',
7
10
  MuiToggleButtonLabel: 'span[class^="MuiToggleButton-label"]',
8
11
  MuiBoxLabel: 'div[class*="MuiBox-root"] label',
9
12
  MuiTableHead: 'th[class*="MuiTableCell-root"]',
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=global-obj.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"global-obj.test.d.ts","sourceRoot":"","sources":["../../../src/playwright/page-objects/global-obj.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,14 @@
1
+ import assert from "node:assert";
2
+ import { describe, it } from "node:test";
3
+ import { WAIT_OBJECTS } from "./global-obj.js";
4
+ describe("WAIT_OBJECTS", () => {
5
+ it("includes MUI loading selectors", () => {
6
+ assert.ok(WAIT_OBJECTS.MuiLinearProgress);
7
+ assert.ok(WAIT_OBJECTS.MuiCircularProgress);
8
+ });
9
+ it("includes BUI loading selectors", () => {
10
+ assert.ok(WAIT_OBJECTS.progressBar);
11
+ assert.ok(WAIT_OBJECTS.buttonSpinner);
12
+ assert.ok(WAIT_OBJECTS.alertSpinner);
13
+ });
14
+ });
@@ -1,14 +1,16 @@
1
1
  import { type Page } from "@playwright/test";
2
+ import { UIhelper } from "../helpers/ui-helper.js";
2
3
  export declare class NotificationPage {
3
4
  private readonly page;
4
5
  private readonly uiHelper;
5
- constructor(page: Page);
6
- clickNotificationsNavBarItem(): Promise<void>;
6
+ constructor(page: Page, uiHelper?: UIhelper);
7
+ /** Navigate to the notifications page and wait until it is ready. */
8
+ navigateToNotifications(): Promise<void>;
7
9
  notificationContains(text: string | RegExp): Promise<void>;
8
10
  clickNotificationHeadingLink(text: string | RegExp): Promise<void>;
9
11
  markAllNotificationsAsRead(): Promise<void>;
10
12
  selectAllNotifications(): Promise<void>;
11
- selectNotification(nth?: number): Promise<void>;
13
+ selectNotification(textOrNth?: string | RegExp | number): Promise<void>;
12
14
  selectSeverity(severity?: string): Promise<void>;
13
15
  saveSelected(): Promise<void>;
14
16
  saveAllSelected(): Promise<void>;
@@ -20,5 +22,14 @@ export declare class NotificationPage {
20
22
  viewUnRead(): Promise<void>;
21
23
  sortByOldestOnTop(): Promise<void>;
22
24
  sortByNewestOnTop(): Promise<void>;
25
+ private severityFilter;
26
+ private saveSelectedButton;
27
+ private notificationRowsBui;
28
+ private findNotificationRow;
29
+ private waitForNotificationsReady;
30
+ private dismissNotificationToasts;
31
+ private setRowsPerPage;
32
+ private toggleRead;
33
+ private expectUnreadCountDecreased;
23
34
  }
24
35
  //# sourceMappingURL=notifications.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"notifications.d.ts","sourceRoot":"","sources":["../../../src/playwright/pages/notifications.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAGrD,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAO;IAC5B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAW;gBAExB,IAAI,EAAE,IAAI;IAKhB,4BAA4B;IAO5B,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAW1C,4BAA4B,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAOlD,0BAA0B;IAiB1B,sBAAsB;IAItB,kBAAkB,CAAC,GAAG,SAAI;IAI1B,cAAc,CAAC,QAAQ,SAAK;IAW5B,YAAY;IAWZ,eAAe;IAWf,SAAS;IAQT,0BAA0B;IAK1B,sBAAsB,CAAC,IAAI,EAAE,MAAM;IAKnC,4BAA4B;IAK5B,QAAQ;IAUR,UAAU;IAUV,iBAAiB;IAQjB,iBAAiB;CAOxB"}
1
+ {"version":3,"file":"notifications.d.ts","sourceRoot":"","sources":["../../../src/playwright/pages/notifications.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwB,KAAK,IAAI,EAAE,MAAM,kBAAkB,CAAC;AACnE,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAEnD,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAO;IAC5B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAW;gBAExB,IAAI,EAAE,IAAI,EAAE,QAAQ,CAAC,EAAE,QAAQ;IAK3C,qEAAqE;IAC/D,uBAAuB;IAgBvB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAU1C,4BAA4B,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAQlD,0BAA0B;IAsB1B,sBAAsB;IAItB,kBAAkB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM;IAgCvD,cAAc,CAAC,QAAQ,SAAK;IAS5B,YAAY;IAKZ,eAAe;IAKf,SAAS;IAMT,0BAA0B;IAI1B,sBAAsB,CAAC,IAAI,EAAE,MAAM;IAInC,4BAA4B;IAI5B,QAAQ;IAQR,UAAU;IAQV,iBAAiB;IAMjB,iBAAiB;IAMvB,OAAO,CAAC,cAAc;IAItB,OAAO,CAAC,kBAAkB;IAW1B,OAAO,CAAC,mBAAmB;IAM3B,OAAO,CAAC,mBAAmB;YAMb,yBAAyB;YAezB,yBAAyB;YAczB,cAAc;YAed,UAAU;YAyBV,0BAA0B;CAezC"}
@@ -3,21 +3,35 @@ import { UIhelper } from "../helpers/ui-helper.js";
3
3
  export class NotificationPage {
4
4
  page;
5
5
  uiHelper;
6
- constructor(page) {
6
+ constructor(page, uiHelper) {
7
7
  this.page = page;
8
- this.uiHelper = new UIhelper(page);
9
- }
10
- async clickNotificationsNavBarItem() {
11
- await this.uiHelper.openSidebar("Notifications");
12
- await expect(this.page.getByTestId("loading-indicator").getByRole("img")).toHaveCount(0);
8
+ this.uiHelper = uiHelper ?? new UIhelper(page);
9
+ }
10
+ /** Navigate to the notifications page and wait until it is ready. */
11
+ async navigateToNotifications() {
12
+ await this.dismissNotificationToasts();
13
+ await expect(async () => {
14
+ try {
15
+ await this.uiHelper.openSidebar("Notifications");
16
+ await this.waitForNotificationsReady(15_000);
17
+ await this.uiHelper.verifyHeading("Notifications", 15_000);
18
+ }
19
+ catch {
20
+ await this.page.goto("/notifications");
21
+ await this.waitForNotificationsReady(15_000);
22
+ await expect(this.page).toHaveURL(/\/notifications/);
23
+ await this.uiHelper.verifyHeading("Notifications", 15_000);
24
+ }
25
+ }).toPass({ timeout: 60_000, intervals: [2_000] });
13
26
  }
14
27
  async notificationContains(text) {
15
- await this.page.getByLabel(/.*rows/).click();
16
- // always expand the notifications table to show as many notifications as possible
17
- await this.page.getByRole("option", { name: "20" }).click();
18
- await expect(this.page.getByTestId("loading-indicator").getByRole("img")).toHaveCount(0);
19
- const row = this.page.locator(`tr`, { hasText: text }).first();
20
- await expect(row).toHaveCount(1);
28
+ await this.dismissNotificationToasts();
29
+ let row = this.findNotificationRow(text);
30
+ if (!(await row.isVisible())) {
31
+ await this.setRowsPerPage(20);
32
+ row = this.findNotificationRow(text);
33
+ }
34
+ await expect(row).toBeVisible();
21
35
  }
22
36
  async clickNotificationHeadingLink(text) {
23
37
  await this.page
@@ -27,86 +41,192 @@ export class NotificationPage {
27
41
  .click();
28
42
  }
29
43
  async markAllNotificationsAsRead() {
30
- const markAllNotificationsAsReadIsVisible = await this.page
44
+ const markAllButton = this.page
31
45
  .getByTitle("Mark all read")
32
- .getByRole("button")
33
- .isVisible();
34
- console.log(markAllNotificationsAsReadIsVisible);
35
- // If button isn't visible there are no records in the notification table
36
- if (markAllNotificationsAsReadIsVisible.toString() != "false") {
37
- await this.page.getByTitle("Mark all read").getByRole("button").click();
38
- await this.page.getByRole("button", { name: "MARK ALL" }).click();
39
- await expect(this.page.getByTestId("loading-indicator").getByRole("img")).toHaveCount(0);
46
+ .getByRole("button");
47
+ const markAllByLabel = this.page.getByRole("button", {
48
+ name: /mark all read/i,
49
+ });
50
+ const target = (await markAllButton.isVisible().catch(() => false))
51
+ ? markAllButton
52
+ : markAllByLabel;
53
+ if (await target.isVisible().catch(() => false)) {
54
+ await target.click();
55
+ const confirm = this.page.getByRole("button", { name: /MARK ALL/i });
56
+ if (await confirm.isVisible().catch(() => false)) {
57
+ await confirm.click();
58
+ }
59
+ await this.waitForNotificationsReady();
40
60
  await expect(this.page.getByText("No records to display")).toBeVisible();
41
61
  }
42
62
  }
43
63
  async selectAllNotifications() {
44
64
  await this.page.getByRole("checkbox").first().click();
45
65
  }
46
- async selectNotification(nth = 1) {
47
- await this.page.getByRole("checkbox").nth(nth).click();
66
+ async selectNotification(textOrNth) {
67
+ await this.dismissNotificationToasts();
68
+ if (typeof textOrNth === "number") {
69
+ await this.page.getByRole("checkbox").nth(textOrNth).click();
70
+ return;
71
+ }
72
+ let row = textOrNth !== undefined
73
+ ? this.findNotificationRow(textOrNth).first()
74
+ : this.notificationRowsBui().first();
75
+ if (textOrNth !== undefined && !(await row.isVisible())) {
76
+ await this.setRowsPerPage(20);
77
+ row = this.findNotificationRow(textOrNth).first();
78
+ }
79
+ const buiCheckbox = row.getByRole("checkbox", {
80
+ name: "Select notification",
81
+ });
82
+ if (await buiCheckbox.isVisible().catch(() => false)) {
83
+ await buiCheckbox.check({ force: true });
84
+ return;
85
+ }
86
+ if (textOrNth === undefined) {
87
+ await this.page.getByRole("checkbox").first().click();
88
+ return;
89
+ }
90
+ await row.getByRole("checkbox").first().click();
48
91
  }
49
92
  async selectSeverity(severity = "") {
50
- await this.page.getByLabel("Severity").click();
93
+ await this.severityFilter().click();
51
94
  await this.page.getByRole("option", { name: severity }).click();
52
95
  await expect(this.page.getByRole("table").filter({ hasText: "Rows per page" })).toBeVisible();
53
- await expect(this.page.getByTestId("loading-indicator").getByRole("img")).toHaveCount(0);
96
+ await this.waitForNotificationsReady();
54
97
  }
55
98
  async saveSelected() {
56
- await this.page
57
- .locator("thead")
58
- .getByTitle("Save selected for later")
59
- .getByRole("button")
60
- .click();
61
- await expect(this.page.getByTestId("loading-indicator").getByRole("img")).toHaveCount(0);
99
+ await this.saveSelectedButton().click();
100
+ await this.waitForNotificationsReady();
62
101
  }
63
102
  async saveAllSelected() {
64
- await this.page
65
- .locator("thead")
66
- .getByTitle("Save selected for later")
67
- .getByRole("button")
68
- .click();
69
- await expect(this.page.getByTestId("loading-indicator").getByRole("img")).toHaveCount(0);
103
+ await this.saveSelectedButton().click();
104
+ await this.waitForNotificationsReady();
70
105
  }
71
106
  async viewSaved() {
72
107
  await this.page.getByLabel("View").click();
73
108
  await this.page.getByRole("option", { name: "Saved" }).click();
74
- await expect(this.page.getByTestId("loading-indicator").getByRole("img")).toHaveCount(0);
109
+ await this.waitForNotificationsReady();
75
110
  }
76
111
  async markLastNotificationAsRead() {
77
- const row = this.page.locator("td:nth-child(3) > div").first();
78
- await row.getByRole("button").nth(1).click();
112
+ await this.toggleRead("unread");
79
113
  }
80
114
  async markNotificationAsRead(text) {
81
- const row = this.page.locator(`tr:has-text("${text}")`);
82
- await row.getByRole("button").nth(1).click();
115
+ await this.toggleRead("unread", text);
83
116
  }
84
117
  async markLastNotificationAsUnRead() {
85
- const row = this.page.locator("td:nth-child(3) > div").first();
86
- await row.getByRole("button").nth(1).click();
118
+ await this.toggleRead("read");
87
119
  }
88
120
  async viewRead() {
89
121
  await this.page.getByLabel("View").click();
90
122
  await this.page
91
123
  .getByRole("option", { name: "Read notifications", exact: true })
92
124
  .click();
93
- await expect(this.page.getByTestId("loading-indicator").getByRole("img")).toHaveCount(0);
125
+ await this.waitForNotificationsReady();
94
126
  }
95
127
  async viewUnRead() {
96
128
  await this.page.getByLabel("View").click();
97
129
  await this.page
98
130
  .getByRole("option", { name: "Unread notifications", exact: true })
99
131
  .click();
100
- await expect(this.page.getByTestId("loading-indicator").getByRole("img")).toHaveCount(0);
132
+ await this.waitForNotificationsReady();
101
133
  }
102
134
  async sortByOldestOnTop() {
103
135
  await this.page.getByLabel("Sort by").click();
104
136
  await this.page.getByRole("option", { name: "Oldest on top" }).click();
105
- await expect(this.page.getByTestId("loading-indicator").getByRole("img")).toHaveCount(0);
137
+ await this.waitForNotificationsReady();
106
138
  }
107
139
  async sortByNewestOnTop() {
108
140
  await this.page.getByLabel("Sort by").click();
109
141
  await this.page.getByRole("option", { name: "Newest on top" }).click();
110
- await expect(this.page.getByTestId("loading-indicator").getByRole("img")).toHaveCount(0);
142
+ await this.waitForNotificationsReady();
143
+ }
144
+ severityFilter() {
145
+ return this.page.getByLabel(/^(Min )?severity$/i);
146
+ }
147
+ saveSelectedButton() {
148
+ const bui = this.page.locator('thead button[aria-label="Save selected for later"]');
149
+ const mui = this.page
150
+ .locator("thead")
151
+ .getByTitle("Save selected for later")
152
+ .getByRole("button");
153
+ return bui.or(mui).first();
154
+ }
155
+ notificationRowsBui() {
156
+ return this.page.getByRole("row").filter({
157
+ has: this.page.getByRole("checkbox", { name: "Select notification" }),
158
+ });
159
+ }
160
+ findNotificationRow(text) {
161
+ const buiRow = this.notificationRowsBui().filter({ hasText: text }).first();
162
+ const legacyRow = this.page.locator("tr", { hasText: text }).first();
163
+ return buiRow.or(legacyRow).first();
164
+ }
165
+ async waitForNotificationsReady(timeout = 30_000) {
166
+ await this.uiHelper.waitForAppReady(timeout);
167
+ const muiLoader = this.page
168
+ .getByTestId("loading-indicator")
169
+ .getByRole("img");
170
+ if (await muiLoader
171
+ .first()
172
+ .isVisible()
173
+ .catch(() => false)) {
174
+ await expect(muiLoader).toHaveCount(0, { timeout });
175
+ }
176
+ }
177
+ async dismissNotificationToasts() {
178
+ const toasts = this.page.locator("#notistack-snackbar, .notistack-CollapseWrapper");
179
+ if (await toasts
180
+ .first()
181
+ .isVisible()
182
+ .catch(() => false)) {
183
+ await expect(toasts.first()).toBeHidden({ timeout: 15_000 });
184
+ }
185
+ }
186
+ async setRowsPerPage(size) {
187
+ await this.dismissNotificationToasts();
188
+ const rowsPerPage = this.page.getByRole("button", {
189
+ name: /rows per page/i,
190
+ });
191
+ const legacyRows = this.page.getByLabel(/.*rows/i);
192
+ if (await rowsPerPage.isVisible().catch(() => false)) {
193
+ await rowsPerPage.click({ force: true });
194
+ }
195
+ else {
196
+ await legacyRows.click();
197
+ }
198
+ await this.page.getByRole("option", { name: String(size) }).click();
199
+ await this.waitForNotificationsReady();
200
+ }
201
+ async toggleRead(currentState, text) {
202
+ await this.dismissNotificationToasts();
203
+ const readButtonName = currentState === "unread"
204
+ ? /Mark selected as read/i
205
+ : /Return selected among unread/i;
206
+ const buiRows = this.notificationRowsBui();
207
+ const buiRow = text ? buiRows.filter({ hasText: text }) : buiRows.first();
208
+ const buiAction = buiRow.getByRole("button", { name: readButtonName });
209
+ if (await buiAction.isVisible().catch(() => false)) {
210
+ const count = await buiRows.count();
211
+ await buiAction.click();
212
+ await this.waitForNotificationsReady();
213
+ await this.expectUnreadCountDecreased(buiRows, count, currentState);
214
+ return;
215
+ }
216
+ const legacyRow = text
217
+ ? this.page.locator(`tr:has-text("${text}")`)
218
+ : this.page.locator("td:nth-child(3) > div").first();
219
+ await legacyRow.getByRole("button").nth(1).click();
220
+ await this.waitForNotificationsReady();
221
+ }
222
+ async expectUnreadCountDecreased(rows, count, currentState) {
223
+ const viewPattern = currentState === "unread"
224
+ ? /Unread notifications \(/
225
+ : /Read notifications \(/;
226
+ if (await this.page.getByText(viewPattern).isVisible()) {
227
+ await expect(async () => {
228
+ await expect(rows).toHaveCount(count - 1);
229
+ }).toPass({ timeout: 15_000, intervals: [1_000] });
230
+ }
111
231
  }
112
232
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@red-hat-developer-hub/e2e-test-utils",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "Test utilities for RHDH E2E tests",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {