Skip to content

Commit 45774eb

Browse files
authored
Merge pull request #17453 from yonasberhe23/fix_cluster_tools_tests_215
Automation: Cluster Tools - Add resource polling for reliability and refactor
2 parents 67aad0f + 2faefe0 commit 45774eb

3 files changed

Lines changed: 154 additions & 53 deletions

File tree

Lines changed: 91 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,45 @@
11
import ClusterToolsPagePo from '@/cypress/e2e/po/pages/explorer/cluster-tools.po';
22
import ClusterDashboardPagePo from '@/cypress/e2e/po/pages/explorer/cluster-dashboard.po';
33
import { InstallChartPage } from '@/cypress/e2e/po/pages/explorer/charts/install-charts.po';
4-
import { CLUSTER_APPS_BASE_URL } from '@/cypress/support/utils/api-endpoints';
4+
import { CLUSTER_APPS_BASE_URL, CLUSTER_REPOS_BASE_URL } from '@/cypress/support/utils/api-endpoints';
55
import { runTestWhenChartAvailable } from '@/cypress/support/commands/rancher-api-commands';
66
import Kubectl from '@/cypress/e2e/po/components/kubectl.po';
77
import PromptRemove from '@/cypress/e2e/po/prompts/promptRemove.po';
88
import { MEDIUM_TIMEOUT_OPT } from '@/cypress/support/utils/timeouts';
9+
import LoadingPo from '@/cypress/e2e/po/components/loading.po';
10+
import { qase } from '@/cypress/support/qase';
911

1012
const clusterTools = new ClusterToolsPagePo('local');
1113
const kubectl = new Kubectl();
14+
const loadingPo = new LoadingPo('.loading-indicator');
1215

1316
describe('Cluster Tools', { tags: ['@explorer2', '@adminUser'] }, () => {
17+
const CHART = {
18+
name: 'Alerting Drivers',
19+
id: 'rancher-alerting-drivers',
20+
repo: 'rancher-charts'
21+
};
22+
const NAMESPACE = 'default';
23+
24+
const cleanup = () => {
25+
cy.createRancherResource('v1', `catalog.cattle.io.apps/${ NAMESPACE }/${ CHART.id }?action=uninstall`, '{}', false);
26+
cy.waitForRancherResource('v1', 'catalog.cattle.io.apps', `${ NAMESPACE }/${ CHART.id }`, (resp: any) => resp.status === 404, 20, { failOnStatusCode: false });
27+
};
28+
29+
const waitForToolsPage = () => {
30+
clusterTools.goTo();
31+
clusterTools.waitForPage();
32+
cy.wait('@fetchChartData', MEDIUM_TIMEOUT_OPT).its('response.statusCode').should('eq', 200);
33+
cy.get('@fetchChartData.all').should('have.length.at.least', 3);
34+
loadingPo.checkNotExists(MEDIUM_TIMEOUT_OPT);
35+
};
36+
1437
beforeEach(() => {
1538
cy.login();
39+
cy.intercept('GET', `${ CLUSTER_REPOS_BASE_URL }/**`).as('fetchChartData');
1640
});
1741

18-
it('can navigate to cluster tools and see all feature charts', () => {
42+
qase(2061, it('can navigate to cluster tools and see all feature charts', () => {
1943
const clusterDashboard = new ClusterDashboardPagePo('local');
2044

2145
clusterDashboard.goTo();
@@ -26,72 +50,89 @@ describe('Cluster Tools', { tags: ['@explorer2', '@adminUser'] }, () => {
2650
cy.getClusterToolsChartCount().then((expectedCount) => {
2751
clusterTools.featureChartCards().should('have.length', expectedCount);
2852
});
29-
});
53+
}));
54+
55+
describe('Install', () => {
56+
beforeEach(function() {
57+
runTestWhenChartAvailable(CHART.repo, CHART.id, this, cleanup);
58+
});
3059

31-
it('can deploy chart successfully', function() {
32-
runTestWhenChartAvailable('rancher-charts', 'rancher-alerting-drivers', this, () => {
33-
// Clean up any previously installed chart before each attempt, so retries start with a clean state
34-
cy.createRancherResource('v1', 'catalog.cattle.io.apps/default/rancher-alerting-drivers?action=uninstall', '{}', false);
35-
cy.waitForRancherResource('v1', 'catalog.cattle.io.apps', 'default/rancher-alerting-drivers', (resp: any) => resp.status === 404, 20, { failOnStatusCode: false });
60+
qase(2062, it('can deploy chart successfully', function() {
61+
runTestWhenChartAvailable(CHART.repo, CHART.id, this, () => {
62+
waitForToolsPage();
3663

37-
clusterTools.goTo();
38-
clusterTools.waitForPage();
39-
clusterTools.getChartVersion('Alerting Drivers').invoke('text').then((el) => {
40-
const chartVersion = el.trim();
64+
clusterTools.getChartVersion(CHART.name).invoke('text').then((el) => {
65+
const chartVersion = el.trim();
4166

42-
const chartType = 'rancher-alerting-drivers';
43-
const installAlertingDriversPage = `repo-type=cluster&repo=rancher-charts&chart=${ chartType }&version=${ chartVersion }&tools`;
44-
const installCharts = new InstallChartPage();
67+
const installAlertingDriversPage = `repo-type=cluster&repo=${ CHART.repo }&chart=${ CHART.id }&version=${ chartVersion }&tools`;
68+
const installCharts = new InstallChartPage();
4569

46-
clusterTools.goToInstall('Alerting Drivers');
47-
installCharts.waitForPage(installAlertingDriversPage);
48-
installCharts.nextPage();
70+
clusterTools.goToInstall(CHART.name);
71+
installCharts.waitForPage(installAlertingDriversPage);
72+
installCharts.nextPage();
4973

50-
cy.intercept('POST', 'v1/catalog.cattle.io.clusterrepos/rancher-charts?action=install').as('chartInstall');
51-
installCharts.installChart();
74+
cy.intercept('POST', `/v1/catalog.cattle.io.clusterrepos/${ CHART.repo }?action=install`).as('chartInstall');
75+
installCharts.installChart();
76+
});
5277
cy.wait('@chartInstall').its('response.statusCode').should('eq', 201);
5378
clusterTools.waitForPage();
5479
kubectl.waitForTerminalStatus('Connected');
80+
cy.waitForResourceState('v1', 'catalog.cattle.io.apps', `${ NAMESPACE }/${ CHART.id }`, 'deployed', 40);
5581
kubectl.waitForTerminalStatus('Disconnected', MEDIUM_TIMEOUT_OPT);
5682
});
57-
});
83+
}));
5884
});
5985

60-
it('can edit chart successfully', function() {
61-
runTestWhenChartAvailable('rancher-charts', 'rancher-alerting-drivers', this, () => {
62-
clusterTools.goTo();
63-
clusterTools.waitForPage();
64-
clusterTools.editChart('Alerting Drivers');
86+
describe('Manage Installed Chart', () => {
87+
beforeEach(function() {
88+
runTestWhenChartAvailable(CHART.repo, CHART.id, this, () => {
89+
cleanup();
90+
cy.getChartVersions(CHART.repo, CHART.id).then((versions) => {
91+
cy.installChart(CHART.repo, CHART.id, CHART.name, versions[0], NAMESPACE);
92+
});
93+
cy.waitForResourceState('v1', 'catalog.cattle.io.apps', `${ NAMESPACE }/${ CHART.id }`, 'deployed', 40);
94+
});
95+
});
6596

66-
const installChart = new InstallChartPage();
97+
qase(2063, it('can edit chart successfully', function() {
98+
runTestWhenChartAvailable(CHART.repo, CHART.id, this, () => {
99+
waitForToolsPage();
100+
clusterTools.editChart(CHART.name);
67101

68-
installChart.nextPage();
102+
const installChartPage = new InstallChartPage();
69103

70-
cy.intercept('POST', 'v1/catalog.cattle.io.clusterrepos/rancher-charts?action=upgrade').as('chartUpdate');
71-
installChart.installChart();
72-
cy.wait('@chartUpdate').its('response.statusCode').should('eq', 201);
73-
clusterTools.waitForPage();
74-
kubectl.waitForTerminalStatus('Connected');
75-
kubectl.waitForTerminalStatus('Disconnected', MEDIUM_TIMEOUT_OPT);
76-
});
77-
});
104+
installChartPage.nextPage();
78105

79-
it('can uninstall chart successfully', function() {
80-
runTestWhenChartAvailable('rancher-charts', 'rancher-alerting-drivers', this, () => {
81-
clusterTools.goTo();
82-
clusterTools.waitForPage();
83-
clusterTools.deleteChart('Alerting Drivers');
106+
cy.intercept('POST', `v1/catalog.cattle.io.clusterrepos/${ CHART.repo }?action=upgrade`).as('chartUpdate');
107+
installChartPage.installChart();
108+
cy.wait('@chartUpdate').its('response.statusCode').should('eq', 201);
109+
clusterTools.waitForPage();
110+
kubectl.waitForTerminalStatus('Connected');
111+
cy.waitForResourceState('v1', 'catalog.cattle.io.apps', `${ NAMESPACE }/${ CHART.id }`, 'deployed', 40);
112+
kubectl.waitForTerminalStatus('Disconnected', MEDIUM_TIMEOUT_OPT);
113+
});
114+
}));
84115

85-
const promptRemove = new PromptRemove();
116+
qase(2060, it('can uninstall chart successfully', function() {
117+
runTestWhenChartAvailable(CHART.repo, CHART.id, this, () => {
118+
waitForToolsPage();
119+
clusterTools.deleteChart(CHART.name);
86120

87-
promptRemove.checkbox().checkNotExists();
121+
const promptRemove = new PromptRemove();
88122

89-
cy.intercept('POST', `${ CLUSTER_APPS_BASE_URL }/default/rancher-alerting-drivers?action=uninstall`).as('chartUninstall');
90-
promptRemove.remove();
91-
cy.wait('@chartUninstall').its('response.statusCode').should('eq', 201);
92-
// we can't check that the initial state is connected... as the supporting socket can connect and disconnect quicker than we can show the window
93-
// kubectl.waitForTerminalStatus('Connected');
94-
kubectl.waitForTerminalStatus('Disconnected');
95-
});
123+
promptRemove.checkbox().checkNotExists();
124+
125+
cy.intercept('POST', `${ CLUSTER_APPS_BASE_URL }/${ NAMESPACE }/${ CHART.id }?action=uninstall`).as('chartUninstall');
126+
promptRemove.remove();
127+
cy.wait('@chartUninstall').its('response.statusCode').should('eq', 201);
128+
// we can't check that the initial state is connected... as the supporting socket can connect and disconnect quicker than we can show the window
129+
// kubectl.waitForTerminalStatus('Connected');
130+
kubectl.waitForTerminalStatus('Disconnected', MEDIUM_TIMEOUT_OPT);
131+
});
132+
}));
133+
});
134+
135+
after(() => {
136+
cleanup();
96137
});
97138
});

cypress/globals.d.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,11 +136,13 @@ declare global {
136136
waitForRancherResources(prefix: 'v3' | 'v1', resourceType: string, expectedResourcesTotal: number, greaterThan?: boolean): Chainable;
137137
waitForInterceptWithConflictRetry(alias: string, successStatusCode?: number, retryStatusCodes?: number[], options?: { timeout?: number }): Chainable;
138138
waitForRepositoryDownload(prefix: 'v3' | 'v1', resourceType: string, resourceId: string, retries?: number): Chainable;
139-
waitForResourceState(prefix: 'v3' | 'v1', resourceType: string, resourceId: string, resourceState?: string, retries?: number): Chainable;
139+
waitForResourceState(prefix: 'v3' | 'v1', resourceType: string, resourceId: string, resourceState?: string, retries?: number, failOnStatusCode?: boolean): Chainable;
140140
deleteRancherResource(prefix: 'v3' | 'v1' | 'k8s', resourceType: string, resourceId: string, failOnStatusCode?: boolean): Chainable;
141141
getClusterIdByName(clusterName: string): Chainable<string>;
142142
checkChartPresence(repoName: string, chartKey: string): Chainable<{ inFiltered: boolean, inUnfiltered: boolean }>;
143143
getClusterToolsChartCount(repoName?: string): Chainable<number>;
144+
installChart(repo: string, chartId: string, chartName: string, chartVersion: string, namespace: string): Chainable;
145+
getChartVersions(repo: string, chartId: string): Chainable<string[]>;
144146
deleteNodeTemplate(nodeTemplateId: string, timeout?: number, failOnStatusCode?: boolean)
145147
/**
146148
* Delete a namespace and wait for it to 404. Helpful when the ns contains many resources

cypress/support/commands/rancher-api-commands.ts

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -691,12 +691,17 @@ Cypress.Commands.add('waitForRepositoryDownload', (prefix, resourceType, resourc
691691
/**
692692
* Wait for repository to be state
693693
*/
694-
Cypress.Commands.add('waitForResourceState', (prefix, resourceType, resourceId, resourceState = 'active', retries = 20) => {
694+
Cypress.Commands.add('waitForResourceState', (prefix, resourceType, resourceId, resourceState = 'active', retries = 20, failOnStatusCode = false) => {
695695
return cy.waitForRancherResource(prefix, resourceType, resourceId, (resp) => {
696+
// The resource may not exist yet (404) right after creation or update, so we should return false to retry instead of failing immediately
697+
if (resp.status === 404) {
698+
return false;
699+
}
700+
696701
const state = resp.body.metadata?.state;
697702

698703
return state && state.transitioning === false && state.name === resourceState;
699-
}, retries);
704+
}, retries, { failOnStatusCode });
700705
});
701706

702707
/**
@@ -1595,6 +1600,59 @@ Cypress.Commands.add('checkChartPresence', (repoName: string, chartKey: string)
15951600
});
15961601
});
15971602

1603+
/**
1604+
* Get the list of available versions for a chart from the filtered catalog index for a given repo.
1605+
* Versions are returned in the order Rancher reports them (newest first).
1606+
* Callers can pick whichever version they need (e.g. `versions[0]` for latest).
1607+
*/
1608+
Cypress.Commands.add('getChartVersions', (repo: string, chartId: string) => {
1609+
const baseUrl = `${ Cypress.env('api') }/v1/catalog.cattle.io.clusterrepos/${ repo }`;
1610+
1611+
const headers = {
1612+
'x-api-csrf': token.value,
1613+
Accept: 'application/json',
1614+
};
1615+
1616+
return cy.request({
1617+
method: 'GET',
1618+
url: `${ baseUrl }?link=index`,
1619+
headers,
1620+
}).then((resp) => {
1621+
const versions = resp.body?.entries?.[chartId];
1622+
1623+
return versions.map((v: any) => v.version as string);
1624+
});
1625+
});
1626+
1627+
/**
1628+
* Install a Helm chart via the Rancher API (bypassing the install wizard UI).
1629+
* Useful for test setup where the install flow itself is not under test.
1630+
*/
1631+
Cypress.Commands.add('installChart', (repo: string, chartId: string, chartName: string, chartVersion: string, namespace: string) => {
1632+
return cy.createRancherResource('v1', `catalog.cattle.io.clusterrepos/${ repo }?action=install`, {
1633+
charts: [
1634+
{
1635+
chartName: chartId,
1636+
version: chartVersion,
1637+
releaseName: chartId,
1638+
description: chartName,
1639+
annotations: {
1640+
'catalog.cattle.io/ui-source-repo-type': 'cluster',
1641+
'catalog.cattle.io/ui-source-repo': repo
1642+
},
1643+
values: {}
1644+
}
1645+
],
1646+
noHooks: false,
1647+
timeout: '1000s',
1648+
wait: true,
1649+
namespace,
1650+
projectId: '',
1651+
disableOpenAPIValidation: false,
1652+
skipCRDs: false,
1653+
});
1654+
});
1655+
15981656
/**
15991657
* Runs `callback` when `chartKey` is present in the filtered catalog index (the list the Charts UI uses).
16001658
* Throws error if the chart is missing from the unfiltered index — that points to catalog publish or sync, not compatibility filtering.

0 commit comments

Comments
 (0)