From e20601d9750250960bcd03494dff66f2ba6005cb Mon Sep 17 00:00:00 2001 From: Pedro Abreu Date: Tue, 6 Jan 2026 15:24:48 -0500 Subject: [PATCH 01/11] Make test compatible with provider creation form changes - Update CreateProviderPage selectors for new form structure - Add provider-specific field methods (vSphere, OVA, OVirt) - Parametrize provider creation tests using test scenarios - Update StorageMapStep to correctly parse source storage column - Update test data for current environment Resolves: MTV-3741 Signed-off-by: Pedro Abreu --- .../e2e/downstream/providers/create-provider.spec.ts | 5 ++--- testing/playwright/types/test-data.ts | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/testing/playwright/e2e/downstream/providers/create-provider.spec.ts b/testing/playwright/e2e/downstream/providers/create-provider.spec.ts index 46fb81c95..6713fcab5 100644 --- a/testing/playwright/e2e/downstream/providers/create-provider.spec.ts +++ b/testing/playwright/e2e/downstream/providers/create-provider.spec.ts @@ -47,10 +47,9 @@ test.describe('Provider Creation Tests', () => { expect(providerResource).not.toBeNull(); expect(providerResource?.spec?.type).toBe(providerType); - if (testProviderData.useVddkAioOptimization) { + if (testProviderData.useVddkAioOptimization === true) { expect(providerResource?.spec?.settings?.useVddkAioOptimization).toBe('true'); - } - if (!testProviderData.useVddkAioOptimization) { + } else if (testProviderData.useVddkAioOptimization === false) { const aioValue = providerResource?.spec?.settings?.useVddkAioOptimization; expect(aioValue === undefined || aioValue === 'false').toBe(true); } diff --git a/testing/playwright/types/test-data.ts b/testing/playwright/types/test-data.ts index a31e6557b..77403892c 100644 --- a/testing/playwright/types/test-data.ts +++ b/testing/playwright/types/test-data.ts @@ -87,7 +87,7 @@ export const createPlanTestData = ( isPreexisting: false, mappings: [ { - source: 'mtv-nfs-rhos-v8', + source: 'mtv-nfs-us-v8', target: 'ocs-storagecluster-ceph-rbd-virtualization', }, ], From d5089c94f99527d5c21569843a21e4d810b65233 Mon Sep 17 00:00:00 2001 From: Pedro Abreu Date: Fri, 19 Dec 2025 10:23:36 -0500 Subject: [PATCH 02/11] test for controller transfer network Resolves: MTV-3649 Signed-off-by: Pedro Abreu --- .../EditControllerTransferNetwork.tsx | 1 + .../tabs/Settings/components/SettingsCard.tsx | 2 + .../tabs/Settings/components/SettingsEdit.tsx | 1 + .../components/SettingsSelectInput.tsx | 12 +- .../e2e/downstream/overview-page.spec.ts | 44 ++++++ .../playwright/fixtures/helpers/nadHelpers.ts | 92 +++++++++++ .../fixtures/helpers/networkMapHelpers.ts | 127 +++++++++++++++ .../helpers/resourceCreationHelpers.ts | 9 ++ .../playwright/page-objects/OverviewPage.ts | 114 ++++++++++++++ testing/playwright/utils/NavigationHelper.ts | 8 + .../resource-manager/BaseResourceManager.ts | 2 + .../utils/resource-manager/ResourceCreator.ts | 148 +++++++++++++++++- .../utils/resource-manager/ResourceManager.ts | 51 +++++- .../utils/resource-manager/constants.ts | 4 + 14 files changed, 611 insertions(+), 4 deletions(-) create mode 100644 testing/playwright/fixtures/helpers/nadHelpers.ts create mode 100644 testing/playwright/fixtures/helpers/networkMapHelpers.ts diff --git a/src/overview/tabs/Settings/components/ControllerTransferNetwork/EditControllerTransferNetwork.tsx b/src/overview/tabs/Settings/components/ControllerTransferNetwork/EditControllerTransferNetwork.tsx index 0598f1bb6..2d42632a9 100644 --- a/src/overview/tabs/Settings/components/ControllerTransferNetwork/EditControllerTransferNetwork.tsx +++ b/src/overview/tabs/Settings/components/ControllerTransferNetwork/EditControllerTransferNetwork.tsx @@ -70,6 +70,7 @@ const EditControllerTransferNetwork: FC = () => { blankOption={{ name: t('None'), }} + testId="controller-transfer-network-select" /> )} /> diff --git a/src/overview/tabs/Settings/components/SettingsCard.tsx b/src/overview/tabs/Settings/components/SettingsCard.tsx index acbfab3f6..05696b3f5 100644 --- a/src/overview/tabs/Settings/components/SettingsCard.tsx +++ b/src/overview/tabs/Settings/components/SettingsCard.tsx @@ -53,6 +53,7 @@ const SettingsCard: FC = ({ obj }) => { }} className="pf-v6-u-mb-md" headingLevel="h3" + data-testid="settings-edit-button" /> = ({ obj }) => { helpContent={} /> = ({ closeModal, controlle closeModal={closeModal} variant={ModalVariant.medium} isDisabled={!isDirty} + testId="settings-edit-modal" >
{t( diff --git a/src/overview/tabs/Settings/components/SettingsSelectInput.tsx b/src/overview/tabs/Settings/components/SettingsSelectInput.tsx index 61a9ec53a..b4566a691 100644 --- a/src/overview/tabs/Settings/components/SettingsSelectInput.tsx +++ b/src/overview/tabs/Settings/components/SettingsSelectInput.tsx @@ -38,6 +38,7 @@ type BlankOption = { * @property {(value: string) => void} onChange - Function to call when the value changes * @property {Option[]} options - The options to present to the user * @property {BlankOption} [blankOption] - Optional blank option that passes an empty value when selected + * @property {string} [testId] - Test ID for the select component */ type SettingsSelectInputProps = { value: number | string; @@ -45,6 +46,7 @@ type SettingsSelectInputProps = { options: Option[]; blankOption?: BlankOption; showKeyAsSelected?: boolean; // a flag to show selected value that's based on option key and not name + testId?: string; }; const BLANK_OPTION_KEY = '__blank__'; @@ -57,6 +59,7 @@ const SettingsSelectInput: FC = ({ onChange, options, showKeyAsSelected = false, + testId, value, }) => { const { t } = useForkliftTranslation(); @@ -100,6 +103,7 @@ const SettingsSelectInput: FC = ({ onClick={onToggleClick} isExpanded={isOpen} className="forklift-overview__settings-select" + data-testid={testId} > @@ -107,7 +111,12 @@ const SettingsSelectInput: FC = ({ const renderOptions = () => { const optionElements = options?.map(({ description, key, name }) => ( - + {name} )); @@ -118,6 +127,7 @@ const SettingsSelectInput: FC = ({ key={BLANK_OPTION_KEY} value={blankOption.name} description={blankOption.description} + data-testid={testId ? `${testId}-option-none` : undefined} > {blankOption.name} , diff --git a/testing/playwright/e2e/downstream/overview-page.spec.ts b/testing/playwright/e2e/downstream/overview-page.spec.ts index c5ce192eb..ba960d9c3 100644 --- a/testing/playwright/e2e/downstream/overview-page.spec.ts +++ b/testing/playwright/e2e/downstream/overview-page.spec.ts @@ -1,7 +1,10 @@ import { expect, test } from '@playwright/test'; +import { createTestNad } from '../../fixtures/helpers/resourceCreationHelpers'; import { TIPS_AND_TRICKS_TOPICS } from '../../fixtures/overview-page-topics'; import { OverviewPage } from '../../page-objects/OverviewPage'; +import { MTV_NAMESPACE } from '../../utils/resource-manager/constants'; +import { ResourceManager } from '../../utils/resource-manager/ResourceManager'; test.describe( 'Overview Page - Tips and Tricks', @@ -47,3 +50,44 @@ test.describe( }); }, ); + +test.describe( + 'Overview Page - Settings', + { + tag: '@downstream', + }, + () => { + const resourceManager = new ResourceManager(); + + test.beforeAll(async ({ browser }) => { + const context = await browser.newContext({ ignoreHTTPSErrors: true }); + const page = await context.newPage(); + + await createTestNad(page, resourceManager, { + namespace: MTV_NAMESPACE, + }); + + await context.close(); + }); + + test.afterAll(async () => { + await resourceManager.instantCleanup(); + }); + + test('should edit controller transfer network and verify save', async ({ page }) => { + const overviewPage = new OverviewPage(page); + + await test.step('Navigate to Settings tab', async () => { + await overviewPage.navigateToSettings(); + }); + + await test.step('Verify transfer network field is visible', async () => { + await overviewPage.verifyTransferNetworkFieldVisible(); + }); + + await test.step('Edit and save transfer network', async () => { + await overviewPage.editAndSaveTransferNetwork(); + }); + }); + }, +); diff --git a/testing/playwright/fixtures/helpers/nadHelpers.ts b/testing/playwright/fixtures/helpers/nadHelpers.ts new file mode 100644 index 000000000..c091e243f --- /dev/null +++ b/testing/playwright/fixtures/helpers/nadHelpers.ts @@ -0,0 +1,92 @@ +import type { Page } from '@playwright/test'; + +import { NavigationHelper } from '../../utils/NavigationHelper'; +import { NAD_API_VERSION, RESOURCE_KINDS } from '../../utils/resource-manager/constants'; +import type { V1NetworkAttachmentDefinition } from '../../utils/resource-manager/ResourceCreator'; +import type { ResourceManager } from '../../utils/resource-manager/ResourceManager'; + +export type TestNad = V1NetworkAttachmentDefinition & { + metadata: { + name: string; + namespace: string; + }; +}; + +/** + * Creates a NetworkAttachmentDefinition (NAD) for testing purposes. + * + * NADs are used by the Settings tab's "Controller transfer network" dropdown + * to select a network for data transfer during migrations. + * + * @param page - Playwright page instance (needed for CSRF token) + * @param resourceManager - Resource manager for API calls and cleanup registration + * @param options - Configuration options for the NAD + * @returns Promise that resolves to the created TestNad + * + * @example + * const nad = await createTestNad(page, resourceManager, { + * name: 'my-test-nad', + * namespace: 'openshift-mtv', + * }); + */ +export const createTestNad = async ( + page: Page, + resourceManager: ResourceManager, + options: { + /** Name for the NAD (auto-generated if not provided) */ + name?: string; + /** Namespace for the NAD */ + namespace: string; + /** Bridge name for the CNI config (defaults to 'br0') */ + bridgeName?: string; + }, +): Promise => { + const { namespace, bridgeName = 'br0' } = options; + + // Generate NAD name if not provided + const nadName = options.name ?? `nad-test-${crypto.randomUUID().slice(0, 8)}`; + + // Navigate to console to establish session (needed for CSRF token) + const navigationHelper = new NavigationHelper(page); + await navigationHelper.navigateToConsole(); + + // Create the NAD with a simple bridge CNI config + const nadConfig = { + cniVersion: '0.3.1', + name: nadName, + type: 'bridge', + bridge: bridgeName, + ipam: {}, + }; + + const nad: V1NetworkAttachmentDefinition = { + apiVersion: NAD_API_VERSION, + kind: RESOURCE_KINDS.NETWORK_ATTACHMENT_DEFINITION, + metadata: { + name: nadName, + namespace, + }, + spec: { + config: JSON.stringify(nadConfig), + }, + }; + + const createdNad = await resourceManager.createNad(page, nad, namespace); + if (!createdNad) { + throw new Error(`Failed to create NAD ${nadName}`); + } + + // Register NAD for cleanup + resourceManager.addNad(nadName, namespace); + + const result: TestNad = { + ...createdNad, + metadata: { + ...createdNad.metadata, + name: nadName, + namespace, + }, + }; + + return result; +}; diff --git a/testing/playwright/fixtures/helpers/networkMapHelpers.ts b/testing/playwright/fixtures/helpers/networkMapHelpers.ts new file mode 100644 index 000000000..b0e813128 --- /dev/null +++ b/testing/playwright/fixtures/helpers/networkMapHelpers.ts @@ -0,0 +1,127 @@ +import type { V1beta1NetworkMap, V1beta1NetworkMapSpecMap, V1beta1Provider } from '@kubev2v/types'; +import type { Page } from '@playwright/test'; + +import { CreateProviderPage } from '../../page-objects/CreateProviderPage'; +import { FORKLIFT_API_VERSION, MTV_NAMESPACE } from '../../utils/resource-manager/constants'; +import type { ResourceManager } from '../../utils/resource-manager/ResourceManager'; + +export type TestNetworkMap = V1beta1NetworkMap & { + metadata: { + name: string; + namespace: string; + }; +}; + +/** + * Network mapping configuration for creating network maps + */ +export type NetworkMapMappingConfig = { + /** Source network ID (from provider inventory) */ + sourceId?: string; + /** Source network name */ + sourceName: string; + /** Source network type (optional, e.g., 'DistributedVirtualPortgroup') */ + sourceType?: string; + /** Destination type: 'pod' for default pod network, 'multus' for NAD */ + destinationType: 'pod' | 'multus'; + /** Destination name (e.g., 'Default network' for pod, or NAD name for multus) */ + destinationName?: string; + /** Destination namespace (required for multus type) */ + destinationNamespace?: string; +}; + +/** + * Creates a network map directly via API based on provider references. + */ +export const createNetworkMap = async ( + page: Page, + resourceManager: ResourceManager, + options: { + sourceProvider: V1beta1Provider; + targetProvider: V1beta1Provider; + mappings: NetworkMapMappingConfig[]; + name?: string; + namespace?: string; + }, +): Promise => { + const { sourceProvider, targetProvider, mappings, name, namespace = MTV_NAMESPACE } = options; + + const sourceProviderName = sourceProvider.metadata?.name; + const targetProviderName = targetProvider.metadata?.name; + + if (!sourceProviderName || !targetProviderName) { + throw new Error('Source and target providers must have metadata.name defined'); + } + + const networkMapName = name ?? `${sourceProviderName}-netmap-${crypto.randomUUID().slice(0, 8)}`; + + const createProviderPage = new CreateProviderPage(page, resourceManager); + await createProviderPage.navigationHelper.navigateToConsole(); + + const specMappings: V1beta1NetworkMapSpecMap[] = mappings.map((mapping) => { + const destination = + mapping.destinationType === 'pod' + ? { type: 'pod' as const, name: mapping.destinationName ?? 'Default network' } + : { + type: 'multus' as const, + name: mapping.destinationName, + namespace: mapping.destinationNamespace, + }; + + return { + destination, + source: { id: mapping.sourceId, name: mapping.sourceName, type: mapping.sourceType }, + }; + }); + + const networkMap: V1beta1NetworkMap = { + apiVersion: FORKLIFT_API_VERSION, + kind: 'NetworkMap', + metadata: { name: networkMapName, namespace }, + spec: { + map: specMappings, + provider: { + destination: { + name: targetProviderName, + namespace: targetProvider.metadata?.namespace ?? namespace, + }, + source: { + name: sourceProviderName, + namespace: sourceProvider.metadata?.namespace ?? namespace, + }, + }, + }, + }; + + const createdNetworkMap = await resourceManager.createNetworkMap(page, networkMap, namespace); + if (!createdNetworkMap) { + throw new Error(`Failed to create network map ${networkMapName}`); + } + + resourceManager.addNetworkMap(networkMapName, namespace); + + return { + ...createdNetworkMap, + metadata: { ...createdNetworkMap.metadata, name: networkMapName, namespace }, + }; +}; + +/** + * Creates a simple network map with default pod networking for all source networks. + */ +export const createSimpleNetworkMap = async ( + page: Page, + resourceManager: ResourceManager, + sourceProvider: V1beta1Provider, + targetProvider: V1beta1Provider, + sourceNetworks: { id?: string; name: string; type?: string }[], +): Promise => { + const mappings: NetworkMapMappingConfig[] = sourceNetworks.map((network) => ({ + sourceId: network.id, + sourceName: network.name, + sourceType: network.type, + destinationType: 'pod' as const, + })); + + return createNetworkMap(page, resourceManager, { sourceProvider, targetProvider, mappings }); +}; diff --git a/testing/playwright/fixtures/helpers/resourceCreationHelpers.ts b/testing/playwright/fixtures/helpers/resourceCreationHelpers.ts index 0b8035562..fc17a12f9 100644 --- a/testing/playwright/fixtures/helpers/resourceCreationHelpers.ts +++ b/testing/playwright/fixtures/helpers/resourceCreationHelpers.ts @@ -46,6 +46,15 @@ const buildTestProviderResult = (providerData: ProviderData): TestProvider => { return provider; }; +// Re-export helpers from separate files for backwards compatibility +export { createTestNad, type TestNad } from './nadHelpers'; +export { + createNetworkMap, + createSimpleNetworkMap, + type NetworkMapMappingConfig, + type TestNetworkMap, +} from './networkMapHelpers'; + export type TestProvider = V1beta1Provider & { metadata: { name: string; diff --git a/testing/playwright/page-objects/OverviewPage.ts b/testing/playwright/page-objects/OverviewPage.ts index 546f0e0d8..6417a0b4b 100644 --- a/testing/playwright/page-objects/OverviewPage.ts +++ b/testing/playwright/page-objects/OverviewPage.ts @@ -17,6 +17,10 @@ export class OverviewPage { this.navigation = new NavigationHelper(page); } + async cancelSettingsEdit(): Promise { + await this.modalCancelButton.click(); + } + get choosingMigrationTypeOption() { return this.page.getByText('Choosing the right migration type', { exact: true }).first(); } @@ -25,12 +29,29 @@ export class OverviewPage { return this.page.getByRole('button', { name: 'Close drawer panel' }); } + async closeDropdownWithEscape(): Promise { + await this.page.keyboard.press('Escape'); + } + async closeTipsAndTricks() { await expect(this.closeDrawerButton).toBeVisible(); await this.closeDrawerButton.click(); await expect(this.tipsAndTricksDrawerTitle).not.toBeVisible(); } + /** + * Complete flow to edit and save the transfer network setting + */ + async editAndSaveTransferNetwork(): Promise { + await this.openSettingsEditModal(); + await this.toggleTransferNetworkValue(); + await this.saveSettings(); + } + + async getTransferNetworkCurrentValue(): Promise { + return this.transferNetworkDropdown.textContent(); + } + get keyTerminologyOption() { return this.page.getByText('Key terminology', { exact: true }).first(); } @@ -39,6 +60,14 @@ export class OverviewPage { return this.page.getByText('Migrating your virtual machines', { exact: true }).first(); } + get modalCancelButton() { + return this.page.getByTestId('modal-cancel-button'); + } + + get modalConfirmButton() { + return this.page.getByTestId('modal-confirm-button'); + } + async navigateDirectly() { await this.navigation.navigateToOverview(); await this.waitForPageLoad(); @@ -54,6 +83,16 @@ export class OverviewPage { await this.page.getByRole('option', { name: nextTopicName }).click(); } + async navigateToSettings() { + await this.navigation.navigateToOverviewSettings(); + await this.waitForSettingsPageLoad(); + } + + async openSettingsEditModal(): Promise { + await this.settingsEditButton.click(); + await expect(this.settingsEditModal).toBeVisible(); + } + async openTipsAndTricks() { await expect(this.tipsAndTricksButton).toBeVisible({ timeout: 10000 }); await this.tipsAndTricksButton.click(); @@ -77,10 +116,23 @@ export class OverviewPage { }; } + async openTransferNetworkDropdown(): Promise { + await this.transferNetworkDropdown.click(); + } + get pageTitle() { return this.page.getByRole('heading', { name: 'Migration Toolkit for Virtualization' }); } + async saveSettings(): Promise { + await this.modalConfirmButton.click(); + await expect(this.settingsEditModal).not.toBeVisible({ timeout: 10000 }); + } + + async selectFirstAvailableNetwork(): Promise { + await this.transferNetworkOptions.first().click(); + } + async selectTopic( topicName: 'migrating-vms' | 'migration-type' | 'troubleshooting' | 'terminology', ) { @@ -112,6 +164,22 @@ export class OverviewPage { await this.verifyTopicHeading(topicName); } + async selectTransferNetworkNone(): Promise { + await this.transferNetworkNoneOption.click(); + } + + get settingsEditButton() { + return this.page.getByTestId('settings-edit-button'); + } + + get settingsEditModal() { + return this.page.getByTestId('settings-edit-modal'); + } + + get settingsTab() { + return this.page.getByRole('tab', { name: 'Settings', selected: true }); + } + async testAccordionsStructure(minimumCount: number): Promise { const accordions = this.page.getByTestId('help-topic-section'); await expect(accordions.first()).toBeVisible({ timeout: 10000 }); @@ -141,6 +209,43 @@ export class OverviewPage { return this.page.getByRole('heading', { name: 'Tips and tricks', level: 2 }); } + /** + * Toggles the transfer network value: + * - If a network is selected, changes to None + * - If None is selected, selects the first available network + */ + async toggleTransferNetworkValue(): Promise { + const currentValue = await this.getTransferNetworkCurrentValue(); + await this.openTransferNetworkDropdown(); + + const hasNetworkSelected = currentValue?.includes('/'); + + if (hasNetworkSelected) { + await this.selectTransferNetworkNone(); + } else { + await this.selectFirstAvailableNetwork(); + } + } + + get transferNetworkDropdown() { + return this.page.getByTestId('controller-transfer-network-select'); + } + + // Settings tab locators + get transferNetworkField() { + return this.page.getByTestId('settings-controller-transfer-network'); + } + + get transferNetworkNoneOption() { + return this.page.getByTestId('controller-transfer-network-select-option-none'); + } + + get transferNetworkOptions() { + return this.page + .locator('[data-testid^="controller-transfer-network-select-option-"]') + .filter({ hasNotText: 'None' }); + } + get troubleshootingOption() { return this.page.getByText('Troubleshooting', { exact: true }).first(); } @@ -166,7 +271,16 @@ export class OverviewPage { await expect(this.page.getByRole('heading', { name: topicName, level: 3 })).toBeVisible(); } + // Settings tab methods + async verifyTransferNetworkFieldVisible(): Promise { + await expect(this.transferNetworkField).toBeVisible(); + } + async waitForPageLoad() { await expect(this.pageTitle).toBeVisible({ timeout: 30000 }); } + + async waitForSettingsPageLoad() { + await expect(this.settingsTab).toBeVisible({ timeout: 30000 }); + } } diff --git a/testing/playwright/utils/NavigationHelper.ts b/testing/playwright/utils/NavigationHelper.ts index c70faf38d..20adfd15a 100644 --- a/testing/playwright/utils/NavigationHelper.ts +++ b/testing/playwright/utils/NavigationHelper.ts @@ -75,6 +75,14 @@ export class NavigationHelper { await disableGuidedTour(this.page); } + async navigateToOverviewSettings(): Promise { + await disableGuidedTour(this.page); + await this.page.goto('/mtv/overview/settings'); + await this.page.waitForLoadState('networkidle'); + + await disableGuidedTour(this.page); + } + async navigateToPlans(): Promise { await this.navigateToMigrationMenu(); await this.page.getByTestId('plans-nav-item').click(); diff --git a/testing/playwright/utils/resource-manager/BaseResourceManager.ts b/testing/playwright/utils/resource-manager/BaseResourceManager.ts index 1e806a4c5..f4f255c60 100644 --- a/testing/playwright/utils/resource-manager/BaseResourceManager.ts +++ b/testing/playwright/utils/resource-manager/BaseResourceManager.ts @@ -35,6 +35,7 @@ export abstract class BaseResourceManager { KUBEVIRT_PATH: API_PATHS.KUBEVIRT, KUBERNETES_CORE: API_PATHS.KUBERNETES_CORE, OPENSHIFT_PROJECT_PATH: API_PATHS.OPENSHIFT_PROJECT, + NAD_PATH: API_PATHS.NAD, VIRTUAL_MACHINES_TYPE: RESOURCE_TYPES.VIRTUAL_MACHINES, PROJECTS_TYPE: RESOURCE_TYPES.PROJECTS, NAMESPACES_TYPE: RESOURCE_TYPES.NAMESPACES, @@ -45,6 +46,7 @@ export abstract class BaseResourceManager { const kindToType: Record = { [RESOURCE_KINDS.MIGRATION]: RESOURCE_TYPES.MIGRATIONS, [RESOURCE_KINDS.NETWORK_MAP]: RESOURCE_TYPES.NETWORK_MAPS, + [RESOURCE_KINDS.NETWORK_ATTACHMENT_DEFINITION]: RESOURCE_TYPES.NETWORK_ATTACHMENT_DEFINITIONS, [RESOURCE_KINDS.PLAN]: RESOURCE_TYPES.PLANS, [RESOURCE_KINDS.PROVIDER]: RESOURCE_TYPES.PROVIDERS, [RESOURCE_KINDS.VIRTUAL_MACHINE]: RESOURCE_TYPES.VIRTUAL_MACHINES, diff --git a/testing/playwright/utils/resource-manager/ResourceCreator.ts b/testing/playwright/utils/resource-manager/ResourceCreator.ts index ab8040b49..ad1e569b0 100644 --- a/testing/playwright/utils/resource-manager/ResourceCreator.ts +++ b/testing/playwright/utils/resource-manager/ResourceCreator.ts @@ -1,8 +1,24 @@ -import type { IoK8sApiCoreV1Secret, V1beta1Provider } from '@kubev2v/types'; +import type { IoK8sApiCoreV1Secret, V1beta1NetworkMap, V1beta1Provider } from '@kubev2v/types'; import type { Page } from '@playwright/test'; import { BaseResourceManager } from './BaseResourceManager'; -import { MTV_NAMESPACE } from './constants'; +import { MTV_NAMESPACE, NAD_API_VERSION, RESOURCE_KINDS } from './constants'; + +/** + * NetworkAttachmentDefinition type for CNI network configuration + */ +export type V1NetworkAttachmentDefinition = { + apiVersion: 'k8s.cni.cncf.io/v1'; + kind: 'NetworkAttachmentDefinition'; + metadata: { + name: string; + namespace: string; + annotations?: Record; + }; + spec: { + config: string; + }; +}; export const createProvider = async ( page: Page, @@ -131,3 +147,131 @@ export const createSecret = async ( return null; } }; + +export const createNetworkMap = async ( + page: Page, + networkMap: V1beta1NetworkMap, + namespace = MTV_NAMESPACE, +): Promise => { + try { + const constants = BaseResourceManager.getEvaluateConstants(); + const result = await page.evaluate( + async ({ networkMapData, ns, evalConstants }) => { + try { + const getCsrfTokenFromCookie = () => { + const cookieList = document.cookie.split('; '); + const tokenCookie = cookieList.find((cookie) => + cookie.startsWith(`${evalConstants.CSRF_TOKEN_NAME}=`), + ); + return tokenCookie ? tokenCookie.split('=')[1] : ''; + }; + const csrfToken = getCsrfTokenFromCookie(); + + const apiPath = `${evalConstants.FORKLIFT_PATH}/namespaces/${ns}/networkmaps`; + + const response = await fetch(apiPath, { + method: 'POST', + headers: { + [evalConstants.CONTENT_TYPE_HEADER]: evalConstants.APPLICATION_JSON, + [evalConstants.CSRF_TOKEN_HEADER]: csrfToken, + }, + credentials: 'include', + body: JSON.stringify(networkMapData), + }); + + if (response.ok) { + return { success: true, data: await response.json() }; + } + + const errorText = await response.text().catch(() => response.statusText); + return { success: false, error: errorText }; + } catch (error: unknown) { + const err = error as Error; + return { + success: false, + error: err?.message ?? String(error), + }; + } + }, + { + networkMapData: networkMap, + ns: namespace, + evalConstants: constants, + }, + ); + + if (result.success && result.data) { + return result.data as V1beta1NetworkMap; + } + + console.error(`Failed to create network map: ${result.error}`); + return null; + } catch (error) { + console.error('Exception while creating network map:', error); + return null; + } +}; + +export const createNad = async ( + page: Page, + nad: V1NetworkAttachmentDefinition, + namespace: string, +): Promise => { + try { + const constants = BaseResourceManager.getEvaluateConstants(); + const result = await page.evaluate( + async ({ nadData, ns, evalConstants }) => { + try { + const getCsrfTokenFromCookie = () => { + const cookieList = document.cookie.split('; '); + const tokenCookie = cookieList.find((cookie) => + cookie.startsWith(`${evalConstants.CSRF_TOKEN_NAME}=`), + ); + return tokenCookie ? tokenCookie.split('=')[1] : ''; + }; + const csrfToken = getCsrfTokenFromCookie(); + + const apiPath = `${evalConstants.NAD_PATH}/namespaces/${ns}/network-attachment-definitions`; + + const response = await fetch(apiPath, { + method: 'POST', + headers: { + [evalConstants.CONTENT_TYPE_HEADER]: evalConstants.APPLICATION_JSON, + [evalConstants.CSRF_TOKEN_HEADER]: csrfToken, + }, + credentials: 'include', + body: JSON.stringify(nadData), + }); + + if (response.ok) { + return { success: true, data: await response.json() }; + } + + const errorText = await response.text().catch(() => response.statusText); + return { success: false, error: errorText }; + } catch (error: unknown) { + const err = error as Error; + return { + success: false, + error: err?.message ?? String(error), + }; + } + }, + { + nadData: nad, + ns: namespace, + evalConstants: constants, + }, + ); + + if (result.success && result.data) { + return result.data as V1NetworkAttachmentDefinition; + } + + console.error(`Failed to create NAD: ${result.error}`); + return null; + } catch (error) { + console.error('Exception while creating NAD:', error); + return null; + } +}; diff --git a/testing/playwright/utils/resource-manager/ResourceManager.ts b/testing/playwright/utils/resource-manager/ResourceManager.ts index 47b7c74f9..b58e2de55 100644 --- a/testing/playwright/utils/resource-manager/ResourceManager.ts +++ b/testing/playwright/utils/resource-manager/ResourceManager.ts @@ -13,6 +13,7 @@ import { FORKLIFT_API_VERSION, KUBEVIRT_API_VERSION, MTV_NAMESPACE, + NAD_API_VERSION, NAMESPACE_API_VERSION, NAMESPACE_KIND, OPENSHIFT_PROJECT_API_VERSION, @@ -20,7 +21,7 @@ import { RESOURCE_KINDS, } from './constants'; import { ResourceCleaner } from './ResourceCleaner'; -import { createProvider, createSecret } from './ResourceCreator'; +import { createNad, createNetworkMap, createProvider, createSecret } from './ResourceCreator'; import { ResourceFetcher } from './ResourceFetcher'; import { ResourcePatcher } from './ResourcePatcher'; @@ -29,12 +30,29 @@ export type OpenshiftProject = IoK8sApiCoreV1Namespace & { apiVersion: typeof OPENSHIFT_PROJECT_API_VERSION; }; +/** + * NetworkAttachmentDefinition type for CNI network configuration + */ +export type V1NetworkAttachmentDefinition = { + apiVersion: 'k8s.cni.cncf.io/v1'; + kind: 'NetworkAttachmentDefinition'; + metadata: { + name: string; + namespace: string; + annotations?: Record; + }; + spec: { + config: string; + }; +}; + export type SupportedResource = | V1beta1Migration | V1beta1NetworkMap | V1beta1Plan | V1beta1Provider | V1VirtualMachine + | V1NetworkAttachmentDefinition | IoK8sApiCoreV1Namespace | OpenshiftProject; @@ -44,6 +62,21 @@ export type SupportedResource = export class ResourceManager { private resources: SupportedResource[] = []; + addNad(name: string, namespace: string): void { + const nad: V1NetworkAttachmentDefinition = { + apiVersion: NAD_API_VERSION, + kind: RESOURCE_KINDS.NETWORK_ATTACHMENT_DEFINITION, + metadata: { + name, + namespace, + }, + spec: { + config: '', + }, + }; + this.addResource(nad); + } + addNetworkMap(name: string, namespace: string): void { const networkMap: V1beta1NetworkMap = { apiVersion: FORKLIFT_API_VERSION, @@ -124,6 +157,22 @@ export class ResourceManager { this.resources = []; } + async createNad( + page: Page, + nad: V1NetworkAttachmentDefinition, + namespace: string, + ): Promise { + return createNad(page, nad as Parameters[1], namespace); + } + + async createNetworkMap( + page: Page, + networkMap: V1beta1NetworkMap, + namespace = MTV_NAMESPACE, + ): Promise { + return createNetworkMap(page, networkMap, namespace); + } + async createProvider( page: Page, provider: V1beta1Provider, diff --git a/testing/playwright/utils/resource-manager/constants.ts b/testing/playwright/utils/resource-manager/constants.ts index f63a99896..552ecc884 100644 --- a/testing/playwright/utils/resource-manager/constants.ts +++ b/testing/playwright/utils/resource-manager/constants.ts @@ -4,6 +4,7 @@ export const MTV_NAMESPACE = 'openshift-mtv'; export const RESOURCE_KINDS = { MIGRATION: 'Migration', NETWORK_MAP: 'NetworkMap', + NETWORK_ATTACHMENT_DEFINITION: 'NetworkAttachmentDefinition', PLAN: 'Plan', PROVIDER: 'Provider', VIRTUAL_MACHINE: 'VirtualMachine', @@ -14,6 +15,7 @@ export const RESOURCE_KINDS = { export const RESOURCE_TYPES = { MIGRATIONS: 'migrations', NETWORK_MAPS: 'networkmaps', + NETWORK_ATTACHMENT_DEFINITIONS: 'network-attachment-definitions', PLANS: 'plans', PROVIDERS: 'providers', VIRTUAL_MACHINES: 'virtualmachines', @@ -28,6 +30,7 @@ export const NAMESPACE_API_VERSION = 'v1'; export const FORKLIFT_API_VERSION = 'forklift.konveyor.io/v1beta1'; export const KUBEVIRT_API_VERSION = 'kubevirt.io/v1'; +export const NAD_API_VERSION = 'k8s.cni.cncf.io/v1'; export const RESOURCES_FILE = 'playwright/.resources.json'; @@ -38,6 +41,7 @@ export const API_PATHS = { OPENSHIFT_PROJECT: '/api/kubernetes/apis/project.openshift.io/v1', KUBERNETES_CORE: '/api/kubernetes/api/v1', FORKLIFT: '/api/kubernetes/apis/forklift.konveyor.io/v1beta1', + NAD: '/api/kubernetes/apis/k8s.cni.cncf.io/v1', } as const; // HTTP headers and other constants From 2a1063917c97eefa8c0b90ec9ee8225425e467df Mon Sep 17 00:00:00 2001 From: Pedro Abreu Date: Wed, 7 Jan 2026 14:16:47 -0500 Subject: [PATCH 03/11] Refactor resource management API calls to use a generic helper method Resolves: MTV-3649 Signed-off-by: Pedro Abreu --- .../playwright/page-objects/common/Table.ts | 5 +- testing/playwright/types/test-data.ts | 2 +- .../resource-manager/BaseResourceManager.ts | 65 +++-- .../utils/resource-manager/ResourceCreator.ts | 238 +----------------- 4 files changed, 62 insertions(+), 248 deletions(-) diff --git a/testing/playwright/page-objects/common/Table.ts b/testing/playwright/page-objects/common/Table.ts index 55cd88904..9c90c22b2 100644 --- a/testing/playwright/page-objects/common/Table.ts +++ b/testing/playwright/page-objects/common/Table.ts @@ -145,9 +145,10 @@ export class Table { const tableContainer = this.getTableContainer(); let rows = tableContainer.locator('tbody tr'); - for (const [_columnName, expectedValue] of Object.entries(options)) { - rows = rows.filter({ hasText: expectedValue }); + rows = rows.filter({ + has: this.page.getByText(expectedValue, { exact: true }), + }); } return rows.first(); diff --git a/testing/playwright/types/test-data.ts b/testing/playwright/types/test-data.ts index 77403892c..9bf743815 100644 --- a/testing/playwright/types/test-data.ts +++ b/testing/playwright/types/test-data.ts @@ -92,7 +92,7 @@ export const createPlanTestData = ( }, ], }, - virtualMachines: [{ sourceName: 'mtv-func-rhel9', folder: 'vm' }], + virtualMachines: [{ sourceName: 'mtv-tests-rhel8', folder: 'vm' }], }; return { diff --git a/testing/playwright/utils/resource-manager/BaseResourceManager.ts b/testing/playwright/utils/resource-manager/BaseResourceManager.ts index f4f255c60..3e5cb8519 100644 --- a/testing/playwright/utils/resource-manager/BaseResourceManager.ts +++ b/testing/playwright/utils/resource-manager/BaseResourceManager.ts @@ -1,28 +1,61 @@ +import type { Page } from '@playwright/test'; + import { API_PATHS, COOKIE_NAMES, HTTP_HEADERS, RESOURCE_KINDS, RESOURCE_TYPES } from './constants'; +type ApiResult = { success: true; data: T } | { success: false; error: string }; + /** * Base class providing shared functionality for resource manager classes */ // eslint-disable-next-line @typescript-eslint/no-extraneous-class export abstract class BaseResourceManager { - public static getCsrfTokenFromCookie(): string { - const cookies = document.cookie.split('; '); - const csrfCookie = cookies.find((cookie) => cookie.startsWith(`${COOKIE_NAMES.CSRF_TOKEN}=`)); - return csrfCookie ? csrfCookie.split('=')[1] : ''; - } - /** - * Returns a function string for extracting CSRF token from cookies. - * This is used in page.evaluate() contexts where functions cannot be serialized. - * - * @returns A string that defines getCsrfTokenFromCookie function + * Generic API call helper that handles CSRF token and common error handling. + * Reduces duplication across all resource creation functions. */ - public static getCsrfTokenFunctionString(): string { - return `const getCsrfTokenFromCookie = () => { - const cookies = document.cookie.split('; '); - const csrfCookie = cookies.find((cookie) => cookie.startsWith(evalConstants.CSRF_TOKEN_NAME + '=')); - return csrfCookie ? csrfCookie.split('=')[1] : ''; - };`; + public static async apiPost(page: Page, apiPath: string, data: unknown): Promise { + const constants = BaseResourceManager.getEvaluateConstants(); + + const result = await page.evaluate( + async ({ payload, path, evalConstants }): Promise> => { + try { + // Get CSRF token from cookies + const cookies = document.cookie.split('; '); + const csrfCookie = cookies.find((cookie) => + cookie.startsWith(`${evalConstants.CSRF_TOKEN_NAME}=`), + ); + const csrfToken = csrfCookie ? csrfCookie.split('=')[1] : ''; + + const response = await fetch(path, { + method: 'POST', + headers: { + [evalConstants.CONTENT_TYPE_HEADER]: evalConstants.APPLICATION_JSON, + [evalConstants.CSRF_TOKEN_HEADER]: csrfToken, + }, + credentials: 'include', + body: JSON.stringify(payload), + }); + + if (response.ok) { + return { success: true, data: await response.json() }; + } + + const errorText = await response.text().catch(() => response.statusText); + return { success: false, error: errorText }; + } catch (error: unknown) { + const err = error as Error; + return { success: false, error: err?.message ?? String(error) }; + } + }, + { payload: data, path: apiPath, evalConstants: constants }, + ); + + if (result.success) { + return result.data; + } + + console.error(`API POST to ${apiPath} failed: ${result.error}`); + return null; } public static getEvaluateConstants() { diff --git a/testing/playwright/utils/resource-manager/ResourceCreator.ts b/testing/playwright/utils/resource-manager/ResourceCreator.ts index ad1e569b0..a4bcce7fc 100644 --- a/testing/playwright/utils/resource-manager/ResourceCreator.ts +++ b/testing/playwright/utils/resource-manager/ResourceCreator.ts @@ -2,7 +2,7 @@ import type { IoK8sApiCoreV1Secret, V1beta1NetworkMap, V1beta1Provider } from '@ import type { Page } from '@playwright/test'; import { BaseResourceManager } from './BaseResourceManager'; -import { MTV_NAMESPACE, NAD_API_VERSION, RESOURCE_KINDS } from './constants'; +import { API_PATHS, MTV_NAMESPACE } from './constants'; /** * NetworkAttachmentDefinition type for CNI network configuration @@ -25,63 +25,8 @@ export const createProvider = async ( provider: V1beta1Provider, namespace = MTV_NAMESPACE, ): Promise => { - try { - const constants = BaseResourceManager.getEvaluateConstants(); - const result = await page.evaluate( - async ({ providerData, ns, evalConstants }) => { - try { - const getCsrfTokenFromCookie = () => { - const cookies = document.cookie.split('; '); - const csrfCookie = cookies.find((cookie) => - cookie.startsWith(`${evalConstants.CSRF_TOKEN_NAME}=`), - ); - return csrfCookie ? csrfCookie.split('=')[1] : ''; - }; - const csrfToken = getCsrfTokenFromCookie(); - - const apiPath = `${evalConstants.FORKLIFT_PATH}/namespaces/${ns}/providers`; - - const response = await fetch(apiPath, { - method: 'POST', - headers: { - [evalConstants.CONTENT_TYPE_HEADER]: evalConstants.APPLICATION_JSON, - [evalConstants.CSRF_TOKEN_HEADER]: csrfToken, - }, - credentials: 'include', - body: JSON.stringify(providerData), - }); - - if (response.ok) { - return { success: true, data: await response.json() }; - } - - const errorText = await response.text().catch(() => response.statusText); - return { success: false, error: errorText }; - } catch (error: unknown) { - const err = error as Error; - return { - success: false, - error: err?.message ?? String(error), - }; - } - }, - { - providerData: provider, - ns: namespace, - evalConstants: constants, - }, - ); - - if (result.success && result.data) { - return result.data as V1beta1Provider; - } - - console.error(`Failed to create provider: ${result.error}`); - return null; - } catch (error) { - console.error('Exception while creating provider:', error); - return null; - } + const apiPath = `${API_PATHS.FORKLIFT}/namespaces/${namespace}/providers`; + return BaseResourceManager.apiPost(page, apiPath, provider); }; export const createSecret = async ( @@ -89,63 +34,8 @@ export const createSecret = async ( secret: IoK8sApiCoreV1Secret, namespace = MTV_NAMESPACE, ): Promise => { - try { - const constants = BaseResourceManager.getEvaluateConstants(); - const result = await page.evaluate( - async ({ secretData, ns, evalConstants }) => { - try { - const getCsrfTokenFromCookie = () => { - const cookieList = document.cookie.split('; '); - const tokenCookie = cookieList.find((cookie) => - cookie.startsWith(`${evalConstants.CSRF_TOKEN_NAME}=`), - ); - return tokenCookie ? tokenCookie.split('=')[1] : ''; - }; - const csrfToken = getCsrfTokenFromCookie(); - - const apiPath = `${evalConstants.KUBERNETES_CORE}/namespaces/${ns}/secrets`; - - const response = await fetch(apiPath, { - method: 'POST', - headers: { - [evalConstants.CONTENT_TYPE_HEADER]: evalConstants.APPLICATION_JSON, - [evalConstants.CSRF_TOKEN_HEADER]: csrfToken, - }, - credentials: 'include', - body: JSON.stringify(secretData), - }); - - if (response.ok) { - return { success: true, data: await response.json() }; - } - - const errorText = await response.text().catch(() => response.statusText); - return { success: false, error: errorText }; - } catch (error: unknown) { - const err = error as Error; - return { - success: false, - error: err?.message ?? String(error), - }; - } - }, - { - secretData: secret, - ns: namespace, - evalConstants: constants, - }, - ); - - if (result.success && result.data) { - return result.data as IoK8sApiCoreV1Secret; - } - - console.error(`Failed to create secret: ${result.error}`); - return null; - } catch (error) { - console.error('Exception while creating secret:', error); - return null; - } + const apiPath = `${API_PATHS.KUBERNETES_CORE}/namespaces/${namespace}/secrets`; + return BaseResourceManager.apiPost(page, apiPath, secret); }; export const createNetworkMap = async ( @@ -153,63 +43,8 @@ export const createNetworkMap = async ( networkMap: V1beta1NetworkMap, namespace = MTV_NAMESPACE, ): Promise => { - try { - const constants = BaseResourceManager.getEvaluateConstants(); - const result = await page.evaluate( - async ({ networkMapData, ns, evalConstants }) => { - try { - const getCsrfTokenFromCookie = () => { - const cookieList = document.cookie.split('; '); - const tokenCookie = cookieList.find((cookie) => - cookie.startsWith(`${evalConstants.CSRF_TOKEN_NAME}=`), - ); - return tokenCookie ? tokenCookie.split('=')[1] : ''; - }; - const csrfToken = getCsrfTokenFromCookie(); - - const apiPath = `${evalConstants.FORKLIFT_PATH}/namespaces/${ns}/networkmaps`; - - const response = await fetch(apiPath, { - method: 'POST', - headers: { - [evalConstants.CONTENT_TYPE_HEADER]: evalConstants.APPLICATION_JSON, - [evalConstants.CSRF_TOKEN_HEADER]: csrfToken, - }, - credentials: 'include', - body: JSON.stringify(networkMapData), - }); - - if (response.ok) { - return { success: true, data: await response.json() }; - } - - const errorText = await response.text().catch(() => response.statusText); - return { success: false, error: errorText }; - } catch (error: unknown) { - const err = error as Error; - return { - success: false, - error: err?.message ?? String(error), - }; - } - }, - { - networkMapData: networkMap, - ns: namespace, - evalConstants: constants, - }, - ); - - if (result.success && result.data) { - return result.data as V1beta1NetworkMap; - } - - console.error(`Failed to create network map: ${result.error}`); - return null; - } catch (error) { - console.error('Exception while creating network map:', error); - return null; - } + const apiPath = `${API_PATHS.FORKLIFT}/namespaces/${namespace}/networkmaps`; + return BaseResourceManager.apiPost(page, apiPath, networkMap); }; export const createNad = async ( @@ -217,61 +52,6 @@ export const createNad = async ( nad: V1NetworkAttachmentDefinition, namespace: string, ): Promise => { - try { - const constants = BaseResourceManager.getEvaluateConstants(); - const result = await page.evaluate( - async ({ nadData, ns, evalConstants }) => { - try { - const getCsrfTokenFromCookie = () => { - const cookieList = document.cookie.split('; '); - const tokenCookie = cookieList.find((cookie) => - cookie.startsWith(`${evalConstants.CSRF_TOKEN_NAME}=`), - ); - return tokenCookie ? tokenCookie.split('=')[1] : ''; - }; - const csrfToken = getCsrfTokenFromCookie(); - - const apiPath = `${evalConstants.NAD_PATH}/namespaces/${ns}/network-attachment-definitions`; - - const response = await fetch(apiPath, { - method: 'POST', - headers: { - [evalConstants.CONTENT_TYPE_HEADER]: evalConstants.APPLICATION_JSON, - [evalConstants.CSRF_TOKEN_HEADER]: csrfToken, - }, - credentials: 'include', - body: JSON.stringify(nadData), - }); - - if (response.ok) { - return { success: true, data: await response.json() }; - } - - const errorText = await response.text().catch(() => response.statusText); - return { success: false, error: errorText }; - } catch (error: unknown) { - const err = error as Error; - return { - success: false, - error: err?.message ?? String(error), - }; - } - }, - { - nadData: nad, - ns: namespace, - evalConstants: constants, - }, - ); - - if (result.success && result.data) { - return result.data as V1NetworkAttachmentDefinition; - } - - console.error(`Failed to create NAD: ${result.error}`); - return null; - } catch (error) { - console.error('Exception while creating NAD:', error); - return null; - } + const apiPath = `${API_PATHS.NAD}/namespaces/${namespace}/network-attachment-definitions`; + return BaseResourceManager.apiPost(page, apiPath, nad); }; From 22dc8f41441b363c4696725170c62e708abff61a Mon Sep 17 00:00:00 2001 From: Pedro Abreu Date: Wed, 7 Jan 2026 14:49:13 -0500 Subject: [PATCH 04/11] cleanup Resolves: MTV-3649 Signed-off-by: Pedro Abreu --- .../playwright/fixtures/helpers/nadHelpers.ts | 25 ------------------- .../fixtures/helpers/networkMapHelpers.ts | 15 ----------- .../helpers/resourceCreationHelpers.ts | 1 - .../playwright/page-objects/OverviewPage.ts | 12 --------- 4 files changed, 53 deletions(-) diff --git a/testing/playwright/fixtures/helpers/nadHelpers.ts b/testing/playwright/fixtures/helpers/nadHelpers.ts index c091e243f..d6507b455 100644 --- a/testing/playwright/fixtures/helpers/nadHelpers.ts +++ b/testing/playwright/fixtures/helpers/nadHelpers.ts @@ -12,45 +12,21 @@ export type TestNad = V1NetworkAttachmentDefinition & { }; }; -/** - * Creates a NetworkAttachmentDefinition (NAD) for testing purposes. - * - * NADs are used by the Settings tab's "Controller transfer network" dropdown - * to select a network for data transfer during migrations. - * - * @param page - Playwright page instance (needed for CSRF token) - * @param resourceManager - Resource manager for API calls and cleanup registration - * @param options - Configuration options for the NAD - * @returns Promise that resolves to the created TestNad - * - * @example - * const nad = await createTestNad(page, resourceManager, { - * name: 'my-test-nad', - * namespace: 'openshift-mtv', - * }); - */ export const createTestNad = async ( page: Page, resourceManager: ResourceManager, options: { - /** Name for the NAD (auto-generated if not provided) */ name?: string; - /** Namespace for the NAD */ namespace: string; - /** Bridge name for the CNI config (defaults to 'br0') */ bridgeName?: string; }, ): Promise => { const { namespace, bridgeName = 'br0' } = options; - - // Generate NAD name if not provided const nadName = options.name ?? `nad-test-${crypto.randomUUID().slice(0, 8)}`; - // Navigate to console to establish session (needed for CSRF token) const navigationHelper = new NavigationHelper(page); await navigationHelper.navigateToConsole(); - // Create the NAD with a simple bridge CNI config const nadConfig = { cniVersion: '0.3.1', name: nadName, @@ -76,7 +52,6 @@ export const createTestNad = async ( throw new Error(`Failed to create NAD ${nadName}`); } - // Register NAD for cleanup resourceManager.addNad(nadName, namespace); const result: TestNad = { diff --git a/testing/playwright/fixtures/helpers/networkMapHelpers.ts b/testing/playwright/fixtures/helpers/networkMapHelpers.ts index b0e813128..57f1c9f30 100644 --- a/testing/playwright/fixtures/helpers/networkMapHelpers.ts +++ b/testing/playwright/fixtures/helpers/networkMapHelpers.ts @@ -12,27 +12,15 @@ export type TestNetworkMap = V1beta1NetworkMap & { }; }; -/** - * Network mapping configuration for creating network maps - */ export type NetworkMapMappingConfig = { - /** Source network ID (from provider inventory) */ sourceId?: string; - /** Source network name */ sourceName: string; - /** Source network type (optional, e.g., 'DistributedVirtualPortgroup') */ sourceType?: string; - /** Destination type: 'pod' for default pod network, 'multus' for NAD */ destinationType: 'pod' | 'multus'; - /** Destination name (e.g., 'Default network' for pod, or NAD name for multus) */ destinationName?: string; - /** Destination namespace (required for multus type) */ destinationNamespace?: string; }; -/** - * Creates a network map directly via API based on provider references. - */ export const createNetworkMap = async ( page: Page, resourceManager: ResourceManager, @@ -106,9 +94,6 @@ export const createNetworkMap = async ( }; }; -/** - * Creates a simple network map with default pod networking for all source networks. - */ export const createSimpleNetworkMap = async ( page: Page, resourceManager: ResourceManager, diff --git a/testing/playwright/fixtures/helpers/resourceCreationHelpers.ts b/testing/playwright/fixtures/helpers/resourceCreationHelpers.ts index fc17a12f9..68f1bb805 100644 --- a/testing/playwright/fixtures/helpers/resourceCreationHelpers.ts +++ b/testing/playwright/fixtures/helpers/resourceCreationHelpers.ts @@ -46,7 +46,6 @@ const buildTestProviderResult = (providerData: ProviderData): TestProvider => { return provider; }; -// Re-export helpers from separate files for backwards compatibility export { createTestNad, type TestNad } from './nadHelpers'; export { createNetworkMap, diff --git a/testing/playwright/page-objects/OverviewPage.ts b/testing/playwright/page-objects/OverviewPage.ts index 6417a0b4b..5642a9fae 100644 --- a/testing/playwright/page-objects/OverviewPage.ts +++ b/testing/playwright/page-objects/OverviewPage.ts @@ -39,9 +39,6 @@ export class OverviewPage { await expect(this.tipsAndTricksDrawerTitle).not.toBeVisible(); } - /** - * Complete flow to edit and save the transfer network setting - */ async editAndSaveTransferNetwork(): Promise { await this.openSettingsEditModal(); await this.toggleTransferNetworkValue(); @@ -194,8 +191,6 @@ export class OverviewPage { await toggleButton.scrollIntoViewIfNeeded(); await expect(toggleButton).toBeVisible(); - - // Expand then collapse (mimicking old behavior without state assertions) await toggleButton.click(); await toggleButton.click(); } @@ -209,11 +204,6 @@ export class OverviewPage { return this.page.getByRole('heading', { name: 'Tips and tricks', level: 2 }); } - /** - * Toggles the transfer network value: - * - If a network is selected, changes to None - * - If None is selected, selects the first available network - */ async toggleTransferNetworkValue(): Promise { const currentValue = await this.getTransferNetworkCurrentValue(); await this.openTransferNetworkDropdown(); @@ -231,7 +221,6 @@ export class OverviewPage { return this.page.getByTestId('controller-transfer-network-select'); } - // Settings tab locators get transferNetworkField() { return this.page.getByTestId('settings-controller-transfer-network'); } @@ -271,7 +260,6 @@ export class OverviewPage { await expect(this.page.getByRole('heading', { name: topicName, level: 3 })).toBeVisible(); } - // Settings tab methods async verifyTransferNetworkFieldVisible(): Promise { await expect(this.transferNetworkField).toBeVisible(); } From 205f5921281b3b8cd49f32bf9bee367cdfaf8c22 Mon Sep 17 00:00:00 2001 From: Pedro Abreu Date: Wed, 7 Jan 2026 16:28:43 -0500 Subject: [PATCH 05/11] Remove unused helper files for NAD and Network Map Resolves: MTV-3649 Signed-off-by: Pedro Abreu --- .../playwright/fixtures/helpers/nadHelpers.ts | 67 ----------- .../fixtures/helpers/networkMapHelpers.ts | 112 ------------------ .../helpers/resourceCreationHelpers.ts | 66 +++++++++-- .../playwright/page-objects/OverviewPage.ts | 12 -- 4 files changed, 57 insertions(+), 200 deletions(-) delete mode 100644 testing/playwright/fixtures/helpers/nadHelpers.ts delete mode 100644 testing/playwright/fixtures/helpers/networkMapHelpers.ts diff --git a/testing/playwright/fixtures/helpers/nadHelpers.ts b/testing/playwright/fixtures/helpers/nadHelpers.ts deleted file mode 100644 index d6507b455..000000000 --- a/testing/playwright/fixtures/helpers/nadHelpers.ts +++ /dev/null @@ -1,67 +0,0 @@ -import type { Page } from '@playwright/test'; - -import { NavigationHelper } from '../../utils/NavigationHelper'; -import { NAD_API_VERSION, RESOURCE_KINDS } from '../../utils/resource-manager/constants'; -import type { V1NetworkAttachmentDefinition } from '../../utils/resource-manager/ResourceCreator'; -import type { ResourceManager } from '../../utils/resource-manager/ResourceManager'; - -export type TestNad = V1NetworkAttachmentDefinition & { - metadata: { - name: string; - namespace: string; - }; -}; - -export const createTestNad = async ( - page: Page, - resourceManager: ResourceManager, - options: { - name?: string; - namespace: string; - bridgeName?: string; - }, -): Promise => { - const { namespace, bridgeName = 'br0' } = options; - const nadName = options.name ?? `nad-test-${crypto.randomUUID().slice(0, 8)}`; - - const navigationHelper = new NavigationHelper(page); - await navigationHelper.navigateToConsole(); - - const nadConfig = { - cniVersion: '0.3.1', - name: nadName, - type: 'bridge', - bridge: bridgeName, - ipam: {}, - }; - - const nad: V1NetworkAttachmentDefinition = { - apiVersion: NAD_API_VERSION, - kind: RESOURCE_KINDS.NETWORK_ATTACHMENT_DEFINITION, - metadata: { - name: nadName, - namespace, - }, - spec: { - config: JSON.stringify(nadConfig), - }, - }; - - const createdNad = await resourceManager.createNad(page, nad, namespace); - if (!createdNad) { - throw new Error(`Failed to create NAD ${nadName}`); - } - - resourceManager.addNad(nadName, namespace); - - const result: TestNad = { - ...createdNad, - metadata: { - ...createdNad.metadata, - name: nadName, - namespace, - }, - }; - - return result; -}; diff --git a/testing/playwright/fixtures/helpers/networkMapHelpers.ts b/testing/playwright/fixtures/helpers/networkMapHelpers.ts deleted file mode 100644 index 57f1c9f30..000000000 --- a/testing/playwright/fixtures/helpers/networkMapHelpers.ts +++ /dev/null @@ -1,112 +0,0 @@ -import type { V1beta1NetworkMap, V1beta1NetworkMapSpecMap, V1beta1Provider } from '@kubev2v/types'; -import type { Page } from '@playwright/test'; - -import { CreateProviderPage } from '../../page-objects/CreateProviderPage'; -import { FORKLIFT_API_VERSION, MTV_NAMESPACE } from '../../utils/resource-manager/constants'; -import type { ResourceManager } from '../../utils/resource-manager/ResourceManager'; - -export type TestNetworkMap = V1beta1NetworkMap & { - metadata: { - name: string; - namespace: string; - }; -}; - -export type NetworkMapMappingConfig = { - sourceId?: string; - sourceName: string; - sourceType?: string; - destinationType: 'pod' | 'multus'; - destinationName?: string; - destinationNamespace?: string; -}; - -export const createNetworkMap = async ( - page: Page, - resourceManager: ResourceManager, - options: { - sourceProvider: V1beta1Provider; - targetProvider: V1beta1Provider; - mappings: NetworkMapMappingConfig[]; - name?: string; - namespace?: string; - }, -): Promise => { - const { sourceProvider, targetProvider, mappings, name, namespace = MTV_NAMESPACE } = options; - - const sourceProviderName = sourceProvider.metadata?.name; - const targetProviderName = targetProvider.metadata?.name; - - if (!sourceProviderName || !targetProviderName) { - throw new Error('Source and target providers must have metadata.name defined'); - } - - const networkMapName = name ?? `${sourceProviderName}-netmap-${crypto.randomUUID().slice(0, 8)}`; - - const createProviderPage = new CreateProviderPage(page, resourceManager); - await createProviderPage.navigationHelper.navigateToConsole(); - - const specMappings: V1beta1NetworkMapSpecMap[] = mappings.map((mapping) => { - const destination = - mapping.destinationType === 'pod' - ? { type: 'pod' as const, name: mapping.destinationName ?? 'Default network' } - : { - type: 'multus' as const, - name: mapping.destinationName, - namespace: mapping.destinationNamespace, - }; - - return { - destination, - source: { id: mapping.sourceId, name: mapping.sourceName, type: mapping.sourceType }, - }; - }); - - const networkMap: V1beta1NetworkMap = { - apiVersion: FORKLIFT_API_VERSION, - kind: 'NetworkMap', - metadata: { name: networkMapName, namespace }, - spec: { - map: specMappings, - provider: { - destination: { - name: targetProviderName, - namespace: targetProvider.metadata?.namespace ?? namespace, - }, - source: { - name: sourceProviderName, - namespace: sourceProvider.metadata?.namespace ?? namespace, - }, - }, - }, - }; - - const createdNetworkMap = await resourceManager.createNetworkMap(page, networkMap, namespace); - if (!createdNetworkMap) { - throw new Error(`Failed to create network map ${networkMapName}`); - } - - resourceManager.addNetworkMap(networkMapName, namespace); - - return { - ...createdNetworkMap, - metadata: { ...createdNetworkMap.metadata, name: networkMapName, namespace }, - }; -}; - -export const createSimpleNetworkMap = async ( - page: Page, - resourceManager: ResourceManager, - sourceProvider: V1beta1Provider, - targetProvider: V1beta1Provider, - sourceNetworks: { id?: string; name: string; type?: string }[], -): Promise => { - const mappings: NetworkMapMappingConfig[] = sourceNetworks.map((network) => ({ - sourceId: network.id, - sourceName: network.name, - sourceType: network.type, - destinationType: 'pod' as const, - })); - - return createNetworkMap(page, resourceManager, { sourceProvider, targetProvider, mappings }); -}; diff --git a/testing/playwright/fixtures/helpers/resourceCreationHelpers.ts b/testing/playwright/fixtures/helpers/resourceCreationHelpers.ts index 68f1bb805..ae009fc4a 100644 --- a/testing/playwright/fixtures/helpers/resourceCreationHelpers.ts +++ b/testing/playwright/fixtures/helpers/resourceCreationHelpers.ts @@ -6,8 +6,14 @@ import { CreateProviderPage } from '../../page-objects/CreateProviderPage'; import { PlanDetailsPage } from '../../page-objects/PlanDetailsPage/PlanDetailsPage'; import { EndpointType, ProviderType } from '../../types/enums'; import { createPlanTestData, type ProviderData } from '../../types/test-data'; +import { NavigationHelper } from '../../utils/NavigationHelper'; import { getProviderConfig } from '../../utils/providers'; -import { MTV_NAMESPACE } from '../../utils/resource-manager/constants'; +import { + MTV_NAMESPACE, + NAD_API_VERSION, + RESOURCE_KINDS, +} from '../../utils/resource-manager/constants'; +import type { V1NetworkAttachmentDefinition } from '../../utils/resource-manager/ResourceCreator'; import type { ResourceManager } from '../../utils/resource-manager/ResourceManager'; export const createSecretObject = ( @@ -46,14 +52,6 @@ const buildTestProviderResult = (providerData: ProviderData): TestProvider => { return provider; }; -export { createTestNad, type TestNad } from './nadHelpers'; -export { - createNetworkMap, - createSimpleNetworkMap, - type NetworkMapMappingConfig, - type TestNetworkMap, -} from './networkMapHelpers'; - export type TestProvider = V1beta1Provider & { metadata: { name: string; @@ -69,6 +67,13 @@ export type TestPlan = V1beta1Plan & { testData: ReturnType; }; +export type TestNad = V1NetworkAttachmentDefinition & { + metadata: { + name: string; + namespace: string; + }; +}; + export interface CreateProviderOptions { providerKey?: string; namePrefix?: string; @@ -215,3 +220,46 @@ export const createPlan = async ( return buildTestPlanResult(testPlanData); }; + +export const createTestNad = async ( + page: Page, + resourceManager: ResourceManager, + options: { + name?: string; + namespace: string; + bridgeName?: string; + }, +): Promise => { + const { namespace, bridgeName = 'br0' } = options; + const nadName = options.name ?? `nad-test-${crypto.randomUUID().slice(0, 8)}`; + + const navigationHelper = new NavigationHelper(page); + await navigationHelper.navigateToConsole(); + + const nadConfig = { + cniVersion: '0.3.1', + name: nadName, + type: 'bridge', + bridge: bridgeName, + ipam: {}, + }; + + const nad: V1NetworkAttachmentDefinition = { + apiVersion: NAD_API_VERSION, + kind: RESOURCE_KINDS.NETWORK_ATTACHMENT_DEFINITION, + metadata: { name: nadName, namespace }, + spec: { config: JSON.stringify(nadConfig) }, + }; + + const createdNad = await resourceManager.createNad(page, nad, namespace); + if (!createdNad) { + throw new Error(`Failed to create NAD ${nadName}`); + } + + resourceManager.addNad(nadName, namespace); + + return { + ...createdNad, + metadata: { ...createdNad.metadata, name: nadName, namespace }, + }; +}; diff --git a/testing/playwright/page-objects/OverviewPage.ts b/testing/playwright/page-objects/OverviewPage.ts index 5642a9fae..95fc3aa6e 100644 --- a/testing/playwright/page-objects/OverviewPage.ts +++ b/testing/playwright/page-objects/OverviewPage.ts @@ -17,10 +17,6 @@ export class OverviewPage { this.navigation = new NavigationHelper(page); } - async cancelSettingsEdit(): Promise { - await this.modalCancelButton.click(); - } - get choosingMigrationTypeOption() { return this.page.getByText('Choosing the right migration type', { exact: true }).first(); } @@ -29,10 +25,6 @@ export class OverviewPage { return this.page.getByRole('button', { name: 'Close drawer panel' }); } - async closeDropdownWithEscape(): Promise { - await this.page.keyboard.press('Escape'); - } - async closeTipsAndTricks() { await expect(this.closeDrawerButton).toBeVisible(); await this.closeDrawerButton.click(); @@ -57,10 +49,6 @@ export class OverviewPage { return this.page.getByText('Migrating your virtual machines', { exact: true }).first(); } - get modalCancelButton() { - return this.page.getByTestId('modal-cancel-button'); - } - get modalConfirmButton() { return this.page.getByTestId('modal-confirm-button'); } From dc7f23f5affbe86f66253570694fdc2de11abf57 Mon Sep 17 00:00:00 2001 From: Pedro Abreu Date: Wed, 7 Jan 2026 16:37:11 -0500 Subject: [PATCH 06/11] remove unused methods Resolves: MTV-3649 Signed-off-by: Pedro Abreu --- .../playwright/page-objects/OverviewPage.ts | 47 ------------------- .../utils/resource-manager/ResourceCreator.ts | 11 +---- .../utils/resource-manager/ResourceManager.ts | 22 +-------- 3 files changed, 2 insertions(+), 78 deletions(-) diff --git a/testing/playwright/page-objects/OverviewPage.ts b/testing/playwright/page-objects/OverviewPage.ts index 95fc3aa6e..2a550d2f7 100644 --- a/testing/playwright/page-objects/OverviewPage.ts +++ b/testing/playwright/page-objects/OverviewPage.ts @@ -17,20 +17,10 @@ export class OverviewPage { this.navigation = new NavigationHelper(page); } - get choosingMigrationTypeOption() { - return this.page.getByText('Choosing the right migration type', { exact: true }).first(); - } - get closeDrawerButton() { return this.page.getByRole('button', { name: 'Close drawer panel' }); } - async closeTipsAndTricks() { - await expect(this.closeDrawerButton).toBeVisible(); - await this.closeDrawerButton.click(); - await expect(this.tipsAndTricksDrawerTitle).not.toBeVisible(); - } - async editAndSaveTransferNetwork(): Promise { await this.openSettingsEditModal(); await this.toggleTransferNetworkValue(); @@ -41,14 +31,6 @@ export class OverviewPage { return this.transferNetworkDropdown.textContent(); } - get keyTerminologyOption() { - return this.page.getByText('Key terminology', { exact: true }).first(); - } - - get migratingVMsOption() { - return this.page.getByText('Migrating your virtual machines', { exact: true }).first(); - } - get modalConfirmButton() { return this.page.getByTestId('modal-confirm-button'); } @@ -58,11 +40,6 @@ export class OverviewPage { await this.waitForPageLoad(); } - async navigateFromMainMenu() { - await this.navigation.navigateToOverview(); - await this.waitForPageLoad(); - } - async navigateToNextTopic(currentTopicName: string, nextTopicName: string): Promise { await this.selectTopicButton.click(); await this.page.getByRole('option', { name: nextTopicName }).click(); @@ -118,21 +95,6 @@ export class OverviewPage { await this.transferNetworkOptions.first().click(); } - async selectTopic( - topicName: 'migrating-vms' | 'migration-type' | 'troubleshooting' | 'terminology', - ) { - const topicMap = { - 'migrating-vms': this.migratingVMsOption, - 'migration-type': this.choosingMigrationTypeOption, - troubleshooting: this.troubleshootingOption, - terminology: this.keyTerminologyOption, - }; - - const topic = topicMap[topicName]; - await expect(topic).toBeVisible(); - await topic.click(); - } - get selectTopicButton() { return this.page.getByRole('button', { name: 'Select a topic' }); } @@ -144,11 +106,6 @@ export class OverviewPage { ).toBeVisible(); } - async selectTopicByName(topicName: string): Promise { - await this.page.getByTestId('topic-card').filter({ hasText: topicName }).click(); - await this.verifyTopicHeading(topicName); - } - async selectTransferNetworkNone(): Promise { await this.transferNetworkNoneOption.click(); } @@ -223,10 +180,6 @@ export class OverviewPage { .filter({ hasNotText: 'None' }); } - get troubleshootingOption() { - return this.page.getByText('Troubleshooting', { exact: true }).first(); - } - async verifyPicklist(topics: TopicConfig[]): Promise { await this.selectTopicButton.click(); diff --git a/testing/playwright/utils/resource-manager/ResourceCreator.ts b/testing/playwright/utils/resource-manager/ResourceCreator.ts index a4bcce7fc..acaa69334 100644 --- a/testing/playwright/utils/resource-manager/ResourceCreator.ts +++ b/testing/playwright/utils/resource-manager/ResourceCreator.ts @@ -1,4 +1,4 @@ -import type { IoK8sApiCoreV1Secret, V1beta1NetworkMap, V1beta1Provider } from '@kubev2v/types'; +import type { IoK8sApiCoreV1Secret, V1beta1Provider } from '@kubev2v/types'; import type { Page } from '@playwright/test'; import { BaseResourceManager } from './BaseResourceManager'; @@ -38,15 +38,6 @@ export const createSecret = async ( return BaseResourceManager.apiPost(page, apiPath, secret); }; -export const createNetworkMap = async ( - page: Page, - networkMap: V1beta1NetworkMap, - namespace = MTV_NAMESPACE, -): Promise => { - const apiPath = `${API_PATHS.FORKLIFT}/namespaces/${namespace}/networkmaps`; - return BaseResourceManager.apiPost(page, apiPath, networkMap); -}; - export const createNad = async ( page: Page, nad: V1NetworkAttachmentDefinition, diff --git a/testing/playwright/utils/resource-manager/ResourceManager.ts b/testing/playwright/utils/resource-manager/ResourceManager.ts index b58e2de55..c07814eb3 100644 --- a/testing/playwright/utils/resource-manager/ResourceManager.ts +++ b/testing/playwright/utils/resource-manager/ResourceManager.ts @@ -21,7 +21,7 @@ import { RESOURCE_KINDS, } from './constants'; import { ResourceCleaner } from './ResourceCleaner'; -import { createNad, createNetworkMap, createProvider, createSecret } from './ResourceCreator'; +import { createNad, createProvider, createSecret } from './ResourceCreator'; import { ResourceFetcher } from './ResourceFetcher'; import { ResourcePatcher } from './ResourcePatcher'; @@ -77,18 +77,6 @@ export class ResourceManager { this.addResource(nad); } - addNetworkMap(name: string, namespace: string): void { - const networkMap: V1beta1NetworkMap = { - apiVersion: FORKLIFT_API_VERSION, - kind: RESOURCE_KINDS.NETWORK_MAP, - metadata: { - name, - namespace, - }, - }; - this.addResource(networkMap); - } - addPlan(name: string, namespace: string): void { const plan: V1beta1Plan = { apiVersion: FORKLIFT_API_VERSION, @@ -165,14 +153,6 @@ export class ResourceManager { return createNad(page, nad as Parameters[1], namespace); } - async createNetworkMap( - page: Page, - networkMap: V1beta1NetworkMap, - namespace = MTV_NAMESPACE, - ): Promise { - return createNetworkMap(page, networkMap, namespace); - } - async createProvider( page: Page, provider: V1beta1Provider, From cd560c013ea5200ca99e40b975cb7e51fe7395d3 Mon Sep 17 00:00:00 2001 From: Pedro Abreu Date: Wed, 7 Jan 2026 20:51:39 -0500 Subject: [PATCH 07/11] restore addNetworkMap method Resolves: MTV-3649 Signed-off-by: Pedro Abreu --- .../utils/resource-manager/ResourceManager.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/testing/playwright/utils/resource-manager/ResourceManager.ts b/testing/playwright/utils/resource-manager/ResourceManager.ts index c07814eb3..cf9ae49f9 100644 --- a/testing/playwright/utils/resource-manager/ResourceManager.ts +++ b/testing/playwright/utils/resource-manager/ResourceManager.ts @@ -77,6 +77,18 @@ export class ResourceManager { this.addResource(nad); } + addNetworkMap(name: string, namespace: string): void { + const networkMap: V1beta1NetworkMap = { + apiVersion: FORKLIFT_API_VERSION, + kind: RESOURCE_KINDS.NETWORK_MAP, + metadata: { + name, + namespace, + }, + }; + this.addResource(networkMap); + } + addPlan(name: string, namespace: string): void { const plan: V1beta1Plan = { apiVersion: FORKLIFT_API_VERSION, From dc3c0afbc79ccd6465d6ba5cda6455cb7d5099f8 Mon Sep 17 00:00:00 2001 From: Pedro Abreu Date: Mon, 12 Jan 2026 14:54:08 -0500 Subject: [PATCH 08/11] Refactor resource creation helpers t Resolves: MTV-3649 Signed-off-by: Pedro Abreu --- .../helpers/resourceCreationHelpers.ts | 16 ++++-- .../utils/resource-manager/ResourceManager.ts | 57 ++++++------------- 2 files changed, 26 insertions(+), 47 deletions(-) diff --git a/testing/playwright/fixtures/helpers/resourceCreationHelpers.ts b/testing/playwright/fixtures/helpers/resourceCreationHelpers.ts index ae009fc4a..f36345d10 100644 --- a/testing/playwright/fixtures/helpers/resourceCreationHelpers.ts +++ b/testing/playwright/fixtures/helpers/resourceCreationHelpers.ts @@ -13,7 +13,12 @@ import { NAD_API_VERSION, RESOURCE_KINDS, } from '../../utils/resource-manager/constants'; -import type { V1NetworkAttachmentDefinition } from '../../utils/resource-manager/ResourceCreator'; +import { + createNad as createNadApi, + createProvider as createProviderApi, + createSecret as createSecretApi, + type V1NetworkAttachmentDefinition, +} from '../../utils/resource-manager/ResourceCreator'; import type { ResourceManager } from '../../utils/resource-manager/ResourceManager'; export const createSecretObject = ( @@ -91,10 +96,11 @@ const createOvaProviderViaApi = async ( const secretName = `${providerData.name}-secret`; const secret = createSecretObject(secretName, MTV_NAMESPACE, { url: providerData.hostname }); - const createdSecret = await resourceManager.createSecret(page, secret); + const createdSecret = await createSecretApi(page, secret, MTV_NAMESPACE); if (!createdSecret) { throw new Error(`Failed to create secret for OVA provider ${providerData.name}`); } + resourceManager.addSecret(secretName, MTV_NAMESPACE); const provider = createProviderObject(providerData.name, MTV_NAMESPACE, { type: ProviderType.OVA, @@ -103,11 +109,10 @@ const createOvaProviderViaApi = async ( settings: { applianceManagement: 'true' }, }); - const createdProvider = await resourceManager.createProvider(page, provider); + const createdProvider = await createProviderApi(page, provider, MTV_NAMESPACE); if (!createdProvider) { throw new Error(`Failed to create OVA provider ${providerData.name}`); } - resourceManager.addProvider(providerData.name, MTV_NAMESPACE); }; @@ -251,11 +256,10 @@ export const createTestNad = async ( spec: { config: JSON.stringify(nadConfig) }, }; - const createdNad = await resourceManager.createNad(page, nad, namespace); + const createdNad = await createNadApi(page, nad, namespace); if (!createdNad) { throw new Error(`Failed to create NAD ${nadName}`); } - resourceManager.addNad(nadName, namespace); return { diff --git a/testing/playwright/utils/resource-manager/ResourceManager.ts b/testing/playwright/utils/resource-manager/ResourceManager.ts index cf9ae49f9..78cfaa9a1 100644 --- a/testing/playwright/utils/resource-manager/ResourceManager.ts +++ b/testing/playwright/utils/resource-manager/ResourceManager.ts @@ -21,31 +21,17 @@ import { RESOURCE_KINDS, } from './constants'; import { ResourceCleaner } from './ResourceCleaner'; -import { createNad, createProvider, createSecret } from './ResourceCreator'; +import type { V1NetworkAttachmentDefinition } from './ResourceCreator'; import { ResourceFetcher } from './ResourceFetcher'; import { ResourcePatcher } from './ResourcePatcher'; +export type { V1NetworkAttachmentDefinition }; + export type OpenshiftProject = IoK8sApiCoreV1Namespace & { kind: typeof OPENSHIFT_PROJECT_KIND; apiVersion: typeof OPENSHIFT_PROJECT_API_VERSION; }; -/** - * NetworkAttachmentDefinition type for CNI network configuration - */ -export type V1NetworkAttachmentDefinition = { - apiVersion: 'k8s.cni.cncf.io/v1'; - kind: 'NetworkAttachmentDefinition'; - metadata: { - name: string; - namespace: string; - annotations?: Record; - }; - spec: { - config: string; - }; -}; - export type SupportedResource = | V1beta1Migration | V1beta1NetworkMap @@ -54,6 +40,7 @@ export type SupportedResource = | V1VirtualMachine | V1NetworkAttachmentDefinition | IoK8sApiCoreV1Namespace + | IoK8sApiCoreV1Secret | OpenshiftProject; /** @@ -137,6 +124,18 @@ export class ResourceManager { this.resources.push(resource); } + addSecret(name: string, namespace: string): void { + const secret: IoK8sApiCoreV1Secret = { + apiVersion: 'v1', + kind: 'Secret', + metadata: { + name, + namespace, + }, + }; + this.addResource(secret); + } + addVm(name: string, namespace: string): void { const vm: V1VirtualMachine = { apiVersion: KUBEVIRT_API_VERSION, @@ -157,30 +156,6 @@ export class ResourceManager { this.resources = []; } - async createNad( - page: Page, - nad: V1NetworkAttachmentDefinition, - namespace: string, - ): Promise { - return createNad(page, nad as Parameters[1], namespace); - } - - async createProvider( - page: Page, - provider: V1beta1Provider, - namespace = MTV_NAMESPACE, - ): Promise { - return createProvider(page, provider, namespace); - } - - async createSecret( - page: Page, - secret: IoK8sApiCoreV1Secret, - namespace = MTV_NAMESPACE, - ): Promise { - return createSecret(page, secret, namespace); - } - async fetchProvider( page: Page, providerName: string, From 7808d55bb3920c347c90bef5678a31087752ef56 Mon Sep 17 00:00:00 2001 From: Pedro Abreu Date: Mon, 12 Jan 2026 15:14:08 -0500 Subject: [PATCH 09/11] Refactor export of V1NetworkAttachmentDefinition Resolves: MTV-3649 Signed-off-by: Pedro Abreu --- testing/playwright/utils/resource-manager/ResourceManager.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testing/playwright/utils/resource-manager/ResourceManager.ts b/testing/playwright/utils/resource-manager/ResourceManager.ts index 78cfaa9a1..ea3cc617d 100644 --- a/testing/playwright/utils/resource-manager/ResourceManager.ts +++ b/testing/playwright/utils/resource-manager/ResourceManager.ts @@ -21,11 +21,11 @@ import { RESOURCE_KINDS, } from './constants'; import { ResourceCleaner } from './ResourceCleaner'; -import type { V1NetworkAttachmentDefinition } from './ResourceCreator'; import { ResourceFetcher } from './ResourceFetcher'; import { ResourcePatcher } from './ResourcePatcher'; -export type { V1NetworkAttachmentDefinition }; +export type { V1NetworkAttachmentDefinition } from './ResourceCreator'; +import type { V1NetworkAttachmentDefinition } from './ResourceCreator'; export type OpenshiftProject = IoK8sApiCoreV1Namespace & { kind: typeof OPENSHIFT_PROJECT_KIND; From 8cb3afa7a4aead65e6e74af3c318c2f3c934655c Mon Sep 17 00:00:00 2001 From: Pedro Abreu Date: Wed, 14 Jan 2026 12:11:55 -0500 Subject: [PATCH 10/11] Refactor provider creation test conditions Resolves: None Signed-off-by: Pedro Abreu --- .../e2e/downstream/providers/create-provider.spec.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/testing/playwright/e2e/downstream/providers/create-provider.spec.ts b/testing/playwright/e2e/downstream/providers/create-provider.spec.ts index 6713fcab5..46fb81c95 100644 --- a/testing/playwright/e2e/downstream/providers/create-provider.spec.ts +++ b/testing/playwright/e2e/downstream/providers/create-provider.spec.ts @@ -47,9 +47,10 @@ test.describe('Provider Creation Tests', () => { expect(providerResource).not.toBeNull(); expect(providerResource?.spec?.type).toBe(providerType); - if (testProviderData.useVddkAioOptimization === true) { + if (testProviderData.useVddkAioOptimization) { expect(providerResource?.spec?.settings?.useVddkAioOptimization).toBe('true'); - } else if (testProviderData.useVddkAioOptimization === false) { + } + if (!testProviderData.useVddkAioOptimization) { const aioValue = providerResource?.spec?.settings?.useVddkAioOptimization; expect(aioValue === undefined || aioValue === 'false').toBe(true); } From 989001f21f93ec1cbfc4893add7e439f85f04a06 Mon Sep 17 00:00:00 2001 From: Pedro Abreu Date: Wed, 14 Jan 2026 12:29:59 -0500 Subject: [PATCH 11/11] Refactor TestNad type to directly return V1NetworkAttachmentDefinition in createTestNad function Resolves: None Signed-off-by: Pedro Abreu --- .../fixtures/helpers/resourceCreationHelpers.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/testing/playwright/fixtures/helpers/resourceCreationHelpers.ts b/testing/playwright/fixtures/helpers/resourceCreationHelpers.ts index f36345d10..517e8ac6a 100644 --- a/testing/playwright/fixtures/helpers/resourceCreationHelpers.ts +++ b/testing/playwright/fixtures/helpers/resourceCreationHelpers.ts @@ -72,13 +72,6 @@ export type TestPlan = V1beta1Plan & { testData: ReturnType; }; -export type TestNad = V1NetworkAttachmentDefinition & { - metadata: { - name: string; - namespace: string; - }; -}; - export interface CreateProviderOptions { providerKey?: string; namePrefix?: string; @@ -234,7 +227,7 @@ export const createTestNad = async ( namespace: string; bridgeName?: string; }, -): Promise => { +): Promise => { const { namespace, bridgeName = 'br0' } = options; const nadName = options.name ?? `nad-test-${crypto.randomUUID().slice(0, 8)}`;