Skip to content
Merged
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
6 changes: 3 additions & 3 deletions cypress/e2e/po/components/resource-detail-masthead.po.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import ComponentPo from '@/cypress/e2e/po/components/component.po';
import ComponentPo, { GetOptions } from '@/cypress/e2e/po/components/component.po';

export default class ResourceDetailMastheadPo extends ComponentPo {
/**
* Get the resource status badge in the masthead
*/
resourceStatus() {
return this.self().find('h1.title .badge-state .msg');
resourceStatus(options?: GetOptions) {
return this.self().find('h1.title .badge-state .msg', options);
}

/**
Expand Down
17 changes: 17 additions & 0 deletions cypress/e2e/po/edit/resource-detail.po.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import CruResourcePo from '@/cypress/e2e/po/components/cru-resource.po';
import ResourceYamlPo from '@/cypress/e2e/po/components/resource-yaml.po';
import ResourceDetailMastheadPo from '@/cypress/e2e/po/components/resource-detail-masthead.po';
import TabbedPo from '@/cypress/e2e/po/components/tabbed.po';
import ResourceTablePo from '@/cypress/e2e/po/components/resource-table.po';

export default class ResourceDetailPo extends ComponentPo {
/**
Expand Down Expand Up @@ -42,6 +43,18 @@ export default class ResourceDetailPo extends ComponentPo {
return new TabbedPo('[data-testid="tabbed"]');
}

/**
* @param tabId - the id of the tab
* @param index - the index of the list (only used for 'related' tab)
* @returns the list of the tab
*/
tabbedList(tabId: string, index?: number) {
const baseSelector = `#${ tabId } [data-testid="sortable-table-list-container"]`;
const selector = tabId === 'related' ? `${ baseSelector }:nth-of-type(${ index })` : baseSelector;

return new ResourceTablePo(selector);
}

title(): Cypress.Chainable<string> {
return this.self().find('.title-bar h1.title, .primaryheader h1').invoke('text');
}
Expand All @@ -53,4 +66,8 @@ export default class ResourceDetailPo extends ComponentPo {
masthead() {
return new ResourceDetailMastheadPo(this.self());
}

resourceGauges() {
return this.self().find('.gauges .count-gauge');
}
}
153 changes: 110 additions & 43 deletions cypress/e2e/tests/pages/explorer2/workloads/cronjobs.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import SortableTablePo from '@/cypress/e2e/po/components/sortable-table.po';
import ClusterDashboardPagePo from '@/cypress/e2e/po/pages/explorer/cluster-dashboard.po';
import { generateCronJobsDataSmall } from '@/cypress/e2e/blueprints/explorer/workloads/cronjobs/cronjobs-get';
import { SMALL_CONTAINER } from '@/cypress/e2e/tests/pages/explorer2/workloads/workload.utils';
import { MEDIUM_TIMEOUT_OPT, LONG_TIMEOUT_OPT } from '@/cypress/support/utils/timeouts';
import { MEDIUM_TIMEOUT_OPT } from '@/cypress/support/utils/timeouts';

describe('CronJobs', { testIsolation: 'off', tags: ['@explorer2', '@adminUser'] }, () => {
const localCluster = 'local';
Expand All @@ -15,6 +15,115 @@ describe('CronJobs', { testIsolation: 'off', tags: ['@explorer2', '@adminUser']
cy.login();
});

describe('Details', () => {
let cronJobName: string;
let jobName: string;
let podName: string;
const defaultNamespace = 'default';

before('set up', () => {
// Create a cronjob for the test
cy.getRootE2EResourceName().then((root) => {
cronJobName = root;

return cy.createRancherResource('v1', 'batch.cronjob', JSON.stringify({
apiVersion: 'batch/v1',
kind: 'CronJob',
metadata: {
name: cronJobName,
namespace: defaultNamespace
},
spec: {
schedule: '1 1 1 1 1', // basically never
concurrencyPolicy: 'Allow',
failedJobsHistoryLimit: 1,
successfulJobsHistoryLimit: 3,
suspend: false,
jobTemplate: {
spec: {
template: {
spec: {
containers: [SMALL_CONTAINER],
restartPolicy: 'Never'
}
}
}
}
}
}));
});
});

it('Jobs list updates automatically in CronJob details page', () => {
// Set namespace filter to include the test cronjob namespace
cy.tableRowsPerPageAndNamespaceFilter(10, localCluster, 'none', `{\"local\":[\"ns://${ defaultNamespace }\"]}`);

WorkloadsCronJobsListPagePo.navTo();
cronJobListPage.waitForPage();

// Trigger "Run Now" action which will create a new job and pod from the CronJob
cy.intercept('POST', `v1/batch.jobs/${ defaultNamespace }`).as('runNow');
cronJobListPage.runNow(cronJobName);
cy.wait('@runNow').its('response.statusCode').should('eq', 201);

// Retrieve the job and pod names created by the CronJob
cy.getRancherResource('v1', 'batch.job', `${ defaultNamespace }`).then((resp) => {
const job = resp.body.data.find((job: any) => job.metadata.name.startsWith(cronJobName));

jobName = job.metadata.name;
cy.getRancherResource('v1', 'pods', `${ defaultNamespace }`).then((resp) => {
const pod = resp.body.data.find((pod: any) => pod.metadata.name.startsWith(cronJobName));

podName = pod.metadata.name;

// User is redirected to the job's details page after "Run Now"
const jobDetailsPage = new WorkLoadsJobDetailsPagePo(jobName, undefined, 'local', defaultNamespace);

jobDetailsPage.waitForPage(undefined, 'pods');

// Verify job details page displays correct status
// Job status should be Active
jobDetailsPage.resourceDetail().masthead().resourceStatus(MEDIUM_TIMEOUT_OPT)
.should('contain', 'Active');

// Pod status should be Running
jobDetailsPage.resourceDetail().resourceGauges().should('contain', 'Running');
jobDetailsPage.resourceDetail().tabbedList('pods').resourceTableDetails(podName, 1).contains('Running', MEDIUM_TIMEOUT_OPT);
});

// Navigate back to CronJobs list page
WorkloadsCronJobsListPagePo.navTo();
cronJobListPage.waitForPage();

// Verify CronJob status is Active in the list
cronJobListPage.resourceTableDetails(cronJobName, 1).contains('Active');

// Navigate to CronJob details page
cronJobListPage.goToDetailsPage(cronJobName);

const cronJobDetailsPage = new WorkloadsCronJobDetailPagePo(cronJobName, 'local', defaultNamespace);

cronJobDetailsPage.waitForPage(undefined, 'jobs');

// Verify CronJob status is Active in details page
cronJobDetailsPage.resourceDetail().masthead().resourceStatus()
.should('contain', 'Active');

// Verify the job in the jobs tab shows correct status without manual page refresh
// Testing https://github.com/rancher/dashboard/issues/14981:
// The job list should update automatically and not show stale "In Progress" status
cronJobDetailsPage.resourceDetail().tabbedList('jobs').resourceTableDetails(jobName, 1).contains('Active');
});
});

after('clean up', () => {
// Ensure the default rows per page value is set after running the tests
cy.tableRowsPerPageAndNamespaceFilter(100, localCluster, 'none', '{"local":["all://user"]}');
// Delete the cronjob
cy.deleteRancherResource('v1', 'batch.cronjob', `${ defaultNamespace }/${ cronJobName }`);
});
});

describe('List', { tags: ['@noVai', '@adminUser'] }, () => {
let uniqueCronJob = SortableTablePo.firstByDefaultName('cronjob');
let detailsPageCronJob = SortableTablePo.firstByDefaultName('detailscron');
Expand Down Expand Up @@ -280,48 +389,6 @@ describe('CronJobs', { testIsolation: 'off', tags: ['@explorer2', '@adminUser']
.checkNotExists();
});

it('Cronjob details page refresh dashboard', () => {
// Set namespace filter to include the test cronjob namespace
cy.tableRowsPerPageAndNamespaceFilter(10, localCluster, 'none', `{\"local\":[\"ns://${ nsName3 }\"]}`);

WorkloadsCronJobsListPagePo.navTo();
cronJobListPage.waitForPage();

cronJobListPage.runNow(detailsPageCronJob);

cy.url().should('include', '/explorer/batch.job/', MEDIUM_TIMEOUT_OPT);

const jobDetailsPage = new WorkLoadsJobDetailsPagePo('dummy-job');

jobDetailsPage.resourceDetail().masthead().resourceStatus().should('be.visible', LONG_TIMEOUT_OPT)
.and(($el) => {
const status = $el.text().trim();

expect(['Running', 'Active', 'Pending', 'Creating', 'Succeeded']).to.include(status);
});

jobDetailsPage.resourceDetail().masthead().resourceStatus().should('not.be.empty');

WorkloadsCronJobsListPagePo.navTo();
cronJobListPage.waitForPage();

cronJobListPage.goToDetailsPage(detailsPageCronJob);

cy.url(MEDIUM_TIMEOUT_OPT).should('include', '/explorer/batch.cronjob/');

const cronJobDetailsPage = new WorkloadsCronJobDetailPagePo(detailsPageCronJob, 'local', nsName3);

cronJobDetailsPage.resourceDetail().masthead().resourceStatus().should('be.visible', MEDIUM_TIMEOUT_OPT);

// CronJob should NOT show stale "In Progress" status
cronJobDetailsPage.resourceDetail().masthead().resourceStatus().should('not.contain', 'In Progress');

cy.reload();
cy.url(MEDIUM_TIMEOUT_OPT).should('include', '/explorer/batch.cronjob/');

cronJobDetailsPage.resourceDetail().masthead().resourceStatus().should('be.visible', MEDIUM_TIMEOUT_OPT);
});

after('clean up', () => {
// Ensure the default rows per page value is set after running the tests
cy.tableRowsPerPageAndNamespaceFilter(100, localCluster, 'none', '{"local":["all://user"]}');
Expand Down