diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..9550c3d1 --- /dev/null +++ b/.env.example @@ -0,0 +1,5 @@ +BRIDGE_BASE_ADDRESS=https://console-openshift-console.apps.your-cluster.example.com +BRIDGE_KUBEADMIN_PASSWORD=your-password +TEST_NS=cy-test-ns +UDN_NS=udn-test-ns +HIDE_XHR=true diff --git a/.gitignore b/.gitignore index bde351d5..ceb70bf2 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ integration-tests/screenshots integration-tests/.DS_Store yarn-error.log .DS_Store +cypress/gui-test-screenshots/ +.env diff --git a/package-lock.json b/package-lock.json index 8a063d2a..82ca50cd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -41,6 +41,7 @@ "css-loader": "^6.7.1", "cypress": "^15.0.0", "cypress-multi-reporters": "^2.0.0", + "dotenv": "^17.4.2", "esbuild-loader": "^4.4.2", "eslint": "^8.57.1", "eslint-config-prettier": "^8.10.0", @@ -7642,6 +7643,19 @@ "url": "https://github.com/fb55/domutils?sponsor=1" } }, + "node_modules/dotenv": { + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", diff --git a/package.json b/package.json index d3630408..ac7757f6 100644 --- a/package.json +++ b/package.json @@ -13,12 +13,14 @@ "start-console": "./start-console.sh", "i18n": "./i18n-scripts/build-i18n.sh && node ./i18n-scripts/set-english-defaults.js", "ts-node": "ts-node -O '{\"module\":\"commonjs\"}'", - "lint": "eslint ./src ./integration-tests", - "lint-fix": "eslint ./src ./integration-tests --fix", - "test-cypress": "cd integration-tests s && cypress open --env openshift=true", + "lint": "eslint ./src ./integration-tests ./ui-tests-cy", + "lint-fix": "eslint ./src ./integration-tests ./ui-tests-cy --fix", + "test-cypress": "cd integration-tests && cypress open --env openshift=true", "test-cypress-headless": "cd integration-tests && node --max-old-space-size=4096 ../node_modules/.bin/cypress run --env openshift=true --browser ${BRIDGE_E2E_BROWSER_NAME:=electron}", - "cypress-merge": "mochawesome-merge ./integration-tests/screenshots/cypress_report*.json > ./integration-tests/screenshots/cypress.json", - "cypress-generate": "marge -o ./integration-tests/screenshots/ -f cypress-report -t 'OpenShift Console Plugin Template Cypress Test Results' -p 'OpenShift Cypress Plugin Template Test Results' --showPassed false --assetsDir ./integration-tests/screenshots/cypress/assets ./integration-tests/screenshots/cypress.json", + "test-e2e": "cd cypress && node --max-old-space-size=4096 ../node_modules/.bin/cypress run --env openshift=true --browser ${BRIDGE_E2E_BROWSER_NAME:=electron}", + "test-e2e-open": "cd cypress && cypress open --env openshift=true", + "cypress-merge": "mochawesome-merge ./cypress/gui-test-screenshots/cypress_report*.json > ./cypress/gui-test-screenshots/cypress.json", + "cypress-generate": "marge -o ./cypress/gui-test-screenshots/ -f cypress-report -t 'Networking Console Plugin Test Results' -p 'Networking Plugin Test Results' --showPassed false --assetsDir ./cypress/gui-test-screenshots/assets ./cypress/gui-test-screenshots/cypress.json", "cypress-postreport": "npm run cypress-merge && npm run cypress-generate", "memsource-upload": "./i18n-scripts/memsource-upload.sh", "memsource-download": "./i18n-scripts/memsource-download.sh", @@ -59,6 +61,7 @@ "css-loader": "^6.7.1", "cypress": "^15.0.0", "cypress-multi-reporters": "^2.0.0", + "dotenv": "^17.4.2", "esbuild-loader": "^4.4.2", "eslint": "^8.57.1", "eslint-config-prettier": "^8.10.0", diff --git a/ui-tests-cy/.eslintrc b/ui-tests-cy/.eslintrc new file mode 100644 index 00000000..f01d92ce --- /dev/null +++ b/ui-tests-cy/.eslintrc @@ -0,0 +1,17 @@ +{ + "env": { + "cypress/globals": true, + "node": true + }, + "extends": ["../.eslintrc.json", "plugin:cypress/recommended"], + "plugins": ["cypress"], + "rules": { + "no-console": "off", + "no-namespace": "off", + "no-redeclare": "off", + "promise/catch-or-return": "off", + "promise/no-nesting": "off", + "@typescript-eslint/no-var-requires":"off", + "@typescript-eslint/no-namespace":"off" + } + } \ No newline at end of file diff --git a/ui-tests-cy/cypress.config.js b/ui-tests-cy/cypress.config.js new file mode 100644 index 00000000..f2dcc5a2 --- /dev/null +++ b/ui-tests-cy/cypress.config.js @@ -0,0 +1,31 @@ +const { defineConfig } = require('cypress'); + +module.exports = defineConfig({ + defaultCommandTimeout: 60000, + e2e: { + setupNodeEvents(on, config) { + return require('./plugins/index.ts')(on, config); + }, + specPattern: 'tests/**/*.cy.ts', + supportFile: 'support/index.ts', + testIsolation: false, + }, + fixturesFolder: false, + pageLoadTimeout: 120000, + reporter: '../node_modules/cypress-multi-reporters', + reporterOptions: { + configFile: 'reporter-config.json', + }, + retries: { + openMode: 0, + runMode: 0, + }, + screenshotOnRunFailure: true, + screenshotsFolder: './gui-test-screenshots/screenshots/', + trashAssetsBeforeRuns: true, + video: true, + videosFolder: './gui-test-screenshots/videos/', + viewportHeight: 1080, + viewportWidth: 1920, + watchForFileChanges: false, +}); diff --git a/ui-tests-cy/plugins/index.ts b/ui-tests-cy/plugins/index.ts new file mode 100644 index 00000000..9d7dc67f --- /dev/null +++ b/ui-tests-cy/plugins/index.ts @@ -0,0 +1,31 @@ +const wp = require('@cypress/webpack-preprocessor'); +const path = require('path'); +const dotenv = require('dotenv'); + +dotenv.config({ path: path.resolve(__dirname, '../../.env') }); + +module.exports = (on, config) => { + const options = { + webpackOptions: { + module: { + rules: [ + { + exclude: /node_modules/, + loader: 'esbuild-loader', + test: /\.ts$/, + }, + ], + }, + resolve: { + extensions: ['.ts', '.tsx', '.js'], + }, + }, + }; + on('file:preprocessor', wp(options)); + config.baseUrl = `${process.env.BRIDGE_BASE_ADDRESS || 'http://localhost:9000/'}`; + config.env.BRIDGE_KUBEADMIN_PASSWORD = process.env.BRIDGE_KUBEADMIN_PASSWORD; + config.env.TEST_NS = process.env.TEST_NS; + config.env.UDN_NS = process.env.UDN_NS; + config.env.HIDE_XHR = process.env.HIDE_XHR; + return config; +}; diff --git a/ui-tests-cy/reporter-config.json b/ui-tests-cy/reporter-config.json new file mode 100644 index 00000000..83ca6c5d --- /dev/null +++ b/ui-tests-cy/reporter-config.json @@ -0,0 +1,14 @@ +{ + "reporterEnabled": "mocha-junit-reporter, mochawesome", + "mochaJunitReporterReporterOptions": { + "mochaFile": "./gui-test-screenshots/junit_cypress-[hash].xml", + "toConsole": false + }, + "mochawesomeReporterOptions": { + "reportDir": "./gui-test-screenshots/", + "reportFilename": "cypress_report", + "overwrite": false, + "html": true, + "json": true + } +} \ No newline at end of file diff --git a/ui-tests-cy/support/commands.ts b/ui-tests-cy/support/commands.ts new file mode 100644 index 00000000..2de56782 --- /dev/null +++ b/ui-tests-cy/support/commands.ts @@ -0,0 +1,45 @@ +import { MINUTE, SECOND } from '../utils/const/base'; + +declare global { + namespace Cypress { + interface Chainable { + deleteResource(kind: string, name: string, namespace?: string): void; + switchProject(projectName: string): void; + } + } +} + +Cypress.Commands.add('deleteResource', (kind: string, name: string, namespace?: string) => { + const args = ['oc', 'delete', '--ignore-not-found=true']; + if (namespace) { + args.push('-n', namespace); + } + args.push(kind, name, '--wait=true', '--timeout=300s'); + cy.exec(args.join(' '), { failOnNonZeroExit: false, timeout: 5 * MINUTE }); +}); + +Cypress.Commands.add('switchProject', (projectName: string) => { + cy.byLegacyTestID('namespace-bar-dropdown').contains('Project:').click(); + if (projectName === 'All Projects') { + cy.contains('All Projects').click(); + return; + } + cy.get('body').then(($body) => { + if ($body.find('[data-test="showSystemSwitch"]').length) { + cy.byTestID('showSystemSwitch').check(); + cy.wait(3 * SECOND); + } + }); + cy.get( + 'input[data-test="dropdown-text-filter"], input[placeholder*="name"], input[aria-label*="filter"]', + { timeout: 10000 }, + ) + .first() + .then(($input) => { + cy.wrap($input).clear(); + cy.wrap($input).type(projectName); + }); + cy.contains('[data-test="dropdown-menu-item-link"], [role="option"], button', projectName, { + timeout: 10000, + }).click(); +}); diff --git a/ui-tests-cy/support/index.ts b/ui-tests-cy/support/index.ts new file mode 100644 index 00000000..37d65a8a --- /dev/null +++ b/ui-tests-cy/support/index.ts @@ -0,0 +1,20 @@ +import './login'; +import './selectors'; +import './commands'; +import './nav'; + +Cypress.on('uncaught:exception', (err) => { + if (err.message.includes('ResizeObserver') || err.message.includes('Script error')) { + return false; + } +}); + +if (Cypress.env('HIDE_XHR')) { + const origLog = Cypress.log; + Cypress.log = function (opts, ...other) { + if (opts.displayName === 'fetch' || opts.displayName === 'xhr') { + return; + } + return origLog(opts, ...other); + }; +} diff --git a/ui-tests-cy/support/login.ts b/ui-tests-cy/support/login.ts new file mode 100644 index 00000000..39df9b4f --- /dev/null +++ b/ui-tests-cy/support/login.ts @@ -0,0 +1,60 @@ +import { MINUTE } from '../utils/const/base'; + +const KUBEADMIN_USERNAME = 'kubeadmin'; +const KUBEADMIN_IDP = 'kube:admin'; +const TOUR_DISMISS = '[data-test="tour-step-footer-secondary"]'; + +declare global { + namespace Cypress { + interface Chainable { + login(providerName?: string, username?: string, password?: string): Chainable; + logout(): void; + } + } +} + +Cypress.Commands.add('login', (provider: string, username: string, password: string) => { + const usr = username || KUBEADMIN_USERNAME; + const pwd = password || Cypress.env('BRIDGE_KUBEADMIN_PASSWORD'); + const idp = provider || KUBEADMIN_IDP; + + cy.visit(''); + + cy.origin( + Cypress.config('baseUrl').replace('console-openshift-console', 'oauth-openshift'), + { args: { idp, pwd, usr } }, + ({ idp: originIdp, pwd: originPwd, usr: originUsr }) => { + // Wait for either the login form or IDP selection page + cy.get('body', { timeout: 180000 }).should('be.visible'); + cy.get('body').then(($body) => { + if ($body.find('#inputUsername').length === 0) { + // IDP selection page — click the matching provider + if ($body.text().includes(originIdp)) { + cy.contains('a', originIdp).click(); + } else if ($body.text().includes('kubeadmin')) { + cy.contains('a', 'kubeadmin').click(); + } else { + cy.get('a').first().click(); + } + } + }); + cy.get('#inputUsername', { timeout: 180000 }).should('be.visible'); + cy.get('#inputUsername').type(originUsr); + cy.get('#inputPassword').type(originPwd, { log: false }); + cy.get('button[type=submit]').click(); + }, + ); + + cy.url({ timeout: 2 * MINUTE }).should('include', 'console-openshift-console'); + cy.get('body').then(($body) => { + if ($body.find(TOUR_DISMISS).length) { + cy.get(TOUR_DISMISS).click(); + } + }); +}); + +Cypress.Commands.add('logout', () => { + cy.get('[data-test="user-dropdown"]').click(); + cy.get('[data-test="log-out"]').should('be.visible'); + cy.get('[data-test="log-out"]').click({ force: true }); +}); diff --git a/ui-tests-cy/support/nav.ts b/ui-tests-cy/support/nav.ts new file mode 100644 index 00000000..bde8f12d --- /dev/null +++ b/ui-tests-cy/support/nav.ts @@ -0,0 +1,28 @@ +import { MINUTE } from '../utils/const/base'; + +declare global { + namespace Cypress { + interface Chainable { + visitNAD(): void; + visitService(): void; + visitUDN(): void; + } + } +} + +Cypress.Commands.add('visitNAD', () => { + const ns = Cypress.env('TEST_NS') || 'cy-test-ns'; + cy.visit(`/k8s/ns/${ns}/k8s.cni.cncf.io~v1~NetworkAttachmentDefinition`); + cy.checkTitle('NetworkAttachmentDefinitions', MINUTE); +}); + +Cypress.Commands.add('visitUDN', () => { + cy.visit('/k8s/all-namespaces/k8s.ovn.org~v1~UserDefinedNetwork'); + cy.checkTitle('UserDefinedNetworks', MINUTE); + cy.byButtonText('Create').should('be.visible'); +}); + +Cypress.Commands.add('visitService', () => { + cy.visit('/k8s/all-namespaces/services'); + cy.checkTitle('Services', MINUTE); +}); diff --git a/ui-tests-cy/support/selectors.ts b/ui-tests-cy/support/selectors.ts new file mode 100644 index 00000000..30212a96 --- /dev/null +++ b/ui-tests-cy/support/selectors.ts @@ -0,0 +1,81 @@ +import Loggable = Cypress.Loggable; +import Timeoutable = Cypress.Timeoutable; +import Withinable = Cypress.Withinable; +import Shadow = Cypress.Shadow; +import { MINUTE, SECOND } from '../utils/const/base'; + +export {}; +declare global { + namespace Cypress { + interface Chainable { + byButtonText(selector: string): Chainable; + byLegacyTestID(selector: string): Chainable; + byTestActionID(selector: string): Chainable; + byTestID( + selector: string, + options?: Partial, + ): Chainable; + byTestRows(selector: string): Chainable; + checkSubTitle(title: string, timeout?: number): void; + checkTitle(title: string, timeout?: number): void; + clickBtn(btnTxt: string): void; + clickNavLink(path: [string, string?]): Chainable; + clickNextBtn(): void; + clickSaveBtn(): void; + } + } +} + +Cypress.Commands.add( + 'byTestID', + (selector: string, options?: Partial) => { + return cy.get(`[data-test="${selector}"]`, options); + }, +); + +Cypress.Commands.add('byLegacyTestID', (selector: string) => + cy.get(`[data-test-id="${selector}"]`, { timeout: MINUTE * 3 }), +); + +Cypress.Commands.add('byTestRows', (selector: string) => + cy.get(`[data-test-rows="${selector}"]`, { timeout: MINUTE }), +); + +Cypress.Commands.add('byTestActionID', (selector: string) => + cy.get(`[data-test-action="${selector}"]:not(.pf-m-disabled)`), +); + +Cypress.Commands.add('clickNavLink', (path: [string, string?]) => { + cy.byTestID('nav', { timeout: MINUTE }) + .contains(path[0], { timeout: SECOND * 10 }) + .should(($el) => { + if ($el.attr('aria-expanded') == 'false') { + $el.click(); + } + }); + if (path.length > 1) { + cy.get('#page-sidebar').contains(path[1]).click(); + } +}); + +Cypress.Commands.add('byButtonText', (selector: string) => cy.contains('button', `${selector}`)); + +Cypress.Commands.add('clickSaveBtn', () => { + cy.get('button[data-test="save-button"]').click({ force: true }); +}); + +Cypress.Commands.add('checkTitle', (title: string, timeout?: number) => { + const t_o = timeout ? timeout : MINUTE * 3; + cy.contains('h1', title, { timeout: t_o }).should('exist'); +}); + +Cypress.Commands.add('checkSubTitle', (subTitle: string, timeout?: number) => { + const t_o = timeout ? timeout : MINUTE * 3; + cy.contains('h2', subTitle, { timeout: t_o }).should('exist'); +}); + +Cypress.Commands.add('clickNextBtn', () => cy.contains('button[type="submit"]', 'Next').click()); + +Cypress.Commands.add('clickBtn', (btnTxt?: string) => { + return cy.contains('button[type="submit"]', btnTxt).click(); +}); diff --git a/ui-tests-cy/tsconfig.json b/ui-tests-cy/tsconfig.json new file mode 100644 index 00000000..c8f71161 --- /dev/null +++ b/ui-tests-cy/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "noEmit": true, + "types":["cypress","node"], + "isolatedModules": false + }, + "include": ["../node_modules/cypress", "./**/*.ts"] +} \ No newline at end of file diff --git a/ui-tests-cy/utils/const/base.ts b/ui-tests-cy/utils/const/base.ts new file mode 100644 index 00000000..432cbbfd --- /dev/null +++ b/ui-tests-cy/utils/const/base.ts @@ -0,0 +1,23 @@ +export const adminOnlyDescribe = Cypress.env('NON_PRIV') ? xdescribe : describe; +export const adminOnlyIT = Cypress.env('NON_PRIV') ? xit : it; + +export const TEST_NS = Cypress.env('TEST_NS') || 'cy-test-ns'; +export const UDN_NS = Cypress.env('UDN_NS') || 'udn-test-ns'; + +export const EXAMPLE = 'example'; +export const ALL_PROJ_NS = 'All Projects'; +export const TEST_ROUTE_NAME = 'auto-test-route'; +export const TEST_POL_NAME = 'auto-test-net-policy'; +export const TEST_MULTI_NAME = 'auto-test-multi-policy'; + +export const SECOND = 1000; +export const MINUTE = 60000; + +export const UDN_LABEL = 'k8s.ovn.org/primary-user-defined-network'; + +export enum K8S_KIND { + CUDN = 'ClusterUserDefinedNetwork', + NAD = 'net-attach-def', + Project = 'project', + UDN = 'UserDefinedNetwork', +} diff --git a/ui-tests-cy/utils/const/nad.ts b/ui-tests-cy/utils/const/nad.ts new file mode 100644 index 00000000..96cc3434 --- /dev/null +++ b/ui-tests-cy/utils/const/nad.ts @@ -0,0 +1,28 @@ +import { NadData } from '../types/nad'; + +export const bridge = 'br0'; + +export const NAD_BRIDGE: NadData = { + bridge: bridge, + description: 'bridge nad', + macSpoof: true, + name: 'network-bridge', + type: 'Bridge', + vlan: '100', +}; + +export const NAD_OVN: NadData = { + description: 'ovn nad', + name: 'network-ovn', + type: 'OVN', + vlan: '200', +}; + +export const NAD_LOCALNET: NadData = { + bridge: bridge, + description: 'localnet nad', + mtu: '1500', + name: 'network-localnet', + type: 'Localnet', + vlan: '300', +}; diff --git a/ui-tests-cy/utils/types/nad.ts b/ui-tests-cy/utils/types/nad.ts new file mode 100644 index 00000000..5d8898bb --- /dev/null +++ b/ui-tests-cy/utils/types/nad.ts @@ -0,0 +1,11 @@ +export type NadData = { + bridge?: string; + description?: string; + exclude?: string; + macSpoof?: boolean; + mtu?: string; + name?: string; + subnet?: string; + type?: string; + vlan?: string; +}; diff --git a/ui-tests-cy/views/actions.ts b/ui-tests-cy/views/actions.ts new file mode 100644 index 00000000..f41d8972 --- /dev/null +++ b/ui-tests-cy/views/actions.ts @@ -0,0 +1,12 @@ +export const nameInput = 'input#name'; + +export const getRow = (name: string, within: VoidFunction) => + cy.byTestRows('resource-row').contains(name).parents('tr').within(within); + +export const checkActionMenu = (item: string) => { + cy.byButtonText('Actions').click(); + cy.contains('button', 'Edit labels').should('exist'); + cy.contains('button', 'Edit annotations').should('exist'); + cy.contains('button', `Edit ${item}`).should('exist'); + cy.contains('button', `Delete ${item}`).should('exist'); +}; diff --git a/ui-tests-cy/views/nad.ts b/ui-tests-cy/views/nad.ts new file mode 100644 index 00000000..7fa38635 --- /dev/null +++ b/ui-tests-cy/views/nad.ts @@ -0,0 +1,84 @@ +import { NadData } from '../utils/types/nad'; + +import { row } from './selector-common'; + +export const name = 'input[name="name"]'; +export const description = 'input[name="description"]'; +export const type = '#toggle-nads-network-type'; +export const cnvBridgeLink = '[data-test-dropdown-menu="cnv-bridge"]'; +export const ovnNetwork = 'button[id="ovn-k8s-cni-overlay-link"]'; +export const localnet = 'button[id="ovn-k8s-cni-overlay-localnet-link"]'; +export const bridgeName = 'input[name="bridge.bridge"]'; +export const bridgeVlan = 'input[name="bridge.vlanTagNum"]'; +export const bridgeMap = 'input[name="ovn-k8s-cni-overlay-localnet.bridgeMapping"]'; +export const bridgeMTU = 'input[name="ovn-k8s-cni-overlay-localnet.mtu"]'; +export const localnetVlan = 'input[name="ovn-k8s-cni-overlay-localnet.vlanID"]'; +export const confirmBtn = '[data-test="confirm-action"]'; +export const heading = '[data-test-section-heading="NetworkAttachmentDefinition details"]'; +export const createBtn = '#save-changes'; +export const macSpoofCHK = 'input[id="bridge.macspoofchk"]'; +export const ovnSubnet = 'input[name="ovn-k8s-cni-overlay.subnets"]'; +export const localnetSubnet = 'input[name="ovn-k8s-cni-overlay-localnet.subnets"]'; +export const exclude = 'input[name="ovn-k8s-cni-overlay-localnet.excludeSubnets"]'; + +export const createNAD = (nad: NadData) => { + cy.visitNAD(); + cy.byButtonText('Create NetworkAttachmentDefinition').click(); + cy.get('#form').check(); + cy.get(name, { timeout: 10000 }).should('be.visible'); + cy.get(name).clear(); + cy.get(name).type(nad.name); + cy.get(description).clear(); + cy.get(description).type(nad.description); + cy.get(type).click(); + switch (nad.type) { + case 'Bridge': { + cy.byButtonText('Linux bridge').click(); + cy.get(bridgeName).clear(); + cy.get(bridgeName).type(nad.bridge); + if (nad.vlan) { + cy.get(bridgeVlan).clear(); + cy.get(bridgeVlan).type(nad.vlan); + } + if (!nad.macSpoof) { + cy.get(macSpoofCHK).uncheck(); + } + break; + } + case 'OVN': { + cy.byButtonText('L2 overlay').click(); + if (nad.subnet) { + cy.get(ovnSubnet).type(nad.subnet); + } + break; + } + case 'Localnet': { + cy.byButtonText('secondary localnet').click(); + cy.get(bridgeMap).clear(); + cy.get(bridgeMap).type(nad.bridge); + cy.get(bridgeMTU).clear(); + cy.get(bridgeMTU).type(nad.mtu); + if (nad.vlan) { + cy.get(localnetVlan).clear(); + cy.get(localnetVlan).type(nad.vlan); + } + if (nad.subnet) { + cy.get(localnetSubnet).type(nad.subnet); + cy.get(exclude).type(nad.exclude); + } + break; + } + } + cy.get(createBtn).click(); + cy.get(heading).should('exist'); + cy.contains('[data-test-selector="details-item-label__Description"]', 'Description') + .parent() + .should('contain', nad.description); +}; + +export const deleteNAD = (nadName: string) => { + cy.contains(row, nadName).find('.pf-v6-c-table__action').find('button').click(); + cy.byButtonText('Delete').click(); + cy.get(confirmBtn).click(); + cy.contains(row, nadName).should('not.exist'); +}; diff --git a/ui-tests-cy/views/selector-common.ts b/ui-tests-cy/views/selector-common.ts new file mode 100644 index 00000000..77ed1559 --- /dev/null +++ b/ui-tests-cy/views/selector-common.ts @@ -0,0 +1,14 @@ +export const row = '[data-test-rows="resource-row"]'; +export const actionsBtn = 'button.pf-v6-c-menu-toggle.pf-m-plain'; +export const itemCreateBtn = '[data-test="item-create"]'; +export const labelContent = '.pf-v6-c-label__content'; +export const brCrumbItem = '[aria-label="Breadcrumb"]'; +export const itemFilter = '[data-test-id="item-filter"]'; +export const createBtn = '#save-changes'; +export const confirmBtn = '[data-test="confirm-action"]'; +export const dropDownItem = '.pf-v6-c-dropdown__menu-item'; + +export const ingressHeader = '#ingress-header'; +export const egressHeader = '#egress-header'; +export const ingressOff = '#denyAllIngress'; +export const egressOff = '#denyAllEgress'; diff --git a/ui-tests-cy/views/udn.ts b/ui-tests-cy/views/udn.ts new file mode 100644 index 00000000..8b063796 --- /dev/null +++ b/ui-tests-cy/views/udn.ts @@ -0,0 +1,39 @@ +import { ALL_PROJ_NS } from '../utils/const/base'; + +import { checkActionMenu } from './actions'; +import { row } from './selector-common'; +import { brCrumbItem } from './selector-common'; + +export const UDN_PRIM_UDN_NS = 'primary-udn'; + +export const createUDN = (project: string, subnet: string) => { + cy.visitUDN(); + cy.byButtonText('Create').click(); + cy.get('body').then(($body) => { + if ($body.find('[data-test="list-page-create-dropdown-item-UserDefinedNetwork"]').length) { + cy.get('[data-test="list-page-create-dropdown-item-UserDefinedNetwork"]') + .find('button') + .click(); + } + }); + cy.checkTitle('Create UserDefinedNetwork'); + cy.get('#typeahead-select-input').find('input').type(project); + cy.get(`#select-typeahead-${project}`, { timeout: 10000 }).click(); + cy.byTestID('input-udn-subnet').clear(); + cy.byTestID('input-udn-subnet').type(subnet); + cy.byTestID('create-udn-submit').should('not.be.disabled').click(); + cy.checkTitle(UDN_PRIM_UDN_NS); + checkActionMenu('UserDefinedNetwork'); + cy.contains(brCrumbItem, 'UserDefinedNetworks').find('a').eq(0).click(); + cy.contains(row, UDN_PRIM_UDN_NS).should('exist'); +}; + +export const deleteUDN = () => { + cy.visitUDN(); + cy.switchProject(ALL_PROJ_NS); + cy.contains('[data-label="name"]', UDN_PRIM_UDN_NS).find('a').click(); + cy.byButtonText('Actions').click(); + cy.get('[data-test-action="Delete UserDefinedNetwork"]').click(); + cy.byTestID('confirm-action').click(); + cy.contains(UDN_PRIM_UDN_NS).should('not.exist'); +};