@red-hat-developer-hub/e2e-test-utils 1.1.10

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 (113) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +6 -0
  3. package/dist/deployment/keycloak/config/keycloak-values.yaml +96 -0
  4. package/dist/deployment/keycloak/constants.d.ts +29 -0
  5. package/dist/deployment/keycloak/constants.d.ts.map +1 -0
  6. package/dist/deployment/keycloak/constants.js +75 -0
  7. package/dist/deployment/keycloak/deployment.d.ts +89 -0
  8. package/dist/deployment/keycloak/deployment.d.ts.map +1 -0
  9. package/dist/deployment/keycloak/deployment.js +435 -0
  10. package/dist/deployment/keycloak/index.d.ts +2 -0
  11. package/dist/deployment/keycloak/index.d.ts.map +1 -0
  12. package/dist/deployment/keycloak/index.js +1 -0
  13. package/dist/deployment/keycloak/types.d.ts +59 -0
  14. package/dist/deployment/keycloak/types.d.ts.map +1 -0
  15. package/dist/deployment/keycloak/types.js +1 -0
  16. package/dist/deployment/rhdh/config/auth/guest/app-config.yaml +5 -0
  17. package/dist/deployment/rhdh/config/auth/keycloak/app-config.yaml +19 -0
  18. package/dist/deployment/rhdh/config/auth/keycloak/dynamic-plugins.yaml +3 -0
  19. package/dist/deployment/rhdh/config/auth/keycloak/secrets.yaml +12 -0
  20. package/dist/deployment/rhdh/config/common/app-config-rhdh.yaml +6 -0
  21. package/dist/deployment/rhdh/config/common/dynamic-plugins.yaml +3 -0
  22. package/dist/deployment/rhdh/config/common/rhdh-secrets.yaml +7 -0
  23. package/dist/deployment/rhdh/config/helm/value_file.yaml +7 -0
  24. package/dist/deployment/rhdh/config/operator/subscription.yaml +21 -0
  25. package/dist/deployment/rhdh/constants.d.ts +19 -0
  26. package/dist/deployment/rhdh/constants.d.ts.map +1 -0
  27. package/dist/deployment/rhdh/constants.js +27 -0
  28. package/dist/deployment/rhdh/deployment.d.ts +40 -0
  29. package/dist/deployment/rhdh/deployment.d.ts.map +1 -0
  30. package/dist/deployment/rhdh/deployment.js +303 -0
  31. package/dist/deployment/rhdh/index.d.ts +2 -0
  32. package/dist/deployment/rhdh/index.d.ts.map +1 -0
  33. package/dist/deployment/rhdh/index.js +1 -0
  34. package/dist/deployment/rhdh/types.d.ts +31 -0
  35. package/dist/deployment/rhdh/types.d.ts.map +1 -0
  36. package/dist/deployment/rhdh/types.js +1 -0
  37. package/dist/eslint/base.config.d.ts +10 -0
  38. package/dist/eslint/base.config.d.ts.map +1 -0
  39. package/dist/eslint/base.config.js +213 -0
  40. package/dist/playwright/base-config.d.ts +14 -0
  41. package/dist/playwright/base-config.d.ts.map +1 -0
  42. package/dist/playwright/base-config.js +47 -0
  43. package/dist/playwright/fixtures/test.d.ts +13 -0
  44. package/dist/playwright/fixtures/test.d.ts.map +1 -0
  45. package/dist/playwright/fixtures/test.js +48 -0
  46. package/dist/playwright/global-setup.d.ts +6 -0
  47. package/dist/playwright/global-setup.d.ts.map +1 -0
  48. package/dist/playwright/global-setup.js +67 -0
  49. package/dist/playwright/helpers/accessibility.d.ts +13 -0
  50. package/dist/playwright/helpers/accessibility.d.ts.map +1 -0
  51. package/dist/playwright/helpers/accessibility.js +24 -0
  52. package/dist/playwright/helpers/api-endpoints.d.ts +11 -0
  53. package/dist/playwright/helpers/api-endpoints.d.ts.map +1 -0
  54. package/dist/playwright/helpers/api-endpoints.js +15 -0
  55. package/dist/playwright/helpers/api-helper.d.ts +77 -0
  56. package/dist/playwright/helpers/api-helper.d.ts.map +1 -0
  57. package/dist/playwright/helpers/api-helper.js +285 -0
  58. package/dist/playwright/helpers/common.d.ts +31 -0
  59. package/dist/playwright/helpers/common.d.ts.map +1 -0
  60. package/dist/playwright/helpers/common.js +342 -0
  61. package/dist/playwright/helpers/index.d.ts +5 -0
  62. package/dist/playwright/helpers/index.d.ts.map +1 -0
  63. package/dist/playwright/helpers/index.js +4 -0
  64. package/dist/playwright/helpers/navbar.d.ts +2 -0
  65. package/dist/playwright/helpers/navbar.d.ts.map +1 -0
  66. package/dist/playwright/helpers/navbar.js +1 -0
  67. package/dist/playwright/helpers/ui-helper.d.ts +106 -0
  68. package/dist/playwright/helpers/ui-helper.d.ts.map +1 -0
  69. package/dist/playwright/helpers/ui-helper.js +439 -0
  70. package/dist/playwright/page-objects/global-obj.d.ts +25 -0
  71. package/dist/playwright/page-objects/global-obj.d.ts.map +1 -0
  72. package/dist/playwright/page-objects/global-obj.js +24 -0
  73. package/dist/playwright/page-objects/page-obj.d.ts +41 -0
  74. package/dist/playwright/page-objects/page-obj.d.ts.map +1 -0
  75. package/dist/playwright/page-objects/page-obj.js +40 -0
  76. package/dist/playwright/pages/catalog-import.d.ts +31 -0
  77. package/dist/playwright/pages/catalog-import.d.ts.map +1 -0
  78. package/dist/playwright/pages/catalog-import.js +65 -0
  79. package/dist/playwright/pages/catalog.d.ts +14 -0
  80. package/dist/playwright/pages/catalog.d.ts.map +1 -0
  81. package/dist/playwright/pages/catalog.js +37 -0
  82. package/dist/playwright/pages/extensions.d.ts +38 -0
  83. package/dist/playwright/pages/extensions.d.ts.map +1 -0
  84. package/dist/playwright/pages/extensions.js +110 -0
  85. package/dist/playwright/pages/home-page.d.ts +10 -0
  86. package/dist/playwright/pages/home-page.d.ts.map +1 -0
  87. package/dist/playwright/pages/home-page.js +46 -0
  88. package/dist/playwright/pages/index.d.ts +6 -0
  89. package/dist/playwright/pages/index.d.ts.map +1 -0
  90. package/dist/playwright/pages/index.js +5 -0
  91. package/dist/playwright/pages/notifications.d.ts +24 -0
  92. package/dist/playwright/pages/notifications.d.ts.map +1 -0
  93. package/dist/playwright/pages/notifications.js +112 -0
  94. package/dist/utils/bash.d.ts +3 -0
  95. package/dist/utils/bash.d.ts.map +1 -0
  96. package/dist/utils/bash.js +4 -0
  97. package/dist/utils/common.d.ts +3 -0
  98. package/dist/utils/common.d.ts.map +1 -0
  99. package/dist/utils/common.js +8 -0
  100. package/dist/utils/index.d.ts +5 -0
  101. package/dist/utils/index.d.ts.map +1 -0
  102. package/dist/utils/index.js +4 -0
  103. package/dist/utils/kubernetes-client.d.ts +97 -0
  104. package/dist/utils/kubernetes-client.d.ts.map +1 -0
  105. package/dist/utils/kubernetes-client.js +558 -0
  106. package/dist/utils/merge-yamls.d.ts +50 -0
  107. package/dist/utils/merge-yamls.d.ts.map +1 -0
  108. package/dist/utils/merge-yamls.js +96 -0
  109. package/dist/utils/plugin-metadata.d.ts +116 -0
  110. package/dist/utils/plugin-metadata.d.ts.map +1 -0
  111. package/dist/utils/plugin-metadata.js +330 -0
  112. package/package.json +108 -0
  113. package/tsconfig.base.json +16 -0
@@ -0,0 +1,31 @@
1
+ import { UIhelper } from "./ui-helper.js";
2
+ import type { Browser, Page, TestInfo } from "@playwright/test";
3
+ export declare class LoginHelper {
4
+ page: Page;
5
+ uiHelper: UIhelper;
6
+ constructor(page: Page);
7
+ loginAsGuest(): Promise<void>;
8
+ signOut(): Promise<void>;
9
+ private logintoGithub;
10
+ logintoKeycloak(userid: string, password: string): Promise<void>;
11
+ loginAsKeycloakUser(userid?: string, password?: string): Promise<void>;
12
+ loginAsGithubUser(userid?: string): Promise<void>;
13
+ checkAndReauthorizeGithubApp(): Promise<void>;
14
+ googleSignIn(email: string): Promise<void>;
15
+ checkAndClickOnGHloginPopup(force?: boolean): Promise<void>;
16
+ getButtonSelector(label: string): string;
17
+ getLoginBtnSelector(): string;
18
+ clickOnGHloginPopup(): Promise<void>;
19
+ getGitHub2FAOTP(userid: string): string;
20
+ getGoogle2FAOTP(): string;
21
+ keycloakLogin(username: string, password: string): Promise<"Already logged in" | "Login successful" | "User does not exist">;
22
+ private handleGitHubPopupLogin;
23
+ githubLogin(username: string, password: string, twofactor: string): Promise<string>;
24
+ githubLoginFromSettingsPage(username: string, password: string, twofactor: string): Promise<string>;
25
+ microsoftAzureLogin(username: string, password: string): Promise<"Already logged in" | "Login successful" | "User does not exist">;
26
+ }
27
+ export declare function setupBrowser(browser: Browser, testInfo: TestInfo): Promise<{
28
+ page: Page;
29
+ context: import("playwright-core").BrowserContext;
30
+ }>;
31
+ //# sourceMappingURL=common.d.ts.map
@@ -0,0 +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;IAmCrB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;IAYhD,mBAAmB,CACvB,MAAM,GAAE,MAAkC,EAC1C,QAAQ,GAAE,MAAkC;IASxC,iBAAiB,CAAC,MAAM,GAAE,MAAyC;IA4BnE,4BAA4B;IAqB5B,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;IAcvC,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"}
@@ -0,0 +1,342 @@
1
+ import { UIhelper } from "./ui-helper.js";
2
+ import { authenticator } from "otplib";
3
+ import { test, expect } from "@playwright/test";
4
+ import { SETTINGS_PAGE_COMPONENTS } from "../page-objects/page-obj.js";
5
+ import { UI_HELPER_ELEMENTS } from "../page-objects/global-obj.js";
6
+ import * as path from "path";
7
+ import * as fs from "fs";
8
+ import { DEFAULT_USERS } from "../../deployment/keycloak/constants.js";
9
+ export class LoginHelper {
10
+ page;
11
+ uiHelper;
12
+ constructor(page) {
13
+ this.page = page;
14
+ this.uiHelper = new UIhelper(page);
15
+ }
16
+ async loginAsGuest() {
17
+ await this.page.goto("/");
18
+ await this.uiHelper.waitForLoad(240000);
19
+ // TODO - Remove it after https://issues.redhat.com/browse/RHIDP-2043. A Dynamic plugin for Guest Authentication Provider needs to be created
20
+ this.page.on("dialog", async (dialog) => {
21
+ console.log(`Dialog message: ${dialog.message()}`);
22
+ await dialog.accept();
23
+ });
24
+ await this.uiHelper.verifyHeading("Select a sign-in method");
25
+ await this.uiHelper.clickButton("Enter");
26
+ await this.page.waitForSelector("nav a", { timeout: 10_000 });
27
+ }
28
+ async signOut() {
29
+ await this.page.click(SETTINGS_PAGE_COMPONENTS.userSettingsMenu);
30
+ await this.page.click(SETTINGS_PAGE_COMPONENTS.signOut);
31
+ await this.uiHelper.verifyHeading("Select a sign-in method");
32
+ }
33
+ async logintoGithub(userid) {
34
+ await this.page.goto("https://github.com/login");
35
+ await this.page.waitForSelector("#login_field");
36
+ await this.page.fill("#login_field", userid);
37
+ switch (userid) {
38
+ case process.env.GH_USER_ID:
39
+ await this.page.fill("#password", process.env.GH_USER_PASS);
40
+ break;
41
+ case process.env.GH_USER2_ID:
42
+ await this.page.fill("#password", process.env.GH_USER2_PASS);
43
+ break;
44
+ default:
45
+ throw new Error("Invalid User ID");
46
+ }
47
+ await this.page.click('[value="Sign in"]');
48
+ await this.page.fill("#app_totp", this.getGitHub2FAOTP(userid));
49
+ test.setTimeout(130000);
50
+ if ((await this.uiHelper.isTextVisible("The two-factor code you entered has already been used")) ||
51
+ (await this.uiHelper.isTextVisible("too many codes have been submitted", 3000))) {
52
+ await this.page.waitForTimeout(60000);
53
+ await this.page.fill("#app_totp", this.getGitHub2FAOTP(userid));
54
+ }
55
+ await this.page.waitForTimeout(3_000);
56
+ }
57
+ async logintoKeycloak(userid, password) {
58
+ await new Promise((resolve) => {
59
+ this.page.once("popup", async (popup) => {
60
+ await popup.waitForLoadState();
61
+ await popup.locator("#username").fill(userid);
62
+ await popup.locator("#password").fill(password);
63
+ await popup.locator("#kc-login").click();
64
+ resolve();
65
+ });
66
+ });
67
+ }
68
+ async loginAsKeycloakUser(userid = DEFAULT_USERS[0].username, password = DEFAULT_USERS[0].password) {
69
+ await this.page.goto("/");
70
+ await this.uiHelper.waitForLoad(240000);
71
+ await this.uiHelper.clickButton("Sign In");
72
+ await this.logintoKeycloak(userid, password);
73
+ await this.page.waitForSelector("nav a", { timeout: 10_000 });
74
+ }
75
+ async loginAsGithubUser(userid = process.env.GH_USER_ID) {
76
+ const sessionFileName = `authState_${userid}.json`;
77
+ // Check if a session file for this specific user already exists
78
+ if (fs.existsSync(sessionFileName)) {
79
+ // Load and reuse existing authentication state
80
+ const cookies = JSON.parse(fs.readFileSync(sessionFileName, "utf-8")).cookies;
81
+ await this.page.context().addCookies(cookies);
82
+ console.log(`Reusing existing authentication state for user: ${userid}`);
83
+ await this.page.goto("/");
84
+ await this.uiHelper.waitForLoad(12000);
85
+ await this.uiHelper.clickButton("Sign In");
86
+ await this.checkAndReauthorizeGithubApp();
87
+ }
88
+ else {
89
+ // Perform login if no session file exists, then save the state
90
+ await this.logintoGithub(userid);
91
+ await this.page.goto("/");
92
+ await this.uiHelper.waitForLoad(240000);
93
+ await this.uiHelper.clickButton("Sign In");
94
+ await this.checkAndReauthorizeGithubApp();
95
+ await this.page.waitForSelector("nav a", { timeout: 10_000 });
96
+ await this.page.context().storageState({ path: sessionFileName });
97
+ console.log(`Authentication state saved for user: ${userid}`);
98
+ }
99
+ }
100
+ async checkAndReauthorizeGithubApp() {
101
+ await new Promise((resolve) => {
102
+ this.page.once("popup", async (popup) => {
103
+ await popup.waitForLoadState();
104
+ // Check for popup closure for up to 10 seconds before proceeding
105
+ for (let attempts = 0; attempts < 10 && !popup.isClosed(); attempts++) {
106
+ await this.page.waitForTimeout(1000); // Using page here because if the popup closes automatically, it throws an error during the wait
107
+ }
108
+ const locator = popup.locator("button.js-oauth-authorize-btn");
109
+ if (!popup.isClosed() && (await locator.isVisible())) {
110
+ await popup.locator("body").click();
111
+ await locator.waitFor();
112
+ await locator.click();
113
+ }
114
+ resolve();
115
+ });
116
+ });
117
+ }
118
+ async googleSignIn(email) {
119
+ await new Promise((resolve) => {
120
+ this.page.once("popup", async (popup) => {
121
+ await popup.waitForLoadState();
122
+ const locator = popup
123
+ .getByRole("link", { name: email, exact: false })
124
+ .first();
125
+ await popup.waitForTimeout(3000);
126
+ await locator.waitFor({ state: "visible" });
127
+ await locator.click({ force: true });
128
+ await popup.waitForTimeout(3000);
129
+ await popup
130
+ .locator("[name=Passwd]")
131
+ .fill(process.env.GOOGLE_USER_PASS);
132
+ await popup.locator("[name=Passwd]").press("Enter");
133
+ await popup.waitForTimeout(3500);
134
+ await popup.locator("[name=totpPin]").fill(this.getGoogle2FAOTP());
135
+ await popup.locator("[name=totpPin]").press("Enter");
136
+ await popup
137
+ .getByRole("button", { name: /Continue|Weiter/ })
138
+ .click({ timeout: 60000 });
139
+ resolve();
140
+ });
141
+ });
142
+ }
143
+ async checkAndClickOnGHloginPopup(force = false) {
144
+ const frameLocator = this.page.getByLabel("Login Required");
145
+ try {
146
+ await frameLocator.waitFor({ state: "visible", timeout: 2000 });
147
+ await this.clickOnGHloginPopup();
148
+ }
149
+ catch (error) {
150
+ if (force)
151
+ throw error;
152
+ }
153
+ }
154
+ getButtonSelector(label) {
155
+ return `${UI_HELPER_ELEMENTS.MuiButtonLabel}:has-text("${label}")`;
156
+ }
157
+ getLoginBtnSelector() {
158
+ return 'MuiListItem-root li.MuiListItem-root button.MuiButton-root:has(span.MuiButton-label:text("Log in"))';
159
+ }
160
+ async clickOnGHloginPopup() {
161
+ const isLoginRequiredVisible = await this.uiHelper.isTextVisible("Sign in");
162
+ if (isLoginRequiredVisible) {
163
+ await this.uiHelper.clickButton("Sign in");
164
+ await this.uiHelper.clickButton("Log in");
165
+ await this.checkAndReauthorizeGithubApp();
166
+ await this.page.waitForSelector(this.getLoginBtnSelector(), {
167
+ state: "detached",
168
+ });
169
+ }
170
+ else {
171
+ console.log('"Log in" button is not visible. Skipping login popup actions.');
172
+ }
173
+ }
174
+ getGitHub2FAOTP(userid) {
175
+ const secrets = {
176
+ [process.env.GH_USER_ID]: process.env.GH_2FA_SECRET,
177
+ [process.env.GH_USER2_ID]: process.env.GH_USER2_2FA_SECRET,
178
+ };
179
+ const secret = secrets[userid];
180
+ if (!secret) {
181
+ throw new Error("Invalid User ID");
182
+ }
183
+ return authenticator.generate(secret);
184
+ }
185
+ getGoogle2FAOTP() {
186
+ const secret = process.env.GOOGLE_2FA_SECRET;
187
+ return authenticator.generate(secret);
188
+ }
189
+ async keycloakLogin(username, password) {
190
+ await this.page.goto("/");
191
+ await this.page.waitForSelector('p:has-text("Sign in using OIDC")');
192
+ const [popup] = await Promise.all([
193
+ this.page.waitForEvent("popup"),
194
+ this.uiHelper.clickButton("Sign In"),
195
+ ]);
196
+ await popup.waitForLoadState("domcontentloaded");
197
+ // Check if popup closes automatically (already logged in)
198
+ try {
199
+ await popup.waitForEvent("close", { timeout: 5000 });
200
+ return "Already logged in";
201
+ }
202
+ catch {
203
+ // Popup didn't close, proceed with login
204
+ }
205
+ try {
206
+ await popup.locator("#username").click();
207
+ await popup.locator("#username").fill(username);
208
+ await popup.locator("#password").fill(password);
209
+ await popup.locator("[name=login]").click({ timeout: 5000 });
210
+ await popup.waitForEvent("close", { timeout: 2000 });
211
+ return "Login successful";
212
+ }
213
+ catch (e) {
214
+ const usernameError = popup.locator("id=input-error");
215
+ if (await usernameError.isVisible()) {
216
+ await popup.close();
217
+ return "User does not exist";
218
+ }
219
+ else {
220
+ throw e;
221
+ }
222
+ }
223
+ }
224
+ async handleGitHubPopupLogin(popup, username, password, twofactor) {
225
+ await expect(async () => {
226
+ await popup.waitForLoadState("domcontentloaded");
227
+ expect(popup).toBeTruthy();
228
+ }).toPass({
229
+ intervals: [5_000, 10_000],
230
+ timeout: 20 * 1000,
231
+ });
232
+ // Check if popup closes automatically
233
+ try {
234
+ await popup.waitForEvent("close", { timeout: 5000 });
235
+ return "Already logged in";
236
+ }
237
+ catch {
238
+ // Popup didn't close, proceed with login
239
+ }
240
+ try {
241
+ await popup.locator("#login_field").click({ timeout: 5000 });
242
+ await popup.locator("#login_field").fill(username, { timeout: 5000 });
243
+ const cookieLocator = popup.locator("#wcpConsentBannerCtrl");
244
+ if (await cookieLocator.isVisible()) {
245
+ await popup.click('button:has-text("Reject")', { timeout: 5000 });
246
+ }
247
+ await popup.locator("#password").click({ timeout: 5000 });
248
+ await popup.locator("#password").fill(password, { timeout: 5000 });
249
+ await popup
250
+ .locator("[type='submit'][value='Sign in']:not(webauthn-status *)")
251
+ .first()
252
+ .click({ timeout: 5000 });
253
+ const twofactorcode = authenticator.generate(twofactor);
254
+ await popup.locator("#app_totp").click({ timeout: 5000 });
255
+ await popup.locator("#app_totp").fill(twofactorcode, { timeout: 5000 });
256
+ await popup.waitForEvent("close", { timeout: 20000 });
257
+ return "Login successful";
258
+ }
259
+ catch (e) {
260
+ const authorization = popup.locator("button.js-oauth-authorize-btn");
261
+ if (await authorization.isVisible()) {
262
+ await authorization.click();
263
+ return "Login successful";
264
+ }
265
+ else {
266
+ throw e;
267
+ }
268
+ }
269
+ }
270
+ async githubLogin(username, password, twofactor) {
271
+ await this.page.goto("/");
272
+ await this.page.waitForSelector('p:has-text("Sign in using GitHub")');
273
+ const [popup] = await Promise.all([
274
+ this.page.waitForEvent("popup"),
275
+ this.uiHelper.clickButton("Sign In"),
276
+ ]);
277
+ return this.handleGitHubPopupLogin(popup, username, password, twofactor);
278
+ }
279
+ async githubLoginFromSettingsPage(username, password, twofactor) {
280
+ await this.page.goto("/settings/auth-providers");
281
+ const [popup] = await Promise.all([
282
+ this.page.waitForEvent("popup"),
283
+ this.page.getByTitle("Sign in to GitHub").click(),
284
+ this.uiHelper.clickButton("Log in"),
285
+ ]);
286
+ return this.handleGitHubPopupLogin(popup, username, password, twofactor);
287
+ }
288
+ async microsoftAzureLogin(username, password) {
289
+ await this.page.goto("/");
290
+ await this.page.waitForSelector('p:has-text("Sign in using Microsoft")');
291
+ const [popup] = await Promise.all([
292
+ this.page.waitForEvent("popup"),
293
+ this.uiHelper.clickButton("Sign In"),
294
+ ]);
295
+ await popup.waitForLoadState("domcontentloaded");
296
+ if (popup.url().startsWith(process.env.BASE_URL)) {
297
+ // an active microsoft session is already logged in and the popup will automatically close
298
+ return "Already logged in";
299
+ }
300
+ else {
301
+ try {
302
+ await popup.locator("[name=loginfmt]").click();
303
+ await popup
304
+ .locator("[name=loginfmt]")
305
+ .fill(username, { timeout: 5000 });
306
+ await popup
307
+ .locator('[type=submit]:has-text("Next")')
308
+ .click({ timeout: 5000 });
309
+ await popup.locator("[name=passwd]").click();
310
+ await popup.locator("[name=passwd]").fill(password, { timeout: 5000 });
311
+ await popup
312
+ .locator('[type=submit]:has-text("Sign in")')
313
+ .click({ timeout: 5000 });
314
+ await popup
315
+ .locator('[type=button]:has-text("No")')
316
+ .click({ timeout: 15000 });
317
+ return "Login successful";
318
+ }
319
+ catch (e) {
320
+ const usernameError = popup.locator("id=usernameError");
321
+ if (await usernameError.isVisible()) {
322
+ return "User does not exist";
323
+ }
324
+ else {
325
+ throw e;
326
+ }
327
+ }
328
+ }
329
+ }
330
+ }
331
+ export async function setupBrowser(browser, testInfo) {
332
+ const context = await browser.newContext({
333
+ recordVideo: {
334
+ dir: `test-results/${path
335
+ .parse(testInfo.file)
336
+ .name.replace(".spec", "")}/${testInfo.titlePath[1]}`,
337
+ size: { width: 1920, height: 1080 },
338
+ },
339
+ });
340
+ const page = await context.newPage();
341
+ return { page, context };
342
+ }
@@ -0,0 +1,5 @@
1
+ export { GITHUB_API_ENDPOINTS } from "./api-endpoints.js";
2
+ export { APIHelper } from "./api-helper.js";
3
+ export { LoginHelper, setupBrowser } from "./common.js";
4
+ export { UIhelper } from "./ui-helper.js";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,4 @@
1
+ export { GITHUB_API_ENDPOINTS } from "./api-endpoints.js";
2
+ export { APIHelper } from "./api-helper.js";
3
+ export { LoginHelper, setupBrowser } from "./common.js";
4
+ export { UIhelper } from "./ui-helper.js";
@@ -0,0 +1,2 @@
1
+ export type SidebarTabs = "Catalog" | "Settings" | "My Group" | "Home" | "Self-service" | "Learning Paths" | "Extensions" | "Bulk import" | "Docs" | "Clusters" | "Tech Radar" | "Notifications" | "Orchestrator";
2
+ //# sourceMappingURL=navbar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"navbar.d.ts","sourceRoot":"","sources":["../../../src/playwright/helpers/navbar.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GACnB,SAAS,GACT,UAAU,GACV,UAAU,GACV,MAAM,GACN,cAAc,GACd,gBAAgB,GAChB,YAAY,GACZ,aAAa,GACb,MAAM,GACN,UAAU,GACV,YAAY,GACZ,eAAe,GACf,cAAc,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,106 @@
1
+ import type { Locator, Page } from "@playwright/test";
2
+ import type { SidebarTabs } from "./navbar.js";
3
+ export declare class UIhelper {
4
+ private page;
5
+ constructor(page: Page);
6
+ waitForLoad(timeout?: number): Promise<void>;
7
+ verifyComponentInCatalog(kind: string, expectedRows: string[]): Promise<void>;
8
+ getSideBarMenuItem(menuItem: string): Locator;
9
+ fillTextInputByLabel(label: string, text: string): Promise<void>;
10
+ /**
11
+ * Fills the search input with the provided text.
12
+ *
13
+ * @param searchText - The text to be entered into the search input field.
14
+ */
15
+ searchInputPlaceholder(searchText: string): Promise<void>;
16
+ searchInputAriaLabel(searchText: string): Promise<void>;
17
+ pressTab(): Promise<void>;
18
+ checkCheckbox(text: string): Promise<void>;
19
+ uncheckCheckbox(text: string): Promise<void>;
20
+ clickButton(label: string | RegExp, options?: {
21
+ exact?: boolean;
22
+ force?: boolean;
23
+ }): Promise<Locator>;
24
+ clickBtnByTitleIfNotPressed(title: string): Promise<void>;
25
+ clickByDataTestId(dataTestId: string): Promise<void>;
26
+ /**
27
+ * Clicks on a button element by its text content, waiting for it to be visible first.
28
+ *
29
+ * @param buttonText - The text content of the button to click on.
30
+ * @param options - Optional configuration for exact match, timeout, and force click.
31
+ */
32
+ clickButtonByText(buttonText: string | RegExp, options?: {
33
+ exact?: boolean;
34
+ timeout?: number;
35
+ force?: boolean;
36
+ }): Promise<void>;
37
+ clickButtonByLabel(label: string | RegExp): Promise<void>;
38
+ clickLink(options: string | {
39
+ href: string;
40
+ } | {
41
+ ariaLabel: string;
42
+ }): Promise<void>;
43
+ openProfileDropdown(): Promise<void>;
44
+ goToPageUrl(url: string, heading?: string): Promise<void>;
45
+ verifyLink(arg: string | {
46
+ label: string;
47
+ }, options?: {
48
+ exact?: boolean;
49
+ notVisible?: boolean;
50
+ }): Promise<void>;
51
+ private isElementVisible;
52
+ isBtnVisibleByTitle(text: string): Promise<boolean>;
53
+ isBtnVisible(text: string): Promise<boolean>;
54
+ isTextVisible(text: string, timeout?: number): Promise<boolean>;
55
+ verifyTextVisible(text: string, exact?: boolean, timeout?: number): Promise<void>;
56
+ verifyLinkVisible(text: string, timeout?: number): Promise<void>;
57
+ openSidebar(navBarText: SidebarTabs): Promise<void>;
58
+ openCatalogSidebar(kind: string): Promise<void>;
59
+ openSidebarButton(navBarButtonLabel: string): Promise<void>;
60
+ selectMuiBox(label: string, value: string): Promise<void>;
61
+ verifyRowsInTable(rowTexts: (string | RegExp)[], exact?: boolean): Promise<void>;
62
+ waitForTextDisappear(text: string): Promise<void>;
63
+ verifyText(text: string | RegExp, exact?: boolean): Promise<void>;
64
+ private verifyTextInLocator;
65
+ verifyTextInSelector(selector: string, expectedText: string): Promise<void>;
66
+ verifyColumnHeading(rowTexts: string[] | RegExp[], exact?: boolean): Promise<void>;
67
+ verifyHeading(heading: string | RegExp, timeout?: number): Promise<void>;
68
+ verifyParagraph(paragraph: string): Promise<void>;
69
+ waitForTitle(text: string, level?: number): Promise<void>;
70
+ clickTab(tabName: string): Promise<void>;
71
+ verifyCellsInTable(texts: (string | RegExp)[]): Promise<void>;
72
+ verifyButtonURL(label: string | RegExp, url: string | RegExp, options?: {
73
+ locator?: string;
74
+ exact?: boolean;
75
+ }): Promise<void>;
76
+ /**
77
+ * Verifies that a table row, identified by unique text, contains specific cell texts.
78
+ * @param {string} uniqueRowText - The unique text present in one of the cells within the row. This is used to identify the specific row.
79
+ * @param {Array<string | RegExp>} cellTexts - An array of cell texts or regular expressions to match against the cells within the identified row.
80
+ * @example
81
+ * // Example usage to verify that a row containing "Developer-hub" has cells with the texts "service" and "active":
82
+ * await verifyRowInTableByUniqueText('Developer-hub', ['service', 'active']);
83
+ */
84
+ verifyRowInTableByUniqueText(uniqueRowText: string, cellTexts: string[] | RegExp[]): Promise<void>;
85
+ /**
86
+ * Clicks on a link within a table row that contains a unique text and matches a link's text.
87
+ * @param {string} uniqueRowText - The unique text present in one of the cells within the row. This is used to identify the specific row.
88
+ * @param {string | RegExp} linkText - The text of the link, can be a string or a regular expression.
89
+ * @param {boolean} [exact=true] - Whether to match the link text exactly. By default, this is set to true.
90
+ */
91
+ clickOnLinkInTableByUniqueText(uniqueRowText: string, linkText: string | RegExp, exact?: boolean): Promise<void>;
92
+ /**
93
+ * Clicks on a button within a table row that contains a unique text and matches a button's label or aria-label.
94
+ * @param {string} uniqueRowText - The unique text present in one of the cells within the row. This is used to identify the specific row.
95
+ * @param {string | RegExp} textOrLabel - The text of the button or the `aria-label` attribute, can be a string or a regular expression.
96
+ */
97
+ clickOnButtonInTableByUniqueText(uniqueRowText: string, textOrLabel: string | RegExp): Promise<void>;
98
+ verifyLinkinCard(cardHeading: string, linkText: string, exact?: boolean): Promise<void>;
99
+ clickBtnInCard(cardText: string, btnText: string, exact?: boolean): Promise<void>;
100
+ verifyTextinCard(cardHeading: string, text: string | RegExp, exact?: boolean): Promise<void>;
101
+ verifyTableHeadingAndRows(texts: string[]): Promise<void>;
102
+ verifyTableIsEmpty(): Promise<void>;
103
+ verifyAlertErrorMessage(message: string | RegExp): Promise<void>;
104
+ verifyTextInTooltip(text: string | RegExp): Promise<void>;
105
+ }
106
+ //# sourceMappingURL=ui-helper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ui-helper.d.ts","sourceRoot":"","sources":["../../../src/playwright/helpers/ui-helper.ts"],"names":[],"mappings":"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"}