test: delete from volume page and prune volume e2e tests (#8128)

* test: reset head of the branch to fix rebase issue

Signed-off-by: Daniel Villanueva <davillan@redhat.com>

* test: code refactor

Signed-off-by: Daniel Villanueva <davillan@redhat.com>

* test: some code cleanup

Signed-off-by: Daniel Villanueva <davillan@redhat.com>

---------

Signed-off-by: Daniel Villanueva <davillan@redhat.com>
This commit is contained in:
Daniel Villanueva 2024-08-01 12:00:40 +02:00 committed by GitHub
parent 02eab4165d
commit 94e0619b1f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 148 additions and 2 deletions

View file

@ -18,6 +18,7 @@
import type { Locator, Page } from '@playwright/test';
import { waitUntil } from '../../utility/wait';
import { BasePage } from './base-page';
/**
@ -70,6 +71,10 @@ export abstract class MainPage extends BasePage {
return this.content.getByRole('table');
}
async rowsAreVisible(): Promise<boolean> {
return await this.page.getByRole('row').first().isVisible();
}
async getRowFromTableByName(name: string): Promise<Locator | undefined> {
if (await this.pageIsEmpty()) {
return undefined;
@ -94,4 +99,25 @@ export abstract class MainPage extends BasePage {
}
return undefined;
}
async getRowsFromTableByStatus(status: string): Promise<Locator[]> {
await waitUntil(async () => await this.rowsAreVisible(), { sendError: false });
const table = this.content.getByRole('table');
const rows = await table.getByRole('row').all();
const filteredRows = [];
for (let rowNum = 1; rowNum < rows.length; rowNum++) {
//skip header
const statusCount = await rows[rowNum].getByRole('cell').nth(2).getByTitle(status, { exact: true }).count();
if (statusCount > 0) filteredRows.push(rows[rowNum]);
}
return filteredRows;
}
async countRowsFromTable(): Promise<number> {
await waitUntil(async () => await this.rowsAreVisible(), { sendError: false });
const table = this.content.getByRole('table');
const rows = await table.getByRole('row').all();
return rows.length > 1 ? rows.length - 1 : 0;
}
}

View file

@ -19,7 +19,9 @@
import type { Locator, Page } from '@playwright/test';
import { expect as playExpect } from '@playwright/test';
import { handleConfirmationDialog } from '../../utility/operations';
import { waitUntil, waitWhile } from '../../utility/wait';
import { VolumeState } from '../core/states';
import { CreateVolumePage } from './create-volume-page';
import { MainPage } from './main-page';
import { VolumeDetailsPage } from './volume-details-page';
@ -58,6 +60,19 @@ export class VolumesPage extends MainPage {
return new VolumeDetailsPage(this.page, volumeName);
}
async deleteVolume(volumeName: string): Promise<VolumesPage> {
const volumeRow = await this.getVolumeRowByName(volumeName);
if (volumeRow === undefined) {
throw Error(`Volume: ${volumeName} does not exist`);
}
const containerRowDeleteButton = volumeRow.getByRole('button', { name: 'Delete Volume' });
await playExpect(containerRowDeleteButton).toBeEnabled();
await containerRowDeleteButton.click();
await handleConfirmationDialog(this.page);
return this;
}
async getVolumeRowByName(name: string): Promise<Locator | undefined> {
return this.getRowFromTableByName(name);
}
@ -67,6 +82,14 @@ export class VolumesPage extends MainPage {
return result !== undefined;
}
async countVolumesFromTable(): Promise<number> {
return this.countRowsFromTable();
}
async countUsedVolumesFromTable(): Promise<number> {
return (await this.getRowsFromTableByStatus(VolumeState.Used)).length;
}
async waitForVolumeExists(name: string): Promise<boolean> {
await waitUntil(async () => await this.volumeExists(name));
return true;
@ -76,4 +99,11 @@ export class VolumesPage extends MainPage {
await waitWhile(async () => await this.volumeExists(name));
return true;
}
async pruneVolumes(): Promise<VolumesPage> {
await playExpect(this.pruneVolumesButton).toBeEnabled();
await this.pruneVolumesButton.click();
await handleConfirmationDialog(this.page, 'Prune');
return this;
}
}

View file

@ -20,6 +20,8 @@ import type { Page } from '@playwright/test';
import { expect as playExpect } from '@playwright/test';
import { afterAll, beforeAll, beforeEach, describe, test } from 'vitest';
import { ContainerState, VolumeState } from '../model/core/states';
import type { ContainerInteractiveParams } from '../model/core/types';
import { WelcomePage } from '../model/pages/welcome-page';
import { NavigationBar } from '../model/workbench/navigation';
import { PodmanDesktopRunner } from '../runner/podman-desktop-runner';
@ -30,6 +32,11 @@ let pdRunner: PodmanDesktopRunner;
let page: Page;
let navBar: NavigationBar;
const imageToPull = 'quay.io/centos-bootc/bootc-image-builder';
const imageTag = 'latest';
const containerToRun = 'bootc-image-builder';
const containerStartParams: ContainerInteractiveParams = { attachTerminal: false };
beforeAll(async () => {
pdRunner = new PodmanDesktopRunner();
page = await pdRunner.start();
@ -55,12 +62,11 @@ describe('Volume workflow verification', async () => {
test('Create new Volume', async () => {
let volumesPage = await navBar.openVolumes();
await playExpect(volumesPage.heading).toBeVisible();
const createVolumePage = await volumesPage.openCreateVolumePage(volumeName);
volumesPage = await createVolumePage.createVolume(volumeName);
await playExpect.poll(async () => await volumesPage.waitForVolumeExists(volumeName)).toBeTruthy();
});
test('Test navigation between pages', async () => {
const volumesPage = await navBar.openVolumes();
await playExpect(volumesPage.heading).toBeVisible();
@ -77,10 +83,29 @@ describe('Volume workflow verification', async () => {
await volumeDetails.closeButton.click();
await playExpect(volumesPage.heading).toBeVisible();
});
test('Delete volume from Volumes page', async () => {
let volumesPage = await navBar.openVolumes();
await playExpect(volumesPage.heading).toBeVisible();
const volumeRow = await volumesPage.getVolumeRowByName(volumeName);
playExpect(volumeRow).not.toBeUndefined();
volumesPage = await volumesPage.deleteVolume(volumeName);
await playExpect.poll(async () => await volumesPage.waitForVolumeDelete(volumeName)).toBeTruthy();
});
test('Delete volume through details page', async () => {
//re-create a new volume
let volumesPage = await navBar.openVolumes();
await playExpect(volumesPage.heading).toBeVisible();
const createVolumePage = await volumesPage.openCreateVolumePage(volumeName);
volumesPage = await createVolumePage.createVolume(volumeName);
await playExpect.poll(async () => await volumesPage.waitForVolumeExists(volumeName)).toBeTruthy();
//delete it from the details page
volumesPage = await navBar.openVolumes();
await playExpect(volumesPage.heading).toBeVisible();
const volumeRow = await volumesPage.getVolumeRowByName(volumeName);
playExpect(volumeRow).not.toBeUndefined();
@ -89,4 +114,69 @@ describe('Volume workflow verification', async () => {
await playExpect.poll(async () => await volumesPage.waitForVolumeDelete(volumeName)).toBeTruthy();
});
test('Create volumes from bootc-image-builder', async () => {
//count the number of existing volumes
const navigationBar = new NavigationBar(page);
let volumesPage = await navigationBar.openVolumes();
let previousVolumes = await volumesPage.countVolumesFromTable();
//if there are volumes, check how many are used
if (previousVolumes > 0) {
const usedVolumes = await volumesPage.countUsedVolumesFromTable();
//if there are unused volumes, prune them
if (previousVolumes - usedVolumes > 0) {
volumesPage = await volumesPage.pruneVolumes();
await playExpect
.poll(async () => (await volumesPage.getRowsFromTableByStatus(VolumeState.Unused)).length, { timeout: 10000 })
.toBe(0);
previousVolumes = await volumesPage.countVolumesFromTable();
}
}
//pull image from quay.io/centos-bootc/bootc-image-builder
let images = await navigationBar.openImages();
const pullImagePage = await images.openPullImage();
images = await pullImagePage.pullImage(imageToPull, imageTag);
await playExpect.poll(async () => await images.waitForImageExists(imageToPull)).toBeTruthy();
//start a container from the image (generates 4 new volumes)
const imageDetails = await images.openImageDetails(imageToPull);
const runImage = await imageDetails.openRunImage();
let containers = await runImage.startContainer(containerToRun, containerStartParams);
await playExpect(containers.header).toBeVisible();
await playExpect
.poll(async () => await containers.containerExists(containerToRun), { timeout: 10000 })
.toBeTruthy();
await containers.startContainer(containerToRun);
//check that four volumes are created (in addition to the existing ones)
volumesPage = await navigationBar.openVolumes();
await playExpect(volumesPage.heading).toBeVisible();
const newVolumes = await volumesPage.countVolumesFromTable();
playExpect(newVolumes - previousVolumes).toBe(4);
//check the container is stopped and delete it
containers = await navigationBar.openContainers();
const containerDetails = await containers.openContainersDetails(containerToRun);
await playExpect
.poll(async () => containerDetails.getState(), { timeout: 20000 })
.toContain(ContainerState.Exited.toLowerCase());
await playExpect(await containerDetails.getStateLocator()).toHaveText(ContainerState.Exited.toLowerCase());
containers = await navigationBar.openContainers();
const containersPage = await containers.deleteContainer(containerToRun);
await playExpect(containersPage.heading).toBeVisible();
await playExpect
.poll(async () => await containersPage.containerExists(containerToRun), { timeout: 15000 })
.toBeFalsy();
//prune unused volumes
volumesPage = await navigationBar.openVolumes();
volumesPage = await volumesPage.pruneVolumes();
await playExpect
.poll(async () => (await volumesPage.getRowsFromTableByStatus(VolumeState.Unused)).length, { timeout: 10000 })
.toBe(0);
const finalVolumes = await volumesPage.countVolumesFromTable();
playExpect(finalVolumes - previousVolumes).toBe(0);
});
});