Skip to content

Commit cb8b279

Browse files
Add helpers and pages and objects
1 parent fc8cb94 commit cb8b279

File tree

20 files changed

+2539
-13
lines changed

20 files changed

+2539
-13
lines changed

README.md

Lines changed: 270 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -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
179185
import { test, expect } from "rhdh-e2e-test-utils/test";
186+
180187
test.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}");
388402
const 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

393623
Pre-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

package.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@
2727
"./utils": {
2828
"types": "./dist/utils/index.d.ts",
2929
"default": "./dist/utils/index.js"
30+
},
31+
"./helpers": {
32+
"types": "./dist/playwright/helpers/index.d.ts",
33+
"default": "./dist/playwright/helpers/index.js"
34+
},
35+
"./pages": {
36+
"types": "./dist/playwright/pages/index.d.ts",
37+
"default": "./dist/playwright/pages/index.js"
3038
}
3139
},
3240
"files": [
@@ -58,6 +66,7 @@
5866
"@playwright/test": "^1.57.0"
5967
},
6068
"devDependencies": {
69+
"@backstage/catalog-model": "1.7.5",
6170
"@playwright/test": "^1.57.0",
6271
"@types/fs-extra": "^11.0.4",
6372
"@types/js-yaml": "^4.0.9",
@@ -74,6 +83,7 @@
7483
"fs-extra": "^11.3.2",
7584
"js-yaml": "^4.1.1",
7685
"lodash.mergewith": "^4.6.2",
86+
"otplib": "12.0.1",
7787
"prettier": "^3.7.4",
7888
"typescript": "^5.9.3",
7989
"typescript-eslint": "^8.48.1",

0 commit comments

Comments
 (0)