Skip to content

Keycloak Deployment ​

The KeycloakHelper class provides Keycloak deployment and management capabilities for OIDC authentication testing.

Basic Usage ​

typescript
import { KeycloakHelper } from "@red-hat-developer-hub/e2e-test-utils/keycloak";

const keycloak = new KeycloakHelper({
  namespace: "rhdh-keycloak",
});

// Deploy Keycloak
await keycloak.deploy();

// Configure for RHDH
await keycloak.configureForRHDH();

// Access configuration
console.log(`Keycloak URL: ${keycloak.keycloakUrl}`);
console.log(`Realm: ${keycloak.realm}`);
console.log(`Client ID: ${keycloak.clientId}`);

Automatic Deployment via Global Setup ​

When using the default Playwright configuration, Keycloak is automatically deployed in global setup:

  • Namespace: rhdh-keycloak
  • Realm: rhdh
  • Client: rhdh-client
  • Users: test1, test2

To skip automatic deployment:

bash
SKIP_KEYCLOAK_DEPLOYMENT=true yarn playwright test

Deployment Options ​

typescript
type KeycloakDeploymentOptions = {
  namespace?: string;       // Default: "rhdh-keycloak"
  releaseName?: string;     // Default: "keycloak"
  valuesFile?: string;      // Custom Helm values file
  adminUser?: string;       // Default: "admin"
  adminPassword?: string;   // Default: "admin123"
};

Example: Custom Configuration ​

typescript
const keycloak = new KeycloakHelper({
  namespace: "my-keycloak",
  releaseName: "my-kc",
  adminUser: "admin",
  adminPassword: "mySecurePassword",
});

Methods ​

deploy() ​

Deploy Keycloak using the Bitnami Helm chart:

typescript
await keycloak.deploy();

This:

  1. Creates the namespace
  2. Installs the Bitnami Keycloak Helm chart
  3. Waits for the StatefulSet to be ready
  4. Sets keycloakUrl property

configureForRHDH(options?) ​

Configure Keycloak with realm, client, groups, and users for RHDH:

typescript
// Use defaults
await keycloak.configureForRHDH();

// Custom configuration
await keycloak.configureForRHDH({
  realm: "my-realm",
  client: {
    clientId: "my-client",
    clientSecret: "my-secret",
  },
  groups: [{ name: "developers" }, { name: "admins" }],
  users: [
    { username: "user1", password: "pass1", groups: ["developers"] },
    { username: "user2", password: "pass2", groups: ["admins"] },
  ],
});

This also sets environment variables:

  • KEYCLOAK_BASE_URL
  • KEYCLOAK_REALM
  • KEYCLOAK_CLIENT_ID
  • KEYCLOAK_CLIENT_SECRET
  • KEYCLOAK_METADATA_URL
  • KEYCLOAK_LOGIN_REALM

isRunning() ​

Check if Keycloak is accessible:

typescript
const running = await keycloak.isRunning();
if (running) {
  console.log("Keycloak is ready");
}

connect(config) ​

Connect to an existing Keycloak instance:

typescript
// With admin credentials
await keycloak.connect({
  baseUrl: "https://keycloak.example.com",
  username: "admin",
  password: "admin-password",
});

// With client credentials
await keycloak.connect({
  baseUrl: "https://keycloak.example.com",
  realm: "master",
  clientId: "admin-client",
  clientSecret: "client-secret",
});

User Management ​

typescript
// Create user
await keycloak.createUser("rhdh", {
  username: "newuser",
  password: "password123",
  email: "user@example.com",
  groups: ["developers"],
});

// Get all users
const users = await keycloak.getUsers("rhdh");

// Delete user
await keycloak.deleteUser("rhdh", "username");

// Create multiple users
await keycloak.createUsersAndGroups("rhdh", {
  users: [
    { username: "user1", password: "pass1" },
    { username: "user2", password: "pass2", groups: ["developers"] },
  ],
});

// Delete multiple users - by objects or by usernames
await keycloak.deleteUsersAndGroups("rhdh", { users });
await keycloak.deleteUsersAndGroups("rhdh", { users: ["user1", "user2"] });

Group Management ​

typescript
// Create group
await keycloak.createGroup("rhdh", { name: "testers" });

// Get all groups
const groups = await keycloak.getGroups("rhdh");

// Delete group
await keycloak.deleteGroup("rhdh", "testers");

// Create multiple groups
await keycloak.createUsersAndGroups("rhdh", {
  groups: [{ name: "admins" }, { name: "viewers" }],
});

// Delete multiple groups - by objects or by group names
await keycloak.deleteUsersAndGroups("rhdh", { groups });
await keycloak.deleteUsersAndGroups("rhdh", { groups: ["admins", "viewers"] });

Using getUsers and getGroupsOfUser in tests ​

When testing against an existing Keycloak (e.g. RHDH with Keycloak auth), use connect(config) with base URL, realm, and client id/secret (or username/password). Then use getUsers(realm) to get the list of users and getGroupsOfUser(realm, user.username) to get each user's groups for UI assertions (e.g. catalog users page):

typescript
await keycloak.connect({
  baseUrl: process.env.KEYCLOAK_BASE_URL!,
  realm: process.env.KEYCLOAK_REALM!,
  clientId: process.env.KEYCLOAK_CLIENT_ID!,
  clientSecret: process.env.KEYCLOAK_CLIENT_SECRET!,
});

const users = await keycloak.getUsers(realm);
for (const user of users) {
  const groups = await keycloak.getGroupsOfUser(realm, user.username);
  // Assert Backstage UI shows same groups for this user
}

Realm Management ​

typescript
// Create realm
await keycloak.createRealm({
  realm: "new-realm",
  displayName: "New Realm",
  enabled: true,
});

// Delete realm
await keycloak.deleteRealm("new-realm");

Client Management ​

typescript
// Create client
await keycloak.createClient("rhdh", {
  clientId: "new-client",
  clientSecret: "client-secret",
  standardFlowEnabled: true,
  redirectUris: ["https://app.example.com/*"],
});

teardown() ​

Delete the Keycloak namespace:

typescript
await keycloak.teardown();

waitUntilReady(timeout?) ​

Wait for Keycloak to be ready:

typescript
await keycloak.waitUntilReady(500); // default: 500 seconds (~8 minutes)

Properties ​

PropertyTypeDescription
keycloakUrlstringKeycloak instance URL
realmstringConfigured realm name
clientIdstringConfigured client ID
clientSecretstringConfigured client secret
deploymentConfigKeycloakDeploymentConfigCurrent configuration
k8sClientKubernetesClientHelperKubernetes client

Default Configuration ​

Realm ​

SettingValue
Realm namerhdh

Client (rhdh-client) ​

SettingValue
Client secretrhdh-client-secret
Standard flowEnabled
Implicit flowEnabled
Direct access grantsEnabled
Service accountsEnabled
Authorization servicesDisabled

Groups ​

  • developers
  • admins
  • viewers

Users ​

UsernamePasswordDescription
test1test1@123Default test user with standard permissions
test2test2@123Secondary test user for multi-user scenarios

Environment Variables

You can override these defaults using environment variables:

  • KEYCLOAK_USERNAME - Override the default username
  • KEYCLOAK_PASSWORD - Override the default password

Full Example: RHDH + Keycloak Setup ​

typescript
import { test } from "@red-hat-developer-hub/e2e-test-utils/test";
import { KeycloakHelper } from "@red-hat-developer-hub/e2e-test-utils/keycloak";

let keycloak: KeycloakHelper;

test.beforeAll(async ({ rhdh }) => {
  // Deploy Keycloak (or connect to existing)
  keycloak = new KeycloakHelper({ namespace: "rhdh-keycloak" });

  if (!(await keycloak.isRunning())) {
    await keycloak.deploy();
  }

  await keycloak.configureForRHDH();

  // Create additional test users
  await keycloak.createUser("rhdh", {
    username: "admin-user",
    password: "admin123",
    groups: ["admins"],
  });

  // Deploy RHDH with Keycloak auth
  await rhdh.configure({ auth: "keycloak" });
  await rhdh.deploy();
});

test("login as admin", async ({ page, loginHelper }) => {
  await page.goto("/");
  await loginHelper.loginAsKeycloakUser("admin-user", "admin123");
  // ... assertions
});

test.afterAll(async () => {
  // Optional: cleanup
  await keycloak.deleteUser("rhdh", "admin-user");
});

Released under the Apache-2.0 License.