Comprehensive E2E test development guidance for Eclipse Che / Red Hat OpenShift Dev Spaces...
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