Skip to content
This repository was archived by the owner on Mar 1, 2024. It is now read-only.

Commit ff15532

Browse files
author
Radim Hrazdil
committed
Added tests for vm list/vies actions (start, stop, ...)
1 parent 514e47a commit ff15532

File tree

7 files changed

+274
-92
lines changed

7 files changed

+274
-92
lines changed

frontend/integration-tests/protractor.conf.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ export const config: Config = {
101101
catalog: ['tests/base.scenario.ts', 'tests/catalog.scenario.ts'],
102102
marketplace: ['tests/base.scenario.ts', 'tests/marketplace/kubernetes-marketplace.scenario.ts'],
103103
overview: ['tests/base.scenario.ts', 'tests/overview/overview.scenario.ts'],
104-
kubevirt: ['tests/base.scenario.ts', 'tests/kubevirt/vm.wizard.scenario.ts'],
104+
kubevirt: ['tests/base.scenario.ts', 'tests/kubevirt/vm.wizard.scenario.ts', 'tests/kubevirt/vm.actions.scenario.ts'],
105105
all: ['tests/base.scenario.ts',
106106
'tests/crud.scenario.ts',
107107
'tests/overview/overview.scenareio.ts',
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/* eslint-disable no-undef */
2+
import { testName } from '../../protractor.conf';
3+
4+
const testLabel = 'automatedTestName';
5+
6+
export const testVM = {
7+
apiVersion: 'kubevirt.io/v1alpha2',
8+
kind: 'VirtualMachine',
9+
metadata: {
10+
name: `vm-${testName}`,
11+
namespace: testName,
12+
labels: {[testLabel]: testName},
13+
},
14+
spec: {
15+
running: false,
16+
template: {
17+
spec: {
18+
domain: {
19+
cpu: {
20+
cores: 1,
21+
},
22+
devices: {
23+
disks: [
24+
{
25+
bootOrder: 1,
26+
disk: {
27+
bus: 'virtio',
28+
},
29+
name: 'rootdisk',
30+
volumeName: 'rootdisk',
31+
},
32+
],
33+
interfaces: [
34+
{
35+
bridge: {},
36+
name: 'eth0',
37+
},
38+
],
39+
},
40+
resources: {
41+
requests: {
42+
memory: '1G',
43+
},
44+
},
45+
},
46+
networks: [
47+
{
48+
name: 'eth0',
49+
pod: {},
50+
},
51+
],
52+
volumes: [
53+
{
54+
containerDisk: {
55+
image: 'kubevirt/cirros-registry-disk-demo:latest',
56+
},
57+
name: 'rootdisk',
58+
},
59+
],
60+
},
61+
},
62+
},
63+
};
64+
65+
export const testNAD = {
66+
apiVersion: 'k8s.cni.cncf.io/v1',
67+
kind: 'NetworkAttachmentDefinition',
68+
metadata: {
69+
name: `ovs-net-1${testName}-${testName}`,
70+
namespace: testName,
71+
labels: {[testLabel]: testName},
72+
},
73+
spec: {
74+
config: '{ "cniVersion": "0.3.1", "type": "ovs", "bridge": "br0" }',
75+
},
76+
};
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/* eslint-disable no-undef */
2+
import { execSync } from 'child_process';
3+
4+
export function removeLeakedResources(leakedResources: Set<string>){
5+
const leakedArray: Array<string> = [...leakedResources];
6+
if (leakedArray.length > 0) {
7+
console.error(`Leaked ${leakedArray.join()}`);
8+
leakedArray.map(r => JSON.parse(r) as {name: string, namespace: string, kind: string})
9+
.forEach(({name, namespace, kind}) => {
10+
try {
11+
execSync(`kubectl delete -n ${namespace} --cascade ${kind} ${name}`);
12+
} catch (error) {
13+
console.error(`Failed to delete ${kind} ${name}:\n${error}`);
14+
}
15+
});
16+
}
17+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/* eslint-disable no-undef */
2+
3+
import { execSync } from 'child_process';
4+
import { browser, ExpectedConditions as until } from 'protractor';
5+
import { appHost, testName } from '../../protractor.conf';
6+
import { resourceRowsPresent, filterForName, isLoaded } from '../../views/crud.view';
7+
import { testVM } from './mocks';
8+
import { removeLeakedResources } from './utils';
9+
import * as vmActionsView from '../../views/kubevirt/vm.actions.view';
10+
11+
const VM_BOOTUP_TIMEOUT = 60000;
12+
const VM_ACTIONS_TIMEOUT = 90000;
13+
14+
describe('Test VM actions', () => {
15+
const leakedResources = new Set<string>();
16+
17+
afterAll(async() => {
18+
removeLeakedResources(leakedResources);
19+
});
20+
21+
describe('Test VM list view kebab actions', () => {
22+
const vmName = `vm-list-view-actions-${testName}`;
23+
beforeAll(async() => {
24+
testVM.metadata.name = vmName;
25+
execSync(`echo '${JSON.stringify(testVM)}' | kubectl create -f -`);
26+
leakedResources.add(JSON.stringify({name: vmName, namespace: testName, kind: 'vm'}));
27+
});
28+
29+
it('Navigates to VMs', async() => {
30+
await browser.get(`${appHost}/k8s/all-namespaces/virtualmachines`);
31+
await isLoaded();
32+
await filterForName(vmName);
33+
await resourceRowsPresent();
34+
});
35+
36+
it('Starts VM', async() => {
37+
await vmActionsView.listViewAction(vmName)('Start');
38+
await browser.wait(until.textToBePresentInElement(vmActionsView.listViewVMmStatus(vmName), 'Running'), VM_BOOTUP_TIMEOUT);
39+
});
40+
41+
it('Restarts VM', async() => {
42+
await vmActionsView.listViewAction(vmName)('Restart');
43+
await browser.wait(until.textToBePresentInElement(vmActionsView.listViewVMmStatus(vmName), 'Starting'), VM_BOOTUP_TIMEOUT);
44+
await browser.wait(until.textToBePresentInElement(vmActionsView.listViewVMmStatus(vmName), 'Running'), VM_BOOTUP_TIMEOUT);
45+
}, VM_ACTIONS_TIMEOUT);
46+
47+
it('Stops VM', async() => {
48+
await vmActionsView.listViewAction(vmName)('Stop');
49+
await browser.wait(until.textToBePresentInElement(vmActionsView.listViewVMmStatus(vmName), 'Off'), 10000);
50+
});
51+
52+
it('Deletes VM', async() => {
53+
await vmActionsView.listViewAction(vmName)('Delete');
54+
await browser.wait(until.not(until.presenceOf(vmActionsView.listViewVMmStatus(vmName))));
55+
leakedResources.delete(JSON.stringify({name: vmName, namespace: testName, kind: 'vm'}));
56+
});
57+
});
58+
59+
describe('Test VM detail view kebab actions', () => {
60+
const vmName = `vm-detail-view-actions-${testName}`;
61+
beforeAll(async() => {
62+
testVM.metadata.name = vmName;
63+
execSync(`echo '${JSON.stringify(testVM)}' | kubectl create -f -`);
64+
leakedResources.add(JSON.stringify({name: vmName, namespace: testName, kind: 'vm'}));
65+
});
66+
67+
it('Navigates to VMs detail page', async() => {
68+
await browser.get(`${appHost}/k8s/all-namespaces/virtualmachines/${vmName}`);
69+
await isLoaded();
70+
});
71+
72+
it('Starts VM', async() => {
73+
await vmActionsView.detailViewAction('Start');
74+
await browser.wait(until.textToBePresentInElement(vmActionsView.detailViewVMmStatus, 'Running'), VM_BOOTUP_TIMEOUT);
75+
});
76+
77+
it('Restarts VM', async() => {
78+
await vmActionsView.detailViewAction('Restart');
79+
await browser.wait(until.textToBePresentInElement(vmActionsView.detailViewVMmStatus, 'Starting'), VM_BOOTUP_TIMEOUT);
80+
await browser.wait(until.textToBePresentInElement(vmActionsView.detailViewVMmStatus, 'Running'), VM_BOOTUP_TIMEOUT);
81+
}, VM_ACTIONS_TIMEOUT);
82+
83+
it('Stops VM', async() => {
84+
await vmActionsView.detailViewAction('Stop');
85+
await browser.wait(until.textToBePresentInElement(vmActionsView.detailViewVMmStatus, 'Off'), 10000);
86+
});
87+
88+
it('Deletes VM', async() => {
89+
await vmActionsView.detailViewAction('Delete');
90+
leakedResources.delete(JSON.stringify({name: vmName, namespace: testName, kind: 'vm'}));
91+
});
92+
});
93+
});

frontend/integration-tests/tests/kubevirt/vm.wizard.scenario.ts

Lines changed: 58 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
import { execSync } from 'child_process';
44
import { browser, by, ExpectedConditions as until } from 'protractor';
55
import { appHost, testName } from '../../protractor.conf';
6-
import { resourceRowsPresent, filterForName, deleteRow, isLoaded, createItemButton, errorMessage } from '../../views/crud.view';
6+
import { resourceRowsPresent, filterForName, deleteRow, createItemButton, isLoaded, errorMessage } from '../../views/crud.view';
7+
import { removeLeakedResources } from './utils';
78
import * as vmView from '../../views/kubevirt/vm.view';
89
import { OrderedMap } from 'immutable';
9-
10+
import { testNAD } from './mocks';
1011

1112
describe('Kubevirt create VM using wizard', () => {
1213
const leakedResources = new Set<string>();
@@ -16,19 +17,7 @@ describe('Kubevirt create VM using wizard', () => {
1617
const workloadProfile = 'generic';
1718
const sourceURL = 'https://download.cirros-cloud.net/0.4.0/cirros-0.4.0-x86_64-disk.img';
1819
const sourceContainer = 'kubevirt/cirros-registry-disk-demo:latest';
19-
const networkDefinitionName = `${testName}-ovs-net-1`;
2020
const pxeInterface = 'eth1';
21-
const testNAD = {
22-
apiVersion: 'k8s.cni.cncf.io/v1',
23-
kind: 'NetworkAttachmentDefinition',
24-
metadata: {
25-
name: networkDefinitionName,
26-
namespace: testName,
27-
},
28-
spec: {
29-
config: '{ "cniVersion": "0.3.1", "type": "ovs", "bridge": "br0" }',
30-
},
31-
};
3221
const provisionMethods = OrderedMap<string, (provisionSource: string) => void>()
3322
.set('PXE', async function(provisionSource) {
3423
await vmView.provisionSourceButton.click();
@@ -47,96 +36,74 @@ describe('Kubevirt create VM using wizard', () => {
4736
await vmView.provisionSourceURL.sendKeys(sourceURL);
4837
});
4938

50-
beforeAll(async() => {
51-
execSync(`echo '${JSON.stringify(testNAD)}' | kubectl create -f -`);
52-
});
53-
54-
afterAll(async() => {
55-
execSync(`kubectl delete -n ${testName} net-attach-def ${networkDefinitionName}`);
56-
const leakedArray: Array<string> = [...leakedResources];
57-
if (leakedArray.length > 0) {
58-
console.error(`Leaked ${leakedArray.join()}`);
59-
leakedArray.map(r => JSON.parse(r) as {name: string, namespace: string, kind: string})
60-
.forEach(({name, namespace, kind}) => {
61-
try {
62-
execSync(`kubectl delete -n ${namespace} --cascade ${kind} ${name}`);
63-
} catch (error) {
64-
console.error(`Failed to delete ${kind} ${name}:\n${error}`);
65-
}
66-
});
67-
}
68-
});
69-
70-
provisionMethods.forEach((provisionMethod, methodName) => {
71-
describe(`Using ${methodName} method.`, () => {
72-
it('Navigates to VMs', async() => {
73-
await browser.get(`${appHost}/k8s/all-namespaces/virtualmachines`);
74-
await isLoaded();
75-
});
76-
77-
it('Opens VM wizard', async() => {
78-
await createItemButton.click().then(() => vmView.createWithWizardLink.click());
79-
});
39+
async function fillBasicSettings(provisionMethod: (provisionSource: string) => void, provisionSourceName: string){
40+
await browser.wait(until.presenceOf(vmView.nameInput), 10000);
41+
await vmView.nameInput.sendKeys(vmName);
8042

81-
it('Configures VM Basic Settings', async() => {
82-
await browser.wait(until.presenceOf(vmView.nameInput), 10000);
83-
await vmView.nameInput.sendKeys(vmName);
43+
await vmView.namespaceButton.click();
44+
await vmView.namespaceMenu.element(by.linkText(testName)).click();
8445

85-
await vmView.namespaceButton.click();
86-
await vmView.namespaceMenu.element(by.linkText(testName)).click();
46+
await provisionMethod(provisionSourceName);
8747

88-
await provisionMethod(methodName);
48+
await vmView.operatingSystemButton.click();
49+
await vmView.operatingSystemMenu.element(by.linkText(operatingSystem)).click();
8950

90-
await vmView.operatingSystemButton.click();
91-
await vmView.operatingSystemMenu.element(by.linkText(operatingSystem)).click();
51+
await vmView.flavorButton.click();
52+
await vmView.flavorSourceMenu.element(by.linkText(flavor)).click();
9253

93-
await vmView.flavorButton.click();
94-
await vmView.flavorSourceMenu.element(by.linkText(flavor)).click();
54+
await vmView.workloadProfileButton.click();
55+
await vmView.workloadProfileMenu.element(by.linkText(workloadProfile)).click();
9556

96-
await vmView.workloadProfileButton.click();
97-
await vmView.workloadProfileMenu.element(by.linkText(workloadProfile)).click();
57+
await vmView.startVMOnCreation.click();
9858

99-
await vmView.startVMOnCreation.click();
59+
await vmView.nextButton.click();
60+
}
10061

101-
await vmView.nextButton.click();
102-
});
62+
async function fillVMNetworking(provisionSourceName: string){
63+
if (provisionSourceName === 'PXE'){
64+
await vmView.createNIC.click();
10365

104-
it('Configures VM Networking', async() => {
105-
if (methodName === 'PXE'){
106-
await vmView.createNIC.click();
66+
await vmView.networkDefinitionButton.click();
67+
await vmView.networkDefinitionMenu.element(by.linkText(testNAD.metadata.name)).click();
10768

108-
await vmView.networkDefinitionButton.click();
109-
await vmView.networkDefinitionMenu.element(by.linkText(networkDefinitionName)).click();
110-
111-
await vmView.pxeNICButton.click();
112-
await vmView.pxeNICMenu.element(by.linkText(pxeInterface)).click();
113-
await vmView.applyButton.click();
114-
}
115-
await vmView.nextButton.click();
116-
});
117-
118-
it('Configures VM Storage', async() => {
119-
await vmView.nextButton.click();
120-
});
121-
122-
it('Confirms to create VM', async() => {
123-
await browser.wait(until.elementToBeClickable(vmView.nextButton), 5000).then(() => vmView.nextButton.click());
69+
await vmView.pxeNICButton.click();
70+
await vmView.pxeNICMenu.element(by.linkText(pxeInterface)).click();
71+
await vmView.applyButton.click();
72+
}
73+
await vmView.nextButton.click();
74+
}
12475

125-
expect(errorMessage.isPresent()).toBe(false);
126-
leakedResources.add(JSON.stringify({name: vmName, namespace: testName, kind: 'vm'}));
127-
});
76+
beforeAll(async() => {
77+
console.log(testNAD);
78+
execSync(`echo '${JSON.stringify(testNAD)}' | kubectl create -f -`);
79+
});
12880

129-
it('Verifies created VM', async() => {
130-
await browser.wait(until.invisibilityOf(vmView.wizardHeader), 5000);
131-
await filterForName(vmName);
132-
await resourceRowsPresent();
133-
await browser.wait(until.textToBePresentInElement(vmView.firstRowVMStatus, 'Running'), 20000);
134-
});
81+
afterAll(async() => {
82+
execSync(`kubectl delete -n ${testName} net-attach-def ${testNAD.metadata.name}`);
83+
removeLeakedResources(leakedResources);
84+
});
13585

136-
it('Removes created VM', async() => {
137-
await deleteRow('VirtualMachine')(vmName);
138-
leakedResources.delete(JSON.stringify({name: vmName, namespace: testName, kind: 'vm'}));
139-
});
86+
provisionMethods.forEach((provisionMethod, methodName) => {
87+
it(`Using ${methodName} provision source.`, async() => {
88+
await browser.get(`${appHost}/k8s/all-namespaces/virtualmachines`);
89+
await isLoaded();
90+
await createItemButton.click().then(() => vmView.createWithWizardLink.click());
91+
await fillBasicSettings(provisionMethod, methodName);
92+
await fillVMNetworking(methodName);
93+
// Use default storage settings
94+
await vmView.nextButton.click();
95+
// Confirm to create VM
96+
await browser.wait(until.elementToBeClickable(vmView.nextButton), 5000).then(() => vmView.nextButton.click());
97+
expect(errorMessage.isPresent()).toBe(false);
98+
leakedResources.add(JSON.stringify({name: vmName, namespace: testName, kind: 'vm'}));
99+
// Verify VM is created and running
100+
await browser.wait(until.invisibilityOf(vmView.wizardHeader), 5000);
101+
await filterForName(vmName);
102+
await resourceRowsPresent();
103+
await browser.wait(until.textToBePresentInElement(vmView.firstRowVMStatus, 'Running'), 20000);
104+
// Delete VM
105+
await deleteRow('VirtualMachine')(vmName);
106+
leakedResources.delete(JSON.stringify({name: vmName, namespace: testName, kind: 'vm'}));
140107
});
141108
});
142109
});

frontend/integration-tests/views/crud.view.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ export const saveChangesBtn = $('#save-changes');
1313
export const reloadBtn = $('#reload-object');
1414
export const cancelBtn = $('#cancel');
1515

16+
export const confirmAction = () => browser.wait(until.presenceOf($('#confirm-action'))).then(() => $('#confirm-action').click());
17+
1618
/**
1719
* Returns a promise that resolves after the loading spinner is not present.
1820
*/

0 commit comments

Comments
 (0)