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

automation: services tests #13152

Open
wants to merge 1 commit into
base: master
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
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: 2 additions & 0 deletions cypress/e2e/tests/pages/extensions/extensions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ describe('Extensions page', { tags: ['@extensions', '@adminUser'] }, () => {
// Ensure that the banner should be shown (by confirming that a required repo isn't there)
appRepoList.goTo();
appRepoList.waitForPage();
appRepoList.sortableTable().checkLoadingIndicatorNotVisible();
appRepoList.sortableTable().noRowsShouldNotExist();
appRepoList.sortableTable().rowNames().then((names: any) => {
if (names.includes(UI_PLUGINS_PARTNERS_REPO_NAME)) {
Expand Down Expand Up @@ -412,6 +413,7 @@ describe('Extensions page', { tags: ['@extensions', '@adminUser'] }, () => {

// Install unauthenticated extension
extensionsPo.extensionCardInstallClick(UNAUTHENTICATED_EXTENSION_NAME);
extensionsPo.extensionInstallModal().scrollIntoView();
extensionsPo.extensionInstallModal().should('be.visible');
extensionsPo.installModalInstallClick();

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
2 changes: 1 addition & 1 deletion shell/components/form/__tests__/KeyValue.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ describe('component: KeyValue', () => {
expect(secondKeyInput.exists()).toBe(false);
expect(secondValueInput.exists()).toBe(false);

const addButton = wrapper.find('[data-testid="add_link_button"]');
const addButton = wrapper.find('[data-testid="add_row_item_button"]');

addButton.trigger('click');
await nextTick();
Expand Down
Loading