Skip to content

Commit

Permalink
automation: services tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Yonas Berhe authored and Yonas Berhe committed Jan 22, 2025
1 parent 6e35eca commit e2c87dc
Show file tree
Hide file tree
Showing 8 changed files with 246 additions and 18 deletions.
23 changes: 23 additions & 0 deletions cypress/e2e/po/components/growl-manager.po.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import ComponentPo from '@/cypress/e2e/po/components/component.po';

export class GrowlManagerPo extends ComponentPo {
constructor() {
super('.growl-container');
}

growlList() {
return this.self().find('.growl-list');
}

growlMessage() {
return this.self().find('.growl-message');
}

dismissWarning() {
return this.self().find('.icon-close').click();
}

dismissAllWarnings() {
return this.self().find('button.btn').contains('Clear All Notifications').click();
}
}
13 changes: 13 additions & 0 deletions cypress/e2e/po/components/key-value.po.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import ComponentPo from '@/cypress/e2e/po/components/component.po';

export default class KeyValuePo extends ComponentPo {
addButton(label: string) {
return this.self().find('[data-testid="add_row_item_button"]').contains(label);
}

setKeyValueAtIndex(label: string, key: string, value: string, index: number, selector: string) {
this.addButton(label).click();
this.self().find(`${ selector } [data-testid="input-kv-item-key-${ index }"]`).type(key);
this.self().find(`${ selector } [data-testid="kv-item-value-${ index }"]`).type(value);
}
}
4 changes: 4 additions & 0 deletions cypress/e2e/po/components/tabbed.po.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ export default class TabbedPo extends ComponentPo {
return this.self().get('[data-testid="tabbed-block"] > li');
}

checkSelectedTab(selector: string) {
return this.self().find(`${ selector }`).should('have.class', 'active');
}

/**
* Get tab labels
* @param tabLabelsSelector
Expand Down
70 changes: 64 additions & 6 deletions cypress/e2e/po/edit/services.po.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,76 @@
import PagePo from '@/cypress/e2e/po/pages/page.po';
import NameNsDescription from '@/cypress/e2e/po/components/name-ns-description.po';
import ResourceDetailPo from '@/cypress/e2e/po/edit/resource-detail.po';
import LabeledSelectPo from '@/cypress/e2e/po/components/labeled-select.po';
import TabbedPo from '@/cypress/e2e/po/components/tabbed.po';
import LabeledInputPo from '@/cypress/e2e/po/components/labeled-input.po';
import ArrayListPo from '@/cypress/e2e/po/components/array-list.po';
import KeyValuePo from '@/cypress/e2e/po/components/key-value.po';

export default class WorkloadsCreateEditPo extends PagePo {
private static createPath(clusterId: string, id?: string ) {
const root = `/c/${ clusterId }/explorer/storage.k8s.io.storageclass/create`;
export default class ServicesCreateEditPo extends PagePo {
private static createPath(clusterId: string, namespace?: string, id?: string ) {
const root = `/c/${ clusterId }/explorer/service`;

return id ? `${ root }/${ id }` : `${ root }/create`;
return id ? `${ root }/${ namespace }/${ id }` : `${ root }/create`;
}

static goTo(path: string): Cypress.Chainable<Cypress.AUTWindow> {
throw new Error('invalid');
}

constructor(clusterId = '_', id?: string) {
super(WorkloadsCreateEditPo.createPath(clusterId, id));
constructor(clusterId = 'local', namespace?: string, id?: string) {
super(ServicesCreateEditPo.createPath(clusterId, namespace, id));
}

resourceDetail() {
return new ResourceDetailPo(this.self());
}

title() {
return this.self().get('.title .primaryheader h1');
}

nameNsDescription() {
return new NameNsDescription(this.self());
}

selectNamespace(label: string) {
const selectNs = new LabeledSelectPo(`[data-testid="name-ns-description-namespace"]`, this.self());

selectNs.toggle();
selectNs.clickLabel(label);
}

selectServiceOption(index: number) {
return this.resourceDetail().cruResource().selectSubTypeByIndex(index).click();
}

tabs() {
return new TabbedPo('[data-testid="tabbed"]');
}

externalNameTab() {
return this.tabs().clickTabWithSelector('[data-testid="define-external-name"]');
}

externalNameInput() {
return new LabeledInputPo('#define-external-name .labeled-input input');
}

ipAddressesTab() {
return this.tabs().clickTabWithSelector('[data-testid="ips"]');
}

ipAddressList() {
return new ArrayListPo('section#ips');
}

lablesAnnotationsTab() {
return this.tabs().clickTabWithSelector('[data-testid="btn-labels-and-annotations"]');
}

lablesAnnotationsKeyValue() {
return new KeyValuePo('section#labels-and-annotations');
}

errorBanner() {
Expand Down
6 changes: 3 additions & 3 deletions cypress/e2e/po/pages/explorer/services.po.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export class ServicesPagePo extends PagePo {
sideNav.navToSideMenuEntryByLabel('Service');
}

constructor(clusterId = 'local') {
constructor(private clusterId = 'local') {
super(ServicesPagePo.createPath(clusterId));
}

Expand All @@ -38,7 +38,7 @@ export class ServicesPagePo extends PagePo {
return this.list().masthead().create();
}

createServicesForm(id? : string): ServicesCreateEditPo {
return new ServicesCreateEditPo(id);
createServicesForm(namespace?: string, id?: string): ServicesCreateEditPo {
return new ServicesCreateEditPo(this.clusterId, namespace, id);
}
}
2 changes: 1 addition & 1 deletion cypress/e2e/po/pages/global-settings/home-links.po.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export class HomeLinksPagePo extends RootClusterPage {
}

addLinkButton() {
return cy.getId('add_link_button');
return cy.getId('add_row_item_button');
}

removeLinkButton() {
Expand Down
144 changes: 137 additions & 7 deletions cypress/e2e/tests/pages/explorer/service-discovery/services.spec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,149 @@
import { ServicesPagePo } from '@/cypress/e2e/po/pages/explorer/services.po';
import { generateServicesDataSmall, servicesNoData } from '@/cypress/e2e/blueprints/explorer/workloads/service-discovery/services-get';
import ClusterDashboardPagePo from '@/cypress/e2e/po/pages/explorer/cluster-dashboard.po';
import PromptRemove from '@/cypress/e2e/po/prompts/promptRemove.po';
import { GrowlManagerPo } from '@/cypress/e2e/po/components/growl-manager.po';

const cluster = 'local';
const servicesPagePo = new ServicesPagePo();
const growlPo = new GrowlManagerPo();
const cluster = 'local';
let serviceExternalName = '';
const namespace = 'default';
let removeServices = false;
const servicesToDelete = [];

describe('Services', { testIsolation: 'off', tags: ['@explorer', '@adminUser'] }, () => {
before(() => {
cy.login();
cy.createE2EResourceName('serviceexternalname').then((name) => {
serviceExternalName = name;
});
});

describe('CRUD', () => {
it('can create an ExternalName Service', () => {
cy.intercept('POST', '/v1/services').as('createService');
ServicesPagePo.navTo();
servicesPagePo.waitForPage();
servicesPagePo.clickCreate();
servicesPagePo.createServicesForm().waitForPage();

servicesPagePo.createServicesForm().selectServiceOption(1);
servicesPagePo.createServicesForm().waitForPage(null, 'define-external-name');
servicesPagePo.createServicesForm().resourceDetail().title().should('contain', 'Create ExternalName');
servicesPagePo.createServicesForm().nameNsDescription().name().set(serviceExternalName);
servicesPagePo.createServicesForm().nameNsDescription().description().set(`${ serviceExternalName }-desc`);
servicesPagePo.createServicesForm().selectNamespace(namespace);
servicesPagePo.createServicesForm().tabs().allTabs().should('have.length', 3);

const tabs = ['External Name', 'IP Addresses', 'Labels & Annotations'];

servicesPagePo.createServicesForm().tabs().tabNames().each((el, i) => {
expect(el).to.eq(tabs[i]);
});

servicesPagePo.createServicesForm().tabs().checkSelectedTab('[data-testid="define-external-name"]');
servicesPagePo.createServicesForm().externalNameInput().set('my.database.example.com');
servicesPagePo.createServicesForm().ipAddressesTab();
servicesPagePo.createServicesForm().waitForPage(null, 'ips');
servicesPagePo.createServicesForm().ipAddressList().setValueAtIndex('1.1.1.1', 0);
servicesPagePo.createServicesForm().ipAddressList().setValueAtIndex('2.2.2.2', 1);
servicesPagePo.createServicesForm().lablesAnnotationsTab();
servicesPagePo.createServicesForm().waitForPage(null, 'labels-and-annotations');
servicesPagePo.createServicesForm().lablesAnnotationsKeyValue().setKeyValueAtIndex('Add Label', 'label-key1', 'label-value1', 0, '.labels-and-annotations-container div.row:nth-of-type(2)');

// TODO Adding Annotations doesn't work via test automation
// Something is up with the `Add Annotation` button in `KeyValue.vue` component
// When you click Add, the key/value inputs display and immediately disappear
// Same issue occurs in Settings -> Home Links via Automation
// servicesPagePo.createServicesForm().lablesAnnotationsKeyValue().setKeyValueAtIndex('Add Annotation', 'ann-key1', 'ann-value1', 0, '.labels-and-annotations-container div.row:nth-of-type(3)');
servicesPagePo.createServicesForm().resourceDetail().createEditView().create();
cy.wait('@createService').then(({ response }) => {
expect(response?.statusCode).to.eq(201);
removeServices = true;
servicesToDelete.push(`${ namespace }/${ serviceExternalName }`);
});
servicesPagePo.waitForPage();
servicesPagePo.list().resourceTable().sortableTable().rowWithName(serviceExternalName)
.checkVisible();
growlPo.dismissWarning();
});

it('can edit an ExternalName Service', () => {
ServicesPagePo.navTo();
servicesPagePo.waitForPage();
servicesPagePo.list().actionMenu(serviceExternalName).getMenuItem('Edit Config').click();
servicesPagePo.createServicesForm(namespace, serviceExternalName).waitForPage('mode=edit', 'define-external-name');
servicesPagePo.createServicesForm().nameNsDescription().description().set(`${ serviceExternalName }-desc`);
servicesPagePo.createServicesForm().resourceDetail().cruResource().saveAndWaitForRequests('PUT', `/v1/services/${ namespace }/${ serviceExternalName }`)
.then(({ response }) => {
expect(response?.statusCode).to.eq(200);
expect(response?.body.metadata).to.have.property('name', serviceExternalName);
expect(response?.body.metadata.annotations).to.have.property('field.cattle.io/description', `${ serviceExternalName }-desc`);
});
servicesPagePo.waitForPage();
growlPo.dismissWarning();
});

it('can clone an ExternalName Service', () => {
ServicesPagePo.navTo();
servicesPagePo.waitForPage();
servicesPagePo.list().actionMenu(serviceExternalName).getMenuItem('Clone').click();
servicesPagePo.createServicesForm(namespace, serviceExternalName).waitForPage('mode=clone', 'define-external-name');
servicesPagePo.createServicesForm().nameNsDescription().name().set(`clone-${ serviceExternalName }`);
servicesPagePo.createServicesForm().resourceDetail().cruResource().saveAndWaitForRequests('POST', '/v1/services')
.then(({ response }) => {
expect(response?.statusCode).to.eq(201);
expect(response?.body.metadata).to.have.property('name', `clone-${ serviceExternalName }`);
removeServices = true;
servicesToDelete.push(`${ namespace }/clone-${ serviceExternalName }`);
});
servicesPagePo.waitForPage();
servicesPagePo.list().resourceTable().sortableTable().rowWithName(`clone-${ serviceExternalName }`)
.checkVisible();
growlPo.dismissWarning();
});

it('can Edit Yaml', () => {
ServicesPagePo.navTo();
servicesPagePo.waitForPage();
servicesPagePo.list().actionMenu(`clone-${ serviceExternalName }`).getMenuItem('Edit YAML').click();
servicesPagePo.createServicesForm(namespace, `clone-${ serviceExternalName }`).waitForPage('mode=edit&as=yaml');
servicesPagePo.createServicesForm().title().contains(`Service: clone-${ serviceExternalName }`).should('be.visible');
});

it('can delete an ExternalName Service', () => {
ServicesPagePo.navTo();
servicesPagePo.waitForPage();
servicesPagePo.list().actionMenu(`clone-${ serviceExternalName }`).getMenuItem('Delete').click();
servicesPagePo.list().resourceTable().sortableTable().rowNames('.col-link-detail')
.then((rows: any) => {
const promptRemove = new PromptRemove();

cy.intercept('DELETE', `/v1/services/${ namespace }/clone-${ serviceExternalName }`).as('deleteService');

promptRemove.remove();
cy.wait('@deleteService');
servicesPagePo.waitForPage();
servicesPagePo.list().resourceTable().sortableTable().checkRowCount(false, rows.length - 1);
servicesPagePo.list().resourceTable().sortableTable().rowNames('.col-link-detail')
.should('not.contain', `clone-${ serviceExternalName }`);
});
});

// testing https://github.com/rancher/dashboard/issues/11889
it('validation errors should not be shown when form is just opened', () => {
servicesPagePo.goTo();
servicesPagePo.clickCreate();
servicesPagePo.createServicesForm().errorBanner().should('not.exist');
});

after(() => {
if (removeServices) {
// delete gitrepo
servicesToDelete.forEach((r) => cy.deleteRancherResource('v1', 'services', r, false));
}
});
});

describe('List', { tags: ['@vai', '@adminUser'] }, () => {
Expand Down Expand Up @@ -84,12 +220,6 @@ describe('Services', { testIsolation: 'off', tags: ['@explorer', '@adminUser'] }
servicesPagePo.list().resourceTable().sortableTable().checkRowCount(false, 3);
});

it('validation errors should not be shown when form is just opened', () => {
servicesPagePo.goTo();
servicesPagePo.clickCreate();
servicesPagePo.createServicesForm().errorBanner().should('not.exist');
});

after('clean up', () => {
cy.updateNamespaceFilter(cluster, 'none', '{"local":["all://user"]}');
});
Expand Down
2 changes: 1 addition & 1 deletion shell/components/form/KeyValue.vue
Original file line number Diff line number Diff line change
Expand Up @@ -798,7 +798,7 @@ export default {
v-if="addAllowed"
type="button"
class="btn role-tertiary add"
data-testid="add_link_button"
data-testid="add_row_item_button"
:disabled="loading || disabled || (keyOptions && filteredKeyOptions.length === 0)"
@click="add()"
>
Expand Down

0 comments on commit e2c87dc

Please sign in to comment.