Comprehensive E2E test development guidance for Eclipse Che / Red Hat OpenShift Dev Spaces. Use this skill when writing, modifying, or reviewing TypeScript Mocha Selenium tests, page objects, utilities, or test infrastructure. Provides code style rules, patterns, dependency injection setup, and best practices.
You are a Software Quality Engineer, who is an expert developer for Eclipse Che / Red Hat OpenShift Dev Spaces E2E tests. This skill provides comprehensive guidance for developing and maintaining E2E TypeScript Mocha Selenium tests.
This is the E2E test suite for Eclipse Che / Red Hat OpenShift Dev Spaces. It uses:
suite(), test(), suiteSetup(), suiteTeardown())| Directory | Purpose |
|---|
specs/ | Test specifications organized by category (api/, factory/, dashboard-samples/, miscellaneous/) |
pageobjects/ | Page Object classes for UI elements (dashboard/, ide/, login/, openshift/, git-providers/, webterminal/) |
utils/ | Utilities (DriverHelper, BrowserTabsUtil, Logger, API handlers, KubernetesCommandLineToolsExecutor) |
tests-library/ | Reusable test helpers (WorkspaceHandlingTests, LoginTests, ProjectAndFileTests) |
constants/ | Environment variable mappings (BASE_TEST_CONSTANTS, TIMEOUT_CONSTANTS, FACTORY_TEST_CONSTANTS, etc.) |
configs/ | Mocha config, Inversify container (inversify.config.ts), shell scripts |
suites/ | Test suite configurations for different environments |
driver/ | Chrome driver configuration |
build/dockerfiles/ | Docker image for running tests |
# Install dependencies
npm ci
# Lint and format
npm run lint
npm run prettier
# Build TypeScript only
npm run tsc
# Run all tests (requires environment variables)
export TS_SELENIUM_BASE_URL=<che-url>
export TS_SELENIUM_OCP_USERNAME=<username>
export TS_SELENIUM_OCP_PASSWORD=<password>
npm run test
# Run a single test file (without .spec.ts extension)
export USERSTORY=SmokeTest
npm run test
# Run API-only tests (no browser)
export USERSTORY=EmptyWorkspaceAPI
npm run driver-less-test
# View Allure test report
npm run open-allure-dasboard
Every TypeScript file MUST start with this exact header:
/** *******************************************************************
* copyright (c) 2026 Red Hat, Inc.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
@injectable() decorator on ALL page objects and utilities@inject() decoratorsimport { inject, injectable } from 'inversify';
import 'reflect-metadata';
import { CLASSES } from '../../configs/inversify.types';
@injectable()
export class MyPageObject {
constructor(@inject(CLASSES.DriverHelper) private readonly driverHelper: DriverHelper) {}
}
public keywordLogger.debug() at the START of every public methodasync clickButton(timeout: number = TIMEOUT_CONSTANTS.TS_CLICK_DASHBOARD_ITEM_TIMEOUT): Promise<void> {
Logger.debug();
await this.driverHelper.waitAndClick(MyPage.BUTTON_LOCATOR, timeout);
}
private static readonly fields of type Byprivate methods that return By// Static locators (correct)
private static readonly SUBMIT_BUTTON: By = By.xpath('//button[@type="submit"]');
private static readonly INPUT_FIELD: By = By.id('input-field');
// Dynamic locators (correct)
private getItemLocator(itemName: string): By {
return By.xpath(`//div[text()="${itemName}"]`);
}
// WRONG - Never do this inside a method
async wrongMethod(): Promise<void> {
const locator: By = By.xpath('//button'); // AVOID THIS
}
Classes must follow this order:
Naming
*.spec.ts (e.g., Factory.spec.ts)*API.spec.ts (e.g., EmptyWorkspaceAPI.spec.ts)Mocha TDD Style (Required)
suite(), test(), suiteSetup(), suiteTeardown()suite('My Test Suite', function (): void {
// Inject dependencies inside suite() to avoid unnecessary execution
const dashboard: Dashboard = e2eContainer.get(CLASSES.Dashboard);
const loginTests: LoginTests = e2eContainer.get(CLASSES.LoginTests);
suiteSetup('Login to application', async function (): Promise<void> {
await loginTests.loginIntoChe();
});
test('Verify dashboard is visible', async function (): Promise<void> {
await dashboard.waitPage();
});
suiteTeardown('Cleanup', async function (): Promise<void> {
// cleanup code
});
});
import { e2eContainer } from '../../configs/inversify.config';import { CLASSES, TYPES } from '../../configs/inversify.types';import { e2eContainer } from '../../configs/inversify.config';
import { CLASSES, TYPES } from '../../configs/inversify.types';
suite('Test Suite', function (): void {
const workspaceHandlingTests: WorkspaceHandlingTests = e2eContainer.get(CLASSES.WorkspaceHandlingTests);
const testWorkspaceUtil: ITestWorkspaceUtil = e2eContainer.get(TYPES.WorkspaceUtil);
// ... test implementation
});
// Correct
async function doSomething(param: string, timeout: number): Promise<void> {
const result: string = await someOperation();
}
// Wrong - missing types
async function doSomething(param, timeout) {
const result = await someOperation();
}
Naming Conventions
String Quotes
Comments
// todo and issue number: // todo commented due to issue crw-1010npm run prettier to fix formattingnpm run lint to fix linting issuesCore variables (defined in constants/ directory):
| Variable | Description |
|---|---|
TS_SELENIUM_BASE_URL | Che/DevSpaces dashboard URL |
TS_SELENIUM_OCP_USERNAME | OpenShift username |
TS_SELENIUM_OCP_PASSWORD | OpenShift password |
USERSTORY | Specific test file to run (without .spec.ts) |
TS_PLATFORM | openshift (default) or kubernetes |
TS_SELENIUM_FACTORY_GIT_REPO_URL | Git repo for factory tests |
TS_SELENIUM_VALUE_OPENSHIFT_OAUTH | Enable OAuth (true/false) |
TS_SELENIUM_LOG_LEVEL | Logging level (TRACE, DEBUG, INFO, etc.) |
DELETE_WORKSPACE_ON_SUCCESSFUL_TEST | Delete workspace on success |
pageobjects/ subdirectory@injectable() decoratorconfigs/inversify.config.tsconfigs/inversify.types.ts// 1. Create pageobjects/dashboard/NewPage.ts
@injectable()
export class NewPage {
private static readonly ELEMENT_LOCATOR: By = By.id('element');
constructor(@inject(CLASSES.DriverHelper) private readonly driverHelper: DriverHelper) {}
async waitForElement(): Promise<void> {
Logger.debug();
await this.driverHelper.waitVisibility(NewPage.ELEMENT_LOCATOR);
}
}
// 2. Add to configs/inversify.types.ts
const CLASSES: any = {
// ... existing classes
NewPage: 'NewPage'
};
// 3. Add to configs/inversify.config.ts
import { NewPage } from '../pageobjects/dashboard/NewPage';
e2eContainer.bind<NewPage>(CLASSES.NewPage).to(NewPage);
specs/ subdirectoryexport USERSTORY=<filename-without-extension> && npm run testwaitVisibility(locator, timeout) - Wait for element to be visiblewaitAndClick(locator, timeout) - Wait and click elementwaitAndGetText(locator, timeout) - Wait and get textwaitDisappearance(locator, timeout) - Wait for element to disappearnavigateToUrl(url) - Navigate to URLwait(ms) - Wait for specified millisecondsnavigateTo(url) - Navigate to URLgetCurrentUrl() - Get current URLmaximize() - Maximize browser windowcloseAllTabsExceptCurrent() - Close extra tabsLogger.debug() - Log method name (use at start of public methods)Logger.info(message) - Log info messageLogger.error(message) - Log error messageTriggers on:
tests/e2e/** or the workflow fileSteps performed:
npm run tscnpm run lintWhen modifying tests:
npm run prettier passes locallynpm run tsc compiles without errorsnpm run lint passessuite('Workspace Test', function (): void {
const workspaceHandlingTests: WorkspaceHandlingTests = e2eContainer.get(CLASSES.WorkspaceHandlingTests);
const testWorkspaceUtil: ITestWorkspaceUtil = e2eContainer.get(TYPES.WorkspaceUtil);
const loginTests: LoginTests = e2eContainer.get(CLASSES.LoginTests);
suiteSetup('Login', async function (): Promise<void> {
await loginTests.loginIntoChe();
});
test('Create workspace', async function (): Promise<void> {
await workspaceHandlingTests.createAndStartWorkspace();
});
test('Obtain workspace name', async function (): Promise<void> {
await workspaceHandlingTests.obtainWorkspaceNameFromStartingPage();
});
test('Register running workspace', function (): void {
registerRunningWorkspace(WorkspaceHandlingTests.getWorkspaceName());
});
suiteTeardown('Stop and delete workspace', async function (): Promise<void> {
await testWorkspaceUtil.stopAndDeleteWorkspaceByName(WorkspaceHandlingTests.getWorkspaceName());
});
});
suite('API Test', function (): void {
const kubernetesCommandLineToolsExecutor: KubernetesCommandLineToolsExecutor = e2eContainer.get(
CLASSES.KubernetesCommandLineToolsExecutor
);
suiteSetup('Setup', async function (): Promise<void> {
kubernetesCommandLineToolsExecutor.loginToOcp();
});
test('Execute API operation', function (): void {
const output: ShellString = kubernetesCommandLineToolsExecutor.applyAndWaitDevWorkspace(yaml);
expect(output.stdout).contains('condition met');
});
suiteTeardown('Cleanup', function (): void {
kubernetesCommandLineToolsExecutor.deleteDevWorkspace();
});
});
npm install --save-dev <package>npm installnpm ci for clean install from lock fileTS_SELENIUM_LOG_LEVEL=TRACE for verbose loggingnpm run open-allure-dasboardDriverHelper.wait()TIMEOUT_CONSTANTSWhen helping with E2E test development and maintenance:
Keep command files up to date - When test infrastructure changes (new environment variables, test patterns, or execution methods), update the command file .claude/commands/run-e2e-test.md.
Review test-specific parameters - When adding or modifying tests that require new environment variables, update the "Test-Specific Parameters" section in the command files.
Validate examples - Ensure code examples and commands in documentation match current implementation patterns.
Check for deprecated patterns - When refactoring, search for outdated patterns across both test code and documentation.
Update inversify configuration - When adding new page objects or utilities, ensure both inversify.types.ts and inversify.config.ts are updated.
/run-e2e-test - Unified E2E Test RunnerRun E2E tests against Eclipse Che or Red Hat OpenShift Dev Spaces using either a local Chrome browser or Podman container.
Usage: /run-e2e-test [URL USERNAME PASSWORD [TESTNAME] [METHOD]]
Features:
origin/main and rebuilds only when neededExamples:
/run-e2e-test - Interactive mode (recommended)/run-e2e-test https://devspaces.apps.example.com/ admin password SmokeTest npm/run-e2e-test https://che.apps.example.com/ admin password EmptyWorkspace podmanRun methods:
npm - Local Chrome browser (faster, see browser in real-time)podman - Isolated container with VNC support at localhost:5920