@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.
- package/dist/playwright/helpers/auth-token.d.ts +5 -0
- package/dist/playwright/helpers/auth-token.d.ts.map +1 -0
- package/dist/playwright/helpers/auth-token.js +37 -0
- package/dist/playwright/helpers/catalog-api-helper.d.ts +15 -0
- package/dist/playwright/helpers/catalog-api-helper.d.ts.map +1 -0
- package/dist/playwright/helpers/catalog-api-helper.js +63 -0
- package/dist/playwright/helpers/catalog-api-helper.test.d.ts +2 -0
- package/dist/playwright/helpers/catalog-api-helper.test.d.ts.map +1 -0
- package/dist/playwright/helpers/catalog-api-helper.test.js +66 -0
- package/dist/playwright/helpers/common.d.ts +0 -1
- package/dist/playwright/helpers/common.d.ts.map +1 -1
- package/dist/playwright/helpers/common.js +0 -4
- package/dist/playwright/helpers/index.d.ts +3 -0
- package/dist/playwright/helpers/index.d.ts.map +1 -1
- package/dist/playwright/helpers/index.js +3 -0
- package/dist/playwright/helpers/notifications-api-helper.d.ts +32 -0
- package/dist/playwright/helpers/notifications-api-helper.d.ts.map +1 -0
- package/dist/playwright/helpers/notifications-api-helper.js +35 -0
- package/dist/playwright/helpers/ui-helper.d.ts +2 -0
- package/dist/playwright/helpers/ui-helper.d.ts.map +1 -1
- package/dist/playwright/helpers/ui-helper.js +7 -10
- package/dist/playwright/page-objects/global-obj.d.ts +4 -1
- package/dist/playwright/page-objects/global-obj.d.ts.map +1 -1
- package/dist/playwright/page-objects/global-obj.js +4 -1
- package/dist/playwright/page-objects/global-obj.test.d.ts +2 -0
- package/dist/playwright/page-objects/global-obj.test.d.ts.map +1 -0
- package/dist/playwright/page-objects/global-obj.test.js +14 -0
- package/dist/playwright/pages/notifications.d.ts +14 -3
- package/dist/playwright/pages/notifications.d.ts.map +1 -1
- package/dist/playwright/pages/notifications.js +168 -48
- 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 @@
|
|
|
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;
|
|
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":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAMtD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG/C,qBAAa,QAAQ;IACnB,OAAO,CAAC,IAAI,CAAO;gBAEP,IAAI,EAAE,IAAI;IAIhB,WAAW,CAAC,OAAO,SAAS;IASlC;;;OAGG;IACG,0BAA0B,CAAC,OAAO,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE;IAY9D,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE;IAMnE,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAIvC,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;IAItD;;;;OAIG;IACG,sBAAsB,CAAC,UAAU,EAAE,MAAM;IAOzC,oBAAoB,CAAC,UAAU,EAAE,MAAM;IAIvC,QAAQ;IAIR,aAAa,CAAC,IAAI,EAAE,MAAM;IAO1B,eAAe,CAAC,IAAI,EAAE,MAAM;IAO5B,WAAW,CACf,KAAK,EAAE,MAAM,GAAG,MAAM,EACtB,OAAO,GAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAG1C;
|
|
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
|
-
.
|
|
76
|
-
.getByText(label, { exact: options.exact })
|
|
78
|
+
.getByRole("button", { name: label, exact: options.exact })
|
|
77
79
|
.first();
|
|
78
|
-
|
|
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.
|
|
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;;;
|
|
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 @@
|
|
|
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
|
-
|
|
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(
|
|
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,
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
await
|
|
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.
|
|
16
|
-
|
|
17
|
-
await
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
|
44
|
+
const markAllButton = this.page
|
|
31
45
|
.getByTitle("Mark all read")
|
|
32
|
-
.getByRole("button")
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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(
|
|
47
|
-
await this.
|
|
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.
|
|
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
|
|
96
|
+
await this.waitForNotificationsReady();
|
|
54
97
|
}
|
|
55
98
|
async saveSelected() {
|
|
56
|
-
await this.
|
|
57
|
-
|
|
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.
|
|
65
|
-
|
|
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
|
|
109
|
+
await this.waitForNotificationsReady();
|
|
75
110
|
}
|
|
76
111
|
async markLastNotificationAsRead() {
|
|
77
|
-
|
|
78
|
-
await row.getByRole("button").nth(1).click();
|
|
112
|
+
await this.toggleRead("unread");
|
|
79
113
|
}
|
|
80
114
|
async markNotificationAsRead(text) {
|
|
81
|
-
|
|
82
|
-
await row.getByRole("button").nth(1).click();
|
|
115
|
+
await this.toggleRead("unread", text);
|
|
83
116
|
}
|
|
84
117
|
async markLastNotificationAsUnRead() {
|
|
85
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
}
|