rhdh-e2e-test-utils 1.1.5 → 1.1.7

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/README.md CHANGED
@@ -1,1170 +1,6 @@
1
1
  # rhdh-e2e-test-utils
2
2
 
3
- A comprehensive test utility package for Red Hat Developer Hub (RHDH) end-to-end testing. This package provides a unified framework for deploying RHDH instances, running Playwright tests, and managing Kubernetes resources in OpenShift environments.
3
+ Documentation is available online:
4
4
 
5
- ## Table of Contents
6
-
7
- - [Overview](#overview)
8
- - [Features](#features)
9
- - [Installation](#installation)
10
- - [Requirements](#requirements)
11
- - [Package Exports](#package-exports)
12
- - [Quick Start](#quick-start)
13
- - [Detailed Usage](#detailed-usage)
14
- - [Playwright Test Fixtures](#playwright-test-fixtures)
15
- - [Playwright Configuration](#playwright-configuration)
16
- - [Global Setup](#global-setup)
17
- - [RHDH Deployment](#rhdh-deployment)
18
- - [Keycloak Deployment](#keycloak-deployment)
19
- - [Utilities](#utilities)
20
- - [Helpers](#helpers)
21
- - [Page Objects](#page-objects)
22
- - [ESLint Configuration](#eslint-configuration)
23
- - [TypeScript Configuration](#typescript-configuration)
24
- - [Configuration Files](#configuration-files)
25
- - [Environment Variables](#environment-variables)
26
- - [Examples](#examples)
27
- - [Development](#development)
28
- - [Testing Local Changes in Consumer Projects](#testing-local-changes-in-consumer-projects)
29
-
30
- ## Overview
31
-
32
- `rhdh-e2e-test-utils` simplifies end-to-end testing for RHDH plugins by providing:
33
-
34
- - **Automated RHDH Deployment**: Deploy RHDH instances via Helm or the RHDH Operator
35
- - **Keycloak Integration**: Deploy and configure Keycloak for OIDC authentication testing
36
- - **Modular Auth Configuration**: Switch between guest and Keycloak authentication with a single option
37
- - **Playwright Integration**: Custom test fixtures that manage deployment lifecycle
38
- - **Kubernetes Utilities**: Helper functions for managing namespaces, ConfigMaps, Secrets, and Routes
39
- - **Configuration Merging**: YAML merging with environment variable substitution
40
- - **Standardized ESLint Rules**: Pre-configured linting for Playwright tests
41
-
42
- ## Features
43
-
44
- - Deploy RHDH using Helm charts or the RHDH Operator
45
- - Deploy Keycloak for authentication testing with automatic realm, client, and user configuration
46
- - Modular authentication configuration (guest, Keycloak)
47
- - Automatic namespace creation and cleanup
48
- - Dynamic plugin configuration
49
- - Helpers for UI, API and common Utils
50
- - Kubernetes client helper for OpenShift resources
51
- - Pre-configured Playwright settings optimized for RHDH testing
52
- - ESLint configuration with Playwright and TypeScript best practices
53
-
54
- ## Installation
55
-
56
- ```bash
57
- npm install rhdh-e2e-test-utils
58
- ```
59
-
60
- Or directly from GitHub:
61
-
62
- ```bash
63
- npm install github:redhat-developer/rhdh-e2e-test-utils#main
64
- ```
65
-
66
- ## Requirements
67
-
68
- ### System Requirements
69
-
70
- - **Node.js**: >= 22
71
- - **Yarn**: >= 3 (this project uses Yarn 3 with Corepack)
72
-
73
- ### OpenShift Cluster
74
-
75
- You must be logged into an OpenShift cluster with sufficient permissions to:
76
-
77
- - Create and delete namespaces
78
- - Create ConfigMaps and Secrets
79
- - Install Helm charts or use the RHDH Operator
80
- - Read cluster ingress configuration
81
-
82
- ## Package Exports
83
-
84
- The package provides multiple entry points for different use cases:
85
-
86
- | Export Path | Description |
87
- |-------------|-------------|
88
- | `rhdh-e2e-test-utils/test` | Playwright test fixtures with RHDH deployment |
89
- | `rhdh-e2e-test-utils/playwright-config` | Base Playwright configuration |
90
- | `rhdh-e2e-test-utils/rhdh` | RHDH deployment class and types |
91
- | `rhdh-e2e-test-utils/keycloak` | Keycloak deployment helper for authentication testing |
92
- | `rhdh-e2e-test-utils/utils` | Utility functions (bash, YAML, Kubernetes) |
93
- | `rhdh-e2e-test-utils/helpers` | UI, API, and login helper classes |
94
- | `rhdh-e2e-test-utils/pages` | Page object classes for common RHDH pages |
95
- | `rhdh-e2e-test-utils/eslint` | ESLint configuration factory |
96
- | `rhdh-e2e-test-utils/tsconfig` | Base TypeScript configuration |
97
-
98
- ## Quick Start
99
-
100
- ### 1. Set Up Your E2E Test Project
101
-
102
- ```bash
103
- mkdir e2e-tests && cd e2e-tests
104
- yarn init -y
105
- yarn add @playwright/test rhdh-e2e-test-utils
106
- ```
107
-
108
- ### 2. Create Playwright Configuration
109
-
110
- ```typescript
111
- // playwright.config.ts
112
- import { defineConfig } from "rhdh-e2e-test-utils/playwright-config";
113
-
114
- export default defineConfig({
115
- projects: [
116
- {
117
- name: "my-plugin",
118
- },
119
- ],
120
- });
121
- ```
122
-
123
- ### 3. Create Your Test
124
-
125
- ```typescript
126
- // tests/my-plugin.spec.ts
127
- import { test, expect } from "rhdh-e2e-test-utils/test";
128
-
129
- test.beforeAll(async ({ rhdh }) => {
130
- await rhdh.deploy();
131
- });
132
-
133
- test("my plugin test", async ({ page }) => {
134
- await page.goto("/");
135
- await expect(page).toHaveTitle(/Red Hat Developer Hub/);
136
- });
137
- ```
138
-
139
- ### 4. Create Configuration Files
140
-
141
- Create a `tests/config/` directory with your RHDH configuration:
142
-
143
- ```
144
- tests/config/
145
- ├── app-config-rhdh.yaml # App configuration
146
- ├── dynamic-plugins.yaml # Dynamic plugins configuration
147
- └── rhdh-secrets.yaml # Secrets (with env var placeholders)
148
- ```
149
-
150
- ### 5. Set Environment Variables
151
-
152
- ```bash
153
- export RHDH_VERSION="1.5" # RHDH version
154
- export INSTALLATION_METHOD="helm" # "helm" or "operator"
155
- ```
156
-
157
- ### 6. Run Tests
158
-
159
- ```bash
160
- yarn playwright test
161
- ```
162
-
163
- ## Detailed Usage
164
-
165
- ### Playwright Test Fixtures
166
-
167
- The package extends Playwright's test with RHDH-specific fixtures:
168
-
169
- ```typescript
170
- import { test, expect } from "rhdh-e2e-test-utils/test";
171
- ```
172
-
173
- #### Available Fixtures
174
-
175
- | Fixture | Scope | Description |
176
- |---------|-------|-------------|
177
- | `rhdh` | worker | Shared RHDHDeployment across all tests in a worker |
178
- | `uiHelper` | test | UIhelper instance for common UI interactions |
179
- | `loginHelper` | test | LoginHelper instance for authentication flows |
180
- | `baseURL` | test | Automatically set to the RHDH instance URL |
181
-
182
- #### Fixture Behavior
183
-
184
- - **Automatic Namespace**: The namespace is derived from the Playwright project name
185
- - **Auto-cleanup**: In CI environments, namespaces are automatically deleted after tests
186
- - **Shared Deployment**: All tests in a worker share the same RHDH deployment
187
-
188
- ```typescript
189
- import { test, expect } from "rhdh-e2e-test-utils/test";
190
-
191
- test.beforeAll(async ({ rhdh }) => {
192
- // Configure RHDH (creates namespace, and optional DeploymentOptions)
193
- await rhdh.configure();
194
-
195
- // Perform any pre-deployment setup
196
- // ...
197
-
198
- // Deploy RHDH
199
- await rhdh.deploy();
200
- });
201
-
202
- test("example test", async ({ page, rhdh, uiHelper, loginHelper }) => {
203
- // page.goto("/") will use rhdh.rhdhUrl as base
204
- await page.goto("/");
205
-
206
- // Login as guest user
207
- await loginHelper.loginAsGuest();
208
-
209
- // Use UI helper for common interactions
210
- await uiHelper.verifyHeading("Welcome");
211
- await uiHelper.clickButton("Get Started");
212
-
213
- // Access deployment info
214
- console.log(`Namespace: ${rhdh.deploymentConfig.namespace}`);
215
- console.log(`URL: ${rhdh.rhdhUrl}`);
216
-
217
- // Perform any deployment/config update
218
- // ...
219
- await rhdh.rolloutRestart();
220
- // ...
221
- });
222
- ```
223
-
224
- ### Playwright Configuration
225
-
226
- Use `defineConfig` for sensible defaults:
227
-
228
- ```typescript
229
- // playwright.config.ts
230
- import { defineConfig } from "rhdh-e2e-test-utils/playwright-config";
231
-
232
- export default defineConfig({
233
- projects: [
234
- {
235
- name: "tech-radar", // Also used as namespace
236
- },
237
- {
238
- name: "catalog",
239
- },
240
- ],
241
- });
242
- ```
243
-
244
- #### Base Configuration Defaults
245
-
246
- | Setting | Value |
247
- |---------|-------|
248
- | `testDir` | `./tests` |
249
- | `timeout` | 90,000ms |
250
- | `retries` | 2 in CI, 0 locally |
251
- | `workers` | 50% of CPUs |
252
- | `viewport` | 1920x1080 |
253
- | `video` | Always on |
254
- | `trace` | Retain on failure |
255
- | `screenshot` | Only on failure |
256
-
257
- #### Global Setup
258
-
259
- The package includes a global setup function that runs once before all tests. It performs the following:
260
-
261
- 1. **Binary Check**: Verifies that required binaries (`oc`, `kubectl`, `helm`) are installed
262
- 2. **Cluster Router Base**: Fetches the OpenShift ingress domain and sets `K8S_CLUSTER_ROUTER_BASE`
263
- 3. **Keycloak Deployment**: Automatically deploys and configures Keycloak for OIDC authentication
264
-
265
- ```typescript
266
- // playwright.config.ts
267
- import { defineConfig } from "rhdh-e2e-test-utils/playwright-config";
268
-
269
- export default defineConfig({
270
- // Global setup is automatically included
271
- projects: [{ name: "my-plugin" }],
272
- });
273
- ```
274
-
275
- **Keycloak Auto-Deployment Behavior:**
276
-
277
- - Keycloak is deployed to the `rhdh-keycloak` namespace
278
- - If Keycloak is already running, deployment is skipped
279
- - All Keycloak environment variables are automatically set after deployment
280
- - The following test credentials are created:
281
- - Username: `test1`, Password: `test1@123`
282
- - Username: `test2`, Password: `test2@123`
283
-
284
- **Skip Keycloak Deployment:**
285
-
286
- If your tests don't require Keycloak/OIDC authentication, set the environment variable:
287
-
288
- ```bash
289
- SKIP_KEYCLOAK_DEPLOYMENT=true yarn playwright test
290
- ```
291
-
292
- ### RHDH Deployment
293
-
294
- #### RHDHDeployment Class
295
-
296
- The core class for managing RHDH deployments:
297
-
298
- ```typescript
299
- import { RHDHDeployment } from "rhdh-e2e-test-utils/rhdh";
300
-
301
- // Create deployment with namespace (required)
302
- const deployment = new RHDHDeployment("my-test-namespace");
303
-
304
- // Configure with options (call before deploy)
305
- await deployment.configure({
306
- version: "1.5", // Optional, uses RHDH_VERSION env
307
- method: "helm", // Optional, uses INSTALLATION_METHOD env
308
- auth: "keycloak", // Optional, defaults to "keycloak"
309
- appConfig: "config/app-config-rhdh.yaml", // Optional
310
- secrets: "config/rhdh-secrets.yaml", // Optional
311
- dynamicPlugins: "config/dynamic-plugins.yaml", // Optional
312
- valueFile: "config/value_file.yaml", // Optional & Helm only
313
- subscription: "config/subscription.yaml", // Optional & Operator only
314
- });
315
-
316
- // Deploy RHDH
317
- await deployment.deploy();
318
- ```
319
-
320
- #### Deployment Options
321
-
322
- ```typescript
323
- type DeploymentOptions = {
324
- namespace?: string; // Kubernetes namespace (set via constructor)
325
- version?: string; // RHDH version (e.g., "1.5", "1.5.1-CI")
326
- method?: "helm" | "operator"; // Installation method
327
- auth?: "guest" | "keycloak"; // Authentication provider (default: "keycloak")
328
- appConfig?: string; // Path to app-config YAML
329
- secrets?: string; // Path to secrets YAML
330
- dynamicPlugins?: string; // Path to dynamic-plugins YAML
331
- valueFile?: string; // Helm values file (helm only)
332
- subscription?: string; // Backstage CR file (operator only)
333
- };
334
- ```
335
-
336
- #### Authentication Providers
337
-
338
- The package supports modular authentication configuration. Set the `auth` option to automatically include the appropriate auth-specific configurations:
339
-
340
- | Provider | Description |
341
- |----------|-------------|
342
- | `guest` | Guest authentication for development/testing |
343
- | `keycloak` | OIDC authentication via Keycloak (default) |
344
-
345
- Auth-specific configurations are automatically merged with your project configurations. For Keycloak authentication, see [Keycloak Deployment](#keycloak-deployment).
346
-
347
- #### Deployment Methods
348
-
349
- ##### Helm Deployment
350
-
351
- ```typescript
352
- const rhdh = new RHDHDeployment({
353
- namespace: "my-plugin-tests",
354
- method: "helm",
355
- valueFile: "config/value_file.yaml",
356
- });
357
-
358
- await rhdh.deploy();
359
- ```
360
-
361
- ##### Operator Deployment
362
-
363
- ```typescript
364
- const rhdh = new RHDHDeployment({
365
- namespace: "my-plugin-tests",
366
- method: "operator",
367
- subscription: "config/subscription.yaml",
368
- });
369
-
370
- await rhdh.deploy();
371
- ```
372
-
373
- #### RHDHDeployment API
374
-
375
- | Method | Description |
376
- |--------|-------------|
377
- | `configure(options?)` | Create namespace and prepare for deployment |
378
- | `deploy()` | Full deployment (configs, secrets, plugins, RHDH) |
379
- | `waitUntilReady(timeout?)` | Wait for deployment to be ready |
380
- | `rolloutRestart()` | Restart the RHDH deployment |
381
- | `teardown()` | Delete the namespace and all resources |
382
-
383
- #### Properties
384
-
385
- | Property | Type | Description |
386
- |----------|------|-------------|
387
- | `rhdhUrl` | `string` | The RHDH instance URL |
388
- | `deploymentConfig` | `DeploymentConfig` | Current deployment configuration |
389
- | `k8sClient` | `KubernetesClientHelper` | Kubernetes client instance |
390
-
391
- ### Keycloak Deployment
392
-
393
- The package provides a `KeycloakHelper` class for deploying and configuring Keycloak in OpenShift, enabling OIDC authentication testing with RHDH.
394
-
395
- #### KeycloakHelper Class
396
-
397
- ```typescript
398
- import { KeycloakHelper } from "rhdh-e2e-test-utils/keycloak";
399
-
400
- const keycloak = new KeycloakHelper({
401
- namespace: "rhdh-keycloak", // Optional, defaults to "rhdh-keycloak"
402
- releaseName: "keycloak", // Optional, defaults to "keycloak"
403
- adminUser: "admin", // Optional, defaults to "admin"
404
- adminPassword: "admin123", // Optional, defaults to "admin123"
405
- });
406
-
407
- // Deploy Keycloak using Bitnami Helm chart
408
- await keycloak.deploy();
409
-
410
- // Configure realm, client, groups, and users for RHDH
411
- await keycloak.configureForRHDH();
412
- ```
413
-
414
- #### Deployment Options
415
-
416
- ```typescript
417
- type KeycloakDeploymentOptions = {
418
- namespace?: string; // Kubernetes namespace (default: "rhdh-keycloak")
419
- releaseName?: string; // Helm release name (default: "keycloak")
420
- valuesFile?: string; // Custom Helm values file
421
- adminUser?: string; // Admin username (default: "admin")
422
- adminPassword?: string; // Admin password (default: "admin123")
423
- };
424
- ```
425
-
426
- #### KeycloakHelper API
427
-
428
- | Method | Description |
429
- |--------|-------------|
430
- | `deploy()` | Deploy Keycloak using Helm and wait for it to be ready |
431
- | `configureForRHDH(options?)` | Configure realm, client, groups, and users for RHDH |
432
- | `isRunning()` | Check if Keycloak is accessible |
433
- | `connect(config)` | Connect to an existing Keycloak instance |
434
- | `createRealm(config)` | Create a new realm |
435
- | `createClient(realm, config)` | Create a client in a realm |
436
- | `createGroup(realm, config)` | Create a group in a realm |
437
- | `createUser(realm, config)` | Create a user with optional group membership |
438
- | `getUsers(realm)` | Get all users in a realm |
439
- | `getGroups(realm)` | Get all groups in a realm |
440
- | `deleteUser(realm, username)` | Delete a user |
441
- | `deleteGroup(realm, groupName)` | Delete a group |
442
- | `deleteRealm(realm)` | Delete a realm |
443
- | `teardown()` | Delete the Keycloak namespace |
444
- | `waitUntilReady(timeout?)` | Wait for Keycloak StatefulSet to be ready |
445
-
446
- #### Properties
447
-
448
- | Property | Type | Description |
449
- |----------|------|-------------|
450
- | `keycloakUrl` | `string` | The Keycloak instance URL |
451
- | `realm` | `string` | Configured realm name |
452
- | `clientId` | `string` | Configured client ID |
453
- | `clientSecret` | `string` | Configured client secret |
454
- | `deploymentConfig` | `KeycloakDeploymentConfig` | Current deployment configuration |
455
- | `k8sClient` | `KubernetesClientHelper` | Kubernetes client instance |
456
-
457
- #### Default Configuration
458
-
459
- When using `configureForRHDH()`, the following defaults are applied:
460
-
461
- **Default Realm**: `rhdh`
462
-
463
- **Default Client** (`rhdh-client`):
464
- - Client secret: `rhdh-client-secret`
465
- - Standard flow, implicit flow, direct access grants enabled
466
- - Service accounts enabled with realm-management roles
467
-
468
- **Default Groups**:
469
- - `developers`
470
- - `admins`
471
- - `viewers`
472
-
473
- **Default Users**:
474
- | Username | Password | Groups |
475
- |----------|----------|--------|
476
- | `test1` | `test1@123` | developers |
477
- | `test2` | `test2@123` | developers |
478
-
479
- #### Example: Full RHDH + Keycloak Setup
480
-
481
- ```typescript
482
- import { test } from "rhdh-e2e-test-utils/test";
483
- import { KeycloakHelper } from "rhdh-e2e-test-utils/keycloak";
484
-
485
- let keycloak: KeycloakHelper;
486
-
487
- test.beforeAll(async ({ rhdh }) => {
488
- // Deploy Keycloak
489
- keycloak = new KeycloakHelper({ namespace: "rhdh-keycloak" });
490
- await keycloak.deploy();
491
- await keycloak.configureForRHDH();
492
-
493
- // Set environment variables for RHDH
494
- process.env.KEYCLOAK_BASE_URL = keycloak.keycloakUrl;
495
- process.env.KEYCLOAK_REALM = keycloak.realm;
496
- process.env.KEYCLOAK_CLIENT_ID = keycloak.clientId;
497
- process.env.KEYCLOAK_CLIENT_SECRET = keycloak.clientSecret;
498
- process.env.KEYCLOAK_METADATA_URL = `${keycloak.keycloakUrl}/realms/${keycloak.realm}/.well-known/openid-configuration`;
499
- process.env.KEYCLOAK_LOGIN_REALM = keycloak.realm;
500
-
501
- // Deploy RHDH with Keycloak authentication
502
- await rhdh.configure({ auth: "keycloak" });
503
- await rhdh.deploy();
504
- });
505
-
506
- test("login with Keycloak user", async ({ page, loginHelper }) => {
507
- await page.goto("/");
508
- await loginHelper.loginAsKeycloakUser("test1", "test1@123");
509
- // ... test assertions
510
- });
511
-
512
- test.afterAll(async () => {
513
- await keycloak.teardown();
514
- });
515
- ```
516
-
517
- #### Connect to Existing Keycloak
518
-
519
- ```typescript
520
- import { KeycloakHelper } from "rhdh-e2e-test-utils/keycloak";
521
-
522
- const keycloak = new KeycloakHelper();
523
-
524
- // Connect with admin credentials
525
- await keycloak.connect({
526
- baseUrl: "https://keycloak.example.com",
527
- username: "admin",
528
- password: "admin-password",
529
- });
530
-
531
- // Or connect with client credentials
532
- await keycloak.connect({
533
- baseUrl: "https://keycloak.example.com",
534
- realm: "my-realm",
535
- clientId: "admin-client",
536
- clientSecret: "client-secret",
537
- });
538
-
539
- // Now you can manage users, groups, etc.
540
- await keycloak.createUser("my-realm", {
541
- username: "newuser",
542
- password: "password123",
543
- groups: ["developers"],
544
- });
545
- ```
546
-
547
- ### Utilities
548
-
549
- #### Bash Command Execution
550
-
551
- Execute shell commands using `zx`:
552
-
553
- ```typescript
554
- import { $ } from "rhdh-e2e-test-utils/utils";
555
-
556
- // Execute commands
557
- await $`oc get pods -n my-namespace`;
558
-
559
- // With variables
560
- const namespace = "my-namespace";
561
- await $`oc get pods -n ${namespace}`;
562
-
563
- // Capture output
564
- const result = await $`oc get pods -o json`;
565
- console.log(result.stdout);
566
- ```
567
-
568
- #### Kubernetes Client Helper
569
-
570
- ```typescript
571
- import { KubernetesClientHelper } from "rhdh-e2e-test-utils/utils";
572
-
573
- const k8sClient = new KubernetesClientHelper();
574
-
575
- // Create namespace
576
- await k8sClient.createNamespaceIfNotExists("my-namespace");
577
-
578
- // Apply ConfigMap from object
579
- await k8sClient.applyConfigMapFromObject(
580
- "my-config",
581
- { key: "value" },
582
- "my-namespace"
583
- );
584
-
585
- // Apply Secret from object
586
- await k8sClient.applySecretFromObject(
587
- "my-secret",
588
- { stringData: { TOKEN: "secret-value" } },
589
- "my-namespace"
590
- );
591
-
592
- // Get route URL
593
- const url = await k8sClient.getRouteLocation("my-namespace", "my-route");
594
-
595
- // Get cluster ingress domain
596
- const domain = await k8sClient.getClusterIngressDomain();
597
-
598
- // Delete namespace
599
- await k8sClient.deleteNamespace("my-namespace");
600
- ```
601
-
602
- #### Environment Variable Substitution
603
-
604
- ```typescript
605
- import { envsubst } from "rhdh-e2e-test-utils/utils";
606
-
607
- // Simple substitution
608
- const result = envsubst("Hello $USER");
609
-
610
- // With default values
611
- const result = envsubst("Port: ${PORT:-8080}");
612
-
613
- // With braces
614
- const result = envsubst("API: ${API_URL}");
615
- ```
616
-
617
- ### Helpers
618
-
619
- The package provides helper classes for common testing operations.
620
-
621
- #### UIhelper
622
-
623
- A utility class for common UI interactions with Material-UI components:
624
-
625
- ```typescript
626
- import { UIhelper } from "rhdh-e2e-test-utils/helpers";
627
-
628
- const uiHelper = new UIhelper(page);
629
-
630
- // Wait for page to fully load
631
- await uiHelper.waitForLoad();
632
-
633
- // Verify headings and text
634
- await uiHelper.verifyHeading("Welcome to RHDH");
635
- await uiHelper.verifyText("Some content");
636
-
637
- // Button interactions
638
- await uiHelper.clickButton("Submit");
639
- await uiHelper.clickButtonByLabel("Close");
640
-
641
- // Navigation
642
- await uiHelper.openSidebar("Catalog");
643
- await uiHelper.clickTab("Overview");
644
-
645
- // Table operations
646
- await uiHelper.verifyRowsInTable(["row1", "row2"]);
647
- await uiHelper.verifyCellsInTable(["cell1", "cell2"]);
648
-
649
- // MUI component interactions
650
- await uiHelper.selectMuiBox("Kind", "Component");
651
- await uiHelper.fillTextInputByLabel("Name", "my-component");
652
- ```
653
-
654
- #### LoginHelper
655
-
656
- Handles authentication flows for different providers:
657
-
658
- ```typescript
659
- import { LoginHelper } from "rhdh-e2e-test-utils/helpers";
660
-
661
- const loginHelper = new LoginHelper(page);
662
-
663
- // Guest authentication
664
- await loginHelper.loginAsGuest();
665
- await loginHelper.signOut();
666
-
667
- // Keycloak authentication
668
- await loginHelper.loginAsKeycloakUser("username", "password");
669
-
670
- // GitHub authentication (requires environment variables)
671
- await loginHelper.loginAsGithubUser();
672
- ```
673
-
674
- #### APIHelper
675
-
676
- Provides utilities for API interactions with both GitHub and Backstage catalog:
677
-
678
- ```typescript
679
- import { APIHelper } from "rhdh-e2e-test-utils/helpers";
680
-
681
- // GitHub API operations
682
- await APIHelper.createGitHubRepo("owner", "repo-name");
683
- await APIHelper.deleteGitHubRepo("owner", "repo-name");
684
- const prs = await APIHelper.getGitHubPRs("owner", "repo", "open");
685
-
686
- // Backstage catalog API operations
687
- const apiHelper = new APIHelper();
688
- await apiHelper.setBaseUrl(rhdhUrl);
689
- await apiHelper.setStaticToken(token);
690
-
691
- const users = await apiHelper.getAllCatalogUsersFromAPI();
692
- const groups = await apiHelper.getAllCatalogGroupsFromAPI();
693
- const locations = await apiHelper.getAllCatalogLocationsFromAPI();
694
-
695
- // Schedule entity refresh
696
- await apiHelper.scheduleEntityRefreshFromAPI("my-component", "component", token);
697
- ```
698
-
699
- #### setupBrowser
700
-
701
- Utility function for setting up a shared browser context with video recording. Use this in `test.beforeAll` for serial test suites or when you want to persist the browser context across multiple tests (e.g., to avoid repeated logins):
702
-
703
- ```typescript
704
- import { test } from "@playwright/test";
705
- import { setupBrowser, LoginHelper } from "rhdh-e2e-test-utils/helpers";
706
- import type { Page, BrowserContext } from "@playwright/test";
707
-
708
- test.describe.configure({ mode: "serial" });
709
-
710
- let page: Page;
711
- let context: BrowserContext;
712
-
713
- test.beforeAll(async ({ browser }, testInfo) => {
714
- // Setup shared browser context with video recording
715
- ({ page, context } = await setupBrowser(browser, testInfo));
716
-
717
- // Login once, session persists across all tests in this suite
718
- const loginHelper = new LoginHelper(page);
719
- await page.goto("/");
720
- await loginHelper.loginAsKeycloakUser();
721
- });
722
-
723
- test.afterAll(async () => {
724
- await context.close();
725
- });
726
-
727
- test("first test - already logged in", async () => {
728
- await page.goto("/catalog");
729
- // No need to login again
730
- });
731
-
732
- test("second test - session persists", async () => {
733
- await page.goto("/settings");
734
- // Still logged in from beforeAll
735
- });
736
- ```
737
-
738
- ### Page Objects
739
-
740
- Pre-built page object classes for common RHDH pages:
741
-
742
- ```typescript
743
- import {
744
- CatalogPage,
745
- HomePage,
746
- CatalogImportPage,
747
- ExtensionsPage,
748
- NotificationPage,
749
- } from "rhdh-e2e-test-utils/pages";
750
- ```
751
-
752
- #### CatalogPage
753
-
754
- ```typescript
755
- const catalogPage = new CatalogPage(page);
756
-
757
- // Navigate to catalog
758
- await catalogPage.go();
759
-
760
- // Search for entities
761
- await catalogPage.search("my-component");
762
-
763
- // Navigate to specific component
764
- await catalogPage.goToByName("my-component");
765
- ```
766
-
767
- #### HomePage
768
-
769
- ```typescript
770
- const homePage = new HomePage(page);
771
-
772
- // Verify quick search functionality
773
- await homePage.verifyQuickSearchBar("search-term");
774
-
775
- // Verify quick access sections
776
- await homePage.verifyQuickAccess("Favorites", "My Component");
777
- ```
778
-
779
- #### CatalogImportPage
780
-
781
- ```typescript
782
- const catalogImportPage = new CatalogImportPage(page);
783
-
784
- // Register or refresh an existing component
785
- const wasAlreadyRegistered = await catalogImportPage.registerExistingComponent(
786
- "https://github.com/org/repo/blob/main/catalog-info.yaml"
787
- );
788
-
789
- // Analyze a component URL
790
- await catalogImportPage.analyzeComponent("https://github.com/org/repo/blob/main/catalog-info.yaml");
791
-
792
- // Inspect entity and verify YAML content
793
- await catalogImportPage.inspectEntityAndVerifyYaml("kind: Component");
794
- ```
795
-
796
- #### ExtensionsPage
797
-
798
- ```typescript
799
- const extensionsPage = new ExtensionsPage(page);
800
-
801
- // Filter by support type
802
- await extensionsPage.selectSupportTypeFilter("Red Hat");
803
-
804
- // Verify plugin details
805
- await extensionsPage.verifyPluginDetails({
806
- pluginName: "Topology",
807
- badgeLabel: "Red Hat support",
808
- badgeText: "Red Hat",
809
- });
810
-
811
- // Search and verify results
812
- await extensionsPage.waitForSearchResults("catalog");
813
- ```
814
-
815
- #### NotificationPage
816
-
817
- ```typescript
818
- const notificationPage = new NotificationPage(page);
819
-
820
- // Navigate to notifications
821
- await notificationPage.clickNotificationsNavBarItem();
822
-
823
- // Check notification content
824
- await notificationPage.notificationContains("Build completed");
825
-
826
- // Manage notifications
827
- await notificationPage.markAllNotificationsAsRead();
828
- await notificationPage.selectSeverity("critical");
829
- await notificationPage.viewSaved();
830
- await notificationPage.sortByNewestOnTop();
831
- ```
832
-
833
- ### ESLint Configuration
834
-
835
- Pre-configured ESLint rules for Playwright tests:
836
-
837
- ```javascript
838
- // eslint.config.js
839
- import { createEslintConfig } from "rhdh-e2e-test-utils/eslint";
840
-
841
- export default createEslintConfig(import.meta.dirname);
842
- ```
843
-
844
-
845
- ### TypeScript Configuration
846
-
847
- Extend the base tsconfig:
848
-
849
- ```json
850
- {
851
- "extends": "rhdh-e2e-test-utils/tsconfig",
852
- "include": ["tests/**/*.ts"]
853
- }
854
- ```
855
-
856
- ## Configuration Files
857
-
858
- ### Default Configuration Structure
859
-
860
- The package includes default configurations organized in a modular structure:
861
-
862
- ```
863
- src/deployment/rhdh/config/
864
- ├── common/ # Base configurations (always applied)
865
- │ ├── app-config-rhdh.yaml # Base app configuration
866
- │ ├── dynamic-plugins.yaml # Default dynamic plugins
867
- │ └── rhdh-secrets.yaml # Base secrets template
868
- ├── auth/ # Auth-specific configurations
869
- │ ├── guest/
870
- │ │ └── app-config.yaml # Guest auth configuration
871
- │ └── keycloak/
872
- │ ├── app-config.yaml # Keycloak OIDC configuration
873
- │ ├── dynamic-plugins.yaml # Keycloak-specific plugins
874
- │ └── secrets.yaml # Keycloak secrets template
875
- ├── helm/
876
- │ └── value_file.yaml # Default Helm values
877
- └── operator/
878
- └── subscription.yaml # Default Backstage CR
879
- ```
880
-
881
- ### Configuration Merging
882
-
883
- Configurations are merged in the following order (later overrides earlier):
884
-
885
- 1. **Common configs** (`config/common/`) - Base configurations
886
- 2. **Auth configs** (`config/auth/{provider}/`) - Auth-provider-specific configurations
887
- 3. **Project configs** (`tests/config/`) - Your project's custom configurations
888
-
889
- This allows you to use built-in defaults while only overriding what you need.
890
-
891
- ### Project Configuration
892
-
893
- Create these files in your project's `tests/config/` directory:
894
-
895
- #### app-config-rhdh.yaml
896
-
897
- ```yaml
898
- app:
899
- title: My RHDH Test Instance
900
-
901
- backend:
902
- reading:
903
- allow:
904
- - host: ${MY_BACKEND_HOST}
905
-
906
- # Plugin-specific config
907
- techRadar:
908
- url: "http://${DATA_SOURCE_URL}/tech-radar"
909
-
910
- # Note: Auth configuration is automatically included based on the 'auth' option
911
- # You only need to add auth config here if you want to override the defaults
912
- ```
913
-
914
- #### dynamic-plugins.yaml
915
-
916
- ```yaml
917
- includes:
918
- - dynamic-plugins.default.yaml
919
-
920
- plugins:
921
- - package: ./dynamic-plugins/dist/my-frontend-plugin
922
- disabled: false
923
- - package: ./dynamic-plugins/dist/my-backend-plugin-dynamic
924
- disabled: false
925
- ```
926
-
927
- #### rhdh-secrets.yaml
928
-
929
- Secrets support environment variable substitution (`$VAR` or `${VAR}` syntax).
930
-
931
- ```yaml
932
- apiVersion: v1
933
- kind: Secret
934
- metadata:
935
- name: rhdh-secrets
936
- type: Opaque
937
- stringData:
938
- GITHUB_TOKEN: $GITHUB_TOKEN
939
- MY_API_KEY: $MY_API_KEY
940
- ```
941
-
942
- - **Local development**: Define secrets in a `.env` file at your project root
943
- - **CI**: Set environment variables directly in your CI pipeline
944
- - **Runtime secrets**: Set `process.env.MY_SECRET` before calling `rhdh.deploy()`
945
-
946
- ## Environment Variables
947
-
948
- ### Required Environment Variables
949
-
950
- | Variable | Description |
951
- |----------|-------------|
952
- | `RHDH_VERSION` | RHDH version to deploy (e.g., "1.5") |
953
- | `INSTALLATION_METHOD` | Deployment method ("helm" or "operator") |
954
-
955
- ### Auto-Generated Environment Variables
956
-
957
- | Variable | Description |
958
- |----------|-------------|
959
- | `K8S_CLUSTER_ROUTER_BASE` | OpenShift ingress domain (set by global setup) |
960
- | `RHDH_BASE_URL` | Full RHDH URL (set during deployment) |
961
-
962
- ### Optional Environment Variables
963
-
964
- | Variable | Description |
965
- |----------|-------------|
966
- | `CI` | If set, namespaces are auto-deleted after tests |
967
- | `CHART_URL` | Custom Helm chart URL (default: `oci://quay.io/rhdh/chart`) |
968
- | `SKIP_KEYCLOAK_DEPLOYMENT` | Set to `true` to skip automatic Keycloak deployment in global setup |
969
-
970
- ### Keycloak Environment Variables (for `auth: "keycloak"`)
971
-
972
- When using Keycloak authentication, these environment variables are required:
973
-
974
- | Variable | Description |
975
- |----------|-------------|
976
- | `KEYCLOAK_BASE_URL` | Keycloak instance URL |
977
- | `KEYCLOAK_METADATA_URL` | OIDC metadata URL (e.g., `{KEYCLOAK_BASE_URL}/realms/{realm}/.well-known/openid-configuration`) |
978
- | `KEYCLOAK_CLIENT_ID` | OIDC client ID |
979
- | `KEYCLOAK_CLIENT_SECRET` | OIDC client secret |
980
- | `KEYCLOAK_REALM` | Keycloak realm name |
981
- | `KEYCLOAK_LOGIN_REALM` | Login realm (usually same as `KEYCLOAK_REALM`) |
982
-
983
- These are automatically set when using `KeycloakHelper.configureForRHDH()`. See [Keycloak Deployment](#keycloak-deployment) for details.
984
-
985
-
986
- ## Examples
987
-
988
- ### Custom Deployment Configuration
989
-
990
- ```typescript
991
- import { test } from "rhdh-e2e-test-utils/test";
992
-
993
- test.beforeAll(async ({ rhdh }) => {
994
- await rhdh.configure({
995
- version: "1.5",
996
- method: "helm",
997
- auth: "keycloak", // or "guest" for development
998
- appConfig: "tests/config/app-config.yaml",
999
- secrets: "tests/config/secrets.yaml",
1000
- dynamicPlugins: "tests/config/plugins.yaml",
1001
- valueFile: "tests/config/values.yaml",
1002
- });
1003
-
1004
- await rhdh.deploy();
1005
- });
1006
- ```
1007
-
1008
- ### Guest Authentication (Development)
1009
-
1010
- ```typescript
1011
- import { test } from "rhdh-e2e-test-utils/test";
1012
-
1013
- test.beforeAll(async ({ rhdh }) => {
1014
- await rhdh.configure({ auth: "guest" });
1015
- await rhdh.deploy();
1016
- });
1017
-
1018
- test("test with guest login", async ({ page, loginHelper }) => {
1019
- await page.goto("/");
1020
- await loginHelper.loginAsGuest();
1021
- // ... test assertions
1022
- });
1023
- ```
1024
-
1025
- ### Using Helpers and Page Objects
1026
-
1027
- ```typescript
1028
- import { test, expect } from "rhdh-e2e-test-utils/test";
1029
- import { CatalogPage } from "rhdh-e2e-test-utils/pages";
1030
- import { APIHelper } from "rhdh-e2e-test-utils/helpers";
1031
-
1032
- test.beforeAll(async ({ rhdh }) => {
1033
- await rhdh.deploy();
1034
- });
1035
-
1036
- test("catalog interaction", async ({ page, uiHelper, loginHelper }) => {
1037
- // Login
1038
- await loginHelper.loginAsKeycloakUser();
1039
-
1040
- // Use page object for catalog operations
1041
- const catalogPage = new CatalogPage(page);
1042
- await catalogPage.go();
1043
- await catalogPage.search("my-component");
1044
-
1045
- // Use UI helper for assertions
1046
- await uiHelper.verifyRowsInTable(["my-component"]);
1047
- });
1048
-
1049
- test("API operations", async ({ rhdh }) => {
1050
- // Create GitHub repo via API
1051
- await APIHelper.createGitHubRepo("my-org", "test-repo");
1052
-
1053
- // Clean up
1054
- await APIHelper.deleteGitHubRepo("my-org", "test-repo");
1055
- });
1056
- ```
1057
-
1058
-
1059
- ## Development
1060
-
1061
- ### Setup
1062
-
1063
- This project uses Yarn 3 with Corepack. To get started:
1064
-
1065
- ```bash
1066
- # Enable Corepack (if not already enabled)
1067
- corepack enable
1068
-
1069
- # Install dependencies
1070
- yarn install
1071
-
1072
- # Build the project
1073
- yarn build
1074
- ```
1075
-
1076
- ### Available Scripts
1077
-
1078
- | Script | Description |
1079
- |--------|-------------|
1080
- | `yarn build` | Clean and build the TypeScript project |
1081
- | `yarn check` | Run typecheck, lint, and prettier checks |
1082
- | `yarn lint:check` | Check for ESLint issues |
1083
- | `yarn lint:fix` | Auto-fix ESLint issues |
1084
- | `yarn prettier:check` | Check code formatting |
1085
- | `yarn prettier:fix` | Auto-fix code formatting |
1086
-
1087
- ### Testing Local Changes in Consumer Projects
1088
-
1089
- When developing features or fixes in `rhdh-e2e-test-utils`, you can test your local changes in a consumer project (e.g., a plugin's e2e-tests) before publishing.
1090
-
1091
- #### 1. Build your local changes
1092
-
1093
- ```bash
1094
- cd /path/to/rhdh-e2e-test-utils
1095
- yarn build
1096
- ```
1097
-
1098
- #### 2. Update the consumer project's package.json
1099
-
1100
- In your e2e-tests project, update the dependency to point to your local package using the `file:` protocol:
1101
-
1102
- ```json
1103
- "rhdh-e2e-test-utils": "file:/path/to/rhdh-e2e-test-utils"
1104
- ```
1105
-
1106
- Example:
1107
- ```json
1108
- "rhdh-e2e-test-utils": "file:/Users/yourname/Documents/rhdh/rhdh-e2e-test-utils"
1109
- ```
1110
-
1111
- #### 3. Install dependencies in the consumer project
1112
-
1113
- ```bash
1114
- yarn install
1115
- ```
1116
-
1117
- #### 4. Run tests with NODE_PRESERVE_SYMLINKS
1118
-
1119
- When running tests with a local symlinked package, you **must** set the `NODE_PRESERVE_SYMLINKS` environment variable:
1120
-
1121
- ```bash
1122
- NODE_PRESERVE_SYMLINKS=1 yarn test
1123
- NODE_PRESERVE_SYMLINKS=1 yarn test:headed
1124
- NODE_PRESERVE_SYMLINKS=1 yarn test:ui
1125
- ```
1126
-
1127
- > **Why is NODE_PRESERVE_SYMLINKS needed?**
1128
- >
1129
- > When using local packages via `file:` protocol, the package manager creates a symlink. Node.js follows symlinks by default and tries to resolve peer dependencies (like `@playwright/test`) from the original package location. This causes duplicate Playwright instances which fails with:
1130
- > ```
1131
- > Error: Requiring @playwright/test second time
1132
- > ```
1133
- > Setting `NODE_PRESERVE_SYMLINKS=1` tells Node.js to resolve dependencies from the symlink location (your project's `node_modules`) instead of the original package location.
1134
-
1135
- #### 5. Rebuild after making changes
1136
-
1137
- When you make further changes to `rhdh-e2e-test-utils`, rebuild before running tests:
1138
-
1139
- ```bash
1140
- cd /path/to/rhdh-e2e-test-utils
1141
- yarn build
1142
- ```
1143
-
1144
- Then run your tests again in the consumer project (no need to reinstall).
1145
-
1146
- #### 6. Restore the published version
1147
-
1148
- After testing, restore the published version in the consumer project's `package.json`:
1149
-
1150
- ```json
1151
- "rhdh-e2e-test-utils": "^1.0.0"
1152
- ```
1153
-
1154
- Then run:
1155
- ```bash
1156
- yarn install
1157
- ```
1158
-
1159
- You can now run tests normally without `NODE_PRESERVE_SYMLINKS`.
1160
-
1161
- ### CI/CD
1162
-
1163
- The project includes GitHub Actions workflows:
1164
-
1165
- - **PR Build and Check**: Runs on pull requests to `main`. Executes linting, type checking, and build verification.
1166
- - **Publish to NPM**: Manual workflow dispatch to publish the package to npm registry.
1167
-
1168
- ## License
1169
-
1170
- Apache-2.0
5
+ - Package documentation: https://redhat-developer.github.io/rhdh-e2e-test-utils/
6
+ - Overlay testing documentation: https://redhat-developer.github.io/rhdh-e2e-test-utils/overlay/
@@ -26,6 +26,11 @@ export declare class RHDHDeployment {
26
26
  waitUntilReady(timeout?: number): Promise<void>;
27
27
  teardown(): Promise<void>;
28
28
  private _resolveChartVersion;
29
+ /**
30
+ * Resolve the semantic version from the "next" tag by looking up the
31
+ * downstream image (rhdh-hub-rhel9) and finding tags with the same digest.
32
+ */
33
+ private _resolveVersionFromNextTag;
29
34
  private _buildDeploymentConfig;
30
35
  configure(deploymentOptions?: DeploymentOptions): Promise<void>;
31
36
  private _buildBaseUrl;
@@ -1 +1 @@
1
- {"version":3,"file":"deployment.d.ts","sourceRoot":"","sources":["../../../src/deployment/rhdh/deployment.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;AAgB1E,OAAO,KAAK,EACV,iBAAiB,EACjB,gBAAgB,EAGjB,MAAM,YAAY,CAAC;AAEpB,qBAAa,cAAc;IAClB,SAAS,yBAAgC;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,EAAE,gBAAgB,CAAC;gBAE9B,SAAS,EAAE,MAAM;IAUvB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;YAqBf,eAAe;YAgBf,aAAa;IAe3B;;;OAGG;YACW,0BAA0B;YAuC1B,oBAAoB;YAWpB,eAAe;YAmCf,mBAAmB;IA4B3B,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAWrC;;;;OAIG;IACG,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC;IAOpC,cAAc,CAAC,OAAO,GAAE,MAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAwBpD,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;YAIjB,oBAAoB;IA6BlC,OAAO,CAAC,sBAAsB;IAoCxB,SAAS,CAAC,iBAAiB,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAcrE,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,IAAI;IAIZ,OAAO,CAAC,SAAS;CAMlB"}
1
+ {"version":3,"file":"deployment.d.ts","sourceRoot":"","sources":["../../../src/deployment/rhdh/deployment.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;AAiB1E,OAAO,KAAK,EACV,iBAAiB,EACjB,gBAAgB,EAGjB,MAAM,YAAY,CAAC;AAEpB,qBAAa,cAAc;IAClB,SAAS,yBAAgC;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,EAAE,gBAAgB,CAAC;gBAE9B,SAAS,EAAE,MAAM;IAUvB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;YAqBf,eAAe;YAgBf,aAAa;IAqB3B;;;OAGG;YACW,0BAA0B;YAuC1B,oBAAoB;YAWpB,eAAe;YAmCf,mBAAmB;IAiD3B,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAWrC;;;;OAIG;IACG,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC;IAOpC,cAAc,CAAC,OAAO,GAAE,MAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAwBpD,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;YAIjB,oBAAoB;IAsClC;;;OAGG;YACW,0BAA0B;IAwCxC,OAAO,CAAC,sBAAsB;IAoCxB,SAAS,CAAC,iBAAiB,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAcrE,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,IAAI;IAIZ,OAAO,CAAC,SAAS;CAMlB"}
@@ -5,6 +5,7 @@ import { test } from "@playwright/test";
5
5
  import { mergeYamlFilesIfExists, deepMerge } from "../../utils/merge-yamls.js";
6
6
  import { loadAndInjectPluginMetadata, generateDynamicPluginsConfigFromMetadata, } from "../../utils/plugin-metadata.js";
7
7
  import { envsubst } from "../../utils/common.js";
8
+ import cloneDeepWith from "lodash.clonedeepwith";
8
9
  import fs from "fs-extra";
9
10
  import { DEFAULT_CONFIG_PATHS, AUTH_CONFIG_PATHS, CHART_URL, } from "./constants.js";
10
11
  export class RHDHDeployment {
@@ -51,7 +52,13 @@ export class RHDHDeployment {
51
52
  authConfig.secrets,
52
53
  this.deploymentConfig.secrets,
53
54
  ]);
54
- await this.k8sClient.applySecretFromObject("rhdh-secrets", JSON.parse(envsubst(JSON.stringify(secretsYaml))), this.deploymentConfig.namespace);
55
+ // Use cloneDeepWith to substitute env vars in-place, avoiding JSON.parse issues
56
+ // with control characters in secrets (e.g., private keys with newlines)
57
+ const substituted = cloneDeepWith(secretsYaml, (value) => {
58
+ if (typeof value === "string")
59
+ return envsubst(value);
60
+ });
61
+ await this.k8sClient.applySecretFromObject("rhdh-secrets", substituted, this.deploymentConfig.namespace);
55
62
  }
56
63
  /**
57
64
  * Builds the merged dynamic plugins configuration.
@@ -117,9 +124,25 @@ export class RHDHDeployment {
117
124
  ]);
118
125
  this._logBoxen("Subscription", subscriptionObject);
119
126
  fs.writeFileSync(`/tmp/${this.deploymentConfig.namespace}-subscription.yaml`, yaml.dump(subscriptionObject));
127
+ const version = this.deploymentConfig.version;
128
+ const isSemanticVersion = /^\d+(\.\d+)?$/.test(version);
129
+ // Use main branch for non-semantic versions (e.g., "next", "latest")
130
+ const branch = isSemanticVersion ? `release-${version}` : "main";
131
+ // Build version argument based on version type
132
+ let versionArg;
133
+ if (isSemanticVersion) {
134
+ versionArg = `-v ${version}`;
135
+ }
136
+ else if (version === "next") {
137
+ versionArg = "--next";
138
+ }
139
+ else {
140
+ throw new Error(`Invalid RHDH version "${version}". Use semantic version (e.g., "1.5") or "next".`);
141
+ }
142
+ this._log(`Using operator branch: ${branch}, version arg: ${versionArg}`);
120
143
  await $ `
121
144
  set -e;
122
- curl -s https://raw.githubusercontent.com/redhat-developer/rhdh-operator/refs/heads/release-${this.deploymentConfig.version}/.rhdh/scripts/install-rhdh-catalog-source.sh | bash -s -- -v ${this.deploymentConfig.version} --install-operator rhdh
145
+ curl -sf https://raw.githubusercontent.com/redhat-developer/rhdh-operator/refs/heads/${branch}/.rhdh/scripts/install-rhdh-catalog-source.sh | bash -s -- ${versionArg} --install-operator rhdh
123
146
 
124
147
  timeout 300 bash -c '
125
148
  while ! oc get crd/backstages.rhdh.redhat.com -n "${this.deploymentConfig.namespace}" >/dev/null 2>&1; do
@@ -165,33 +188,66 @@ export class RHDHDeployment {
165
188
  await this.k8sClient.deleteNamespace(this.deploymentConfig.namespace);
166
189
  }
167
190
  async _resolveChartVersion(version) {
168
- // Semantic versions (e.g., 1.2)
169
- if (/^(\d+(\.\d+)?)$/.test(version)) {
191
+ let resolvedVersion = version;
192
+ // Handle "next" tag by looking up the corresponding version from downstream image
193
+ if (version === "next") {
194
+ resolvedVersion = await this._resolveVersionFromNextTag();
195
+ this._log(`Resolved "next" tag to version: ${resolvedVersion}`);
196
+ }
197
+ // Semantic versions (e.g., 1.2, 1.10)
198
+ if (/^(\d+(\.\d+)?)$/.test(resolvedVersion)) {
170
199
  const response = await fetch("https://quay.io/api/v1/repository/rhdh/chart/tag/?onlyActiveTags=true&limit=600");
171
200
  if (!response.ok)
172
201
  throw new Error(`Failed to fetch chart versions: ${response.statusText}`);
173
202
  const data = (await response.json());
174
203
  const matching = data.tags
175
204
  .map((t) => t.name)
176
- .filter((name) => name.startsWith(`${version}-`))
205
+ .filter((name) => name.startsWith(`${resolvedVersion}-`))
177
206
  .sort((a, b) => a.localeCompare(b, undefined, { numeric: true }));
178
207
  const latest = matching.at(-1);
179
208
  if (!latest)
180
- throw new Error(`No chart version found for ${version}`);
209
+ throw new Error(`No chart version found for ${resolvedVersion}`);
181
210
  return latest;
182
211
  }
183
212
  // CI build versions (e.g., 1.2.3-CI)
184
- if (version.endsWith("CI"))
185
- return version;
213
+ if (resolvedVersion.endsWith("CI"))
214
+ return resolvedVersion;
186
215
  throw new Error(`Invalid Helm chart version format: "${version}"`);
187
216
  }
217
+ /**
218
+ * Resolve the semantic version from the "next" tag by looking up the
219
+ * downstream image (rhdh-hub-rhel9) and finding tags with the same digest.
220
+ */
221
+ async _resolveVersionFromNextTag() {
222
+ // Fetch all active tags in a single API call
223
+ const response = await fetch("https://quay.io/api/v1/repository/rhdh/rhdh-hub-rhel9/tag/?onlyActiveTags=true&limit=75");
224
+ if (!response.ok) {
225
+ throw new Error(`Failed to fetch image tags: ${response.statusText}`);
226
+ }
227
+ // Use Record to avoid snake_case linting issues with Quay API response
228
+ const data = (await response.json());
229
+ // Find the "next" tag and get its digest
230
+ const nextTag = data.tags.find((t) => t["name"] === "next");
231
+ if (!nextTag) {
232
+ throw new Error('No "next" tag found in rhdh-hub-rhel9 repository');
233
+ }
234
+ const digest = nextTag["manifest_digest"];
235
+ this._log(`"next" tag digest: ${digest}`);
236
+ // Find semantic version tag (e.g., "1.10") with the same digest
237
+ const semanticVersionTag = data.tags.find((t) => t["manifest_digest"] === digest &&
238
+ /^\d+\.\d+$/.test(t["name"]));
239
+ if (!semanticVersionTag) {
240
+ throw new Error(`Could not find semantic version tag for "next" (digest: ${digest})`);
241
+ }
242
+ return semanticVersionTag["name"];
243
+ }
188
244
  _buildDeploymentConfig(input) {
189
- const version = input.version ?? process.env.RHDH_VERSION;
190
- const method = input.method ?? process.env.INSTALLATION_METHOD;
191
- if (!version)
192
- throw new Error("RHDH version is required");
193
- if (!method)
194
- throw new Error("Installation method (helm/operator) is required");
245
+ // Default to "next" if RHDH_VERSION not set
246
+ const version = input.version ?? process.env.RHDH_VERSION ?? "next";
247
+ // Default to "helm" if INSTALLATION_METHOD not set
248
+ const method = input.method ??
249
+ process.env.INSTALLATION_METHOD ??
250
+ "helm";
195
251
  const base = {
196
252
  version,
197
253
  namespace: input.namespace ?? this.deploymentConfig.namespace,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rhdh-e2e-test-utils",
3
- "version": "1.1.5",
3
+ "version": "1.1.7",
4
4
  "description": "Test utilities for RHDH E2E tests",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -74,6 +74,7 @@
74
74
  "@playwright/test": "^1.57.0",
75
75
  "@types/fs-extra": "^11.0.4",
76
76
  "@types/js-yaml": "^4.0.9",
77
+ "@types/lodash.clonedeepwith": "^4.5.9",
77
78
  "@types/lodash.mergewith": "^4.6.9",
78
79
  "@types/node": "^24.10.1"
79
80
  },
@@ -87,6 +88,7 @@
87
88
  "eslint-plugin-playwright": "^2.4.0",
88
89
  "fs-extra": "^11.3.2",
89
90
  "js-yaml": "^4.1.1",
91
+ "lodash.clonedeepwith": "^4.5.0",
90
92
  "lodash.mergewith": "^4.6.2",
91
93
  "otplib": "12.0.1",
92
94
  "prettier": "^3.7.4",