[CONSOLE-5237] Migrate OLM Cypress tests to Playwright - #16899
Conversation
|
Pipeline controller notification For optional jobs, comment This repository is configured in: LGTM mode |
|
Skipping CI for Draft Pull Request. |
|
PR needs rebase. 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. |
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: trgeiger 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 |
WalkthroughAdds Kubernetes custom-resource helpers, reusable OLM cleanup workflows, Playwright page objects, broad OLM end-to-end coverage, console test stabilization, and dedicated test selectors. ChangesOLM E2E coverage
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant PlaywrightTest
participant OperatorInstallPage
participant InstalledOperatorsPage
participant OperatorDetailsPage
participant KubernetesClient
PlaywrightTest->>OperatorInstallPage: install operator
OperatorInstallPage->>InstalledOperatorsPage: verify installation
InstalledOperatorsPage-->>PlaywrightTest: return status
PlaywrightTest->>OperatorDetailsPage: create or delete operand
OperatorDetailsPage->>KubernetesClient: verify custom-resource state
PlaywrightTest->>OperatorDetailsPage: uninstall operator
OperatorDetailsPage-->>PlaywrightTest: return uninstall result
Possibly related PRs
Suggested labels: Important Pre-merge checks failedPlease resolve all errors before merging. Addressing warnings is optional. ❌ Failed checks (1 error, 3 warnings)
✅ Passed checks (11 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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: 20
🧹 Nitpick comments (28)
frontend/e2e/clients/kubernetes-client.ts (3)
661-685: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winLog the swallowed errors.
Both methods return
[]for every failure, including RBAC denials and network errors. The callers infrontend/e2e/test-utils/operator-cleanup.tsandfrontend/e2e/test-utils/olm-test-cleanup.tsthen treat the empty result as "nothing to clean up", so cleanup silently becomes a no-op. The callers already wrap these calls intry/catch, so logging the error keeps the current control flow and makes the failure visible in the test output.♻️ Proposed refactor
return (response as any)?.items || []; - } catch { + } catch (err) { + console.log(`listClusterCustomResources(${group}/${version}/${plural}) failed: ${err}`); return []; } } async listNamespaces(): Promise<unknown[]> { try { const response = await this.k8sApi.listNamespace(); return (response?.items || []); - } catch { + } catch (err) { + console.log(`listNamespaces failed: ${err}`); return []; } }🤖 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/clients/kubernetes-client.ts` around lines 661 - 685, Update listClusterCustomResources and listNamespaces to log the caught error before returning the existing empty-array fallback. Preserve the current return behavior and use the client’s established logging mechanism so RBAC, network, and other failures are visible to test callers.
282-288: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winReplace the placeholder fallback value.
getCurrentUser()returns{ name: 'idk' }when kubeconfig retrieval fails. A caller cannot distinguish this fake user from a real one. Returnundefinedinstead, and use the typedk8s.Userreturn type for consistency withgetCurrentUserToken().♻️ Proposed refactor
- getCurrentUser(): any { + getCurrentUser(): k8s.User { try { return this.kubeConfig.getCurrentUser(); } catch { - return { name: 'idk' }; + return undefined; } }🤖 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/clients/kubernetes-client.ts` around lines 282 - 288, Update getCurrentUser() to return undefined when kubeConfig.getCurrentUser() throws instead of the placeholder user object, and change its return type from any to the typed k8s.User-compatible optional return type used consistently with getCurrentUserToken().Source: Learnings
571-586: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueUse
k8s.PatchStrategy.JsonPatchinpatchClusterCustomResource.
patchCustomResourcealready uses the client enum for the JSON Patch content type. Use the same value here to avoid duplicating the literal'application/json-patch+json'.♻️ Proposed refactor
body: patch, - contentType: 'application/json-patch+json', + contentType: k8s.PatchStrategy.JsonPatch, } as any);🤖 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/clients/kubernetes-client.ts` around lines 571 - 586, Update patchClusterCustomResource to use k8s.PatchStrategy.JsonPatch for the contentType value, matching the existing patchCustomResource implementation and removing the duplicated literal.Source: Linters/SAST tools
frontend/e2e/test-utils/cluster-cleanup.ts (2)
154-185: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winMake the operand types configurable and log the swallowed error.
Two points in this block:
- The operand types are hardcoded to
infinispan.org. The function accepts a configurabletargetOperator, so the operand types should also come fromClusterCleanupOptions. Otherwise this "comprehensive" utility only cleans one operator's operands.- The
catchon Lines 182-184 discards every error. A missing CRD and an RBAC denial produce the same silent result. Log the error so a failed cleanup is visible in the test output.♻️ Proposed refactor
export interface ClusterCleanupOptions { dryRun?: boolean; targetOperator?: string; olderThanMinutes?: number; + operandTypes?: { group: string; version: string; plural: string }[]; }- const operandTypes = [ - { group: 'infinispan.org', version: 'v1', plural: 'infinispans' }, - { group: 'infinispan.org', version: 'v1', plural: 'backups' }, - ]; + const operandTypes = options.operandTypes ?? [ + { group: 'infinispan.org', version: 'v1', plural: 'infinispans' }, + { group: 'infinispan.org', version: 'v1', plural: 'backups' }, + ];- } catch (error) { - // Ignore - operand type may not exist - } + } catch (error) { + console.log( + ` Skipping ${operandType.plural} in ${namespaceName}: ${error.message}`, + ); + }🤖 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/test-utils/cluster-cleanup.ts` around lines 154 - 185, Update the operand cleanup flow around operandTypes to derive the operand group, version, and plurals from the configurable targetOperator in ClusterCleanupOptions instead of hardcoding infinispan.org. In the catch block surrounding listCustomResources and deleteCustomResource, log the caught error with enough context to identify the operand type and namespace while preserving the existing cleanup continuation behavior.
59-151: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract the repeated resource cleanup block into a helper.
The CSV, Subscription, and InstallPlan blocks are identical except for the plural name and the match predicate. A single helper removes about 70 duplicated lines and gives one place to fix the logging and error handling.
♻️ Proposed refactor
+ const cleanupResourceType = async ( + plural: string, + version: string, + matches: (item: any) => boolean, + ): Promise<void> => { + try { + const items = await k8sClient.listCustomResources( + 'operators.coreos.com', + version, + namespaceName, + plural, + ); + const targets = (items || []).filter(matches); + console.log(`Found ${targets.length} ${targetOperator} ${plural} in ${namespaceName}`); + for (const item of targets) { + console.log(` ${dryRun ? 'Would delete' : 'Deleting'} ${plural}: ${item.metadata.name}`); + if (!dryRun) { + await k8sClient.deleteCustomResource( + 'operators.coreos.com', + version, + namespaceName, + plural, + item.metadata.name, + ); + } + } + } catch (error) { + console.log(` Error checking ${plural} in ${namespaceName}: ${error.message}`); + } + };Then call it for each type:
await cleanupResourceType('clusterserviceversions', 'v1alpha1', (csv) => Boolean(csv.metadata.name?.includes(targetOperator)), ); await cleanupResourceType( 'subscriptions', 'v1alpha1', (sub) => Boolean(sub.metadata.name?.includes(targetOperator)) || Boolean(sub.spec?.name?.includes(targetOperator)), ); await cleanupResourceType('installplans', 'v1alpha1', (ip) => (ip.spec?.clusterServiceVersionNames || []).some((n: string) => n.includes(targetOperator)), );🤖 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/test-utils/cluster-cleanup.ts` around lines 59 - 151, Extract the repeated CSV, Subscription, and InstallPlan cleanup logic into a shared cleanupResourceType helper that accepts the resource plural, API version, and match predicate, while preserving dry-run behavior, logging, listing, deletion, and error handling. Replace the three inline try/catch blocks with calls to this helper using the existing resource-specific predicates and targetOperator matching.frontend/e2e/test-utils/operator-cleanup.ts (3)
98-100: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueMatch predicate differs from the other cleanup modules.
This module matches subscriptions with
===.frontend/e2e/test-utils/olm-test-cleanup.ts(Line 119) andfrontend/e2e/test-utils/olm-cleanup.ts(Line 40) match withincludesandstartsWith. The three modules therefore select different resource sets for the samepackageName.The exact match used here is the safest of the three. Align the other modules to it, or document why each module needs a different matching rule.
🤖 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/test-utils/operator-cleanup.ts` around lines 98 - 100, Align the subscription matching predicates in the cleanup functions of olm-test-cleanup.ts and olm-cleanup.ts with the exact-match behavior used by operator-cleanup.ts, replacing broader includes/startsWith checks where appropriate. If either module must retain broader matching, document the specific reason and intended resource-selection difference.
13-13: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winType the
k8sClientparameter asKubernetesClientin all four cleanup modules. Every new cleanup function acceptsk8sClient: any. The shared root cause is that no module imports the concrete client type, so the compiler cannot check any method name, argument order, or return type. This matters here because the modules call positional helpers such asdeleteCustomResource(group, version, namespace, plural, name); a swappednamespaceandpluralargument compiles today and silently deletes nothing at runtime.KubernetesClientis a default export fromfrontend/e2e/clients/kubernetes-client.tsand already declares every method these modules use.
frontend/e2e/test-utils/operator-cleanup.ts#L13-L13: addimport type KubernetesClient from '../clients/kubernetes-client';and change bothcleanupAllOperatorsByPackageNameandcleanupOperatorResourcesto acceptk8sClient: KubernetesClient.frontend/e2e/test-utils/cluster-cleanup.ts#L15-L15: import the same type and changecleanupClusterTestResourcesto acceptk8sClient: KubernetesClient.frontend/e2e/test-utils/olm-cleanup.ts#L17-L20: import the same type and change bothcleanupOLMOperatorCompletelyandcleanupOperatorWithOLMResources(Line 96) to acceptk8sClient: KubernetesClient.frontend/e2e/test-utils/olm-test-cleanup.ts#L15-L15: import the same type and changeperformOperatorCleanup,performAggressiveOperatorCleanup(Line 47),cleanupTestNamespaces(Line 75), andverifyAndForceCleanup(Line 115) to acceptk8sClient: KubernetesClient. Also type the hook fixtures at Lines 149-171 instead ofany.The list methods return
unknown[], so eachfiltercallback keeps its(item: any)annotation or gains a narrow local interface.🤖 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/test-utils/operator-cleanup.ts` at line 13, Replace the any-typed Kubernetes client parameters with the default-imported KubernetesClient type across frontend/e2e/test-utils/operator-cleanup.ts#L13-L13, frontend/e2e/test-utils/cluster-cleanup.ts#L15-L15, frontend/e2e/test-utils/olm-cleanup.ts#L17-L20, and frontend/e2e/test-utils/olm-test-cleanup.ts#L15-L15: update all named cleanup functions in those files, and type the olm-test-cleanup hook fixtures at Lines 149-171 instead of any. Preserve (item: any) in filter callbacks or use a narrow local interface because KubernetesClient list methods return unknown[].
17-52: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winReplace the per-namespace subscription list with the cluster-wide method.
This function currently calls
listCustomResources(..., nsName, ...)for every namespace returned bylistNamespaces(), then silently swallows per-namespace errors.listClusterCustomResources('operators.coreos.com', 'v1alpha1', 'subscriptions')makes one call, and each result includes its namespace, so delete it against thatmetadata.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/test-utils/operator-cleanup.ts` around lines 17 - 52, Update the operator cleanup flow to replace the per-namespace loop and listCustomResources calls with a single listClusterCustomResources call for subscriptions. Use each subscription’s metadata.namespace when invoking deleteCustomResource, while preserving matching by operatorPackageName and deletion of all matching subscriptions.frontend/e2e/pages/catalog-page.ts (1)
72-78: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRemove the duplicate search-input getter.
getSearchInputandgetSearchInputElementreturn the same locator. Keep one accessor to avoid two names for one concept.♻️ Proposed cleanup
getSearchInput(): Locator { return this.searchCatalogInput; } - - getSearchInputElement(): Locator { - return this.searchCatalogInput; - }🤖 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/catalog-page.ts` around lines 72 - 78, Remove the duplicate accessor between getSearchInput and getSearchInputElement in the catalog page object, retaining a single search-input getter and updating any callers to use the retained method.frontend/e2e/pages/operator-install-page.ts (1)
24-38: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract the shared catalog-to-install-form prologue.
All three install methods repeat the same ten steps: navigate to
/catalog/all-namespaces, select the Operator tab, search, verify and click the card, then verify and click the install button. Extract one private helper and call it from each method. This keeps selector and timeout changes in one place.♻️ Proposed refactor
+ private async openInstallForm(operatorName: string, operatorCardTestID: string): Promise<void> { + await this.goTo('/catalog/all-namespaces'); + await this.catalogPage.clickOperatorTab(); + await this.catalogPage.searchOperators(operatorName); + + const operatorCard = this.page.getByTestId(operatorCardTestID); + await expect(operatorCard).toBeVisible({ timeout: 30_000 }); + await this.robustClick(operatorCard); + + await expect(this.installButton).toBeVisible(); + await expect(this.installButton).toHaveAttribute('href'); + await this.robustClick(this.installButton); + } + async installOperatorGlobally(operatorName: string, operatorCardTestID: string): Promise<void> { - await this.goTo('/catalog/all-namespaces'); - - await this.catalogPage.clickOperatorTab(); - await this.catalogPage.searchOperators(operatorName); - - // Verify operator exists before clicking - const operatorCard = this.page.getByTestId(operatorCardTestID); - await expect(operatorCard).toBeVisible({ timeout: 30_000 }); - await this.robustClick(operatorCard); - - // Wait for install button and verify it has href - await expect(this.installButton).toBeVisible(); - await expect(this.installButton).toHaveAttribute('href'); - await this.robustClick(this.installButton); + await this.openInstallForm(operatorName, operatorCardTestID);🤖 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/operator-install-page.ts` around lines 24 - 38, Extract the repeated catalog-to-install-form flow from installOperatorGlobally and the other two install methods into a private helper: navigate to /catalog/all-namespaces, select the Operator tab, search by operator name, verify and click the operator card, then verify and click the install button. Update all three methods to call this helper, preserving the existing selectors, timeout, and robustClick behavior.frontend/e2e/pages/operator-hub-details-page.ts (1)
78-99: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueMove the assertions in
toggleSourceAndVerifyto the spec.
toggleSourceAndVerifyperforms the toggle flow and also asserts the modal title and both status values. Keep page objects action-only and assert in the spec file, so failures point at the test intent. Expose the toggle steps and let the spec callexpectongetSourceStatus.Based on learnings: real expectations should be asserted in the spec files (e.g.,
expect(...).toBeVisible()), not in page objects.🤖 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/operator-hub-details-page.ts` around lines 78 - 99, Refactor toggleSourceAndVerify in the operator hub details page object to perform only the source-toggle actions, removing its modal-title and source-status assertions and exposing the necessary steps for callers. Move those expectations into the consuming spec, including both status checks via getSourceStatus and the modal-title checks, so test intent and failures remain in the spec.Source: Learnings
frontend/e2e/pages/operator-details-page.ts (4)
293-299: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winRemove commented-out code.
Lines 294 and 296 contain commented-out selector definitions no longer used. Remove them for clarity.
🤖 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/operator-details-page.ts` around lines 293 - 299, Remove the unused commented-out selector declarations from verifyUninstallAlert, including the alert and modal-title comments, while leaving the dialog visibility and expected-text assertions unchanged.
101-124: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winRemove commented-out code.
Lines 107-118 contain commented-out navigation logic. Remove it, since it adds no value and clutters the method.
🤖 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/operator-details-page.ts` around lines 101 - 124, Remove the commented-out destructuring and navigation blocks from deleteOperand, including the unused breadcrumb, tab-navigation, and operand-link code, while preserving the URL assertion and deletion flow.
198-228: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract shared uninstall-modal setup logic.
uninstallOperatoranduninstallOperatorWithOperandsduplicate the click-page-action, modal-open, title-check, and skeleton-wait sequence. Extract this into a private helper to reduce duplication and the risk of the two methods diverging.🤖 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/operator-details-page.ts` around lines 198 - 228, Extract the shared page-action click, modal-open wait, title assertion, and loading-skeleton wait from uninstallOperator and uninstallOperatorWithOperands into a private helper on the page object. Have both methods call this helper, while preserving their existing submit and delete-all-operands behavior.
51-82: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract shared logic between
createOperandandcreateOperandFromTab.
createOperandandcreateOperandFromTabduplicate the same create-form logic.createOperandonly adds a precedingnavigateToOperandTabcall. HavecreateOperandcallnavigateToOperandTaband then delegate tocreateOperandFromTabto avoid future divergence.♻️ Proposed refactor
async createOperand(testOperand: TestOperandProps, isGlobal: boolean = true): Promise<void> { - const { exampleName, createActionID } = testOperand; - await this.navigateToOperandTab(testOperand.name, isGlobal); - - // Verify operand doesn't already exist - await expect(this.getOperandLink(exampleName)).not.toBeAttached(); - - // Click create button - await this.robustClick(this.createItemButton); - - // If specific create action ID is provided, wait for dropdown and click it - if (createActionID) { - // Wait for the dropdown item to be visible before clicking - await expect(this.page.getByTestId(createActionID)).toBeVisible({ timeout: 30_000 }); - await this.robustClick(this.page.getByTestId(createActionID)); - } - - // Verify we're on the create form - await expect(this.page).toHaveURL(/~new/); - - // Fill in the name - await expect(this.nameInput).toBeEnabled(); - await this.nameInput.clear(); - await this.nameInput.fill(exampleName); - - // Submit the form - await this.clickSubmitButton(); - - // Wait for form submission and redirect - await expect(this.page).not.toHaveURL(/~new/, { timeout: 60_000 }); + await this.createOperandFromTab(testOperand); }Also applies to: 139-168
🤖 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/operator-details-page.ts` around lines 51 - 82, Refactor createOperand to only call navigateToOperandTab with the operand name and isGlobal, then delegate the remaining creation flow to createOperandFromTab using the same testOperand argument. Move or reuse the shared create-form logic through createOperandFromTab so both methods cannot diverge.frontend/e2e/tests/olm/operator-install-global.spec.ts (2)
41-51: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract duplicated cluster-operator cleanup logic.
The
beforeEachandafterEachhooks both listoperators.coreos.comresources, filter byoperatorPackageNamesubstring, and delete each match. Extract this into a shared helper function (for example alongsidecleanupDataGridOperatorResources) to avoid the two copies diverging over time.Also applies to: 91-101
🤖 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/olm/operator-install-global.spec.ts` around lines 41 - 51, Extract the duplicated cluster-operator cleanup flow from the beforeEach and afterEach hooks into a shared helper near cleanupDataGridOperatorResources. Have the helper list operators.coreos.com resources, filter names by operatorPackageName, delete each matching resource, and preserve the existing error logging; replace both hook implementations with calls to this helper.
80-80: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winReplace fixed sleeps with a deterministic wait.
Line 80 uses
page.waitForTimeout(5000)and line 88 uses a rawsetTimeoutpromise for 10 seconds to let cleanup propagate. Fixed sleeps make the suite slower than necessary when cleanup finishes early, and flaky when cleanup takes longer than the fixed duration. Poll for the actual absence of the operator/operand resources instead of waiting a fixed duration.Also applies to: 88-88
🤖 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/olm/operator-install-global.spec.ts` at line 80, Replace the fixed delays in the operator cleanup flow around the waitForTimeout call and raw setTimeout promise with deterministic polling that repeatedly checks until the operator and operand resources are absent. Preserve the cleanup sequencing, but allow the wait to finish immediately when resources disappear and continue until the configured polling timeout when propagation is slow.frontend/e2e/tests/olm/catalog-source-details.spec.ts (1)
72-73: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueCompute the shared suffix once.
Line 72 and Line 73 call
Date.now()separately. The two names diverge when the calls straddle a millisecond boundary. Compute the timestamp once and reuse it.♻️ Proposed refactor
- const testNs = `test-catsrc-${Date.now()}`; - const catalogSourceName = `test-catsrc-${Date.now()}`; + const suffix = Date.now(); + const testNs = `test-catsrc-${suffix}`; + const catalogSourceName = `test-catsrc-${suffix}`;🤖 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/olm/catalog-source-details.spec.ts` around lines 72 - 73, Compute the timestamp once before the testNs and catalogSourceName declarations, then reuse that shared value in both template strings so the names always have the same suffix.frontend/e2e/tests/olm/create-namespace.spec.ts (3)
27-42: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRename the shadowing loop variable.
Line 28 declares
nsNameinside the loop. It shadows the suite-levelnsNamethat Line 17 assigns and thatafterEachuses at Line 58 and Line 66. The shadowing is easy to misread during later edits. Rename the loop variable tostaleNsName.🤖 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/olm/create-namespace.spec.ts` around lines 27 - 42, Rename the loop-local variable in the test namespace cleanup loop from nsName to staleNsName, and update all references within that loop, including logging, cleanupOperatorResources, deleteNamespace, and the error message. Preserve the suite-level nsName used by afterEach unchanged.
47-48: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winReplace the fixed 10 s sleep with a readiness poll.
cleanupAllOperatorsByPackageNamealready sleeps 5 s internally. This adds 10 s more to every test in the suite. The hook also takes thepagefixture only to callwaitForTimeout.Poll the API for the absence of the subscriptions and ClusterServiceVersions instead. Then drop
pagefrom the hook signature.🤖 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/olm/create-namespace.spec.ts` around lines 47 - 48, Replace the fixed waitForTimeout call in the cleanup hook with polling against the API until the targeted subscriptions and ClusterServiceVersions are absent, reusing the existing cleanupAllOperatorsByPackageName context and allowing the poll to time out appropriately. Remove the unused page fixture from the hook signature and its callers while preserving the cleanup behavior.
52-59: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueTake
k8sClientfrom the fixture inafterEach.The hook uses the suite-level
k8sClientvariable thatbeforeEachassigns.operator-install-single-namespace.spec.tsdestructures the fixture directly inafterEach. Follow that pattern here and delete the suite-level variable. The hook then works even whenbeforeEachfails early.♻️ Proposed refactor
- test.afterEach(async () => { + test.afterEach(async ({ k8sClient }) => { console.log('=== CREATE NAMESPACE AFTER EACH: Starting cleanup ===');🤖 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/olm/create-namespace.spec.ts` around lines 52 - 59, Update the create-namespace test cleanup hook to destructure k8sClient directly from the afterEach fixture, matching the pattern in operator-install-single-namespace.spec.ts. Remove the suite-level k8sClient variable and its beforeEach assignment, while continuing to pass the fixture client to cleanupOperatorResources.frontend/e2e/tests/olm/edit-default-sources.spec.ts (1)
26-36: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winAdd an explicit timeout to the status assertions.
After the modal submits, the console patches the
OperatorHubresource and waits for the watch update. The default expect timeout can expire before the status text changes. Set an explicit timeout on both assertions.♻️ Proposed change
- await expect(operatorHubPage.getSourceStatus(defaultSourceToBeToggled)).toHaveText('Disabled'); + await expect(operatorHubPage.getSourceStatus(defaultSourceToBeToggled)).toHaveText( + 'Disabled', + { timeout: 60_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/olm/edit-default-sources.spec.ts` around lines 26 - 36, Update both getSourceStatus(defaultSourceToBeToggled) status assertions in the toggle flow to use an explicit timeout long enough for the OperatorHub watch update after modal submission, preserving the existing Disabled and Enabled expectations.frontend/e2e/pages/installed-operators-page.ts (3)
111-136: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDelete the two
waitForFunctionblocks. They do not wait for what the comments claim.The first block waits for
!input.disabled. The filter input is never disabled, so the predicate is true on the first evaluation. It does not wait for the 250 ms debounce. The followingexpect(...).toBeVisible()performs the real wait.The second block re-checks opacity and visibility.
toBeVisible()already covers that, andaria-busyis checked on the row element only.The comment at Line 111 says the namespace is selected only when it is not
openshift-operators, butselectNamespacealways runs. Update the comment or the logic.♻️ Proposed simplification
- // Select namespace if not openshift-operators + // Scope the list to the target namespace await this.selectNamespace(namespace); await this.filterByName(operatorName); - // Wait for debounce to complete before clicking (filter-toolbar.tsx uses 250ms debounce) - await this.page.waitForFunction(() => { - const input = document.querySelector('[data-test="name-filter-input"]') as HTMLInputElement; - return input && !input.disabled; - }); - // Wait for the operator row to be visible await expect(this.getOperatorRow(operatorName)).toBeVisible({ timeout: 30_000 }); - - // Additional wait to ensure the table row is stable and ready for interaction - await this.page.waitForFunction( - (name) => { - const row = document.querySelector(`[data-test="operator-row-${name}"]`); - if (!row) return false; - // Check that row is fully rendered and stable - const style = window.getComputedStyle(row); - return style.opacity === '1' && style.visibility === 'visible' && !row.hasAttribute('aria-busy'); - }, - operatorName, - { timeout: 10_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/pages/installed-operators-page.ts` around lines 111 - 136, Remove both waitForFunction blocks surrounding getOperatorRow(operatorName), along with their misleading comments, because toBeVisible already provides the necessary wait and visibility check. Also reconcile the namespace comment with the behavior of selectNamespace: either update the comment to state that it always runs or conditionally call selectNamespace only when namespace is not openshift-operators.
63-103: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winReplace the manual polling loop with a web-first assertion.
The loop reimplements Playwright retry logic. It also re-creates the
status-textlocator at Line 72, although the class already holdsstatusTextat Line 11.expect.pollkeeps the fail-fast behavior onFailedand produces better trace output.If the filtered table can render more than one row,
statusElement.textContent()raises a strict-mode error. The current code swallows that error and only fails after 3 minutes. Scope the locator to the operator row to avoid this.♻️ Proposed refactor
- // Debug the status element by polling its text content - const statusElement = this.page.getByTestId('status-text'); - - console.log(`Waiting for ${operatorName} operator status to be 'Succeeded'...`); - - // Poll the status text every 5 seconds and log what we see - let attempts = 0; - const maxAttempts = 36; // 3 minutes worth of 5-second polls - - while (attempts < maxAttempts) { - let currentText: string | null = null; - try { - currentText = await statusElement.textContent({ timeout: 5000 }); - console.log(`Attempt ${attempts + 1}: Status text is "${currentText}"`); - } catch (error) { - console.log(`Attempt ${attempts + 1}: Could not read status text: ${error.message}`); - } - - if (currentText?.includes('Succeeded')) { - console.log('✅ Found "Succeeded" in status text!'); - return; // Success! - } - - if (currentText?.includes('Failed')) { - throw new Error(`Operator installation failed. Status: ${currentText}`); - } - - attempts++; - await this.page.waitForTimeout(5000); // Wait 5 seconds between polls - } - - throw new Error(`Timeout waiting for operator status to be 'Succeeded' after ${maxAttempts * 5} seconds`); + const statusElement = operatorRow.getByTestId('status-text'); + + await expect + .poll( + async () => { + const text = await statusElement.textContent().catch(() => null); + if (text?.includes('Failed')) { + throw new Error(`Operator installation failed. Status: ${text}`); + } + return text ?? ''; + }, + { intervals: [5_000], timeout: 180_000 }, + ) + .toContain('Succeeded');🤖 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/installed-operators-page.ts` around lines 63 - 103, Replace the manual polling in verifyOperatorInstallationSucceeded with Playwright expect.poll, reusing the class-level statusText locator where applicable. Scope statusText to the located operatorRow so multiple table rows cannot cause a strict-mode error, preserve immediate failure when the status contains “Failed,” and assert successful completion when it contains “Succeeded.”
47-58: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
clickOperatorRowignoresoperatorURLName, which makes the pinned operator version dead data. The parameter is threaded from the spec throughnavigateToOperatorDetailsintoclickOperatorRowand is never read. The pinned version therefore implies a coupling that does not exist.
frontend/e2e/pages/installed-operators-page.ts#L47-L58: remove theoperatorURLNameparameter fromclickOperatorRow, and remove it fromnavigateToOperatorDetails.frontend/e2e/tests/olm/operator-install-single-namespace.spec.ts#L8-L12: remove theurlNamefield fromtestOperatorand drop the argument from thenavigateToOperatorDetailscalls at Lines 129, 157, and 177.🤖 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/installed-operators-page.ts` around lines 47 - 58, Remove the unused operatorURLName parameter from clickOperatorRow and navigateToOperatorDetails in frontend/e2e/pages/installed-operators-page.ts, updating their call chain accordingly. In frontend/e2e/tests/olm/operator-install-single-namespace.spec.ts, remove testOperator.urlName and omit that argument from navigateToOperatorDetails calls at lines 129, 157, and 177.frontend/e2e/tests/olm/packageserver-tabs.spec.ts (1)
12-53: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConstruct the page objects once per test.
Each
test.stepcreates a newDetailsPageorYamlEditorPagefor the samepage. The test at Line 67 already builds them once outside the steps. Follow that pattern here.♻️ Proposed refactor for the Details tab test
test('renders Details tab correctly', async ({ page }) => { + const detailsPage = new DetailsPage(page); + await test.step('Navigate to PackageManifest Details tab', async () => { - const detailsPage = new DetailsPage(page); await detailsPage.navigateToDetailsUrl(baseUrl); }); await test.step('Verify page title shows package name', async () => { - const detailsPage = new DetailsPage(page); await expect(detailsPage.title).toContainText(packageManifestName); });🤖 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/olm/packageserver-tabs.spec.ts` around lines 12 - 53, The Details and YAML tests recreate page objects inside each test.step; instantiate each test’s DetailsPage or YamlEditorPage once near the start of the test and reuse it across all steps, matching the existing pattern used by the later test.frontend/e2e/tests/olm/operator-uninstall.spec.ts (1)
9-34: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract the duplicated Data Grid test data.
testOperatorandtestOperandhere repeat the definitions infrontend/e2e/tests/olm/operator-install-single-namespace.spec.tsat Lines 8-21. OnlycreateActionIDdiffers. Move the shared constants into a helper module underfrontend/e2e/test-utils/and import them in both specs. The pinnedurlNamethen has one owner.🤖 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/olm/operator-uninstall.spec.ts` around lines 9 - 34, Extract the shared Data Grid definitions from testOperator and testOperand in both operator-uninstall.spec.ts and operator-install-single-namespace.spec.ts into a helper module under frontend/e2e/test-utils/. Export and reuse the common operator and operand constants in both specs, while allowing each spec to provide its distinct createActionID; keep the pinned urlName defined only in the helper.frontend/e2e/tests/olm/operator-install-single-namespace.spec.ts (1)
76-92: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winResolve the deferred cleanup decision in
afterEach.The hook only logs when operators remain. The comment at Line 86 leaves the decision open. When the test fails before the UI uninstall step, the Subscription in
openshift-operatorsand the cluster-scopedOperatorobject survive the run. Thecleanupfixture removes only the tracked namespace.Call
cleanupAllOperatorsByPackageNamewhenstillThere.length > 0, and keep the log line for diagnostics. I can prepare that change or open a tracking issue.🤖 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/olm/operator-install-single-namespace.spec.ts` around lines 76 - 92, The afterEach verification must perform deferred cleanup when operators remain after UI uninstall. In the stillThere.length > 0 branch, retain the diagnostic log and call cleanupAllOperatorsByPackageName with the relevant operator package name so both the Subscription and cluster-scoped Operator are removed.
🤖 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/installed-operators-page.ts`:
- Around line 153-156: Update frontend/e2e/pages/installed-operators-page.ts at
lines 153-156 in verifyOperatorNotExists to wait for .loading-skeleton--table to
detach, then call filterByName before asserting the operator row is not
attached; apply the same loading-skeleton wait after selectNamespace and
filterByName in verifyOperatorNotInstalledInNamespace at lines 175-181 before
its negative assertion.
- Around line 197-205: Update the namespaceOption locator in the
installed-operators page flow to use an anchored exact-text match for namespace
rather than substring filtering and first-item selection. Ensure the final
namespace-bar-dropdown assertion also verifies the complete namespace value,
preventing similarly prefixed generated namespaces from being treated as
matches.
In `@frontend/e2e/pages/operator-install-page.ts`:
- Around line 131-134: Update the namespace-radio branch in the operator install
page to use the same bounded wait as installOperatorInNamespace instead of an
immediate selectNamespaceRadio.count() probe. Preserve the conditional
check-and-check behavior while allowing the radio to appear asynchronously.
- Around line 85-88: Update the conditional around selectNamespaceRadio in the
operator-install page to wait for the radio element with a bounded timeout
instead of using count(). Preserve the existing check behavior when the element
becomes available, while allowing the flow to continue if it does not appear
within the timeout.
In `@frontend/e2e/pages/yaml-editor-page.ts`:
- Around line 121-126: Update getEditorContent so the browser evaluation safely
checks that window.monaco and its editor API exist before calling getModels,
returning an empty string when Monaco is not yet initialized; preserve the
existing model-content behavior when the editor is available.
In `@frontend/e2e/test-utils/cluster-cleanup.ts`:
- Around line 26-40: Update the namespace discovery in the cleanup function to
call KubernetesClient.listNamespaces() and use its returned namespace
collection, removing the kubectl child_process imports, execution, JSON parsing,
and obsolete limitation comments. Keep filtering to names beginning with “test-”
and remove the redundant test-operator- predicate before continuing with the
existing cleanup flow.
In `@frontend/e2e/test-utils/olm-cleanup.ts`:
- Around line 112-115: Update the cleanupOLMOperatorCompletely call to always
scope namespacePattern to the caller’s namespace instead of passing undefined
for non-test namespaces. Preserve the existing test-namespace behavior while
ensuring cleanup cannot match resources across the entire cluster.
- Around line 63-71: Update the forceDelete branch in the cleanup deletion catch
block to perform the force deletion using
KubernetesClient.patchClusterCustomResource to strip finalizers, then retry
deletion; otherwise remove or disable forceDelete and log the original error
instead of claiming a retry occurred. Ensure failures in the force path also
surface the relevant error.
- Around line 76-82: Replace the namespacePattern no-op in the cleanup flow with
k8sClient.listNamespaces(), filter results to names starting with test-, and
delete each matching namespace through the client. Remove the stale limitation
note and manual kubectl command, while preserving the existing cleanup
completion behavior.
In `@frontend/e2e/test-utils/olm-test-cleanup.ts`:
- Around line 79-83: Update the testNamespaces predicate in
performOperatorCleanup to match only namespaces owned by this helper, removing
the broad name.includes(packageName) condition while preserving the test- prefix
matching; continue relying on the existing explicit cleanup of globalNamespace
and openshift-marketplace.
- Line 61: Replace the fixed cleanup sleeps in the hooks around
verifyAndForceCleanup and the waits at the referenced cleanup points with
timeout-backed polling using the existing listClusterCustomResources(...,
'operators') call. Continue polling until matching Operator resources disappear,
then force-clean only remaining stragglers, while preserving the existing
timeout behavior and cleanup flow.
In `@frontend/e2e/test-utils/operator-cleanup.ts`:
- Around line 136-141: Update the cleanup flow around the try/catch in the
operator cleanup function so the “✅ Cleanup complete” message is emitted only
after successful cleanup. Move that log into the try block or otherwise prevent
it from running after the catch handles an error, while preserving the existing
error logging.
In `@frontend/e2e/tests/console/crud/other-routes.spec.ts`:
- Around line 141-142: Update the URL assertion around expectedPath to prevent
substring matches by anchoring the regular expression with ^ and $, while
allowing an optional query suffix; alternatively compare the parsed URL pathname
exactly. Preserve the existing route.path query stripping and escaping behavior.
In `@frontend/e2e/tests/olm/create-namespace.spec.ts`:
- Around line 22-45: Update the stale namespace filter in the cleanup block to
match the prefix produced by generateTestNamespace(), such as test-, so leftover
namespaces are discovered and deleted; keep the existing
cleanupOperatorResources and deleteNamespace flow unchanged.
In `@frontend/e2e/tests/olm/edit-default-sources.spec.ts`:
- Around line 19-37: Add an afterEach teardown for the test that uses k8sClient
to patch the OperatorHub cluster resource, ensuring the redhat-operators default
source is enabled even when assertions or toggling fail. Keep the existing
toggle verification flow unchanged and make the cleanup unconditional.
In `@frontend/e2e/tests/olm/operator-hub.spec.ts`:
- Around line 39-53: Validate that originalTileText from
getFirstCatalogTileTitle().textContent() is non-empty before passing it to
verifyTileTextChanged; do not use an empty-string fallback that can make the
comparison vacuously succeed. Preserve the existing Community-to-Certified
filter flow and changed-title assertion once the captured title is confirmed.
- Around line 77-97: Update the empty-search verification in the operator hub
test to use toHaveCount(0) for catalogPage.getCatalogTiles(), avoiding
strict-mode issues with the multi-element locator. Remove the clearButton count
conditional and assert the clear-filters button is visible directly, then click
it and verify the search input is empty and catalog tiles return.
In `@frontend/e2e/tests/olm/operator-install-single-namespace.spec.ts`:
- Around line 48-66: Scope the namespace cleanup around generateTestNamespace to
only namespaces created by this test worker, rather than every name beginning
with test-. Track generated namespace names in the suite (or include and filter
by a unique per-run identifier), then use that owned set when invoking
cleanupOperatorResources; preserve cleanup of all resources within those owned
namespaces.
- Around line 133-144: Replace the empty-state assertion in the isolation
verification step with
InstalledOperatorsPage.verifyOperatorNotInstalledInNamespace, passing the
operator under test and globalNamespace. Keep the existing navigation and
namespace selection, and assert only that this specific operator is absent from
the global namespace.
In `@frontend/e2e/tests/olm/operator-uninstall.spec.ts`:
- Around line 116-137: The uninstall flow in the “Successfully uninstall
operator (without operands)” step conflicts with the created example-backup and
subsequent deletion assertion. Update the step to call
operatorDetailsPage.uninstallOperatorWithOperands() so the operand is explicitly
deleted, and revise the step title/comment to reflect that an operand exists and
is removed.
---
Nitpick comments:
In `@frontend/e2e/clients/kubernetes-client.ts`:
- Around line 661-685: Update listClusterCustomResources and listNamespaces to
log the caught error before returning the existing empty-array fallback.
Preserve the current return behavior and use the client’s established logging
mechanism so RBAC, network, and other failures are visible to test callers.
- Around line 282-288: Update getCurrentUser() to return undefined when
kubeConfig.getCurrentUser() throws instead of the placeholder user object, and
change its return type from any to the typed k8s.User-compatible optional return
type used consistently with getCurrentUserToken().
- Around line 571-586: Update patchClusterCustomResource to use
k8s.PatchStrategy.JsonPatch for the contentType value, matching the existing
patchCustomResource implementation and removing the duplicated literal.
In `@frontend/e2e/pages/catalog-page.ts`:
- Around line 72-78: Remove the duplicate accessor between getSearchInput and
getSearchInputElement in the catalog page object, retaining a single
search-input getter and updating any callers to use the retained method.
In `@frontend/e2e/pages/installed-operators-page.ts`:
- Around line 111-136: Remove both waitForFunction blocks surrounding
getOperatorRow(operatorName), along with their misleading comments, because
toBeVisible already provides the necessary wait and visibility check. Also
reconcile the namespace comment with the behavior of selectNamespace: either
update the comment to state that it always runs or conditionally call
selectNamespace only when namespace is not openshift-operators.
- Around line 63-103: Replace the manual polling in
verifyOperatorInstallationSucceeded with Playwright expect.poll, reusing the
class-level statusText locator where applicable. Scope statusText to the located
operatorRow so multiple table rows cannot cause a strict-mode error, preserve
immediate failure when the status contains “Failed,” and assert successful
completion when it contains “Succeeded.”
- Around line 47-58: Remove the unused operatorURLName parameter from
clickOperatorRow and navigateToOperatorDetails in
frontend/e2e/pages/installed-operators-page.ts, updating their call chain
accordingly. In
frontend/e2e/tests/olm/operator-install-single-namespace.spec.ts, remove
testOperator.urlName and omit that argument from navigateToOperatorDetails calls
at lines 129, 157, and 177.
In `@frontend/e2e/pages/operator-details-page.ts`:
- Around line 293-299: Remove the unused commented-out selector declarations
from verifyUninstallAlert, including the alert and modal-title comments, while
leaving the dialog visibility and expected-text assertions unchanged.
- Around line 101-124: Remove the commented-out destructuring and navigation
blocks from deleteOperand, including the unused breadcrumb, tab-navigation, and
operand-link code, while preserving the URL assertion and deletion flow.
- Around line 198-228: Extract the shared page-action click, modal-open wait,
title assertion, and loading-skeleton wait from uninstallOperator and
uninstallOperatorWithOperands into a private helper on the page object. Have
both methods call this helper, while preserving their existing submit and
delete-all-operands behavior.
- Around line 51-82: Refactor createOperand to only call navigateToOperandTab
with the operand name and isGlobal, then delegate the remaining creation flow to
createOperandFromTab using the same testOperand argument. Move or reuse the
shared create-form logic through createOperandFromTab so both methods cannot
diverge.
In `@frontend/e2e/pages/operator-hub-details-page.ts`:
- Around line 78-99: Refactor toggleSourceAndVerify in the operator hub details
page object to perform only the source-toggle actions, removing its modal-title
and source-status assertions and exposing the necessary steps for callers. Move
those expectations into the consuming spec, including both status checks via
getSourceStatus and the modal-title checks, so test intent and failures remain
in the spec.
In `@frontend/e2e/pages/operator-install-page.ts`:
- Around line 24-38: Extract the repeated catalog-to-install-form flow from
installOperatorGlobally and the other two install methods into a private helper:
navigate to /catalog/all-namespaces, select the Operator tab, search by operator
name, verify and click the operator card, then verify and click the install
button. Update all three methods to call this helper, preserving the existing
selectors, timeout, and robustClick behavior.
In `@frontend/e2e/test-utils/cluster-cleanup.ts`:
- Around line 154-185: Update the operand cleanup flow around operandTypes to
derive the operand group, version, and plurals from the configurable
targetOperator in ClusterCleanupOptions instead of hardcoding infinispan.org. In
the catch block surrounding listCustomResources and deleteCustomResource, log
the caught error with enough context to identify the operand type and namespace
while preserving the existing cleanup continuation behavior.
- Around line 59-151: Extract the repeated CSV, Subscription, and InstallPlan
cleanup logic into a shared cleanupResourceType helper that accepts the resource
plural, API version, and match predicate, while preserving dry-run behavior,
logging, listing, deletion, and error handling. Replace the three inline
try/catch blocks with calls to this helper using the existing resource-specific
predicates and targetOperator matching.
In `@frontend/e2e/test-utils/operator-cleanup.ts`:
- Around line 98-100: Align the subscription matching predicates in the cleanup
functions of olm-test-cleanup.ts and olm-cleanup.ts with the exact-match
behavior used by operator-cleanup.ts, replacing broader includes/startsWith
checks where appropriate. If either module must retain broader matching,
document the specific reason and intended resource-selection difference.
- Line 13: Replace the any-typed Kubernetes client parameters with the
default-imported KubernetesClient type across
frontend/e2e/test-utils/operator-cleanup.ts#L13-L13,
frontend/e2e/test-utils/cluster-cleanup.ts#L15-L15,
frontend/e2e/test-utils/olm-cleanup.ts#L17-L20, and
frontend/e2e/test-utils/olm-test-cleanup.ts#L15-L15: update all named cleanup
functions in those files, and type the olm-test-cleanup hook fixtures at Lines
149-171 instead of any. Preserve (item: any) in filter callbacks or use a narrow
local interface because KubernetesClient list methods return unknown[].
- Around line 17-52: Update the operator cleanup flow to replace the
per-namespace loop and listCustomResources calls with a single
listClusterCustomResources call for subscriptions. Use each subscription’s
metadata.namespace when invoking deleteCustomResource, while preserving matching
by operatorPackageName and deletion of all matching subscriptions.
In `@frontend/e2e/tests/olm/catalog-source-details.spec.ts`:
- Around line 72-73: Compute the timestamp once before the testNs and
catalogSourceName declarations, then reuse that shared value in both template
strings so the names always have the same suffix.
In `@frontend/e2e/tests/olm/create-namespace.spec.ts`:
- Around line 27-42: Rename the loop-local variable in the test namespace
cleanup loop from nsName to staleNsName, and update all references within that
loop, including logging, cleanupOperatorResources, deleteNamespace, and the
error message. Preserve the suite-level nsName used by afterEach unchanged.
- Around line 47-48: Replace the fixed waitForTimeout call in the cleanup hook
with polling against the API until the targeted subscriptions and
ClusterServiceVersions are absent, reusing the existing
cleanupAllOperatorsByPackageName context and allowing the poll to time out
appropriately. Remove the unused page fixture from the hook signature and its
callers while preserving the cleanup behavior.
- Around line 52-59: Update the create-namespace test cleanup hook to
destructure k8sClient directly from the afterEach fixture, matching the pattern
in operator-install-single-namespace.spec.ts. Remove the suite-level k8sClient
variable and its beforeEach assignment, while continuing to pass the fixture
client to cleanupOperatorResources.
In `@frontend/e2e/tests/olm/edit-default-sources.spec.ts`:
- Around line 26-36: Update both getSourceStatus(defaultSourceToBeToggled)
status assertions in the toggle flow to use an explicit timeout long enough for
the OperatorHub watch update after modal submission, preserving the existing
Disabled and Enabled expectations.
In `@frontend/e2e/tests/olm/operator-install-global.spec.ts`:
- Around line 41-51: Extract the duplicated cluster-operator cleanup flow from
the beforeEach and afterEach hooks into a shared helper near
cleanupDataGridOperatorResources. Have the helper list operators.coreos.com
resources, filter names by operatorPackageName, delete each matching resource,
and preserve the existing error logging; replace both hook implementations with
calls to this helper.
- Line 80: Replace the fixed delays in the operator cleanup flow around the
waitForTimeout call and raw setTimeout promise with deterministic polling that
repeatedly checks until the operator and operand resources are absent. Preserve
the cleanup sequencing, but allow the wait to finish immediately when resources
disappear and continue until the configured polling timeout when propagation is
slow.
In `@frontend/e2e/tests/olm/operator-install-single-namespace.spec.ts`:
- Around line 76-92: The afterEach verification must perform deferred cleanup
when operators remain after UI uninstall. In the stillThere.length > 0 branch,
retain the diagnostic log and call cleanupAllOperatorsByPackageName with the
relevant operator package name so both the Subscription and cluster-scoped
Operator are removed.
In `@frontend/e2e/tests/olm/operator-uninstall.spec.ts`:
- Around line 9-34: Extract the shared Data Grid definitions from testOperator
and testOperand in both operator-uninstall.spec.ts and
operator-install-single-namespace.spec.ts into a helper module under
frontend/e2e/test-utils/. Export and reuse the common operator and operand
constants in both specs, while allowing each spec to provide its distinct
createActionID; keep the pinned urlName defined only in the helper.
In `@frontend/e2e/tests/olm/packageserver-tabs.spec.ts`:
- Around line 12-53: The Details and YAML tests recreate page objects inside
each test.step; instantiate each test’s DetailsPage or YamlEditorPage once near
the start of the test and reuse it across all steps, matching the existing
pattern used by the later test.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
| async verifyOperatorNotExists(operatorName: string): Promise<void> { | ||
| await this.navigateToInstalledOperators(); | ||
| await expect(this.getOperatorRow(operatorName)).not.toBeAttached(); | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Negative operator-row assertions run before the operator list loads. Both methods navigate and then assert not.toBeAttached(). Right after navigation the table renders .loading-skeleton--table, so the row is absent and the assertion passes even when the operator is still installed. verifyNoOperatorsInstalled at Line 165 already shows the correct wait.
frontend/e2e/pages/installed-operators-page.ts#L153-L156: wait for.loading-skeleton--tableto detach, then applyfilterByName, before thenot.toBeAttached()assertion inverifyOperatorNotExists.frontend/e2e/pages/installed-operators-page.ts#L175-L181: wait for.loading-skeleton--tableto detach afterselectNamespaceandfilterByName, before thenot.toBeAttached()assertion inverifyOperatorNotInstalledInNamespace.
📍 Affects 1 file
frontend/e2e/pages/installed-operators-page.ts#L153-L156(this comment)frontend/e2e/pages/installed-operators-page.ts#L175-L181
🤖 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/installed-operators-page.ts` around lines 153 - 156,
Update frontend/e2e/pages/installed-operators-page.ts at lines 153-156 in
verifyOperatorNotExists to wait for .loading-skeleton--table to detach, then
call filterByName before asserting the operator row is not attached; apply the
same loading-skeleton wait after selectNamespace and filterByName in
verifyOperatorNotInstalledInNamespace at lines 175-181 before its negative
assertion.
| // Filter the namespace list to make the target namespace visible | ||
| const textFilter = this.page.getByTestId('dropdown-text-filter'); | ||
| await textFilter.fill(namespace); | ||
|
|
||
| // Select the dropdown menu item that contains our namespace text | ||
| const namespaceOption = this.page.getByTestId('dropdown-menu-item-link').filter({ hasText: namespace }).first(); | ||
| await this.robustClick(namespaceOption); | ||
|
|
||
| await expect(this.page.getByTestId('namespace-bar-dropdown')).toContainText(namespace); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Match the namespace option exactly.
filter({ hasText: namespace }) performs a substring match, and .first() then takes whichever item appears first. If the dropdown contains a namespace whose name starts with the target name, the wrong namespace is selected. Generated namespaces share the test- prefix, so several candidates can coexist. The final toContainText(namespace) assertion is also substring-based, so it does not catch the mistake.
Use an anchored match instead.
🐛 Proposed fix
+ const escaped = namespace.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const namespaceOption = this.page
.getByTestId('dropdown-menu-item-link')
- .filter({ hasText: namespace })
+ .filter({ hasText: new RegExp(`^${escaped}$`) })
.first();
await this.robustClick(namespaceOption);📝 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.
| // Filter the namespace list to make the target namespace visible | |
| const textFilter = this.page.getByTestId('dropdown-text-filter'); | |
| await textFilter.fill(namespace); | |
| // Select the dropdown menu item that contains our namespace text | |
| const namespaceOption = this.page.getByTestId('dropdown-menu-item-link').filter({ hasText: namespace }).first(); | |
| await this.robustClick(namespaceOption); | |
| await expect(this.page.getByTestId('namespace-bar-dropdown')).toContainText(namespace); | |
| // Filter the namespace list to make the target namespace visible | |
| const textFilter = this.page.getByTestId('dropdown-text-filter'); | |
| await textFilter.fill(namespace); | |
| const escaped = namespace.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); | |
| // Select the dropdown menu item that contains our namespace text | |
| const namespaceOption = this.page.getByTestId('dropdown-menu-item-link').filter({ hasText: new RegExp(`^${escaped}$`) }).first(); | |
| await this.robustClick(namespaceOption); | |
| await expect(this.page.getByTestId('namespace-bar-dropdown')).toContainText(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/pages/installed-operators-page.ts` around lines 197 - 205,
Update the namespaceOption locator in the installed-operators page flow to use
an anchored exact-text match for namespace rather than substring filtering and
first-item selection. Ensure the final namespace-bar-dropdown assertion also
verifies the complete namespace value, preventing similarly prefixed generated
namespaces from being treated as matches.
| // Check if select namespace radio exists | ||
| if (await this.selectNamespaceRadio.count() > 0) { | ||
| await this.selectNamespaceRadio.check(); | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
count() does not wait, so this branch can skip the radio selection.
selectNamespaceRadio.count() returns immediately. The console renders the namespace radio group after specificNamespaceRadio.check() resolves, so the count can still be 0 and the test then clicks the dropdown without selecting the radio. Wait for the element with a bounded timeout instead.
🐛 Proposed fix
- // Check if select namespace radio exists
- if (await this.selectNamespaceRadio.count() > 0) {
- await this.selectNamespaceRadio.check();
- }
+ // Wait for the optional radio to render before deciding
+ const hasSelectNamespaceRadio = await this.selectNamespaceRadio
+ .waitFor({ state: 'attached', timeout: 10_000 })
+ .then(() => true)
+ .catch(() => false);
+ if (hasSelectNamespaceRadio) {
+ await this.selectNamespaceRadio.check();
+ }📝 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.
| // Check if select namespace radio exists | |
| if (await this.selectNamespaceRadio.count() > 0) { | |
| await this.selectNamespaceRadio.check(); | |
| } | |
| // Wait for the optional radio to render before deciding | |
| const hasSelectNamespaceRadio = await this.selectNamespaceRadio | |
| .waitFor({ state: 'attached', timeout: 10_000 }) | |
| .then(() => true) | |
| .catch(() => false); | |
| if (hasSelectNamespaceRadio) { | |
| await this.selectNamespaceRadio.check(); | |
| } |
🤖 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/operator-install-page.ts` around lines 85 - 88, Update the
conditional around selectNamespaceRadio in the operator-install page to wait for
the radio element with a bounded timeout instead of using count(). Preserve the
existing check behavior when the element becomes available, while allowing the
flow to continue if it does not appear within the timeout.
| // Check if select namespace radio exists | ||
| if (await this.selectNamespaceRadio.count() > 0) { | ||
| await this.selectNamespaceRadio.check(); | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
Same non-waiting count() check as in installOperatorInNamespace.
This branch repeats the immediate count() probe. Apply the same bounded wait here. See the consolidated comment for the shared fix.
🤖 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/operator-install-page.ts` around lines 131 - 134, Update
the namespace-radio branch in the operator install page to use the same bounded
wait as installOperatorInNamespace instead of an immediate
selectNamespaceRadio.count() probe. Preserve the conditional check-and-check
behavior while allowing the radio to appear asynchronously.
| async getEditorContent(): Promise<string> { | ||
| return this.page.evaluate(() => { | ||
| const models = (window as any).monaco.editor.getModels(); | ||
| return models[0]?.getValue() || ''; | ||
| }); | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
Guard the monaco global, not just the model.
models[0]?.getValue() || '' only guards a missing model. If window.monaco is not yet assigned, getModels() throws TypeError: Cannot read properties of undefined, and the empty-string fallback never applies. Three specs call this helper, so a slow editor load fails the test with a confusing browser error.
🐛 Proposed fix
async getEditorContent(): Promise<string> {
+ await this.waitForEditorReady();
return this.page.evaluate(() => {
- const models = (window as any).monaco.editor.getModels();
- return models[0]?.getValue() || '';
+ const models = (window as any).monaco?.editor?.getModels?.() ?? [];
+ return models[0]?.getValue() ?? '';
});
}📝 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 getEditorContent(): Promise<string> { | |
| return this.page.evaluate(() => { | |
| const models = (window as any).monaco.editor.getModels(); | |
| return models[0]?.getValue() || ''; | |
| }); | |
| } | |
| async getEditorContent(): Promise<string> { | |
| await this.waitForEditorReady(); | |
| return this.page.evaluate(() => { | |
| const models = (window as any).monaco?.editor?.getModels?.() ?? []; | |
| return models[0]?.getValue() ?? ''; | |
| }); | |
| } |
🤖 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/yaml-editor-page.ts` around lines 121 - 126, Update
getEditorContent so the browser evaluation safely checks that window.monaco and
its editor API exist before calling getModels, returning an empty string when
Monaco is not yet initialized; preserve the existing model-content behavior when
the editor is available.
| // Track which tile is first with Community filter | ||
| const originalTileText = await catalogPage.getFirstCatalogTileTitle().textContent(); | ||
|
|
||
| // Disable Community filter | ||
| await catalogPage.toggleSourceFilter('community'); | ||
|
|
||
| // Enable Certified filter | ||
| await catalogPage.toggleSourceFilter('certified'); | ||
| await expect(async () => { | ||
| const count = await catalogPage.getCatalogTiles().count(); | ||
| expect(count).toBeGreaterThan(0); | ||
| }).toPass(); | ||
|
|
||
| // Verify the first tile title is different from Community filter | ||
| await catalogPage.verifyTileTextChanged(originalTileText || ''); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
The empty-string fallback can make the assertion always pass.
textContent() returns string | null. If the title is not yet rendered, originalTileText is null, and line 53 asserts not.toHaveText(''), which any non-empty tile satisfies. The step then reports success without comparing the two filters. Assert the captured text is non-empty before you use it.
🐛 Proposed fix
- const originalTileText = await catalogPage.getFirstCatalogTileTitle().textContent();
+ const originalTileText = await catalogPage.getFirstCatalogTileTitle().innerText();
+ expect(originalTileText.trim()).not.toBe('');
@@
- await catalogPage.verifyTileTextChanged(originalTileText || '');
+ await catalogPage.verifyTileTextChanged(originalTileText);📝 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.
| // Track which tile is first with Community filter | |
| const originalTileText = await catalogPage.getFirstCatalogTileTitle().textContent(); | |
| // Disable Community filter | |
| await catalogPage.toggleSourceFilter('community'); | |
| // Enable Certified filter | |
| await catalogPage.toggleSourceFilter('certified'); | |
| await expect(async () => { | |
| const count = await catalogPage.getCatalogTiles().count(); | |
| expect(count).toBeGreaterThan(0); | |
| }).toPass(); | |
| // Verify the first tile title is different from Community filter | |
| await catalogPage.verifyTileTextChanged(originalTileText || ''); | |
| // Track which tile is first with Community filter | |
| const originalTileText = await catalogPage.getFirstCatalogTileTitle().innerText(); | |
| expect(originalTileText.trim()).not.toBe(''); | |
| // Disable Community filter | |
| await catalogPage.toggleSourceFilter('community'); | |
| // Enable Certified filter | |
| await catalogPage.toggleSourceFilter('certified'); | |
| await expect(async () => { | |
| const count = await catalogPage.getCatalogTiles().count(); | |
| expect(count).toBeGreaterThan(0); | |
| }).toPass(); | |
| // Verify the first tile title is different from Community filter | |
| await catalogPage.verifyTileTextChanged(originalTileText); |
🤖 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/olm/operator-hub.spec.ts` around lines 39 - 53, Validate
that originalTileText from getFirstCatalogTileTitle().textContent() is non-empty
before passing it to verifyTileTextChanged; do not use an empty-string fallback
that can make the comparison vacuously succeed. Preserve the existing
Community-to-Certified filter flow and changed-title assertion once the captured
title is confirmed.
| // Wait for search to complete and verify no tiles | ||
| await expect(catalogPage.getCatalogTiles()).not.toBeAttached({ timeout: 10_000 }); | ||
|
|
||
| // Check if clear filters button appears | ||
| const clearButton = catalogPage.getClearFiltersButton(); | ||
| if (await clearButton.count() > 0) { | ||
| await expect(clearButton).toBeVisible(); | ||
| await catalogPage.clickClearAllFilters(); | ||
| await expect(catalogPage.getSearchInput()).toBeEmpty(); | ||
| await expect(async () => { | ||
| const count = await catalogPage.getCatalogTiles().count(); | ||
| expect(count).toBeGreaterThan(0); | ||
| }).toPass(); | ||
| } else { | ||
| // If clear button doesn't appear, just clear the search manually | ||
| await catalogPage.clearSearchFilter(); | ||
| await expect(async () => { | ||
| const count = await catalogPage.getCatalogTiles().count(); | ||
| expect(count).toBeGreaterThan(0); | ||
| }).toPass(); | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Use toHaveCount(0) and remove the conditional branch.
Two problems in this step:
getCatalogTiles()matches many elements.not.toBeAttached()on a multi-element locator is a strict-mode operation. UsetoHaveCount(0)to assert an empty result set.- The
if (await clearButton.count() > 0)branch means the test passes whether or not the clear-filters button appears. The step then does not verify the documented behavior. The empty search result always renders the empty state, so assert the button directly.
🐛 Proposed fix
- // Wait for search to complete and verify no tiles
- await expect(catalogPage.getCatalogTiles()).not.toBeAttached({ timeout: 10_000 });
-
- // Check if clear filters button appears
- const clearButton = catalogPage.getClearFiltersButton();
- if (await clearButton.count() > 0) {
- await expect(clearButton).toBeVisible();
- await catalogPage.clickClearAllFilters();
- await expect(catalogPage.getSearchInput()).toBeEmpty();
- await expect(async () => {
- const count = await catalogPage.getCatalogTiles().count();
- expect(count).toBeGreaterThan(0);
- }).toPass();
- } else {
- // If clear button doesn't appear, just clear the search manually
- await catalogPage.clearSearchFilter();
- await expect(async () => {
- const count = await catalogPage.getCatalogTiles().count();
- expect(count).toBeGreaterThan(0);
- }).toPass();
- }
+ // Wait for search to complete and verify no tiles
+ await expect(catalogPage.getCatalogTiles()).toHaveCount(0, { timeout: 10_000 });
+
+ await expect(catalogPage.getClearFiltersButton()).toBeVisible();
+ await catalogPage.clickClearAllFilters();
+ await expect(catalogPage.getSearchInput()).toBeEmpty();
+ await expect(catalogPage.getCatalogTiles().first()).toBeVisible();📝 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.
| // Wait for search to complete and verify no tiles | |
| await expect(catalogPage.getCatalogTiles()).not.toBeAttached({ timeout: 10_000 }); | |
| // Check if clear filters button appears | |
| const clearButton = catalogPage.getClearFiltersButton(); | |
| if (await clearButton.count() > 0) { | |
| await expect(clearButton).toBeVisible(); | |
| await catalogPage.clickClearAllFilters(); | |
| await expect(catalogPage.getSearchInput()).toBeEmpty(); | |
| await expect(async () => { | |
| const count = await catalogPage.getCatalogTiles().count(); | |
| expect(count).toBeGreaterThan(0); | |
| }).toPass(); | |
| } else { | |
| // If clear button doesn't appear, just clear the search manually | |
| await catalogPage.clearSearchFilter(); | |
| await expect(async () => { | |
| const count = await catalogPage.getCatalogTiles().count(); | |
| expect(count).toBeGreaterThan(0); | |
| }).toPass(); | |
| } | |
| // Wait for search to complete and verify no tiles | |
| await expect(catalogPage.getCatalogTiles()).toHaveCount(0, { timeout: 10_000 }); | |
| await expect(catalogPage.getClearFiltersButton()).toBeVisible(); | |
| await catalogPage.clickClearAllFilters(); | |
| await expect(catalogPage.getSearchInput()).toBeEmpty(); | |
| await expect(catalogPage.getCatalogTiles().first()).toBeVisible(); |
🤖 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/olm/operator-hub.spec.ts` around lines 77 - 97, Update the
empty-search verification in the operator hub test to use toHaveCount(0) for
catalogPage.getCatalogTiles(), avoiding strict-mode issues with the
multi-element locator. Remove the clearButton count conditional and assert the
clear-filters button is visible directly, then click it and verify the search
input is empty and catalog tiles return.
| // Also clean up any test namespaces that might have conflicting OperatorGroups | ||
| try { | ||
| const namespaces = await k8sClient.listNamespaces(); | ||
| const testNamespaces = namespaces.filter((ns: any) => ns.metadata.name.startsWith('test-')); | ||
|
|
||
| for (const testNs of testNamespaces) { | ||
| const nsName = (testNs as any)?.metadata?.name; | ||
| if (nsName) { | ||
| console.log(`Cleaning up test namespace: ${nsName}`); | ||
| await cleanupOperatorResources(k8sClient, { | ||
| operatorPackageName, | ||
| operandPlural: 'backups', | ||
| namespace: nsName, | ||
| }); | ||
| } | ||
| } | ||
| } catch (error) { | ||
| console.log('Error cleaning up test namespaces:', error.message); | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift
The test- prefix cleanup deletes resources that belong to other running tests.
Line 51 matches every namespace whose name starts with test-. cleanupOperatorResources in frontend/e2e/test-utils/operator-cleanup.ts deletes all OperatorGroups in any namespace with that prefix. generateTestNamespace() gives every spec in this suite the same test- prefix. When Playwright runs specs in parallel, or when a shared cluster holds a user namespace named test-*, this hook deletes Subscriptions and OperatorGroups that another test owns.
Scope the cleanup to namespaces this worker created. Track the created namespaces in the suite, or add a per-run identifier to the generated names and filter on it.
🤖 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/olm/operator-install-single-namespace.spec.ts` around
lines 48 - 66, Scope the namespace cleanup around generateTestNamespace to only
namespaces created by this test worker, rather than every name beginning with
test-. Track generated namespace names in the suite (or include and filter by a
unique per-run identifier), then use that owned set when invoking
cleanupOperatorResources; preserve cleanup of all resources within those owned
namespaces.
| await test.step('Verify operator is NOT installed globally (isolation test)', async () => { | ||
| // This is a key verification that distinguishes single namespace from global installation | ||
| await installedOperatorsPage.navigateToInstalledOperators(); | ||
|
|
||
| // Switch to global namespace and verify operator is not there | ||
| await installedOperatorsPage.selectNamespace(globalNamespace); | ||
|
|
||
| const emptyState = page.getByTestId('console-empty-state'); | ||
| await expect(emptyState.or(page.locator('[data-test="msg-box-title"]'))).toContainText( | ||
| /No Operators found|No results found/, | ||
| ); | ||
| }); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
The isolation check asserts an empty state in a namespace that usually holds operators.
Lines 140-143 require openshift-operators to show "No Operators found" or "No results found". Most clusters already have at least one globally installed operator in openshift-operators, so the empty state never renders and the test fails for a reason unrelated to isolation.
Assert that this specific operator is absent instead. InstalledOperatorsPage.verifyOperatorNotInstalledInNamespace already does that.
🐛 Proposed fix
- await installedOperatorsPage.navigateToInstalledOperators();
-
- // Switch to global namespace and verify operator is not there
- await installedOperatorsPage.selectNamespace(globalNamespace);
-
- const emptyState = page.getByTestId('console-empty-state');
- await expect(emptyState.or(page.locator('[data-test="msg-box-title"]'))).toContainText(
- /No Operators found|No results found/,
- );
+ await installedOperatorsPage.verifyOperatorNotInstalledInNamespace(
+ testOperator.name,
+ globalNamespace,
+ );📝 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('Verify operator is NOT installed globally (isolation test)', async () => { | |
| // This is a key verification that distinguishes single namespace from global installation | |
| await installedOperatorsPage.navigateToInstalledOperators(); | |
| // Switch to global namespace and verify operator is not there | |
| await installedOperatorsPage.selectNamespace(globalNamespace); | |
| const emptyState = page.getByTestId('console-empty-state'); | |
| await expect(emptyState.or(page.locator('[data-test="msg-box-title"]'))).toContainText( | |
| /No Operators found|No results found/, | |
| ); | |
| }); | |
| await test.step('Verify operator is NOT installed globally (isolation test)', async () => { | |
| // This is a key verification that distinguishes single namespace from global installation | |
| await installedOperatorsPage.verifyOperatorNotInstalledInNamespace( | |
| testOperator.name, | |
| globalNamespace, | |
| ); | |
| }); |
🤖 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/olm/operator-install-single-namespace.spec.ts` around
lines 133 - 144, Replace the empty-state assertion in the isolation verification
step with InstalledOperatorsPage.verifyOperatorNotInstalledInNamespace, passing
the operator under test and globalNamespace. Keep the existing navigation and
namespace selection, and assert only that this specific operator is absent from
the global namespace.
| await test.step('Successfully uninstall operator (without operands)', async () => { | ||
| // Navigate back to operator details page to ensure clean state | ||
| await installedOperatorsPage.navigateToOperatorDetails(testOperator.name, testOperator.urlName, testNamespace); | ||
|
|
||
| // Uninstall operator normally (without trying to delete operands since none exist) | ||
| await operatorDetailsPage.uninstallOperator(); | ||
|
|
||
| // Verify operator no longer exists | ||
| await installedOperatorsPage.verifyOperatorNotExists(testOperator.name); | ||
| }); | ||
|
|
||
| await test.step('Verify operand instance is deleted', async () => { | ||
| // Verify operand is deleted via K8s API (should throw 404) | ||
| await expect(async () => { | ||
| await k8sClient.getCustomResource( | ||
| testOperand.group, | ||
| testOperand.version, | ||
| testNamespace, | ||
| 'backups', | ||
| testOperand.exampleName | ||
| ); | ||
| }).rejects.toThrow(); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
fd -t f 'operator-details-page.ts' frontend/e2e | xargs -r rg -n -A 40 'async uninstallOperator'
rg -n 'delete-all-operands|Delete all operand' frontend/packages/operator-lifecycle-manager/src/components/modals/uninstall-operator-modal.tsxRepository: openshift/console
Length of output: 3326
🏁 Script executed:
#!/bin/bash
set -euo pipefail
file="$(fd -t f 'operator-uninstall.spec.ts' frontend/e2e | head -n1)"
echo "FILE=$file"
wc -l "$file"
sed -n '1,160p' "$file" | cat -n
rg -n "uninstallOperator|uninstallOperatorWithOperands|Delete all operand|delete-all-operands|verifyOperatorNotExists|getCustomResource|example-backup|backups" frontend/e2e frontend/packages/operator-lifecycle-manager/src/components/modals/uninstall-operator-modal.tsxRepository: openshift/console
Length of output: 10478
Do not uninstall the operator while expecting the operand to be removed by default.
Lines 80-82 create example-backup, so the test state contradicts the “without operands” step title and “since none exist” comment. operatorDetailsPage.uninstallOperator() only submits the uninstall modal and does not select delete-all-operands; operatorDetailsPage.uninstallOperatorWithOperands() selects it. If the operand may remain after uninstall, use uninstallOperatorWithOperands() in this step or remove the deletion assertion.
🤖 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/olm/operator-uninstall.spec.ts` around lines 116 - 137,
The uninstall flow in the “Successfully uninstall operator (without operands)”
step conflicts with the created example-backup and subsequent deletion
assertion. Update the step to call
operatorDetailsPage.uninstallOperatorWithOperands() so the operand is explicitly
deleted, and revise the step title/comment to reflect that an operand exists and
is removed.
Analysis / Root cause:
Solution description:
Screenshots / screen recording:
Test setup:
Test cases:
Browser conformance:
Additional info:
Reviewers and assignees:
Summary by CodeRabbit
New Features
Bug Fixes
Tests