rhdh-e2e-test-utils 1.0.0 → 1.1.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 (95) hide show
  1. package/README.md +652 -56
  2. package/dist/deployment/keycloak/config/keycloak-values.yaml +94 -0
  3. package/dist/deployment/keycloak/constants.d.ts +29 -0
  4. package/dist/deployment/keycloak/constants.d.ts.map +1 -0
  5. package/dist/deployment/keycloak/constants.js +75 -0
  6. package/dist/deployment/keycloak/deployment.d.ts +89 -0
  7. package/dist/deployment/keycloak/deployment.d.ts.map +1 -0
  8. package/dist/deployment/keycloak/deployment.js +437 -0
  9. package/dist/deployment/keycloak/index.d.ts +2 -0
  10. package/dist/deployment/keycloak/index.d.ts.map +1 -0
  11. package/dist/deployment/keycloak/index.js +1 -0
  12. package/dist/deployment/keycloak/types.d.ts +59 -0
  13. package/dist/deployment/keycloak/types.d.ts.map +1 -0
  14. package/dist/deployment/keycloak/types.js +1 -0
  15. package/dist/deployment/rhdh/config/auth/guest/app-config.yaml +5 -0
  16. package/dist/deployment/rhdh/config/auth/keycloak/app-config.yaml +19 -0
  17. package/dist/deployment/rhdh/config/auth/keycloak/dynamic-plugins.yaml +3 -0
  18. package/dist/deployment/rhdh/config/auth/keycloak/secrets.yaml +12 -0
  19. package/dist/deployment/rhdh/config/common/app-config-rhdh.yaml +6 -0
  20. package/dist/deployment/rhdh/config/common/dynamic-plugins.yaml +3 -0
  21. package/dist/deployment/rhdh/config/common/rhdh-secrets.yaml +7 -0
  22. package/dist/deployment/rhdh/config/helm/value_file.yaml +7 -0
  23. package/dist/deployment/rhdh/config/operator/subscription.yaml +21 -0
  24. package/dist/deployment/rhdh/constants.d.ts +6 -0
  25. package/dist/deployment/rhdh/constants.d.ts.map +1 -1
  26. package/dist/deployment/rhdh/constants.js +17 -5
  27. package/dist/deployment/rhdh/deployment.d.ts +8 -1
  28. package/dist/deployment/rhdh/deployment.d.ts.map +1 -1
  29. package/dist/deployment/rhdh/deployment.js +47 -39
  30. package/dist/deployment/rhdh/index.d.ts +0 -1
  31. package/dist/deployment/rhdh/index.d.ts.map +1 -1
  32. package/dist/deployment/rhdh/types.d.ts +4 -1
  33. package/dist/deployment/rhdh/types.d.ts.map +1 -1
  34. package/dist/eslint/base.config.d.ts.map +1 -1
  35. package/dist/eslint/base.config.js +9 -2
  36. package/dist/playwright/base-config.d.ts +3 -3
  37. package/dist/playwright/base-config.d.ts.map +1 -1
  38. package/dist/playwright/base-config.js +5 -4
  39. package/dist/playwright/fixtures/test.d.ts +4 -1
  40. package/dist/playwright/fixtures/test.d.ts.map +1 -1
  41. package/dist/playwright/fixtures/test.js +16 -4
  42. package/dist/playwright/global-setup.d.ts.map +1 -1
  43. package/dist/playwright/global-setup.js +36 -1
  44. package/dist/playwright/helpers/accessibility.d.ts +13 -0
  45. package/dist/playwright/helpers/accessibility.d.ts.map +1 -0
  46. package/dist/playwright/helpers/accessibility.js +24 -0
  47. package/dist/playwright/helpers/api-endpoints.d.ts +11 -0
  48. package/dist/playwright/helpers/api-endpoints.d.ts.map +1 -0
  49. package/dist/playwright/helpers/api-endpoints.js +15 -0
  50. package/dist/playwright/helpers/api-helper.d.ts +77 -0
  51. package/dist/playwright/helpers/api-helper.d.ts.map +1 -0
  52. package/dist/playwright/helpers/api-helper.js +285 -0
  53. package/dist/playwright/helpers/common.d.ts +31 -0
  54. package/dist/playwright/helpers/common.d.ts.map +1 -0
  55. package/dist/playwright/helpers/common.js +342 -0
  56. package/dist/playwright/helpers/index.d.ts +5 -0
  57. package/dist/playwright/helpers/index.d.ts.map +1 -0
  58. package/dist/playwright/helpers/index.js +4 -0
  59. package/dist/playwright/helpers/navbar.d.ts +2 -0
  60. package/dist/playwright/helpers/navbar.d.ts.map +1 -0
  61. package/dist/playwright/helpers/navbar.js +1 -0
  62. package/dist/playwright/helpers/ui-helper.d.ts +106 -0
  63. package/dist/playwright/helpers/ui-helper.d.ts.map +1 -0
  64. package/dist/playwright/helpers/ui-helper.js +439 -0
  65. package/dist/playwright/page-objects/global-obj.d.ts +25 -0
  66. package/dist/playwright/page-objects/global-obj.d.ts.map +1 -0
  67. package/dist/playwright/page-objects/global-obj.js +24 -0
  68. package/dist/playwright/page-objects/page-obj.d.ts +41 -0
  69. package/dist/playwright/page-objects/page-obj.d.ts.map +1 -0
  70. package/dist/playwright/page-objects/page-obj.js +40 -0
  71. package/dist/playwright/pages/catalog-import.d.ts +31 -0
  72. package/dist/playwright/pages/catalog-import.d.ts.map +1 -0
  73. package/dist/playwright/pages/catalog-import.js +65 -0
  74. package/dist/playwright/pages/catalog.d.ts +14 -0
  75. package/dist/playwright/pages/catalog.d.ts.map +1 -0
  76. package/dist/playwright/pages/catalog.js +37 -0
  77. package/dist/playwright/pages/extensions.d.ts +38 -0
  78. package/dist/playwright/pages/extensions.d.ts.map +1 -0
  79. package/dist/playwright/pages/extensions.js +110 -0
  80. package/dist/playwright/pages/home-page.d.ts +10 -0
  81. package/dist/playwright/pages/home-page.d.ts.map +1 -0
  82. package/dist/playwright/pages/home-page.js +46 -0
  83. package/dist/playwright/pages/index.d.ts +6 -0
  84. package/dist/playwright/pages/index.d.ts.map +1 -0
  85. package/dist/playwright/pages/index.js +5 -0
  86. package/dist/playwright/pages/notifications.d.ts +24 -0
  87. package/dist/playwright/pages/notifications.d.ts.map +1 -0
  88. package/dist/playwright/pages/notifications.js +112 -0
  89. package/dist/utils/kubernetes-client.d.ts +9 -0
  90. package/dist/utils/kubernetes-client.d.ts.map +1 -1
  91. package/dist/utils/kubernetes-client.js +57 -2
  92. package/dist/utils/merge-yamls.d.ts +25 -4
  93. package/dist/utils/merge-yamls.d.ts.map +1 -1
  94. package/dist/utils/merge-yamls.js +52 -12
  95. package/package.json +19 -6
@@ -10,12 +10,35 @@ $.verbose = true;
10
10
  class KubernetesClientHelper {
11
11
  _kc;
12
12
  _k8sApi;
13
+ _appsApi;
13
14
  _customObjectsApi;
14
15
  constructor() {
15
16
  this._kc = new k8s.KubeConfig();
16
17
  this._kc.loadFromDefault();
17
- this._k8sApi = this._kc.makeApiClient(k8s.CoreV1Api);
18
- this._customObjectsApi = this._kc.makeApiClient(k8s.CustomObjectsApi);
18
+ try {
19
+ this._k8sApi = this._kc.makeApiClient(k8s.CoreV1Api);
20
+ this._appsApi = this._kc.makeApiClient(k8s.AppsV1Api);
21
+ this._customObjectsApi = this._kc.makeApiClient(k8s.CustomObjectsApi);
22
+ }
23
+ catch (error) {
24
+ if (error instanceof Error &&
25
+ error.message.includes("No active cluster")) {
26
+ const currentContext = this._kc.getCurrentContext();
27
+ const contexts = this._kc.getContexts().map((c) => c.name);
28
+ throw new Error(`No active Kubernetes cluster found.\n\n` +
29
+ `The kubeconfig was loaded but no cluster is configured or the current context is invalid.\n\n` +
30
+ `Current context: ${currentContext || "(none)"}\n` +
31
+ `Available contexts: ${contexts.length > 0 ? contexts.join(", ") : "(none)"}\n\n` +
32
+ `To fix this:\n` +
33
+ ` 1. Log in to your k8s cluster: oc login or kubectl login\n` +
34
+ ` 2. Or set a valid context: kubectl config use-context <context-name>\n` +
35
+ ` 3. Verify your connection: oc whoami && oc cluster-info\n\n` +
36
+ `Kubeconfig locations checked:\n` +
37
+ ` - KUBECONFIG env: ${process.env.KUBECONFIG || "(not set)"}\n` +
38
+ ` - Default: ~/.kube/config`);
39
+ }
40
+ throw error;
41
+ }
19
42
  }
20
43
  /**
21
44
  * Create or update a ConfigMap from a file
@@ -263,6 +286,38 @@ class KubernetesClientHelper {
263
286
  }
264
287
  }
265
288
  }
289
+ /**
290
+ * Check if a StatefulSet is ready (all replicas are available)
291
+ */
292
+ async isStatefulSetReady(namespace, name) {
293
+ try {
294
+ const statefulSet = await this._appsApi.readNamespacedStatefulSet({
295
+ name,
296
+ namespace,
297
+ });
298
+ const replicas = statefulSet.spec?.replicas ?? 1;
299
+ const readyReplicas = statefulSet.status?.readyReplicas ?? 0;
300
+ return readyReplicas >= replicas;
301
+ }
302
+ catch {
303
+ return false;
304
+ }
305
+ }
306
+ /**
307
+ * Wait for a StatefulSet to be ready (all replicas available)
308
+ */
309
+ async waitForStatefulSetReady(namespace, name, timeoutSeconds = 300, pollIntervalMs = 5000) {
310
+ const startTime = Date.now();
311
+ const timeoutMs = timeoutSeconds * 1000;
312
+ while (Date.now() - startTime < timeoutMs) {
313
+ if (await this.isStatefulSetReady(namespace, name)) {
314
+ console.log(`✓ StatefulSet ${name} is ready`);
315
+ return true;
316
+ }
317
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
318
+ }
319
+ throw new Error(`StatefulSet ${name} in namespace ${namespace} not ready after ${timeoutSeconds}s`);
320
+ }
266
321
  /**
267
322
  * Get the cluster's ingress domain from OpenShift config
268
323
  * Equivalent to: oc get ingresses.config.openshift.io cluster -o jsonpath='{.spec.domain}'
@@ -1,24 +1,45 @@
1
1
  import yaml from "js-yaml";
2
+ /**
3
+ * Array merge strategy options for YAML merging.
4
+ */
5
+ export type ArrayMergeStrategy = "replace" | "concat" | {
6
+ byKey: string;
7
+ };
8
+ /**
9
+ * Options for YAML merging.
10
+ */
11
+ export interface MergeOptions {
12
+ /**
13
+ * Strategy for merging arrays.
14
+ * - "replace": Replace arrays entirely (default)
15
+ * - "concat": Concatenate arrays
16
+ * - { byKey: "keyName" }: Merge arrays of objects by a specific key
17
+ */
18
+ arrayMergeStrategy?: ArrayMergeStrategy;
19
+ }
2
20
  /**
3
21
  * Merge multiple YAML files into one object.
4
22
  *
5
23
  * @param paths List of YAML file paths (base first, overlays last)
24
+ * @param options Optional merge options (e.g., arrayMergeStrategy)
6
25
  * @returns Merged YAML object
7
26
  */
8
- export declare function mergeYamlFiles(paths: string[]): Promise<Record<string, unknown>>;
27
+ export declare function mergeYamlFiles(paths: string[], options?: MergeOptions): Promise<Record<string, unknown>>;
9
28
  /**
10
29
  * Merge multiple YAML files if they exist.
11
30
  *
12
31
  * @param paths List of YAML file paths
32
+ * @param options Optional merge options (e.g., arrayMergeStrategy)
13
33
  * @returns Merged YAML object
14
34
  */
15
- export declare function mergeYamlFilesIfExists(paths: string[]): Promise<Record<string, unknown>>;
35
+ export declare function mergeYamlFilesIfExists(paths: string[], options?: MergeOptions): Promise<Record<string, unknown>>;
16
36
  /**
17
37
  * Merge multiple YAML files and write the result to an output file.
18
38
  *
19
39
  * @param inputPaths List of input YAML files
20
40
  * @param outputPath Output YAML file path
21
- * @param options Optional dump formatting
41
+ * @param dumpOptions Optional dump formatting
42
+ * @param mergeOptions Optional merge options (e.g., arrayMergeStrategy)
22
43
  */
23
- export declare function mergeYamlFilesToFile(inputPaths: string[], outputPath: string, options?: yaml.DumpOptions): Promise<void>;
44
+ export declare function mergeYamlFilesToFile(inputPaths: string[], outputPath: string, dumpOptions?: yaml.DumpOptions, mergeOptions?: MergeOptions): Promise<void>;
24
45
  //# sourceMappingURL=merge-yamls.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"merge-yamls.d.ts","sourceRoot":"","sources":["../../src/utils/merge-yamls.ts"],"names":[],"mappings":"AACA,OAAO,IAAI,MAAM,SAAS,CAAC;AAkB3B;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,KAAK,EAAE,MAAM,EAAE,GACd,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAUlC;AAED;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,MAAM,EAAE,GACd,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAQlC;AAED;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CACxC,UAAU,EAAE,MAAM,EAAE,EACpB,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,IAAI,CAAC,WAA+B,GAC5C,OAAO,CAAC,IAAI,CAAC,CAKf"}
1
+ {"version":3,"file":"merge-yamls.d.ts","sourceRoot":"","sources":["../../src/utils/merge-yamls.ts"],"names":[],"mappings":"AACA,OAAO,IAAI,MAAM,SAAS,CAAC;AAG3B;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAC1B,SAAS,GACT,QAAQ,GACR;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtB;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;CACzC;AAwED;;;;;;GAMG;AACH,wBAAsB,cAAc,CAClC,KAAK,EAAE,MAAM,EAAE,EACf,OAAO,GAAE,YAAiB,GACzB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAUlC;AAED;;;;;;GAMG;AACH,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,MAAM,EAAE,EACf,OAAO,GAAE,YAAiB,GACzB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CASlC;AAED;;;;;;;GAOG;AACH,wBAAsB,oBAAoB,CACxC,UAAU,EAAE,MAAM,EAAE,EACpB,UAAU,EAAE,MAAM,EAClB,WAAW,GAAE,IAAI,CAAC,WAA+B,EACjD,YAAY,GAAE,YAAiB,GAC9B,OAAO,CAAC,IAAI,CAAC,CAKf"}
@@ -1,14 +1,51 @@
1
1
  import fs from "fs-extra";
2
2
  import yaml from "js-yaml";
3
3
  import mergeWith from "lodash.mergewith";
4
+ /**
5
+ * Merges two arrays of objects by a specific key.
6
+ * Objects with matching keys are deeply merged, new objects are appended.
7
+ */
8
+ function mergeArraysByKey(target, source, key, mergeOptions) {
9
+ const result = [...target];
10
+ for (const srcItem of source) {
11
+ if (typeof srcItem === "object" && srcItem !== null && key in srcItem) {
12
+ const srcKeyValue = srcItem[key];
13
+ const existingIndex = result.findIndex((item) => typeof item === "object" &&
14
+ item !== null &&
15
+ item[key] === srcKeyValue);
16
+ if (existingIndex !== -1) {
17
+ // Merge existing object with source object
18
+ result[existingIndex] = deepMerge(result[existingIndex], srcItem, mergeOptions);
19
+ }
20
+ else {
21
+ // Append new object
22
+ result.push(srcItem);
23
+ }
24
+ }
25
+ else {
26
+ // Non-object or missing key, append as-is
27
+ result.push(srcItem);
28
+ }
29
+ }
30
+ return result;
31
+ }
4
32
  /**
5
33
  * Deeply merges two YAML-compatible objects.
6
- * Arrays are replaced (not concatenated) this mimics Kustomize-style merging.
34
+ * Array handling is controlled by the arrayMergeStrategy option.
7
35
  */
8
- function deepMerge(target, source) {
9
- return mergeWith(target, source, (objValue, srcValue) => {
36
+ function deepMerge(target, source, options = {}) {
37
+ const strategy = options.arrayMergeStrategy ?? "replace";
38
+ return mergeWith({ ...target }, source, (objValue, srcValue) => {
10
39
  if (Array.isArray(objValue) && Array.isArray(srcValue)) {
11
- return srcValue; // Replace arrays instead of merging
40
+ if (strategy === "replace") {
41
+ return srcValue;
42
+ }
43
+ else if (strategy === "concat") {
44
+ return [...objValue, ...srcValue];
45
+ }
46
+ else if (typeof strategy === "object" && "byKey" in strategy) {
47
+ return mergeArraysByKey(objValue, srcValue, strategy.byKey, options);
48
+ }
12
49
  }
13
50
  });
14
51
  }
@@ -16,14 +53,15 @@ function deepMerge(target, source) {
16
53
  * Merge multiple YAML files into one object.
17
54
  *
18
55
  * @param paths List of YAML file paths (base first, overlays last)
56
+ * @param options Optional merge options (e.g., arrayMergeStrategy)
19
57
  * @returns Merged YAML object
20
58
  */
21
- export async function mergeYamlFiles(paths) {
59
+ export async function mergeYamlFiles(paths, options = {}) {
22
60
  let merged = {};
23
61
  for (const path of paths) {
24
62
  const content = await fs.readFile(path, "utf8");
25
63
  const parsed = (yaml.load(content) || {});
26
- merged = deepMerge(merged, parsed);
64
+ merged = deepMerge(merged, parsed, options);
27
65
  }
28
66
  return merged;
29
67
  }
@@ -31,26 +69,28 @@ export async function mergeYamlFiles(paths) {
31
69
  * Merge multiple YAML files if they exist.
32
70
  *
33
71
  * @param paths List of YAML file paths
72
+ * @param options Optional merge options (e.g., arrayMergeStrategy)
34
73
  * @returns Merged YAML object
35
74
  */
36
- export async function mergeYamlFilesIfExists(paths) {
75
+ export async function mergeYamlFilesIfExists(paths, options = {}) {
37
76
  return await mergeYamlFiles(paths.filter((path) => {
38
77
  const exists = fs.existsSync(path);
39
78
  if (!exists)
40
79
  console.log(`YAML file ${path} does not exist`);
41
80
  return exists;
42
- }));
81
+ }), options);
43
82
  }
44
83
  /**
45
84
  * Merge multiple YAML files and write the result to an output file.
46
85
  *
47
86
  * @param inputPaths List of input YAML files
48
87
  * @param outputPath Output YAML file path
49
- * @param options Optional dump formatting
88
+ * @param dumpOptions Optional dump formatting
89
+ * @param mergeOptions Optional merge options (e.g., arrayMergeStrategy)
50
90
  */
51
- export async function mergeYamlFilesToFile(inputPaths, outputPath, options = { lineWidth: -1 }) {
52
- const merged = await mergeYamlFiles(inputPaths);
53
- const yamlString = yaml.dump(merged, options);
91
+ export async function mergeYamlFilesToFile(inputPaths, outputPath, dumpOptions = { lineWidth: -1 }, mergeOptions = {}) {
92
+ const merged = await mergeYamlFiles(inputPaths, mergeOptions);
93
+ const yamlString = yaml.dump(merged, dumpOptions);
54
94
  await fs.outputFile(outputPath, yamlString);
55
95
  console.log(`Merged ${inputPaths.length} YAML files into ${outputPath}`);
56
96
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rhdh-e2e-test-utils",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Test utilities for RHDH E2E tests",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -27,17 +27,26 @@
27
27
  "./utils": {
28
28
  "types": "./dist/utils/index.d.ts",
29
29
  "default": "./dist/utils/index.js"
30
+ },
31
+ "./helpers": {
32
+ "types": "./dist/playwright/helpers/index.d.ts",
33
+ "default": "./dist/playwright/helpers/index.js"
34
+ },
35
+ "./pages": {
36
+ "types": "./dist/playwright/pages/index.d.ts",
37
+ "default": "./dist/playwright/pages/index.js"
38
+ },
39
+ "./keycloak": {
40
+ "types": "./dist/deployment/keycloak/index.d.ts",
41
+ "default": "./dist/deployment/keycloak/index.js"
30
42
  }
31
43
  },
32
44
  "files": [
33
45
  "dist",
34
- "tsconfig.base.json",
35
- "src/deployment/rhdh/config",
36
- "src/deployment/rhdh/helm",
37
- "src/deployment/rhdh/operator"
46
+ "tsconfig.base.json"
38
47
  ],
39
48
  "scripts": {
40
- "build": "yarn clean && tsc -p tsconfig.build.json",
49
+ "build": "yarn clean && tsc -p tsconfig.build.json && cp -r src/deployment/rhdh/config dist/deployment/rhdh/ && cp -r src/deployment/keycloak/config dist/deployment/keycloak/",
41
50
  "check": "yarn typecheck && yarn lint:check && yarn prettier:check",
42
51
  "clean": "rm -rf dist",
43
52
  "lint:check": "eslint . --ignore-pattern dist --ignore-pattern README.md",
@@ -61,6 +70,7 @@
61
70
  "@playwright/test": "^1.57.0"
62
71
  },
63
72
  "devDependencies": {
73
+ "@backstage/catalog-model": "1.7.5",
64
74
  "@playwright/test": "^1.57.0",
65
75
  "@types/fs-extra": "^11.0.4",
66
76
  "@types/js-yaml": "^4.0.9",
@@ -68,7 +78,9 @@
68
78
  "@types/node": "^24.10.1"
69
79
  },
70
80
  "dependencies": {
81
+ "@axe-core/playwright": "^4.11.0",
71
82
  "@eslint/js": "^9.39.1",
83
+ "@keycloak/keycloak-admin-client": "^26.0.0",
72
84
  "@kubernetes/client-node": "^1.4.0",
73
85
  "boxen": "^8.0.1",
74
86
  "eslint": "^9.39.1",
@@ -77,6 +89,7 @@
77
89
  "fs-extra": "^11.3.2",
78
90
  "js-yaml": "^4.1.1",
79
91
  "lodash.mergewith": "^4.6.2",
92
+ "otplib": "12.0.1",
80
93
  "prettier": "^3.7.4",
81
94
  "typescript": "^5.9.3",
82
95
  "typescript-eslint": "^8.48.1",