openshift/migrate-cypress
Migrate a Cypress test file (.cy.ts) or Gherkin feature file (.feature) to Playwright following Console's architecture. This is the ONLY skill for Cypress-to-Playwright conversion work, you can perform full migration, analysis-only (--analyze), or dry-run (--dry-run) modes.
npx skills add https://github.com/openshift/console --skill migrate-cypress
Translate Cypress E2E tests into idiomatic Playwright code following Console's layered architecture. Adapted from openshift-ui-tests-template/migrate.md.
frontend/e2e/.env exists. If missing, copy frontend/e2e/.env.example to frontend/e2e/.env and tell the user to fill in their cluster values before continuing..claude/migration-context.md for the complete API translation tables, structural transformation rules, Console selector mappings, and migration checklist. That file is the single source of truth for all translation context./migrate-cypress <cypress-file-or-feature> — full migration (analyze → implement → validate)/migrate-cypress <cypress-file-or-feature> --analyze — analysis only, produce migration plan/migrate-cypress <cypress-file-or-feature> --dry-run — generate code without writing files/migrate-cypress frontend/packages/integration-tests/tests/cluster-settings/upstream-modal.cy.ts
/migrate-cypress upstream-modal --analyze
/migrate-cypress frontend/packages/dev-console/integration-tests/features/addFlow/ --dry-run
Migrated tests go under e2e/tests/<package>/ based on their source package:
| Source package | Playwright project | Output directory |
| -------------------------------------------------------- | ------------------ | --------------------------- |
| packages/integration-tests/tests/<area>/ | console | e2e/tests/console/<area>/ |
| packages/dev-console/integration-tests/ | dev-console | e2e/tests/dev-console/ |
| packages/helm-plugin/integration-tests/ | helm | e2e/tests/helm/ |
| packages/knative-plugin/integration-tests/ | knative | e2e/tests/knative/ |
| packages/operator-lifecycle-manager/integration-tests/ | olm | e2e/tests/olm/ |
| packages/topology/integration-tests/ | topology | e2e/tests/topology/ |
| packages/webterminal-plugin/integration-tests/ | webterminal | e2e/tests/webterminal/ |
| packages/console-telemetry-plugin/integration-tests/ | telemetry | e2e/tests/telemetry/ |
Tests requiring developer auth go in a developer/ subdirectory (e.g. e2e/tests/dev-console/developer/).
.feature files, also resolve step definitions (support/step-definitions/), page actions (support/pages/), and page object selectors (support/pageObjects/).it block or Scenario tests in plain languagefind frontend/e2e/pages frontend/e2e/clients -name "*.ts" 2>/dev/nullExpected output: a structured plan listing each test's intent, the Playwright components to use/create, the isolation strategy, and the output file path.
Stop here if --analyze was specified.
data-test attributes and element rolesIf MCP is unavailable or no cluster is reachable, log a warning: "Playwright MCP not available — selectors translated literally. They may be stale. Run /debug-test after deployment to verify." Proceed to Phase 3.
Locator type from @playwright/test and default-import BasePagegetByTestId() for data-test attributes, locator() for other selectorsdata-test-id, data-test-rows, data-test-dropdown-menu, etc.) but no data-test, add data-test to the React component source and use getByTestId() — never use legacy attribute selectors directlygetX(): Locator), keep locator properties private readonlyrobustClick() inside page objects; specs use plain .click()waitFor() before action methods (fill(), click(), check()) — Playwright auto-waits for actionabilitylegacy prefix — name for what they doExample:
// e2e/pages/cluster-settings.ts
import type { Locator } from "@playwright/test";
import BasePage from "./base-page";
export class ClusterSettingsPage extends BasePage {
private readonly detailsTab = this.page.getByTestId("horizontal-link-Details");
private readonly pageHeading = this.page.getByTestId("cluster-settings-page-heading");
async navigateToDetails(): Promise<void> {
await this.goTo("/settings/cluster");
await this.detailsTab.waitFor({ state: "visible" });
}
getPageHeading(): Locator {
return this.pageHeading;
}
}
import { test, expect } from '../../fixtures'test.describe with tags (e.g. { tag: ['@smoke'] })e2e/tests/<package>/, developer tests in e2e/tests/<package>/developer/test()test.step() for logical grouping when a test has 3+ distinct phasesScenario Outline + Examples: use for...of loop@manual / @broken-test: use test.skip(true, 'reason') or test.fixme('reason') with Jira linknpx tsc --noEmit -p e2e/tsconfig.json and cd frontend && yarn eslint <generated-files> — fix any type or lint errorsPrint code without writing if --dry-run was specified.
npx playwright test --project=<package> <output-file> --retries=0. The project name matches the package directory under e2e/tests/ (e.g. --project=helm, --project=console). For developer tests, use --project=<package>-developer. Add --ui only if the user requests interactive debugging. Note: e2e/.env may override WEB_CONSOLE_URL with a remote cluster URL. If running against localhost, ensure the user has updated e2e/.env or override with WEB_CONSOLE_URL=http://localhost:9000. Migration complete: <source-file> → <output-file>
Tests migrated: N
Page objects created: [list]
Page objects reused: [list]
Files written: [list]
Validation: passed
Test mapping:
| Cypress test | Playwright test | Assertions |
|---------------------------------------|---------------------------------------|------------|
| it('does something') | test('does something') | 3 → 3 |
| it('handles edge case') | test('handles edge case') | 2 → 2 |
| ... | ... | ... |
| Total | | 8 → 8 |
The "Assertions" column shows <cypress count> → <playwright count>. If a Playwright test has fewer assertions than its Cypress counterpart, flag it with ⚠ and investigate — assertions may have been silently dropped.
it blocks into one test() with test.step()cy.wait(ms) with condition-based waits or assertion timeoutswaitFor() — fill(), click(), check(), etc. auto-wait for actionability; only use waitFor() when waiting for state without acting on the elementcy.exec('oc ...') with KubernetesClientk8sClient.deleteNamespace(), deleteCustomResource(), and deleteClusterCustomResource() already swallow 404 errorsdata-test to React source — when the component only has legacy test attributes (data-test-id, data-test-rows, etc.), add data-test alongside and use getByTestId()If MCP tools fail with "tool not found" or "connection refused": skip Phase 2, translate selectors literally, and warn the user. Selectors can be verified later with /debug-test.
If npx tsc --noEmit reports errors in the generated spec or page object: read the errors, fix missing imports or type mismatches, and re-run. Common causes: missing page object import, wrong fixture type, incorrect KubernetesClient method signature.
Compare assertion count with the original Cypress test. If the migrated test has fewer assertions, the agent may have silently dropped failing ones. Restore them using the intent documented in Phase 1.
.claude/migration-context.md for API translation tablesTake openshift/migrate-cypress from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.
The instructions reference npx.
Without those the skill loads but fails at the first command.