Spec Files
Overlay Documentation
This page covers writing tests within rhdh-plugin-export-overlays. For using rhdh-e2e-test-utils in external projects, see the Guide.
This page explains how to write test specification files for overlay E2E tests.
File Location
Spec files are placed in tests/specs/:
tests/specs/
├── <plugin>.spec.ts # Main test file
├── feature-a.spec.ts # Additional test files (optional)
└── deploy-*.sh # Deployment scripts (optional)2
3
4
Basic Structure
A typical spec file follows this structure:
import { test, expect, Page } from "rhdh-e2e-test-utils/test";
test.describe("Test <plugin>", () => {
// Setup: Deploy RHDH once per worker
test.beforeAll(async ({ rhdh }) => {
await rhdh.configure({ auth: "keycloak" });
await rhdh.deploy();
});
// Login before each test
test.beforeEach(async ({ loginHelper }) => {
await loginHelper.loginAsKeycloakUser();
});
// Test cases
test("Verify functionality", async ({ page, uiHelper }) => {
// Test implementation
});
});2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Imports
Import test utilities from rhdh-e2e-test-utils:
// Core test fixtures
import { test, expect, Page } from "rhdh-e2e-test-utils/test";
// Utility functions
import { $ } from "rhdh-e2e-test-utils/utils";
// Node.js modules
import path from "path";2
3
4
5
6
7
8
Available Fixtures
The following fixtures are automatically injected into tests:
| Fixture | Type | Scope | Description |
|---|---|---|---|
rhdh | RHDHDeployment | Worker | RHDH deployment management |
uiHelper | UIhelper | Test | UI interaction helper |
loginHelper | LoginHelper | Test | Authentication helper |
page | Page | Test | Playwright Page object |
baseURL | string | Worker | RHDH instance URL |
Setup Patterns
There are two main scenarios for test setup:
- Without pre-requisites: Just configure plugin settings and deploy RHDH
- With pre-requisites: Deploy external services first, then deploy RHDH
Scenario 1: Without Pre-requisites
For plugins that only need configuration (no external services):
test.beforeAll(async ({ rhdh }) => {
await rhdh.configure({ auth: "keycloak" });
await rhdh.deploy();
});2
3
4
This is the simplest setup. RHDH is configured and deployed directly. Plugin configuration comes from tests/config/app-config-rhdh.yaml.
Scenario 2: With Pre-requisites (External Services)
Some plugins require external services to be running before RHDH starts. For example, the Tech Radar plugin needs a data provider service.
The order matters:
- Configure RHDH
- Deploy external service(s)
- Get service URL and set as environment variable
- Deploy RHDH (uses the environment variable in its configuration)
import { $ } from "rhdh-e2e-test-utils/utils";
import path from "path";
const setupScript = path.join(
import.meta.dirname,
"deploy-customization-provider.sh",
);
test.beforeAll(async ({ rhdh }) => {
const project = rhdh.deploymentConfig.namespace;
// 1. Configure RHDH first
await rhdh.configure({ auth: "keycloak" });
// 2. Deploy external service
await $`bash ${setupScript} ${project}`;
// 3. Get service URL and set as env var
process.env.TECH_RADAR_DATA_URL = (
await rhdh.k8sClient.getRouteLocation(
project,
"test-backstage-customization-provider",
)
).replace("http://", "");
// 4. Deploy RHDH (will use TECH_RADAR_DATA_URL from rhdh-secrets.yaml)
await rhdh.deploy();
});2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
See Tech Radar Example for a complete implementation.
Guest Auth Setup
For simpler tests without Keycloak:
test.beforeAll(async ({ rhdh }) => {
await rhdh.configure({ auth: "guest" });
await rhdh.deploy();
});
test.beforeEach(async ({ loginHelper }) => {
await loginHelper.loginAsGuest();
});2
3
4
5
6
7
8
Login Patterns
Keycloak Login
test.beforeEach(async ({ loginHelper }) => {
await loginHelper.loginAsKeycloakUser();
});2
3
Default Keycloak Users
The global setup creates these users automatically:
| Username | Password | Groups |
|---|---|---|
test1 | test1@123 | developers |
test2 | test2@123 | developers |
Custom Credentials
test.beforeEach(async ({ loginHelper }) => {
await loginHelper.loginAsKeycloakUser("user2", "password2");
});2
3
Guest Login
test.beforeEach(async ({ loginHelper }) => {
await loginHelper.loginAsGuest();
});2
3
Writing Tests
UI Navigation
test("Navigate to plugin", async ({ uiHelper }) => {
await uiHelper.openSidebar("Tech Radar");
await uiHelper.verifyHeading("Tech Radar");
});2
3
4
Verify Content
test("Verify content exists", async ({ uiHelper }) => {
await uiHelper.verifyText("Expected text");
await uiHelper.verifyHeading("Expected heading");
});2
3
4
Custom Locators
test("Verify specific element", async ({ page }) => {
const element = page.locator('h2:has-text("Section")');
await expect(element).toBeVisible();
});2
3
4
Helper Functions
Create reusable verification functions:
async function verifyRadarDetails(page: Page, section: string, text: string) {
const sectionLocator = page
.locator(`h2:has-text("${section}")`)
.locator("xpath=ancestor::*")
.locator(`text=${text}`);
await sectionLocator.scrollIntoViewIfNeeded();
await expect(sectionLocator).toBeVisible();
}
test("Verify radar sections", async ({ page }) => {
await verifyRadarDetails(page, "Languages", "JavaScript");
await verifyRadarDetails(page, "Frameworks", "React");
});2
3
4
5
6
7
8
9
10
11
12
13
Real Example: Tech Radar
Complete spec file from the tech-radar workspace:
import { test, expect, Page } from "rhdh-e2e-test-utils/test";
import { $ } from "rhdh-e2e-test-utils/utils";
import path from "path";
const setupScript = path.join(
import.meta.dirname,
"deploy-customization-provider.sh",
);
test.describe("Test tech-radar plugin", () => {
test.beforeAll(async ({ rhdh }) => {
const project = rhdh.deploymentConfig.namespace;
await rhdh.configure({ auth: "keycloak" });
await $`bash ${setupScript} ${project}`;
process.env.TECH_RADAR_DATA_URL = (
await rhdh.k8sClient.getRouteLocation(
project,
"test-backstage-customization-provider",
)
).replace("http://", "");
await rhdh.deploy();
});
test.beforeEach(async ({ loginHelper }) => {
await loginHelper.loginAsKeycloakUser();
});
test("Verify tech-radar", async ({ page, uiHelper }) => {
await uiHelper.openSidebar("Tech Radar");
await uiHelper.verifyHeading("Tech Radar");
await uiHelper.verifyHeading("Company Radar");
await verifyRadarDetails(page, "Languages", "JavaScript");
await verifyRadarDetails(page, "Frameworks", "React");
await verifyRadarDetails(page, "Infrastructure", "GitHub Actions");
});
});
async function verifyRadarDetails(page: Page, section: string, text: string) {
const sectionLocator = page
.locator(`h2:has-text("${section}")`)
.locator("xpath=ancestor::*")
.locator(`text=${text}`);
await sectionLocator.scrollIntoViewIfNeeded();
await expect(sectionLocator).toBeVisible();
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
UIhelper Methods
Common methods from UIhelper:
| Method | Description |
|---|---|
openSidebar(name) | Click sidebar item |
verifyHeading(text) | Verify H1-H6 heading |
verifyText(text) | Verify text is visible |
clickButton(name) | Click button by name |
clickLink(text) | Click link by text |
waitForLoad() | Wait for page load |
See UIhelper API for full reference.
Related Pages
- Directory Layout - File placement
- Configuration Files - YAML setup
- Pre-requisite Services - Deploy dependencies before RHDH
- UIhelper API - Full helper reference