CONSOLE-5299: migrate dev-console Cypress tests to Playwright (batch 1)#16475
CONSOLE-5299: migrate dev-console Cypress tests to Playwright (batch 1)#16475Cragsmann wants to merge 1 commit into
Conversation
Migrate 18 Gherkin feature files from dev-console Cypress integration tests to Playwright, covering: project lifecycle, search, routes, config maps, pods, vulnerabilities, samples, health checks, user preferences, catalog customization, perspective configuration, and pinned resources. - 10 page objects under e2e/pages/dev-console/ - 18 spec files under e2e/tests/dev-console/ - Manual/broken tests preserved as test.skip() with reason annotations - Automatable tests use self-contained create→assert→cleanup pattern Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
@Cragsmann: This pull request references CONSOLE-5299 which is a valid jira issue. Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the sub-task to target the "5.0.0" version, but no target version was set. DetailsIn response to this:
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository. |
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: Cragsmann The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
📝 Walkthrough<review_stack_artifact> WalkthroughThis PR introduces a comprehensive Playwright E2E test framework for the OpenShift Console developer perspective. It adds 10 reusable page objects that abstract common UI interactions (perspective switching, project selection, CRUD workflows across ConfigMaps, Routes, Health Checks, Samples, Search, Vulnerabilities, and Pod lists) and 16 test suites that exercise these page objects across smoke tests, feature workflows, customization scenarios, and complex multi-step interactions. The infrastructure establishes a maintainable pattern for test authoring by decoupling locators and helpers from test logic, enabling developers to write concise, readable test flows. Suggested reviewers
🚥 Pre-merge checks | ✅ 10 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (10 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Warning Review ran into problems🔥 ProblemsGit: Failed to clone repository. Please run the Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 11
🧹 Nitpick comments (1)
frontend/e2e/tests/dev-console/customization-of-pinned-resource.spec.ts (1)
22-24: ⚡ Quick winMigrate to the
cleanupfixture for robust resource teardownThe current
afterAllpattern is missing a null-guard onk8s. IfbeforeAllthrows before initialization, the subsequent cleanup will fail with an undefined error, masking the original failure.However, the better approach is to use the provided
cleanupfixture (per E2E guidelines). It provides built-in robustness with null-checks, error handling, and proper logging. Seeproject-creation.spec.tsfor the pattern: accept{ cleanup }in the test function and callcleanup.trackNamespace(namespace).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/e2e/tests/dev-console/customization-of-pinned-resource.spec.ts` around lines 22 - 24, Replace the fragile test.afterAll cleanup that calls k8s.deleteNamespace(namespace) with the E2E cleanup fixture: accept the fixture in the test declaration (use the same pattern as project-creation.spec.ts), remove the test.afterAll block, and call cleanup.trackNamespace(namespace) when the namespace is created so teardown is robust and avoids null-guard issues with k8s; update references to test.afterAll/k8s.deleteNamespace to use cleanup.trackNamespace(namespace).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@frontend/e2e/pages/dev-console/perspective-page.ts`:
- Around line 43-52: The current implementation in switchToAdministrator calls
switchToPerspective('Administrator') unconditionally which can fail when the UI
uses the new "Core platform" label; first read
perspectiveSwitcherToggle.textContent() and if it already contains
"Administrator" return, otherwise if it contains "Core platform" call
switchToPerspective('Core platform'); only if neither label is present attempt
switchToPerspective('Administrator') and if that doesn't result in the expected
label then fallback to switchToPerspective('Core platform') so the function
handles both legacy and new labels reliably.
In `@frontend/e2e/pages/dev-console/route-page.ts`:
- Around line 41-42: The dropdown selector breaks because port is derived via a
fixed 4-character substring; update the parsing in route-page.ts where
targetPort is handled so the port is extracted as the actual numeric port string
(e.g., strip protocol/suffix like "/tcp" and non-digit chars, or split on '/'
and take the first token, or parseInt and convert back to string) before calling
this.page.locator and this.robustClick (referenced symbols: targetPort, const
port, page.locator, robustClick). Ensure the resulting port string exactly
matches the dropdown data-test value (e.g., "80" or "8080") rather than a
fixed-width slice.
In `@frontend/e2e/pages/dev-console/samples-page.ts`:
- Around line 72-74: The test currently only checks visibility; update
expectGitUrlReadonly to assert the field is actually read-only by verifying it's
not editable and/or has the readonly attribute. For example, replace the
visibility-only assertion with checks against the Locator this.gitUrlInput such
as awaiting expect(this.gitUrlInput).not.toBeEditable() and/or awaiting
expect(this.gitUrlInput).toHaveAttribute('readonly', expect.any(String)) so the
method verifies read-only behavior rather than just visibility.
In `@frontend/e2e/tests/dev-console/add-health-checks-topology-sidebar.spec.ts`:
- Around line 20-45: The test creates multiple deployments using deployments and
k8s.appsV1Api.createNamespacedDeployment but proceeds immediately to topology
actions; add an await loop after creation that polls the Deployment status (via
k8s.appsV1Api.readNamespacedDeployment or readNamespacedDeploymentStatus) for
each name in deployments in the given namespace until status.availableReplicas
>= 1 (or desired replicas) or a timeout is reached, and only continue once all
created deployments report ready to avoid race flakes.
In `@frontend/e2e/tests/dev-console/configure-perspectives.spec.ts`:
- Around line 43-46: Replace the silent .catch on the click with an explicit
visibility check: locate the clear button via
page.locator('button[aria-label="Clear input value"]') into a variable (e.g.,
clearButton), call await clearButton.isVisible() and only call await
clearButton.click() if that returns true so failures to click aren't swallowed;
use the same locator and methods (page.locator, isVisible, click) to implement
this conditional flow.
- Around line 31-42: The test falls back to selecting the first
.co-resource-item when the expected consoleItem (variable consoleItem) isn't
found, masking failures; update the UI component that renders resource items to
include a deterministic data-test attribute (e.g.,
data-test={`resource-item-${groupName}`} on the resource item element) and
change the test (using the existing combobox, filterInput and consoleItem
locators) to select by that data-test selector and assert its count is > 0,
failing the test if the specific resource-item-{groupName} is not present
instead of using .first().
In
`@frontend/e2e/tests/dev-console/customization-of-catalog-add-page-form-view.spec.ts`:
- Around line 26-188: Add an afterEach cleanup that restores cluster
customization to a known baseline after each test (or mark the suite .serial);
implement it by instantiating CustomizationPage (and PerspectivePage if needed),
navigating to consoles/cluster/customization (use methods navigateToConsoles,
clickCluster, openCustomization), switching to the Developer tab
(clickDeveloperTab), and calling the appropriate reset method (e.g.,
enableAll/enableOnlySoftwareCatalogItem, disableAllAddPageItems reversal or a
new restoreDefaultCustomization helper) then saving and waiting for
expectSaveMessage to succeed so each test leaves the cluster in a consistent
state.
- Around line 22-24: The afterAll teardown unconditionally calls
k8s.deleteNamespace(namespace) which can throw if namespace or k8s were never
initialized; guard the call by checking that both the KubernetesClient instance
(k8s) and the namespace variable are defined before calling deleteNamespace.
Locate the test.afterAll block and wrap or conditionally execute
k8s.deleteNamespace(namespace) only when namespace and k8s are truthy (mirror
the pattern used in oauth.spec.ts that checks if (!originalOAuthConfig ||
!client)). Ensure the check covers failures from new KubernetesClient() and
createNamespace() in beforeAll.
In `@frontend/e2e/tests/dev-console/pod-list-page.spec.ts`:
- Around line 44-56: The current setup swallows failures from
k8s.waitForCustomResourceCondition (called with 'apps','v1', namespace,
'deployments','nodejs-ex-git') which masks real cluster issues; remove the
.catch suppression so the test fails fast and surfaces the original error, or
replace the suppressed branch with an explicit assertion that pods exist (e.g.,
call a pod-listing/wait helper for the deployment and assert length>0) so
failures are reported with context before proceeding to UI steps.
In `@frontend/e2e/tests/dev-console/project-creation.spec.ts`:
- Around line 16-19: Move the cleanup registration to happen before triggering
project creation so the namespace is always tracked even if creation throws;
specifically, call cleanup.trackNamespace(projectName) before awaiting
perspectivePage.selectOrCreateProject(projectName) in the test step that opens
the Create Project modal (references: perspectivePage.selectOrCreateProject and
cleanup.trackNamespace). Ensure the order is changed in that block so
cleanup.trackNamespace runs first but keep the same projectName variable and
existing assertions intact.
In `@frontend/e2e/tests/dev-console/user-preferences-dev-perspective.spec.ts`:
- Around line 80-221: Tests mutate durable user preferences (Topology,
Create/Edit resource method, Resource Type) without restoring them; capture the
current preference before calling UserPreferencesPage.changePreferenceDropdown
and restore it in a finally block (or implement a shared afterEach cleanup) so
mutations don't leak across tests: for each test that calls
UserPreferencesPage.changePreferenceDropdown (e.g., tests using PerspectivePage
and UserPreferencesPage, methods
openUserPreferences/changePreferenceDropdown/reloadConsole/expectTopologyGraphView/expectYamlViewSelected/clickTab),
read and store the prior value (via an existing getter on UserPreferencesPage or
by reading the dropdown state), then in finally call changePreferenceDropdown to
revert to the saved value (or call a new UserPreferencesPage.resetPreference
helper), ensuring cleanup occurs even on failures.
---
Nitpick comments:
In `@frontend/e2e/tests/dev-console/customization-of-pinned-resource.spec.ts`:
- Around line 22-24: Replace the fragile test.afterAll cleanup that calls
k8s.deleteNamespace(namespace) with the E2E cleanup fixture: accept the fixture
in the test declaration (use the same pattern as project-creation.spec.ts),
remove the test.afterAll block, and call cleanup.trackNamespace(namespace) when
the namespace is created so teardown is robust and avoids null-guard issues with
k8s; update references to test.afterAll/k8s.deleteNamespace to use
cleanup.trackNamespace(namespace).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository YAML (base), Central YAML (inherited)
Review profile: CHILL
Plan: Enterprise
Run ID: 280e00db-b189-416a-9ee5-ff23daa97f09
📒 Files selected for processing (28)
frontend/e2e/pages/dev-console/config-map-page.tsfrontend/e2e/pages/dev-console/customization-page.tsfrontend/e2e/pages/dev-console/health-checks-page.tsfrontend/e2e/pages/dev-console/perspective-page.tsfrontend/e2e/pages/dev-console/pod-list-page.tsfrontend/e2e/pages/dev-console/route-page.tsfrontend/e2e/pages/dev-console/samples-page.tsfrontend/e2e/pages/dev-console/search-page.tsfrontend/e2e/pages/dev-console/user-preferences-page.tsfrontend/e2e/pages/dev-console/vulnerability-page.tsfrontend/e2e/tests/dev-console/add-health-checks-git-form.spec.tsfrontend/e2e/tests/dev-console/add-health-checks-topology-sidebar.spec.tsfrontend/e2e/tests/dev-console/config-maps-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/configure-perspectives.spec.tsfrontend/e2e/tests/dev-console/configure-pinned-resources.spec.tsfrontend/e2e/tests/dev-console/customization-of-catalog-add-page-form-view.spec.tsfrontend/e2e/tests/dev-console/customization-of-catalog.spec.tsfrontend/e2e/tests/dev-console/customization-of-pinned-resource.spec.tsfrontend/e2e/tests/dev-console/getting-started-to-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/getting-started-tour-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/pod-list-page.spec.tsfrontend/e2e/tests/dev-console/project-access.spec.tsfrontend/e2e/tests/dev-console/project-creation.spec.tsfrontend/e2e/tests/dev-console/project-vulnerability.spec.tsfrontend/e2e/tests/dev-console/route-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/sample-card-add-page.spec.tsfrontend/e2e/tests/dev-console/search-devperspective.spec.tsfrontend/e2e/tests/dev-console/user-preferences-dev-perspective.spec.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (14)
frontend/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
frontend/**/*.{ts,tsx,js,jsx}: Never import from package index files (e.g.,@console/shared) in new code, as they can create circular dependencies and slow builds. Import from specific file paths instead.
Do not use backticks int()calls for i18n strings, as the i18n parser cannot extract keys from template literals. Use single or double quotes instead.
Files:
frontend/e2e/pages/dev-console/pod-list-page.tsfrontend/e2e/tests/dev-console/customization-of-pinned-resource.spec.tsfrontend/e2e/tests/dev-console/add-health-checks-git-form.spec.tsfrontend/e2e/tests/dev-console/getting-started-tour-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/project-access.spec.tsfrontend/e2e/tests/dev-console/customization-of-catalog.spec.tsfrontend/e2e/pages/dev-console/config-map-page.tsfrontend/e2e/pages/dev-console/search-page.tsfrontend/e2e/tests/dev-console/configure-pinned-resources.spec.tsfrontend/e2e/tests/dev-console/getting-started-to-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/configure-perspectives.spec.tsfrontend/e2e/tests/dev-console/project-creation.spec.tsfrontend/e2e/tests/dev-console/search-devperspective.spec.tsfrontend/e2e/tests/dev-console/project-vulnerability.spec.tsfrontend/e2e/tests/dev-console/route-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/sample-card-add-page.spec.tsfrontend/e2e/pages/dev-console/health-checks-page.tsfrontend/e2e/pages/dev-console/user-preferences-page.tsfrontend/e2e/pages/dev-console/samples-page.tsfrontend/e2e/pages/dev-console/customization-page.tsfrontend/e2e/tests/dev-console/pod-list-page.spec.tsfrontend/e2e/tests/dev-console/add-health-checks-topology-sidebar.spec.tsfrontend/e2e/pages/dev-console/route-page.tsfrontend/e2e/tests/dev-console/customization-of-catalog-add-page-form-view.spec.tsfrontend/e2e/pages/dev-console/perspective-page.tsfrontend/e2e/pages/dev-console/vulnerability-page.tsfrontend/e2e/tests/dev-console/config-maps-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/user-preferences-dev-perspective.spec.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Never import from deprecated packages or use code with the
@deprecatedTSdoc tag in new code.
**/*.{ts,tsx}: Use React functional components with hooks instead of class components
State Management: Use React hooks and Context API (migrating away from legacy Redux/Immutable.js)
Hooks: Use existing hooks fromconsole-sharedwhen possible (useK8sWatchResource,useUserSettings, etc.)
API calls: Use k8s resource hooks for data fetching,consoleFetchJSONfor HTTP requests
Extensions: Use console extension points for plugin integration
Types: Check existing types inconsole-sharedbefore creating new ones
Dynamic Plugins: Use console extension points for plugin integration
Styling: Use SCSS modules co-located with components, PatternFly design system components, avoid any SCSS/CSS if possible
Accessibility: Follow WCAG 2.1 AA standards, use semantic HTML, ARIA labels where needed, ensure keyboard navigation, test with screen readers
i18n: UseuseTranslation('namespace')hook withkeyformat for translation keys
Error Handling: Use ErrorBoundary components and graceful degradation patterns
Optimize re-renders: UseuseCallbackfor memoized callbacks to avoid function recreation every render
Optimize re-renders: UseuseMemofor expensive computations to avoid recalculating on every render
Lazy loading: UseReact.lazy()to lazy load heavy components
TypeScript type safety: Avoid usinganytype; suggest proper type definitions and verify null/undefined are handled properly
Type component props properly: Reuse existing component prop types instead of duplicating type definitions
Use proper hooks: Use specialized hooks likeusePluginInfofor plugin data instead of generic data fetching patterns
Avoid deprecated components: Check for JSDoc@deprecatedtags, import paths containing/deprecated, andDEPRECATED_file name prefix before using components
Importing from barrel files and circular dependencies: Import directly from specific files instead...
Files:
frontend/e2e/pages/dev-console/pod-list-page.tsfrontend/e2e/tests/dev-console/customization-of-pinned-resource.spec.tsfrontend/e2e/tests/dev-console/add-health-checks-git-form.spec.tsfrontend/e2e/tests/dev-console/getting-started-tour-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/project-access.spec.tsfrontend/e2e/tests/dev-console/customization-of-catalog.spec.tsfrontend/e2e/pages/dev-console/config-map-page.tsfrontend/e2e/pages/dev-console/search-page.tsfrontend/e2e/tests/dev-console/configure-pinned-resources.spec.tsfrontend/e2e/tests/dev-console/getting-started-to-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/configure-perspectives.spec.tsfrontend/e2e/tests/dev-console/project-creation.spec.tsfrontend/e2e/tests/dev-console/search-devperspective.spec.tsfrontend/e2e/tests/dev-console/project-vulnerability.spec.tsfrontend/e2e/tests/dev-console/route-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/sample-card-add-page.spec.tsfrontend/e2e/pages/dev-console/health-checks-page.tsfrontend/e2e/pages/dev-console/user-preferences-page.tsfrontend/e2e/pages/dev-console/samples-page.tsfrontend/e2e/pages/dev-console/customization-page.tsfrontend/e2e/tests/dev-console/pod-list-page.spec.tsfrontend/e2e/tests/dev-console/add-health-checks-topology-sidebar.spec.tsfrontend/e2e/pages/dev-console/route-page.tsfrontend/e2e/tests/dev-console/customization-of-catalog-add-page-form-view.spec.tsfrontend/e2e/pages/dev-console/perspective-page.tsfrontend/e2e/pages/dev-console/vulnerability-page.tsfrontend/e2e/tests/dev-console/config-maps-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/user-preferences-dev-perspective.spec.ts
frontend/**/*.{ts,tsx,js,jsx,json}
📄 CodeRabbit inference engine (AGENTS.md)
Never use absolute URLs or paths in the console code. The console runs behind a proxy under an arbitrary path.
Files:
frontend/e2e/pages/dev-console/pod-list-page.tsfrontend/e2e/tests/dev-console/customization-of-pinned-resource.spec.tsfrontend/e2e/tests/dev-console/add-health-checks-git-form.spec.tsfrontend/e2e/tests/dev-console/getting-started-tour-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/project-access.spec.tsfrontend/e2e/tests/dev-console/customization-of-catalog.spec.tsfrontend/e2e/pages/dev-console/config-map-page.tsfrontend/e2e/pages/dev-console/search-page.tsfrontend/e2e/tests/dev-console/configure-pinned-resources.spec.tsfrontend/e2e/tests/dev-console/getting-started-to-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/configure-perspectives.spec.tsfrontend/e2e/tests/dev-console/project-creation.spec.tsfrontend/e2e/tests/dev-console/search-devperspective.spec.tsfrontend/e2e/tests/dev-console/project-vulnerability.spec.tsfrontend/e2e/tests/dev-console/route-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/sample-card-add-page.spec.tsfrontend/e2e/pages/dev-console/health-checks-page.tsfrontend/e2e/pages/dev-console/user-preferences-page.tsfrontend/e2e/pages/dev-console/samples-page.tsfrontend/e2e/pages/dev-console/customization-page.tsfrontend/e2e/tests/dev-console/pod-list-page.spec.tsfrontend/e2e/tests/dev-console/add-health-checks-topology-sidebar.spec.tsfrontend/e2e/pages/dev-console/route-page.tsfrontend/e2e/tests/dev-console/customization-of-catalog-add-page-form-view.spec.tsfrontend/e2e/pages/dev-console/perspective-page.tsfrontend/e2e/pages/dev-console/vulnerability-page.tsfrontend/e2e/tests/dev-console/config-maps-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/user-preferences-dev-perspective.spec.ts
frontend/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
When writing code for static plugins, ensure that all
$codeRefreference the corresponding extension type from the@console/dynamic-plugin-sdkpackage.
Files:
frontend/e2e/pages/dev-console/pod-list-page.tsfrontend/e2e/tests/dev-console/customization-of-pinned-resource.spec.tsfrontend/e2e/tests/dev-console/add-health-checks-git-form.spec.tsfrontend/e2e/tests/dev-console/getting-started-tour-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/project-access.spec.tsfrontend/e2e/tests/dev-console/customization-of-catalog.spec.tsfrontend/e2e/pages/dev-console/config-map-page.tsfrontend/e2e/pages/dev-console/search-page.tsfrontend/e2e/tests/dev-console/configure-pinned-resources.spec.tsfrontend/e2e/tests/dev-console/getting-started-to-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/configure-perspectives.spec.tsfrontend/e2e/tests/dev-console/project-creation.spec.tsfrontend/e2e/tests/dev-console/search-devperspective.spec.tsfrontend/e2e/tests/dev-console/project-vulnerability.spec.tsfrontend/e2e/tests/dev-console/route-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/sample-card-add-page.spec.tsfrontend/e2e/pages/dev-console/health-checks-page.tsfrontend/e2e/pages/dev-console/user-preferences-page.tsfrontend/e2e/pages/dev-console/samples-page.tsfrontend/e2e/pages/dev-console/customization-page.tsfrontend/e2e/tests/dev-console/pod-list-page.spec.tsfrontend/e2e/tests/dev-console/add-health-checks-topology-sidebar.spec.tsfrontend/e2e/pages/dev-console/route-page.tsfrontend/e2e/tests/dev-console/customization-of-catalog-add-page-form-view.spec.tsfrontend/e2e/pages/dev-console/perspective-page.tsfrontend/e2e/pages/dev-console/vulnerability-page.tsfrontend/e2e/tests/dev-console/config-maps-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/user-preferences-dev-perspective.spec.ts
**/*.{tsx,ts}
📄 CodeRabbit inference engine (TESTING.md)
**/*.{tsx,ts}: Always usepage.getByTestId('x')for Playwright selectors which queries[data-test="x"]. If a React element only has a legacy test attribute, adddata-testto the element. Never remove legacy attributes
Preferdata-testattributes in Cypress selectors (e.g.,cy.get('[data-test="create-deployment"]')) over brittle CSS/ARIA selectorsFile Naming: PascalCase for components, kebab-case for utilities,
*.spec.ts(x)for tests
Files:
frontend/e2e/pages/dev-console/pod-list-page.tsfrontend/e2e/tests/dev-console/customization-of-pinned-resource.spec.tsfrontend/e2e/tests/dev-console/add-health-checks-git-form.spec.tsfrontend/e2e/tests/dev-console/getting-started-tour-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/project-access.spec.tsfrontend/e2e/tests/dev-console/customization-of-catalog.spec.tsfrontend/e2e/pages/dev-console/config-map-page.tsfrontend/e2e/pages/dev-console/search-page.tsfrontend/e2e/tests/dev-console/configure-pinned-resources.spec.tsfrontend/e2e/tests/dev-console/getting-started-to-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/configure-perspectives.spec.tsfrontend/e2e/tests/dev-console/project-creation.spec.tsfrontend/e2e/tests/dev-console/search-devperspective.spec.tsfrontend/e2e/tests/dev-console/project-vulnerability.spec.tsfrontend/e2e/tests/dev-console/route-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/sample-card-add-page.spec.tsfrontend/e2e/pages/dev-console/health-checks-page.tsfrontend/e2e/pages/dev-console/user-preferences-page.tsfrontend/e2e/pages/dev-console/samples-page.tsfrontend/e2e/pages/dev-console/customization-page.tsfrontend/e2e/tests/dev-console/pod-list-page.spec.tsfrontend/e2e/tests/dev-console/add-health-checks-topology-sidebar.spec.tsfrontend/e2e/pages/dev-console/route-page.tsfrontend/e2e/tests/dev-console/customization-of-catalog-add-page-form-view.spec.tsfrontend/e2e/pages/dev-console/perspective-page.tsfrontend/e2e/pages/dev-console/vulnerability-page.tsfrontend/e2e/tests/dev-console/config-maps-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/user-preferences-dev-perspective.spec.ts
frontend/e2e/pages/**/*.ts
📄 CodeRabbit inference engine (TESTING.md)
Extend
BasePagein Playwright page objects which providesrobustClick(),waitForLoadingComplete(), andgoTo(). Locators areprivate readonlyproperties; actions areasyncmethods
Files:
frontend/e2e/pages/dev-console/pod-list-page.tsfrontend/e2e/pages/dev-console/config-map-page.tsfrontend/e2e/pages/dev-console/search-page.tsfrontend/e2e/pages/dev-console/health-checks-page.tsfrontend/e2e/pages/dev-console/user-preferences-page.tsfrontend/e2e/pages/dev-console/samples-page.tsfrontend/e2e/pages/dev-console/customization-page.tsfrontend/e2e/pages/dev-console/route-page.tsfrontend/e2e/pages/dev-console/perspective-page.tsfrontend/e2e/pages/dev-console/vulnerability-page.ts
**/*.{go,ts,tsx,js,jsx}
📄 CodeRabbit inference engine (STYLEGUIDE.md)
Use lowercase dash-separated names for all files to avoid git issues with case-insensitive file systems
Files:
frontend/e2e/pages/dev-console/pod-list-page.tsfrontend/e2e/tests/dev-console/customization-of-pinned-resource.spec.tsfrontend/e2e/tests/dev-console/add-health-checks-git-form.spec.tsfrontend/e2e/tests/dev-console/getting-started-tour-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/project-access.spec.tsfrontend/e2e/tests/dev-console/customization-of-catalog.spec.tsfrontend/e2e/pages/dev-console/config-map-page.tsfrontend/e2e/pages/dev-console/search-page.tsfrontend/e2e/tests/dev-console/configure-pinned-resources.spec.tsfrontend/e2e/tests/dev-console/getting-started-to-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/configure-perspectives.spec.tsfrontend/e2e/tests/dev-console/project-creation.spec.tsfrontend/e2e/tests/dev-console/search-devperspective.spec.tsfrontend/e2e/tests/dev-console/project-vulnerability.spec.tsfrontend/e2e/tests/dev-console/route-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/sample-card-add-page.spec.tsfrontend/e2e/pages/dev-console/health-checks-page.tsfrontend/e2e/pages/dev-console/user-preferences-page.tsfrontend/e2e/pages/dev-console/samples-page.tsfrontend/e2e/pages/dev-console/customization-page.tsfrontend/e2e/tests/dev-console/pod-list-page.spec.tsfrontend/e2e/tests/dev-console/add-health-checks-topology-sidebar.spec.tsfrontend/e2e/pages/dev-console/route-page.tsfrontend/e2e/tests/dev-console/customization-of-catalog-add-page-form-view.spec.tsfrontend/e2e/pages/dev-console/perspective-page.tsfrontend/e2e/pages/dev-console/vulnerability-page.tsfrontend/e2e/tests/dev-console/config-maps-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/user-preferences-dev-perspective.spec.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (STYLEGUIDE.md)
**/*.{ts,tsx,js,jsx}: New code MUST be written in TypeScript, not JavaScript
Prefer functional programming patterns and immutable data structures
Run the linter and follow all rules defined in .eslintrc
Never use absolute paths in code - the app should be able to run behind a proxy under an arbitrary path
Files:
frontend/e2e/pages/dev-console/pod-list-page.tsfrontend/e2e/tests/dev-console/customization-of-pinned-resource.spec.tsfrontend/e2e/tests/dev-console/add-health-checks-git-form.spec.tsfrontend/e2e/tests/dev-console/getting-started-tour-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/project-access.spec.tsfrontend/e2e/tests/dev-console/customization-of-catalog.spec.tsfrontend/e2e/pages/dev-console/config-map-page.tsfrontend/e2e/pages/dev-console/search-page.tsfrontend/e2e/tests/dev-console/configure-pinned-resources.spec.tsfrontend/e2e/tests/dev-console/getting-started-to-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/configure-perspectives.spec.tsfrontend/e2e/tests/dev-console/project-creation.spec.tsfrontend/e2e/tests/dev-console/search-devperspective.spec.tsfrontend/e2e/tests/dev-console/project-vulnerability.spec.tsfrontend/e2e/tests/dev-console/route-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/sample-card-add-page.spec.tsfrontend/e2e/pages/dev-console/health-checks-page.tsfrontend/e2e/pages/dev-console/user-preferences-page.tsfrontend/e2e/pages/dev-console/samples-page.tsfrontend/e2e/pages/dev-console/customization-page.tsfrontend/e2e/tests/dev-console/pod-list-page.spec.tsfrontend/e2e/tests/dev-console/add-health-checks-topology-sidebar.spec.tsfrontend/e2e/pages/dev-console/route-page.tsfrontend/e2e/tests/dev-console/customization-of-catalog-add-page-form-view.spec.tsfrontend/e2e/pages/dev-console/perspective-page.tsfrontend/e2e/pages/dev-console/vulnerability-page.tsfrontend/e2e/tests/dev-console/config-maps-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/user-preferences-dev-perspective.spec.ts
**/*.ts
📄 CodeRabbit inference engine (STYLEGUIDE.md)
Plugin SDK Changes: Any updates to
console-dynamic-plugin-sdkshould aim to maintain backward compatibility as it's a public API - use theplugin-api-reviewskill to vet changes for public API impact and ensure proper documentation updates
Files:
frontend/e2e/pages/dev-console/pod-list-page.tsfrontend/e2e/tests/dev-console/customization-of-pinned-resource.spec.tsfrontend/e2e/tests/dev-console/add-health-checks-git-form.spec.tsfrontend/e2e/tests/dev-console/getting-started-tour-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/project-access.spec.tsfrontend/e2e/tests/dev-console/customization-of-catalog.spec.tsfrontend/e2e/pages/dev-console/config-map-page.tsfrontend/e2e/pages/dev-console/search-page.tsfrontend/e2e/tests/dev-console/configure-pinned-resources.spec.tsfrontend/e2e/tests/dev-console/getting-started-to-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/configure-perspectives.spec.tsfrontend/e2e/tests/dev-console/project-creation.spec.tsfrontend/e2e/tests/dev-console/search-devperspective.spec.tsfrontend/e2e/tests/dev-console/project-vulnerability.spec.tsfrontend/e2e/tests/dev-console/route-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/sample-card-add-page.spec.tsfrontend/e2e/pages/dev-console/health-checks-page.tsfrontend/e2e/pages/dev-console/user-preferences-page.tsfrontend/e2e/pages/dev-console/samples-page.tsfrontend/e2e/pages/dev-console/customization-page.tsfrontend/e2e/tests/dev-console/pod-list-page.spec.tsfrontend/e2e/tests/dev-console/add-health-checks-topology-sidebar.spec.tsfrontend/e2e/pages/dev-console/route-page.tsfrontend/e2e/tests/dev-console/customization-of-catalog-add-page-form-view.spec.tsfrontend/e2e/pages/dev-console/perspective-page.tsfrontend/e2e/pages/dev-console/vulnerability-page.tsfrontend/e2e/tests/dev-console/config-maps-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/user-preferences-dev-perspective.spec.ts
frontend/**/*.{js,ts,tsx}
📄 CodeRabbit inference engine (README.md)
frontend/**/*.{js,ts,tsx}: Support only the latest versions of Edge, Chrome, Safari, and Firefox browsers; IE 11 and earlier are not supported
CSP violations should be automatically reported to telemetry by parsing dynamic plugin names from securitypolicyviolation events, with throttling to prevent duplicate reports within a day
Files:
frontend/e2e/pages/dev-console/pod-list-page.tsfrontend/e2e/tests/dev-console/customization-of-pinned-resource.spec.tsfrontend/e2e/tests/dev-console/add-health-checks-git-form.spec.tsfrontend/e2e/tests/dev-console/getting-started-tour-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/project-access.spec.tsfrontend/e2e/tests/dev-console/customization-of-catalog.spec.tsfrontend/e2e/pages/dev-console/config-map-page.tsfrontend/e2e/pages/dev-console/search-page.tsfrontend/e2e/tests/dev-console/configure-pinned-resources.spec.tsfrontend/e2e/tests/dev-console/getting-started-to-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/configure-perspectives.spec.tsfrontend/e2e/tests/dev-console/project-creation.spec.tsfrontend/e2e/tests/dev-console/search-devperspective.spec.tsfrontend/e2e/tests/dev-console/project-vulnerability.spec.tsfrontend/e2e/tests/dev-console/route-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/sample-card-add-page.spec.tsfrontend/e2e/pages/dev-console/health-checks-page.tsfrontend/e2e/pages/dev-console/user-preferences-page.tsfrontend/e2e/pages/dev-console/samples-page.tsfrontend/e2e/pages/dev-console/customization-page.tsfrontend/e2e/tests/dev-console/pod-list-page.spec.tsfrontend/e2e/tests/dev-console/add-health-checks-topology-sidebar.spec.tsfrontend/e2e/pages/dev-console/route-page.tsfrontend/e2e/tests/dev-console/customization-of-catalog-add-page-form-view.spec.tsfrontend/e2e/pages/dev-console/perspective-page.tsfrontend/e2e/pages/dev-console/vulnerability-page.tsfrontend/e2e/tests/dev-console/config-maps-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/user-preferences-dev-perspective.spec.ts
**/*.{ts,tsx,js}
📄 CodeRabbit inference engine (INTERNATIONALIZATION.md)
For dynamic translation keys that cannot be parsed by i18next-parser (t(key), t('key' + id), t(
key${id})), specify possible static values in comments for the parser to extract
Files:
frontend/e2e/pages/dev-console/pod-list-page.tsfrontend/e2e/tests/dev-console/customization-of-pinned-resource.spec.tsfrontend/e2e/tests/dev-console/add-health-checks-git-form.spec.tsfrontend/e2e/tests/dev-console/getting-started-tour-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/project-access.spec.tsfrontend/e2e/tests/dev-console/customization-of-catalog.spec.tsfrontend/e2e/pages/dev-console/config-map-page.tsfrontend/e2e/pages/dev-console/search-page.tsfrontend/e2e/tests/dev-console/configure-pinned-resources.spec.tsfrontend/e2e/tests/dev-console/getting-started-to-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/configure-perspectives.spec.tsfrontend/e2e/tests/dev-console/project-creation.spec.tsfrontend/e2e/tests/dev-console/search-devperspective.spec.tsfrontend/e2e/tests/dev-console/project-vulnerability.spec.tsfrontend/e2e/tests/dev-console/route-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/sample-card-add-page.spec.tsfrontend/e2e/pages/dev-console/health-checks-page.tsfrontend/e2e/pages/dev-console/user-preferences-page.tsfrontend/e2e/pages/dev-console/samples-page.tsfrontend/e2e/pages/dev-console/customization-page.tsfrontend/e2e/tests/dev-console/pod-list-page.spec.tsfrontend/e2e/tests/dev-console/add-health-checks-topology-sidebar.spec.tsfrontend/e2e/pages/dev-console/route-page.tsfrontend/e2e/tests/dev-console/customization-of-catalog-add-page-form-view.spec.tsfrontend/e2e/pages/dev-console/perspective-page.tsfrontend/e2e/pages/dev-console/vulnerability-page.tsfrontend/e2e/tests/dev-console/config-maps-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/user-preferences-dev-perspective.spec.ts
frontend/e2e/tests/**/*.spec.ts
📄 CodeRabbit inference engine (TESTING.md)
frontend/e2e/tests/**/*.spec.ts: E2E tests must validate full user workflows against a real OpenShift cluster using Playwright, located infrontend/e2e/tests/<package>/
Each Playwright E2E test block must create its own resources, assert independently, and clean up via thecleanupfixture
UseKubernetesClientfor cluster interactions in Playwright tests - never use shell commands in tests
Importtestandexpectfrome2e/fixtures, not from@playwright/test. Custom fixtures providecleanup,testConfig, andk8sClient
Files:
frontend/e2e/tests/dev-console/customization-of-pinned-resource.spec.tsfrontend/e2e/tests/dev-console/add-health-checks-git-form.spec.tsfrontend/e2e/tests/dev-console/getting-started-tour-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/project-access.spec.tsfrontend/e2e/tests/dev-console/customization-of-catalog.spec.tsfrontend/e2e/tests/dev-console/configure-pinned-resources.spec.tsfrontend/e2e/tests/dev-console/getting-started-to-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/configure-perspectives.spec.tsfrontend/e2e/tests/dev-console/project-creation.spec.tsfrontend/e2e/tests/dev-console/search-devperspective.spec.tsfrontend/e2e/tests/dev-console/project-vulnerability.spec.tsfrontend/e2e/tests/dev-console/route-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/sample-card-add-page.spec.tsfrontend/e2e/tests/dev-console/pod-list-page.spec.tsfrontend/e2e/tests/dev-console/add-health-checks-topology-sidebar.spec.tsfrontend/e2e/tests/dev-console/customization-of-catalog-add-page-form-view.spec.tsfrontend/e2e/tests/dev-console/config-maps-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/user-preferences-dev-perspective.spec.ts
**/*.spec.{ts,tsx}
📄 CodeRabbit inference engine (STYLEGUIDE.md)
Tests should follow a similar 'test tables' convention as used in Go where applicable
Files:
frontend/e2e/tests/dev-console/customization-of-pinned-resource.spec.tsfrontend/e2e/tests/dev-console/add-health-checks-git-form.spec.tsfrontend/e2e/tests/dev-console/getting-started-tour-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/project-access.spec.tsfrontend/e2e/tests/dev-console/customization-of-catalog.spec.tsfrontend/e2e/tests/dev-console/configure-pinned-resources.spec.tsfrontend/e2e/tests/dev-console/getting-started-to-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/configure-perspectives.spec.tsfrontend/e2e/tests/dev-console/project-creation.spec.tsfrontend/e2e/tests/dev-console/search-devperspective.spec.tsfrontend/e2e/tests/dev-console/project-vulnerability.spec.tsfrontend/e2e/tests/dev-console/route-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/sample-card-add-page.spec.tsfrontend/e2e/tests/dev-console/pod-list-page.spec.tsfrontend/e2e/tests/dev-console/add-health-checks-topology-sidebar.spec.tsfrontend/e2e/tests/dev-console/customization-of-catalog-add-page-form-view.spec.tsfrontend/e2e/tests/dev-console/config-maps-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/user-preferences-dev-perspective.spec.ts
**/*route*.{ts,tsx}
📄 CodeRabbit inference engine (STYLEGUIDE.md)
Routing: Plugin routes go in plugin-specific route files
Files:
frontend/e2e/tests/dev-console/route-dev-perspective.spec.tsfrontend/e2e/pages/dev-console/route-page.ts
🧠 Learnings (4)
📚 Learning: 2026-05-19T17:15:42.558Z
Learnt from: fsgreco
Repo: openshift/console PR: 16461
File: frontend/e2e/pages/web-terminal-config-page.ts:18-18
Timestamp: 2026-05-19T17:15:42.558Z
Learning: In the openshift/console repo, the guideline “Never use absolute URLs or paths in the console code” should be treated as an application-source-only rule. Do not flag absolute-root paths/URLs (e.g., strings starting with `/` like `/k8s/cluster/...`) in Playwright E2E code under `frontend/e2e/`, including page objects (e.g., `page.goto()` / `goTo()` calls). These absolute paths are considered correct and intentional because Playwright resolves them against the `baseURL` configured in `frontend/e2e/playwright.config.ts`. Only enforce the no-absolute-paths rule in application code under `frontend/public` and `frontend/packages`.
Applied to files:
frontend/e2e/pages/dev-console/pod-list-page.tsfrontend/e2e/tests/dev-console/customization-of-pinned-resource.spec.tsfrontend/e2e/tests/dev-console/add-health-checks-git-form.spec.tsfrontend/e2e/tests/dev-console/getting-started-tour-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/project-access.spec.tsfrontend/e2e/tests/dev-console/customization-of-catalog.spec.tsfrontend/e2e/pages/dev-console/config-map-page.tsfrontend/e2e/pages/dev-console/search-page.tsfrontend/e2e/tests/dev-console/configure-pinned-resources.spec.tsfrontend/e2e/tests/dev-console/getting-started-to-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/configure-perspectives.spec.tsfrontend/e2e/tests/dev-console/project-creation.spec.tsfrontend/e2e/tests/dev-console/search-devperspective.spec.tsfrontend/e2e/tests/dev-console/project-vulnerability.spec.tsfrontend/e2e/tests/dev-console/route-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/sample-card-add-page.spec.tsfrontend/e2e/pages/dev-console/health-checks-page.tsfrontend/e2e/pages/dev-console/user-preferences-page.tsfrontend/e2e/pages/dev-console/samples-page.tsfrontend/e2e/pages/dev-console/customization-page.tsfrontend/e2e/tests/dev-console/pod-list-page.spec.tsfrontend/e2e/tests/dev-console/add-health-checks-topology-sidebar.spec.tsfrontend/e2e/pages/dev-console/route-page.tsfrontend/e2e/tests/dev-console/customization-of-catalog-add-page-form-view.spec.tsfrontend/e2e/pages/dev-console/perspective-page.tsfrontend/e2e/pages/dev-console/vulnerability-page.tsfrontend/e2e/tests/dev-console/config-maps-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/user-preferences-dev-perspective.spec.ts
📚 Learning: 2026-05-20T13:09:57.325Z
Learnt from: fsgreco
Repo: openshift/console PR: 16461
File: frontend/e2e/pages/web-terminal-page.ts:59-59
Timestamp: 2026-05-20T13:09:57.325Z
Learning: In page object files under `frontend/e2e/pages/`, do not treat `.catch(() => {})` (or other empty/ignored catches) chained to `waitFor(...)` as a bug when the wait is intended to be best-effort. These catches indicate the element may or may not appear, so the page object should not fail the test; real expectations should be asserted in the spec files (e.g., `expect(...).toBeVisible()`). This is consistent with the `waitForLoadingComplete` behavior in `BasePage`—reviewers should allow the empty catches when they match this pattern.
Applied to files:
frontend/e2e/pages/dev-console/pod-list-page.tsfrontend/e2e/pages/dev-console/config-map-page.tsfrontend/e2e/pages/dev-console/search-page.tsfrontend/e2e/pages/dev-console/health-checks-page.tsfrontend/e2e/pages/dev-console/user-preferences-page.tsfrontend/e2e/pages/dev-console/samples-page.tsfrontend/e2e/pages/dev-console/customization-page.tsfrontend/e2e/pages/dev-console/route-page.tsfrontend/e2e/pages/dev-console/perspective-page.tsfrontend/e2e/pages/dev-console/vulnerability-page.ts
📚 Learning: 2026-05-20T13:21:50.712Z
Learnt from: fsgreco
Repo: openshift/console PR: 16461
File: frontend/e2e/pages/web-terminal-page.ts:11-26
Timestamp: 2026-05-20T13:21:50.712Z
Learning: In Playwright page objects under `frontend/e2e/pages/`, treat locators that intentionally use dual CSS selectors like `.locator('[data-test="X"], [data-test-id="X"]')` as a backwards-compatibility shim. The legacy `data-test-id` is required because newly added `data-test` attributes may not exist on yet-to-be-upgraded deployed clusters. Do not flag these dual-selector locators as a code smell and do not suggest replacing them with a single `getByTestId('X')` until the new `data-test` attributes have propagated to all target clusters.
Applied to files:
frontend/e2e/pages/dev-console/pod-list-page.tsfrontend/e2e/pages/dev-console/config-map-page.tsfrontend/e2e/pages/dev-console/search-page.tsfrontend/e2e/pages/dev-console/health-checks-page.tsfrontend/e2e/pages/dev-console/user-preferences-page.tsfrontend/e2e/pages/dev-console/samples-page.tsfrontend/e2e/pages/dev-console/customization-page.tsfrontend/e2e/pages/dev-console/route-page.tsfrontend/e2e/pages/dev-console/perspective-page.tsfrontend/e2e/pages/dev-console/vulnerability-page.ts
📚 Learning: 2026-05-20T18:30:38.087Z
Learnt from: stefanonardo
Repo: openshift/console PR: 16431
File: frontend/e2e/tests/console/app/filtering-and-searching.spec.ts:20-62
Timestamp: 2026-05-20T18:30:38.087Z
Learning: In the OpenShift console Playwright E2E tests (frontend/e2e/tests/), follow the documented teardown strategy:
- Use the per-test Playwright `cleanup` fixture when each test creates its own resources and needs isolated create/cleanup.
- If multiple tests in the same suite share an expensive resource (e.g., a Deployment that takes ~30s to become Ready), prefer manual suite setup/teardown using `beforeAll`/`afterAll` with a `KubernetesClient` instance instead of the `cleanup` fixture, because the `cleanup` fixture is not meant for resources shared across multiple tests.
- During review, do not flag `beforeAll`/`afterAll` teardown that uses `KubernetesClient` as an “isolation violation” when it is clearly sharing a common resource for performance and matches this Strategy B pattern.
Applied to files:
frontend/e2e/tests/dev-console/customization-of-pinned-resource.spec.tsfrontend/e2e/tests/dev-console/add-health-checks-git-form.spec.tsfrontend/e2e/tests/dev-console/getting-started-tour-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/project-access.spec.tsfrontend/e2e/tests/dev-console/customization-of-catalog.spec.tsfrontend/e2e/tests/dev-console/configure-pinned-resources.spec.tsfrontend/e2e/tests/dev-console/getting-started-to-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/configure-perspectives.spec.tsfrontend/e2e/tests/dev-console/project-creation.spec.tsfrontend/e2e/tests/dev-console/search-devperspective.spec.tsfrontend/e2e/tests/dev-console/project-vulnerability.spec.tsfrontend/e2e/tests/dev-console/route-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/sample-card-add-page.spec.tsfrontend/e2e/tests/dev-console/pod-list-page.spec.tsfrontend/e2e/tests/dev-console/add-health-checks-topology-sidebar.spec.tsfrontend/e2e/tests/dev-console/customization-of-catalog-add-page-form-view.spec.tsfrontend/e2e/tests/dev-console/config-maps-dev-perspective.spec.tsfrontend/e2e/tests/dev-console/user-preferences-dev-perspective.spec.ts
🔇 Additional comments (25)
frontend/e2e/pages/dev-console/config-map-page.ts (1)
5-62: LGTM!frontend/e2e/pages/dev-console/health-checks-page.ts (1)
6-69: LGTM!frontend/e2e/pages/dev-console/pod-list-page.ts (1)
6-29: LGTM!frontend/e2e/pages/dev-console/route-page.ts (1)
6-40: LGTM!Also applies to: 45-69
frontend/e2e/pages/dev-console/samples-page.ts (1)
6-71: LGTM!Also applies to: 76-109
frontend/e2e/pages/dev-console/search-page.ts (1)
6-48: LGTM!frontend/e2e/pages/dev-console/vulnerability-page.ts (1)
6-129: LGTM!frontend/e2e/tests/dev-console/config-maps-dev-perspective.spec.ts (1)
1-90: LGTM!frontend/e2e/tests/dev-console/route-dev-perspective.spec.ts (1)
1-134: LGTM!frontend/e2e/tests/dev-console/sample-card-add-page.spec.ts (1)
1-158: LGTM!frontend/e2e/tests/dev-console/project-vulnerability.spec.ts (1)
1-153: LGTM!frontend/e2e/tests/dev-console/search-devperspective.spec.ts (1)
1-112: LGTM!frontend/e2e/pages/dev-console/perspective-page.ts (1)
1-42: LGTM!Also applies to: 55-103
frontend/e2e/pages/dev-console/user-preferences-page.ts (1)
1-89: LGTM!frontend/e2e/pages/dev-console/customization-page.ts (1)
1-113: LGTM!frontend/e2e/tests/dev-console/add-health-checks-git-form.spec.ts (1)
1-7: LGTM!frontend/e2e/tests/dev-console/getting-started-to-dev-perspective.spec.ts (1)
1-7: LGTM!frontend/e2e/tests/dev-console/getting-started-tour-dev-perspective.spec.ts (1)
1-19: LGTM!frontend/e2e/tests/dev-console/configure-pinned-resources.spec.ts (1)
1-35: LGTM!frontend/e2e/tests/dev-console/project-creation.spec.ts (1)
1-15: LGTM!Also applies to: 21-28
frontend/e2e/tests/dev-console/configure-perspectives.spec.ts (2)
7-17: LGTM!
19-77: 🏗️ Heavy liftRestore cluster configuration after enabling Developer perspective
This test mutates cluster-wide perspective configuration but the provided snippet does not show any cleanup or state restoration. Per the E2E testing guidelines, tests must clean up resources via the
cleanupfixture. Either add teardown logic to restore the previous perspective state, or use the providedcleanupfixture if the Page Object already supports recording changes for cleanup.frontend/e2e/tests/dev-console/customization-of-catalog.spec.ts (1)
1-23: LGTM!frontend/e2e/tests/dev-console/customization-of-pinned-resource.spec.ts (1)
26-60: LGTM!frontend/e2e/tests/dev-console/project-access.spec.ts (1)
1-11: LGTM!
| async switchToAdministrator(): Promise<void> { | ||
| await this.switchToPerspective('Administrator'); | ||
| // Also handle "Core platform" label (OpenShift 5.0+) | ||
| const currentText = await this.perspectiveSwitcherToggle.textContent(); | ||
| if ( | ||
| !currentText?.includes('Administrator') && | ||
| !currentText?.includes('Core platform') | ||
| ) { | ||
| await this.switchToPerspective('Core platform'); | ||
| } |
There was a problem hiding this comment.
Fix fallback order for Administrator/Core platform switching.
On Line 44, switchToPerspective('Administrator') can fail before the Core platform fallback runs (Lines 46–52) when only the new label is present.
Proposed fix
async switchToAdministrator(): Promise<void> {
- await this.switchToPerspective('Administrator');
- // Also handle "Core platform" label (OpenShift 5.0+)
- const currentText = await this.perspectiveSwitcherToggle.textContent();
- if (
- !currentText?.includes('Administrator') &&
- !currentText?.includes('Core platform')
- ) {
- await this.switchToPerspective('Core platform');
- }
+ const currentText = await this.perspectiveSwitcherToggle.textContent();
+ if (currentText?.includes('Administrator') || currentText?.includes('Core platform')) {
+ return;
+ }
+
+ await this.robustClick(this.perspectiveSwitcherToggle);
+ const options = this.perspectiveSwitcherMenu.locator(
+ '[data-test-id="perspective-switcher-menu-option"]',
+ );
+ const administratorOption = options.filter({ hasText: 'Administrator' });
+ const corePlatformOption = options.filter({ hasText: 'Core platform' });
+
+ if ((await administratorOption.count()) > 0) {
+ await this.robustClick(administratorOption);
+ } else {
+ await this.robustClick(corePlatformOption);
+ }
+ await this.waitForLoadingComplete();
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/e2e/pages/dev-console/perspective-page.ts` around lines 43 - 52, The
current implementation in switchToAdministrator calls
switchToPerspective('Administrator') unconditionally which can fail when the UI
uses the new "Core platform" label; first read
perspectiveSwitcherToggle.textContent() and if it already contains
"Administrator" return, otherwise if it contains "Core platform" call
switchToPerspective('Core platform'); only if neither label is present attempt
switchToPerspective('Administrator') and if that doesn't result in the expected
label then fallback to switchToPerspective('Core platform') so the function
handles both legacy and new labels reliably.
| const port = targetPort.substring(0, 4); | ||
| await this.robustClick(this.page.locator(`[data-test-dropdown-menu="${port}-tcp"]`)); |
There was a problem hiding this comment.
Fix target-port parsing to avoid invalid dropdown selectors.
The current 4-char slice can build wrong selectors for non-4-digit ports, causing route creation to fail.
Proposed fix
- const port = targetPort.substring(0, 4);
- await this.robustClick(this.page.locator(`[data-test-dropdown-menu="${port}-tcp"]`));
+ const port = targetPort.split('-')[0];
+ await this.robustClick(this.page.locator(`[data-test-dropdown-menu="${port}-tcp"]`));📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const port = targetPort.substring(0, 4); | |
| await this.robustClick(this.page.locator(`[data-test-dropdown-menu="${port}-tcp"]`)); | |
| const port = targetPort.split('-')[0]; | |
| await this.robustClick(this.page.locator(`[data-test-dropdown-menu="${port}-tcp"]`)); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/e2e/pages/dev-console/route-page.ts` around lines 41 - 42, The
dropdown selector breaks because port is derived via a fixed 4-character
substring; update the parsing in route-page.ts where targetPort is handled so
the port is extracted as the actual numeric port string (e.g., strip
protocol/suffix like "/tcp" and non-digit chars, or split on '/' and take the
first token, or parseInt and convert back to string) before calling
this.page.locator and this.robustClick (referenced symbols: targetPort, const
port, page.locator, robustClick). Ensure the resulting port string exactly
matches the dropdown data-test value (e.g., "80" or "8080") rather than a
fixed-width slice.
| async expectGitUrlReadonly(): Promise<void> { | ||
| await expect(this.gitUrlInput).toBeVisible(); | ||
| } |
There was a problem hiding this comment.
Assert read-only state, not just visibility.
expectGitUrlReadonly() does not currently validate read-only behavior, so the check can pass even when the field is editable.
Proposed fix
async expectGitUrlReadonly(): Promise<void> {
- await expect(this.gitUrlInput).toBeVisible();
+ await expect(this.gitUrlInput).toBeVisible();
+ await expect(this.gitUrlInput).toBeDisabled();
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| async expectGitUrlReadonly(): Promise<void> { | |
| await expect(this.gitUrlInput).toBeVisible(); | |
| } | |
| async expectGitUrlReadonly(): Promise<void> { | |
| await expect(this.gitUrlInput).toBeVisible(); | |
| await expect(this.gitUrlInput).toBeDisabled(); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/e2e/pages/dev-console/samples-page.ts` around lines 72 - 74, The
test currently only checks visibility; update expectGitUrlReadonly to assert the
field is actually read-only by verifying it's not editable and/or has the
readonly attribute. For example, replace the visibility-only assertion with
checks against the Locator this.gitUrlInput such as awaiting
expect(this.gitUrlInput).not.toBeEditable() and/or awaiting
expect(this.gitUrlInput).toHaveAttribute('readonly', expect.any(String)) so the
method verifies read-only behavior rather than just visibility.
| for (const name of deployments) { | ||
| await k8s.appsV1Api.createNamespacedDeployment({ | ||
| namespace, | ||
| body: { | ||
| apiVersion: 'apps/v1', | ||
| kind: 'Deployment', | ||
| metadata: { name, namespace }, | ||
| spec: { | ||
| replicas: 1, | ||
| selector: { matchLabels: { app: name } }, | ||
| template: { | ||
| metadata: { labels: { app: name } }, | ||
| spec: { | ||
| containers: [ | ||
| { | ||
| name, | ||
| image: 'registry.access.redhat.com/ubi8/nodejs-18:latest', | ||
| ports: [{ containerPort: 8080, protocol: 'TCP' }], | ||
| }, | ||
| ], | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| }); | ||
| } |
There was a problem hiding this comment.
Wait for seeded deployments to become available before topology actions.
The suite creates deployments and immediately runs topology interactions that assume nodes exist. Add readiness waits in setup to avoid race-driven flakes.
Suggested fix
for (const name of deployments) {
await k8s.appsV1Api.createNamespacedDeployment({
namespace,
body: {
@@
},
});
+ await k8s.waitForCustomResourceCondition(
+ 'apps',
+ 'v1',
+ namespace,
+ 'deployments',
+ name,
+ (resource: any) => resource?.status?.availableReplicas > 0,
+ 120_000,
+ );
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/e2e/tests/dev-console/add-health-checks-topology-sidebar.spec.ts`
around lines 20 - 45, The test creates multiple deployments using deployments
and k8s.appsV1Api.createNamespacedDeployment but proceeds immediately to
topology actions; add an await loop after creation that polls the Deployment
status (via k8s.appsV1Api.readNamespacedDeployment or
readNamespacedDeploymentStatus) for each name in deployments in the given
namespace until status.availableReplicas >= 1 (or desired replicas) or a timeout
is reached, and only continue once all created deployments report ready to avoid
race flakes.
| const combobox = page.locator('[role="combobox"]'); | ||
| await combobox.click(); | ||
| const filterInput = page.locator('[aria-label="Type to filter"]'); | ||
| await filterInput.fill('console'); | ||
| const consoleItem = page.locator('[class="co-resource-item"]').filter({ | ||
| hasText: 'operator.openshift.io', | ||
| }); | ||
| if ((await consoleItem.count()) > 0) { | ||
| await consoleItem.click(); | ||
| } else { | ||
| await page.locator('[class="co-resource-item"]').first().click(); | ||
| } |
There was a problem hiding this comment.
❓ Verification inconclusive
Script executed:
#!/bin/bash
# First, check if the file exists and read the relevant lines
if [ -f "frontend/e2e/tests/dev-console/configure-perspectives.spec.ts" ]; then
echo "=== File found. Reading lines around 31-42 ==="
wc -l frontend/e2e/tests/dev-console/configure-perspectives.spec.ts
echo ""
sed -n '25,50p' frontend/e2e/tests/dev-console/configure-perspectives.spec.ts
else
echo "File not found"
find . -name "*configure-perspectives*" -type f
fiRepository: openshift/console
Repository: openshift/console
Exit code: 0
stdout:
=== File found. Reading lines around 31-42 ===
79 frontend/e2e/tests/dev-console/configure-perspectives.spec.ts
await page.goto('/');
await perspectivePage.switchToAdministrator();
});
await test.step('Search for console resource', async () => {
await navPage.clickNavLink(['Home', 'Search']);
const combobox = page.locator('[role="combobox"]');
await combobox.click();
const filterInput = page.locator('[aria-label="Type to filter"]');
await filterInput.fill('console');
const consoleItem = page.locator('[class="co-resource-item"]').filter({
hasText: 'operator.openshift.io',
});
if ((await consoleItem.count()) > 0) {
await consoleItem.click();
} else {
await page.locator('[class="co-resource-item"]').first().click();
}
await page
.locator('button[aria-label="Clear input value"]')
.click()
.catch(() => {});
await page.locator('body').click();
});
await test.step('Click on cluster', async () => {
Script executed:
#!/bin/bash
# Find E2E test files and understand the test structure
find frontend/e2e/tests -name "*.spec.ts" | head -5
echo ""
echo "=== Listing dev-console test directory ==="
ls -la frontend/e2e/tests/dev-console/ 2>/dev/null | head -20Repository: openshift/console
Repository: openshift/console
Exit code: 0
stdout:
frontend/e2e/tests/console/cluster-settings/channel-modal.spec.ts
frontend/e2e/tests/console/cluster-settings/cluster-settings.spec.ts
frontend/e2e/tests/console/cluster-settings/managed-control-plane.spec.ts
frontend/e2e/tests/console/cluster-settings/oauth.spec.ts
frontend/e2e/tests/console/cluster-settings/update-in-progress.spec.ts
=== Listing dev-console test directory ===
total 100
drwxr-xr-x 2 jailuser jailuser 400 May 20 18:48 .
drwxr-xr-x 5 jailuser jailuser 100 May 20 18:48 ..
-rw-r--r-- 1 jailuser jailuser 284 May 20 18:48 add-health-checks-git-form.spec.ts
-rw-r--r-- 1 jailuser jailuser 6920 May 20 18:48 add-health-checks-topology-sidebar.spec.ts
-rw-r--r-- 1 jailuser jailuser 3321 May 20 18:48 config-maps-dev-perspective.spec.ts
-rw-r--r-- 1 jailuser jailuser 3153 May 20 18:48 configure-perspectives.spec.ts
-rw-r--r-- 1 jailuser jailuser 1440 May 20 18:48 configure-pinned-resources.spec.ts
-rw-r--r-- 1 jailuser jailuser 7810 May 20 18:48 customization-of-catalog-add-page-form-view.spec.ts
-rw-r--r-- 1 jailuser jailuser 816 May 20 18:48 customization-of-catalog.spec.ts
-rw-r--r-- 1 jailuser jailuser 2363 May 20 18:48 customization-of-pinned-resource.spec.ts
-rw-r--r-- 1 jailuser jailuser 331 May 20 18:48 getting-started-to-dev-perspective.spec.ts
-rw-r--r-- 1 jailuser jailuser 649 May 20 18:48 getting-started-tour-dev-perspective.spec.ts
-rw-r--r-- 1 jailuser jailuser 3513 May 20 18:48 pod-list-page.spec.ts
-rw-r--r-- 1 jailuser jailuser 467 May 20 18:48 project-access.spec.ts
-rw-r--r-- 1 jailuser jailuser 1096 May 20 18:48 project-creation.spec.ts
-rw-r--r-- 1 jailuser jailuser 5638 May 20 18:48 project-vulnerability.spec.ts
-rw-r--r-- 1 jailuser jailuser 4665 May 20 18:48 route-dev-perspective.spec.ts
-rw-r--r-- 1 jailuser jailuser 6051 May 20 18:48 sample-card-add-page.spec.ts
-rw-r--r-- 1 jailuser jailuser 3896 May 20 18:48 search-devperspective.spec.ts
Script executed:
#!/bin/bash
# Search for the resource item component and search components to check for data-test attributes
echo "=== Searching for co-resource-item in components ==="
rg "co-resource-item" --type ts --type tsx -l | head -10
echo ""
echo "=== Searching for data-test attributes on resource items ==="
rg "co-resource-item" --type ts --type tsx -B2 -A2 | head -40Repository: openshift/console
Repository: openshift/console
Exit code: 0
stdout:
=== Searching for co-resource-item in components ===
=== Searching for data-test attributes on resource items ===
stderr:
unrecognized file type: tsx
unrecognized file type: tsx
Script executed:
#!/bin/bash
# Look at the SearchPage helper to understand the test pattern
find frontend/e2e -name "*search*" -o -name "*page*" | grep -E "\.(ts|tsx)$" | head -15
echo ""
echo "=== Check fixtures directory ==="
ls frontend/e2e/fixtures/ 2>/dev/null | head -20Repository: openshift/console
Repository: openshift/console
Exit code: 0
stdout:
frontend/e2e/pages/base-page.ts
frontend/e2e/pages/cluster-settings-page.ts
frontend/e2e/pages/details-page.ts
frontend/e2e/pages/dev-console/config-map-page.ts
frontend/e2e/pages/dev-console/customization-page.ts
frontend/e2e/pages/dev-console/health-checks-page.ts
frontend/e2e/pages/dev-console/perspective-page.ts
frontend/e2e/pages/dev-console/pod-list-page.ts
frontend/e2e/pages/dev-console/route-page.ts
frontend/e2e/pages/dev-console/samples-page.ts
frontend/e2e/pages/dev-console/search-page.ts
frontend/e2e/pages/dev-console/user-preferences-page.ts
frontend/e2e/pages/dev-console/vulnerability-page.ts
frontend/e2e/pages/oauth-page.ts
frontend/e2e/pages/overview-page.ts
=== Check fixtures directory ===
cleanup-fixture.ts
index.ts
Script executed:
#!/bin/bash
# Search for data-test attributes on combobox and filter inputs in the codebase
echo "=== Searching for combobox with data-test ==="
rg 'data-test.*combobox|role="combobox"' --type ts --type tsx -B1 -A1 | head -50
echo ""
echo "=== Searching for SearchResourceItem or similar components ==="
rg "SearchResourceItem|resource.*item|co-resource-item" --type ts --type tsx -l | head -10Repository: openshift/console
Repository: openshift/console
Exit code: 0
stdout:
=== Searching for combobox with data-test ===
=== Searching for SearchResourceItem or similar components ===
stderr:
unrecognized file type: tsx
unrecognized file type: tsx
Script executed:
#!/bin/bash
# Check the actual component that renders the resource items
find frontend -path "*/node_modules" -prune -o -name "*.tsx" -o -name "*.ts" | xargs grep -l "co-resource-item" | head -10Repository: openshift/console
Repository: openshift/console
Exit code: 0
stdout:
frontend/e2e/tests/dev-console/configure-perspectives.spec.ts
frontend/packages/console-app/src/components/volume-snapshot/volume-snapshot-class.tsx
frontend/packages/console-shared/src/components/dropdown/ResourceDropdown.tsx
frontend/packages/dev-console/integration-tests/support/step-definitions/customization/configure-perspectives.ts
frontend/packages/dev-console/src/components/catalog/PinnedResourcesConfiguration.tsx
frontend/packages/helm-plugin/src/components/list-page/HelmReleaseListRow.tsx
frontend/packages/helm-plugin/src/topology/sidebar/release-panel/resource-link.tsx
frontend/packages/integration-tests/tests/app/demo-dynamic-plugin.cy.ts
frontend/packages/integration-tests/views/environment.ts
frontend/packages/integration-tests/views/rolebindings.ts
stderr:
xargs: grep: terminated by signal 13
Script executed:
#!/bin/bash
# Find where co-resource-item class is used and what components render it
rg "className.*co-resource-item|co-resource-item" --type tsx --type ts -B3 -A3 | head -60Repository: openshift/console
Repository: openshift/console
Exit code: 0
stderr:
unrecognized file type: tsx
Script executed:
#!/bin/bash
# Read the search page helper
cat frontend/e2e/pages/dev-console/search-page.tsRepository: openshift/console
Repository: openshift/console
Exit code: 0
stdout:
import type { Page } from '`@playwright/test`';
import { expect } from '`@playwright/test`';
import BasePage from '../base-page';
export class SearchPage extends BasePage {
private readonly resourceFilterInput = this.page.locator('input[placeholder="Resources"]');
private readonly resourceOptionsMenu = this.page.locator('[aria-label="Options menu"]');
private readonly recentlyUsedSection = this.page.locator('[aria-labelledby="Recently-used"]');
private readonly clearHistoryButton = this.page.locator('[data-test-id="close-icon"]');
constructor(page: Page) {
super(page);
}
async searchAndSelectResource(resourceName: string): Promise<void> {
await this.robustClick(this.page.locator('[aria-label="Type to filter"]'));
await this.resourceFilterInput.clear();
await this.resourceFilterInput.fill(resourceName);
await this.robustClick(this.page.locator(`label[id$="${resourceName}"]`));
}
async openResourceFilter(): Promise<void> {
await expect(this.resourceOptionsMenu).toBeVisible();
await this.robustClick(this.resourceOptionsMenu);
}
async expectRecentlyUsedToContain(resourceName: string): Promise<void> {
await expect(
this.recentlyUsedSection.locator(`[data-filter-text="AR${resourceName}"]`),
).toBeVisible();
}
async expectRecentlyUsedToContainAll(resources: string[]): Promise<void> {
for (const resource of resources) {
await expect(this.recentlyUsedSection.locator('label')).toContainText(resource);
}
}
async clearHistory(): Promise<void> {
await expect(this.clearHistoryButton).toBeVisible();
await this.robustClick(this.clearHistoryButton, { force: true });
}
async expectRecentlyUsedNotVisible(): Promise<void> {
await expect(this.recentlyUsedSection).not.toBeAttached();
}
}
Script executed:
#!/bin/bash
# Check the ResourceDropdown component
head -100 frontend/packages/console-shared/src/components/dropdown/ResourceDropdown.tsxRepository: openshift/console
Repository: openshift/console
Exit code: 0
stdout:
import type { FC, ReactNode } from 'react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import * as fuzzy from 'fuzzysearch';
import * as _ from 'lodash';
import { useTranslation } from 'react-i18next';
import type { WatchK8sResultsObject } from '`@console/dynamic-plugin-sdk`';
import type { ConsoleSelectProps } from '`@console/internal/components/utils/console-select`';
import { ConsoleSelect } from '`@console/internal/components/utils/console-select`';
import { ResourceIcon } from '`@console/internal/components/utils/resource-icon`';
import { LoadingInline } from '`@console/internal/components/utils/status-box`';
import type { K8sResourceKind, K8sKind } from '`@console/internal/module/k8s`';
import { referenceForModel, modelFor, referenceFor } from '`@console/internal/module/k8s`';
/** Extended result type that includes optional kind for badge display fallback */
type ResourceDropdownResult = WatchK8sResultsObject<K8sResourceKind | K8sResourceKind[]> & {
kind?: string;
};
type DropdownItemProps = {
model: K8sKind;
name: string;
};
const DropdownItem: FC<DropdownItemProps> = ({ model, name }) => (
<span className="co-resource-item">
<span className="co-resource-icon--fixed-width">
<ResourceIcon kind={referenceForModel(model)} />
</span>
<span className="co-resource-item__resource-name">
<span>{name}</span>
</span>
</span>
);
export type ResourceDropdownItems = ConsoleSelectProps['items'];
export interface ResourceDropdownProps {
actionItems?: ConsoleSelectProps['actionItems'];
ariaLabel?: ConsoleSelectProps['ariaLabel'];
autocompleteFilter?: ConsoleSelectProps['autocompleteFilter'];
buttonClassName?: ConsoleSelectProps['buttonClassName'];
className?: ConsoleSelectProps['className'];
disabled?: ConsoleSelectProps['disabled'];
id?: ConsoleSelectProps['id'];
isFullWidth?: ConsoleSelectProps['isFullWidth'];
menuClassName?: ConsoleSelectProps['menuClassName'];
placeholder?: ConsoleSelectProps['autocompletePlaceholder'];
selectedKey: ConsoleSelectProps['selectedKey'];
title?: ConsoleSelectProps['title'];
titlePrefix?: ConsoleSelectProps['titlePrefix'];
userPreferencePrefix?: ConsoleSelectProps['userPreferencePrefix'];
allSelectorItem?: {
allSelectorKey?: string;
allSelectorTitle?: string;
};
noneSelectorItem?: {
noneSelectorKey?: string;
noneSelectorTitle?: string;
};
dataSelector: string[] | number[] | symbol[];
transformLabel?: Function;
loaded?: boolean;
loadError?: unknown;
resources?: ResourceDropdownResult[];
autoSelect?: boolean;
resourceFilter?: (resource: K8sResourceKind) => boolean;
onChange?: (
key: string,
name?: ResourceDropdownItems[keyof ResourceDropdownItems],
selectedResource?: K8sResourceKind,
) => void;
onLoad?: (items: ResourceDropdownItems) => void;
showBadge?: boolean;
customResourceKey?: (key: string, resource: K8sResourceKind) => string;
appendItems?: ResourceDropdownItems;
}
const craftResourceKey = (
resource: K8sResourceKind,
dataSelector: ResourceDropdownProps['dataSelector'],
resourceFilter: ResourceDropdownProps['resourceFilter'],
customResourceKey: ResourceDropdownProps['customResourceKey'],
): { customKey: string; key: string } => {
let key;
if (resourceFilter && resourceFilter(resource)) {
key = _.get(resource, dataSelector);
} else if (!resourceFilter) {
key = _.get(resource, dataSelector);
}
return {
customKey: customResourceKey ? customResourceKey(key, resource) : key,
key,
};
};
export const ResourceDropdown: FC<ResourceDropdownProps> = ({
actionItems,
allSelectorItem,
appendItems,
Script executed:
#!/bin/bash
# Search for data-test attributes in the dropdown/search components
grep -r "data-test" frontend/packages/console-shared/src/components/dropdown/ | head -20Repository: openshift/console
Repository: openshift/console
Exit code: 0
stdout:
frontend/packages/console-shared/src/components/dropdown/dropdown-with-switch/DropdownWithSwitchGroups.tsx: data-test={`dropdown-with-switch-menu-item-${iKey}`}
frontend/packages/console-shared/src/components/dropdown/dropdown-with-switch/DropdownWithSwitchMenu.tsx: data-test="dropdown-with-switch-switch"
frontend/packages/console-shared/src/components/dropdown/dropdown-with-switch/DropdownWithSwitchToggle.tsx: data-test="dropdown-with-switch-toggle"
Script executed:
#!/bin/bash
# Look at the PinnedResourcesConfiguration to understand resource selection
head -150 frontend/packages/dev-console/src/components/catalog/PinnedResourcesConfiguration.tsxRepository: openshift/console
Repository: openshift/console
Exit code: 0
stdout:
import type { FC, ReactElement } from 'react';
import { useState, useMemo, memo, useEffect } from 'react';
import { FormHelperText, FormSection, Icon, Tooltip } from '`@patternfly/react-core`';
import { DualListSelector } from '`@patternfly/react-core/deprecated`';
import * as fuzzy from 'fuzzysearch';
import type { Map as ImmutableMap } from 'immutable';
import { Set as ImmutableSet } from 'immutable';
import { useTranslation } from 'react-i18next';
import { connect } from 'react-redux';
import type {
DiscoveryResources,
ExtensionK8sModel,
K8sKind,
K8sModel,
} from '`@console/dynamic-plugin-sdk/src/lib-core`';
import { ResourceIcon } from '`@console/dynamic-plugin-sdk/src/lib-core`';
import type { K8sResourceKind } from '`@console/internal/module/k8s`';
import {
modelFor,
referenceForGroupVersionKind,
referenceForModel,
} from '`@console/internal/module/k8s`';
import type { RootState } from '`@console/internal/redux`';
import { LoadError } from '`@console/shared/src/components/cluster-configuration/LoadError`';
import { patchConsoleOperatorConfig } from '`@console/shared/src/components/cluster-configuration/patchConsoleOperatorConfig`';
import type { SaveStatusProps } from '`@console/shared/src/components/cluster-configuration/SaveStatus`';
import { SaveStatus } from '`@console/shared/src/components/cluster-configuration/SaveStatus`';
import { useConsoleOperatorConfig } from '`@console/shared/src/components/cluster-configuration/useConsoleOperatorConfig`';
import { YellowExclamationTriangleIcon } from '`@console/shared/src/components/status/icons`';
import { useDebounceCallback } from '`@console/shared/src/hooks/useDebounceCallback`';
import type {
Perspective,
PerspectivePinnedResource,
} from '`@console/shared/src/hooks/usePerspectives`';
import {
PerspectiveVisibilityState,
usePerspectives,
} from '`@console/shared/src/hooks/usePerspectives`';
import { useTelemetry } from '`@console/shared/src/hooks/useTelemetry`';
import './PinnedResourcesConfiguration.scss';
// skip duplicate resources.
const skipGroups = ImmutableSet([
// Prefer rbac.authorization.k8s.io/v1, which has the same resources.
'authorization.openshift.io',
]);
const skipResources = ImmutableSet([
// Prefer core/v1
'events.k8s.io/v1beta1.Event',
]);
type PerspectivesConsoleConfig = K8sResourceKind & {
spec: {
customization?: {
perspectives: Perspective[];
};
};
};
type DefaultPins = {
dev?: ExtensionK8sModel[];
};
type PinnedResourcesConfigurationProps = {
readonly: boolean;
groupVersionMap: DiscoveryResources['groupVersionMap'];
allK8sModels: ImmutableMap<string, K8sModel>;
};
const PinnedResourcesConfiguration: FC<PinnedResourcesConfigurationProps> = ({
readonly,
allK8sModels,
groupVersionMap,
}) => {
const { t } = useTranslation();
const fireTelemetryEvent = useTelemetry();
const perspectiveExtensions = usePerspectives();
const [pinnedResources, setPinnedResources] = useState<PerspectivePinnedResource[]>();
const [perspectiveData, setPerspectiveData] = useState<Perspective[]>();
const [pinnedResourcesConfigured, setPinnedResourcesConfigured] = useState<
PerspectivePinnedResource[]
>();
const defaultPins: DefaultPins = useMemo(
() =>
perspectiveExtensions.reduce(
(acc, e) => ({
...acc,
[e.properties.id]: (e.properties.defaultPins || []).map((gvk) => gvk),
}),
{},
),
[perspectiveExtensions],
);
const resources = useMemo(() => {
return allK8sModels
?.filter(({ apiGroup, apiVersion, kind, verbs }) => {
if (skipGroups.has(apiGroup) || skipResources.has(`${apiGroup}/${apiVersion}.${kind}`)) {
return false;
}
// Only show resources that can be listed.
if (!verbs?.some((v) => v === 'list')) {
return false;
}
// Only show preferred version for resources in the same API group.
const preferred = (m: K8sKind) =>
groupVersionMap?.[m.apiGroup]?.preferredVersion === m.apiVersion;
const sameGroupKind = (m: K8sKind) =>
m.kind === kind && m.apiGroup === apiGroup && m.apiVersion !== apiVersion;
return !allK8sModels.find((m) => sameGroupKind(m) && preferred(m));
})
.toOrderedMap()
.sortBy(({ kind, apiGroup }) => `${kind} ${apiGroup}`);
}, [allK8sModels, groupVersionMap]);
// Track duplicate names so we know when to show the group.
const kinds = resources.groupBy((m) => m.kind);
const isDup = (kind) => kinds.get(kind).size > 1;
type ItemProps = { title?: string; model?: K8sKind };
const Item = memo<ItemProps>(({ model }) => (
<div style={{ display: 'flex', alignItems: 'center' }}>
<span className="co-resource-item">
<span className="co-resource-icon--fixed-width">
<ResourceIcon kind={referenceForModel(model)} />
</span>
<span className="co-resource-item__resource-name">
<span>
{model.labelKey ? t(model.labelKey) : model.kind}
{model.badge && model.badge === 'Tech Preview' && (
<span className="co-resource-item__tech-dev-preview">{t('public~Tech Preview')}</span>
)}
</span>
{isDup(model.kind) && (
<div className="pf-v6-u-font-size-xs pf-v6-u-text-color-subtle co-truncate co-nowrap">
{model.apiGroup || 'core'}/{model.apiVersion}
</div>
)}
</span>
</span>
</div>
));
const InvalidItem = memo<ItemProps>(({ title }) => (
Add data-test attributes to resource items and assert the expected resource exists rather than falling back
The fallback to .first() is non-deterministic—if the expected resource isn't found, the test selects a random item and masks the real issue. Per E2E test guidelines, resource items should be identifiable via data-test attributes; consider adding them to the resource item components (e.g., data-test={resource-item-${groupName}}) so the test can assert the expected item exists rather than silently accepting the first result.
Code snippet
const consoleItem = page.locator('[class="co-resource-item"]').filter({
hasText: 'operator.openshift.io',
});
if ((await consoleItem.count()) > 0) {
await consoleItem.click();
} else {
await page.locator('[class="co-resource-item"]').first().click();
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/e2e/tests/dev-console/configure-perspectives.spec.ts` around lines
31 - 42, The test falls back to selecting the first .co-resource-item when the
expected consoleItem (variable consoleItem) isn't found, masking failures;
update the UI component that renders resource items to include a deterministic
data-test attribute (e.g., data-test={`resource-item-${groupName}`} on the
resource item element) and change the test (using the existing combobox,
filterInput and consoleItem locators) to select by that data-test selector and
assert its count is > 0, failing the test if the specific
resource-item-{groupName} is not present instead of using .first().
| test.afterAll(async () => { | ||
| await k8s.deleteNamespace(namespace); | ||
| }); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check the file structure and the specific lines mentioned
cat -n "frontend/e2e/tests/dev-console/customization-of-catalog-add-page-form-view.spec.ts" | head -50Repository: openshift/console
Length of output: 2381
🏁 Script executed:
#!/bin/bash
# Also check what imports are used and how k8s is obtained
head -30 "frontend/e2e/tests/dev-console/customization-of-catalog-add-page-form-view.spec.ts"Repository: openshift/console
Length of output: 1212
🏁 Script executed:
#!/bin/bash
# Look at the fixture definitions to understand k8s initialization
find frontend/e2e -name "fixtures*" -type f | head -5Repository: openshift/console
Length of output: 43
🏁 Script executed:
#!/bin/bash
# Find the fixtures file
find frontend/e2e -name "fixtures.ts" -o -name "fixtures.tsx" -o -name "fixtures/index.ts"Repository: openshift/console
Length of output: 43
🏁 Script executed:
#!/bin/bash
# Search for other afterAll patterns in e2e tests
grep -r "test.afterAll" frontend/e2e/tests --include="*.spec.ts" -A 2 | head -40Repository: openshift/console
Length of output: 2842
🏁 Script executed:
#!/bin/bash
# Check if there are any try-catch patterns in afterAll blocks
grep -r "test.afterAll" frontend/e2e/tests --include="*.spec.ts" -A 5 | grep -A 5 "try\|catch\|if"Repository: openshift/console
Length of output: 992
🏁 Script executed:
#!/bin/bash
# Look at the oauth.spec.ts to understand the guarding pattern
grep -B 10 -A 10 "if (!originalOAuthConfig || !client)" frontend/e2e/tests/console/cluster-settings/oauth.spec.tsRepository: openshift/console
Length of output: 617
🏁 Script executed:
#!/bin/bash
# Check the fixtures file to understand k8s initialization
find frontend/e2e -name "fixtures*" -type fRepository: openshift/console
Length of output: 43
🏁 Script executed:
#!/bin/bash
# Search for fixtures index
ls -la frontend/e2e/Repository: openshift/console
Length of output: 770
🏁 Script executed:
#!/bin/bash
# Check the fixtures directory
ls -la frontend/e2e/fixtures/Repository: openshift/console
Length of output: 299
🏁 Script executed:
#!/bin/bash
# Look at the fixtures file content
cat frontend/e2e/fixtures/index.tsRepository: openshift/console
Length of output: 2410
🏁 Script executed:
#!/bin/bash
# Look at the oauth.spec.ts full context around initialization
grep -B 20 "test.beforeAll" frontend/e2e/tests/console/cluster-settings/oauth.spec.ts | head -40Repository: openshift/console
Length of output: 516
🏁 Script executed:
#!/bin/bash
# Check if this test pattern is documented or if there are any error handling guides
grep -r "beforeAll" frontend/e2e/tests --include="*.spec.ts" -A 10 | grep -B 3 -A 7 "try\|catch" | head -50Repository: openshift/console
Length of output: 43
🏁 Script executed:
#!/bin/bash
# Check the oauth.spec.ts beforeAll to see how it initializes
grep -A 15 "test.beforeAll" frontend/e2e/tests/console/cluster-settings/oauth.spec.tsRepository: openshift/console
Length of output: 547
Guard teardown against initialization failures
The afterAll unconditionally calls k8s.deleteNamespace(namespace). If new KubernetesClient() or createNamespace() fails in beforeAll, the variable remains undefined and teardown throws a secondary error, masking the original failure. The codebase already establishes this pattern (see oauth.spec.ts's if (!originalOAuthConfig || !client)).
Suggested fix
test.afterAll(async () => {
- await k8s.deleteNamespace(namespace);
+ if (k8s) {
+ await k8s.deleteNamespace(namespace);
+ }
});🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@frontend/e2e/tests/dev-console/customization-of-catalog-add-page-form-view.spec.ts`
around lines 22 - 24, The afterAll teardown unconditionally calls
k8s.deleteNamespace(namespace) which can throw if namespace or k8s were never
initialized; guard the call by checking that both the KubernetesClient instance
(k8s) and the namespace variable are defined before calling deleteNamespace.
Locate the test.afterAll block and wrap or conditionally execute
k8s.deleteNamespace(namespace) only when namespace and k8s are truthy (mirror
the pattern used in oauth.spec.ts that checks if (!originalOAuthConfig ||
!client)). Ensure the check covers failures from new KubernetesClient() and
createNamespace() in beforeAll.
| test('when all the sub-catalogs are disabled', async ({ page }) => { | ||
| const perspectivePage = new PerspectivePage(page); | ||
| const customizationPage = new CustomizationPage(page); | ||
|
|
||
| await test.step('Navigate to console customization', async () => { | ||
| await page.goto('/'); | ||
| await perspectivePage.switchToDeveloper(); | ||
| await perspectivePage.selectOrCreateProject(namespace); | ||
| await customizationPage.navigateToConsoles(); | ||
| await customizationPage.clickCluster(); | ||
| await customizationPage.openCustomization(); | ||
| }); | ||
|
|
||
| await test.step('Disable all software catalog items', async () => { | ||
| await customizationPage.clickDeveloperTab(); | ||
| await customizationPage.disableAllSoftwareCatalogItems(); | ||
| }); | ||
|
|
||
| await test.step('Verify save message', async () => { | ||
| await customizationPage.expectSaveMessage(); | ||
| }); | ||
|
|
||
| await test.step('Verify software catalog not visible', async () => { | ||
| await perspectivePage.switchToAdministrator(); | ||
| await perspectivePage.switchToDeveloper(); | ||
| await perspectivePage.selectOrCreateProject(namespace); | ||
| await perspectivePage.navigateToAdd(); | ||
| await page.reload(); | ||
| await page.waitForLoadState('domcontentloaded'); | ||
| await expect(page.getByTestId('card developer-catalog')).toBeHidden(); | ||
| }); | ||
| }); | ||
|
|
||
| test('when specific sub-catalog is disabled', async ({ page }) => { | ||
| const perspectivePage = new PerspectivePage(page); | ||
| const customizationPage = new CustomizationPage(page); | ||
|
|
||
| await test.step('Navigate to customization', async () => { | ||
| await page.goto('/'); | ||
| await perspectivePage.switchToDeveloper(); | ||
| await perspectivePage.selectOrCreateProject(namespace); | ||
| await customizationPage.navigateToConsoles(); | ||
| await customizationPage.clickCluster(); | ||
| await customizationPage.openCustomization(); | ||
| }); | ||
|
|
||
| await test.step('Disable Helm Charts in software catalog', async () => { | ||
| await customizationPage.clickDeveloperTab(); | ||
| await customizationPage.disableSoftwareCatalogItem('Helm Charts'); | ||
| }); | ||
|
|
||
| await test.step('Verify save message', async () => { | ||
| await customizationPage.expectSaveMessage(); | ||
| }); | ||
|
|
||
| await test.step('Verify Helm Charts not visible but catalog exists', async () => { | ||
| await perspectivePage.switchToAdministrator(); | ||
| await perspectivePage.switchToDeveloper(); | ||
| await perspectivePage.selectOrCreateProject(namespace); | ||
| await perspectivePage.navigateToAdd(); | ||
| await page.reload(); | ||
| await page.waitForLoadState('domcontentloaded'); | ||
| await expect(page.getByTestId('card developer-catalog')).toBeVisible({ timeout: 60_000 }); | ||
| await expect(page.getByTestId('item helm')).toBeHidden(); | ||
| }); | ||
| }); | ||
|
|
||
| test('when specific sub-catalog is enabled', async ({ page }) => { | ||
| const perspectivePage = new PerspectivePage(page); | ||
| const customizationPage = new CustomizationPage(page); | ||
|
|
||
| await test.step('Navigate to customization', async () => { | ||
| await page.goto('/'); | ||
| await perspectivePage.switchToDeveloper(); | ||
| await perspectivePage.selectOrCreateProject(namespace); | ||
| await customizationPage.navigateToConsoles(); | ||
| await customizationPage.clickCluster(); | ||
| await customizationPage.openCustomization(); | ||
| }); | ||
|
|
||
| await test.step('Enable only Helm Charts', async () => { | ||
| await customizationPage.clickDeveloperTab(); | ||
| await customizationPage.enableOnlySoftwareCatalogItem('Helm Charts'); | ||
| }); | ||
|
|
||
| await test.step('Verify save message', async () => { | ||
| await customizationPage.expectSaveMessage(); | ||
| }); | ||
|
|
||
| await test.step('Verify only Helm Charts visible', async () => { | ||
| await perspectivePage.switchToAdministrator(); | ||
| await perspectivePage.switchToDeveloper(); | ||
| await perspectivePage.selectOrCreateProject(namespace); | ||
| await perspectivePage.navigateToAdd(); | ||
| await page.reload(); | ||
| await page.waitForLoadState('domcontentloaded'); | ||
| await expect(page.getByTestId('card developer-catalog')).toBeVisible({ timeout: 60_000 }); | ||
| await expect(page.getByTestId('item helm')).toBeVisible(); | ||
| await expect(page.getByTestId('item operator-backed')).toBeHidden(); | ||
| }); | ||
| }); | ||
|
|
||
| test('when all the add page items are disabled', async ({ page }) => { | ||
| const perspectivePage = new PerspectivePage(page); | ||
| const customizationPage = new CustomizationPage(page); | ||
|
|
||
| await test.step('Navigate to customization', async () => { | ||
| await page.goto('/'); | ||
| await perspectivePage.switchToDeveloper(); | ||
| await perspectivePage.selectOrCreateProject(namespace); | ||
| await customizationPage.navigateToConsoles(); | ||
| await customizationPage.clickCluster(); | ||
| await customizationPage.openCustomization(); | ||
| }); | ||
|
|
||
| await test.step('Disable all add page items', async () => { | ||
| await customizationPage.clickDeveloperTab(); | ||
| await customizationPage.disableAllAddPageItems(); | ||
| }); | ||
|
|
||
| await test.step('Verify save message', async () => { | ||
| await customizationPage.expectSaveMessage(); | ||
| }); | ||
|
|
||
| await test.step('Verify add page only shows Getting Started', async () => { | ||
| await perspectivePage.navigateToAdd(); | ||
| await page.reload(); | ||
| await page.waitForLoadState('domcontentloaded'); | ||
| await expect(page.getByTestId('getting-started')).toBeVisible({ timeout: 60_000 }); | ||
| const cards = page.getByTestId('add-page').locator('[data-test^=card]'); | ||
| await expect(cards).toHaveCount(2); | ||
| }); | ||
| }); | ||
|
|
||
| test('when specific add page item is disabled', async ({ page }) => { | ||
| const perspectivePage = new PerspectivePage(page); | ||
| const customizationPage = new CustomizationPage(page); | ||
|
|
||
| await test.step('Navigate to customization', async () => { | ||
| await page.goto('/'); | ||
| await perspectivePage.switchToDeveloper(); | ||
| await perspectivePage.selectOrCreateProject(namespace); | ||
| await customizationPage.navigateToConsoles(); | ||
| await customizationPage.clickCluster(); | ||
| await customizationPage.openCustomization(); | ||
| }); | ||
|
|
||
| await test.step('Disable Import from Git', async () => { | ||
| await customizationPage.clickDeveloperTab(); | ||
| await customizationPage.disableAddPageItem('Import from Git'); | ||
| }); | ||
|
|
||
| await test.step('Verify save message', async () => { | ||
| await customizationPage.expectSaveMessage(); | ||
| }); | ||
|
|
||
| await test.step('Verify Import from Git not visible', async () => { | ||
| await perspectivePage.navigateToAdd(); | ||
| await page.reload(); | ||
| await page.waitForLoadState('domcontentloaded'); | ||
| await expect(page.getByTestId('card git-repository')).toBeHidden(); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify Playwright parallelism settings and whether dev-console customization specs are serialized.
fd -a 'playwright.config.ts' frontend/e2e --exec sed -n '1,240p' {}
# Inspect relevant specs for serial mode and cluster-customization mutations.
rg -n --type=ts -C2 'test\.describe(\.serial)?|fullyParallel|workers|openCustomization|disableAllSoftwareCatalogItems|enableOnlySoftwareCatalogItem|disableAllAddPageItems|disableAddPageItem' frontend/e2e/tests/dev-consoleRepository: openshift/console
Length of output: 16697
🏁 Script executed:
#!/bin/bash
# Find playwright config file
fd -type f -name 'playwright.config.*'
# Get the exact test.describe declaration and any afterAll/beforeAll hooks from the target file
sed -n '1,30p' frontend/e2e/tests/dev-console/customization-of-catalog-add-page-form-view.spec.ts
# Look for afterAll hook and cleanup logic
tail -20 frontend/e2e/tests/dev-console/customization-of-catalog-add-page-form-view.spec.tsRepository: openshift/console
Length of output: 2042
🏁 Script executed:
#!/bin/bash
# Find playwright config
find . -name 'playwright.config.*' -type f
# Check full file for .serial modifier and any restoration logic
grep -n -E '(\.serial|restore|revert|beforeEach|afterEach|cleanup.*custom)' frontend/e2e/tests/dev-console/customization-of-catalog-add-page-form-view.spec.ts
# Read the entire target file to see structure
wc -l frontend/e2e/tests/dev-console/customization-of-catalog-add-page-form-view.spec.tsRepository: openshift/console
Length of output: 180
🏁 Script executed:
#!/bin/bash
# Read playwright config for parallelism settings
cat ./frontend/playwright.config.ts
# Read full target file to see complete structure
cat -n frontend/e2e/tests/dev-console/customization-of-catalog-add-page-form-view.spec.tsRepository: openshift/console
Length of output: 13101
Add afterEach cleanup to restore cluster customization state between tests
Tests in this suite mutate cluster-wide customization (catalog/add-page visibility) without resetting state between tests, creating order dependencies that violate the E2E guideline requiring each test to create and clean up its own resources. While CI serialization (workers: 1) prevents cross-suite interference during CI runs, local development environments run tests in parallel, exposing potential flakiness. Add an afterEach hook to restore baseline customization after each test, or consider marking the suite with .serial for local environments.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@frontend/e2e/tests/dev-console/customization-of-catalog-add-page-form-view.spec.ts`
around lines 26 - 188, Add an afterEach cleanup that restores cluster
customization to a known baseline after each test (or mark the suite .serial);
implement it by instantiating CustomizationPage (and PerspectivePage if needed),
navigating to consoles/cluster/customization (use methods navigateToConsoles,
clickCluster, openCustomization), switching to the Developer tab
(clickDeveloperTab), and calling the appropriate reset method (e.g.,
enableAll/enableOnlySoftwareCatalogItem, disableAllAddPageItems reversal or a
new restoreDefaultCustomization helper) then saving and waiting for
expectSaveMessage to succeed so each test leaves the cluster in a consistent
state.
| await k8s | ||
| .waitForCustomResourceCondition( | ||
| 'apps', | ||
| 'v1', | ||
| namespace, | ||
| 'deployments', | ||
| 'nodejs-ex-git', | ||
| (resource: any) => resource?.status?.availableReplicas > 0, | ||
| 120_000, | ||
| ) | ||
| .catch(() => { | ||
| // Deployment may not be fully ready, but we need pods to exist | ||
| }); |
There was a problem hiding this comment.
Fail fast on deployment readiness failures in setup.
Suppressing the readiness error here hides real cluster/setup failures and turns them into later flaky UI failures. Let this fail with context (or assert pod existence explicitly) before executing test steps.
Suggested fix
- await k8s
- .waitForCustomResourceCondition(
- 'apps',
- 'v1',
- namespace,
- 'deployments',
- 'nodejs-ex-git',
- (resource: any) => resource?.status?.availableReplicas > 0,
- 120_000,
- )
- .catch(() => {
- // Deployment may not be fully ready, but we need pods to exist
- });
+ await k8s.waitForCustomResourceCondition(
+ 'apps',
+ 'v1',
+ namespace,
+ 'deployments',
+ 'nodejs-ex-git',
+ (resource: any) => resource?.status?.availableReplicas > 0,
+ 120_000,
+ );📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| await k8s | |
| .waitForCustomResourceCondition( | |
| 'apps', | |
| 'v1', | |
| namespace, | |
| 'deployments', | |
| 'nodejs-ex-git', | |
| (resource: any) => resource?.status?.availableReplicas > 0, | |
| 120_000, | |
| ) | |
| .catch(() => { | |
| // Deployment may not be fully ready, but we need pods to exist | |
| }); | |
| await k8s.waitForCustomResourceCondition( | |
| 'apps', | |
| 'v1', | |
| namespace, | |
| 'deployments', | |
| 'nodejs-ex-git', | |
| (resource: any) => resource?.status?.availableReplicas > 0, | |
| 120_000, | |
| ); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/e2e/tests/dev-console/pod-list-page.spec.ts` around lines 44 - 56,
The current setup swallows failures from k8s.waitForCustomResourceCondition
(called with 'apps','v1', namespace, 'deployments','nodejs-ex-git') which masks
real cluster issues; remove the .catch suppression so the test fails fast and
surfaces the original error, or replace the suppressed branch with an explicit
assertion that pods exist (e.g., call a pod-listing/wait helper for the
deployment and assert length>0) so failures are reported with context before
proceeding to UI steps.
| await test.step('Open Create Project modal', async () => { | ||
| await perspectivePage.selectOrCreateProject(projectName); | ||
| cleanup.trackNamespace(projectName); | ||
| }); |
There was a problem hiding this comment.
Register cleanup before triggering project creation.
On Line 18, cleanup is tracked only after selectOrCreateProject returns. If creation succeeds but that helper throws later, the namespace can be orphaned.
Proposed fix
await test.step('Open Create Project modal', async () => {
- await perspectivePage.selectOrCreateProject(projectName);
cleanup.trackNamespace(projectName);
+ await perspectivePage.selectOrCreateProject(projectName);
});As per coding guidelines: "Each Playwright E2E test block must create its own resources, assert independently, and clean up via the cleanup fixture".
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| await test.step('Open Create Project modal', async () => { | |
| await perspectivePage.selectOrCreateProject(projectName); | |
| cleanup.trackNamespace(projectName); | |
| }); | |
| await test.step('Open Create Project modal', async () => { | |
| cleanup.trackNamespace(projectName); | |
| await perspectivePage.selectOrCreateProject(projectName); | |
| }); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/e2e/tests/dev-console/project-creation.spec.ts` around lines 16 -
19, Move the cleanup registration to happen before triggering project creation
so the namespace is always tracked even if creation throws; specifically, call
cleanup.trackNamespace(projectName) before awaiting
perspectivePage.selectOrCreateProject(projectName) in the test step that opens
the Create Project modal (references: perspectivePage.selectOrCreateProject and
cleanup.trackNamespace). Ensure the order is changed in that block so
cleanup.trackNamespace runs first but keep the same projectName variable and
existing assertions intact.
| test('setting graph preference for topology', { tag: ['@regression'] }, async ({ page }) => { | ||
| const perspectivePage = new PerspectivePage(page); | ||
| const userPreferencesPage = new UserPreferencesPage(page); | ||
|
|
||
| await test.step('Create deployment workload', async () => { | ||
| await k8s.appsV1Api.createNamespacedDeployment({ | ||
| namespace, | ||
| body: { | ||
| apiVersion: 'apps/v1', | ||
| kind: 'Deployment', | ||
| metadata: { name: 'node1', namespace }, | ||
| spec: { | ||
| replicas: 1, | ||
| selector: { matchLabels: { app: 'node1' } }, | ||
| template: { | ||
| metadata: { labels: { app: 'node1' } }, | ||
| spec: { | ||
| containers: [ | ||
| { | ||
| name: 'node1', | ||
| image: 'registry.access.redhat.com/ubi8/nodejs-18:latest', | ||
| ports: [{ containerPort: 8080, protocol: 'TCP' }], | ||
| }, | ||
| ], | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| }); | ||
| }); | ||
|
|
||
| await test.step('Set topology preference to Graph', async () => { | ||
| await page.goto('/'); | ||
| await perspectivePage.switchToAdministrator(); | ||
| await userPreferencesPage.openUserPreferences(); | ||
| await userPreferencesPage.changePreferenceDropdown('Topology', 'Graph'); | ||
| }); | ||
|
|
||
| await test.step('Reload and navigate to topology', async () => { | ||
| await userPreferencesPage.reloadConsole(); | ||
| await perspectivePage.switchToDeveloper(); | ||
| await perspectivePage.selectOrCreateProject(namespace); | ||
| await perspectivePage.navigateToTopology(); | ||
| }); | ||
|
|
||
| await test.step('Verify graph view', async () => { | ||
| await userPreferencesPage.expectTopologyGraphView(); | ||
| }); | ||
| }); | ||
|
|
||
| test('setting list preference for topology', { tag: ['@regression'] }, async () => { | ||
| test.skip(true, 'Marked @broken-test in Cypress — bugzilla 2014313'); | ||
| }); | ||
|
|
||
| test( | ||
| 'setting form preference for create/edit resource method', | ||
| { tag: ['@regression'] }, | ||
| async () => { | ||
| test.skip(true, 'Marked @broken-test in Cypress'); | ||
| }, | ||
| ); | ||
|
|
||
| test( | ||
| 'setting YAML preference for create/edit resource method', | ||
| { tag: ['@regression'] }, | ||
| async ({ page }) => { | ||
| const perspectivePage = new PerspectivePage(page); | ||
| const userPreferencesPage = new UserPreferencesPage(page); | ||
|
|
||
| await test.step('Set create/edit preference to YAML', async () => { | ||
| await page.goto('/'); | ||
| await perspectivePage.switchToAdministrator(); | ||
| await userPreferencesPage.openUserPreferences(); | ||
| await userPreferencesPage.changePreferenceDropdown('Create/Edit resource method', 'YAML'); | ||
| }); | ||
|
|
||
| await test.step('Navigate to Helm charts and install', async () => { | ||
| await perspectivePage.switchToDeveloper(); | ||
| await perspectivePage.selectOrCreateProject(namespace); | ||
| await perspectivePage.navigateToAdd(); | ||
| const helmCard = page.getByTestId('card helm'); | ||
| await helmCard.click(); | ||
| const searchInput = page.locator('[data-test="search-catalog"]'); | ||
| await searchInput.fill('Nodejs'); | ||
| const helmChartCard = page | ||
| .locator('[data-test^="HelmChart"]') | ||
| .filter({ hasText: 'Nodejs' }) | ||
| .first(); | ||
| await helmChartCard.click(); | ||
| const installButton = page.locator('[data-test="install-helm"]'); | ||
| await installButton.click(); | ||
| }); | ||
|
|
||
| await test.step('Verify YAML view selected', async () => { | ||
| await userPreferencesPage.expectYamlViewSelected(); | ||
| }); | ||
| }, | ||
| ); | ||
|
|
||
| test('setting a preference for language', { tag: ['@regression'] }, async () => { | ||
| test.skip(true, 'Marked @broken-test in Cypress'); | ||
| }); | ||
|
|
||
| test('setting routing options preference for import form', { tag: ['@regression'] }, async () => { | ||
| test.skip(true, 'Marked @broken-test in Cypress — ODC-6303'); | ||
| }); | ||
|
|
||
| test('setting theme preference for console', { tag: ['@regression'] }, async () => { | ||
| test.skip(true, 'Manual test — ODC-5990'); | ||
| }); | ||
|
|
||
| test( | ||
| 'setting resource type preference for console', | ||
| { tag: ['@regression'] }, | ||
| async ({ page }) => { | ||
| const perspectivePage = new PerspectivePage(page); | ||
| const userPreferencesPage = new UserPreferencesPage(page); | ||
|
|
||
| await test.step('Set resource type to DeploymentConfig', async () => { | ||
| await page.goto('/'); | ||
| await perspectivePage.switchToAdministrator(); | ||
| await userPreferencesPage.openUserPreferences(); | ||
| await userPreferencesPage.clickTab('Applications'); | ||
| await userPreferencesPage.changePreferenceDropdown('Resource Type', 'DeploymentConfig'); | ||
| }); | ||
|
|
||
| await test.step('Navigate to Container images', async () => { | ||
| await perspectivePage.switchToDeveloper(); | ||
| await perspectivePage.selectOrCreateProject(namespace); | ||
| await perspectivePage.navigateToAdd(); | ||
| const containerImageCard = page.getByTestId('card container-image'); | ||
| await containerImageCard.click(); | ||
| }); | ||
|
|
||
| await test.step('Verify DeploymentConfig is selected', async () => { | ||
| const resourceDropdown = page.locator('[data-test-id="dropdown-button"]').filter({ | ||
| hasText: 'DeploymentConfig', | ||
| }); | ||
| await expect(resourceDropdown.first()).toBeVisible({ timeout: 30_000 }); | ||
| }); | ||
| }, | ||
| ); |
There was a problem hiding this comment.
Reset persisted user-preference mutations to avoid cross-test leakage.
These tests change durable preferences (Topology, Create/Edit resource method, Resource Type) and don’t restore them. That can contaminate later tests and produce order-dependent failures.
Use try/finally in each mutating test (or a shared afterEach) to restore previous values.
As per coding guidelines: “Each Playwright E2E test block must create its own resources, assert independently, and clean up via the cleanup fixture”.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/e2e/tests/dev-console/user-preferences-dev-perspective.spec.ts`
around lines 80 - 221, Tests mutate durable user preferences (Topology,
Create/Edit resource method, Resource Type) without restoring them; capture the
current preference before calling UserPreferencesPage.changePreferenceDropdown
and restore it in a finally block (or implement a shared afterEach cleanup) so
mutations don't leak across tests: for each test that calls
UserPreferencesPage.changePreferenceDropdown (e.g., tests using PerspectivePage
and UserPreferencesPage, methods
openUserPreferences/changePreferenceDropdown/reloadConsole/expectTopologyGraphView/expectYamlViewSelected/clickTab),
read and store the prior value (via an existing getter on UserPreferencesPage or
by reading the dropdown state), then in finally call changePreferenceDropdown to
revert to the saved value (or call a new UserPreferencesPage.resetPreference
helper), ensuring cleanup occurs even on failures.
|
@Cragsmann: The following test failed, say
Full PR test history. Your PR dashboard. DetailsInstructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here. |
Analysis / Root cause:
Migrating dev-console Cypress E2E tests to Playwright as part of the broader Cypress→Playwright migration effort (CONSOLE-5299). The existing Gherkin
.featurefiles use a 4-layer indirection (feature → step-def → page-action → page-object) that is collapsed into idiomatic Playwright 2-file structure (spec + page object).Solution description:
Migrate 18 Gherkin feature files from
packages/dev-console/integration-tests/to Playwright undere2e/tests/dev-console/, covering:Files added:
e2e/pages/dev-console/e2e/tests/dev-console/Key patterns:
test.skip(true, 'reason')with annotationsBasePagewithrobustClick(),waitForLoadingComplete(),goTo()KubernetesClientreplacescy.exec('oc ...')for resource managementScreenshots / screen recording:
N/A — test infrastructure only, no UI changes
Test setup:
e2e/.envwith cluster credentialsnpx playwright test --project=dev-consoleTest cases:
npx tsc --noEmit -p e2e/tsconfig.json)yarn eslint e2e/tests/dev-console/ e2e/pages/dev-console/)Browser conformance:
Additional info:
This is batch 1 of the dev-console migration. ~42 feature files remain for subsequent batches.
🤖 Generated with Claude Code
Summary by CodeRabbit