Create e2e test for `configmap`

This test allows us to assert that blocks containing long lines (over 80 characters) entered into a configmap value field will output the correct value upon saving. The intent of this test is to assert that the regression described in #10044 is resolved and will not be reintroduced if further changes are made to `create-yaml`.

Signed-off-by: Phillip Rak <rak.phillip@gmail.com>
This commit is contained in:
Phillip Rak 2023-12-12 10:43:32 -07:00
parent 01c043099e
commit a6b3c1c927
6 changed files with 223 additions and 0 deletions

View File

@ -0,0 +1,52 @@
import ComponentPo from '@/cypress/e2e/po/components/component.po';
import { CypressChainable } from '@/cypress/e2e/po/po.types';
export default class CodeMirrorPo extends ComponentPo {
static byLabel(self: CypressChainable, label: string): CodeMirrorPo {
throw new Error('Not implemented');
}
static bySelector(self: CypressChainable, selector: string): CodeMirrorPo {
return new CodeMirrorPo(
self
.find(`${ selector } .CodeMirror`, { includeShadowDom: true })
);
}
/**
* Type value in the input
* @param value Value to be typed
* @returns
*/
set(value: string): Cypress.Chainable {
this.input().should('be.visible');
return this.input()
.then(($codeMirror) => {
const codeMirrorInstance = $codeMirror[0].CodeMirror;
codeMirrorInstance.setValue(value);
});
}
clear() {
return this.input().clear();
}
value(): Cypress.Chainable {
return this.input()
.then(($codeMirror) => {
const codeMirrorInstance = $codeMirror[0].CodeMirror;
return codeMirrorInstance.getValue();
});
}
/**
* Return the input HTML element from given container
* @returns HTML Element
*/
private input(): Cypress.Chainable {
return this.self();
}
}

View File

@ -0,0 +1,53 @@
import ComponentPo from '@/cypress/e2e/po/components/component.po';
import { CypressChainable } from '@/cypress/e2e/po/po.types';
export default class InputPo extends ComponentPo {
static byLabel(self: CypressChainable, label: string): InputPo {
return new InputPo(
self
.find('.labeled-input', { includeShadowDom: true })
.contains(label)
.next()
);
}
static bySelector(self: CypressChainable, selector: string): InputPo {
return new InputPo(
self
.find(`input${ selector }`, { includeShadowDom: true }).eq(0)
);
}
/**
* Type value in the input
* @param value Value to be typed
* @returns
*/
set(value: string): Cypress.Chainable {
this.input().should('be.visible');
this.input().focus();
this.input().clear();
return this.input().type(value);
}
clear() {
return this.input().clear();
}
value(): Cypress.Chainable {
throw new Error('Not implements');
// The text for the input field is in a shadow dom element. Neither the proposed two methods
// to dive in to the shadow dom work
// return this.input().find('div', { includeShadowDom: true }).invoke('text');
// return this.input().shadow().find('div').invoke('text');
}
/**
* Return the input HTML element from given container
* @returns HTML Element
*/
private input(): Cypress.Chainable {
return this.self();
}
}

View File

@ -0,0 +1,31 @@
import CreateEditViewPo from '@/cypress/e2e/po/components/create-edit-view.po';
import AsyncButtonPo from '@/cypress/e2e/po/components/async-button.po';
import LabeledInputPo from '@/cypress/e2e/po/components/labeled-input.po';
import InputPo from '@/cypress/e2e/po/components/input.po';
import CodeMirrorPo from '@/cypress/e2e/po/components/code-mirror.po';
export default class ConfigMapPo extends CreateEditViewPo {
constructor(selector = '.dashboard-root') {
super(selector);
}
nameInput() {
return LabeledInputPo.bySelector(this.self(), '[data-testid="name-ns-description-name"]');
}
keyInput() {
return InputPo.bySelector(this.self(), '[data-testid="input-kv-item-key-0"]');
}
valueInput() {
return CodeMirrorPo.bySelector(this.self(), '[data-testid="code-mirror-multiline-field"]');
}
descriptionInput() {
return LabeledInputPo.byLabel(this.self(), 'Description');
}
saveCreateForm(): AsyncButtonPo {
return new AsyncButtonPo('[data-testid="form-save"]', this.self());
}
}

View File

@ -0,0 +1,28 @@
import PagePo from '@/cypress/e2e/po/pages/page.po';
import BaseResourceList from '@/cypress/e2e/po/lists/base-resource-list.po';
export class ConfigMapPagePo extends PagePo {
private static createPath(clusterId: string) {
return `/c/${ clusterId }/explorer/configmap`;
}
static goTo(clusterId: string): Cypress.Chainable<Cypress.AUTWindow> {
return super.goTo(ConfigMapPagePo.createPath(clusterId));
}
constructor(clusterId = 'local') {
super(ConfigMapPagePo.createPath(clusterId));
}
clickCreate() {
const baseResourceList = new BaseResourceList(this.self());
return baseResourceList.masthead().actions().eq(0).click();
}
listElementWithName(name:string) {
const baseResourceList = new BaseResourceList(this.self());
return baseResourceList.resourceTable().sortableTable().rowElementWithName(name);
}
}

View File

@ -0,0 +1,56 @@
import { ConfigMapPagePo } from '@/cypress/e2e/po/pages/explorer/config-map.po';
import ConfigMapPo from '@/cypress/e2e/po/components/storage/config-map.po';
describe('ConfigMap', () => {
beforeEach(() => {
cy.login();
});
it('creates a configmap and displays it in the list', () => {
const expectedValue = `# Sample XPlanManagerAPI Configuration (if this comment is longer than 80 characters, the output should remain the same)
apiUrl=https://example.com/xplan-api-manager
contactEmailAddress=contact@example.com
termsOfServiceUrl=https://example.com/terms
documentationUrl=https://example.com/docs
wmsUrl=https://example.com/xplan-wms/services
skipSemantic=false
skipGeometric=true`;
// Visit the main menu and select the 'local' cluster
// Navigate to Service Discovery => ConfigMaps
const configMapPage = new ConfigMapPagePo('local');
configMapPage.goTo();
// Click on Create
configMapPage.clickCreate();
// Enter ConfigMap name
// Enter ConfigMap key
// Enter ConfigMap value
// Enter ConfigMap description
const configMapPo = new ConfigMapPo();
configMapPo.nameInput().set('custom-config-map');
configMapPo.keyInput().set('managerApiConfiguration.properties');
configMapPo.valueInput().set(expectedValue);
configMapPo.descriptionInput().set('Custom Config Map Description');
// Click on Create
configMapPo.saveCreateForm().click();
// Check if the ConfigMap is created successfully
configMapPage.listElementWithName('custom-config-map').should('exist');
// Navigate back to the edit page
configMapPage.listElementWithName('custom-config-map')
.find(`button[data-testid="sortable-table-0-action-button"]`)
.click()
.get(`li[data-testid="action-menu-0-item"]`)
.click();
// Assert the current value yaml dumps will append a newline at the end
configMapPo.valueInput().value().should('eq', `${ expectedValue }\n`);
});
});

View File

@ -644,6 +644,7 @@ export default {
:clearable="false"
:taggable="keyTaggable"
:options="calculateOptions(row[keyName])"
:data-testid="`select-kv-item-key-${i}`"
@input="queueUpdate"
/>
<input
@ -652,6 +653,7 @@ export default {
v-model="row[keyName]"
:disabled="isView || disabled || !keyEditable || isProtected(row.key)"
:placeholder="keyPlaceholder"
:data-testid="`input-kv-item-key-${i}`"
@input="queueUpdate"
@paste="onPaste(i, $event)"
>
@ -661,6 +663,7 @@ export default {
<!-- Value -->
<div
:key="i+'value'"
:data-testid="`kv-item-value-${i}`"
class="kv-item value"
>
<slot