@@ -15,6 +15,8 @@ A comprehensive test utility package for Red Hat Developer Hub (RHDH) end-to-end
1515 - [ Playwright Configuration] ( #playwright-configuration )
1616 - [ RHDH Deployment] ( #rhdh-deployment )
1717 - [ Utilities] ( #utilities )
18+ - [ Helpers] ( #helpers )
19+ - [ Page Objects] ( #page-objects )
1820 - [ ESLint Configuration] ( #eslint-configuration )
1921 - [ TypeScript Configuration] ( #typescript-configuration )
2022- [ Configuration Files] ( #configuration-files )
@@ -81,6 +83,8 @@ The package provides multiple entry points for different use cases:
8183| ` rhdh-e2e-test-utils/playwright-config ` | Base Playwright configuration |
8284| ` rhdh-e2e-test-utils/rhdh ` | RHDH deployment class and types |
8385| ` rhdh-e2e-test-utils/utils ` | Utility functions (bash, YAML, Kubernetes) |
86+ | ` rhdh-e2e-test-utils/helpers ` | UI, API, and login helper classes |
87+ | ` rhdh-e2e-test-utils/pages ` | Page object classes for common RHDH pages |
8488| ` rhdh-e2e-test-utils/eslint ` | ESLint configuration factory |
8589| ` rhdh-e2e-test-utils/tsconfig ` | Base TypeScript configuration |
8690
@@ -130,10 +134,10 @@ test("my plugin test", async ({ page }) => {
130134
131135### 4. Create Configuration Files
132136
133- Create a ` config/ ` directory with your RHDH configuration:
137+ Create a ` tests/ config/` directory with your RHDH configuration:
134138
135139```
136- config/
140+ tests/ config/
137141├── app-config-rhdh.yaml # App configuration
138142├── dynamic-plugins.yaml # Dynamic plugins configuration
139143└── rhdh-secrets.yaml # Secrets (with env var placeholders)
@@ -167,6 +171,8 @@ import { test, expect } from "rhdh-e2e-test-utils/test";
167171| Fixture | Scope | Description |
168172| ---------| -------| -------------|
169173| ` rhdh ` | worker | Shared RHDHDeployment across all tests in a worker |
174+ | ` uiHelper ` | test | UIhelper instance for common UI interactions |
175+ | ` loginHelper ` | test | LoginHelper instance for authentication flows |
170176| ` baseURL ` | test | Automatically set to the RHDH instance URL |
171177
172178#### Fixture Behavior
@@ -177,6 +183,7 @@ import { test, expect } from "rhdh-e2e-test-utils/test";
177183
178184``` typescript
179185import { test , expect } from " rhdh-e2e-test-utils/test" ;
186+
180187test .beforeAll (async ({ rhdh }) => {
181188 // Configure RHDH (creates namespace, and optional DeploymentOptions)
182189 await rhdh .configure ();
@@ -188,10 +195,17 @@ test.beforeAll(async ({ rhdh }) => {
188195 await rhdh .deploy ();
189196});
190197
191- test (" example test" , async ({ page , rhdh }) => {
198+ test (" example test" , async ({ page , rhdh , uiHelper , loginHelper }) => {
192199 // page.goto("/") will use rhdh.rhdhUrl as base
193200 await page .goto (" /" );
194201
202+ // Login as guest user
203+ await loginHelper .loginAsGuest ();
204+
205+ // Use UI helper for common interactions
206+ await uiHelper .verifyHeading (" Welcome" );
207+ await uiHelper .clickButton (" Get Started" );
208+
195209 // Access deployment info
196210 console .log (` Namespace: ${rhdh .deploymentConfig .namespace } ` );
197211 console .log (` URL: ${rhdh .rhdhUrl } ` );
@@ -388,6 +402,222 @@ const result = envsubst("Port: ${PORT:-8080}");
388402const result = envsubst (" API: ${API_URL}" );
389403```
390404
405+ ### Helpers
406+
407+ The package provides helper classes for common testing operations.
408+
409+ #### UIhelper
410+
411+ A utility class for common UI interactions with Material-UI components:
412+
413+ ``` typescript
414+ import { UIhelper } from " rhdh-e2e-test-utils/helpers" ;
415+
416+ const uiHelper = new UIhelper (page );
417+
418+ // Wait for page to fully load
419+ await uiHelper .waitForLoad ();
420+
421+ // Verify headings and text
422+ await uiHelper .verifyHeading (" Welcome to RHDH" );
423+ await uiHelper .verifyText (" Some content" );
424+
425+ // Button interactions
426+ await uiHelper .clickButton (" Submit" );
427+ await uiHelper .clickButtonByLabel (" Close" );
428+
429+ // Navigation
430+ await uiHelper .openSidebar (" Catalog" );
431+ await uiHelper .clickTab (" Overview" );
432+
433+ // Table operations
434+ await uiHelper .verifyRowsInTable ([" row1" , " row2" ]);
435+ await uiHelper .verifyCellsInTable ([" cell1" , " cell2" ]);
436+
437+ // MUI component interactions
438+ await uiHelper .selectMuiBox (" Kind" , " Component" );
439+ await uiHelper .fillTextInputByLabel (" Name" , " my-component" );
440+ ```
441+
442+ #### LoginHelper
443+
444+ Handles authentication flows for different providers:
445+
446+ ``` typescript
447+ import { LoginHelper } from " rhdh-e2e-test-utils/helpers" ;
448+
449+ const loginHelper = new LoginHelper (page );
450+
451+ // Guest authentication
452+ await loginHelper .loginAsGuest ();
453+ await loginHelper .signOut ();
454+
455+ // Keycloak authentication
456+ await loginHelper .loginAsKeycloakUser (" username" , " password" );
457+
458+ // GitHub authentication (requires environment variables)
459+ await loginHelper .loginAsGithubUser ();
460+ ```
461+
462+ #### APIHelper
463+
464+ Provides utilities for API interactions with both GitHub and Backstage catalog:
465+
466+ ``` typescript
467+ import { APIHelper } from " rhdh-e2e-test-utils/helpers" ;
468+
469+ // GitHub API operations
470+ await APIHelper .createGitHubRepo (" owner" , " repo-name" );
471+ await APIHelper .deleteGitHubRepo (" owner" , " repo-name" );
472+ const prs = await APIHelper .getGitHubPRs (" owner" , " repo" , " open" );
473+
474+ // Backstage catalog API operations
475+ const apiHelper = new APIHelper ();
476+ await apiHelper .setBaseUrl (rhdhUrl );
477+ await apiHelper .setStaticToken (token );
478+
479+ const users = await apiHelper .getAllCatalogUsersFromAPI ();
480+ const groups = await apiHelper .getAllCatalogGroupsFromAPI ();
481+ const locations = await apiHelper .getAllCatalogLocationsFromAPI ();
482+
483+ // Schedule entity refresh
484+ await apiHelper .scheduleEntityRefreshFromAPI (" my-component" , " component" , token );
485+ ```
486+
487+ #### setupBrowser
488+
489+ 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):
490+
491+ ``` typescript
492+ import { test } from " @playwright/test" ;
493+ import { setupBrowser , LoginHelper } from " rhdh-e2e-test-utils/helpers" ;
494+ import type { Page , BrowserContext } from " @playwright/test" ;
495+
496+ test .describe .configure ({ mode: " serial" });
497+
498+ let page: Page ;
499+ let context: BrowserContext ;
500+
501+ test .beforeAll (async ({ browser }, testInfo ) => {
502+ // Setup shared browser context with video recording
503+ ({ page , context } = await setupBrowser (browser , testInfo ));
504+
505+ // Login once, session persists across all tests in this suite
506+ const loginHelper = new LoginHelper (page );
507+ await page .goto (" /" );
508+ await loginHelper .loginAsKeycloakUser ();
509+ });
510+
511+ test .afterAll (async () => {
512+ await context .close ();
513+ });
514+
515+ test (" first test - already logged in" , async () => {
516+ await page .goto (" /catalog" );
517+ // No need to login again
518+ });
519+
520+ test (" second test - session persists" , async () => {
521+ await page .goto (" /settings" );
522+ // Still logged in from beforeAll
523+ });
524+ ```
525+
526+ ### Page Objects
527+
528+ Pre-built page object classes for common RHDH pages:
529+
530+ ``` typescript
531+ import {
532+ CatalogPage ,
533+ HomePage ,
534+ CatalogImportPage ,
535+ ExtensionsPage ,
536+ NotificationPage ,
537+ } from " rhdh-e2e-test-utils/pages" ;
538+ ```
539+
540+ #### CatalogPage
541+
542+ ``` typescript
543+ const catalogPage = new CatalogPage (page );
544+
545+ // Navigate to catalog
546+ await catalogPage .go ();
547+
548+ // Search for entities
549+ await catalogPage .search (" my-component" );
550+
551+ // Navigate to specific component
552+ await catalogPage .goToByName (" my-component" );
553+ ```
554+
555+ #### HomePage
556+
557+ ``` typescript
558+ const homePage = new HomePage (page );
559+
560+ // Verify quick search functionality
561+ await homePage .verifyQuickSearchBar (" search-term" );
562+
563+ // Verify quick access sections
564+ await homePage .verifyQuickAccess (" Favorites" , " My Component" );
565+ ```
566+
567+ #### CatalogImportPage
568+
569+ ``` typescript
570+ const catalogImportPage = new CatalogImportPage (page );
571+
572+ // Register or refresh an existing component
573+ const wasAlreadyRegistered = await catalogImportPage .registerExistingComponent (
574+ " https://github.com/org/repo/blob/main/catalog-info.yaml"
575+ );
576+
577+ // Analyze a component URL
578+ await catalogImportPage .analyzeComponent (" https://github.com/org/repo/blob/main/catalog-info.yaml" );
579+
580+ // Inspect entity and verify YAML content
581+ await catalogImportPage .inspectEntityAndVerifyYaml (" kind: Component" );
582+ ```
583+
584+ #### ExtensionsPage
585+
586+ ``` typescript
587+ const extensionsPage = new ExtensionsPage (page );
588+
589+ // Filter by support type
590+ await extensionsPage .selectSupportTypeFilter (" Red Hat" );
591+
592+ // Verify plugin details
593+ await extensionsPage .verifyPluginDetails ({
594+ pluginName: " Topology" ,
595+ badgeLabel: " Red Hat support" ,
596+ badgeText: " Red Hat" ,
597+ });
598+
599+ // Search and verify results
600+ await extensionsPage .waitForSearchResults (" catalog" );
601+ ```
602+
603+ #### NotificationPage
604+
605+ ``` typescript
606+ const notificationPage = new NotificationPage (page );
607+
608+ // Navigate to notifications
609+ await notificationPage .clickNotificationsNavBarItem ();
610+
611+ // Check notification content
612+ await notificationPage .notificationContains (" Build completed" );
613+
614+ // Manage notifications
615+ await notificationPage .markAllNotificationsAsRead ();
616+ await notificationPage .selectSeverity (" critical" );
617+ await notificationPage .viewSaved ();
618+ await notificationPage .sortByNewestOnTop ();
619+ ```
620+
391621### ESLint Configuration
392622
393623Pre-configured ESLint rules for Playwright tests:
@@ -431,7 +661,7 @@ src/deployment/rhdh/
431661
432662### Project Configuration
433663
434- Create these files in your project's ` config/ ` directory:
664+ Create these files in your project's ` tests / config/ ` directory:
435665
436666#### app-config-rhdh.yaml
437667
@@ -523,14 +753,46 @@ test.beforeAll(async ({ rhdh }) => {
523753 namespace: "custom-test-ns",
524754 version: "1.5",
525755 method: "helm",
526- appConfig: "custom /app-config.yaml",
527- secrets: "custom /secrets.yaml",
528- dynamicPlugins: "custom /plugins.yaml",
529- valueFile: "custom /values.yaml",
756+ appConfig: "tests/config /app-config.yaml",
757+ secrets: "tests/config /secrets.yaml",
758+ dynamicPlugins: "tests/config /plugins.yaml",
759+ valueFile: "tests/config /values.yaml",
530760 });
531761
532762 await rhdh.deploy();
763+ });
764+ ` ` `
765+
766+ ### Using Helpers and Page Objects
767+
768+ ` ` ` typescript
769+ import { test, expect } from "rhdh-e2e-test-utils/test";
770+ import { CatalogPage } from "rhdh-e2e-test-utils/pages";
771+ import { APIHelper } from "rhdh-e2e-test-utils/helpers";
772+
773+ test.beforeAll(async ({ rhdh }) => {
774+ await rhdh.deploy();
775+ });
776+
777+ test("catalog interaction", async ({ page, uiHelper, loginHelper }) => {
778+ // Login
779+ await loginHelper.loginAsKeycloakUser();
780+
781+ // Use page object for catalog operations
782+ const catalogPage = new CatalogPage(page);
783+ await catalogPage.go();
784+ await catalogPage.search("my-component");
785+
786+ // Use UI helper for assertions
787+ await uiHelper.verifyRowsInTable(["my-component"]);
788+ });
789+
790+ test("API operations", async ({ rhdh }) => {
791+ // Create GitHub repo via API
792+ await APIHelper.createGitHubRepo("my-org", "test-repo");
533793
794+ // Clean up
795+ await APIHelper.deleteGitHubRepo("my-org", "test-repo");
534796});
535797` ` `
536798
0 commit comments