{"owner":"eclipse-che","repo":"che","hasSkills":true,"totalSkillsCount":3,"totalTokensCount":11078,"categories":["anthropic-skill","command-prompt"],"hasMcp":false,"mcpConfig":null,"found":["tests/e2e/.claude/skills/e2e-test-developer/SKILL.md","tests/e2e/.claude/commands/run-e2e-test.md","tests/e2e/.claude/skills/explore-and-generate-tests/SKILL.md"],"skills":{"tests/e2e/.claude/skills/e2e-test-developer/SKILL.md":"---\nname: e2e-test-developer\ndescription: 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.\n---\n\n# Eclipse Che E2E TypeScript Mocha Selenium Test Development Skill\n\nYou 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.\n\n## Project Overview\n\nThis is the E2E test suite for Eclipse Che / Red Hat OpenShift Dev Spaces. It uses:\n\n- **Selenium WebDriver** with Chrome browser\n- **Mocha** (TDD style - `suite()`, `test()`, `suiteSetup()`, `suiteTeardown()`)\n- **TypeScript** with strict type checking\n- **Inversify** for dependency injection\n- **Chai** for assertions\n- **Allure** for test reporting\n\n## Directory Structure\n\n| Directory            | Purpose                                                                                                  |\n| -------------------- | -------------------------------------------------------------------------------------------------------- |\n| `specs/`             | Test specifications organized by category (api/, factory/, dashboard-samples/, miscellaneous/)           |\n| `pageobjects/`       | Page Object classes for UI elements (dashboard/, ide/, login/, openshift/, git-providers/, webterminal/) |\n| `utils/`             | Utilities (DriverHelper, BrowserTabsUtil, Logger, API handlers, KubernetesCommandLineToolsExecutor)      |\n| `tests-library/`     | Reusable test helpers (WorkspaceHandlingTests, LoginTests, ProjectAndFileTests)                          |\n| `constants/`         | Environment variable mappings (BASE_TEST_CONSTANTS, TIMEOUT_CONSTANTS, FACTORY_TEST_CONSTANTS, etc.)     |\n| `configs/`           | Mocha config, Inversify container (inversify.config.ts), shell scripts                                   |\n| `suites/`            | Test suite configurations for different environments                                                     |\n| `driver/`            | Chrome driver configuration                                                                              |\n| `build/dockerfiles/` | Docker image for running tests                                                                           |\n\n## Essential Commands\n\n```bash\n# Install dependencies\nnpm ci\n\n# Lint and format\nnpm run lint\nnpm run prettier\n\n# Build TypeScript only\nnpm run tsc\n\n# Run all tests (requires environment variables)\nexport TS_SELENIUM_BASE_URL=<che-url>\nexport TS_SELENIUM_OCP_USERNAME=<username>\nexport TS_SELENIUM_OCP_PASSWORD=<password>\nnpm run test\n\n# Run a single test file (without .spec.ts extension)\nexport USERSTORY=SmokeTest\nnpm run test\n\n# Run API-only tests (no browser)\nexport USERSTORY=EmptyWorkspaceAPI\nnpm run driver-less-test\n\n# View Allure test report\nnpm run open-allure-dasboard\n```\n\n## CODE STYLE REQUIREMENTS (CRITICAL)\n\n### File Header (Required for ALL .ts files)\n\nEvery TypeScript file MUST start with this exact header:\n\n```typescript\n/** *******************************************************************\n * copyright (c) 2026 Red Hat, Inc.\n *\n * This program and the accompanying materials are made\n * available under the terms of the Eclipse Public License 2.0\n * which is available at https://www.eclipse.org/legal/epl-2.0/\n *\n * SPDX-License-Identifier: EPL-2.0\n **********************************************************************/\n```\n\n### Page Object and Utility Classes\n\n1. **Class Declaration with Dependency Injection**\n    - Use `@injectable()` decorator on ALL page objects and utilities\n    - Use constructor injection with `@inject()` decorators\n\n```typescript\nimport { inject, injectable } from 'inversify';\nimport 'reflect-metadata';\nimport { CLASSES } from '../../configs/inversify.types';\n\n@injectable()\nexport class MyPageObject {\n\tconstructor(@inject(CLASSES.DriverHelper) private readonly driverHelper: DriverHelper) {}\n}\n```\n\n2. **Public Methods**\n    - Declare public methods WITHOUT the `public` keyword\n    - Add `Logger.debug()` at the START of every public method\n    - Always specify explicit return types\n\n```typescript\nasync clickButton(timeout: number = TIMEOUT_CONSTANTS.TS_CLICK_DASHBOARD_ITEM_TIMEOUT): Promise<void> {\n    Logger.debug();\n    await this.driverHelper.waitAndClick(MyPage.BUTTON_LOCATOR, timeout);\n}\n```\n\n3. **Locators**\n    - Static locators: `private static readonly` fields of type `By`\n    - Dynamic locators: `private` methods that return `By`\n    - NEVER declare locators as constants inside methods\n\n```typescript\n// Static locators (correct)\nprivate static readonly SUBMIT_BUTTON: By = By.xpath('//button[@type=\"submit\"]');\nprivate static readonly INPUT_FIELD: By = By.id('input-field');\n\n// Dynamic locators (correct)\nprivate getItemLocator(itemName: string): By {\n    return By.xpath(`//div[text()=\"${itemName}\"]`);\n}\n\n// WRONG - Never do this inside a method\nasync wrongMethod(): Promise<void> {\n    const locator: By = By.xpath('//button'); // AVOID THIS\n}\n```\n\n### Member Ordering (Enforced by ESLint)\n\nClasses must follow this order:\n\n1. Static fields\n2. Public fields\n3. Instance fields\n4. Protected fields\n5. Private fields\n6. Abstract fields\n7. Constructor\n8. Public static methods\n9. Protected static methods\n10. Private static methods\n11. Public methods\n12. Protected methods\n13. Private methods\n\n### Test File Conventions\n\n1. **Naming**\n\n    - UI tests: `*.spec.ts` (e.g., `Factory.spec.ts`)\n    - API-only tests: `*API.spec.ts` (e.g., `EmptyWorkspaceAPI.spec.ts`)\n\n2. **Mocha TDD Style (Required)**\n    - Use `suite()`, `test()`, `suiteSetup()`, `suiteTeardown()`\n    - NEVER use arrow functions in test declarations (Mocha context issue)\n\n```typescript\nsuite('My Test Suite', function (): void {\n\t// Inject dependencies inside suite() to avoid unnecessary execution\n\tconst dashboard: Dashboard = e2eContainer.get(CLASSES.Dashboard);\n\tconst loginTests: LoginTests = e2eContainer.get(CLASSES.LoginTests);\n\n\tsuiteSetup('Login to application', async function (): Promise<void> {\n\t\tawait loginTests.loginIntoChe();\n\t});\n\n\ttest('Verify dashboard is visible', async function (): Promise<void> {\n\t\tawait dashboard.waitPage();\n\t});\n\n\tsuiteTeardown('Cleanup', async function (): Promise<void> {\n\t\t// cleanup code\n\t});\n});\n```\n\n3. **Dependency Injection in Tests**\n    - Import container: `import { e2eContainer } from '../../configs/inversify.config';`\n    - Import types: `import { CLASSES, TYPES } from '../../configs/inversify.types';`\n    - Get instances inside suite() function\n\n```typescript\nimport { e2eContainer } from '../../configs/inversify.config';\nimport { CLASSES, TYPES } from '../../configs/inversify.types';\n\nsuite('Test Suite', function (): void {\n\tconst workspaceHandlingTests: WorkspaceHandlingTests = e2eContainer.get(CLASSES.WorkspaceHandlingTests);\n\tconst testWorkspaceUtil: ITestWorkspaceUtil = e2eContainer.get(TYPES.WorkspaceUtil);\n\t// ... test implementation\n});\n```\n\n### TypeScript Requirements\n\n1. **Explicit Type Annotations Required**\n    - All parameters must have type annotations\n    - All property declarations must have type annotations\n    - All variable declarations must have type annotations\n    - All functions must have explicit return types\n\n```typescript\n// Correct\nasync function doSomething(param: string, timeout: number): Promise<void> {\n\tconst result: string = await someOperation();\n}\n\n// Wrong - missing types\nasync function doSomething(param, timeout) {\n\tconst result = await someOperation();\n}\n```\n\n2. **Naming Conventions**\n\n    - Variables: camelCase or UPPER_CASE\n    - No leading/trailing underscores\n\n3. **String Quotes**\n\n    - Use single quotes for strings\n\n4. **Comments**\n    - Comments must start with lowercase (capitalized-comments: never)\n    - Mark workarounds with `// todo` and issue number: `// todo commented due to issue crw-1010`\n\n### Prettier and ESLint\n\n- Pre-commit hooks run automatically via Husky\n- Run `npm run prettier` to fix formatting\n- Run `npm run lint` to fix linting issues\n\n## Environment Variables\n\nCore variables (defined in `constants/` directory):\n\n| Variable                              | Description                                  |\n| ------------------------------------- | -------------------------------------------- |\n| `TS_SELENIUM_BASE_URL`                | Che/DevSpaces dashboard URL                  |\n| `TS_SELENIUM_OCP_USERNAME`            | OpenShift username                           |\n| `TS_SELENIUM_OCP_PASSWORD`            | OpenShift password                           |\n| `USERSTORY`                           | Specific test file to run (without .spec.ts) |\n| `TS_PLATFORM`                         | `openshift` (default) or `kubernetes`        |\n| `TS_SELENIUM_FACTORY_GIT_REPO_URL`    | Git repo for factory tests                   |\n| `TS_SELENIUM_VALUE_OPENSHIFT_OAUTH`   | Enable OAuth (true/false)                    |\n| `TS_SELENIUM_LOG_LEVEL`               | Logging level (TRACE, DEBUG, INFO, etc.)     |\n| `DELETE_WORKSPACE_ON_SUCCESSFUL_TEST` | Delete workspace on success                  |\n\n## Adding New Page Objects\n\n1. Create file in appropriate `pageobjects/` subdirectory\n2. Add `@injectable()` decorator\n3. Register in `configs/inversify.config.ts`\n4. Add class identifier in `configs/inversify.types.ts`\n\n```typescript\n// 1. Create pageobjects/dashboard/NewPage.ts\n@injectable()\nexport class NewPage {\n\tprivate static readonly ELEMENT_LOCATOR: By = By.id('element');\n\n\tconstructor(@inject(CLASSES.DriverHelper) private readonly driverHelper: DriverHelper) {}\n\n\tasync waitForElement(): Promise<void> {\n\t\tLogger.debug();\n\t\tawait this.driverHelper.waitVisibility(NewPage.ELEMENT_LOCATOR);\n\t}\n}\n\n// 2. Add to configs/inversify.types.ts\nconst CLASSES: any = {\n\t// ... existing classes\n\tNewPage: 'NewPage'\n};\n\n// 3. Add to configs/inversify.config.ts\nimport { NewPage } from '../pageobjects/dashboard/NewPage';\ne2eContainer.bind<NewPage>(CLASSES.NewPage).to(NewPage);\n```\n\n## Adding New Tests\n\n1. Create file in appropriate `specs/` subdirectory\n2. Follow naming convention (*.spec.ts for UI, *API.spec.ts for API)\n3. Use TDD style (suite, test, suiteSetup, suiteTeardown)\n4. Run with `export USERSTORY=<filename-without-extension> && npm run test`\n\n## Common Utilities\n\n### DriverHelper (utils/DriverHelper.ts)\n\n- `waitVisibility(locator, timeout)` - Wait for element to be visible\n- `waitAndClick(locator, timeout)` - Wait and click element\n- `waitAndGetText(locator, timeout)` - Wait and get text\n- `waitDisappearance(locator, timeout)` - Wait for element to disappear\n- `navigateToUrl(url)` - Navigate to URL\n- `wait(ms)` - Wait for specified milliseconds\n\n### BrowserTabsUtil (utils/BrowserTabsUtil.ts)\n\n- `navigateTo(url)` - Navigate to URL\n- `getCurrentUrl()` - Get current URL\n- `maximize()` - Maximize browser window\n- `closeAllTabsExceptCurrent()` - Close extra tabs\n\n### Logger (utils/Logger.ts)\n\n- `Logger.debug()` - Log method name (use at start of public methods)\n- `Logger.info(message)` - Log info message\n- `Logger.error(message)` - Log error message\n\n## GitHub Actions Maintenance\n\n### PR Check Workflow (.github/workflows/pr-check.yml)\n\nTriggers on:\n\n- Pull requests to main or 7.\\*\\*.x branches\n- Changes in `tests/e2e/**` or the workflow file\n\nSteps performed:\n\n1. Prettier check - Fails if formatting issues found\n2. TypeScript compilation - `npm run tsc`\n3. ESLint check - `npm run lint`\n4. Deploy Che on minikube\n5. Run Empty Workspace API test\n6. Build E2E Docker image\n7. Run Empty Workspace UI test\n\nWhen modifying tests:\n\n- Ensure `npm run prettier` passes locally\n- Ensure `npm run tsc` compiles without errors\n- Ensure `npm run lint` passes\n- Test locally if possible before pushing\n\n## Test Patterns\n\n### Workspace Lifecycle Pattern\n\n```typescript\nsuite('Workspace Test', function (): void {\n\tconst workspaceHandlingTests: WorkspaceHandlingTests = e2eContainer.get(CLASSES.WorkspaceHandlingTests);\n\tconst testWorkspaceUtil: ITestWorkspaceUtil = e2eContainer.get(TYPES.WorkspaceUtil);\n\tconst loginTests: LoginTests = e2eContainer.get(CLASSES.LoginTests);\n\n\tsuiteSetup('Login', async function (): Promise<void> {\n\t\tawait loginTests.loginIntoChe();\n\t});\n\n\ttest('Create workspace', async function (): Promise<void> {\n\t\tawait workspaceHandlingTests.createAndStartWorkspace();\n\t});\n\n\ttest('Obtain workspace name', async function (): Promise<void> {\n\t\tawait workspaceHandlingTests.obtainWorkspaceNameFromStartingPage();\n\t});\n\n\ttest('Register running workspace', function (): void {\n\t\tregisterRunningWorkspace(WorkspaceHandlingTests.getWorkspaceName());\n\t});\n\n\tsuiteTeardown('Stop and delete workspace', async function (): Promise<void> {\n\t\tawait testWorkspaceUtil.stopAndDeleteWorkspaceByName(WorkspaceHandlingTests.getWorkspaceName());\n\t});\n});\n```\n\n### API Test Pattern (No Browser)\n\n```typescript\nsuite('API Test', function (): void {\n\tconst kubernetesCommandLineToolsExecutor: KubernetesCommandLineToolsExecutor = e2eContainer.get(\n\t\tCLASSES.KubernetesCommandLineToolsExecutor\n\t);\n\n\tsuiteSetup('Setup', async function (): Promise<void> {\n\t\tkubernetesCommandLineToolsExecutor.loginToOcp();\n\t});\n\n\ttest('Execute API operation', function (): void {\n\t\tconst output: ShellString = kubernetesCommandLineToolsExecutor.applyAndWaitDevWorkspace(yaml);\n\t\texpect(output.stdout).contains('condition met');\n\t});\n\n\tsuiteTeardown('Cleanup', function (): void {\n\t\tkubernetesCommandLineToolsExecutor.deleteDevWorkspace();\n\t});\n});\n```\n\n## Package Management\n\n- Add packages as **dev dependencies**: `npm install --save-dev <package>`\n- After any changes to package.json, regenerate lock file: `npm install`\n- Run `npm ci` for clean install from lock file\n\n## Debugging\n\n- Set `TS_SELENIUM_LOG_LEVEL=TRACE` for verbose logging\n- Use VNC on port 5920 when running in Docker\n- View test results with `npm run open-allure-dasboard`\n- Screenshots are captured on test failures by CheReporter\n\n## Common Issues and Solutions\n\n1. **Flaky Tests**: Increase timeout or add explicit waits with `DriverHelper.wait()`\n2. **Element Not Found**: Verify locator with browser dev tools, check for dynamic loading\n3. **Stale Element**: Re-fetch element after page navigation\n4. **Timeout Errors**: Use appropriate timeout constants from `TIMEOUT_CONSTANTS`\n\n## Maintenance Guidelines\n\nWhen helping with E2E test development and maintenance:\n\n1. **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`.\n\n2. **Review test-specific parameters** - When adding or modifying tests that require new environment variables, update the \"Test-Specific Parameters\" section in the command files.\n\n3. **Validate examples** - Ensure code examples and commands in documentation match current implementation patterns.\n\n4. **Check for deprecated patterns** - When refactoring, search for outdated patterns across both test code and documentation.\n\n5. **Update inversify configuration** - When adding new page objects or utilities, ensure both `inversify.types.ts` and `inversify.config.ts` are updated.\n\n## Related Commands\n\n### `/run-e2e-test` - Unified E2E Test Runner\n\nRun E2E tests against **Eclipse Che** or **Red Hat OpenShift Dev Spaces** using either a **local Chrome browser** or **Podman container**.\n\n**Usage:** `/run-e2e-test [URL USERNAME PASSWORD [TESTNAME] [METHOD]]`\n\n**Features:**\n\n- **Interactive mode**: Run without arguments to be prompted for all parameters\n- **Auto-detect platform**: Determines Eclipse Che vs Dev Spaces from URL\n- **Smart rebuild**: Detects local code changes vs `origin/main` and rebuilds only when needed\n- **Test-specific parameters**: Prompts for additional env vars based on selected test type\n\n**Examples:**\n\n- `/run-e2e-test` - Interactive mode (recommended)\n- `/run-e2e-test https://devspaces.apps.example.com/ admin password SmokeTest npm`\n- `/run-e2e-test https://che.apps.example.com/ admin password EmptyWorkspace podman`\n\n**Run methods:**\n\n- `npm` - Local Chrome browser (faster, see browser in real-time)\n- `podman` - Isolated container with VNC support at `localhost:5920`\n","tests/e2e/.claude/commands/run-e2e-test.md":"# Run E2E Tests - Unified Command\n\nRun E2E tests against **Eclipse Che** or **Red Hat OpenShift Dev Spaces** using either a **local Chrome browser** or **Podman container**.\n\n## Arguments: $ARGUMENTS\n\n**Usage:** `/run-e2e-test [URL USERNAME PASSWORD [TESTNAME]]`\n\n**Examples:**\n\n- `/run-e2e-test` - Interactive mode (recommended)\n- `/run-e2e-test https://devspaces.apps.cluster.example.com/ admin mypassword SmokeTest npm`\n- `/run-e2e-test https://che.apps.cluster.example.com/ admin mypassword EmptyWorkspace podman`\n\n---\n\n## Execution Flow\n\n1. **Parse arguments** - Extract parameters from `$ARGUMENTS`\n2. **Prompt if missing** - Collect missing values via AskUserQuestion\n3. **Auto-detect platform** - Determine Eclipse Che vs Dev Spaces from URL\n4. **Detect local changes** - Check if rebuild is needed based on git status\n5. **Detect test-specific parameters** - Read spec file for required env vars\n6. **Generate bash commands** - Build appropriate bash commands\n7. **Execute** - Run the test after user confirmation\n\n---\n\n## ⚠️ MANDATORY RULES\n\n1. **Never print bash commands with `&& \\` or multi-line continuations using `\\`** - Commands using line continuation characters (`\\`) or chained with `&& \\` cannot be copy-pasted properly from the terminal. Instead, present commands as separate lines or use semicolons for simple chains.\n\n    ❌ **Wrong (line continuations):**\n\n    ```bash\n    cd /path/to/dir && \\\n    npm ci && \\\n    npm run test\n    ```\n\n    ❌ **Wrong (backslash continuations):**\n\n    ```bash\n    podman run -it \\\n      --shm-size=2g \\\n      -p 5920:5920 \\\n      quay.io/eclipse/che-e2e:next\n    ```\n\n    ✅ **Correct (separate lines):**\n\n    ```bash\n    cd /path/to/dir\n    npm ci\n    npm run test\n    ```\n\n    ✅ **Correct (single line for podman):**\n\n    ```bash\n    podman run -it --shm-size=2g -p 5920:5920 quay.io/eclipse/che-e2e:next\n    ```\n\n---\n\n## Step 1: Parse Arguments\n\nParse `$ARGUMENTS` to extract positional parameters:\n\n```\n$ARGUMENTS = \"URL USERNAME PASSWORD [TESTNAME]\"\n```\n\n**Parsing logic:**\n\n1. Split `$ARGUMENTS` by whitespace\n2. Assign to variables: `URL`, `USERNAME`, `PASSWORD`, `TESTNAME`, `RUN_METHOD`\n3. If `TESTNAME` is not provided, default to `EmptyWorkspace`\n\n**Example:**\n\n```\nInput: \"https://devspaces.apps.example.com/ admin secret123 SmokeTest npm\"\nResult:\n  - URL = \"https://devspaces.apps.example.com/\"\n  - USERNAME = \"admin\"\n  - PASSWORD = \"secret123\"\n  - TESTNAME = \"SmokeTest\"\n  - RUN_METHOD = \"npm\"\n```\n\n---\n\n## Step 2: Prompt for Missing Values\n\nIf `$ARGUMENTS` is empty or incomplete, use **AskUserQuestion** to collect missing parameters.\n\n### Question 1 - Run Method (header: \"Method\")\n\n- \"How do you want to run the test?\"\n- Options:\n    - Option 1: \"npm run in local browser (Recommended)\" - \"Uses local Chrome and Node.js - faster, see browser in real-time\"\n    - Option 2: \"Podman container\" - \"Isolated environment with VNC support at localhost:5920\"\n\n### Question 2 - Execution Mode (header: \"Build\")\n\n- \"How should local changes be handled?\"\n- Options:\n    - Option 1: \"Build and run (Recommended)\" - \"Compare to origin/main, rebuild if any changes exist\"\n    - Option 2: \"Just run\" - \"Skip build, assume code is already compiled\"\n\n### Question 3 - Test Name (header: \"Test\")\n\n- \"Which test do you want to run?\"\n- Options:\n    - Option 1: \"EmptyWorkspace (Recommended)\" - \"Basic workspace creation test\"\n    - Option 2: \"SmokeTest\" - \"Quick validation of core functionality\"\n    - Option 3: \"Factory\" - \"Factory URL workspace creation with git operations\"\n    - Option 4: \"EmptyWorkspaceAPI\" - \"API-only test (no browser needed)\"\n\n### For URL - Ask in plain text:\n\n> \"What is the dashboard URL? (e.g., https://devspaces.apps.cluster.example.com/ or https://che.apps.cluster.example.com/)\"\n\n### For Username and Password - Ask in plain text:\n\n> \"Please provide your credentials:\n>\n> - **Username**: (e.g., admin, user)\n> - **Password**: your password\"\n\nWait for the user's response before proceeding.\n\n---\n\n## Step 3: Auto-detect Testing Product from URL\n\nAnalyze the URL to determine which product is being tested and set appropriate defaults:\n\n| URL Pattern            | Product                          | Auto-configured Settings                                                                                                                                          |\n| ---------------------- | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| Contains `devspaces`   | **Red Hat OpenShift Dev Spaces** | `TS_SELENIUM_VALUE_OPENSHIFT_OAUTH=true`, `TS_OCP_LOGIN_PAGE_PROVIDER_TITLE=htpasswd` `TS_PLATFORM=openshift`                                                     |\n| Contains `eclipse-che` | **Eclipse Che**                  | May need OAuth config based on deployment. I host contains `crw-qe.com` - it is `TS_PLATFORM=openshift`. Otherwise ask user for platform: openshift or kubernetes |\n\n**Detection logic:**\n\n```bash\nif [[ \"$URL\" == *\"devspaces\"* ]]; then\n  PRODUCT=\"devspaces\"\n  TS_SELENIUM_VALUE_OPENSHIFT_OAUTH=true\n  TS_OCP_LOGIN_PAGE_PROVIDER_TITLE=\"htpasswd\"\nelif [[ \"$URL\" == *\"eclipse-che\"* ]]; then\n  PRODUCT=\"che\"\nfi\n```\n\n---\n\n## Step 4: Detect Local Changes\n\nCheck the git status to determine if code needs to be rebuilt before running tests.\n\n**Important:** Ignore changes to documentation and Claude configuration files:\n\n- `CLAUDE.md`\n- `.claude/**/*`\n- `*.md` files (documentation)\n\n### For Local Browser Method:\n\n**Smart rebuild based on all changes vs origin/main** - rebuild when there are any test code modifications compared to origin/main.\n\n**Logic:**\n\n1. Compare working tree to `origin/main` to detect all changes (committed, staged, and unstaged)\n2. Rebuild if any test code changes exist compared to `origin/main` (excluding docs/config files)\n\n```bash\nNEEDS_REBUILD=false\n\n# Get current branch name for informational purposes\nCURRENT_BRANCH=$(git branch --show-current)\necho \"Current branch: $CURRENT_BRANCH\"\n\n# Check for any changes compared to origin/main (committed, staged, and unstaged)\n# Excludes docs and Claude config files\nCODE_CHANGES=$(git diff --name-only origin/main -- . 2>/dev/null | grep -v -E '(CLAUDE\\.md|\\.claude/|README\\.md|\\.md$)')\n\nif [[ -n \"$CODE_CHANGES\" ]]; then\n  echo \"Code changes detected compared to origin/main:\"\n  echo \"$CODE_CHANGES\"\n  NEEDS_REBUILD=true\nelse\n  echo \"No test code changes compared to origin/main - no rebuild needed\"\nfi\n\necho \"Rebuild needed: $NEEDS_REBUILD\"\n```\n\n### For Podman Container Method:\n\n**Smart mounting based on all changes vs origin/main** - mount full directory when there are any test code modifications compared to origin/main.\n\n**Logic:**\n\n1. Compare working tree to `origin/main` to detect all changes (committed, staged, and unstaged)\n2. Mount full directory if any test code changes exist compared to `origin/main` (excluding docs/config files)\n\n```bash\nE2E_IMAGE=\"quay.io/eclipse/che-e2e:next\"\nMOUNT_FULL_CODE=false\n\n# Get current branch name for informational purposes\nCURRENT_BRANCH=$(git branch --show-current)\necho \"Current branch: $CURRENT_BRANCH\"\n\n# Check for any changes compared to origin/main (committed, staged, and unstaged)\n# Excludes docs and Claude config files\nCODE_CHANGES=$(git diff --name-only origin/main -- . 2>/dev/null | grep -v -E '(CLAUDE\\.md|\\.claude/|README\\.md|\\.md$)')\n\nif [[ -n \"$CODE_CHANGES\" ]]; then\n  echo \"Code changes detected compared to origin/main:\"\n  echo \"$CODE_CHANGES\"\n  MOUNT_FULL_CODE=true\n  PODMAN_MOUNT=\"-v $(pwd):/tmp/e2e:Z\"\nelse\n  echo \"No test code changes compared to origin/main - using published image code\"\n  PODMAN_MOUNT=\"-v $(pwd)/report:/tmp/e2e/report:Z\"\nfi\n\necho \"Mount strategy: $PODMAN_MOUNT\"\n```\n\n---\n\n## Step 5: Detect Test-Specific Parameters\n\nAfter getting the test name, read the test spec file to identify additional required environment variables.\n\n### Find and read the spec file:\n\n```bash\n# Find the test file\nSPEC_FILE=$(find specs -name \"${USERSTORY}.spec.ts\" 2>/dev/null | head -1)\n\nif [[ -n \"$SPEC_FILE\" ]]; then\n  echo \"Found test file: $SPEC_FILE\"\n  # Read the file to identify required environment variables\n  cat \"$SPEC_FILE\"\nfi\n```\n\n### Platform-Specific Configurations\n\n#### OpenShift\n\n```bash\nexport TS_PLATFORM=openshift\nexport TS_SELENIUM_VALUE_OPENSHIFT_OAUTH=true\nexport TS_OCP_LOGIN_PAGE_PROVIDER_TITLE=\"htpasswd\"\n```\n\n#### Kubernetes\n\n```bash\nexport TS_PLATFORM=kubernetes\nexport TS_SELENIUM_K8S_USERNAME=che@eclipse.org\nexport TS_SELENIUM_K8S_PASSWORD=admin\nexport TS_SELENIUM_VALUE_OPENSHIFT_OAUTH=false\n```\n\n### Test-specific parameters by category:\n\n#### Factory URL Tests (Factory, RefusedOAuthFactory, NoSetupRepoFactory, etc.)\n\nThese tests navigate directly to a factory URL to create a workspace.\n**Ask for these additional parameters:**\n\n- **Git Repository URL** (`TS_SELENIUM_FACTORY_GIT_REPO_URL`) - Repository to clone\n- **Git Provider** (`TS_SELENIUM_FACTORY_GIT_PROVIDER`) - `github`, `gitlab`, `bitbucket`, `azure-devops`\n- **OAuth Enabled** (`TS_SELENIUM_GIT_PROVIDER_OAUTH`) - `true` or `false`\n\n#### Git Repo Import Form Tests (FactoryWithGitRepoOptions, CreateWorkspaceWithExistingNameFromGitUrl, etc.)\n\nThese tests use the Dashboard's \"Create Workspace\" form.\n**Ask for these additional parameters:**\n\n- **Git Repository URL** (`TS_SELENIUM_FACTORY_GIT_REPO_URL`) - Repository URL\n- **Branch Name** (`TS_SELENIUM_FACTORY_GIT_REPO_BRANCH`) - Optional (default: `main`)\n- **Project Name** (`TS_SELENIUM_PROJECT_NAME`) - Optional\n\n#### API Tests (EmptyWorkspaceAPI, ContainerOverridesAPI, etc.)\n\n**Ask for these additional parameters:**\n\n- **Namespace** (`TS_API_TEST_NAMESPACE`) - Optional\n- **UDI Image** (`TS_API_TEST_UDI_IMAGE`) - Optional\n\n#### Sample/Devfile Tests\n\n**Ask for these additional parameters:**\n\n- **Sample List** (`TS_SAMPLE_LIST`) - e.g., `Node.js Express`\n\n#### SSH/PAT Tests (SshUrlNoOauthPatFactory, etc.)\n\n**Ask for these additional parameters:**\n\n- **SSH Private Key Path** or **Personal Access Token** details\n\n---\n\n## Step 6: Generate Bash Commands\n\nBased on all collected parameters, generate the appropriate bash commands.\n\n### Option A: Local Browser Commands\n\n#### Browser/ChromeDriver Compatibility Check\n\nBefore running tests, verify ChromeDriver compatibility with local Chrome browser:\n\n```bash\n# Get local Chrome version\nCHROME_VERSION=$(google-chrome --version 2>/dev/null | grep -oP '\\d+' | head -1)\necho \"Local Chrome major version: $CHROME_VERSION\"\n\n# Get ChromeDriver version from package.json\nCHROMEDRIVER_VERSION=$(npm list chromedriver --depth=0 2>/dev/null | grep -oP 'chromedriver@\\K[\\d.]+')\necho \"ChromeDriver version in package.json: $CHROMEDRIVER_VERSION\"\n\n# If versions don't match, suggest installing compatible driver\nif [[ \"${CHROMEDRIVER_VERSION%%.*}\" != \"$CHROME_VERSION\" ]]; then\n  echo \"WARNING: ChromeDriver version mismatch!\"\n  echo \"Install compatible version: npm install chromedriver@$CHROME_VERSION\"\nfi\n```\n\n**If ChromeDriver is incompatible**, install a matching version:\n\n```bash\nnpm install chromedriver@<chrome-major-version>\n```\n\n#### Full Test Run (Build and Run)\n\n```bash\ncd tests/e2e\n\n# Use correct Node.js version\nnvm use v22.10.0\n\n# Get current branch name for informational purposes\nCURRENT_BRANCH=$(git branch --show-current)\necho \"Current branch: $CURRENT_BRANCH\"\n\n# Check for any changes compared to origin/main (committed, staged, and unstaged)\n# Excludes docs and Claude config files\nCODE_CHANGES=$(git diff --name-only origin/main -- . 2>/dev/null | grep -v -E '(CLAUDE\\.md|\\.claude/|README\\.md|\\.md$)')\n\nif [[ -n \"$CODE_CHANGES\" ]]; then\n  echo \"Code changes detected compared to origin/main:\"\n  echo \"$CODE_CHANGES\"\n  NEEDS_REBUILD=true\nelse\n  echo \"No test code changes compared to origin/main - skipping rebuild\"\n  NEEDS_REBUILD=false\nfi\n\n# Check if package.json was modified compared to origin/main\nPACKAGE_JSON_CHANGED=$(git diff --name-only origin/main -- package.json 2>/dev/null)\n\n# Rebuild if changes detected compared to origin/main\nif [[ \"$NEEDS_REBUILD\" == \"true\" ]]; then\n  echo \"Rebuilding TypeScript...\"\n  rm -rf node_modules dist\n\n  # Use npm install if package.json changed, otherwise npm ci\n  if [[ -n \"$PACKAGE_JSON_CHANGED\" ]]; then\n    echo \"package.json modified - using npm install to update package-lock.json\"\n    npm install\n  else\n    npm ci\n  fi\n\n  npm run lint\n  npm run prettier\n  npm run tsc\nfi\n\n# Set environment variables\nexport TS_SELENIUM_BASE_URL=\"<URL>\"\nexport TS_SELENIUM_OCP_USERNAME=\"<USERNAME>\"\nexport TS_SELENIUM_OCP_PASSWORD=\"<PASSWORD>\"\nexport USERSTORY=\"<TESTNAME>\"\nexport TS_SELENIUM_VALUE_OPENSHIFT_OAUTH=true\nexport NODE_TLS_REJECT_UNAUTHORIZED=0\n\n# Run the test\nnpm run test\n```\n\n#### API-Only Tests (No Browser)\n\n```bash\nexport USERSTORY=EmptyWorkspaceAPI\nnpm run driver-less-test\n```\n\n### Option B: Podman Container Commands\n\n**Important:** Use `--env-host` to pass all exported environment variables from the host to the container.\n\n**Smart mounting logic based on changes vs origin/main:**\n\n- **Changes compared to origin/main**: Mount full directory `-v $(pwd):/tmp/e2e:Z` to test local code\n- **No changes compared to origin/main**: Mount only report directory `-v $(pwd)/report:/tmp/e2e/report:Z`\n\n#### With Test Code Changes vs origin/main (mount full directory):\n\nUse this when there are test code changes compared to `origin/main` (works for feature branches or local main with unpushed changes).\n\n```bash\ncd tests/e2e\n\n# Set environment variables\nexport NODE_TLS_REJECT_UNAUTHORIZED=0\nexport TS_SELENIUM_BASE_URL=<URL>\nexport TS_SELENIUM_OCP_USERNAME=<USERNAME>\nexport TS_SELENIUM_OCP_PASSWORD=<PASSWORD>\nexport USERSTORY=<TESTNAME>\n\n# Run with full local code mounted (testing changes vs origin/main)\npodman rm -f selenium-e2e 2>/dev/null\npodman run -it --shm-size=2g -p 5920:5920 --env-host -v $(pwd):/tmp/e2e:Z quay.io/eclipse/che-e2e:next\n```\n\n#### No Test Code Changes vs origin/main (mount only report directory):\n\n```bash\ncd tests/e2e\n\n# Set environment variables\nexport NODE_TLS_REJECT_UNAUTHORIZED=0\nexport TS_SELENIUM_BASE_URL=<URL>\nexport TS_SELENIUM_OCP_USERNAME=<USERNAME>\nexport TS_SELENIUM_OCP_PASSWORD=<PASSWORD>\nexport USERSTORY=<TESTNAME>\n\n# Run with only report directory mounted (no changes vs origin/main, uses published image code)\npodman rm -f selenium-e2e 2>/dev/null\npodman run -it --shm-size=2g -p 5920:5920 --env-host -v $(pwd)/report:/tmp/e2e/report:Z quay.io/eclipse/che-e2e:next\n```\n\n---\n\n## Step 7: Execute After User Confirmation\n\nBefore executing, present the generated command(s) to the user and ask for confirmation.\n\n### Confirmation prompt:\n\n> \"Here is the command I will run:\n>\n> ```bash\n> <generated command>\n> ```\n>\n> **Summary:**\n>\n> - **Platform:** [OpenShift/Kubernetes]\n> - **Product:** [Eclipse Che/Dev Spaces]\n> - **Test:** [TESTNAME]\n> - **Method:** [Local browser/Podman container]\n> - **URL:** [URL]\n>\n> Shall I proceed?\"\n\n### After confirmation:\n\n1. Execute the bash command(s)\n2. Monitor the output\n3. Report success or failure\n4. If using Podman, remind user about VNC access at `localhost:5920`\n\n---\n\n## Debugging Options\n\n### Verbose Logging\n\n```bash\nexport TS_SELENIUM_LOG_LEVEL=TRACE\nexport TS_SELENIUM_PRINT_TIMEOUT_VARIABLES=true\nexport TS_DEBUG_MODE=true\n```\n\n### Headless Mode (CI)\n\n```bash\nexport TS_SELENIUM_HEADLESS=true\n```\n\n### Video Recording\n\n```bash\nexport VIDEO_RECORDING=true\n```\n\n### Keep Workspace on Failure\n\n```bash\nexport TS_DELETE_WORKSPACE_ON_FAILED_TEST=false\n```\n\n---\n\n## VNC Access for Visual Debugging (Podman only)\n\nConnect to VNC at `localhost:5920` to watch tests running in the container.\n\n**VNC clients:**\n\n- **TigerVNC** (recommended): `sudo dnf install tigervnc` then `vncviewer localhost:5920`\n- **Remmina**: `sudo dnf install remmina remmina-plugins-vnc` (GUI-based)\n- **Vinagre**: `sudo dnf install vinagre` then `vinagre localhost:5920`\n\n---\n\n## View Test Reports\n\nAfter test execution:\n\n```bash\nnpm run open-allure-dasboard\n```\n\n---\n\n## Troubleshooting\n\n### Certificate Errors\n\n```bash\nexport NODE_TLS_REJECT_UNAUTHORIZED=0\n```\n\n### Slow Workspace Start\n\n```bash\nexport TS_SELENIUM_START_WORKSPACE_TIMEOUT=600000\nexport TS_SELENIUM_DEFAULT_ATTEMPTS=3\n```\n\n### OAuth Login Issues\n\nVerify the login provider title matches exactly what appears on the login page:\n\n```bash\nexport TS_OCP_LOGIN_PAGE_PROVIDER_TITLE=\"htpasswd\"\n```\n\n### Container Cleanup\n\n```bash\npodman rm -f selenium-e2e\n```\n\n---\n\n## Debugging TypeScript Tests in VS Code\n\nFor debugging E2E tests with breakpoints, see: https://code.visualstudio.com/docs/typescript/typescript-debugging\n","tests/e2e/.claude/skills/explore-and-generate-tests/SKILL.md":"---\nname: explore-and-generate-tests\ndescription: Explore a web page with new functionality using Playwright, record UI interactions, develop a test plan, and generate E2E Mocha/Selenium test code following the project's code style and patterns.\n---\n\n# Explore and Generate E2E Tests\n\nYou are a Software Quality Engineer exploring new functionality in Eclipse Che / Red Hat OpenShift Dev Spaces. Your job is to explore a web page, record what you find, develop a test plan, and write production-ready E2E test code.\n\n## Prerequisites\n\n- Playwright MCP server must be configured (see `.mcp.json` in this repo)\n- Read `CODE_STYLE.md` before generating any code\n\n## Input Parameters\n\nThe user provides:\n\n- **page** - URL or route to explore (e.g., `/#/create-workspace`, `/#/user-preferences`, a full URL)\n- **scope** - (optional) What to focus on: a specific feature, button, form, or \"everything\"\n- **test-cases-repo** - (optional) Path to the test-cases repo for generating Polarion test case markdown (default: `/home/ndp/projects/cci/test-cases`)\n\n## Workflow\n\n### Phase 0: Authenticate\n\nBefore exploring, ensure you have an authenticated browser session.\n\n1. **Determine the target URL:**\n\n    - If the user provided a full URL, extract the base URL\n    - If the user provided only a route (e.g., `/#/create-workspace`), ask for the base URL\n\n2. **Ask for credentials** (if not already provided):\n\n    - Use AskUserQuestion to collect:\n        - **URL** (if not provided) — Dev Spaces or Eclipse Che base URL\n        - **Platform** — `openshift` or `kubernetes` (auto-detect from URL: `devspaces` in URL → openshift; `che` on localhost/minikube → kubernetes)\n        - **Username** — login username\n        - **Password** — login password\n        - **Identity Provider** — (OpenShift only, default: `htpasswd`)\n\n3. **Login via Playwright MCP:**\n\n    Navigate to the base URL using `browser_navigate`, then take a `browser_snapshot` to see the login page state. The login flow depends on the platform:\n\n    #### OpenShift (Dev Spaces / Eclipse Che on OpenShift)\n\n    - Click the identity provider link (e.g., \"htpasswd\") using `browser_click`\n    - Take a `browser_snapshot` to see the login form\n    - Type username into the username field using `browser_type`\n    - Type password into the password field using `browser_type`\n    - Click the \"Log in\" button using `browser_click`\n    - If an authorization page appears (\"Allow selected permissions\"), click to approve using `browser_click`\n\n    #### Kubernetes / Minikube (Eclipse Che with Dex)\n\n    - The login page is a Dex login form — take a `browser_snapshot` to confirm\n    - Click \"Log in with Email\" if multiple login options are shown\n    - Type email/username into the email field using `browser_type`\n    - Type password into the password field using `browser_type`\n    - Click the \"Login\" button using `browser_click`\n    - If a grant access page appears, click \"Grant Access\" using `browser_click`\n\n    #### Common post-login steps\n\n    - Take a `browser_snapshot` to confirm the dashboard loaded successfully\n    - If login fails, report the error and stop\n\n4. **Set environment context** for later use in generated test code:\n    - Remember the base URL as `TS_SELENIUM_BASE_URL`\n    - Remember the platform as `TS_PLATFORM` (`openshift` or `kubernetes`)\n    - For OpenShift: remember username as `TS_SELENIUM_OCP_USERNAME`, identity provider as `TS_OCP_LOGIN_PAGE_PROVIDER_TITLE`\n    - For Kubernetes: remember username as `TS_SELENIUM_K8S_USERNAME`\n\n### Phase 1: Explore the Page\n\nUse Playwright MCP tools to systematically explore the target page.\n\n1. **Navigate** to the target page using `browser_navigate`\n2. **Take a snapshot** using `browser_snapshot` to capture the full accessibility tree\n3. **Identify all interactive elements**: buttons, links, forms, dropdowns, toggles, tabs, modals\n4. **Explore each interactive element**:\n    - Click tabs/navigation items to discover sub-pages\n    - Open dropdowns to see options\n    - Hover over elements to discover tooltips or hidden UI\n    - Check for conditional UI (elements that appear/disappear based on state)\n5. **Document the page structure** as you go:\n    - Page layout and sections\n    - Navigation flows (what leads where)\n    - Forms and their fields (required vs optional, validation rules)\n    - Action buttons and their effects\n    - Error states and edge cases visible in the UI\n\n**Important:**\n\n- Use `browser_snapshot` (accessibility tree) as the primary exploration method\n- Use `browser_click`, `browser_fill_form`, `browser_hover` to interact with elements\n- Use `browser_wait_for` when pages need time to load\n- Take snapshots AFTER each interaction to see the resulting state\n- Do NOT use screenshots — use snapshots for structured data\n\n### Phase 2: Record the Interactions\n\nAfter exploring, produce a structured record of the functionality:\n\n```markdown\n## Page: [Page Name]\n\nURL: [full URL or route]\n\n### UI Elements\n\n| Element                 | Type       | Locator Strategy                                | Behavior                      |\n| ----------------------- | ---------- | ----------------------------------------------- | ----------------------------- |\n| Create Workspace button | button     | By.xpath('//button[text()=\"Create Workspace\"]') | Opens workspace creation form |\n| Git Repo URL input      | text input | By.id('git-repo-url')                           | Accepts git repository URL    |\n| ...                     | ...        | ...                                             | ...                           |\n\n### User Workflows\n\n1. **[Workflow Name]** (e.g., \"Create workspace from git repo\")\n\n    - Step 1: [action] → [expected result]\n    - Step 2: [action] → [expected result]\n    - ...\n\n2. **[Workflow Name]** (e.g., \"Cancel workspace creation\")\n    - Step 1: ...\n\n### Edge Cases & Error States\n\n- [What happens when invalid input is provided]\n- [What happens when network is slow]\n- [Empty states, loading states]\n```\n\n### Phase 3: Develop Test Plan\n\nBased on the recorded interactions, create a comprehensive test plan:\n\n1. **Categorize test scenarios** by priority:\n\n    - **P1 (High)**: Core happy-path workflows that must work\n    - **P2 (Medium)**: Alternative flows, input validation, error handling\n    - **P3 (Low)**: Edge cases, cosmetic checks, accessibility\n\n2. **Define test cases** using this structure:\n\n    ```\n    Test ID: [CATEGORY_LETTER][NUMBER]\n    Title: [descriptive title]\n    Type: positive | negative\n    Priority: high | medium | low\n    Preconditions: [what must be true before the test]\n    Steps:\n      1. [action] → Expected: [result]\n      2. [action] → Expected: [result]\n    ```\n\n3. **Group tests into suites** by feature area or workflow\n\n4. **Present the test plan to the user** and ask for approval before proceeding to code generation. The user may want to:\n    - Add or remove test cases\n    - Change priorities\n    - Modify the scope\n    - Split into multiple specs\n\n### Phase 4: Generate E2E Test Code\n\nAfter the user approves the test plan, generate production-ready test code.\n\n#### Step 4.1: Read Required Reference Files\n\nBefore writing any code, read these files to understand current patterns:\n\n```\nCODE_STYLE.md                           # Coding standards\nconfigs/inversify.types.ts              # Existing class registry\nconfigs/inversify.config.ts             # Existing DI bindings\nconstants/TIMEOUT_CONSTANTS.ts          # Available timeout constants\n```\n\nAlso read 1-2 existing specs and page objects similar to what you're generating, to match the exact style.\n\n#### Step 4.2: Generate Page Objects (if needed)\n\nFor each new page or component that needs interaction, create a page object:\n\n**File:** `pageobjects/<category>/NewPage.ts`\n\n```typescript\n/** *******************************************************************\n * copyright (c) 2026 Red Hat, Inc.\n *\n * This program and the accompanying materials are made\n * available under the terms of the Eclipse Public License 2.0\n * which is available at https://www.eclipse.org/legal/epl-2.0/\n *\n * SPDX-License-Identifier: EPL-2.0\n **********************************************************************/\n\nimport { inject, injectable } from 'inversify';\nimport 'reflect-metadata';\nimport { By } from 'selenium-webdriver';\nimport { CLASSES } from '../../configs/inversify.types';\nimport { DriverHelper } from '../../utils/DriverHelper';\nimport { Logger } from '../../utils/Logger';\nimport { TIMEOUT_CONSTANTS } from '../../constants/TIMEOUT_CONSTANTS';\n\n@injectable()\nexport class NewPage {\n\tprivate static readonly ELEMENT_LOCATOR: By = By.xpath('//...');\n\n\tconstructor(@inject(CLASSES.DriverHelper) private readonly driverHelper: DriverHelper) {}\n\n\tasync waitPage(timeout: number = TIMEOUT_CONSTANTS.TS_COMMON_DASHBOARD_WAIT_TIMEOUT): Promise<void> {\n\t\tLogger.debug();\n\t\tawait this.driverHelper.waitVisibility(NewPage.ELEMENT_LOCATOR, timeout);\n\t}\n\n\tprivate getDynamicLocator(name: string): By {\n\t\treturn By.xpath(`//div[text()=\"${name}\"]`);\n\t}\n}\n```\n\n**Rules for page objects:**\n\n- `@injectable()` decorator on the class\n- `Logger.debug()` at the start of every public method\n- Static locators as `private static readonly NAME: By`\n- Dynamic locators as `private` methods returning `By`\n- Constructor injection with `@inject()` decorators\n- Explicit return types on ALL methods\n- NO `public` keyword on public methods\n- Use XPath as primary locator strategy (matching existing patterns)\n- Use timeout constants from `TIMEOUT_CONSTANTS`, not hardcoded values\n\n#### Step 4.3: Generate Test Spec\n\n**File:** `specs/<category>/NewFeature.spec.ts`\n\n```typescript\n/** *******************************************************************\n * copyright (c) 2026 Red Hat, Inc.\n *\n * This program and the accompanying materials are made\n * available under the terms of the Eclipse Public License 2.0\n * which is available at https://www.eclipse.org/legal/epl-2.0/\n *\n * SPDX-License-Identifier: EPL-2.0\n **********************************************************************/\n\nimport { e2eContainer } from '../../configs/inversify.config';\nimport { CLASSES, TYPES } from '../../configs/inversify.types';\nimport { LoginTests } from '../../tests-library/LoginTests';\nimport { WorkspaceHandlingTests } from '../../tests-library/WorkspaceHandlingTests';\nimport { BASE_TEST_CONSTANTS } from '../../constants/BASE_TEST_CONSTANTS';\n// ... other imports\n\nsuite(`New Feature ${BASE_TEST_CONSTANTS.TEST_ENVIRONMENT}`, function (): void {\n\tconst loginTests: LoginTests = e2eContainer.get(CLASSES.LoginTests);\n\t// ... other DI injections\n\n\tsuiteSetup('Login', async function (): Promise<void> {\n\t\tawait loginTests.loginIntoChe();\n\t});\n\n\ttest('Test case description', async function (): Promise<void> {\n\t\t// test implementation\n\t});\n\n\tsuiteTeardown('Cleanup', async function (): Promise<void> {\n\t\t// cleanup code\n\t});\n});\n```\n\n**Rules for test specs:**\n\n- Mocha TDD style: `suite()`, `test()`, `suiteSetup()`, `suiteTeardown()`\n- NEVER use arrow functions in Mocha declarations\n- Get DI instances inside `suite()` function body\n- Always include login in `suiteSetup`\n- Always include cleanup in `suiteTeardown`\n- Use `function (): void` (not arrow functions) for all callbacks\n\n#### Step 4.4: Register New Components\n\nIf you created new page objects, update:\n\n1. **`configs/inversify.types.ts`** - Add class name to `CLASSES` object\n2. **`configs/inversify.config.ts`** - Add import and `e2eContainer.bind<>()` call\n\n#### Step 4.5: Generate Polarion Test Case Markdown (optional)\n\nIf the user wants test cases synced to Polarion, also generate markdown files in the test-cases repo:\n\n**File:** `<test-cases-repo>/tests/<functional|operational>/<category>/<id>-<title>.md`\n\n```yaml\n---\narch:\n    - value: x86_64\n      assignee: <ask user>\ncaseposneg: positive | negative\ncaseautomation: automated\ncaseimportance: high | medium | low\ncustomerscenario: false\nestimate: 1h\n---\n# <ID> - <Test Title>\n\n## Description\n- Test script: <link to the generated spec file in GitHub>\n```\n\nUse the test-cases repo's `npm run rename` to validate filenames after generating.\n\n### Phase 5: Validate\n\nAfter generating all code:\n\n1. **Run TypeScript compilation**: `npm run tsc` to catch type errors\n2. **Run linter**: `npm run lint` to catch style violations\n3. **Run prettier**: `npm run prettier` to fix formatting\n4. **Review the generated code** against `CODE_STYLE.md` rules\n5. **Present a summary** to the user:\n    - List of files created/modified\n    - Test cases and their priorities\n    - Any manual steps needed (e.g., running the tests, updating Polarion)\n\n## Locator Strategy Guide\n\nWhen translating Playwright MCP snapshot elements to Selenium locators:\n\n| Playwright Snapshot            | Selenium Locator                                          |\n| ------------------------------ | --------------------------------------------------------- |\n| `role=\"button\" name=\"Create\"`  | `By.xpath('//button[text()=\"Create\"]')`                   |\n| `role=\"textbox\" name=\"URL\"`    | `By.xpath('//input[@aria-label=\"URL\"]')`                  |\n| `role=\"link\" name=\"Settings\"`  | `By.xpath('//a[text()=\"Settings\"]')`                      |\n| `role=\"tab\" name=\"Git Repo\"`   | `By.xpath('//button[@role=\"tab\" and text()=\"Git Repo\"]')` |\n| `data-testid=\"workspace-name\"` | `By.css('[data-testid=\"workspace-name\"]')`                |\n| `id=\"input-field\"`             | `By.id('input-field')`                                    |\n\n**Prefer this locator priority:**\n\n1. `data-testid` attributes (most stable)\n2. `id` attributes\n3. XPath with text content (for buttons, links)\n4. XPath with `aria-label` (for inputs, form fields)\n5. XPath with structural path (last resort — fragile)\n\n## Example Invocations\n\n```\nexplore and generate tests for /#/create-workspace\nexplore /#/user-preferences and write E2E tests for the settings page\ngenerate tests for the new workspace sharing feature at /#/workspaces\n```\n"},"files":{"tests/e2e/.claude/skills/e2e-test-developer/SKILL.md":"---\nname: e2e-test-developer\ndescription: 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.\n---\n\n# Eclipse Che E2E TypeScript Mocha Selenium Test Development Skill\n\nYou 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.\n\n## Project Overview\n\nThis is the E2E test suite for Eclipse Che / Red Hat OpenShift Dev Spaces. It uses:\n\n- **Selenium WebDriver** with Chrome browser\n- **Mocha** (TDD style - `suite()`, `test()`, `suiteSetup()`, `suiteTeardown()`)\n- **TypeScript** with strict type checking\n- **Inversify** for dependency injection\n- **Chai** for assertions\n- **Allure** for test reporting\n\n## Directory Structure\n\n| Directory            | Purpose                                                                                                  |\n| -------------------- | -------------------------------------------------------------------------------------------------------- |\n| `specs/`             | Test specifications organized by category (api/, factory/, dashboard-samples/, miscellaneous/)           |\n| `pageobjects/`       | Page Object classes for UI elements (dashboard/, ide/, login/, openshift/, git-providers/, webterminal/) |\n| `utils/`             | Utilities (DriverHelper, BrowserTabsUtil, Logger, API handlers, KubernetesCommandLineToolsExecutor)      |\n| `tests-library/`     | Reusable test helpers (WorkspaceHandlingTests, LoginTests, ProjectAndFileTests)                          |\n| `constants/`         | Environment variable mappings (BASE_TEST_CONSTANTS, TIMEOUT_CONSTANTS, FACTORY_TEST_CONSTANTS, etc.)     |\n| `configs/`           | Mocha config, Inversify container (inversify.config.ts), shell scripts                                   |\n| `suites/`            | Test suite configurations for different environments                                                     |\n| `driver/`            | Chrome driver configuration                                                                              |\n| `build/dockerfiles/` | Docker image for running tests                                                                           |\n\n## Essential Commands\n\n```bash\n# Install dependencies\nnpm ci\n\n# Lint and format\nnpm run lint\nnpm run prettier\n\n# Build TypeScript only\nnpm run tsc\n\n# Run all tests (requires environment variables)\nexport TS_SELENIUM_BASE_URL=<che-url>\nexport TS_SELENIUM_OCP_USERNAME=<username>\nexport TS_SELENIUM_OCP_PASSWORD=<password>\nnpm run test\n\n# Run a single test file (without .spec.ts extension)\nexport USERSTORY=SmokeTest\nnpm run test\n\n# Run API-only tests (no browser)\nexport USERSTORY=EmptyWorkspaceAPI\nnpm run driver-less-test\n\n# View Allure test report\nnpm run open-allure-dasboard\n```\n\n## CODE STYLE REQUIREMENTS (CRITICAL)\n\n### File Header (Required for ALL .ts files)\n\nEvery TypeScript file MUST start with this exact header:\n\n```typescript\n/** *******************************************************************\n * copyright (c) 2026 Red Hat, Inc.\n *\n * This program and the accompanying materials are made\n * available under the terms of the Eclipse Public License 2.0\n * which is available at https://www.eclipse.org/legal/epl-2.0/\n *\n * SPDX-License-Identifier: EPL-2.0\n **********************************************************************/\n```\n\n### Page Object and Utility Classes\n\n1. **Class Declaration with Dependency Injection**\n    - Use `@injectable()` decorator on ALL page objects and utilities\n    - Use constructor injection with `@inject()` decorators\n\n```typescript\nimport { inject, injectable } from 'inversify';\nimport 'reflect-metadata';\nimport { CLASSES } from '../../configs/inversify.types';\n\n@injectable()\nexport class MyPageObject {\n\tconstructor(@inject(CLASSES.DriverHelper) private readonly driverHelper: DriverHelper) {}\n}\n```\n\n2. **Public Methods**\n    - Declare public methods WITHOUT the `public` keyword\n    - Add `Logger.debug()` at the START of every public method\n    - Always specify explicit return types\n\n```typescript\nasync clickButton(timeout: number = TIMEOUT_CONSTANTS.TS_CLICK_DASHBOARD_ITEM_TIMEOUT): Promise<void> {\n    Logger.debug();\n    await this.driverHelper.waitAndClick(MyPage.BUTTON_LOCATOR, timeout);\n}\n```\n\n3. **Locators**\n    - Static locators: `private static readonly` fields of type `By`\n    - Dynamic locators: `private` methods that return `By`\n    - NEVER declare locators as constants inside methods\n\n```typescript\n// Static locators (correct)\nprivate static readonly SUBMIT_BUTTON: By = By.xpath('//button[@type=\"submit\"]');\nprivate static readonly INPUT_FIELD: By = By.id('input-field');\n\n// Dynamic locators (correct)\nprivate getItemLocator(itemName: string): By {\n    return By.xpath(`//div[text()=\"${itemName}\"]`);\n}\n\n// WRONG - Never do this inside a method\nasync wrongMethod(): Promise<void> {\n    const locator: By = By.xpath('//button'); // AVOID THIS\n}\n```\n\n### Member Ordering (Enforced by ESLint)\n\nClasses must follow this order:\n\n1. Static fields\n2. Public fields\n3. Instance fields\n4. Protected fields\n5. Private fields\n6. Abstract fields\n7. Constructor\n8. Public static methods\n9. Protected static methods\n10. Private static methods\n11. Public methods\n12. Protected methods\n13. Private methods\n\n### Test File Conventions\n\n1. **Naming**\n\n    - UI tests: `*.spec.ts` (e.g., `Factory.spec.ts`)\n    - API-only tests: `*API.spec.ts` (e.g., `EmptyWorkspaceAPI.spec.ts`)\n\n2. **Mocha TDD Style (Required)**\n    - Use `suite()`, `test()`, `suiteSetup()`, `suiteTeardown()`\n    - NEVER use arrow functions in test declarations (Mocha context issue)\n\n```typescript\nsuite('My Test Suite', function (): void {\n\t// Inject dependencies inside suite() to avoid unnecessary execution\n\tconst dashboard: Dashboard = e2eContainer.get(CLASSES.Dashboard);\n\tconst loginTests: LoginTests = e2eContainer.get(CLASSES.LoginTests);\n\n\tsuiteSetup('Login to application', async function (): Promise<void> {\n\t\tawait loginTests.loginIntoChe();\n\t});\n\n\ttest('Verify dashboard is visible', async function (): Promise<void> {\n\t\tawait dashboard.waitPage();\n\t});\n\n\tsuiteTeardown('Cleanup', async function (): Promise<void> {\n\t\t// cleanup code\n\t});\n});\n```\n\n3. **Dependency Injection in Tests**\n    - Import container: `import { e2eContainer } from '../../configs/inversify.config';`\n    - Import types: `import { CLASSES, TYPES } from '../../configs/inversify.types';`\n    - Get instances inside suite() function\n\n```typescript\nimport { e2eContainer } from '../../configs/inversify.config';\nimport { CLASSES, TYPES } from '../../configs/inversify.types';\n\nsuite('Test Suite', function (): void {\n\tconst workspaceHandlingTests: WorkspaceHandlingTests = e2eContainer.get(CLASSES.WorkspaceHandlingTests);\n\tconst testWorkspaceUtil: ITestWorkspaceUtil = e2eContainer.get(TYPES.WorkspaceUtil);\n\t// ... test implementation\n});\n```\n\n### TypeScript Requirements\n\n1. **Explicit Type Annotations Required**\n    - All parameters must have type annotations\n    - All property declarations must have type annotations\n    - All variable declarations must have type annotations\n    - All functions must have explicit return types\n\n```typescript\n// Correct\nasync function doSomething(param: string, timeout: number): Promise<void> {\n\tconst result: string = await someOperation();\n}\n\n// Wrong - missing types\nasync function doSomething(param, timeout) {\n\tconst result = await someOperation();\n}\n```\n\n2. **Naming Conventions**\n\n    - Variables: camelCase or UPPER_CASE\n    - No leading/trailing underscores\n\n3. **String Quotes**\n\n    - Use single quotes for strings\n\n4. **Comments**\n    - Comments must start with lowercase (capitalized-comments: never)\n    - Mark workarounds with `// todo` and issue number: `// todo commented due to issue crw-1010`\n\n### Prettier and ESLint\n\n- Pre-commit hooks run automatically via Husky\n- Run `npm run prettier` to fix formatting\n- Run `npm run lint` to fix linting issues\n\n## Environment Variables\n\nCore variables (defined in `constants/` directory):\n\n| Variable                              | Description                                  |\n| ------------------------------------- | -------------------------------------------- |\n| `TS_SELENIUM_BASE_URL`                | Che/DevSpaces dashboard URL                  |\n| `TS_SELENIUM_OCP_USERNAME`            | OpenShift username                           |\n| `TS_SELENIUM_OCP_PASSWORD`            | OpenShift password                           |\n| `USERSTORY`                           | Specific test file to run (without .spec.ts) |\n| `TS_PLATFORM`                         | `openshift` (default) or `kubernetes`        |\n| `TS_SELENIUM_FACTORY_GIT_REPO_URL`    | Git repo for factory tests                   |\n| `TS_SELENIUM_VALUE_OPENSHIFT_OAUTH`   | Enable OAuth (true/false)                    |\n| `TS_SELENIUM_LOG_LEVEL`               | Logging level (TRACE, DEBUG, INFO, etc.)     |\n| `DELETE_WORKSPACE_ON_SUCCESSFUL_TEST` | Delete workspace on success                  |\n\n## Adding New Page Objects\n\n1. Create file in appropriate `pageobjects/` subdirectory\n2. Add `@injectable()` decorator\n3. Register in `configs/inversify.config.ts`\n4. Add class identifier in `configs/inversify.types.ts`\n\n```typescript\n// 1. Create pageobjects/dashboard/NewPage.ts\n@injectable()\nexport class NewPage {\n\tprivate static readonly ELEMENT_LOCATOR: By = By.id('element');\n\n\tconstructor(@inject(CLASSES.DriverHelper) private readonly driverHelper: DriverHelper) {}\n\n\tasync waitForElement(): Promise<void> {\n\t\tLogger.debug();\n\t\tawait this.driverHelper.waitVisibility(NewPage.ELEMENT_LOCATOR);\n\t}\n}\n\n// 2. Add to configs/inversify.types.ts\nconst CLASSES: any = {\n\t// ... existing classes\n\tNewPage: 'NewPage'\n};\n\n// 3. Add to configs/inversify.config.ts\nimport { NewPage } from '../pageobjects/dashboard/NewPage';\ne2eContainer.bind<NewPage>(CLASSES.NewPage).to(NewPage);\n```\n\n## Adding New Tests\n\n1. Create file in appropriate `specs/` subdirectory\n2. Follow naming convention (*.spec.ts for UI, *API.spec.ts for API)\n3. Use TDD style (suite, test, suiteSetup, suiteTeardown)\n4. Run with `export USERSTORY=<filename-without-extension> && npm run test`\n\n## Common Utilities\n\n### DriverHelper (utils/DriverHelper.ts)\n\n- `waitVisibility(locator, timeout)` - Wait for element to be visible\n- `waitAndClick(locator, timeout)` - Wait and click element\n- `waitAndGetText(locator, timeout)` - Wait and get text\n- `waitDisappearance(locator, timeout)` - Wait for element to disappear\n- `navigateToUrl(url)` - Navigate to URL\n- `wait(ms)` - Wait for specified milliseconds\n\n### BrowserTabsUtil (utils/BrowserTabsUtil.ts)\n\n- `navigateTo(url)` - Navigate to URL\n- `getCurrentUrl()` - Get current URL\n- `maximize()` - Maximize browser window\n- `closeAllTabsExceptCurrent()` - Close extra tabs\n\n### Logger (utils/Logger.ts)\n\n- `Logger.debug()` - Log method name (use at start of public methods)\n- `Logger.info(message)` - Log info message\n- `Logger.error(message)` - Log error message\n\n## GitHub Actions Maintenance\n\n### PR Check Workflow (.github/workflows/pr-check.yml)\n\nTriggers on:\n\n- Pull requests to main or 7.\\*\\*.x branches\n- Changes in `tests/e2e/**` or the workflow file\n\nSteps performed:\n\n1. Prettier check - Fails if formatting issues found\n2. TypeScript compilation - `npm run tsc`\n3. ESLint check - `npm run lint`\n4. Deploy Che on minikube\n5. Run Empty Workspace API test\n6. Build E2E Docker image\n7. Run Empty Workspace UI test\n\nWhen modifying tests:\n\n- Ensure `npm run prettier` passes locally\n- Ensure `npm run tsc` compiles without errors\n- Ensure `npm run lint` passes\n- Test locally if possible before pushing\n\n## Test Patterns\n\n### Workspace Lifecycle Pattern\n\n```typescript\nsuite('Workspace Test', function (): void {\n\tconst workspaceHandlingTests: WorkspaceHandlingTests = e2eContainer.get(CLASSES.WorkspaceHandlingTests);\n\tconst testWorkspaceUtil: ITestWorkspaceUtil = e2eContainer.get(TYPES.WorkspaceUtil);\n\tconst loginTests: LoginTests = e2eContainer.get(CLASSES.LoginTests);\n\n\tsuiteSetup('Login', async function (): Promise<void> {\n\t\tawait loginTests.loginIntoChe();\n\t});\n\n\ttest('Create workspace', async function (): Promise<void> {\n\t\tawait workspaceHandlingTests.createAndStartWorkspace();\n\t});\n\n\ttest('Obtain workspace name', async function (): Promise<void> {\n\t\tawait workspaceHandlingTests.obtainWorkspaceNameFromStartingPage();\n\t});\n\n\ttest('Register running workspace', function (): void {\n\t\tregisterRunningWorkspace(WorkspaceHandlingTests.getWorkspaceName());\n\t});\n\n\tsuiteTeardown('Stop and delete workspace', async function (): Promise<void> {\n\t\tawait testWorkspaceUtil.stopAndDeleteWorkspaceByName(WorkspaceHandlingTests.getWorkspaceName());\n\t});\n});\n```\n\n### API Test Pattern (No Browser)\n\n```typescript\nsuite('API Test', function (): void {\n\tconst kubernetesCommandLineToolsExecutor: KubernetesCommandLineToolsExecutor = e2eContainer.get(\n\t\tCLASSES.KubernetesCommandLineToolsExecutor\n\t);\n\n\tsuiteSetup('Setup', async function (): Promise<void> {\n\t\tkubernetesCommandLineToolsExecutor.loginToOcp();\n\t});\n\n\ttest('Execute API operation', function (): void {\n\t\tconst output: ShellString = kubernetesCommandLineToolsExecutor.applyAndWaitDevWorkspace(yaml);\n\t\texpect(output.stdout).contains('condition met');\n\t});\n\n\tsuiteTeardown('Cleanup', function (): void {\n\t\tkubernetesCommandLineToolsExecutor.deleteDevWorkspace();\n\t});\n});\n```\n\n## Package Management\n\n- Add packages as **dev dependencies**: `npm install --save-dev <package>`\n- After any changes to package.json, regenerate lock file: `npm install`\n- Run `npm ci` for clean install from lock file\n\n## Debugging\n\n- Set `TS_SELENIUM_LOG_LEVEL=TRACE` for verbose logging\n- Use VNC on port 5920 when running in Docker\n- View test results with `npm run open-allure-dasboard`\n- Screenshots are captured on test failures by CheReporter\n\n## Common Issues and Solutions\n\n1. **Flaky Tests**: Increase timeout or add explicit waits with `DriverHelper.wait()`\n2. **Element Not Found**: Verify locator with browser dev tools, check for dynamic loading\n3. **Stale Element**: Re-fetch element after page navigation\n4. **Timeout Errors**: Use appropriate timeout constants from `TIMEOUT_CONSTANTS`\n\n## Maintenance Guidelines\n\nWhen helping with E2E test development and maintenance:\n\n1. **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`.\n\n2. **Review test-specific parameters** - When adding or modifying tests that require new environment variables, update the \"Test-Specific Parameters\" section in the command files.\n\n3. **Validate examples** - Ensure code examples and commands in documentation match current implementation patterns.\n\n4. **Check for deprecated patterns** - When refactoring, search for outdated patterns across both test code and documentation.\n\n5. **Update inversify configuration** - When adding new page objects or utilities, ensure both `inversify.types.ts` and `inversify.config.ts` are updated.\n\n## Related Commands\n\n### `/run-e2e-test` - Unified E2E Test Runner\n\nRun E2E tests against **Eclipse Che** or **Red Hat OpenShift Dev Spaces** using either a **local Chrome browser** or **Podman container**.\n\n**Usage:** `/run-e2e-test [URL USERNAME PASSWORD [TESTNAME] [METHOD]]`\n\n**Features:**\n\n- **Interactive mode**: Run without arguments to be prompted for all parameters\n- **Auto-detect platform**: Determines Eclipse Che vs Dev Spaces from URL\n- **Smart rebuild**: Detects local code changes vs `origin/main` and rebuilds only when needed\n- **Test-specific parameters**: Prompts for additional env vars based on selected test type\n\n**Examples:**\n\n- `/run-e2e-test` - Interactive mode (recommended)\n- `/run-e2e-test https://devspaces.apps.example.com/ admin password SmokeTest npm`\n- `/run-e2e-test https://che.apps.example.com/ admin password EmptyWorkspace podman`\n\n**Run methods:**\n\n- `npm` - Local Chrome browser (faster, see browser in real-time)\n- `podman` - Isolated container with VNC support at `localhost:5920`\n","tests/e2e/.claude/commands/run-e2e-test.md":"# Run E2E Tests - Unified Command\n\nRun E2E tests against **Eclipse Che** or **Red Hat OpenShift Dev Spaces** using either a **local Chrome browser** or **Podman container**.\n\n## Arguments: $ARGUMENTS\n\n**Usage:** `/run-e2e-test [URL USERNAME PASSWORD [TESTNAME]]`\n\n**Examples:**\n\n- `/run-e2e-test` - Interactive mode (recommended)\n- `/run-e2e-test https://devspaces.apps.cluster.example.com/ admin mypassword SmokeTest npm`\n- `/run-e2e-test https://che.apps.cluster.example.com/ admin mypassword EmptyWorkspace podman`\n\n---\n\n## Execution Flow\n\n1. **Parse arguments** - Extract parameters from `$ARGUMENTS`\n2. **Prompt if missing** - Collect missing values via AskUserQuestion\n3. **Auto-detect platform** - Determine Eclipse Che vs Dev Spaces from URL\n4. **Detect local changes** - Check if rebuild is needed based on git status\n5. **Detect test-specific parameters** - Read spec file for required env vars\n6. **Generate bash commands** - Build appropriate bash commands\n7. **Execute** - Run the test after user confirmation\n\n---\n\n## ⚠️ MANDATORY RULES\n\n1. **Never print bash commands with `&& \\` or multi-line continuations using `\\`** - Commands using line continuation characters (`\\`) or chained with `&& \\` cannot be copy-pasted properly from the terminal. Instead, present commands as separate lines or use semicolons for simple chains.\n\n    ❌ **Wrong (line continuations):**\n\n    ```bash\n    cd /path/to/dir && \\\n    npm ci && \\\n    npm run test\n    ```\n\n    ❌ **Wrong (backslash continuations):**\n\n    ```bash\n    podman run -it \\\n      --shm-size=2g \\\n      -p 5920:5920 \\\n      quay.io/eclipse/che-e2e:next\n    ```\n\n    ✅ **Correct (separate lines):**\n\n    ```bash\n    cd /path/to/dir\n    npm ci\n    npm run test\n    ```\n\n    ✅ **Correct (single line for podman):**\n\n    ```bash\n    podman run -it --shm-size=2g -p 5920:5920 quay.io/eclipse/che-e2e:next\n    ```\n\n---\n\n## Step 1: Parse Arguments\n\nParse `$ARGUMENTS` to extract positional parameters:\n\n```\n$ARGUMENTS = \"URL USERNAME PASSWORD [TESTNAME]\"\n```\n\n**Parsing logic:**\n\n1. Split `$ARGUMENTS` by whitespace\n2. Assign to variables: `URL`, `USERNAME`, `PASSWORD`, `TESTNAME`, `RUN_METHOD`\n3. If `TESTNAME` is not provided, default to `EmptyWorkspace`\n\n**Example:**\n\n```\nInput: \"https://devspaces.apps.example.com/ admin secret123 SmokeTest npm\"\nResult:\n  - URL = \"https://devspaces.apps.example.com/\"\n  - USERNAME = \"admin\"\n  - PASSWORD = \"secret123\"\n  - TESTNAME = \"SmokeTest\"\n  - RUN_METHOD = \"npm\"\n```\n\n---\n\n## Step 2: Prompt for Missing Values\n\nIf `$ARGUMENTS` is empty or incomplete, use **AskUserQuestion** to collect missing parameters.\n\n### Question 1 - Run Method (header: \"Method\")\n\n- \"How do you want to run the test?\"\n- Options:\n    - Option 1: \"npm run in local browser (Recommended)\" - \"Uses local Chrome and Node.js - faster, see browser in real-time\"\n    - Option 2: \"Podman container\" - \"Isolated environment with VNC support at localhost:5920\"\n\n### Question 2 - Execution Mode (header: \"Build\")\n\n- \"How should local changes be handled?\"\n- Options:\n    - Option 1: \"Build and run (Recommended)\" - \"Compare to origin/main, rebuild if any changes exist\"\n    - Option 2: \"Just run\" - \"Skip build, assume code is already compiled\"\n\n### Question 3 - Test Name (header: \"Test\")\n\n- \"Which test do you want to run?\"\n- Options:\n    - Option 1: \"EmptyWorkspace (Recommended)\" - \"Basic workspace creation test\"\n    - Option 2: \"SmokeTest\" - \"Quick validation of core functionality\"\n    - Option 3: \"Factory\" - \"Factory URL workspace creation with git operations\"\n    - Option 4: \"EmptyWorkspaceAPI\" - \"API-only test (no browser needed)\"\n\n### For URL - Ask in plain text:\n\n> \"What is the dashboard URL? (e.g., https://devspaces.apps.cluster.example.com/ or https://che.apps.cluster.example.com/)\"\n\n### For Username and Password - Ask in plain text:\n\n> \"Please provide your credentials:\n>\n> - **Username**: (e.g., admin, user)\n> - **Password**: your password\"\n\nWait for the user's response before proceeding.\n\n---\n\n## Step 3: Auto-detect Testing Product from URL\n\nAnalyze the URL to determine which product is being tested and set appropriate defaults:\n\n| URL Pattern            | Product                          | Auto-configured Settings                                                                                                                                          |\n| ---------------------- | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| Contains `devspaces`   | **Red Hat OpenShift Dev Spaces** | `TS_SELENIUM_VALUE_OPENSHIFT_OAUTH=true`, `TS_OCP_LOGIN_PAGE_PROVIDER_TITLE=htpasswd` `TS_PLATFORM=openshift`                                                     |\n| Contains `eclipse-che` | **Eclipse Che**                  | May need OAuth config based on deployment. I host contains `crw-qe.com` - it is `TS_PLATFORM=openshift`. Otherwise ask user for platform: openshift or kubernetes |\n\n**Detection logic:**\n\n```bash\nif [[ \"$URL\" == *\"devspaces\"* ]]; then\n  PRODUCT=\"devspaces\"\n  TS_SELENIUM_VALUE_OPENSHIFT_OAUTH=true\n  TS_OCP_LOGIN_PAGE_PROVIDER_TITLE=\"htpasswd\"\nelif [[ \"$URL\" == *\"eclipse-che\"* ]]; then\n  PRODUCT=\"che\"\nfi\n```\n\n---\n\n## Step 4: Detect Local Changes\n\nCheck the git status to determine if code needs to be rebuilt before running tests.\n\n**Important:** Ignore changes to documentation and Claude configuration files:\n\n- `CLAUDE.md`\n- `.claude/**/*`\n- `*.md` files (documentation)\n\n### For Local Browser Method:\n\n**Smart rebuild based on all changes vs origin/main** - rebuild when there are any test code modifications compared to origin/main.\n\n**Logic:**\n\n1. Compare working tree to `origin/main` to detect all changes (committed, staged, and unstaged)\n2. Rebuild if any test code changes exist compared to `origin/main` (excluding docs/config files)\n\n```bash\nNEEDS_REBUILD=false\n\n# Get current branch name for informational purposes\nCURRENT_BRANCH=$(git branch --show-current)\necho \"Current branch: $CURRENT_BRANCH\"\n\n# Check for any changes compared to origin/main (committed, staged, and unstaged)\n# Excludes docs and Claude config files\nCODE_CHANGES=$(git diff --name-only origin/main -- . 2>/dev/null | grep -v -E '(CLAUDE\\.md|\\.claude/|README\\.md|\\.md$)')\n\nif [[ -n \"$CODE_CHANGES\" ]]; then\n  echo \"Code changes detected compared to origin/main:\"\n  echo \"$CODE_CHANGES\"\n  NEEDS_REBUILD=true\nelse\n  echo \"No test code changes compared to origin/main - no rebuild needed\"\nfi\n\necho \"Rebuild needed: $NEEDS_REBUILD\"\n```\n\n### For Podman Container Method:\n\n**Smart mounting based on all changes vs origin/main** - mount full directory when there are any test code modifications compared to origin/main.\n\n**Logic:**\n\n1. Compare working tree to `origin/main` to detect all changes (committed, staged, and unstaged)\n2. Mount full directory if any test code changes exist compared to `origin/main` (excluding docs/config files)\n\n```bash\nE2E_IMAGE=\"quay.io/eclipse/che-e2e:next\"\nMOUNT_FULL_CODE=false\n\n# Get current branch name for informational purposes\nCURRENT_BRANCH=$(git branch --show-current)\necho \"Current branch: $CURRENT_BRANCH\"\n\n# Check for any changes compared to origin/main (committed, staged, and unstaged)\n# Excludes docs and Claude config files\nCODE_CHANGES=$(git diff --name-only origin/main -- . 2>/dev/null | grep -v -E '(CLAUDE\\.md|\\.claude/|README\\.md|\\.md$)')\n\nif [[ -n \"$CODE_CHANGES\" ]]; then\n  echo \"Code changes detected compared to origin/main:\"\n  echo \"$CODE_CHANGES\"\n  MOUNT_FULL_CODE=true\n  PODMAN_MOUNT=\"-v $(pwd):/tmp/e2e:Z\"\nelse\n  echo \"No test code changes compared to origin/main - using published image code\"\n  PODMAN_MOUNT=\"-v $(pwd)/report:/tmp/e2e/report:Z\"\nfi\n\necho \"Mount strategy: $PODMAN_MOUNT\"\n```\n\n---\n\n## Step 5: Detect Test-Specific Parameters\n\nAfter getting the test name, read the test spec file to identify additional required environment variables.\n\n### Find and read the spec file:\n\n```bash\n# Find the test file\nSPEC_FILE=$(find specs -name \"${USERSTORY}.spec.ts\" 2>/dev/null | head -1)\n\nif [[ -n \"$SPEC_FILE\" ]]; then\n  echo \"Found test file: $SPEC_FILE\"\n  # Read the file to identify required environment variables\n  cat \"$SPEC_FILE\"\nfi\n```\n\n### Platform-Specific Configurations\n\n#### OpenShift\n\n```bash\nexport TS_PLATFORM=openshift\nexport TS_SELENIUM_VALUE_OPENSHIFT_OAUTH=true\nexport TS_OCP_LOGIN_PAGE_PROVIDER_TITLE=\"htpasswd\"\n```\n\n#### Kubernetes\n\n```bash\nexport TS_PLATFORM=kubernetes\nexport TS_SELENIUM_K8S_USERNAME=che@eclipse.org\nexport TS_SELENIUM_K8S_PASSWORD=admin\nexport TS_SELENIUM_VALUE_OPENSHIFT_OAUTH=false\n```\n\n### Test-specific parameters by category:\n\n#### Factory URL Tests (Factory, RefusedOAuthFactory, NoSetupRepoFactory, etc.)\n\nThese tests navigate directly to a factory URL to create a workspace.\n**Ask for these additional parameters:**\n\n- **Git Repository URL** (`TS_SELENIUM_FACTORY_GIT_REPO_URL`) - Repository to clone\n- **Git Provider** (`TS_SELENIUM_FACTORY_GIT_PROVIDER`) - `github`, `gitlab`, `bitbucket`, `azure-devops`\n- **OAuth Enabled** (`TS_SELENIUM_GIT_PROVIDER_OAUTH`) - `true` or `false`\n\n#### Git Repo Import Form Tests (FactoryWithGitRepoOptions, CreateWorkspaceWithExistingNameFromGitUrl, etc.)\n\nThese tests use the Dashboard's \"Create Workspace\" form.\n**Ask for these additional parameters:**\n\n- **Git Repository URL** (`TS_SELENIUM_FACTORY_GIT_REPO_URL`) - Repository URL\n- **Branch Name** (`TS_SELENIUM_FACTORY_GIT_REPO_BRANCH`) - Optional (default: `main`)\n- **Project Name** (`TS_SELENIUM_PROJECT_NAME`) - Optional\n\n#### API Tests (EmptyWorkspaceAPI, ContainerOverridesAPI, etc.)\n\n**Ask for these additional parameters:**\n\n- **Namespace** (`TS_API_TEST_NAMESPACE`) - Optional\n- **UDI Image** (`TS_API_TEST_UDI_IMAGE`) - Optional\n\n#### Sample/Devfile Tests\n\n**Ask for these additional parameters:**\n\n- **Sample List** (`TS_SAMPLE_LIST`) - e.g., `Node.js Express`\n\n#### SSH/PAT Tests (SshUrlNoOauthPatFactory, etc.)\n\n**Ask for these additional parameters:**\n\n- **SSH Private Key Path** or **Personal Access Token** details\n\n---\n\n## Step 6: Generate Bash Commands\n\nBased on all collected parameters, generate the appropriate bash commands.\n\n### Option A: Local Browser Commands\n\n#### Browser/ChromeDriver Compatibility Check\n\nBefore running tests, verify ChromeDriver compatibility with local Chrome browser:\n\n```bash\n# Get local Chrome version\nCHROME_VERSION=$(google-chrome --version 2>/dev/null | grep -oP '\\d+' | head -1)\necho \"Local Chrome major version: $CHROME_VERSION\"\n\n# Get ChromeDriver version from package.json\nCHROMEDRIVER_VERSION=$(npm list chromedriver --depth=0 2>/dev/null | grep -oP 'chromedriver@\\K[\\d.]+')\necho \"ChromeDriver version in package.json: $CHROMEDRIVER_VERSION\"\n\n# If versions don't match, suggest installing compatible driver\nif [[ \"${CHROMEDRIVER_VERSION%%.*}\" != \"$CHROME_VERSION\" ]]; then\n  echo \"WARNING: ChromeDriver version mismatch!\"\n  echo \"Install compatible version: npm install chromedriver@$CHROME_VERSION\"\nfi\n```\n\n**If ChromeDriver is incompatible**, install a matching version:\n\n```bash\nnpm install chromedriver@<chrome-major-version>\n```\n\n#### Full Test Run (Build and Run)\n\n```bash\ncd tests/e2e\n\n# Use correct Node.js version\nnvm use v22.10.0\n\n# Get current branch name for informational purposes\nCURRENT_BRANCH=$(git branch --show-current)\necho \"Current branch: $CURRENT_BRANCH\"\n\n# Check for any changes compared to origin/main (committed, staged, and unstaged)\n# Excludes docs and Claude config files\nCODE_CHANGES=$(git diff --name-only origin/main -- . 2>/dev/null | grep -v -E '(CLAUDE\\.md|\\.claude/|README\\.md|\\.md$)')\n\nif [[ -n \"$CODE_CHANGES\" ]]; then\n  echo \"Code changes detected compared to origin/main:\"\n  echo \"$CODE_CHANGES\"\n  NEEDS_REBUILD=true\nelse\n  echo \"No test code changes compared to origin/main - skipping rebuild\"\n  NEEDS_REBUILD=false\nfi\n\n# Check if package.json was modified compared to origin/main\nPACKAGE_JSON_CHANGED=$(git diff --name-only origin/main -- package.json 2>/dev/null)\n\n# Rebuild if changes detected compared to origin/main\nif [[ \"$NEEDS_REBUILD\" == \"true\" ]]; then\n  echo \"Rebuilding TypeScript...\"\n  rm -rf node_modules dist\n\n  # Use npm install if package.json changed, otherwise npm ci\n  if [[ -n \"$PACKAGE_JSON_CHANGED\" ]]; then\n    echo \"package.json modified - using npm install to update package-lock.json\"\n    npm install\n  else\n    npm ci\n  fi\n\n  npm run lint\n  npm run prettier\n  npm run tsc\nfi\n\n# Set environment variables\nexport TS_SELENIUM_BASE_URL=\"<URL>\"\nexport TS_SELENIUM_OCP_USERNAME=\"<USERNAME>\"\nexport TS_SELENIUM_OCP_PASSWORD=\"<PASSWORD>\"\nexport USERSTORY=\"<TESTNAME>\"\nexport TS_SELENIUM_VALUE_OPENSHIFT_OAUTH=true\nexport NODE_TLS_REJECT_UNAUTHORIZED=0\n\n# Run the test\nnpm run test\n```\n\n#### API-Only Tests (No Browser)\n\n```bash\nexport USERSTORY=EmptyWorkspaceAPI\nnpm run driver-less-test\n```\n\n### Option B: Podman Container Commands\n\n**Important:** Use `--env-host` to pass all exported environment variables from the host to the container.\n\n**Smart mounting logic based on changes vs origin/main:**\n\n- **Changes compared to origin/main**: Mount full directory `-v $(pwd):/tmp/e2e:Z` to test local code\n- **No changes compared to origin/main**: Mount only report directory `-v $(pwd)/report:/tmp/e2e/report:Z`\n\n#### With Test Code Changes vs origin/main (mount full directory):\n\nUse this when there are test code changes compared to `origin/main` (works for feature branches or local main with unpushed changes).\n\n```bash\ncd tests/e2e\n\n# Set environment variables\nexport NODE_TLS_REJECT_UNAUTHORIZED=0\nexport TS_SELENIUM_BASE_URL=<URL>\nexport TS_SELENIUM_OCP_USERNAME=<USERNAME>\nexport TS_SELENIUM_OCP_PASSWORD=<PASSWORD>\nexport USERSTORY=<TESTNAME>\n\n# Run with full local code mounted (testing changes vs origin/main)\npodman rm -f selenium-e2e 2>/dev/null\npodman run -it --shm-size=2g -p 5920:5920 --env-host -v $(pwd):/tmp/e2e:Z quay.io/eclipse/che-e2e:next\n```\n\n#### No Test Code Changes vs origin/main (mount only report directory):\n\n```bash\ncd tests/e2e\n\n# Set environment variables\nexport NODE_TLS_REJECT_UNAUTHORIZED=0\nexport TS_SELENIUM_BASE_URL=<URL>\nexport TS_SELENIUM_OCP_USERNAME=<USERNAME>\nexport TS_SELENIUM_OCP_PASSWORD=<PASSWORD>\nexport USERSTORY=<TESTNAME>\n\n# Run with only report directory mounted (no changes vs origin/main, uses published image code)\npodman rm -f selenium-e2e 2>/dev/null\npodman run -it --shm-size=2g -p 5920:5920 --env-host -v $(pwd)/report:/tmp/e2e/report:Z quay.io/eclipse/che-e2e:next\n```\n\n---\n\n## Step 7: Execute After User Confirmation\n\nBefore executing, present the generated command(s) to the user and ask for confirmation.\n\n### Confirmation prompt:\n\n> \"Here is the command I will run:\n>\n> ```bash\n> <generated command>\n> ```\n>\n> **Summary:**\n>\n> - **Platform:** [OpenShift/Kubernetes]\n> - **Product:** [Eclipse Che/Dev Spaces]\n> - **Test:** [TESTNAME]\n> - **Method:** [Local browser/Podman container]\n> - **URL:** [URL]\n>\n> Shall I proceed?\"\n\n### After confirmation:\n\n1. Execute the bash command(s)\n2. Monitor the output\n3. Report success or failure\n4. If using Podman, remind user about VNC access at `localhost:5920`\n\n---\n\n## Debugging Options\n\n### Verbose Logging\n\n```bash\nexport TS_SELENIUM_LOG_LEVEL=TRACE\nexport TS_SELENIUM_PRINT_TIMEOUT_VARIABLES=true\nexport TS_DEBUG_MODE=true\n```\n\n### Headless Mode (CI)\n\n```bash\nexport TS_SELENIUM_HEADLESS=true\n```\n\n### Video Recording\n\n```bash\nexport VIDEO_RECORDING=true\n```\n\n### Keep Workspace on Failure\n\n```bash\nexport TS_DELETE_WORKSPACE_ON_FAILED_TEST=false\n```\n\n---\n\n## VNC Access for Visual Debugging (Podman only)\n\nConnect to VNC at `localhost:5920` to watch tests running in the container.\n\n**VNC clients:**\n\n- **TigerVNC** (recommended): `sudo dnf install tigervnc` then `vncviewer localhost:5920`\n- **Remmina**: `sudo dnf install remmina remmina-plugins-vnc` (GUI-based)\n- **Vinagre**: `sudo dnf install vinagre` then `vinagre localhost:5920`\n\n---\n\n## View Test Reports\n\nAfter test execution:\n\n```bash\nnpm run open-allure-dasboard\n```\n\n---\n\n## Troubleshooting\n\n### Certificate Errors\n\n```bash\nexport NODE_TLS_REJECT_UNAUTHORIZED=0\n```\n\n### Slow Workspace Start\n\n```bash\nexport TS_SELENIUM_START_WORKSPACE_TIMEOUT=600000\nexport TS_SELENIUM_DEFAULT_ATTEMPTS=3\n```\n\n### OAuth Login Issues\n\nVerify the login provider title matches exactly what appears on the login page:\n\n```bash\nexport TS_OCP_LOGIN_PAGE_PROVIDER_TITLE=\"htpasswd\"\n```\n\n### Container Cleanup\n\n```bash\npodman rm -f selenium-e2e\n```\n\n---\n\n## Debugging TypeScript Tests in VS Code\n\nFor debugging E2E tests with breakpoints, see: https://code.visualstudio.com/docs/typescript/typescript-debugging\n","tests/e2e/.claude/skills/explore-and-generate-tests/SKILL.md":"---\nname: explore-and-generate-tests\ndescription: Explore a web page with new functionality using Playwright, record UI interactions, develop a test plan, and generate E2E Mocha/Selenium test code following the project's code style and patterns.\n---\n\n# Explore and Generate E2E Tests\n\nYou are a Software Quality Engineer exploring new functionality in Eclipse Che / Red Hat OpenShift Dev Spaces. Your job is to explore a web page, record what you find, develop a test plan, and write production-ready E2E test code.\n\n## Prerequisites\n\n- Playwright MCP server must be configured (see `.mcp.json` in this repo)\n- Read `CODE_STYLE.md` before generating any code\n\n## Input Parameters\n\nThe user provides:\n\n- **page** - URL or route to explore (e.g., `/#/create-workspace`, `/#/user-preferences`, a full URL)\n- **scope** - (optional) What to focus on: a specific feature, button, form, or \"everything\"\n- **test-cases-repo** - (optional) Path to the test-cases repo for generating Polarion test case markdown (default: `/home/ndp/projects/cci/test-cases`)\n\n## Workflow\n\n### Phase 0: Authenticate\n\nBefore exploring, ensure you have an authenticated browser session.\n\n1. **Determine the target URL:**\n\n    - If the user provided a full URL, extract the base URL\n    - If the user provided only a route (e.g., `/#/create-workspace`), ask for the base URL\n\n2. **Ask for credentials** (if not already provided):\n\n    - Use AskUserQuestion to collect:\n        - **URL** (if not provided) — Dev Spaces or Eclipse Che base URL\n        - **Platform** — `openshift` or `kubernetes` (auto-detect from URL: `devspaces` in URL → openshift; `che` on localhost/minikube → kubernetes)\n        - **Username** — login username\n        - **Password** — login password\n        - **Identity Provider** — (OpenShift only, default: `htpasswd`)\n\n3. **Login via Playwright MCP:**\n\n    Navigate to the base URL using `browser_navigate`, then take a `browser_snapshot` to see the login page state. The login flow depends on the platform:\n\n    #### OpenShift (Dev Spaces / Eclipse Che on OpenShift)\n\n    - Click the identity provider link (e.g., \"htpasswd\") using `browser_click`\n    - Take a `browser_snapshot` to see the login form\n    - Type username into the username field using `browser_type`\n    - Type password into the password field using `browser_type`\n    - Click the \"Log in\" button using `browser_click`\n    - If an authorization page appears (\"Allow selected permissions\"), click to approve using `browser_click`\n\n    #### Kubernetes / Minikube (Eclipse Che with Dex)\n\n    - The login page is a Dex login form — take a `browser_snapshot` to confirm\n    - Click \"Log in with Email\" if multiple login options are shown\n    - Type email/username into the email field using `browser_type`\n    - Type password into the password field using `browser_type`\n    - Click the \"Login\" button using `browser_click`\n    - If a grant access page appears, click \"Grant Access\" using `browser_click`\n\n    #### Common post-login steps\n\n    - Take a `browser_snapshot` to confirm the dashboard loaded successfully\n    - If login fails, report the error and stop\n\n4. **Set environment context** for later use in generated test code:\n    - Remember the base URL as `TS_SELENIUM_BASE_URL`\n    - Remember the platform as `TS_PLATFORM` (`openshift` or `kubernetes`)\n    - For OpenShift: remember username as `TS_SELENIUM_OCP_USERNAME`, identity provider as `TS_OCP_LOGIN_PAGE_PROVIDER_TITLE`\n    - For Kubernetes: remember username as `TS_SELENIUM_K8S_USERNAME`\n\n### Phase 1: Explore the Page\n\nUse Playwright MCP tools to systematically explore the target page.\n\n1. **Navigate** to the target page using `browser_navigate`\n2. **Take a snapshot** using `browser_snapshot` to capture the full accessibility tree\n3. **Identify all interactive elements**: buttons, links, forms, dropdowns, toggles, tabs, modals\n4. **Explore each interactive element**:\n    - Click tabs/navigation items to discover sub-pages\n    - Open dropdowns to see options\n    - Hover over elements to discover tooltips or hidden UI\n    - Check for conditional UI (elements that appear/disappear based on state)\n5. **Document the page structure** as you go:\n    - Page layout and sections\n    - Navigation flows (what leads where)\n    - Forms and their fields (required vs optional, validation rules)\n    - Action buttons and their effects\n    - Error states and edge cases visible in the UI\n\n**Important:**\n\n- Use `browser_snapshot` (accessibility tree) as the primary exploration method\n- Use `browser_click`, `browser_fill_form`, `browser_hover` to interact with elements\n- Use `browser_wait_for` when pages need time to load\n- Take snapshots AFTER each interaction to see the resulting state\n- Do NOT use screenshots — use snapshots for structured data\n\n### Phase 2: Record the Interactions\n\nAfter exploring, produce a structured record of the functionality:\n\n```markdown\n## Page: [Page Name]\n\nURL: [full URL or route]\n\n### UI Elements\n\n| Element                 | Type       | Locator Strategy                                | Behavior                      |\n| ----------------------- | ---------- | ----------------------------------------------- | ----------------------------- |\n| Create Workspace button | button     | By.xpath('//button[text()=\"Create Workspace\"]') | Opens workspace creation form |\n| Git Repo URL input      | text input | By.id('git-repo-url')                           | Accepts git repository URL    |\n| ...                     | ...        | ...                                             | ...                           |\n\n### User Workflows\n\n1. **[Workflow Name]** (e.g., \"Create workspace from git repo\")\n\n    - Step 1: [action] → [expected result]\n    - Step 2: [action] → [expected result]\n    - ...\n\n2. **[Workflow Name]** (e.g., \"Cancel workspace creation\")\n    - Step 1: ...\n\n### Edge Cases & Error States\n\n- [What happens when invalid input is provided]\n- [What happens when network is slow]\n- [Empty states, loading states]\n```\n\n### Phase 3: Develop Test Plan\n\nBased on the recorded interactions, create a comprehensive test plan:\n\n1. **Categorize test scenarios** by priority:\n\n    - **P1 (High)**: Core happy-path workflows that must work\n    - **P2 (Medium)**: Alternative flows, input validation, error handling\n    - **P3 (Low)**: Edge cases, cosmetic checks, accessibility\n\n2. **Define test cases** using this structure:\n\n    ```\n    Test ID: [CATEGORY_LETTER][NUMBER]\n    Title: [descriptive title]\n    Type: positive | negative\n    Priority: high | medium | low\n    Preconditions: [what must be true before the test]\n    Steps:\n      1. [action] → Expected: [result]\n      2. [action] → Expected: [result]\n    ```\n\n3. **Group tests into suites** by feature area or workflow\n\n4. **Present the test plan to the user** and ask for approval before proceeding to code generation. The user may want to:\n    - Add or remove test cases\n    - Change priorities\n    - Modify the scope\n    - Split into multiple specs\n\n### Phase 4: Generate E2E Test Code\n\nAfter the user approves the test plan, generate production-ready test code.\n\n#### Step 4.1: Read Required Reference Files\n\nBefore writing any code, read these files to understand current patterns:\n\n```\nCODE_STYLE.md                           # Coding standards\nconfigs/inversify.types.ts              # Existing class registry\nconfigs/inversify.config.ts             # Existing DI bindings\nconstants/TIMEOUT_CONSTANTS.ts          # Available timeout constants\n```\n\nAlso read 1-2 existing specs and page objects similar to what you're generating, to match the exact style.\n\n#### Step 4.2: Generate Page Objects (if needed)\n\nFor each new page or component that needs interaction, create a page object:\n\n**File:** `pageobjects/<category>/NewPage.ts`\n\n```typescript\n/** *******************************************************************\n * copyright (c) 2026 Red Hat, Inc.\n *\n * This program and the accompanying materials are made\n * available under the terms of the Eclipse Public License 2.0\n * which is available at https://www.eclipse.org/legal/epl-2.0/\n *\n * SPDX-License-Identifier: EPL-2.0\n **********************************************************************/\n\nimport { inject, injectable } from 'inversify';\nimport 'reflect-metadata';\nimport { By } from 'selenium-webdriver';\nimport { CLASSES } from '../../configs/inversify.types';\nimport { DriverHelper } from '../../utils/DriverHelper';\nimport { Logger } from '../../utils/Logger';\nimport { TIMEOUT_CONSTANTS } from '../../constants/TIMEOUT_CONSTANTS';\n\n@injectable()\nexport class NewPage {\n\tprivate static readonly ELEMENT_LOCATOR: By = By.xpath('//...');\n\n\tconstructor(@inject(CLASSES.DriverHelper) private readonly driverHelper: DriverHelper) {}\n\n\tasync waitPage(timeout: number = TIMEOUT_CONSTANTS.TS_COMMON_DASHBOARD_WAIT_TIMEOUT): Promise<void> {\n\t\tLogger.debug();\n\t\tawait this.driverHelper.waitVisibility(NewPage.ELEMENT_LOCATOR, timeout);\n\t}\n\n\tprivate getDynamicLocator(name: string): By {\n\t\treturn By.xpath(`//div[text()=\"${name}\"]`);\n\t}\n}\n```\n\n**Rules for page objects:**\n\n- `@injectable()` decorator on the class\n- `Logger.debug()` at the start of every public method\n- Static locators as `private static readonly NAME: By`\n- Dynamic locators as `private` methods returning `By`\n- Constructor injection with `@inject()` decorators\n- Explicit return types on ALL methods\n- NO `public` keyword on public methods\n- Use XPath as primary locator strategy (matching existing patterns)\n- Use timeout constants from `TIMEOUT_CONSTANTS`, not hardcoded values\n\n#### Step 4.3: Generate Test Spec\n\n**File:** `specs/<category>/NewFeature.spec.ts`\n\n```typescript\n/** *******************************************************************\n * copyright (c) 2026 Red Hat, Inc.\n *\n * This program and the accompanying materials are made\n * available under the terms of the Eclipse Public License 2.0\n * which is available at https://www.eclipse.org/legal/epl-2.0/\n *\n * SPDX-License-Identifier: EPL-2.0\n **********************************************************************/\n\nimport { e2eContainer } from '../../configs/inversify.config';\nimport { CLASSES, TYPES } from '../../configs/inversify.types';\nimport { LoginTests } from '../../tests-library/LoginTests';\nimport { WorkspaceHandlingTests } from '../../tests-library/WorkspaceHandlingTests';\nimport { BASE_TEST_CONSTANTS } from '../../constants/BASE_TEST_CONSTANTS';\n// ... other imports\n\nsuite(`New Feature ${BASE_TEST_CONSTANTS.TEST_ENVIRONMENT}`, function (): void {\n\tconst loginTests: LoginTests = e2eContainer.get(CLASSES.LoginTests);\n\t// ... other DI injections\n\n\tsuiteSetup('Login', async function (): Promise<void> {\n\t\tawait loginTests.loginIntoChe();\n\t});\n\n\ttest('Test case description', async function (): Promise<void> {\n\t\t// test implementation\n\t});\n\n\tsuiteTeardown('Cleanup', async function (): Promise<void> {\n\t\t// cleanup code\n\t});\n});\n```\n\n**Rules for test specs:**\n\n- Mocha TDD style: `suite()`, `test()`, `suiteSetup()`, `suiteTeardown()`\n- NEVER use arrow functions in Mocha declarations\n- Get DI instances inside `suite()` function body\n- Always include login in `suiteSetup`\n- Always include cleanup in `suiteTeardown`\n- Use `function (): void` (not arrow functions) for all callbacks\n\n#### Step 4.4: Register New Components\n\nIf you created new page objects, update:\n\n1. **`configs/inversify.types.ts`** - Add class name to `CLASSES` object\n2. **`configs/inversify.config.ts`** - Add import and `e2eContainer.bind<>()` call\n\n#### Step 4.5: Generate Polarion Test Case Markdown (optional)\n\nIf the user wants test cases synced to Polarion, also generate markdown files in the test-cases repo:\n\n**File:** `<test-cases-repo>/tests/<functional|operational>/<category>/<id>-<title>.md`\n\n```yaml\n---\narch:\n    - value: x86_64\n      assignee: <ask user>\ncaseposneg: positive | negative\ncaseautomation: automated\ncaseimportance: high | medium | low\ncustomerscenario: false\nestimate: 1h\n---\n# <ID> - <Test Title>\n\n## Description\n- Test script: <link to the generated spec file in GitHub>\n```\n\nUse the test-cases repo's `npm run rename` to validate filenames after generating.\n\n### Phase 5: Validate\n\nAfter generating all code:\n\n1. **Run TypeScript compilation**: `npm run tsc` to catch type errors\n2. **Run linter**: `npm run lint` to catch style violations\n3. **Run prettier**: `npm run prettier` to fix formatting\n4. **Review the generated code** against `CODE_STYLE.md` rules\n5. **Present a summary** to the user:\n    - List of files created/modified\n    - Test cases and their priorities\n    - Any manual steps needed (e.g., running the tests, updating Polarion)\n\n## Locator Strategy Guide\n\nWhen translating Playwright MCP snapshot elements to Selenium locators:\n\n| Playwright Snapshot            | Selenium Locator                                          |\n| ------------------------------ | --------------------------------------------------------- |\n| `role=\"button\" name=\"Create\"`  | `By.xpath('//button[text()=\"Create\"]')`                   |\n| `role=\"textbox\" name=\"URL\"`    | `By.xpath('//input[@aria-label=\"URL\"]')`                  |\n| `role=\"link\" name=\"Settings\"`  | `By.xpath('//a[text()=\"Settings\"]')`                      |\n| `role=\"tab\" name=\"Git Repo\"`   | `By.xpath('//button[@role=\"tab\" and text()=\"Git Repo\"]')` |\n| `data-testid=\"workspace-name\"` | `By.css('[data-testid=\"workspace-name\"]')`                |\n| `id=\"input-field\"`             | `By.id('input-field')`                                    |\n\n**Prefer this locator priority:**\n\n1. `data-testid` attributes (most stable)\n2. `id` attributes\n3. XPath with text content (for buttons, links)\n4. XPath with `aria-label` (for inputs, form fields)\n5. XPath with structural path (last resort — fragile)\n\n## Example Invocations\n\n```\nexplore and generate tests for /#/create-workspace\nexplore /#/user-preferences and write E2E tests for the settings page\ngenerate tests for the new workspace sharing feature at /#/workspaces\n```\n"},"items":[{"name":"SKILL.md","path":"tests/e2e/.claude/skills/e2e-test-developer/SKILL.md","rawUrl":"https://raw.githubusercontent.com/eclipse-che/che/HEAD/tests/e2e/.claude/skills/e2e-test-developer/SKILL.md","title":"e2e-test-developer","category":"anthropic-skill","format":"markdown","content":"---\nname: e2e-test-developer\ndescription: 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.\n---\n\n# Eclipse Che E2E TypeScript Mocha Selenium Test Development Skill\n\nYou 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.\n\n## Project Overview\n\nThis is the E2E test suite for Eclipse Che / Red Hat OpenShift Dev Spaces. It uses:\n\n- **Selenium WebDriver** with Chrome browser\n- **Mocha** (TDD style - `suite()`, `test()`, `suiteSetup()`, `suiteTeardown()`)\n- **TypeScript** with strict type checking\n- **Inversify** for dependency injection\n- **Chai** for assertions\n- **Allure** for test reporting\n\n## Directory Structure\n\n| Directory            | Purpose                                                                                                  |\n| -------------------- | -------------------------------------------------------------------------------------------------------- |\n| `specs/`             | Test specifications organized by category (api/, factory/, dashboard-samples/, miscellaneous/)           |\n| `pageobjects/`       | Page Object classes for UI elements (dashboard/, ide/, login/, openshift/, git-providers/, webterminal/) |\n| `utils/`             | Utilities (DriverHelper, BrowserTabsUtil, Logger, API handlers, KubernetesCommandLineToolsExecutor)      |\n| `tests-library/`     | Reusable test helpers (WorkspaceHandlingTests, LoginTests, ProjectAndFileTests)                          |\n| `constants/`         | Environment variable mappings (BASE_TEST_CONSTANTS, TIMEOUT_CONSTANTS, FACTORY_TEST_CONSTANTS, etc.)     |\n| `configs/`           | Mocha config, Inversify container (inversify.config.ts), shell scripts                                   |\n| `suites/`            | Test suite configurations for different environments                                                     |\n| `driver/`            | Chrome driver configuration                                                                              |\n| `build/dockerfiles/` | Docker image for running tests                                                                           |\n\n## Essential Commands\n\n```bash\n# Install dependencies\nnpm ci\n\n# Lint and format\nnpm run lint\nnpm run prettier\n\n# Build TypeScript only\nnpm run tsc\n\n# Run all tests (requires environment variables)\nexport TS_SELENIUM_BASE_URL=<che-url>\nexport TS_SELENIUM_OCP_USERNAME=<username>\nexport TS_SELENIUM_OCP_PASSWORD=<password>\nnpm run test\n\n# Run a single test file (without .spec.ts extension)\nexport USERSTORY=SmokeTest\nnpm run test\n\n# Run API-only tests (no browser)\nexport USERSTORY=EmptyWorkspaceAPI\nnpm run driver-less-test\n\n# View Allure test report\nnpm run open-allure-dasboard\n```\n\n## CODE STYLE REQUIREMENTS (CRITICAL)\n\n### File Header (Required for ALL .ts files)\n\nEvery TypeScript file MUST start with this exact header:\n\n```typescript\n/** *******************************************************************\n * copyright (c) 2026 Red Hat, Inc.\n *\n * This program and the accompanying materials are made\n * available under the terms of the Eclipse Public License 2.0\n * which is available at https://www.eclipse.org/legal/epl-2.0/\n *\n * SPDX-License-Identifier: EPL-2.0\n **********************************************************************/\n```\n\n### Page Object and Utility Classes\n\n1. **Class Declaration with Dependency Injection**\n    - Use `@injectable()` decorator on ALL page objects and utilities\n    - Use constructor injection with `@inject()` decorators\n\n```typescript\nimport { inject, injectable } from 'inversify';\nimport 'reflect-metadata';\nimport { CLASSES } from '../../configs/inversify.types';\n\n@injectable()\nexport class MyPageObject {\n\tconstructor(@inject(CLASSES.DriverHelper) private readonly driverHelper: DriverHelper) {}\n}\n```\n\n2. **Public Methods**\n    - Declare public methods WITHOUT the `public` keyword\n    - Add `Logger.debug()` at the START of every public method\n    - Always specify explicit return types\n\n```typescript\nasync clickButton(timeout: number = TIMEOUT_CONSTANTS.TS_CLICK_DASHBOARD_ITEM_TIMEOUT): Promise<void> {\n    Logger.debug();\n    await this.driverHelper.waitAndClick(MyPage.BUTTON_LOCATOR, timeout);\n}\n```\n\n3. **Locators**\n    - Static locators: `private static readonly` fields of type `By`\n    - Dynamic locators: `private` methods that return `By`\n    - NEVER declare locators as constants inside methods\n\n```typescript\n// Static locators (correct)\nprivate static readonly SUBMIT_BUTTON: By = By.xpath('//button[@type=\"submit\"]');\nprivate static readonly INPUT_FIELD: By = By.id('input-field');\n\n// Dynamic locators (correct)\nprivate getItemLocator(itemName: string): By {\n    return By.xpath(`//div[text()=\"${itemName}\"]`);\n}\n\n// WRONG - Never do this inside a method\nasync wrongMethod(): Promise<void> {\n    const locator: By = By.xpath('//button'); // AVOID THIS\n}\n```\n\n### Member Ordering (Enforced by ESLint)\n\nClasses must follow this order:\n\n1. Static fields\n2. Public fields\n3. Instance fields\n4. Protected fields\n5. Private fields\n6. Abstract fields\n7. Constructor\n8. Public static methods\n9. Protected static methods\n10. Private static methods\n11. Public methods\n12. Protected methods\n13. Private methods\n\n### Test File Conventions\n\n1. **Naming**\n\n    - UI tests: `*.spec.ts` (e.g., `Factory.spec.ts`)\n    - API-only tests: `*API.spec.ts` (e.g., `EmptyWorkspaceAPI.spec.ts`)\n\n2. **Mocha TDD Style (Required)**\n    - Use `suite()`, `test()`, `suiteSetup()`, `suiteTeardown()`\n    - NEVER use arrow functions in test declarations (Mocha context issue)\n\n```typescript\nsuite('My Test Suite', function (): void {\n\t// Inject dependencies inside suite() to avoid unnecessary execution\n\tconst dashboard: Dashboard = e2eContainer.get(CLASSES.Dashboard);\n\tconst loginTests: LoginTests = e2eContainer.get(CLASSES.LoginTests);\n\n\tsuiteSetup('Login to application', async function (): Promise<void> {\n\t\tawait loginTests.loginIntoChe();\n\t});\n\n\ttest('Verify dashboard is visible', async function (): Promise<void> {\n\t\tawait dashboard.waitPage();\n\t});\n\n\tsuiteTeardown('Cleanup', async function (): Promise<void> {\n\t\t// cleanup code\n\t});\n});\n```\n\n3. **Dependency Injection in Tests**\n    - Import container: `import { e2eContainer } from '../../configs/inversify.config';`\n    - Import types: `import { CLASSES, TYPES } from '../../configs/inversify.types';`\n    - Get instances inside suite() function\n\n```typescript\nimport { e2eContainer } from '../../configs/inversify.config';\nimport { CLASSES, TYPES } from '../../configs/inversify.types';\n\nsuite('Test Suite', function (): void {\n\tconst workspaceHandlingTests: WorkspaceHandlingTests = e2eContainer.get(CLASSES.WorkspaceHandlingTests);\n\tconst testWorkspaceUtil: ITestWorkspaceUtil = e2eContainer.get(TYPES.WorkspaceUtil);\n\t// ... test implementation\n});\n```\n\n### TypeScript Requirements\n\n1. **Explicit Type Annotations Required**\n    - All parameters must have type annotations\n    - All property declarations must have type annotations\n    - All variable declarations must have type annotations\n    - All functions must have explicit return types\n\n```typescript\n// Correct\nasync function doSomething(param: string, timeout: number): Promise<void> {\n\tconst result: string = await someOperation();\n}\n\n// Wrong - missing types\nasync function doSomething(param, timeout) {\n\tconst result = await someOperation();\n}\n```\n\n2. **Naming Conventions**\n\n    - Variables: camelCase or UPPER_CASE\n    - No leading/trailing underscores\n\n3. **String Quotes**\n\n    - Use single quotes for strings\n\n4. **Comments**\n    - Comments must start with lowercase (capitalized-comments: never)\n    - Mark workarounds with `// todo` and issue number: `// todo commented due to issue crw-1010`\n\n### Prettier and ESLint\n\n- Pre-commit hooks run automatically via Husky\n- Run `npm run prettier` to fix formatting\n- Run `npm run lint` to fix linting issues\n\n## Environment Variables\n\nCore variables (defined in `constants/` directory):\n\n| Variable                              | Description                                  |\n| ------------------------------------- | -------------------------------------------- |\n| `TS_SELENIUM_BASE_URL`                | Che/DevSpaces dashboard URL                  |\n| `TS_SELENIUM_OCP_USERNAME`            | OpenShift username                           |\n| `TS_SELENIUM_OCP_PASSWORD`            | OpenShift password                           |\n| `USERSTORY`                           | Specific test file to run (without .spec.ts) |\n| `TS_PLATFORM`                         | `openshift` (default) or `kubernetes`        |\n| `TS_SELENIUM_FACTORY_GIT_REPO_URL`    | Git repo for factory tests                   |\n| `TS_SELENIUM_VALUE_OPENSHIFT_OAUTH`   | Enable OAuth (true/false)                    |\n| `TS_SELENIUM_LOG_LEVEL`               | Logging level (TRACE, DEBUG, INFO, etc.)     |\n| `DELETE_WORKSPACE_ON_SUCCESSFUL_TEST` | Delete workspace on success                  |\n\n## Adding New Page Objects\n\n1. Create file in appropriate `pageobjects/` subdirectory\n2. Add `@injectable()` decorator\n3. Register in `configs/inversify.config.ts`\n4. Add class identifier in `configs/inversify.types.ts`\n\n```typescript\n// 1. Create pageobjects/dashboard/NewPage.ts\n@injectable()\nexport class NewPage {\n\tprivate static readonly ELEMENT_LOCATOR: By = By.id('element');\n\n\tconstructor(@inject(CLASSES.DriverHelper) private readonly driverHelper: DriverHelper) {}\n\n\tasync waitForElement(): Promise<void> {\n\t\tLogger.debug();\n\t\tawait this.driverHelper.waitVisibility(NewPage.ELEMENT_LOCATOR);\n\t}\n}\n\n// 2. Add to configs/inversify.types.ts\nconst CLASSES: any = {\n\t// ... existing classes\n\tNewPage: 'NewPage'\n};\n\n// 3. Add to configs/inversify.config.ts\nimport { NewPage } from '../pageobjects/dashboard/NewPage';\ne2eContainer.bind<NewPage>(CLASSES.NewPage).to(NewPage);\n```\n\n## Adding New Tests\n\n1. Create file in appropriate `specs/` subdirectory\n2. Follow naming convention (*.spec.ts for UI, *API.spec.ts for API)\n3. Use TDD style (suite, test, suiteSetup, suiteTeardown)\n4. Run with `export USERSTORY=<filename-without-extension> && npm run test`\n\n## Common Utilities\n\n### DriverHelper (utils/DriverHelper.ts)\n\n- `waitVisibility(locator, timeout)` - Wait for element to be visible\n- `waitAndClick(locator, timeout)` - Wait and click element\n- `waitAndGetText(locator, timeout)` - Wait and get text\n- `waitDisappearance(locator, timeout)` - Wait for element to disappear\n- `navigateToUrl(url)` - Navigate to URL\n- `wait(ms)` - Wait for specified milliseconds\n\n### BrowserTabsUtil (utils/BrowserTabsUtil.ts)\n\n- `navigateTo(url)` - Navigate to URL\n- `getCurrentUrl()` - Get current URL\n- `maximize()` - Maximize browser window\n- `closeAllTabsExceptCurrent()` - Close extra tabs\n\n### Logger (utils/Logger.ts)\n\n- `Logger.debug()` - Log method name (use at start of public methods)\n- `Logger.info(message)` - Log info message\n- `Logger.error(message)` - Log error message\n\n## GitHub Actions Maintenance\n\n### PR Check Workflow (.github/workflows/pr-check.yml)\n\nTriggers on:\n\n- Pull requests to main or 7.\\*\\*.x branches\n- Changes in `tests/e2e/**` or the workflow file\n\nSteps performed:\n\n1. Prettier check - Fails if formatting issues found\n2. TypeScript compilation - `npm run tsc`\n3. ESLint check - `npm run lint`\n4. Deploy Che on minikube\n5. Run Empty Workspace API test\n6. Build E2E Docker image\n7. Run Empty Workspace UI test\n\nWhen modifying tests:\n\n- Ensure `npm run prettier` passes locally\n- Ensure `npm run tsc` compiles without errors\n- Ensure `npm run lint` passes\n- Test locally if possible before pushing\n\n## Test Patterns\n\n### Workspace Lifecycle Pattern\n\n```typescript\nsuite('Workspace Test', function (): void {\n\tconst workspaceHandlingTests: WorkspaceHandlingTests = e2eContainer.get(CLASSES.WorkspaceHandlingTests);\n\tconst testWorkspaceUtil: ITestWorkspaceUtil = e2eContainer.get(TYPES.WorkspaceUtil);\n\tconst loginTests: LoginTests = e2eContainer.get(CLASSES.LoginTests);\n\n\tsuiteSetup('Login', async function (): Promise<void> {\n\t\tawait loginTests.loginIntoChe();\n\t});\n\n\ttest('Create workspace', async function (): Promise<void> {\n\t\tawait workspaceHandlingTests.createAndStartWorkspace();\n\t});\n\n\ttest('Obtain workspace name', async function (): Promise<void> {\n\t\tawait workspaceHandlingTests.obtainWorkspaceNameFromStartingPage();\n\t});\n\n\ttest('Register running workspace', function (): void {\n\t\tregisterRunningWorkspace(WorkspaceHandlingTests.getWorkspaceName());\n\t});\n\n\tsuiteTeardown('Stop and delete workspace', async function (): Promise<void> {\n\t\tawait testWorkspaceUtil.stopAndDeleteWorkspaceByName(WorkspaceHandlingTests.getWorkspaceName());\n\t});\n});\n```\n\n### API Test Pattern (No Browser)\n\n```typescript\nsuite('API Test', function (): void {\n\tconst kubernetesCommandLineToolsExecutor: KubernetesCommandLineToolsExecutor = e2eContainer.get(\n\t\tCLASSES.KubernetesCommandLineToolsExecutor\n\t);\n\n\tsuiteSetup('Setup', async function (): Promise<void> {\n\t\tkubernetesCommandLineToolsExecutor.loginToOcp();\n\t});\n\n\ttest('Execute API operation', function (): void {\n\t\tconst output: ShellString = kubernetesCommandLineToolsExecutor.applyAndWaitDevWorkspace(yaml);\n\t\texpect(output.stdout).contains('condition met');\n\t});\n\n\tsuiteTeardown('Cleanup', function (): void {\n\t\tkubernetesCommandLineToolsExecutor.deleteDevWorkspace();\n\t});\n});\n```\n\n## Package Management\n\n- Add packages as **dev dependencies**: `npm install --save-dev <package>`\n- After any changes to package.json, regenerate lock file: `npm install`\n- Run `npm ci` for clean install from lock file\n\n## Debugging\n\n- Set `TS_SELENIUM_LOG_LEVEL=TRACE` for verbose logging\n- Use VNC on port 5920 when running in Docker\n- View test results with `npm run open-allure-dasboard`\n- Screenshots are captured on test failures by CheReporter\n\n## Common Issues and Solutions\n\n1. **Flaky Tests**: Increase timeout or add explicit waits with `DriverHelper.wait()`\n2. **Element Not Found**: Verify locator with browser dev tools, check for dynamic loading\n3. **Stale Element**: Re-fetch element after page navigation\n4. **Timeout Errors**: Use appropriate timeout constants from `TIMEOUT_CONSTANTS`\n\n## Maintenance Guidelines\n\nWhen helping with E2E test development and maintenance:\n\n1. **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`.\n\n2. **Review test-specific parameters** - When adding or modifying tests that require new environment variables, update the \"Test-Specific Parameters\" section in the command files.\n\n3. **Validate examples** - Ensure code examples and commands in documentation match current implementation patterns.\n\n4. **Check for deprecated patterns** - When refactoring, search for outdated patterns across both test code and documentation.\n\n5. **Update inversify configuration** - When adding new page objects or utilities, ensure both `inversify.types.ts` and `inversify.config.ts` are updated.\n\n## Related Commands\n\n### `/run-e2e-test` - Unified E2E Test Runner\n\nRun E2E tests against **Eclipse Che** or **Red Hat OpenShift Dev Spaces** using either a **local Chrome browser** or **Podman container**.\n\n**Usage:** `/run-e2e-test [URL USERNAME PASSWORD [TESTNAME] [METHOD]]`\n\n**Features:**\n\n- **Interactive mode**: Run without arguments to be prompted for all parameters\n- **Auto-detect platform**: Determines Eclipse Che vs Dev Spaces from URL\n- **Smart rebuild**: Detects local code changes vs `origin/main` and rebuilds only when needed\n- **Test-specific parameters**: Prompts for additional env vars based on selected test type\n\n**Examples:**\n\n- `/run-e2e-test` - Interactive mode (recommended)\n- `/run-e2e-test https://devspaces.apps.example.com/ admin password SmokeTest npm`\n- `/run-e2e-test https://che.apps.example.com/ admin password EmptyWorkspace podman`\n\n**Run methods:**\n\n- `npm` - Local Chrome browser (faster, see browser in real-time)\n- `podman` - Isolated container with VNC support at `localhost:5920`\n","frontmatter":{"name":"e2e-test-developer","description":"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."},"isInternal":false,"tokens":3641,"sizeBytes":16360},{"name":"SKILL.md","path":"tests/e2e/.claude/skills/explore-and-generate-tests/SKILL.md","rawUrl":"https://raw.githubusercontent.com/eclipse-che/che/HEAD/tests/e2e/.claude/skills/explore-and-generate-tests/SKILL.md","title":"explore-and-generate-tests","category":"anthropic-skill","format":"markdown","content":"---\nname: explore-and-generate-tests\ndescription: Explore a web page with new functionality using Playwright, record UI interactions, develop a test plan, and generate E2E Mocha/Selenium test code following the project's code style and patterns.\n---\n\n# Explore and Generate E2E Tests\n\nYou are a Software Quality Engineer exploring new functionality in Eclipse Che / Red Hat OpenShift Dev Spaces. Your job is to explore a web page, record what you find, develop a test plan, and write production-ready E2E test code.\n\n## Prerequisites\n\n- Playwright MCP server must be configured (see `.mcp.json` in this repo)\n- Read `CODE_STYLE.md` before generating any code\n\n## Input Parameters\n\nThe user provides:\n\n- **page** - URL or route to explore (e.g., `/#/create-workspace`, `/#/user-preferences`, a full URL)\n- **scope** - (optional) What to focus on: a specific feature, button, form, or \"everything\"\n- **test-cases-repo** - (optional) Path to the test-cases repo for generating Polarion test case markdown (default: `/home/ndp/projects/cci/test-cases`)\n\n## Workflow\n\n### Phase 0: Authenticate\n\nBefore exploring, ensure you have an authenticated browser session.\n\n1. **Determine the target URL:**\n\n    - If the user provided a full URL, extract the base URL\n    - If the user provided only a route (e.g., `/#/create-workspace`), ask for the base URL\n\n2. **Ask for credentials** (if not already provided):\n\n    - Use AskUserQuestion to collect:\n        - **URL** (if not provided) — Dev Spaces or Eclipse Che base URL\n        - **Platform** — `openshift` or `kubernetes` (auto-detect from URL: `devspaces` in URL → openshift; `che` on localhost/minikube → kubernetes)\n        - **Username** — login username\n        - **Password** — login password\n        - **Identity Provider** — (OpenShift only, default: `htpasswd`)\n\n3. **Login via Playwright MCP:**\n\n    Navigate to the base URL using `browser_navigate`, then take a `browser_snapshot` to see the login page state. The login flow depends on the platform:\n\n    #### OpenShift (Dev Spaces / Eclipse Che on OpenShift)\n\n    - Click the identity provider link (e.g., \"htpasswd\") using `browser_click`\n    - Take a `browser_snapshot` to see the login form\n    - Type username into the username field using `browser_type`\n    - Type password into the password field using `browser_type`\n    - Click the \"Log in\" button using `browser_click`\n    - If an authorization page appears (\"Allow selected permissions\"), click to approve using `browser_click`\n\n    #### Kubernetes / Minikube (Eclipse Che with Dex)\n\n    - The login page is a Dex login form — take a `browser_snapshot` to confirm\n    - Click \"Log in with Email\" if multiple login options are shown\n    - Type email/username into the email field using `browser_type`\n    - Type password into the password field using `browser_type`\n    - Click the \"Login\" button using `browser_click`\n    - If a grant access page appears, click \"Grant Access\" using `browser_click`\n\n    #### Common post-login steps\n\n    - Take a `browser_snapshot` to confirm the dashboard loaded successfully\n    - If login fails, report the error and stop\n\n4. **Set environment context** for later use in generated test code:\n    - Remember the base URL as `TS_SELENIUM_BASE_URL`\n    - Remember the platform as `TS_PLATFORM` (`openshift` or `kubernetes`)\n    - For OpenShift: remember username as `TS_SELENIUM_OCP_USERNAME`, identity provider as `TS_OCP_LOGIN_PAGE_PROVIDER_TITLE`\n    - For Kubernetes: remember username as `TS_SELENIUM_K8S_USERNAME`\n\n### Phase 1: Explore the Page\n\nUse Playwright MCP tools to systematically explore the target page.\n\n1. **Navigate** to the target page using `browser_navigate`\n2. **Take a snapshot** using `browser_snapshot` to capture the full accessibility tree\n3. **Identify all interactive elements**: buttons, links, forms, dropdowns, toggles, tabs, modals\n4. **Explore each interactive element**:\n    - Click tabs/navigation items to discover sub-pages\n    - Open dropdowns to see options\n    - Hover over elements to discover tooltips or hidden UI\n    - Check for conditional UI (elements that appear/disappear based on state)\n5. **Document the page structure** as you go:\n    - Page layout and sections\n    - Navigation flows (what leads where)\n    - Forms and their fields (required vs optional, validation rules)\n    - Action buttons and their effects\n    - Error states and edge cases visible in the UI\n\n**Important:**\n\n- Use `browser_snapshot` (accessibility tree) as the primary exploration method\n- Use `browser_click`, `browser_fill_form`, `browser_hover` to interact with elements\n- Use `browser_wait_for` when pages need time to load\n- Take snapshots AFTER each interaction to see the resulting state\n- Do NOT use screenshots — use snapshots for structured data\n\n### Phase 2: Record the Interactions\n\nAfter exploring, produce a structured record of the functionality:\n\n```markdown\n## Page: [Page Name]\n\nURL: [full URL or route]\n\n### UI Elements\n\n| Element                 | Type       | Locator Strategy                                | Behavior                      |\n| ----------------------- | ---------- | ----------------------------------------------- | ----------------------------- |\n| Create Workspace button | button     | By.xpath('//button[text()=\"Create Workspace\"]') | Opens workspace creation form |\n| Git Repo URL input      | text input | By.id('git-repo-url')                           | Accepts git repository URL    |\n| ...                     | ...        | ...                                             | ...                           |\n\n### User Workflows\n\n1. **[Workflow Name]** (e.g., \"Create workspace from git repo\")\n\n    - Step 1: [action] → [expected result]\n    - Step 2: [action] → [expected result]\n    - ...\n\n2. **[Workflow Name]** (e.g., \"Cancel workspace creation\")\n    - Step 1: ...\n\n### Edge Cases & Error States\n\n- [What happens when invalid input is provided]\n- [What happens when network is slow]\n- [Empty states, loading states]\n```\n\n### Phase 3: Develop Test Plan\n\nBased on the recorded interactions, create a comprehensive test plan:\n\n1. **Categorize test scenarios** by priority:\n\n    - **P1 (High)**: Core happy-path workflows that must work\n    - **P2 (Medium)**: Alternative flows, input validation, error handling\n    - **P3 (Low)**: Edge cases, cosmetic checks, accessibility\n\n2. **Define test cases** using this structure:\n\n    ```\n    Test ID: [CATEGORY_LETTER][NUMBER]\n    Title: [descriptive title]\n    Type: positive | negative\n    Priority: high | medium | low\n    Preconditions: [what must be true before the test]\n    Steps:\n      1. [action] → Expected: [result]\n      2. [action] → Expected: [result]\n    ```\n\n3. **Group tests into suites** by feature area or workflow\n\n4. **Present the test plan to the user** and ask for approval before proceeding to code generation. The user may want to:\n    - Add or remove test cases\n    - Change priorities\n    - Modify the scope\n    - Split into multiple specs\n\n### Phase 4: Generate E2E Test Code\n\nAfter the user approves the test plan, generate production-ready test code.\n\n#### Step 4.1: Read Required Reference Files\n\nBefore writing any code, read these files to understand current patterns:\n\n```\nCODE_STYLE.md                           # Coding standards\nconfigs/inversify.types.ts              # Existing class registry\nconfigs/inversify.config.ts             # Existing DI bindings\nconstants/TIMEOUT_CONSTANTS.ts          # Available timeout constants\n```\n\nAlso read 1-2 existing specs and page objects similar to what you're generating, to match the exact style.\n\n#### Step 4.2: Generate Page Objects (if needed)\n\nFor each new page or component that needs interaction, create a page object:\n\n**File:** `pageobjects/<category>/NewPage.ts`\n\n```typescript\n/** *******************************************************************\n * copyright (c) 2026 Red Hat, Inc.\n *\n * This program and the accompanying materials are made\n * available under the terms of the Eclipse Public License 2.0\n * which is available at https://www.eclipse.org/legal/epl-2.0/\n *\n * SPDX-License-Identifier: EPL-2.0\n **********************************************************************/\n\nimport { inject, injectable } from 'inversify';\nimport 'reflect-metadata';\nimport { By } from 'selenium-webdriver';\nimport { CLASSES } from '../../configs/inversify.types';\nimport { DriverHelper } from '../../utils/DriverHelper';\nimport { Logger } from '../../utils/Logger';\nimport { TIMEOUT_CONSTANTS } from '../../constants/TIMEOUT_CONSTANTS';\n\n@injectable()\nexport class NewPage {\n\tprivate static readonly ELEMENT_LOCATOR: By = By.xpath('//...');\n\n\tconstructor(@inject(CLASSES.DriverHelper) private readonly driverHelper: DriverHelper) {}\n\n\tasync waitPage(timeout: number = TIMEOUT_CONSTANTS.TS_COMMON_DASHBOARD_WAIT_TIMEOUT): Promise<void> {\n\t\tLogger.debug();\n\t\tawait this.driverHelper.waitVisibility(NewPage.ELEMENT_LOCATOR, timeout);\n\t}\n\n\tprivate getDynamicLocator(name: string): By {\n\t\treturn By.xpath(`//div[text()=\"${name}\"]`);\n\t}\n}\n```\n\n**Rules for page objects:**\n\n- `@injectable()` decorator on the class\n- `Logger.debug()` at the start of every public method\n- Static locators as `private static readonly NAME: By`\n- Dynamic locators as `private` methods returning `By`\n- Constructor injection with `@inject()` decorators\n- Explicit return types on ALL methods\n- NO `public` keyword on public methods\n- Use XPath as primary locator strategy (matching existing patterns)\n- Use timeout constants from `TIMEOUT_CONSTANTS`, not hardcoded values\n\n#### Step 4.3: Generate Test Spec\n\n**File:** `specs/<category>/NewFeature.spec.ts`\n\n```typescript\n/** *******************************************************************\n * copyright (c) 2026 Red Hat, Inc.\n *\n * This program and the accompanying materials are made\n * available under the terms of the Eclipse Public License 2.0\n * which is available at https://www.eclipse.org/legal/epl-2.0/\n *\n * SPDX-License-Identifier: EPL-2.0\n **********************************************************************/\n\nimport { e2eContainer } from '../../configs/inversify.config';\nimport { CLASSES, TYPES } from '../../configs/inversify.types';\nimport { LoginTests } from '../../tests-library/LoginTests';\nimport { WorkspaceHandlingTests } from '../../tests-library/WorkspaceHandlingTests';\nimport { BASE_TEST_CONSTANTS } from '../../constants/BASE_TEST_CONSTANTS';\n// ... other imports\n\nsuite(`New Feature ${BASE_TEST_CONSTANTS.TEST_ENVIRONMENT}`, function (): void {\n\tconst loginTests: LoginTests = e2eContainer.get(CLASSES.LoginTests);\n\t// ... other DI injections\n\n\tsuiteSetup('Login', async function (): Promise<void> {\n\t\tawait loginTests.loginIntoChe();\n\t});\n\n\ttest('Test case description', async function (): Promise<void> {\n\t\t// test implementation\n\t});\n\n\tsuiteTeardown('Cleanup', async function (): Promise<void> {\n\t\t// cleanup code\n\t});\n});\n```\n\n**Rules for test specs:**\n\n- Mocha TDD style: `suite()`, `test()`, `suiteSetup()`, `suiteTeardown()`\n- NEVER use arrow functions in Mocha declarations\n- Get DI instances inside `suite()` function body\n- Always include login in `suiteSetup`\n- Always include cleanup in `suiteTeardown`\n- Use `function (): void` (not arrow functions) for all callbacks\n\n#### Step 4.4: Register New Components\n\nIf you created new page objects, update:\n\n1. **`configs/inversify.types.ts`** - Add class name to `CLASSES` object\n2. **`configs/inversify.config.ts`** - Add import and `e2eContainer.bind<>()` call\n\n#### Step 4.5: Generate Polarion Test Case Markdown (optional)\n\nIf the user wants test cases synced to Polarion, also generate markdown files in the test-cases repo:\n\n**File:** `<test-cases-repo>/tests/<functional|operational>/<category>/<id>-<title>.md`\n\n```yaml\n---\narch:\n    - value: x86_64\n      assignee: <ask user>\ncaseposneg: positive | negative\ncaseautomation: automated\ncaseimportance: high | medium | low\ncustomerscenario: false\nestimate: 1h\n---\n# <ID> - <Test Title>\n\n## Description\n- Test script: <link to the generated spec file in GitHub>\n```\n\nUse the test-cases repo's `npm run rename` to validate filenames after generating.\n\n### Phase 5: Validate\n\nAfter generating all code:\n\n1. **Run TypeScript compilation**: `npm run tsc` to catch type errors\n2. **Run linter**: `npm run lint` to catch style violations\n3. **Run prettier**: `npm run prettier` to fix formatting\n4. **Review the generated code** against `CODE_STYLE.md` rules\n5. **Present a summary** to the user:\n    - List of files created/modified\n    - Test cases and their priorities\n    - Any manual steps needed (e.g., running the tests, updating Polarion)\n\n## Locator Strategy Guide\n\nWhen translating Playwright MCP snapshot elements to Selenium locators:\n\n| Playwright Snapshot            | Selenium Locator                                          |\n| ------------------------------ | --------------------------------------------------------- |\n| `role=\"button\" name=\"Create\"`  | `By.xpath('//button[text()=\"Create\"]')`                   |\n| `role=\"textbox\" name=\"URL\"`    | `By.xpath('//input[@aria-label=\"URL\"]')`                  |\n| `role=\"link\" name=\"Settings\"`  | `By.xpath('//a[text()=\"Settings\"]')`                      |\n| `role=\"tab\" name=\"Git Repo\"`   | `By.xpath('//button[@role=\"tab\" and text()=\"Git Repo\"]')` |\n| `data-testid=\"workspace-name\"` | `By.css('[data-testid=\"workspace-name\"]')`                |\n| `id=\"input-field\"`             | `By.id('input-field')`                                    |\n\n**Prefer this locator priority:**\n\n1. `data-testid` attributes (most stable)\n2. `id` attributes\n3. XPath with text content (for buttons, links)\n4. XPath with `aria-label` (for inputs, form fields)\n5. XPath with structural path (last resort — fragile)\n\n## Example Invocations\n\n```\nexplore and generate tests for /#/create-workspace\nexplore /#/user-preferences and write E2E tests for the settings page\ngenerate tests for the new workspace sharing feature at /#/workspaces\n```\n","frontmatter":{"name":"explore-and-generate-tests","description":"Explore a web page with new functionality using Playwright, record UI interactions, develop a test plan, and generate E2E Mocha/Selenium test code following the project's code style and patterns."},"isInternal":false,"tokens":3208,"sizeBytes":13974},{"name":"run-e2e-test.md","path":"tests/e2e/.claude/commands/run-e2e-test.md","rawUrl":"https://raw.githubusercontent.com/eclipse-che/che/HEAD/tests/e2e/.claude/commands/run-e2e-test.md","title":"Command: /run-e2e-test","category":"command-prompt","format":"markdown","content":"# Run E2E Tests - Unified Command\n\nRun E2E tests against **Eclipse Che** or **Red Hat OpenShift Dev Spaces** using either a **local Chrome browser** or **Podman container**.\n\n## Arguments: $ARGUMENTS\n\n**Usage:** `/run-e2e-test [URL USERNAME PASSWORD [TESTNAME]]`\n\n**Examples:**\n\n- `/run-e2e-test` - Interactive mode (recommended)\n- `/run-e2e-test https://devspaces.apps.cluster.example.com/ admin mypassword SmokeTest npm`\n- `/run-e2e-test https://che.apps.cluster.example.com/ admin mypassword EmptyWorkspace podman`\n\n---\n\n## Execution Flow\n\n1. **Parse arguments** - Extract parameters from `$ARGUMENTS`\n2. **Prompt if missing** - Collect missing values via AskUserQuestion\n3. **Auto-detect platform** - Determine Eclipse Che vs Dev Spaces from URL\n4. **Detect local changes** - Check if rebuild is needed based on git status\n5. **Detect test-specific parameters** - Read spec file for required env vars\n6. **Generate bash commands** - Build appropriate bash commands\n7. **Execute** - Run the test after user confirmation\n\n---\n\n## ⚠️ MANDATORY RULES\n\n1. **Never print bash commands with `&& \\` or multi-line continuations using `\\`** - Commands using line continuation characters (`\\`) or chained with `&& \\` cannot be copy-pasted properly from the terminal. Instead, present commands as separate lines or use semicolons for simple chains.\n\n    ❌ **Wrong (line continuations):**\n\n    ```bash\n    cd /path/to/dir && \\\n    npm ci && \\\n    npm run test\n    ```\n\n    ❌ **Wrong (backslash continuations):**\n\n    ```bash\n    podman run -it \\\n      --shm-size=2g \\\n      -p 5920:5920 \\\n      quay.io/eclipse/che-e2e:next\n    ```\n\n    ✅ **Correct (separate lines):**\n\n    ```bash\n    cd /path/to/dir\n    npm ci\n    npm run test\n    ```\n\n    ✅ **Correct (single line for podman):**\n\n    ```bash\n    podman run -it --shm-size=2g -p 5920:5920 quay.io/eclipse/che-e2e:next\n    ```\n\n---\n\n## Step 1: Parse Arguments\n\nParse `$ARGUMENTS` to extract positional parameters:\n\n```\n$ARGUMENTS = \"URL USERNAME PASSWORD [TESTNAME]\"\n```\n\n**Parsing logic:**\n\n1. Split `$ARGUMENTS` by whitespace\n2. Assign to variables: `URL`, `USERNAME`, `PASSWORD`, `TESTNAME`, `RUN_METHOD`\n3. If `TESTNAME` is not provided, default to `EmptyWorkspace`\n\n**Example:**\n\n```\nInput: \"https://devspaces.apps.example.com/ admin secret123 SmokeTest npm\"\nResult:\n  - URL = \"https://devspaces.apps.example.com/\"\n  - USERNAME = \"admin\"\n  - PASSWORD = \"secret123\"\n  - TESTNAME = \"SmokeTest\"\n  - RUN_METHOD = \"npm\"\n```\n\n---\n\n## Step 2: Prompt for Missing Values\n\nIf `$ARGUMENTS` is empty or incomplete, use **AskUserQuestion** to collect missing parameters.\n\n### Question 1 - Run Method (header: \"Method\")\n\n- \"How do you want to run the test?\"\n- Options:\n    - Option 1: \"npm run in local browser (Recommended)\" - \"Uses local Chrome and Node.js - faster, see browser in real-time\"\n    - Option 2: \"Podman container\" - \"Isolated environment with VNC support at localhost:5920\"\n\n### Question 2 - Execution Mode (header: \"Build\")\n\n- \"How should local changes be handled?\"\n- Options:\n    - Option 1: \"Build and run (Recommended)\" - \"Compare to origin/main, rebuild if any changes exist\"\n    - Option 2: \"Just run\" - \"Skip build, assume code is already compiled\"\n\n### Question 3 - Test Name (header: \"Test\")\n\n- \"Which test do you want to run?\"\n- Options:\n    - Option 1: \"EmptyWorkspace (Recommended)\" - \"Basic workspace creation test\"\n    - Option 2: \"SmokeTest\" - \"Quick validation of core functionality\"\n    - Option 3: \"Factory\" - \"Factory URL workspace creation with git operations\"\n    - Option 4: \"EmptyWorkspaceAPI\" - \"API-only test (no browser needed)\"\n\n### For URL - Ask in plain text:\n\n> \"What is the dashboard URL? (e.g., https://devspaces.apps.cluster.example.com/ or https://che.apps.cluster.example.com/)\"\n\n### For Username and Password - Ask in plain text:\n\n> \"Please provide your credentials:\n>\n> - **Username**: (e.g., admin, user)\n> - **Password**: your password\"\n\nWait for the user's response before proceeding.\n\n---\n\n## Step 3: Auto-detect Testing Product from URL\n\nAnalyze the URL to determine which product is being tested and set appropriate defaults:\n\n| URL Pattern            | Product                          | Auto-configured Settings                                                                                                                                          |\n| ---------------------- | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| Contains `devspaces`   | **Red Hat OpenShift Dev Spaces** | `TS_SELENIUM_VALUE_OPENSHIFT_OAUTH=true`, `TS_OCP_LOGIN_PAGE_PROVIDER_TITLE=htpasswd` `TS_PLATFORM=openshift`                                                     |\n| Contains `eclipse-che` | **Eclipse Che**                  | May need OAuth config based on deployment. I host contains `crw-qe.com` - it is `TS_PLATFORM=openshift`. Otherwise ask user for platform: openshift or kubernetes |\n\n**Detection logic:**\n\n```bash\nif [[ \"$URL\" == *\"devspaces\"* ]]; then\n  PRODUCT=\"devspaces\"\n  TS_SELENIUM_VALUE_OPENSHIFT_OAUTH=true\n  TS_OCP_LOGIN_PAGE_PROVIDER_TITLE=\"htpasswd\"\nelif [[ \"$URL\" == *\"eclipse-che\"* ]]; then\n  PRODUCT=\"che\"\nfi\n```\n\n---\n\n## Step 4: Detect Local Changes\n\nCheck the git status to determine if code needs to be rebuilt before running tests.\n\n**Important:** Ignore changes to documentation and Claude configuration files:\n\n- `CLAUDE.md`\n- `.claude/**/*`\n- `*.md` files (documentation)\n\n### For Local Browser Method:\n\n**Smart rebuild based on all changes vs origin/main** - rebuild when there are any test code modifications compared to origin/main.\n\n**Logic:**\n\n1. Compare working tree to `origin/main` to detect all changes (committed, staged, and unstaged)\n2. Rebuild if any test code changes exist compared to `origin/main` (excluding docs/config files)\n\n```bash\nNEEDS_REBUILD=false\n\n# Get current branch name for informational purposes\nCURRENT_BRANCH=$(git branch --show-current)\necho \"Current branch: $CURRENT_BRANCH\"\n\n# Check for any changes compared to origin/main (committed, staged, and unstaged)\n# Excludes docs and Claude config files\nCODE_CHANGES=$(git diff --name-only origin/main -- . 2>/dev/null | grep -v -E '(CLAUDE\\.md|\\.claude/|README\\.md|\\.md$)')\n\nif [[ -n \"$CODE_CHANGES\" ]]; then\n  echo \"Code changes detected compared to origin/main:\"\n  echo \"$CODE_CHANGES\"\n  NEEDS_REBUILD=true\nelse\n  echo \"No test code changes compared to origin/main - no rebuild needed\"\nfi\n\necho \"Rebuild needed: $NEEDS_REBUILD\"\n```\n\n### For Podman Container Method:\n\n**Smart mounting based on all changes vs origin/main** - mount full directory when there are any test code modifications compared to origin/main.\n\n**Logic:**\n\n1. Compare working tree to `origin/main` to detect all changes (committed, staged, and unstaged)\n2. Mount full directory if any test code changes exist compared to `origin/main` (excluding docs/config files)\n\n```bash\nE2E_IMAGE=\"quay.io/eclipse/che-e2e:next\"\nMOUNT_FULL_CODE=false\n\n# Get current branch name for informational purposes\nCURRENT_BRANCH=$(git branch --show-current)\necho \"Current branch: $CURRENT_BRANCH\"\n\n# Check for any changes compared to origin/main (committed, staged, and unstaged)\n# Excludes docs and Claude config files\nCODE_CHANGES=$(git diff --name-only origin/main -- . 2>/dev/null | grep -v -E '(CLAUDE\\.md|\\.claude/|README\\.md|\\.md$)')\n\nif [[ -n \"$CODE_CHANGES\" ]]; then\n  echo \"Code changes detected compared to origin/main:\"\n  echo \"$CODE_CHANGES\"\n  MOUNT_FULL_CODE=true\n  PODMAN_MOUNT=\"-v $(pwd):/tmp/e2e:Z\"\nelse\n  echo \"No test code changes compared to origin/main - using published image code\"\n  PODMAN_MOUNT=\"-v $(pwd)/report:/tmp/e2e/report:Z\"\nfi\n\necho \"Mount strategy: $PODMAN_MOUNT\"\n```\n\n---\n\n## Step 5: Detect Test-Specific Parameters\n\nAfter getting the test name, read the test spec file to identify additional required environment variables.\n\n### Find and read the spec file:\n\n```bash\n# Find the test file\nSPEC_FILE=$(find specs -name \"${USERSTORY}.spec.ts\" 2>/dev/null | head -1)\n\nif [[ -n \"$SPEC_FILE\" ]]; then\n  echo \"Found test file: $SPEC_FILE\"\n  # Read the file to identify required environment variables\n  cat \"$SPEC_FILE\"\nfi\n```\n\n### Platform-Specific Configurations\n\n#### OpenShift\n\n```bash\nexport TS_PLATFORM=openshift\nexport TS_SELENIUM_VALUE_OPENSHIFT_OAUTH=true\nexport TS_OCP_LOGIN_PAGE_PROVIDER_TITLE=\"htpasswd\"\n```\n\n#### Kubernetes\n\n```bash\nexport TS_PLATFORM=kubernetes\nexport TS_SELENIUM_K8S_USERNAME=che@eclipse.org\nexport TS_SELENIUM_K8S_PASSWORD=admin\nexport TS_SELENIUM_VALUE_OPENSHIFT_OAUTH=false\n```\n\n### Test-specific parameters by category:\n\n#### Factory URL Tests (Factory, RefusedOAuthFactory, NoSetupRepoFactory, etc.)\n\nThese tests navigate directly to a factory URL to create a workspace.\n**Ask for these additional parameters:**\n\n- **Git Repository URL** (`TS_SELENIUM_FACTORY_GIT_REPO_URL`) - Repository to clone\n- **Git Provider** (`TS_SELENIUM_FACTORY_GIT_PROVIDER`) - `github`, `gitlab`, `bitbucket`, `azure-devops`\n- **OAuth Enabled** (`TS_SELENIUM_GIT_PROVIDER_OAUTH`) - `true` or `false`\n\n#### Git Repo Import Form Tests (FactoryWithGitRepoOptions, CreateWorkspaceWithExistingNameFromGitUrl, etc.)\n\nThese tests use the Dashboard's \"Create Workspace\" form.\n**Ask for these additional parameters:**\n\n- **Git Repository URL** (`TS_SELENIUM_FACTORY_GIT_REPO_URL`) - Repository URL\n- **Branch Name** (`TS_SELENIUM_FACTORY_GIT_REPO_BRANCH`) - Optional (default: `main`)\n- **Project Name** (`TS_SELENIUM_PROJECT_NAME`) - Optional\n\n#### API Tests (EmptyWorkspaceAPI, ContainerOverridesAPI, etc.)\n\n**Ask for these additional parameters:**\n\n- **Namespace** (`TS_API_TEST_NAMESPACE`) - Optional\n- **UDI Image** (`TS_API_TEST_UDI_IMAGE`) - Optional\n\n#### Sample/Devfile Tests\n\n**Ask for these additional parameters:**\n\n- **Sample List** (`TS_SAMPLE_LIST`) - e.g., `Node.js Express`\n\n#### SSH/PAT Tests (SshUrlNoOauthPatFactory, etc.)\n\n**Ask for these additional parameters:**\n\n- **SSH Private Key Path** or **Personal Access Token** details\n\n---\n\n## Step 6: Generate Bash Commands\n\nBased on all collected parameters, generate the appropriate bash commands.\n\n### Option A: Local Browser Commands\n\n#### Browser/ChromeDriver Compatibility Check\n\nBefore running tests, verify ChromeDriver compatibility with local Chrome browser:\n\n```bash\n# Get local Chrome version\nCHROME_VERSION=$(google-chrome --version 2>/dev/null | grep -oP '\\d+' | head -1)\necho \"Local Chrome major version: $CHROME_VERSION\"\n\n# Get ChromeDriver version from package.json\nCHROMEDRIVER_VERSION=$(npm list chromedriver --depth=0 2>/dev/null | grep -oP 'chromedriver@\\K[\\d.]+')\necho \"ChromeDriver version in package.json: $CHROMEDRIVER_VERSION\"\n\n# If versions don't match, suggest installing compatible driver\nif [[ \"${CHROMEDRIVER_VERSION%%.*}\" != \"$CHROME_VERSION\" ]]; then\n  echo \"WARNING: ChromeDriver version mismatch!\"\n  echo \"Install compatible version: npm install chromedriver@$CHROME_VERSION\"\nfi\n```\n\n**If ChromeDriver is incompatible**, install a matching version:\n\n```bash\nnpm install chromedriver@<chrome-major-version>\n```\n\n#### Full Test Run (Build and Run)\n\n```bash\ncd tests/e2e\n\n# Use correct Node.js version\nnvm use v22.10.0\n\n# Get current branch name for informational purposes\nCURRENT_BRANCH=$(git branch --show-current)\necho \"Current branch: $CURRENT_BRANCH\"\n\n# Check for any changes compared to origin/main (committed, staged, and unstaged)\n# Excludes docs and Claude config files\nCODE_CHANGES=$(git diff --name-only origin/main -- . 2>/dev/null | grep -v -E '(CLAUDE\\.md|\\.claude/|README\\.md|\\.md$)')\n\nif [[ -n \"$CODE_CHANGES\" ]]; then\n  echo \"Code changes detected compared to origin/main:\"\n  echo \"$CODE_CHANGES\"\n  NEEDS_REBUILD=true\nelse\n  echo \"No test code changes compared to origin/main - skipping rebuild\"\n  NEEDS_REBUILD=false\nfi\n\n# Check if package.json was modified compared to origin/main\nPACKAGE_JSON_CHANGED=$(git diff --name-only origin/main -- package.json 2>/dev/null)\n\n# Rebuild if changes detected compared to origin/main\nif [[ \"$NEEDS_REBUILD\" == \"true\" ]]; then\n  echo \"Rebuilding TypeScript...\"\n  rm -rf node_modules dist\n\n  # Use npm install if package.json changed, otherwise npm ci\n  if [[ -n \"$PACKAGE_JSON_CHANGED\" ]]; then\n    echo \"package.json modified - using npm install to update package-lock.json\"\n    npm install\n  else\n    npm ci\n  fi\n\n  npm run lint\n  npm run prettier\n  npm run tsc\nfi\n\n# Set environment variables\nexport TS_SELENIUM_BASE_URL=\"<URL>\"\nexport TS_SELENIUM_OCP_USERNAME=\"<USERNAME>\"\nexport TS_SELENIUM_OCP_PASSWORD=\"<PASSWORD>\"\nexport USERSTORY=\"<TESTNAME>\"\nexport TS_SELENIUM_VALUE_OPENSHIFT_OAUTH=true\nexport NODE_TLS_REJECT_UNAUTHORIZED=0\n\n# Run the test\nnpm run test\n```\n\n#### API-Only Tests (No Browser)\n\n```bash\nexport USERSTORY=EmptyWorkspaceAPI\nnpm run driver-less-test\n```\n\n### Option B: Podman Container Commands\n\n**Important:** Use `--env-host` to pass all exported environment variables from the host to the container.\n\n**Smart mounting logic based on changes vs origin/main:**\n\n- **Changes compared to origin/main**: Mount full directory `-v $(pwd):/tmp/e2e:Z` to test local code\n- **No changes compared to origin/main**: Mount only report directory `-v $(pwd)/report:/tmp/e2e/report:Z`\n\n#### With Test Code Changes vs origin/main (mount full directory):\n\nUse this when there are test code changes compared to `origin/main` (works for feature branches or local main with unpushed changes).\n\n```bash\ncd tests/e2e\n\n# Set environment variables\nexport NODE_TLS_REJECT_UNAUTHORIZED=0\nexport TS_SELENIUM_BASE_URL=<URL>\nexport TS_SELENIUM_OCP_USERNAME=<USERNAME>\nexport TS_SELENIUM_OCP_PASSWORD=<PASSWORD>\nexport USERSTORY=<TESTNAME>\n\n# Run with full local code mounted (testing changes vs origin/main)\npodman rm -f selenium-e2e 2>/dev/null\npodman run -it --shm-size=2g -p 5920:5920 --env-host -v $(pwd):/tmp/e2e:Z quay.io/eclipse/che-e2e:next\n```\n\n#### No Test Code Changes vs origin/main (mount only report directory):\n\n```bash\ncd tests/e2e\n\n# Set environment variables\nexport NODE_TLS_REJECT_UNAUTHORIZED=0\nexport TS_SELENIUM_BASE_URL=<URL>\nexport TS_SELENIUM_OCP_USERNAME=<USERNAME>\nexport TS_SELENIUM_OCP_PASSWORD=<PASSWORD>\nexport USERSTORY=<TESTNAME>\n\n# Run with only report directory mounted (no changes vs origin/main, uses published image code)\npodman rm -f selenium-e2e 2>/dev/null\npodman run -it --shm-size=2g -p 5920:5920 --env-host -v $(pwd)/report:/tmp/e2e/report:Z quay.io/eclipse/che-e2e:next\n```\n\n---\n\n## Step 7: Execute After User Confirmation\n\nBefore executing, present the generated command(s) to the user and ask for confirmation.\n\n### Confirmation prompt:\n\n> \"Here is the command I will run:\n>\n> ```bash\n> <generated command>\n> ```\n>\n> **Summary:**\n>\n> - **Platform:** [OpenShift/Kubernetes]\n> - **Product:** [Eclipse Che/Dev Spaces]\n> - **Test:** [TESTNAME]\n> - **Method:** [Local browser/Podman container]\n> - **URL:** [URL]\n>\n> Shall I proceed?\"\n\n### After confirmation:\n\n1. Execute the bash command(s)\n2. Monitor the output\n3. Report success or failure\n4. If using Podman, remind user about VNC access at `localhost:5920`\n\n---\n\n## Debugging Options\n\n### Verbose Logging\n\n```bash\nexport TS_SELENIUM_LOG_LEVEL=TRACE\nexport TS_SELENIUM_PRINT_TIMEOUT_VARIABLES=true\nexport TS_DEBUG_MODE=true\n```\n\n### Headless Mode (CI)\n\n```bash\nexport TS_SELENIUM_HEADLESS=true\n```\n\n### Video Recording\n\n```bash\nexport VIDEO_RECORDING=true\n```\n\n### Keep Workspace on Failure\n\n```bash\nexport TS_DELETE_WORKSPACE_ON_FAILED_TEST=false\n```\n\n---\n\n## VNC Access for Visual Debugging (Podman only)\n\nConnect to VNC at `localhost:5920` to watch tests running in the container.\n\n**VNC clients:**\n\n- **TigerVNC** (recommended): `sudo dnf install tigervnc` then `vncviewer localhost:5920`\n- **Remmina**: `sudo dnf install remmina remmina-plugins-vnc` (GUI-based)\n- **Vinagre**: `sudo dnf install vinagre` then `vinagre localhost:5920`\n\n---\n\n## View Test Reports\n\nAfter test execution:\n\n```bash\nnpm run open-allure-dasboard\n```\n\n---\n\n## Troubleshooting\n\n### Certificate Errors\n\n```bash\nexport NODE_TLS_REJECT_UNAUTHORIZED=0\n```\n\n### Slow Workspace Start\n\n```bash\nexport TS_SELENIUM_START_WORKSPACE_TIMEOUT=600000\nexport TS_SELENIUM_DEFAULT_ATTEMPTS=3\n```\n\n### OAuth Login Issues\n\nVerify the login provider title matches exactly what appears on the login page:\n\n```bash\nexport TS_OCP_LOGIN_PAGE_PROVIDER_TITLE=\"htpasswd\"\n```\n\n### Container Cleanup\n\n```bash\npodman rm -f selenium-e2e\n```\n\n---\n\n## Debugging TypeScript Tests in VS Code\n\nFor debugging E2E tests with breakpoints, see: https://code.visualstudio.com/docs/typescript/typescript-debugging\n","isInternal":false,"tokens":4229,"sizeBytes":16631}],"systemPromptSnippet":"<yakaai_skills repo=\"eclipse-che/che\">\n<!-- File: tests/e2e/.claude/skills/e2e-test-developer/SKILL.md (Tokens: ~3641 | Category: anthropic-skill) -->\n---\nname: e2e-test-developer\ndescription: 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.\n---\n\n# Eclipse Che E2E TypeScript Mocha Selenium Test Development Skill\n\nYou 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.\n\n## Project Overview\n\nThis is the E2E test suite for Eclipse Che / Red Hat OpenShift Dev Spaces. It uses:\n\n- **Selenium WebDriver** with Chrome browser\n- **Mocha** (TDD style - `suite()`, `test()`, `suiteSetup()`, `suiteTeardown()`)\n- **TypeScript** with strict type checking\n- **Inversify** for dependency injection\n- **Chai** for assertions\n- **Allure** for test reporting\n\n## Directory Structure\n\n| Directory            | Purpose                                                                                                  |\n| -------------------- | -------------------------------------------------------------------------------------------------------- |\n| `specs/`             | Test specifications organized by category (api/, factory/, dashboard-samples/, miscellaneous/)           |\n| `pageobjects/`       | Page Object classes for UI elements (dashboard/, ide/, login/, openshift/, git-providers/, webterminal/) |\n| `utils/`             | Utilities (DriverHelper, BrowserTabsUtil, Logger, API handlers, KubernetesCommandLineToolsExecutor)      |\n| `tests-library/`     | Reusable test helpers (WorkspaceHandlingTests, LoginTests, ProjectAndFileTests)                          |\n| `constants/`         | Environment variable mappings (BASE_TEST_CONSTANTS, TIMEOUT_CONSTANTS, FACTORY_TEST_CONSTANTS, etc.)     |\n| `configs/`           | Mocha config, Inversify container (inversify.config.ts), shell scripts                                   |\n| `suites/`            | Test suite configurations for different environments                                                     |\n| `driver/`            | Chrome driver configuration                                                                              |\n| `build/dockerfiles/` | Docker image for running tests                                                                           |\n\n## Essential Commands\n\n```bash\n# Install dependencies\nnpm ci\n\n# Lint and format\nnpm run lint\nnpm run prettier\n\n# Build TypeScript only\nnpm run tsc\n\n# Run all tests (requires environment variables)\nexport TS_SELENIUM_BASE_URL=<che-url>\nexport TS_SELENIUM_OCP_USERNAME=<username>\nexport TS_SELENIUM_OCP_PASSWORD=<password>\nnpm run test\n\n# Run a single test file (without .spec.ts extension)\nexport USERSTORY=SmokeTest\nnpm run test\n\n# Run API-only tests (no browser)\nexport USERSTORY=EmptyWorkspaceAPI\nnpm run driver-less-test\n\n# View Allure test report\nnpm run open-allure-dasboard\n```\n\n## CODE STYLE REQUIREMENTS (CRITICAL)\n\n### File Header (Required for ALL .ts files)\n\nEvery TypeScript file MUST start with this exact header:\n\n```typescript\n/** *******************************************************************\n * copyright (c) 2026 Red Hat, Inc.\n *\n * This program and the accompanying materials are made\n * available under the terms of the Eclipse Public License 2.0\n * which is available at https://www.eclipse.org/legal/epl-2.0/\n *\n * SPDX-License-Identifier: EPL-2.0\n **********************************************************************/\n```\n\n### Page Object and Utility Classes\n\n1. **Class Declaration with Dependency Injection**\n    - Use `@injectable()` decorator on ALL page objects and utilities\n    - Use constructor injection with `@inject()` decorators\n\n```typescript\nimport { inject, injectable } from 'inversify';\nimport 'reflect-metadata';\nimport { CLASSES } from '../../configs/inversify.types';\n\n@injectable()\nexport class MyPageObject {\n\tconstructor(@inject(CLASSES.DriverHelper) private readonly driverHelper: DriverHelper) {}\n}\n```\n\n2. **Public Methods**\n    - Declare public methods WITHOUT the `public` keyword\n    - Add `Logger.debug()` at the START of every public method\n    - Always specify explicit return types\n\n```typescript\nasync clickButton(timeout: number = TIMEOUT_CONSTANTS.TS_CLICK_DASHBOARD_ITEM_TIMEOUT): Promise<void> {\n    Logger.debug();\n    await this.driverHelper.waitAndClick(MyPage.BUTTON_LOCATOR, timeout);\n}\n```\n\n3. **Locators**\n    - Static locators: `private static readonly` fields of type `By`\n    - Dynamic locators: `private` methods that return `By`\n    - NEVER declare locators as constants inside methods\n\n```typescript\n// Static locators (correct)\nprivate static readonly SUBMIT_BUTTON: By = By.xpath('//button[@type=\"submit\"]');\nprivate static readonly INPUT_FIELD: By = By.id('input-field');\n\n// Dynamic locators (correct)\nprivate getItemLocator(itemName: string): By {\n    return By.xpath(`//div[text()=\"${itemName}\"]`);\n}\n\n// WRONG - Never do this inside a method\nasync wrongMethod(): Promise<void> {\n    const locator: By = By.xpath('//button'); // AVOID THIS\n}\n```\n\n### Member Ordering (Enforced by ESLint)\n\nClasses must follow this order:\n\n1. Static fields\n2. Public fields\n3. Instance fields\n4. Protected fields\n5. Private fields\n6. Abstract fields\n7. Constructor\n8. Public static methods\n9. Protected static methods\n10. Private static methods\n11. Public methods\n12. Protected methods\n13. Private methods\n\n### Test File Conventions\n\n1. **Naming**\n\n    - UI tests: `*.spec.ts` (e.g., `Factory.spec.ts`)\n    - API-only tests: `*API.spec.ts` (e.g., `EmptyWorkspaceAPI.spec.ts`)\n\n2. **Mocha TDD Style (Required)**\n    - Use `suite()`, `test()`, `suiteSetup()`, `suiteTeardown()`\n    - NEVER use arrow functions in test declarations (Mocha context issue)\n\n```typescript\nsuite('My Test Suite', function (): void {\n\t// Inject dependencies inside suite() to avoid unnecessary execution\n\tconst dashboard: Dashboard = e2eContainer.get(CLASSES.Dashboard);\n\tconst loginTests: LoginTests = e2eContainer.get(CLASSES.LoginTests);\n\n\tsuiteSetup('Login to application', async function (): Promise<void> {\n\t\tawait loginTests.loginIntoChe();\n\t});\n\n\ttest('Verify dashboard is visible', async function (): Promise<void> {\n\t\tawait dashboard.waitPage();\n\t});\n\n\tsuiteTeardown('Cleanup', async function (): Promise<void> {\n\t\t// cleanup code\n\t});\n});\n```\n\n3. **Dependency Injection in Tests**\n    - Import container: `import { e2eContainer } from '../../configs/inversify.config';`\n    - Import types: `import { CLASSES, TYPES } from '../../configs/inversify.types';`\n    - Get instances inside suite() function\n\n```typescript\nimport { e2eContainer } from '../../configs/inversify.config';\nimport { CLASSES, TYPES } from '../../configs/inversify.types';\n\nsuite('Test Suite', function (): void {\n\tconst workspaceHandlingTests: WorkspaceHandlingTests = e2eContainer.get(CLASSES.WorkspaceHandlingTests);\n\tconst testWorkspaceUtil: ITestWorkspaceUtil = e2eContainer.get(TYPES.WorkspaceUtil);\n\t// ... test implementation\n});\n```\n\n### TypeScript Requirements\n\n1. **Explicit Type Annotations Required**\n    - All parameters must have type annotations\n    - All property declarations must have type annotations\n    - All variable declarations must have type annotations\n    - All functions must have explicit return types\n\n```typescript\n// Correct\nasync function doSomething(param: string, timeout: number): Promise<void> {\n\tconst result: string = await someOperation();\n}\n\n// Wrong - missing types\nasync function doSomething(param, timeout) {\n\tconst result = await someOperation();\n}\n```\n\n2. **Naming Conventions**\n\n    - Variables: camelCase or UPPER_CASE\n    - No leading/trailing underscores\n\n3. **String Quotes**\n\n    - Use single quotes for strings\n\n4. **Comments**\n    - Comments must start with lowercase (capitalized-comments: never)\n    - Mark workarounds with `// todo` and issue number: `// todo commented due to issue crw-1010`\n\n### Prettier and ESLint\n\n- Pre-commit hooks run automatically via Husky\n- Run `npm run prettier` to fix formatting\n- Run `npm run lint` to fix linting issues\n\n## Environment Variables\n\nCore variables (defined in `constants/` directory):\n\n| Variable                              | Description                                  |\n| ------------------------------------- | -------------------------------------------- |\n| `TS_SELENIUM_BASE_URL`                | Che/DevSpaces dashboard URL                  |\n| `TS_SELENIUM_OCP_USERNAME`            | OpenShift username                           |\n| `TS_SELENIUM_OCP_PASSWORD`            | OpenShift password                           |\n| `USERSTORY`                           | Specific test file to run (without .spec.ts) |\n| `TS_PLATFORM`                         | `openshift` (default) or `kubernetes`        |\n| `TS_SELENIUM_FACTORY_GIT_REPO_URL`    | Git repo for factory tests                   |\n| `TS_SELENIUM_VALUE_OPENSHIFT_OAUTH`   | Enable OAuth (true/false)                    |\n| `TS_SELENIUM_LOG_LEVEL`               | Logging level (TRACE, DEBUG, INFO, etc.)     |\n| `DELETE_WORKSPACE_ON_SUCCESSFUL_TEST` | Delete workspace on success                  |\n\n## Adding New Page Objects\n\n1. Create file in appropriate `pageobjects/` subdirectory\n2. Add `@injectable()` decorator\n3. Register in `configs/inversify.config.ts`\n4. Add class identifier in `configs/inversify.types.ts`\n\n```typescript\n// 1. Create pageobjects/dashboard/NewPage.ts\n@injectable()\nexport class NewPage {\n\tprivate static readonly ELEMENT_LOCATOR: By = By.id('element');\n\n\tconstructor(@inject(CLASSES.DriverHelper) private readonly driverHelper: DriverHelper) {}\n\n\tasync waitForElement(): Promise<void> {\n\t\tLogger.debug();\n\t\tawait this.driverHelper.waitVisibility(NewPage.ELEMENT_LOCATOR);\n\t}\n}\n\n// 2. Add to configs/inversify.types.ts\nconst CLASSES: any = {\n\t// ... existing classes\n\tNewPage: 'NewPage'\n};\n\n// 3. Add to configs/inversify.config.ts\nimport { NewPage } from '../pageobjects/dashboard/NewPage';\ne2eContainer.bind<NewPage>(CLASSES.NewPage).to(NewPage);\n```\n\n## Adding New Tests\n\n1. Create file in appropriate `specs/` subdirectory\n2. Follow naming convention (*.spec.ts for UI, *API.spec.ts for API)\n3. Use TDD style (suite, test, suiteSetup, suiteTeardown)\n4. Run with `export USERSTORY=<filename-without-extension> && npm run test`\n\n## Common Utilities\n\n### DriverHelper (utils/DriverHelper.ts)\n\n- `waitVisibility(locator, timeout)` - Wait for element to be visible\n- `waitAndClick(locator, timeout)` - Wait and click element\n- `waitAndGetText(locator, timeout)` - Wait and get text\n- `waitDisappearance(locator, timeout)` - Wait for element to disappear\n- `navigateToUrl(url)` - Navigate to URL\n- `wait(ms)` - Wait for specified milliseconds\n\n### BrowserTabsUtil (utils/BrowserTabsUtil.ts)\n\n- `navigateTo(url)` - Navigate to URL\n- `getCurrentUrl()` - Get current URL\n- `maximize()` - Maximize browser window\n- `closeAllTabsExceptCurrent()` - Close extra tabs\n\n### Logger (utils/Logger.ts)\n\n- `Logger.debug()` - Log method name (use at start of public methods)\n- `Logger.info(message)` - Log info message\n- `Logger.error(message)` - Log error message\n\n## GitHub Actions Maintenance\n\n### PR Check Workflow (.github/workflows/pr-check.yml)\n\nTriggers on:\n\n- Pull requests to main or 7.\\*\\*.x branches\n- Changes in `tests/e2e/**` or the workflow file\n\nSteps performed:\n\n1. Prettier check - Fails if formatting issues found\n2. TypeScript compilation - `npm run tsc`\n3. ESLint check - `npm run lint`\n4. Deploy Che on minikube\n5. Run Empty Workspace API test\n6. Build E2E Docker image\n7. Run Empty Workspace UI test\n\nWhen modifying tests:\n\n- Ensure `npm run prettier` passes locally\n- Ensure `npm run tsc` compiles without errors\n- Ensure `npm run lint` passes\n- Test locally if possible before pushing\n\n## Test Patterns\n\n### Workspace Lifecycle Pattern\n\n```typescript\nsuite('Workspace Test', function (): void {\n\tconst workspaceHandlingTests: WorkspaceHandlingTests = e2eContainer.get(CLASSES.WorkspaceHandlingTests);\n\tconst testWorkspaceUtil: ITestWorkspaceUtil = e2eContainer.get(TYPES.WorkspaceUtil);\n\tconst loginTests: LoginTests = e2eContainer.get(CLASSES.LoginTests);\n\n\tsuiteSetup('Login', async function (): Promise<void> {\n\t\tawait loginTests.loginIntoChe();\n\t});\n\n\ttest('Create workspace', async function (): Promise<void> {\n\t\tawait workspaceHandlingTests.createAndStartWorkspace();\n\t});\n\n\ttest('Obtain workspace name', async function (): Promise<void> {\n\t\tawait workspaceHandlingTests.obtainWorkspaceNameFromStartingPage();\n\t});\n\n\ttest('Register running workspace', function (): void {\n\t\tregisterRunningWorkspace(WorkspaceHandlingTests.getWorkspaceName());\n\t});\n\n\tsuiteTeardown('Stop and delete workspace', async function (): Promise<void> {\n\t\tawait testWorkspaceUtil.stopAndDeleteWorkspaceByName(WorkspaceHandlingTests.getWorkspaceName());\n\t});\n});\n```\n\n### API Test Pattern (No Browser)\n\n```typescript\nsuite('API Test', function (): void {\n\tconst kubernetesCommandLineToolsExecutor: KubernetesCommandLineToolsExecutor = e2eContainer.get(\n\t\tCLASSES.KubernetesCommandLineToolsExecutor\n\t);\n\n\tsuiteSetup('Setup', async function (): Promise<void> {\n\t\tkubernetesCommandLineToolsExecutor.loginToOcp();\n\t});\n\n\ttest('Execute API operation', function (): void {\n\t\tconst output: ShellString = kubernetesCommandLineToolsExecutor.applyAndWaitDevWorkspace(yaml);\n\t\texpect(output.stdout).contains('condition met');\n\t});\n\n\tsuiteTeardown('Cleanup', function (): void {\n\t\tkubernetesCommandLineToolsExecutor.deleteDevWorkspace();\n\t});\n});\n```\n\n## Package Management\n\n- Add packages as **dev dependencies**: `npm install --save-dev <package>`\n- After any changes to package.json, regenerate lock file: `npm install`\n- Run `npm ci` for clean install from lock file\n\n## Debugging\n\n- Set `TS_SELENIUM_LOG_LEVEL=TRACE` for verbose logging\n- Use VNC on port 5920 when running in Docker\n- View test results with `npm run open-allure-dasboard`\n- Screenshots are captured on test failures by CheReporter\n\n## Common Issues and Solutions\n\n1. **Flaky Tests**: Increase timeout or add explicit waits with `DriverHelper.wait()`\n2. **Element Not Found**: Verify locator with browser dev tools, check for dynamic loading\n3. **Stale Element**: Re-fetch element after page navigation\n4. **Timeout Errors**: Use appropriate timeout constants from `TIMEOUT_CONSTANTS`\n\n## Maintenance Guidelines\n\nWhen helping with E2E test development and maintenance:\n\n1. **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`.\n\n2. **Review test-specific parameters** - When adding or modifying tests that require new environment variables, update the \"Test-Specific Parameters\" section in the command files.\n\n3. **Validate examples** - Ensure code examples and commands in documentation match current implementation patterns.\n\n4. **Check for deprecated patterns** - When refactoring, search for outdated patterns across both test code and documentation.\n\n5. **Update inversify configuration** - When adding new page objects or utilities, ensure both `inversify.types.ts` and `inversify.config.ts` are updated.\n\n## Related Commands\n\n### `/run-e2e-test` - Unified E2E Test Runner\n\nRun E2E tests against **Eclipse Che** or **Red Hat OpenShift Dev Spaces** using either a **local Chrome browser** or **Podman container**.\n\n**Usage:** `/run-e2e-test [URL USERNAME PASSWORD [TESTNAME] [METHOD]]`\n\n**Features:**\n\n- **Interactive mode**: Run without arguments to be prompted for all parameters\n- **Auto-detect platform**: Determines Eclipse Che vs Dev Spaces from URL\n- **Smart rebuild**: Detects local code changes vs `origin/main` and rebuilds only when needed\n- **Test-specific parameters**: Prompts for additional env vars based on selected test type\n\n**Examples:**\n\n- `/run-e2e-test` - Interactive mode (recommended)\n- `/run-e2e-test https://devspaces.apps.example.com/ admin password SmokeTest npm`\n- `/run-e2e-test https://che.apps.example.com/ admin password EmptyWorkspace podman`\n\n**Run methods:**\n\n- `npm` - Local Chrome browser (faster, see browser in real-time)\n- `podman` - Isolated container with VNC support at `localhost:5920`\n\n<!-- File: tests/e2e/.claude/skills/explore-and-generate-tests/SKILL.md (Tokens: ~3208 | Category: anthropic-skill) -->\n---\nname: explore-and-generate-tests\ndescription: Explore a web page with new functionality using Playwright, record UI interactions, develop a test plan, and generate E2E Mocha/Selenium test code following the project's code style and patterns.\n---\n\n# Explore and Generate E2E Tests\n\nYou are a Software Quality Engineer exploring new functionality in Eclipse Che / Red Hat OpenShift Dev Spaces. Your job is to explore a web page, record what you find, develop a test plan, and write production-ready E2E test code.\n\n## Prerequisites\n\n- Playwright MCP server must be configured (see `.mcp.json` in this repo)\n- Read `CODE_STYLE.md` before generating any code\n\n## Input Parameters\n\nThe user provides:\n\n- **page** - URL or route to explore (e.g., `/#/create-workspace`, `/#/user-preferences`, a full URL)\n- **scope** - (optional) What to focus on: a specific feature, button, form, or \"everything\"\n- **test-cases-repo** - (optional) Path to the test-cases repo for generating Polarion test case markdown (default: `/home/ndp/projects/cci/test-cases`)\n\n## Workflow\n\n### Phase 0: Authenticate\n\nBefore exploring, ensure you have an authenticated browser session.\n\n1. **Determine the target URL:**\n\n    - If the user provided a full URL, extract the base URL\n    - If the user provided only a route (e.g., `/#/create-workspace`), ask for the base URL\n\n2. **Ask for credentials** (if not already provided):\n\n    - Use AskUserQuestion to collect:\n        - **URL** (if not provided) — Dev Spaces or Eclipse Che base URL\n        - **Platform** — `openshift` or `kubernetes` (auto-detect from URL: `devspaces` in URL → openshift; `che` on localhost/minikube → kubernetes)\n        - **Username** — login username\n        - **Password** — login password\n        - **Identity Provider** — (OpenShift only, default: `htpasswd`)\n\n3. **Login via Playwright MCP:**\n\n    Navigate to the base URL using `browser_navigate`, then take a `browser_snapshot` to see the login page state. The login flow depends on the platform:\n\n    #### OpenShift (Dev Spaces / Eclipse Che on OpenShift)\n\n    - Click the identity provider link (e.g., \"htpasswd\") using `browser_click`\n    - Take a `browser_snapshot` to see the login form\n    - Type username into the username field using `browser_type`\n    - Type password into the password field using `browser_type`\n    - Click the \"Log in\" button using `browser_click`\n    - If an authorization page appears (\"Allow selected permissions\"), click to approve using `browser_click`\n\n    #### Kubernetes / Minikube (Eclipse Che with Dex)\n\n    - The login page is a Dex login form — take a `browser_snapshot` to confirm\n    - Click \"Log in with Email\" if multiple login options are shown\n    - Type email/username into the email field using `browser_type`\n    - Type password into the password field using `browser_type`\n    - Click the \"Login\" button using `browser_click`\n    - If a grant access page appears, click \"Grant Access\" using `browser_click`\n\n    #### Common post-login steps\n\n    - Take a `browser_snapshot` to confirm the dashboard loaded successfully\n    - If login fails, report the error and stop\n\n4. **Set environment context** for later use in generated test code:\n    - Remember the base URL as `TS_SELENIUM_BASE_URL`\n    - Remember the platform as `TS_PLATFORM` (`openshift` or `kubernetes`)\n    - For OpenShift: remember username as `TS_SELENIUM_OCP_USERNAME`, identity provider as `TS_OCP_LOGIN_PAGE_PROVIDER_TITLE`\n    - For Kubernetes: remember username as `TS_SELENIUM_K8S_USERNAME`\n\n### Phase 1: Explore the Page\n\nUse Playwright MCP tools to systematically explore the target page.\n\n1. **Navigate** to the target page using `browser_navigate`\n2. **Take a snapshot** using `browser_snapshot` to capture the full accessibility tree\n3. **Identify all interactive elements**: buttons, links, forms, dropdowns, toggles, tabs, modals\n4. **Explore each interactive element**:\n    - Click tabs/navigation items to discover sub-pages\n    - Open dropdowns to see options\n    - Hover over elements to discover tooltips or hidden UI\n    - Check for conditional UI (elements that appear/disappear based on state)\n5. **Document the page structure** as you go:\n    - Page layout and sections\n    - Navigation flows (what leads where)\n    - Forms and their fields (required vs optional, validation rules)\n    - Action buttons and their effects\n    - Error states and edge cases visible in the UI\n\n**Important:**\n\n- Use `browser_snapshot` (accessibility tree) as the primary exploration method\n- Use `browser_click`, `browser_fill_form`, `browser_hover` to interact with elements\n- Use `browser_wait_for` when pages need time to load\n- Take snapshots AFTER each interaction to see the resulting state\n- Do NOT use screenshots — use snapshots for structured data\n\n### Phase 2: Record the Interactions\n\nAfter exploring, produce a structured record of the functionality:\n\n```markdown\n## Page: [Page Name]\n\nURL: [full URL or route]\n\n### UI Elements\n\n| Element                 | Type       | Locator Strategy                                | Behavior                      |\n| ----------------------- | ---------- | ----------------------------------------------- | ----------------------------- |\n| Create Workspace button | button     | By.xpath('//button[text()=\"Create Workspace\"]') | Opens workspace creation form |\n| Git Repo URL input      | text input | By.id('git-repo-url')                           | Accepts git repository URL    |\n| ...                     | ...        | ...                                             | ...                           |\n\n### User Workflows\n\n1. **[Workflow Name]** (e.g., \"Create workspace from git repo\")\n\n    - Step 1: [action] → [expected result]\n    - Step 2: [action] → [expected result]\n    - ...\n\n2. **[Workflow Name]** (e.g., \"Cancel workspace creation\")\n    - Step 1: ...\n\n### Edge Cases & Error States\n\n- [What happens when invalid input is provided]\n- [What happens when network is slow]\n- [Empty states, loading states]\n```\n\n### Phase 3: Develop Test Plan\n\nBased on the recorded interactions, create a comprehensive test plan:\n\n1. **Categorize test scenarios** by priority:\n\n    - **P1 (High)**: Core happy-path workflows that must work\n    - **P2 (Medium)**: Alternative flows, input validation, error handling\n    - **P3 (Low)**: Edge cases, cosmetic checks, accessibility\n\n2. **Define test cases** using this structure:\n\n    ```\n    Test ID: [CATEGORY_LETTER][NUMBER]\n    Title: [descriptive title]\n    Type: positive | negative\n    Priority: high | medium | low\n    Preconditions: [what must be true before the test]\n    Steps:\n      1. [action] → Expected: [result]\n      2. [action] → Expected: [result]\n    ```\n\n3. **Group tests into suites** by feature area or workflow\n\n4. **Present the test plan to the user** and ask for approval before proceeding to code generation. The user may want to:\n    - Add or remove test cases\n    - Change priorities\n    - Modify the scope\n    - Split into multiple specs\n\n### Phase 4: Generate E2E Test Code\n\nAfter the user approves the test plan, generate production-ready test code.\n\n#### Step 4.1: Read Required Reference Files\n\nBefore writing any code, read these files to understand current patterns:\n\n```\nCODE_STYLE.md                           # Coding standards\nconfigs/inversify.types.ts              # Existing class registry\nconfigs/inversify.config.ts             # Existing DI bindings\nconstants/TIMEOUT_CONSTANTS.ts          # Available timeout constants\n```\n\nAlso read 1-2 existing specs and page objects similar to what you're generating, to match the exact style.\n\n#### Step 4.2: Generate Page Objects (if needed)\n\nFor each new page or component that needs interaction, create a page object:\n\n**File:** `pageobjects/<category>/NewPage.ts`\n\n```typescript\n/** *******************************************************************\n * copyright (c) 2026 Red Hat, Inc.\n *\n * This program and the accompanying materials are made\n * available under the terms of the Eclipse Public License 2.0\n * which is available at https://www.eclipse.org/legal/epl-2.0/\n *\n * SPDX-License-Identifier: EPL-2.0\n **********************************************************************/\n\nimport { inject, injectable } from 'inversify';\nimport 'reflect-metadata';\nimport { By } from 'selenium-webdriver';\nimport { CLASSES } from '../../configs/inversify.types';\nimport { DriverHelper } from '../../utils/DriverHelper';\nimport { Logger } from '../../utils/Logger';\nimport { TIMEOUT_CONSTANTS } from '../../constants/TIMEOUT_CONSTANTS';\n\n@injectable()\nexport class NewPage {\n\tprivate static readonly ELEMENT_LOCATOR: By = By.xpath('//...');\n\n\tconstructor(@inject(CLASSES.DriverHelper) private readonly driverHelper: DriverHelper) {}\n\n\tasync waitPage(timeout: number = TIMEOUT_CONSTANTS.TS_COMMON_DASHBOARD_WAIT_TIMEOUT): Promise<void> {\n\t\tLogger.debug();\n\t\tawait this.driverHelper.waitVisibility(NewPage.ELEMENT_LOCATOR, timeout);\n\t}\n\n\tprivate getDynamicLocator(name: string): By {\n\t\treturn By.xpath(`//div[text()=\"${name}\"]`);\n\t}\n}\n```\n\n**Rules for page objects:**\n\n- `@injectable()` decorator on the class\n- `Logger.debug()` at the start of every public method\n- Static locators as `private static readonly NAME: By`\n- Dynamic locators as `private` methods returning `By`\n- Constructor injection with `@inject()` decorators\n- Explicit return types on ALL methods\n- NO `public` keyword on public methods\n- Use XPath as primary locator strategy (matching existing patterns)\n- Use timeout constants from `TIMEOUT_CONSTANTS`, not hardcoded values\n\n#### Step 4.3: Generate Test Spec\n\n**File:** `specs/<category>/NewFeature.spec.ts`\n\n```typescript\n/** *******************************************************************\n * copyright (c) 2026 Red Hat, Inc.\n *\n * This program and the accompanying materials are made\n * available under the terms of the Eclipse Public License 2.0\n * which is available at https://www.eclipse.org/legal/epl-2.0/\n *\n * SPDX-License-Identifier: EPL-2.0\n **********************************************************************/\n\nimport { e2eContainer } from '../../configs/inversify.config';\nimport { CLASSES, TYPES } from '../../configs/inversify.types';\nimport { LoginTests } from '../../tests-library/LoginTests';\nimport { WorkspaceHandlingTests } from '../../tests-library/WorkspaceHandlingTests';\nimport { BASE_TEST_CONSTANTS } from '../../constants/BASE_TEST_CONSTANTS';\n// ... other imports\n\nsuite(`New Feature ${BASE_TEST_CONSTANTS.TEST_ENVIRONMENT}`, function (): void {\n\tconst loginTests: LoginTests = e2eContainer.get(CLASSES.LoginTests);\n\t// ... other DI injections\n\n\tsuiteSetup('Login', async function (): Promise<void> {\n\t\tawait loginTests.loginIntoChe();\n\t});\n\n\ttest('Test case description', async function (): Promise<void> {\n\t\t// test implementation\n\t});\n\n\tsuiteTeardown('Cleanup', async function (): Promise<void> {\n\t\t// cleanup code\n\t});\n});\n```\n\n**Rules for test specs:**\n\n- Mocha TDD style: `suite()`, `test()`, `suiteSetup()`, `suiteTeardown()`\n- NEVER use arrow functions in Mocha declarations\n- Get DI instances inside `suite()` function body\n- Always include login in `suiteSetup`\n- Always include cleanup in `suiteTeardown`\n- Use `function (): void` (not arrow functions) for all callbacks\n\n#### Step 4.4: Register New Components\n\nIf you created new page objects, update:\n\n1. **`configs/inversify.types.ts`** - Add class name to `CLASSES` object\n2. **`configs/inversify.config.ts`** - Add import and `e2eContainer.bind<>()` call\n\n#### Step 4.5: Generate Polarion Test Case Markdown (optional)\n\nIf the user wants test cases synced to Polarion, also generate markdown files in the test-cases repo:\n\n**File:** `<test-cases-repo>/tests/<functional|operational>/<category>/<id>-<title>.md`\n\n```yaml\n---\narch:\n    - value: x86_64\n      assignee: <ask user>\ncaseposneg: positive | negative\ncaseautomation: automated\ncaseimportance: high | medium | low\ncustomerscenario: false\nestimate: 1h\n---\n# <ID> - <Test Title>\n\n## Description\n- Test script: <link to the generated spec file in GitHub>\n```\n\nUse the test-cases repo's `npm run rename` to validate filenames after generating.\n\n### Phase 5: Validate\n\nAfter generating all code:\n\n1. **Run TypeScript compilation**: `npm run tsc` to catch type errors\n2. **Run linter**: `npm run lint` to catch style violations\n3. **Run prettier**: `npm run prettier` to fix formatting\n4. **Review the generated code** against `CODE_STYLE.md` rules\n5. **Present a summary** to the user:\n    - List of files created/modified\n    - Test cases and their priorities\n    - Any manual steps needed (e.g., running the tests, updating Polarion)\n\n## Locator Strategy Guide\n\nWhen translating Playwright MCP snapshot elements to Selenium locators:\n\n| Playwright Snapshot            | Selenium Locator                                          |\n| ------------------------------ | --------------------------------------------------------- |\n| `role=\"button\" name=\"Create\"`  | `By.xpath('//button[text()=\"Create\"]')`                   |\n| `role=\"textbox\" name=\"URL\"`    | `By.xpath('//input[@aria-label=\"URL\"]')`                  |\n| `role=\"link\" name=\"Settings\"`  | `By.xpath('//a[text()=\"Settings\"]')`                      |\n| `role=\"tab\" name=\"Git Repo\"`   | `By.xpath('//button[@role=\"tab\" and text()=\"Git Repo\"]')` |\n| `data-testid=\"workspace-name\"` | `By.css('[data-testid=\"workspace-name\"]')`                |\n| `id=\"input-field\"`             | `By.id('input-field')`                                    |\n\n**Prefer this locator priority:**\n\n1. `data-testid` attributes (most stable)\n2. `id` attributes\n3. XPath with text content (for buttons, links)\n4. XPath with `aria-label` (for inputs, form fields)\n5. XPath with structural path (last resort — fragile)\n\n## Example Invocations\n\n```\nexplore and generate tests for /#/create-workspace\nexplore /#/user-preferences and write E2E tests for the settings page\ngenerate tests for the new workspace sharing feature at /#/workspaces\n```\n\n<!-- File: tests/e2e/.claude/commands/run-e2e-test.md (Tokens: ~4229 | Category: command-prompt) -->\n# Run E2E Tests - Unified Command\n\nRun E2E tests against **Eclipse Che** or **Red Hat OpenShift Dev Spaces** using either a **local Chrome browser** or **Podman container**.\n\n## Arguments: $ARGUMENTS\n\n**Usage:** `/run-e2e-test [URL USERNAME PASSWORD [TESTNAME]]`\n\n**Examples:**\n\n- `/run-e2e-test` - Interactive mode (recommended)\n- `/run-e2e-test https://devspaces.apps.cluster.example.com/ admin mypassword SmokeTest npm`\n- `/run-e2e-test https://che.apps.cluster.example.com/ admin mypassword EmptyWorkspace podman`\n\n---\n\n## Execution Flow\n\n1. **Parse arguments** - Extract parameters from `$ARGUMENTS`\n2. **Prompt if missing** - Collect missing values via AskUserQuestion\n3. **Auto-detect platform** - Determine Eclipse Che vs Dev Spaces from URL\n4. **Detect local changes** - Check if rebuild is needed based on git status\n5. **Detect test-specific parameters** - Read spec file for required env vars\n6. **Generate bash commands** - Build appropriate bash commands\n7. **Execute** - Run the test after user confirmation\n\n---\n\n## ⚠️ MANDATORY RULES\n\n1. **Never print bash commands with `&& \\` or multi-line continuations using `\\`** - Commands using line continuation characters (`\\`) or chained with `&& \\` cannot be copy-pasted properly from the terminal. Instead, present commands as separate lines or use semicolons for simple chains.\n\n    ❌ **Wrong (line continuations):**\n\n    ```bash\n    cd /path/to/dir && \\\n    npm ci && \\\n    npm run test\n    ```\n\n    ❌ **Wrong (backslash continuations):**\n\n    ```bash\n    podman run -it \\\n      --shm-size=2g \\\n      -p 5920:5920 \\\n      quay.io/eclipse/che-e2e:next\n    ```\n\n    ✅ **Correct (separate lines):**\n\n    ```bash\n    cd /path/to/dir\n    npm ci\n    npm run test\n    ```\n\n    ✅ **Correct (single line for podman):**\n\n    ```bash\n    podman run -it --shm-size=2g -p 5920:5920 quay.io/eclipse/che-e2e:next\n    ```\n\n---\n\n## Step 1: Parse Arguments\n\nParse `$ARGUMENTS` to extract positional parameters:\n\n```\n$ARGUMENTS = \"URL USERNAME PASSWORD [TESTNAME]\"\n```\n\n**Parsing logic:**\n\n1. Split `$ARGUMENTS` by whitespace\n2. Assign to variables: `URL`, `USERNAME`, `PASSWORD`, `TESTNAME`, `RUN_METHOD`\n3. If `TESTNAME` is not provided, default to `EmptyWorkspace`\n\n**Example:**\n\n```\nInput: \"https://devspaces.apps.example.com/ admin secret123 SmokeTest npm\"\nResult:\n  - URL = \"https://devspaces.apps.example.com/\"\n  - USERNAME = \"admin\"\n  - PASSWORD = \"secret123\"\n  - TESTNAME = \"SmokeTest\"\n  - RUN_METHOD = \"npm\"\n```\n\n---\n\n## Step 2: Prompt for Missing Values\n\nIf `$ARGUMENTS` is empty or incomplete, use **AskUserQuestion** to collect missing parameters.\n\n### Question 1 - Run Method (header: \"Method\")\n\n- \"How do you want to run the test?\"\n- Options:\n    - Option 1: \"npm run in local browser (Recommended)\" - \"Uses local Chrome and Node.js - faster, see browser in real-time\"\n    - Option 2: \"Podman container\" - \"Isolated environment with VNC support at localhost:5920\"\n\n### Question 2 - Execution Mode (header: \"Build\")\n\n- \"How should local changes be handled?\"\n- Options:\n    - Option 1: \"Build and run (Recommended)\" - \"Compare to origin/main, rebuild if any changes exist\"\n    - Option 2: \"Just run\" - \"Skip build, assume code is already compiled\"\n\n### Question 3 - Test Name (header: \"Test\")\n\n- \"Which test do you want to run?\"\n- Options:\n    - Option 1: \"EmptyWorkspace (Recommended)\" - \"Basic workspace creation test\"\n    - Option 2: \"SmokeTest\" - \"Quick validation of core functionality\"\n    - Option 3: \"Factory\" - \"Factory URL workspace creation with git operations\"\n    - Option 4: \"EmptyWorkspaceAPI\" - \"API-only test (no browser needed)\"\n\n### For URL - Ask in plain text:\n\n> \"What is the dashboard URL? (e.g., https://devspaces.apps.cluster.example.com/ or https://che.apps.cluster.example.com/)\"\n\n### For Username and Password - Ask in plain text:\n\n> \"Please provide your credentials:\n>\n> - **Username**: (e.g., admin, user)\n> - **Password**: your password\"\n\nWait for the user's response before proceeding.\n\n---\n\n## Step 3: Auto-detect Testing Product from URL\n\nAnalyze the URL to determine which product is being tested and set appropriate defaults:\n\n| URL Pattern            | Product                          | Auto-configured Settings                                                                                                                                          |\n| ---------------------- | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| Contains `devspaces`   | **Red Hat OpenShift Dev Spaces** | `TS_SELENIUM_VALUE_OPENSHIFT_OAUTH=true`, `TS_OCP_LOGIN_PAGE_PROVIDER_TITLE=htpasswd` `TS_PLATFORM=openshift`                                                     |\n| Contains `eclipse-che` | **Eclipse Che**                  | May need OAuth config based on deployment. I host contains `crw-qe.com` - it is `TS_PLATFORM=openshift`. Otherwise ask user for platform: openshift or kubernetes |\n\n**Detection logic:**\n\n```bash\nif [[ \"$URL\" == *\"devspaces\"* ]]; then\n  PRODUCT=\"devspaces\"\n  TS_SELENIUM_VALUE_OPENSHIFT_OAUTH=true\n  TS_OCP_LOGIN_PAGE_PROVIDER_TITLE=\"htpasswd\"\nelif [[ \"$URL\" == *\"eclipse-che\"* ]]; then\n  PRODUCT=\"che\"\nfi\n```\n\n---\n\n## Step 4: Detect Local Changes\n\nCheck the git status to determine if code needs to be rebuilt before running tests.\n\n**Important:** Ignore changes to documentation and Claude configuration files:\n\n- `CLAUDE.md`\n- `.claude/**/*`\n- `*.md` files (documentation)\n\n### For Local Browser Method:\n\n**Smart rebuild based on all changes vs origin/main** - rebuild when there are any test code modifications compared to origin/main.\n\n**Logic:**\n\n1. Compare working tree to `origin/main` to detect all changes (committed, staged, and unstaged)\n2. Rebuild if any test code changes exist compared to `origin/main` (excluding docs/config files)\n\n```bash\nNEEDS_REBUILD=false\n\n# Get current branch name for informational purposes\nCURRENT_BRANCH=$(git branch --show-current)\necho \"Current branch: $CURRENT_BRANCH\"\n\n# Check for any changes compared to origin/main (committed, staged, and unstaged)\n# Excludes docs and Claude config files\nCODE_CHANGES=$(git diff --name-only origin/main -- . 2>/dev/null | grep -v -E '(CLAUDE\\.md|\\.claude/|README\\.md|\\.md$)')\n\nif [[ -n \"$CODE_CHANGES\" ]]; then\n  echo \"Code changes detected compared to origin/main:\"\n  echo \"$CODE_CHANGES\"\n  NEEDS_REBUILD=true\nelse\n  echo \"No test code changes compared to origin/main - no rebuild needed\"\nfi\n\necho \"Rebuild needed: $NEEDS_REBUILD\"\n```\n\n### For Podman Container Method:\n\n**Smart mounting based on all changes vs origin/main** - mount full directory when there are any test code modifications compared to origin/main.\n\n**Logic:**\n\n1. Compare working tree to `origin/main` to detect all changes (committed, staged, and unstaged)\n2. Mount full directory if any test code changes exist compared to `origin/main` (excluding docs/config files)\n\n```bash\nE2E_IMAGE=\"quay.io/eclipse/che-e2e:next\"\nMOUNT_FULL_CODE=false\n\n# Get current branch name for informational purposes\nCURRENT_BRANCH=$(git branch --show-current)\necho \"Current branch: $CURRENT_BRANCH\"\n\n# Check for any changes compared to origin/main (committed, staged, and unstaged)\n# Excludes docs and Claude config files\nCODE_CHANGES=$(git diff --name-only origin/main -- . 2>/dev/null | grep -v -E '(CLAUDE\\.md|\\.claude/|README\\.md|\\.md$)')\n\nif [[ -n \"$CODE_CHANGES\" ]]; then\n  echo \"Code changes detected compared to origin/main:\"\n  echo \"$CODE_CHANGES\"\n  MOUNT_FULL_CODE=true\n  PODMAN_MOUNT=\"-v $(pwd):/tmp/e2e:Z\"\nelse\n  echo \"No test code changes compared to origin/main - using published image code\"\n  PODMAN_MOUNT=\"-v $(pwd)/report:/tmp/e2e/report:Z\"\nfi\n\necho \"Mount strategy: $PODMAN_MOUNT\"\n```\n\n---\n\n## Step 5: Detect Test-Specific Parameters\n\nAfter getting the test name, read the test spec file to identify additional required environment variables.\n\n### Find and read the spec file:\n\n```bash\n# Find the test file\nSPEC_FILE=$(find specs -name \"${USERSTORY}.spec.ts\" 2>/dev/null | head -1)\n\nif [[ -n \"$SPEC_FILE\" ]]; then\n  echo \"Found test file: $SPEC_FILE\"\n  # Read the file to identify required environment variables\n  cat \"$SPEC_FILE\"\nfi\n```\n\n### Platform-Specific Configurations\n\n#### OpenShift\n\n```bash\nexport TS_PLATFORM=openshift\nexport TS_SELENIUM_VALUE_OPENSHIFT_OAUTH=true\nexport TS_OCP_LOGIN_PAGE_PROVIDER_TITLE=\"htpasswd\"\n```\n\n#### Kubernetes\n\n```bash\nexport TS_PLATFORM=kubernetes\nexport TS_SELENIUM_K8S_USERNAME=che@eclipse.org\nexport TS_SELENIUM_K8S_PASSWORD=admin\nexport TS_SELENIUM_VALUE_OPENSHIFT_OAUTH=false\n```\n\n### Test-specific parameters by category:\n\n#### Factory URL Tests (Factory, RefusedOAuthFactory, NoSetupRepoFactory, etc.)\n\nThese tests navigate directly to a factory URL to create a workspace.\n**Ask for these additional parameters:**\n\n- **Git Repository URL** (`TS_SELENIUM_FACTORY_GIT_REPO_URL`) - Repository to clone\n- **Git Provider** (`TS_SELENIUM_FACTORY_GIT_PROVIDER`) - `github`, `gitlab`, `bitbucket`, `azure-devops`\n- **OAuth Enabled** (`TS_SELENIUM_GIT_PROVIDER_OAUTH`) - `true` or `false`\n\n#### Git Repo Import Form Tests (FactoryWithGitRepoOptions, CreateWorkspaceWithExistingNameFromGitUrl, etc.)\n\nThese tests use the Dashboard's \"Create Workspace\" form.\n**Ask for these additional parameters:**\n\n- **Git Repository URL** (`TS_SELENIUM_FACTORY_GIT_REPO_URL`) - Repository URL\n- **Branch Name** (`TS_SELENIUM_FACTORY_GIT_REPO_BRANCH`) - Optional (default: `main`)\n- **Project Name** (`TS_SELENIUM_PROJECT_NAME`) - Optional\n\n#### API Tests (EmptyWorkspaceAPI, ContainerOverridesAPI, etc.)\n\n**Ask for these additional parameters:**\n\n- **Namespace** (`TS_API_TEST_NAMESPACE`) - Optional\n- **UDI Image** (`TS_API_TEST_UDI_IMAGE`) - Optional\n\n#### Sample/Devfile Tests\n\n**Ask for these additional parameters:**\n\n- **Sample List** (`TS_SAMPLE_LIST`) - e.g., `Node.js Express`\n\n#### SSH/PAT Tests (SshUrlNoOauthPatFactory, etc.)\n\n**Ask for these additional parameters:**\n\n- **SSH Private Key Path** or **Personal Access Token** details\n\n---\n\n## Step 6: Generate Bash Commands\n\nBased on all collected parameters, generate the appropriate bash commands.\n\n### Option A: Local Browser Commands\n\n#### Browser/ChromeDriver Compatibility Check\n\nBefore running tests, verify ChromeDriver compatibility with local Chrome browser:\n\n```bash\n# Get local Chrome version\nCHROME_VERSION=$(google-chrome --version 2>/dev/null | grep -oP '\\d+' | head -1)\necho \"Local Chrome major version: $CHROME_VERSION\"\n\n# Get ChromeDriver version from package.json\nCHROMEDRIVER_VERSION=$(npm list chromedriver --depth=0 2>/dev/null | grep -oP 'chromedriver@\\K[\\d.]+')\necho \"ChromeDriver version in package.json: $CHROMEDRIVER_VERSION\"\n\n# If versions don't match, suggest installing compatible driver\nif [[ \"${CHROMEDRIVER_VERSION%%.*}\" != \"$CHROME_VERSION\" ]]; then\n  echo \"WARNING: ChromeDriver version mismatch!\"\n  echo \"Install compatible version: npm install chromedriver@$CHROME_VERSION\"\nfi\n```\n\n**If ChromeDriver is incompatible**, install a matching version:\n\n```bash\nnpm install chromedriver@<chrome-major-version>\n```\n\n#### Full Test Run (Build and Run)\n\n```bash\ncd tests/e2e\n\n# Use correct Node.js version\nnvm use v22.10.0\n\n# Get current branch name for informational purposes\nCURRENT_BRANCH=$(git branch --show-current)\necho \"Current branch: $CURRENT_BRANCH\"\n\n# Check for any changes compared to origin/main (committed, staged, and unstaged)\n# Excludes docs and Claude config files\nCODE_CHANGES=$(git diff --name-only origin/main -- . 2>/dev/null | grep -v -E '(CLAUDE\\.md|\\.claude/|README\\.md|\\.md$)')\n\nif [[ -n \"$CODE_CHANGES\" ]]; then\n  echo \"Code changes detected compared to origin/main:\"\n  echo \"$CODE_CHANGES\"\n  NEEDS_REBUILD=true\nelse\n  echo \"No test code changes compared to origin/main - skipping rebuild\"\n  NEEDS_REBUILD=false\nfi\n\n# Check if package.json was modified compared to origin/main\nPACKAGE_JSON_CHANGED=$(git diff --name-only origin/main -- package.json 2>/dev/null)\n\n# Rebuild if changes detected compared to origin/main\nif [[ \"$NEEDS_REBUILD\" == \"true\" ]]; then\n  echo \"Rebuilding TypeScript...\"\n  rm -rf node_modules dist\n\n  # Use npm install if package.json changed, otherwise npm ci\n  if [[ -n \"$PACKAGE_JSON_CHANGED\" ]]; then\n    echo \"package.json modified - using npm install to update package-lock.json\"\n    npm install\n  else\n    npm ci\n  fi\n\n  npm run lint\n  npm run prettier\n  npm run tsc\nfi\n\n# Set environment variables\nexport TS_SELENIUM_BASE_URL=\"<URL>\"\nexport TS_SELENIUM_OCP_USERNAME=\"<USERNAME>\"\nexport TS_SELENIUM_OCP_PASSWORD=\"<PASSWORD>\"\nexport USERSTORY=\"<TESTNAME>\"\nexport TS_SELENIUM_VALUE_OPENSHIFT_OAUTH=true\nexport NODE_TLS_REJECT_UNAUTHORIZED=0\n\n# Run the test\nnpm run test\n```\n\n#### API-Only Tests (No Browser)\n\n```bash\nexport USERSTORY=EmptyWorkspaceAPI\nnpm run driver-less-test\n```\n\n### Option B: Podman Container Commands\n\n**Important:** Use `--env-host` to pass all exported environment variables from the host to the container.\n\n**Smart mounting logic based on changes vs origin/main:**\n\n- **Changes compared to origin/main**: Mount full directory `-v $(pwd):/tmp/e2e:Z` to test local code\n- **No changes compared to origin/main**: Mount only report directory `-v $(pwd)/report:/tmp/e2e/report:Z`\n\n#### With Test Code Changes vs origin/main (mount full directory):\n\nUse this when there are test code changes compared to `origin/main` (works for feature branches or local main with unpushed changes).\n\n```bash\ncd tests/e2e\n\n# Set environment variables\nexport NODE_TLS_REJECT_UNAUTHORIZED=0\nexport TS_SELENIUM_BASE_URL=<URL>\nexport TS_SELENIUM_OCP_USERNAME=<USERNAME>\nexport TS_SELENIUM_OCP_PASSWORD=<PASSWORD>\nexport USERSTORY=<TESTNAME>\n\n# Run with full local code mounted (testing changes vs origin/main)\npodman rm -f selenium-e2e 2>/dev/null\npodman run -it --shm-size=2g -p 5920:5920 --env-host -v $(pwd):/tmp/e2e:Z quay.io/eclipse/che-e2e:next\n```\n\n#### No Test Code Changes vs origin/main (mount only report directory):\n\n```bash\ncd tests/e2e\n\n# Set environment variables\nexport NODE_TLS_REJECT_UNAUTHORIZED=0\nexport TS_SELENIUM_BASE_URL=<URL>\nexport TS_SELENIUM_OCP_USERNAME=<USERNAME>\nexport TS_SELENIUM_OCP_PASSWORD=<PASSWORD>\nexport USERSTORY=<TESTNAME>\n\n# Run with only report directory mounted (no changes vs origin/main, uses published image code)\npodman rm -f selenium-e2e 2>/dev/null\npodman run -it --shm-size=2g -p 5920:5920 --env-host -v $(pwd)/report:/tmp/e2e/report:Z quay.io/eclipse/che-e2e:next\n```\n\n---\n\n## Step 7: Execute After User Confirmation\n\nBefore executing, present the generated command(s) to the user and ask for confirmation.\n\n### Confirmation prompt:\n\n> \"Here is the command I will run:\n>\n> ```bash\n> <generated command>\n> ```\n>\n> **Summary:**\n>\n> - **Platform:** [OpenShift/Kubernetes]\n> - **Product:** [Eclipse Che/Dev Spaces]\n> - **Test:** [TESTNAME]\n> - **Method:** [Local browser/Podman container]\n> - **URL:** [URL]\n>\n> Shall I proceed?\"\n\n### After confirmation:\n\n1. Execute the bash command(s)\n2. Monitor the output\n3. Report success or failure\n4. If using Podman, remind user about VNC access at `localhost:5920`\n\n---\n\n## Debugging Options\n\n### Verbose Logging\n\n```bash\nexport TS_SELENIUM_LOG_LEVEL=TRACE\nexport TS_SELENIUM_PRINT_TIMEOUT_VARIABLES=true\nexport TS_DEBUG_MODE=true\n```\n\n### Headless Mode (CI)\n\n```bash\nexport TS_SELENIUM_HEADLESS=true\n```\n\n### Video Recording\n\n```bash\nexport VIDEO_RECORDING=true\n```\n\n### Keep Workspace on Failure\n\n```bash\nexport TS_DELETE_WORKSPACE_ON_FAILED_TEST=false\n```\n\n---\n\n## VNC Access for Visual Debugging (Podman only)\n\nConnect to VNC at `localhost:5920` to watch tests running in the container.\n\n**VNC clients:**\n\n- **TigerVNC** (recommended): `sudo dnf install tigervnc` then `vncviewer localhost:5920`\n- **Remmina**: `sudo dnf install remmina remmina-plugins-vnc` (GUI-based)\n- **Vinagre**: `sudo dnf install vinagre` then `vinagre localhost:5920`\n\n---\n\n## View Test Reports\n\nAfter test execution:\n\n```bash\nnpm run open-allure-dasboard\n```\n\n---\n\n## Troubleshooting\n\n### Certificate Errors\n\n```bash\nexport NODE_TLS_REJECT_UNAUTHORIZED=0\n```\n\n### Slow Workspace Start\n\n```bash\nexport TS_SELENIUM_START_WORKSPACE_TIMEOUT=600000\nexport TS_SELENIUM_DEFAULT_ATTEMPTS=3\n```\n\n### OAuth Login Issues\n\nVerify the login provider title matches exactly what appears on the login page:\n\n```bash\nexport TS_OCP_LOGIN_PAGE_PROVIDER_TITLE=\"htpasswd\"\n```\n\n### Container Cleanup\n\n```bash\npodman rm -f selenium-e2e\n```\n\n---\n\n## Debugging TypeScript Tests in VS Code\n\nFor debugging E2E tests with breakpoints, see: https://code.visualstudio.com/docs/typescript/typescript-debugging\n</yakaai_skills>"}