From 913101737d566bdd1289ef88b63e78fc6fe24989 Mon Sep 17 00:00:00 2001 From: Santiago Greco Date: Fri, 14 Aug 2026 11:17:47 +0200 Subject: [PATCH] CONSOLE-5454: Shared Playwright e2e context and test generation skill Refactor e2e skill architecture to separate universal Playwright knowledge from Cypress-specific migration content. This formalizes the approach discussed in Console CCSIG calls. - Create e2e-context.md: shared Playwright reference (patterns, fixtures, selectors, isolation strategies, flakiness prevention) consumed by gen-e2e-test, migrate-cypress, and debug-test - Slim migration-context.md to Cypress-only content (translation tables, transformation rules, Gherkin collapse) - Create gen-e2e-test skill for writing Playwright tests from scratch - Add flakiness prevention guidance to both shared context and migrate-cypress skill - Update migrate-cypress and debug-test to reference shared context Assisted-by: Claude Opus 4.6 (1M context) --- .claude/e2e-context.md | 636 ++++++++++++++++++++++++ .claude/migration-context.md | 310 ++---------- .claude/skills/debug-test/SKILL.md | 32 +- .claude/skills/gen-e2e-test/SKILL.md | 137 +++++ .claude/skills/migrate-cypress/SKILL.md | 31 +- 5 files changed, 871 insertions(+), 275 deletions(-) create mode 100644 .claude/e2e-context.md create mode 100644 .claude/skills/gen-e2e-test/SKILL.md diff --git a/.claude/e2e-context.md b/.claude/e2e-context.md new file mode 100644 index 00000000000..2c0ae2b054c --- /dev/null +++ b/.claude/e2e-context.md @@ -0,0 +1,636 @@ +# Playwright E2E Test Context + +Shared reference for writing, migrating, and debugging Playwright e2e tests in OpenShift Console. Used by `/gen-e2e-test`, `/migrate-cypress`, and `/debug-test` skills. + +## High-Level Principles + +1. **Self-contained tests.** Each `test()` block must create its own resources, assert independently, and clean up after itself. Never rely on test execution order or shared mutable state. Exception: Strategy B allows sharing read-only resources via `beforeAll`/`afterAll` when per-test creation is too expensive. +2. **Use the most specific API.** Prefer `getByTestId()`, `getByRole()`, `getByText()`, `getByLabel()` over generic `page.locator()`. Use `page.locator('[data-test="..."]')` only when other methods don't apply. +3. **Leverage the framework.** Use existing page objects, clients, and fixtures. Search `e2e/pages/` before creating new page objects. +4. **Live verification.** Use Playwright MCP browser tools to verify selectors, navigation flows, and element presence against the live UI before finalizing code. + +--- + +## Project Structure & File Conventions + +```text +frontend/ + playwright.config.ts # Config: testIdAttribute='data-test', viewport=1920x1080, timeout=120s + e2e/ + fixtures/ # Test fixtures (cleanup, k8sClient, testConfig) + index.ts # Main fixture file. Exports test, expect + cleanup-fixture.ts # CleanupFixture implementation + clients/ + kubernetes-client.ts # KubernetesClient: all cluster API interactions + pages/ # Page objects. Extend BasePage + base-page.ts # Abstract base: robustClick, goTo, waitForLoadingComplete, etc. + .ts # Feature-specific page objects + tests/ # Test specs + / # Grouped by Playwright project + .spec.ts # Admin tests + developer/ # Developer-persona tests + .spec.ts + mocks/ # Test data (TypeScript objects and YAML manifests) + setup/ # Global setup/teardown (auth, cluster, knative) +``` + +### Playwright Projects + +Tests are grouped by project, which determines auth state and test directory: + +| Project | Test directory | Auth | +|---------|---------------|------| +| `smoke` | `e2e/tests/smoke/` | admin | +| `console` | `e2e/tests/console/` | admin | +| `dev-console` | `e2e/tests/dev-console/` | admin | +| `helm` | `e2e/tests/helm/` | admin | +| `knative` | `e2e/tests/knative/` | admin | +| `olm` | `e2e/tests/olm/` | admin | +| `topology` | `e2e/tests/topology/` | admin | +| `webterminal` | `e2e/tests/webterminal/` | admin | + +Projects with developer auth variants: `smoke-developer`, `dev-console-developer`, `topology-developer`, `webterminal-developer`. Developer tests go in a `developer/` subdirectory. + +--- + +## Test Selectors + +Config: `testIdAttribute: 'data-test'` in `playwright.config.ts`, so `page.getByTestId('x')` queries `[data-test="x"]`. + +**Always use `page.getByTestId('x')`** for element selection. If a React element only has a legacy test attribute (`data-test-id`, `data-test-selector`, `data-test-action`, `data-test-dropdown-menu`, etc.) but no `data-test`, **add `data-test=""` to the React component source** so `getByTestId()` can be used. Never remove legacy attributes since external consumers may depend on them. + +### Locator Priority + +1. `getByTestId('x')` for elements with `data-test` +2. `getByRole('button', { name: 'Save' })` for interactive elements by role +3. `getByText('text')` or `locator('selector', { hasText: 'text' })` for text content +4. `locator('css-selector')` as a last resort, for elements that cannot be located clearly by role or by text + +### PatternFly wrapper elements + +Some PatternFly components place `data-test` on a wrapper element, not the actionable child. For example, `TextInputGroup` wraps `` in a `
` that carries `data-test`. If `getByTestId('x').fill()` fails with "Element is not an input", chain `.locator('input')` or `.getByRole('textbox')` to reach the actionable child. + +```typescript +// WRONG: data-test is on the wrapper div, not the input +await this.page.getByTestId('console-select-search-input').fill(text); + +// RIGHT: chain to the actual input element +await this.page.getByTestId('console-select-search-input').locator('input').fill(text); +``` + +### Adding `data-test` to React Components + +When a React component has only a legacy test attribute: + +1. Find the React component that renders the element. +2. Add `data-test="x"` alongside the existing legacy attribute. +3. In the page object, use `this.page.getByTestId('x')`. + +```tsx +// Before +
Details
+ +// After: data-test added, legacy preserved +
Details
+``` + +**Custom React components vs DOM elements:** `data-test="x"` on a native DOM element (`
`, `