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

Vue3 fix workload storage #12070

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
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,36 @@ export const createDeploymentBlueprint = {
}
}
}
],
volumes: [
{
name: 'test-vol',
projected: {
defaultMode: 420,
sources: [
{
configMap: {
items: [{ key: 'test-vol-key', path: 'test-vol-path' }],
name: 'configmap-name'
}
}
]
}
},
{
name: 'test-vol1',
projected: {
defaultMode: 420,
sources: [
{
configMap: {
items: [{ key: 'test-vol-key1', path: 'test-vol-path1' }],
name: 'configmap-name1'
}
}
]
}
}
]
},
metadata: {
Expand Down
7 changes: 7 additions & 0 deletions cypress/e2e/po/components/button-dropdown.po.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import LabeledSelectPo from '@/cypress/e2e/po/components/labeled-select.po';

export default class ButtonDropdownPo extends LabeledSelectPo {
toggle() {
return this.self().find('[data-testid="dropdown-button"]').click();
}
}
4 changes: 3 additions & 1 deletion cypress/e2e/po/components/labeled-select.po.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ export default class LabeledSelectPo extends ComponentPo {
}

clickLabel(label: string) {
return this.getOptions().contains('li', label).click();
const labelRegex = new RegExp(`^${ label } $`, 'g');
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This caused issues for me when running tests locally because it'd match any dropdown option containing the label provided, eg clickLabel('default') might select an option labeled cluster-fleet-default-c-9jtrc-35cc406cb29a

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was it only happening locally? if so, any idea why? I think I may have run into something similar.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've only seen it locally, I'm guessing when the test runs in ci there aren't other namespaces containing default available. There are probably other po methods that are using .contains without a regular expression and brittle in the same way though.


return this.getOptions().contains(labelRegex).click();
}

/**
Expand Down
2 changes: 1 addition & 1 deletion cypress/e2e/po/components/tabbed.po.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export default class TabbedPo extends ComponentPo {
}

clickTabWithSelector(selector: string) {
return this.self().get(`${ selector }`).click();
return this.self().find(`${ selector }`).click();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switching from get to find ensures we click the right tab when the page has multiple tab groups with the same tabs

}

allTabs() {
Expand Down
37 changes: 37 additions & 0 deletions cypress/e2e/po/components/workloads/container-mount-paths.po.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import ComponentPo from '@/cypress/e2e/po/components/component.po';
import ButtonDropdownPo from '@/cypress/e2e/po/components/button-dropdown.po';
import InputPo from '@/cypress/e2e/po/components/input.po';

class ContainerMountPo extends ComponentPo {
constructor(selector = '.dashboard-root') {
super(selector);
}

nthMountPoint(i: number) {
return new InputPo(`[data-testid="mount-path-${ i }"] input:first-child`);
}
}

export default class ContainerMountPathPo extends ComponentPo {
constructor(selector = '.dashboard-root') {
super(selector);
}

addVolumeButton() : ButtonDropdownPo {
// return this.self().find('[data-testid="container-storage-add-button"]');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

aside from this dead code, everything lgtm from e2e test perspective

return new ButtonDropdownPo('[data-testid="container-storage-add-button"]');
}

addVolume(label: string) {
this.addVolumeButton().toggle();
this.addVolumeButton().clickOptionWithLabel(label);
}

nthVolumeMount(i: number): ContainerMountPo {
return new ContainerMountPo(`[data-testid="container-storage-mount-${ i }"]`);
}

removeVolume(i: number) {
this.self().find(`[data-testid="container-storage-array-list"] [data-testid="remove-item-${ i }"]`).click();
}
}
18 changes: 18 additions & 0 deletions cypress/e2e/po/components/workloads/pod-storage.po.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import ComponentPo from '@/cypress/e2e/po/components/component.po';
import CodeMirrorPo from '@/cypress/e2e/po/components/code-mirror.po';

class WorkloadVolumePo extends ComponentPo {
yamlEditor(): CodeMirrorPo {
return CodeMirrorPo.bySelector(this.self(), '[data-testid="yaml-editor-code-mirror"]');
}
}

export default class WorkloadPodStoragePo extends ComponentPo {
constructor(selector = '.dashboard-root') {
super(selector);
}

nthVolumeComponent(n: number) {
return new WorkloadVolumePo(`[data-testid="volume-component-${ n }"]`);
}
}
8 changes: 8 additions & 0 deletions cypress/e2e/po/components/yaml-editor.po.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import CodeMirrorPo from '@/cypress/e2e/po/components/code-mirror.po';
import ComponentPo from '@/cypress/e2e/po/components/component.po';

export default class YamlEditorPo extends ComponentPo {
input(): CodeMirrorPo {
return CodeMirrorPo.bySelector(this.self(), '[data-testid="yaml-editor-code-mirror"]');
}
}
51 changes: 51 additions & 0 deletions cypress/e2e/po/pages/explorer/workloads/workloads.po.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ import AsyncButtonPo from '@/cypress/e2e/po/components/async-button.po';
import LabeledSelectPo from '@/cypress/e2e/po/components/labeled-select.po';
import WorkloadPagePo from '@/cypress/e2e/po/pages/explorer/workloads.po';
import PromptRemove from '@/cypress/e2e/po/prompts/promptRemove.po';
import TabbedPo from '@/cypress/e2e/po/components/tabbed.po';
import WorkloadPodStoragePo from '@/cypress/e2e/po/components/workloads/pod-storage.po';
import ContainerMountPathPo from '@/cypress/e2e/po/components/workloads/container-mount-paths.po';
import { WorkloadType } from '@shell/types/fleet';

export class workloadDetailsPageBasePo extends PagePo {
static url: string;

Expand Down Expand Up @@ -115,6 +119,10 @@ export class WorkloadsListPageBasePo extends PagePo {
return this.sortableTable().rowActionMenuOpen(elemName).getMenuItem('Edit YAML').click();
}

goToEditConfigPage(elemName: string) {
return this.sortableTable().rowActionMenuOpen(elemName).getMenuItem('Edit Config').click();
}

private workload() {
return new WorkloadPagePo();
}
Expand Down Expand Up @@ -164,6 +172,49 @@ export class WorkloadsCreatePageBasePo extends PagePo {
return new AsyncButtonPo('[data-testid="form-save"]', this.self());
}

/**
*
* @returns po for the top level tabs in workloads ie general workload, pod, and one more per container
*/
horizontalTabs(): TabbedPo {
return new TabbedPo('[data-testid="workload-horizontal-tabs"]');
}

/**
*
* @returns po for the vertical tabs within the first horizontal tab, ie non-pod workload configuration
*/
generalTabs(): TabbedPo {
return new TabbedPo('[data-testid="workload-general-tabs"]');
}

/**
*
* @returns po for the vertical tabs within the pod tab
*/
podTabs(): TabbedPo {
return new TabbedPo('[data-testid="workload-pod-tabs"]');
}

/**
*
* @param containerIndex
* @returns po for vertical tabs used to configure nth container
*/
nthContainerTabs(containerIndex: number) {
this.horizontalTabs().clickTabWithSelector(`>ul>li:nth-child(${ containerIndex + 3 })`);

return new TabbedPo(`[data-testid="workload-container-tabs-${ containerIndex }"]`);
}

podStorage(): WorkloadPodStoragePo {
return new WorkloadPodStoragePo();
}

containerStorage(): ContainerMountPathPo {
return new ContainerMountPathPo();
}

createWithUI(name: string, containerImage: string, namespace = 'default') {
// NB: namespace is already selected by default
this.selectNamespace(namespace);
Expand Down
90 changes: 80 additions & 10 deletions cypress/e2e/tests/pages/explorer2/workloads/deployments.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ describe('Cluster Explorer', () => {
let deploymentsListPage: WorkloadsDeploymentsListPagePo;
let deploymentsCreatePage: WorkloadsDeploymentsCreatePagePo;

// collect name/namespace of all workloads created in this test suite & delete them afterwards
// edit deployment tests each create a workload per run to improve their retryability
const e2eWorkloads: { name: string; namespace: string; }[] = [];

beforeEach(() => {
Expand All @@ -23,15 +25,19 @@ describe('Cluster Explorer', () => {
});

it('should be able to create a new deployment with basic options', () => {
const { name, namespace } = deploymentCreateRequest.metadata;
const name = `e2e-deployment-${ Math.random().toString(36).substr(2, 6) }`;

deploymentCreateRequest.metadata.name = name;
const { namespace } = deploymentCreateRequest.metadata;
const containerImage = 'nginx';

deploymentsCreatePage.goTo();

deploymentsCreatePage.createWithUI(name, containerImage, namespace);

cy.wait('@createDeployment').then(({ request, response }) => {
expect(request.body).to.deep.eq(deploymentCreateRequest);
// comparing pod spec instead of the entire request body to avoid needing to compare labels that include the dynamic test name
expect(request.body.spec.template.spec).to.deep.eq(deploymentCreateRequest.spec.template.spec);
expect(response.statusCode).to.eq(201);
expect(response.body.metadata.name).to.eq(name);
expect(response.body.metadata.namespace).to.eq(namespace);
Expand All @@ -43,16 +49,26 @@ describe('Cluster Explorer', () => {
});

describe('Update: Deployments', () => {
const { name: workloadName, namespace } = createDeploymentBlueprint.metadata;
const workloadDetailsPage = new WorkloadsDeploymentsDetailsPagePo(workloadName);
let workloadName;
let workloadDetailsPage;

const { namespace } = createDeploymentBlueprint.metadata;
let deploymentEditConfigPage;

beforeEach(() => {
cy.intercept('GET', `/v1/apps.deployments/${ namespace }/${ workloadName }`).as('testWorkload');
cy.intercept('GET', `/v1/apps.deployments/${ namespace }/${ workloadName }`).as('clonedPod');
workloadName = `e2e-deployment-${ Math.random().toString(36).substr(2, 6) }`;
const testDeployment = { ...createDeploymentBlueprint };

workloadDetailsPage = new WorkloadsDeploymentsDetailsPagePo(workloadName);

testDeployment.metadata.name = workloadName;
deploymentsListPage.goTo();
deploymentsListPage.createWithKubectl(createDeploymentBlueprint);

cy.intercept('PUT', `/v1/apps.deployments/${ namespace }/${ workloadName }`).as('editDeployment');
deploymentsListPage.goTo();
deploymentsListPage.goToEditConfigPage(workloadName);
deploymentEditConfigPage = new WorkloadsDeploymentsCreatePagePo();
// Collect the name of the workload for cleanup
e2eWorkloads.push({ name: workloadName, namespace });
});
Expand All @@ -61,10 +77,65 @@ describe('Cluster Explorer', () => {
workloadDetailsPage.goTo();
workloadDetailsPage.mastheadTitle().should('contain', workloadName);
});

it('Should be able to view and edit configuration of pod volumes with no custom component', () => {
// open the pod tab
deploymentEditConfigPage.horizontalTabs().clickTabWithSelector('li#pod');

// open the pod storage tab
deploymentEditConfigPage.podTabs().clickTabWithSelector('li#storage');

// check that there is a component rendered for each workload volume and that that component has rendered some information about the volume
deploymentEditConfigPage.podStorage().nthVolumeComponent(0).yamlEditor().value()
.should('contain', 'name: test-vol');
deploymentEditConfigPage.podStorage().nthVolumeComponent(1).yamlEditor().value()
.should('contain', 'name: test-vol1');

// now try editing
deploymentEditConfigPage.podStorage().nthVolumeComponent(0).yamlEditor().set('name: test-vol-changed\nprojected:\n defaultMode: 420');

// verify that the list of volumes in the container tab has updated
deploymentEditConfigPage.nthContainerTabs(0).clickTabWithSelector('li#storage');
deploymentEditConfigPage.containerStorage().addVolumeButton().toggle();
deploymentEditConfigPage.containerStorage().addVolumeButton().getOptions().should('contain', 'test-vol-changed (projected)');
deploymentEditConfigPage.containerStorage().addVolumeButton().getOptions().should('not.contain', 'test-vol (projected)');

deploymentEditConfigPage.saveCreateForm().click();

cy.wait('@editDeployment').then(({ request, response }) => {
expect(request.body.spec.template.spec.volumes[0]).to.deep.eq({ name: 'test-vol-changed', projected: { defaultMode: 420 } });
expect(response.body.spec.template.spec.volumes[0]).to.deep.eq({ name: 'test-vol-changed', projected: { defaultMode: 420, sources: null } });
});
});

it('should be able to add container volume mounts', () => {
deploymentEditConfigPage.nthContainerTabs(0).clickTabWithSelector('li#storage');

deploymentEditConfigPage.containerStorage().addVolume('test-vol1');

deploymentEditConfigPage.containerStorage().nthVolumeMount(0).nthMountPoint(0).set('test-123');

deploymentEditConfigPage.saveCreateForm().click();

cy.wait('@editDeployment').then(({ request, response }) => {
expect(request.body.spec.template.spec.containers[0].volumeMounts).to.deep.eq([{ mountPath: 'test-123', name: 'test-vol1' }]);
expect(response.body.spec.template.spec.containers[0].volumeMounts).to.deep.eq([{ mountPath: 'test-123', name: 'test-vol1' }]);
});

// test removing volumes
deploymentsListPage.goToEditConfigPage(workloadName);
deploymentEditConfigPage.nthContainerTabs(0).clickTabWithSelector('li#storage');
deploymentEditConfigPage.containerStorage().removeVolume(0);
deploymentEditConfigPage.saveCreateForm().click();

cy.wait('@editDeployment').then(({ request, response }) => {
expect(request.body.spec.template.spec.containers[0].volumeMounts).to.deep.eq([]);
expect(response.body.spec.template.spec.containers[0].volumeMounts).to.eq(undefined);
});
});
});

describe('List: Deployments', () => {
// To reduce test runtime, will use the same workload for all the tests
it('Should list the workloads', () => {
deploymentsListPage.goTo();
e2eWorkloads.forEach(({ name }) => {
Expand All @@ -74,10 +145,9 @@ describe('Cluster Explorer', () => {
});

describe('Delete: Deployments', () => {
const deploymentName = deploymentCreateRequest.metadata.name;

// To reduce test runtime, will use the same workload for all the tests
it('Should be able to delete a workload', () => {
const deploymentName = e2eWorkloads[0].name;

deploymentsListPage.goTo();

deploymentsListPage.listElementWithName(deploymentName).should('exist');
Expand Down
1 change: 1 addition & 0 deletions shell/components/ButtonDropdown.vue
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ export default {
tabindex="-1"
type="button"
class="dropdown-button-two btn"
data-testid="dropdown-button"
@click="ddButtonAction(option)"
@focus="focusSearch"
>
Expand Down
Loading
Loading