Merge pull request #13152 from yonasberhe23/services_tests_e2e

automation: services - external name tests
This commit is contained in:
yonasberhe23 2025-01-31 16:02:12 -08:00 committed by GitHub
commit 4b836a4d49
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 264 additions and 21 deletions

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -17,6 +17,10 @@ export default class TabbedPo extends ComponentPo {
return this.self().get('[data-testid="tabbed-block"] > li');
}
assertTabIsActive(selector: string) {
return this.self().find(`${ selector }`).should('have.class', 'active');
}
/**
* Get tab labels
* @param tabLabelsSelector

View File

@ -4,14 +4,26 @@ import CruResourcePo from '@/cypress/e2e/po/components/cru-resource.po';
import ResourceYamlPo from '@/cypress/e2e/po/components/resource-yaml.po';
export default class ResourceDetailPo extends ComponentPo {
/**
* components for handling CRUD operations for resources, including cancel/save buttons
* @returns
*/
cruResource() {
return new CruResourcePo(this.self());
}
/**
* components for managing the resource creation and edit forms
* @returns
*/
createEditView() {
return new CreateEditViewPo(this.self());
}
/**
* components for YAML editor
* @returns
*/
resourceYaml() {
return new ResourceYamlPo(this.self());
}

View File

@ -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() {

View File

@ -26,7 +26,7 @@ export class ServicesPagePo extends PagePo {
sideNav.navToSideMenuEntryByLabel('Service');
}
constructor(clusterId = 'local') {
constructor(private clusterId = 'local') {
super(ServicesPagePo.createPath(clusterId));
}
@ -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);
}
}

View File

@ -34,7 +34,7 @@ export default class ExtensionsPagePo extends PagePo {
return this.title().should('contain', 'Extensions');
}
loading(options: any) {
loading() {
return this.self().get('.data-loading');
}

View File

@ -32,7 +32,7 @@ export class HomeLinksPagePo extends RootClusterPage {
}
addLinkButton() {
return cy.getId('add_link_button');
return cy.getId('add_row_item_button');
}
removeLinkButton() {

View File

@ -1,13 +1,147 @@
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().assertTabIsActive('[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)');
// Adding Annotations doesn't work via test automation
// See https://github.com/rancher/dashboard/issues/13191
// 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'] }, () => {
@ -84,12 +218,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"]}');
});

View File

@ -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)) {
@ -409,6 +410,7 @@ describe('Extensions page', { tags: ['@extensions', '@adminUser'] }, () => {
extensionsPo.extensionTabAvailableClick();
extensionsPo.waitForPage(null, 'available');
extensionsPo.loading().should('not.exist');
// Install unauthenticated extension
extensionsPo.extensionCardInstallClick(UNAUTHENTICATED_EXTENSION_NAME);
@ -418,6 +420,8 @@ describe('Extensions page', { tags: ['@extensions', '@adminUser'] }, () => {
// let's check the extension reload banner and reload the page
extensionsPo.extensionReloadBanner().should('be.visible');
extensionsPo.extensionReloadClick();
extensionsPo.waitForPage(null, 'installed');
extensionsPo.loading().should('not.exist');
// make sure both extensions have been imported
extensionsPo.extensionScriptImport(UNAUTHENTICATED_EXTENSION_NAME).should('exist');
@ -436,7 +440,8 @@ describe('Extensions page', { tags: ['@extensions', '@adminUser'] }, () => {
// make sure both extensions have been imported after logging in again
cy.login(undefined, undefined, false);
extensionsPo.goTo();
extensionsPo.waitForPage();
extensionsPo.waitForPage(null, 'installed');
extensionsPo.loading().should('not.exist');
extensionsPo.waitForTitle();
extensionsPo.extensionScriptImport(UNAUTHENTICATED_EXTENSION_NAME).should('exist');
extensionsPo.extensionScriptImport(EXTENSION_NAME).should('exist');

View File

@ -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()"
>

View File

@ -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();