rhdh-e2e-test-utils 1.0.1 → 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 +577 -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/{dynamic-plugins.yaml → common/dynamic-plugins.yaml} +1 -0
  20. package/dist/deployment/rhdh/constants.d.ts +6 -0
  21. package/dist/deployment/rhdh/constants.d.ts.map +1 -1
  22. package/dist/deployment/rhdh/constants.js +17 -5
  23. package/dist/deployment/rhdh/deployment.d.ts +8 -1
  24. package/dist/deployment/rhdh/deployment.d.ts.map +1 -1
  25. package/dist/deployment/rhdh/deployment.js +47 -39
  26. package/dist/deployment/rhdh/index.d.ts +0 -1
  27. package/dist/deployment/rhdh/index.d.ts.map +1 -1
  28. package/dist/deployment/rhdh/types.d.ts +4 -1
  29. package/dist/deployment/rhdh/types.d.ts.map +1 -1
  30. package/dist/eslint/base.config.d.ts.map +1 -1
  31. package/dist/eslint/base.config.js +9 -2
  32. package/dist/playwright/base-config.d.ts +3 -3
  33. package/dist/playwright/base-config.d.ts.map +1 -1
  34. package/dist/playwright/base-config.js +5 -4
  35. package/dist/playwright/fixtures/test.d.ts +4 -1
  36. package/dist/playwright/fixtures/test.d.ts.map +1 -1
  37. package/dist/playwright/fixtures/test.js +16 -4
  38. package/dist/playwright/global-setup.d.ts.map +1 -1
  39. package/dist/playwright/global-setup.js +36 -1
  40. package/dist/playwright/helpers/accessibility.d.ts +13 -0
  41. package/dist/playwright/helpers/accessibility.d.ts.map +1 -0
  42. package/dist/playwright/helpers/accessibility.js +24 -0
  43. package/dist/playwright/helpers/api-endpoints.d.ts +11 -0
  44. package/dist/playwright/helpers/api-endpoints.d.ts.map +1 -0
  45. package/dist/playwright/helpers/api-endpoints.js +15 -0
  46. package/dist/playwright/helpers/api-helper.d.ts +77 -0
  47. package/dist/playwright/helpers/api-helper.d.ts.map +1 -0
  48. package/dist/playwright/helpers/api-helper.js +285 -0
  49. package/dist/playwright/helpers/common.d.ts +31 -0
  50. package/dist/playwright/helpers/common.d.ts.map +1 -0
  51. package/dist/playwright/helpers/common.js +342 -0
  52. package/dist/playwright/helpers/index.d.ts +5 -0
  53. package/dist/playwright/helpers/index.d.ts.map +1 -0
  54. package/dist/playwright/helpers/index.js +4 -0
  55. package/dist/playwright/helpers/navbar.d.ts +2 -0
  56. package/dist/playwright/helpers/navbar.d.ts.map +1 -0
  57. package/dist/playwright/helpers/navbar.js +1 -0
  58. package/dist/playwright/helpers/ui-helper.d.ts +106 -0
  59. package/dist/playwright/helpers/ui-helper.d.ts.map +1 -0
  60. package/dist/playwright/helpers/ui-helper.js +439 -0
  61. package/dist/playwright/page-objects/global-obj.d.ts +25 -0
  62. package/dist/playwright/page-objects/global-obj.d.ts.map +1 -0
  63. package/dist/playwright/page-objects/global-obj.js +24 -0
  64. package/dist/playwright/page-objects/page-obj.d.ts +41 -0
  65. package/dist/playwright/page-objects/page-obj.d.ts.map +1 -0
  66. package/dist/playwright/page-objects/page-obj.js +40 -0
  67. package/dist/playwright/pages/catalog-import.d.ts +31 -0
  68. package/dist/playwright/pages/catalog-import.d.ts.map +1 -0
  69. package/dist/playwright/pages/catalog-import.js +65 -0
  70. package/dist/playwright/pages/catalog.d.ts +14 -0
  71. package/dist/playwright/pages/catalog.d.ts.map +1 -0
  72. package/dist/playwright/pages/catalog.js +37 -0
  73. package/dist/playwright/pages/extensions.d.ts +38 -0
  74. package/dist/playwright/pages/extensions.d.ts.map +1 -0
  75. package/dist/playwright/pages/extensions.js +110 -0
  76. package/dist/playwright/pages/home-page.d.ts +10 -0
  77. package/dist/playwright/pages/home-page.d.ts.map +1 -0
  78. package/dist/playwright/pages/home-page.js +46 -0
  79. package/dist/playwright/pages/index.d.ts +6 -0
  80. package/dist/playwright/pages/index.d.ts.map +1 -0
  81. package/dist/playwright/pages/index.js +5 -0
  82. package/dist/playwright/pages/notifications.d.ts +24 -0
  83. package/dist/playwright/pages/notifications.d.ts.map +1 -0
  84. package/dist/playwright/pages/notifications.js +112 -0
  85. package/dist/utils/kubernetes-client.d.ts +9 -0
  86. package/dist/utils/kubernetes-client.d.ts.map +1 -1
  87. package/dist/utils/kubernetes-client.js +57 -2
  88. package/dist/utils/merge-yamls.d.ts +25 -4
  89. package/dist/utils/merge-yamls.d.ts.map +1 -1
  90. package/dist/utils/merge-yamls.js +52 -12
  91. package/package.json +18 -2
  92. /package/dist/deployment/rhdh/config/{app-config-rhdh.yaml → common/app-config-rhdh.yaml} +0 -0
  93. /package/dist/deployment/rhdh/config/{rhdh-secrets.yaml → common/rhdh-secrets.yaml} +0 -0
  94. /package/dist/deployment/rhdh/{helm → config/helm}/value_file.yaml +0 -0
  95. /package/dist/deployment/rhdh/{operator → config/operator}/subscription.yaml +0 -0
@@ -6,15 +6,16 @@ import { mergeYamlFilesIfExists } from "../../utils/merge-yamls.js";
6
6
  import { envsubst } from "../../utils/common.js";
7
7
  import fs from "fs-extra";
8
8
  import boxen from "boxen";
9
- import { DEFAULT_CONFIG_PATHS, CHART_URL } from "./constants.js";
9
+ import { DEFAULT_CONFIG_PATHS, AUTH_CONFIG_PATHS, CHART_URL, } from "./constants.js";
10
10
  export class RHDHDeployment {
11
11
  k8sClient = new KubernetesClientHelper();
12
12
  rhdhUrl;
13
13
  deploymentConfig;
14
- constructor(deploymentOptions) {
15
- this.deploymentConfig = this._buildDeploymentConfig(deploymentOptions);
14
+ constructor(namespace) {
15
+ this.deploymentConfig = this._buildDeploymentConfig({ namespace });
16
16
  this.rhdhUrl = this._buildBaseUrl();
17
17
  this._log(`RHDH deployment initialized (namespace: ${this.deploymentConfig.namespace})`);
18
+ this._log("RHDH Base URL: " + this.rhdhUrl);
18
19
  console.table(this.deploymentConfig);
19
20
  }
20
21
  async deploy() {
@@ -25,6 +26,7 @@ export class RHDHDeployment {
25
26
  await this._applySecrets();
26
27
  if (this.deploymentConfig.method === "helm") {
27
28
  await this._deployWithHelm(this.deploymentConfig.valueFile);
29
+ await this.scaleDownAndRestart(); // Restart as helm does not monitor config changes
28
30
  }
29
31
  else {
30
32
  await this._applyDynamicPlugins();
@@ -33,34 +35,32 @@ export class RHDHDeployment {
33
35
  await this.waitUntilReady();
34
36
  }
35
37
  async _applyAppConfig() {
38
+ const authConfig = AUTH_CONFIG_PATHS[this.deploymentConfig.auth];
36
39
  const appConfigYaml = await mergeYamlFilesIfExists([
37
40
  DEFAULT_CONFIG_PATHS.appConfig,
41
+ authConfig.appConfig,
38
42
  this.deploymentConfig.appConfig,
39
43
  ]);
40
- console.log(boxen(yaml.dump(appConfigYaml), {
41
- title: "App Config",
42
- padding: 1,
43
- align: "left",
44
- }));
44
+ this._logBoxen("App Config", appConfigYaml);
45
45
  await this.k8sClient.applyConfigMapFromObject("app-config-rhdh", appConfigYaml, this.deploymentConfig.namespace);
46
46
  }
47
47
  async _applySecrets() {
48
+ const authConfig = AUTH_CONFIG_PATHS[this.deploymentConfig.auth];
48
49
  const secretsYaml = await mergeYamlFilesIfExists([
49
50
  DEFAULT_CONFIG_PATHS.secrets,
51
+ authConfig.secrets,
50
52
  this.deploymentConfig.secrets,
51
53
  ]);
52
54
  await this.k8sClient.applySecretFromObject("rhdh-secrets", JSON.parse(envsubst(JSON.stringify(secretsYaml))), this.deploymentConfig.namespace);
53
55
  }
54
56
  async _applyDynamicPlugins() {
57
+ const authConfig = AUTH_CONFIG_PATHS[this.deploymentConfig.auth];
55
58
  const dynamicPluginsYaml = await mergeYamlFilesIfExists([
56
59
  DEFAULT_CONFIG_PATHS.dynamicPlugins,
60
+ authConfig.dynamicPlugins,
57
61
  this.deploymentConfig.dynamicPlugins,
58
- ]);
59
- console.log(boxen(yaml.dump(dynamicPluginsYaml), {
60
- title: "Dynamic Plugins",
61
- padding: 1,
62
- align: "left",
63
- }));
62
+ ], { arrayMergeStrategy: { byKey: "package" } });
63
+ this._logBoxen("Dynamic Plugins", dynamicPluginsYaml);
64
64
  await this.k8sClient.applyConfigMapFromObject("dynamic-plugins", dynamicPluginsYaml, this.deploymentConfig.namespace);
65
65
  }
66
66
  async _deployWithHelm(valueFile) {
@@ -70,24 +70,18 @@ export class RHDHDeployment {
70
70
  DEFAULT_CONFIG_PATHS.helm.valueFile,
71
71
  valueFile,
72
72
  ]));
73
- console.log(boxen(yaml.dump(valueFileObject), {
74
- title: "Value File",
75
- padding: 1,
76
- align: "left",
77
- }));
78
- // Merge dynamic plugins into the values file
73
+ this._logBoxen("Value File", valueFileObject);
74
+ // Merge dynamic plugins into the values file (including auth-specific plugins)
75
+ const authConfig = AUTH_CONFIG_PATHS[this.deploymentConfig.auth];
79
76
  if (!valueFileObject.global) {
80
77
  valueFileObject.global = {};
81
78
  }
82
79
  valueFileObject.global.dynamic = await mergeYamlFilesIfExists([
83
80
  DEFAULT_CONFIG_PATHS.dynamicPlugins,
81
+ authConfig.dynamicPlugins,
84
82
  this.deploymentConfig.dynamicPlugins,
85
- ]);
86
- console.log(boxen(yaml.dump(valueFileObject.global.dynamic), {
87
- title: "Dynamic Plugins",
88
- padding: 1,
89
- align: "left",
90
- }));
83
+ ], { arrayMergeStrategy: { byKey: "package" } });
84
+ this._logBoxen("Dynamic Plugins", valueFileObject.global.dynamic);
91
85
  fs.writeFileSync(`/tmp/${this.deploymentConfig.namespace}-value-file.yaml`, yaml.dump(valueFileObject));
92
86
  await $ `
93
87
  helm upgrade redhat-developer-hub -i "${process.env.CHART_URL || CHART_URL}" --version "${chartVersion}" \
@@ -102,11 +96,7 @@ export class RHDHDeployment {
102
96
  DEFAULT_CONFIG_PATHS.operator.subscription,
103
97
  subscription,
104
98
  ]);
105
- console.log(boxen(yaml.dump(subscriptionObject), {
106
- title: "Subscription",
107
- padding: 1,
108
- align: "left",
109
- }));
99
+ this._logBoxen("Subscription", subscriptionObject);
110
100
  fs.writeFileSync(`/tmp/${this.deploymentConfig.namespace}-subscription.yaml`, yaml.dump(subscriptionObject));
111
101
  await $ `
112
102
  set -e;
@@ -130,6 +120,17 @@ export class RHDHDeployment {
130
120
  this._log(`RHDH deployment restarted successfully in namespace ${this.deploymentConfig.namespace}`);
131
121
  await this.waitUntilReady();
132
122
  }
123
+ /**
124
+ * Performs a clean restart by scaling down to 0 first, waiting for pods to terminate,
125
+ * then scaling back up. This prevents MigrationLocked errors by ensuring no pods
126
+ * hold database locks when new pods start.
127
+ */
128
+ async scaleDownAndRestart() {
129
+ const namespace = this.deploymentConfig.namespace;
130
+ await $ `oc scale deployment -l 'app.kubernetes.io/instance in (redhat-developer-hub,developer-hub)' --replicas=0 -n ${namespace}`;
131
+ await $ `oc wait --for=delete pod -l 'app.kubernetes.io/instance in (redhat-developer-hub,developer-hub),app.kubernetes.io/name!=postgresql' -n ${namespace} --timeout=120s || true`;
132
+ await $ `oc scale deployment -l 'app.kubernetes.io/instance in (redhat-developer-hub,developer-hub)' --replicas=1 -n ${namespace}`;
133
+ }
133
134
  async waitUntilReady(timeout = 300) {
134
135
  this._log(`Waiting for RHDH deployment to be ready in namespace ${this.deploymentConfig.namespace}...`);
135
136
  try {
@@ -137,8 +138,11 @@ export class RHDHDeployment {
137
138
  this._log(`RHDH deployment is ready in namespace ${this.deploymentConfig.namespace}`);
138
139
  }
139
140
  catch (error) {
140
- this._log(`Error waiting for RHDH deployment to be ready in timeout ${timeout}s in namespace ${this.deploymentConfig.namespace}: ${error}`);
141
- throw error;
141
+ console.log("----------------------------------------------------------------");
142
+ console.log("Deployment Failed Logs");
143
+ console.log("----------------------------------------------------------------");
144
+ await $ `oc logs -l 'app.kubernetes.io/instance in (redhat-developer-hub,developer-hub)' -n ${this.deploymentConfig.namespace} --tail=100`;
145
+ throw new Error(`Error waiting for RHDH deployment to be ready in timeout ${timeout}s in namespace ${this.deploymentConfig.namespace}: ${error}`);
142
146
  }
143
147
  }
144
148
  async teardown() {
@@ -174,23 +178,24 @@ export class RHDHDeployment {
174
178
  throw new Error("Installation method (helm/operator) is required");
175
179
  const base = {
176
180
  version,
177
- namespace: input.namespace,
178
- appConfig: input.appConfig ?? `config/app-config-rhdh.yaml`,
179
- secrets: input.secrets ?? `config/rhdh-secrets.yaml`,
180
- dynamicPlugins: input.dynamicPlugins ?? `config/dynamic-plugins.yaml`,
181
+ namespace: input.namespace ?? this.deploymentConfig.namespace,
182
+ auth: input.auth ?? "keycloak",
183
+ appConfig: input.appConfig ?? `tests/config/app-config-rhdh.yaml`,
184
+ secrets: input.secrets ?? `tests/config/rhdh-secrets.yaml`,
185
+ dynamicPlugins: input.dynamicPlugins ?? `tests/config/dynamic-plugins.yaml`,
181
186
  };
182
187
  if (method === "helm") {
183
188
  return {
184
189
  ...base,
185
190
  method,
186
- valueFile: input.valueFile ?? `config/value_file.yaml`,
191
+ valueFile: input.valueFile ?? `tests/config/value_file.yaml`,
187
192
  };
188
193
  }
189
194
  else if (method === "operator") {
190
195
  return {
191
196
  ...base,
192
197
  method,
193
- subscription: input.subscription ?? `config/subscription.yaml`,
198
+ subscription: input.subscription ?? `tests/config/subscription.yaml`,
194
199
  };
195
200
  }
196
201
  else {
@@ -217,4 +222,7 @@ export class RHDHDeployment {
217
222
  _log(...args) {
218
223
  console.log("[RHDHDeployment]", ...args);
219
224
  }
225
+ _logBoxen(title, data) {
226
+ console.log(boxen(yaml.dump(data), { title, padding: 1 }));
227
+ }
220
228
  }
@@ -1,3 +1,2 @@
1
1
  export { RHDHDeployment } from "./deployment.js";
2
- export type { DeploymentOptions, DeploymentConfig, DeploymentConfigBase, DeploymentMethod, HelmDeploymentConfig, OperatorDeploymentConfig, } from "./types.js";
3
2
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/deployment/rhdh/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjD,YAAY,EACV,iBAAiB,EACjB,gBAAgB,EAChB,oBAAoB,EACpB,gBAAgB,EAChB,oBAAoB,EACpB,wBAAwB,GACzB,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/deployment/rhdh/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC"}
@@ -1,7 +1,9 @@
1
1
  export type DeploymentMethod = "helm" | "operator";
2
+ export type AuthProvider = "guest" | "keycloak";
2
3
  export type DeploymentOptions = {
3
4
  version?: string;
4
- namespace: string;
5
+ namespace?: string;
6
+ auth?: AuthProvider;
5
7
  appConfig?: string;
6
8
  secrets?: string;
7
9
  dynamicPlugins?: string;
@@ -20,6 +22,7 @@ export type OperatorDeploymentConfig = {
20
22
  export type DeploymentConfigBase = {
21
23
  version: string;
22
24
  namespace: string;
25
+ auth: AuthProvider;
23
26
  appConfig: string;
24
27
  secrets: string;
25
28
  dynamicPlugins: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/deployment/rhdh/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,UAAU,CAAC;AAEnD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACrC,MAAM,EAAE,UAAU,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG,oBAAoB,GACjD,CAAC,oBAAoB,GAAG,wBAAwB,CAAC,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/deployment/rhdh/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,UAAU,CAAC;AACnD,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,UAAU,CAAC;AAEhD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACrC,MAAM,EAAE,UAAU,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,YAAY,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG,oBAAoB,GACjD,CAAC,oBAAoB,GAAG,wBAAwB,CAAC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"base.config.d.ts","sourceRoot":"","sources":["../../src/eslint/base.config.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAErC;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,eAAe,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,CAoM3E"}
1
+ {"version":3,"file":"base.config.d.ts","sourceRoot":"","sources":["../../src/eslint/base.config.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAErC;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,eAAe,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,CA4M3E"}
@@ -74,6 +74,15 @@ export function createEslintConfig(tsconfigRootDir) {
74
74
  modifiers: ["public"],
75
75
  format: ["camelCase"],
76
76
  },
77
+ // Allow HTTP headers in object literals which require specific formats
78
+ {
79
+ selector: "objectLiteralProperty",
80
+ format: null,
81
+ filter: {
82
+ regex: "^(Accept|Authorization|Content-Type|X-GitHub-Api-Version|X-[A-Za-z-]+)$",
83
+ match: true,
84
+ },
85
+ },
77
86
  ],
78
87
  // Promise handling
79
88
  "@typescript-eslint/no-floating-promises": "error",
@@ -81,8 +90,6 @@ export function createEslintConfig(tsconfigRootDir) {
81
90
  "@typescript-eslint/no-misused-promises": "error",
82
91
  // Allow any type in tests (for mocking, test data)
83
92
  "@typescript-eslint/no-explicit-any": "warn",
84
- // Modern import style
85
- "@typescript-eslint/consistent-type-imports": "error",
86
93
  // Prefer modern syntax
87
94
  "@typescript-eslint/prefer-optional-chain": "error",
88
95
  // Allow unused vars starting with underscore
@@ -1,14 +1,14 @@
1
- import type { PlaywrightTestConfig } from "@playwright/test";
1
+ import { PlaywrightTestConfig } from "@playwright/test";
2
2
  /**
3
3
  * Base Playwright configuration that can be extended by workspace-specific configs.
4
4
  * Provides sensible defaults for RHDH plugin e2e testing.
5
5
  */
6
6
  export declare const baseConfig: PlaywrightTestConfig;
7
7
  /**
8
- * Creates a workspace-specific config by merging with base config.
8
+ * Defines a workspace-specific config by merging with base config.
9
9
  * Only allows overriding the projects configuration.
10
10
  * @param overrides - Object containing projects to override
11
11
  * @returns Merged Playwright configuration
12
12
  */
13
- export declare function createPlaywrightConfig(overrides?: Pick<PlaywrightTestConfig, "projects">): PlaywrightTestConfig;
13
+ export declare function defineConfig(overrides?: Pick<PlaywrightTestConfig, "projects">): PlaywrightTestConfig;
14
14
  //# sourceMappingURL=base-config.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"base-config.d.ts","sourceRoot":"","sources":["../../src/playwright/base-config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAG7D;;;GAGG;AACH,eAAO,MAAM,UAAU,EAAE,oBA4BxB,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,SAAS,GAAE,IAAI,CAAC,oBAAoB,EAAE,UAAU,CAAM,GACrD,oBAAoB,CAKtB"}
1
+ {"version":3,"file":"base-config.d.ts","sourceRoot":"","sources":["../../src/playwright/base-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,oBAAoB,EACrB,MAAM,kBAAkB,CAAC;AAG1B;;;GAGG;AACH,eAAO,MAAM,UAAU,EAAE,oBA4BxB,CAAC;AAEF;;;;;GAKG;AAEH,wBAAgB,YAAY,CAC1B,SAAS,GAAE,IAAI,CAAC,oBAAoB,EAAE,UAAU,CAAM,GACrD,oBAAoB,CAKtB"}
@@ -1,3 +1,4 @@
1
+ import { defineConfig as baseDefineConfig, } from "@playwright/test";
1
2
  import { resolve } from "path";
2
3
  /**
3
4
  * Base Playwright configuration that can be extended by workspace-specific configs.
@@ -33,14 +34,14 @@ export const baseConfig = {
33
34
  globalSetup: resolve(import.meta.dirname, "../playwright/global-setup.js"),
34
35
  };
35
36
  /**
36
- * Creates a workspace-specific config by merging with base config.
37
+ * Defines a workspace-specific config by merging with base config.
37
38
  * Only allows overriding the projects configuration.
38
39
  * @param overrides - Object containing projects to override
39
40
  * @returns Merged Playwright configuration
40
41
  */
41
- export function createPlaywrightConfig(overrides = {}) {
42
- return {
42
+ export function defineConfig(overrides = {}) {
43
+ return baseDefineConfig({
43
44
  ...baseConfig,
44
45
  projects: overrides.projects,
45
- };
46
+ });
46
47
  }
@@ -1,10 +1,13 @@
1
1
  import { RHDHDeployment } from "../../deployment/rhdh/index.js";
2
+ import { LoginHelper, UIhelper } from "../helpers/index.js";
2
3
  type RHDHDeploymentTestFixtures = {
3
4
  rhdh: RHDHDeployment;
5
+ uiHelper: UIhelper;
6
+ loginHelper: LoginHelper;
4
7
  };
5
8
  type RHDHDeploymentWorkerFixtures = {
6
9
  rhdhDeploymentWorker: RHDHDeployment;
7
10
  };
11
+ export * from "@playwright/test";
8
12
  export declare const test: import("playwright/test").TestType<import("playwright/test").PlaywrightTestArgs & import("playwright/test").PlaywrightTestOptions & RHDHDeploymentTestFixtures, import("playwright/test").PlaywrightWorkerArgs & import("playwright/test").PlaywrightWorkerOptions & RHDHDeploymentWorkerFixtures>;
9
- export { expect } from "@playwright/test";
10
13
  //# sourceMappingURL=test.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"test.d.ts","sourceRoot":"","sources":["../../../src/playwright/fixtures/test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAGhE,KAAK,0BAA0B,GAAG;IAChC,IAAI,EAAE,cAAc,CAAC;CACtB,CAAC;AAEF,KAAK,4BAA4B,GAAG;IAClC,oBAAoB,EAAE,cAAc,CAAC;CACtC,CAAC;AAEF,eAAO,MAAM,IAAI,oSAuCf,CAAC;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"test.d.ts","sourceRoot":"","sources":["../../../src/playwright/fixtures/test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAEhE,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAE5D,KAAK,0BAA0B,GAAG;IAChC,IAAI,EAAE,cAAc,CAAC;IACrB,QAAQ,EAAE,QAAQ,CAAC;IACnB,WAAW,EAAE,WAAW,CAAC;CAC1B,CAAC;AAEF,KAAK,4BAA4B,GAAG;IAClC,oBAAoB,EAAE,cAAc,CAAC;CACtC,CAAC;AAEF,cAAc,kBAAkB,CAAC;AAEjC,eAAO,MAAM,IAAI,oSAkDf,CAAC"}
@@ -1,14 +1,15 @@
1
1
  import { RHDHDeployment } from "../../deployment/rhdh/index.js";
2
2
  import { test as base } from "@playwright/test";
3
+ import { LoginHelper, UIhelper } from "../helpers/index.js";
4
+ export * from "@playwright/test";
3
5
  export const test = base.extend({
4
6
  rhdhDeploymentWorker: [
5
7
  // eslint-disable-next-line no-empty-pattern
6
8
  async ({}, use, workerInfo) => {
7
9
  console.log(`Deploying rhdh for plugin ${workerInfo.project.name} in namespace ${workerInfo.project.name}`);
8
- const rhdhDeployment = new RHDHDeployment({
9
- namespace: workerInfo.project.name,
10
- });
10
+ const rhdhDeployment = new RHDHDeployment(workerInfo.project.name);
11
11
  try {
12
+ await rhdhDeployment.configure();
12
13
  await use(rhdhDeployment);
13
14
  }
14
15
  finally {
@@ -26,6 +27,18 @@ export const test = base.extend({
26
27
  },
27
28
  { auto: true, scope: "test" },
28
29
  ],
30
+ uiHelper: [
31
+ async ({ page }, use) => {
32
+ await use(new UIhelper(page));
33
+ },
34
+ { scope: "test" },
35
+ ],
36
+ loginHelper: [
37
+ async ({ page }, use) => {
38
+ await use(new LoginHelper(page));
39
+ },
40
+ { scope: "test" },
41
+ ],
29
42
  baseURL: [
30
43
  async ({ rhdhDeploymentWorker }, use) => {
31
44
  await use(rhdhDeploymentWorker.rhdhUrl);
@@ -33,4 +46,3 @@ export const test = base.extend({
33
46
  { scope: "test" },
34
47
  ],
35
48
  });
36
- export { expect } from "@playwright/test";
@@ -1 +1 @@
1
- {"version":3,"file":"global-setup.d.ts","sourceRoot":"","sources":["../../src/playwright/global-setup.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAgCH,wBAA8B,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAIzD"}
1
+ {"version":3,"file":"global-setup.d.ts","sourceRoot":"","sources":["../../src/playwright/global-setup.ts"],"names":[],"mappings":"AAAA;;;GAGG;AA2EH,wBAA8B,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAMzD"}
@@ -4,12 +4,14 @@
4
4
  */
5
5
  import { KubernetesClientHelper } from "../utils/kubernetes-client.js";
6
6
  import { $ } from "../utils/bash.js";
7
+ import { KeycloakHelper } from "../deployment/keycloak/index.js";
8
+ import { DEFAULT_KEYCLOAK_CONFIG, DEFAULT_RHDH_CLIENT, DEFAULT_USERS, } from "../deployment/keycloak/constants.js";
7
9
  const REQUIRED_BINARIES = ["oc", "kubectl", "helm"];
8
10
  async function checkRequiredBinaries() {
9
11
  const missingBinaries = [];
10
12
  for (const binary of REQUIRED_BINARIES) {
11
13
  try {
12
- await $ `which ${binary}`;
14
+ await $ `command -v ${binary} > /dev/null 2>&1`;
13
15
  }
14
16
  catch {
15
17
  missingBinaries.push(binary);
@@ -25,8 +27,41 @@ async function setClusterRouterBaseEnv() {
25
27
  await k8sClient.getClusterIngressDomain();
26
28
  console.log(`Cluster router base: ${process.env.K8S_CLUSTER_ROUTER_BASE}`);
27
29
  }
30
+ async function deployKeycloak() {
31
+ if (process.env.SKIP_KEYCLOAK_DEPLOYMENT === "true") {
32
+ console.log("Skipping Keycloak deployment");
33
+ return;
34
+ }
35
+ console.log("Set SKIP_KEYCLOAK_DEPLOYMENT=true if test doesn't require keycloak/oidc as auth provider");
36
+ const keycloak = new KeycloakHelper({ namespace: "rhdh-keycloak" });
37
+ // Check if Keycloak is already running
38
+ if (await keycloak.isRunning()) {
39
+ console.log("Keycloak is already running, skipping deployment");
40
+ }
41
+ else {
42
+ await keycloak.deploy();
43
+ await keycloak.configureForRHDH();
44
+ }
45
+ // Set environment variables for RHDH integration
46
+ const realm = DEFAULT_KEYCLOAK_CONFIG.realm;
47
+ process.env.KEYCLOAK_CLIENT_SECRET = DEFAULT_RHDH_CLIENT.clientSecret;
48
+ process.env.KEYCLOAK_CLIENT_ID = DEFAULT_RHDH_CLIENT.clientId;
49
+ process.env.KEYCLOAK_REALM = realm;
50
+ process.env.KEYCLOAK_LOGIN_REALM = realm;
51
+ process.env.KEYCLOAK_METADATA_URL = `${keycloak.keycloakUrl}/realms/${realm}`;
52
+ process.env.KEYCLOAK_BASE_URL = keycloak.keycloakUrl;
53
+ console.table({
54
+ keycloakURL: keycloak.keycloakUrl,
55
+ adminUser: keycloak.deploymentConfig.adminUser,
56
+ adminPassword: keycloak.deploymentConfig.adminPassword,
57
+ testUsername: DEFAULT_USERS[0].username,
58
+ testPassword: DEFAULT_USERS[0].password,
59
+ });
60
+ }
28
61
  export default async function globalSetup() {
29
62
  console.log("Running global setup...");
30
63
  await checkRequiredBinaries();
31
64
  await setClusterRouterBaseEnv();
65
+ await deployKeycloak();
66
+ console.log("Global setup completed successfully");
32
67
  }
@@ -0,0 +1,13 @@
1
+ import type { Page } from "@playwright/test";
2
+ export interface AccessibilityTestOptions {
3
+ /** Custom name for the attached results file. Defaults to "accessibility-scan-results.violations.json" */
4
+ attachName?: string;
5
+ /** Whether to assert that there are no violations. Defaults to true */
6
+ assertNoViolations?: boolean;
7
+ /** WCAG tags to test against. Defaults to ["wcag2a", "wcag2aa", "wcag21a", "wcag21aa"] */
8
+ wcagTags?: string[];
9
+ /** Rules to disable during the scan. Defaults to ["color-contrast"] */
10
+ disabledRules?: string[];
11
+ }
12
+ export declare function runAccessibilityTests(page: Page, options?: AccessibilityTestOptions): Promise<import("axe-core").AxeResults>;
13
+ //# sourceMappingURL=accessibility.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accessibility.d.ts","sourceRoot":"","sources":["../../../src/playwright/helpers/accessibility.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAG7C,MAAM,WAAW,wBAAwB;IACvC,0GAA0G;IAC1G,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uEAAuE;IACvE,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,0FAA0F;IAC1F,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,uEAAuE;IACvE,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B;AASD,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,IAAI,EACV,OAAO,GAAE,wBAA6B,0CAuBvC"}
@@ -0,0 +1,24 @@
1
+ import AxeBuilder from "@axe-core/playwright";
2
+ import { expect, test } from "@playwright/test";
3
+ const DEFAULT_OPTIONS = {
4
+ attachName: "accessibility-scan-results.violations.json",
5
+ assertNoViolations: true,
6
+ wcagTags: ["wcag2a", "wcag2aa", "wcag21a", "wcag21aa"],
7
+ disabledRules: ["color-contrast"],
8
+ };
9
+ export async function runAccessibilityTests(page, options = {}) {
10
+ const config = { ...DEFAULT_OPTIONS, ...options };
11
+ const testInfo = test.info();
12
+ const accessibilityScanResults = await new AxeBuilder({ page })
13
+ .withTags(config.wcagTags)
14
+ .disableRules(config.disabledRules)
15
+ .analyze();
16
+ await testInfo.attach(config.attachName, {
17
+ body: JSON.stringify(accessibilityScanResults.violations, null, 2),
18
+ contentType: "application/json",
19
+ });
20
+ if (config.assertNoViolations) {
21
+ expect(accessibilityScanResults.violations, `Found ${accessibilityScanResults.violations.length} accessibility violation(s)`).toHaveLength(0);
22
+ }
23
+ return accessibilityScanResults;
24
+ }
@@ -0,0 +1,11 @@
1
+ export declare const GITHUB_API_ENDPOINTS: {
2
+ pull: (owner: string, repo: string, state: "open" | "closed" | "all") => string;
3
+ issues: (state: string) => string;
4
+ workflowRuns: string;
5
+ deleteRepo: (owner: string, repo: string) => string;
6
+ mergePR: (owner: string, repoName: string, pullNumber: number) => string;
7
+ createRepo: (owner: string) => string;
8
+ pullFiles: (owner: string, repoName: string, pr: number) => string;
9
+ contents: (owner: string, repoName: string) => string;
10
+ };
11
+ //# sourceMappingURL=api-endpoints.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-endpoints.d.ts","sourceRoot":"","sources":["../../../src/playwright/helpers/api-endpoints.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,oBAAoB;kBACjB,MAAM,QAAQ,MAAM,SAAS,MAAM,GAAG,QAAQ,GAAG,KAAK;oBAGpD,MAAM;;wBAVG,MAAM,QAAQ,MAAM;qBAiB5B,MAAM,YAAY,MAAM,cAAc,MAAM;wBAGzC,MAAM;uBAEP,MAAM,YAAY,MAAM,MAAM,MAAM;sBAGrC,MAAM,YAAY,MAAM;CAE3C,CAAC"}
@@ -0,0 +1,15 @@
1
+ const baseApiUrl = "https://api.github.com";
2
+ const perPage = 100;
3
+ const getRepoUrl = (owner, repo) => `${baseApiUrl}/repos/${owner}/${repo}`;
4
+ const getOrgUrl = (owner) => `${baseApiUrl}/orgs/${owner}`;
5
+ const backstageShowcaseAPI = getRepoUrl("janus-idp", "backstage-showcase");
6
+ export const GITHUB_API_ENDPOINTS = {
7
+ pull: (owner, repo, state) => `${getRepoUrl(owner, repo)}/pulls?per_page=${perPage}&state=${state}`,
8
+ issues: (state) => `${backstageShowcaseAPI}/issues?per_page=${perPage}&sort=updated&state=${state}`,
9
+ workflowRuns: `${backstageShowcaseAPI}/actions/runs?per_page=${perPage}`,
10
+ deleteRepo: getRepoUrl,
11
+ mergePR: (owner, repoName, pullNumber) => `${getRepoUrl(owner, repoName)}/pulls/${pullNumber}/merge`,
12
+ createRepo: (owner) => `${getOrgUrl(owner)}/repos`,
13
+ pullFiles: (owner, repoName, pr) => `${getRepoUrl(owner, repoName)}/pulls/${pr}/files`,
14
+ contents: (owner, repoName) => `${getRepoUrl(owner, repoName)}/contents`,
15
+ };
@@ -0,0 +1,77 @@
1
+ import type { APIResponse } from "@playwright/test";
2
+ export declare class APIHelper {
3
+ private static githubAPIVersion;
4
+ private staticToken;
5
+ private baseUrl;
6
+ useStaticToken: boolean;
7
+ static githubRequest(method: string, url: string, body?: string | object): Promise<APIResponse>;
8
+ static getGithubPaginatedRequest(url: string, pageNo?: number, response?: unknown[]): Promise<unknown[]>;
9
+ static createGitHubRepo(owner: string, repoName: string): Promise<void>;
10
+ static createGitHubRepoWithFile(owner: string, repoName: string, filename: string, fileContent: string): Promise<void>;
11
+ static createFileInRepo(owner: string, repoName: string, filePath: string, content: string, commitMessage: string, branch?: string): Promise<void>;
12
+ static initCommit(owner: string, repo: string, branch?: string): Promise<void>;
13
+ static deleteGitHubRepo(owner: string, repoName: string): Promise<void>;
14
+ static mergeGitHubPR(owner: string, repoName: string, pullNumber: number): Promise<void>;
15
+ static getGitHubPRs(owner: string, repoName: string, state: "open" | "closed" | "all", paginated?: boolean): Promise<any>;
16
+ static getfileContentFromPR(owner: string, repoName: string, pr: number, filename: string): Promise<string>;
17
+ getGuestToken(): Promise<string>;
18
+ getGuestAuthHeader(): Promise<{
19
+ [key: string]: string;
20
+ }>;
21
+ setStaticToken(token: string): Promise<void>;
22
+ setBaseUrl(url: string): Promise<void>;
23
+ static apiRequestWithStaticToken(method: string, url: string, staticToken: string, body?: string | object): Promise<APIResponse>;
24
+ getAllCatalogUsersFromAPI(): Promise<any>;
25
+ getAllCatalogLocationsFromAPI(): Promise<any>;
26
+ getAllCatalogGroupsFromAPI(): Promise<any>;
27
+ getGroupEntityFromAPI(group: string): Promise<any>;
28
+ getCatalogUserFromAPI(user: string): Promise<any>;
29
+ deleteUserEntityFromAPI(user: string): Promise<(() => string) | undefined>;
30
+ getCatalogGroupFromAPI(group: string): Promise<any>;
31
+ deleteGroupEntityFromAPI(group: string): Promise<() => string>;
32
+ scheduleEntityRefreshFromAPI(entity: string, kind: string, token: string): Promise<number>;
33
+ /**
34
+ * Fetches the UID of an entity by its name from the Backstage catalog.
35
+ *
36
+ * @param name - The name of the entity (e.g., 'hello-world-2').
37
+ * @returns The UID string if found, otherwise undefined.
38
+ */
39
+ static getEntityUidByName(name: string): Promise<string | undefined>;
40
+ /**
41
+ * Deletes a location from the Backstage catalog by its UID.
42
+ *
43
+ * @param uid - The UID of the location to delete.
44
+ * @returns The status code of the delete operation.
45
+ */
46
+ static deleteLocationByUid(uid: string): Promise<number>;
47
+ /**
48
+ * Fetches the UID of a Template entity by its name and namespace from the Backstage catalog.
49
+ *
50
+ * @param name - The name of the template entity (e.g., 'hello-world-2').
51
+ * @param namespace - The namespace of the template entity (default: 'default').
52
+ * @returns The UID string if found, otherwise undefined.
53
+ */
54
+ static getTemplateEntityUidByName(name: string, namespace?: string): Promise<string | undefined>;
55
+ /**
56
+ * Deletes an entity location from the Backstage catalog by its ID.
57
+ *
58
+ * @param id - The ID of the entity to delete.
59
+ * @returns The status code of the delete operation.
60
+ */
61
+ static deleteEntityLocationById(id: string): Promise<number>;
62
+ /**
63
+ * Registers a new location in the Backstage catalog.
64
+ *
65
+ * @param target - The target URL of the location to register.
66
+ * @returns The status code of the registration operation.
67
+ */
68
+ static registerLocation(target: string): Promise<number>;
69
+ /**
70
+ * Fetches the ID of a location from the Backstage catalog by its target URL.
71
+ *
72
+ * @param target - The target URL of the location to search for.
73
+ * @returns The ID string if found, otherwise undefined.
74
+ */
75
+ static getLocationIdByTarget(target: string): Promise<string | undefined>;
76
+ }
77
+ //# sourceMappingURL=api-helper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-helper.d.ts","sourceRoot":"","sources":["../../../src/playwright/helpers/api-helper.ts"],"names":[],"mappings":"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"}