Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CRD validation cypress tests #418

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 20 additions & 4 deletions plugin/cypress/integration/kiali/common/istio_config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,14 @@ Then('the AuthorizationPolicy should have a {string}', function (healthStatus: s
);
});

const hexToRgb = (hex: string): string => {
const rValue = parseInt(hex.substring(0, 2), 16);
const gValue = parseInt(hex.substring(2, 4), 16);
const bValue = parseInt(hex.substring(4), 16);

return `rgb(${rValue}, ${gValue}, ${bValue})`;
};

function waitUntilConfigIsVisible(
attempt: number,
crdInstanceName: string,
Expand All @@ -626,22 +634,30 @@ function waitUntilConfigIsVisible(
cy.request({ method: 'GET', url: `${Cypress.config('baseUrl')}/api/istio/config?refresh=0` });
cy.get('[data-test="refresh-button"]').click();
ensureKialiFinishedLoading();

let found = false;
cy.get('tr')
.each($row => {
const dataTestAttr = $row[0].attributes.getNamedItem('data-test');
const hasNA = $row[0].innerText.includes('N/A');

if (dataTestAttr !== null) {
if (dataTestAttr.value === `VirtualItem_Ns${namespace}_${crdName}_${crdInstanceName}` && !hasNA) {
// Check if the health status icon is correct
cy.get(`[data-test=VirtualItem_Ns${namespace}_${crdName}_${crdInstanceName}] span.pf-v5-c-icon`)
.should('be.visible')
.then(icon => {
const colorVar = `--pf-v5-global--${healthStatus}-color--100`;
const color = getComputedStyle(icon[0]).getPropertyValue(colorVar);
if (color) {
found = true;
}
const statusColor = getComputedStyle(icon[0]).getPropertyValue(colorVar).replace('#', '');

cy.wrap(icon[0])
.invoke('css', 'color')
.then(iconColor => {
// Convert the status color to RGB format to compare it with the icon color
if (iconColor.toString() === hexToRgb(statusColor)) {
found = true;
}
});
});
}
}
Expand Down
48 changes: 39 additions & 9 deletions plugin/cypress/integration/kiali/common/mesh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,27 @@ When('user selects mesh node with label {string}', (label: string) => {
assert.isTrue(controller.hasGraph());

const { nodes } = elems(controller);
const node = nodes.find(n => n.getLabel() === label);
const node = nodes.find(n => n.getLabel().toLowerCase() === label.toLowerCase());
assert.exists(node);

const setSelectedIds = state.meshRefs.setSelectedIds as (values: string[]) => void;
setSelectedIds([node!.getId()]);
});
});

When('user selects tracing mesh node', () => {
cy.waitForReact();
cy.get('#loading_kiali_spinner').should('not.exist');
cy.getReact('MeshPageComponent', { state: { isReady: true } })
.should('have.length', 1)
.then($graph => {
const { state } = $graph[0];

const controller = state.meshRefs.getController() as Visualization;
assert.isTrue(controller.hasGraph());

const { nodes } = elems(controller);
const node = nodes.find(n => n.getLabel().toLowerCase() === 'jaeger' || n.getLabel().toLowerCase() === 'tempo');
assert.exists(node);

const setSelectedIds = state.meshRefs.setSelectedIds as (values: string[]) => void;
Expand Down Expand Up @@ -130,17 +150,17 @@ Then('user sees expected mesh infra', () => {
assert.isTrue(controller.hasGraph());

const { nodes, edges } = elems(controller);
const nodeNames = nodes.map(n => n.getLabel());
const nodesLength = nodeNames.some(n => n === 'External Deployments') ? 9 : 8;
const nodeNames = nodes.map(n => n.getLabel().toLowerCase());
const minNodesLength = nodeNames.some(n => n === 'external deployments') ? 9 : 8;

assert.equal(nodes.length, nodesLength, 'Unexpected number of infra nodes');
assert.equal(edges.length, 5, 'Unexpected number of infra edges');
assert.isTrue(nodeNames.some(n => n === 'Data Plane'));
assert.isTrue(nodeNames.some(n => n === 'Grafana'));
assert.isAtLeast(nodes.length, minNodesLength, 'Unexpected number of infra nodes');
assert.isAtLeast(edges.length, 5, 'Unexpected number of infra edges');
assert.isTrue(nodeNames.some(n => n === 'data plane'));
assert.isTrue(nodeNames.some(n => n === 'grafana'));
assert.isTrue(nodeNames.some(n => n.startsWith('istiod')));
assert.isTrue(nodeNames.some(n => n === 'jaeger' || n === 'Tempo'));
assert.isTrue(nodeNames.some(n => n === 'jaeger' || n === 'tempo'));
assert.isTrue(nodeNames.some(n => n === 'kiali'));
assert.isTrue(nodeNames.some(n => n === 'Prometheus'));
assert.isTrue(nodeNames.some(n => n === 'prometheus'));
});
});

Expand Down Expand Up @@ -220,3 +240,13 @@ Then('user sees {string} node side panel', (name: string) => {
cy.contains(name);
});
});

Then('user sees tracing node side panel', () => {
cy.waitForReact();
cy.get('#loading_kiali_spinner').should('not.exist');
cy.get('#target-panel-node')
.should('be.visible')
.within(() => {
cy.contains(new RegExp('jaeger|Jaeger|tempo|Tempo', 'g'));
});
});
6 changes: 3 additions & 3 deletions plugin/cypress/integration/kiali/featureFiles/mesh.feature
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ Feature: Kiali Mesh page
When user selects mesh node with label "Grafana"
Then user sees "Grafana" node side panel

Scenario: Jaeger Infra
When user selects mesh node with label "jaeger"
Then user sees "jaeger" node side panel
Scenario: Tracing Infra
When user selects tracing mesh node
Then user sees tracing node side panel

Scenario: Prometheus Infra
When user selects mesh node with label "Prometheus"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ Feature: Kiali Service Details page
Then user sees trace details

@bookinfo-app
@tracing-tracing
@tracing
@waypoint-tracing
Scenario: See span info after selecting service span
And user sees trace information
When user selects a trace
Expand Down
13 changes: 0 additions & 13 deletions plugin/cypress/integration/kiali/featureFiles/waypoint.feature
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,10 @@ Feature: Kiali Waypoint related features
Background:
Given user is at administrator perspective

@waypoint
Scenario: [Setup] namespace is labeled with waypoint label
Then "bookinfo" namespace is labeled with the waypoint label
And the graph page has enough data

@waypoint
Scenario: [Workload list] See the workload list of bookinfo with the correct info
Given user is at the "workloads" list page
When user selects the "bookinfo" namespace
Expand All @@ -27,7 +25,6 @@ Feature: Kiali Waypoint related features
And the "Details" column on the "waypoint" row has the text "Waypoint Proxy"
And the "Details" column on the "waypoint" row has a link ending in "bookinfo/istio/gateway.networking.k8s.io/v1/Gateway/waypoint"

@waypoint
Scenario: [Workload details - productpage] The workload productpage is enrolled in waypoint
Given user is at the details page for the "workload" "bookinfo/productpage-v1" located in the "" cluster
Then user sees "ambient" badge
Expand All @@ -38,7 +35,6 @@ Feature: Kiali Waypoint related features
And the user sees the L7 "waypoint" link
And the link for the waypoint "waypoint" should redirect to a valid workload details

@waypoint
Scenario: [Workload details - waypoint] The workload details for a waypoint are valid
Given user is at the details page for the "workload" "bookinfo/waypoint" located in the "" cluster
Then the user sees the "L7" badge
Expand All @@ -56,14 +52,12 @@ Feature: Kiali Waypoint related features
Then user goes to the waypoint "Info" subtab
And validates waypoint Info data

@waypoint
Scenario: [Workload details - ztunnel] The workload details for a ztunnel are valid
Given user is at the details page for the "workload" "istio-system/ztunnel" located in the "" cluster
Then the user cannot see the "missing-sidecar" badge for "ztunnel" workload in "istio-system" namespace
And the proxy status is "healthy"
And the user validates the Ztunnel tab

@waypoint
Scenario: [Traffic Graph] User sees ztunnel traffic
Given user is at the "graphpf" page
When user graphs "bookinfo" namespaces
Expand All @@ -72,7 +66,6 @@ Feature: Kiali Waypoint related features
And user "enables" "ambientZtunnel" traffic option
Then 7 edges appear in the graph

@waypoint
Scenario: [Traffic Graph] User sees no Ambient traffic
Given user is at the "graphpf" page
When user graphs "bookinfo" namespaces
Expand All @@ -81,7 +74,6 @@ Feature: Kiali Waypoint related features
And user "disables" "ambient" traffic option
Then 2 edges appear in the graph

@waypoint
Scenario: [Traffic Graph] User sees all Ambient traffic
Given user is at the "graphpf" page
When user graphs "bookinfo" namespaces
Expand All @@ -91,11 +83,9 @@ Feature: Kiali Waypoint related features
And user "enables" "ambient" traffic option
Then 16 edges appear in the graph

@waypoint
Scenario: [Traffic Graph] User doesn't see waypoint proxy
And the "waypoint" node "doesn't" exists

@waypoint
Scenario: [Traffic Graph] User sees waypoint proxy
When user opens display menu
Then the display menu opens
Expand All @@ -105,7 +95,6 @@ Feature: Kiali Waypoint related features
Then 16 edges appear in the graph
And the "waypoint" node "does" exists

@waypoint
Scenario: [Traffic Graph] User sees waypoint traffic
Given user is at the "graphpf" page
When user graphs "bookinfo" namespaces
Expand All @@ -114,13 +103,11 @@ Feature: Kiali Waypoint related features
And user "enables" "ambientWaypoint" traffic option
Then 11 edges appear in the graph

@waypoint
Scenario: [Istio Config] Waypoint should not have validation errors
Given user is at the "istio" page
And user selects the "bookinfo" namespace
Then the "K8sGateway" object in "bookinfo" namespace with "waypoint" name Istio Config is valid

@waypoint
Scenario: [Overview] Namespace is labeled with the waypoint labels
Given user is at the "overview" page
When user clicks in the "LIST" view
Expand Down
86 changes: 86 additions & 0 deletions plugin/cypress/integration/openshift/common/istio_config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Given, Then, When } from '@badeball/cypress-cucumber-preprocessor';
import { getColWithRowText } from './table';
import { istioResources, referenceFor } from './istio_resources';
import { K8sGroupVersionKind } from '@openshift-console/dynamic-plugin-sdk';
import { ensureOSSMCFinishedLoading } from './sidebar_navigation';

Given('user is at the istio config list page', () => {
// Forcing "Pause" to not cause unhandled promises from the browser when cypress is testing
Expand Down Expand Up @@ -80,3 +81,88 @@ Then('the user can create a {string} K8s Istio object in ossmc', (object: string
}
});
});

Then('the AuthorizationPolicy should have a {string} in ossmc', function (healthStatus: string) {
waitUntilConfigIsVisible(
3,
this.targetAuthorizationPolicy,
'AuthorizationPolicy',
this.targetNamespace,
healthStatus
);
});

const hexToRgb = (hex: string): string => {
const rValue = parseInt(hex.substring(0, 2), 16);
const gValue = parseInt(hex.substring(2, 4), 16);
const bValue = parseInt(hex.substring(4), 16);

return `rgb(${rValue}, ${gValue}, ${bValue})`;
};

function waitUntilConfigIsVisible(
attempt: number,
crdInstanceName: string,
crdName: string,
namespace: string,
healthStatus: string
): void {
if (attempt === 0) {
throw new Error(`Condition not met after retries`);
}

cy.reload(true);
ensureOSSMCFinishedLoading();

let found = false;
// Get the link of the item name to distinguish each row
cy.get('td#name a')
.each($link => {
const hRefAttr = $link[0].attributes.getNamedItem('href');

if (hRefAttr !== null) {
const istioResource = istioResources.find(item => item.id.toLowerCase() === crdName.toLowerCase());

if (
istioResource &&
hRefAttr.value ===
`/k8s/ns/${namespace}/${referenceFor(istioResource as K8sGroupVersionKind)}/${crdInstanceName}/ossmconsole`
) {
// Get the row to check the configuration icon
cy.wrap($link)
.parent()
.parent()
.then($row => {
const hasNA = $row[0].innerText.includes('N/A');

if (!hasNA) {
cy.wrap($row)
.find('span.pf-v5-c-icon')
.should('be.visible')
.then(icon => {
const colorVar = `--pf-v5-global--${healthStatus}-color--100`;
const statusColor = getComputedStyle(icon[0]).getPropertyValue(colorVar).replace('#', '');

cy.wrap(icon[0]).should('have.css', 'color', hexToRgb(statusColor));

found = true;
});
}
});
}
}
})
.then(() => {
if (!found) {
cy.wait(10000);
waitUntilConfigIsVisible(attempt - 1, crdInstanceName, crdName, namespace, healthStatus);
}
});
}

Then(
'the {string} {string} of the {string} namespace should have a {string} in ossmc',
(crdInstanceName: string, crdName: string, namespace: string, healthStatus: string) => {
waitUntilConfigIsVisible(3, crdInstanceName, crdName, namespace, healthStatus);
}
);
20 changes: 13 additions & 7 deletions plugin/cypress/integration/openshift/common/istio_resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,12 +131,18 @@ export const referenceFor = (groupVersionKind: K8sGroupVersionKind): string => {
return `${groupVersionKind.group}~${groupVersionKind.version}~${groupVersionKind.kind}`;
};

export const refForKialiIstio = (objectType: string, details: string): string => {
const kialiIstioResource = istioResources.find(item => objectType === item.objectType);
// This helper would translate Istio Kiali format
// i.e. /istio/networking.istio.io/v1/DestinationRule/reviews
// Into the regular format used for resources in OpenShift
// i.e. /networking.istio.io~v1~DestinationRule/reviews
export const refForKialiIstio = (kialiIstioUrl: string): string => {
const kialiIstioResource = kialiIstioUrl.split('/');

if (kialiIstioResource) {
return `/${referenceFor(kialiIstioResource)}${details}`;
}
const groupVersionKind = {
group: kialiIstioResource[2],
version: kialiIstioResource[3],
kind: kialiIstioResource[4]
};

return '';
};
return `/${referenceFor(groupVersionKind)}/${kialiIstioResource[5]}`;
};
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { Then, When } from '@badeball/cypress-cucumber-preprocessor';

export const ensureOSSMCFinishedLoading = () => {
cy.waitForReact(5000, '#app', 'node_modules/resq/dist/index.js'); // Manually passing in the resq module path
};

When('user is at the dashboard page', () => {
cy.visit({ url: `/` });
});
Expand All @@ -21,7 +25,7 @@ When('cypress intercept hooks for sidebar are registered', () => {
});

Then('buttons for Overview, Graph and Istio Config are displayed', () => {
cy.waitForReact(5000, '#app', 'node_modules/resq/dist/index.js'); // Manually passing in the resq module path
ensureOSSMCFinishedLoading();
cy.reload(true); // force reload to make sure OSSMC is loaded
cy.get('a[data-test="nav"][class*="pf-v5-c-nav__link"]').contains('Overview');
cy.get('a[data-test="nav"][class*="pf-v5-c-nav__link"]').contains('Graph');
Expand Down Expand Up @@ -91,7 +95,7 @@ Then(`user sees the {string} graph summary`, (ns: string) => {

Then('user sees the mesh side panel', () => {
cy.wait('@meshRequest').then(interception => {
cy.get('#loading_kiali_spinner').should('not.exist');
ensureOSSMCFinishedLoading();
cy.get('#target-panel-mesh')
.should('be.visible')
.within(() => {
Expand Down
Loading