Skip to content

Commit

Permalink
test: kind details operations (podman-desktop#10710)
Browse files Browse the repository at this point in the history
Signed-off-by: Anton Misskii <[email protected]>
  • Loading branch information
amisskii authored Jan 20, 2025
1 parent 8d30d8a commit 9d38b1c
Show file tree
Hide file tree
Showing 5 changed files with 202 additions and 20 deletions.
1 change: 1 addition & 0 deletions tests/playwright/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ export * from './model/pages/registries-page';
export * from './model/pages/resource-card-page';
export * from './model/pages/resource-cli-card-page';
export * from './model/pages/resource-connection-card-page';
export * from './model/pages/resource-details-page';
export * from './model/pages/resources-page';
export * from './model/pages/run-image-page';
export * from './model/pages/settings-bar';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export abstract class CreateClusterBasePage extends BasePage {
return test.step('Create cluster', async () => {
await Promise.race([
(async (): Promise<void> => {
await playExpect(this.clusterCreationButton).toBeVisible();
await playExpect(this.clusterCreationButton).toBeEnabled();
await this.clusterCreationButton.click();
await this.logsButton.scrollIntoViewIfNeeded();
await this.logsButton.click();
Expand Down
45 changes: 45 additions & 0 deletions tests/playwright/src/model/pages/resource-details-page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**********************************************************************
* Copyright (C) 2025 Red Hat, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
***********************************************************************/

import test, { expect as playExpect, type Locator, type Page } from '@playwright/test';

import type { ResourceElementActions } from '../core/operations';
import { DetailsPage } from './details-page';

export class ResourceDetailsPage extends DetailsPage {
readonly resourceStatus: Locator;

constructor(page: Page, title: string) {
super(page, title);
this.resourceStatus = this.header.getByLabel('Connection Status Label');
}

public async performConnectionActionDetails(
operation: ResourceElementActions,
timeout: number = 25_000,
): Promise<void> {
return test.step(`Perform connection action '${operation}' on resource element '${this.resourceName}' from details page`, async () => {
const button = this.controlActions.getByRole('button', {
name: operation,
exact: true,
});
await playExpect(button).toBeEnabled({ timeout: timeout });
await button.click();
});
}
}
52 changes: 51 additions & 1 deletion tests/playwright/src/specs/kind.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ import {
checkClusterResources,
createKindCluster,
deleteCluster,
deleteClusterFromDetails,
resourceConnectionAction,
resourceConnectionActionDetails,
} from '../utility/cluster-operations';
import { expect as playExpect, test } from '../utility/fixtures';
import { ensureCliInstalled } from '../utility/operations';
Expand Down Expand Up @@ -115,7 +117,7 @@ test.describe.serial('Kind End-to-End Tests', { tag: '@k8s_e2e' }, () => {
);
});

test('Kind cluster operatioms - RESTART', async ({ page }) => {
test('Kind cluster operations - RESTART', async ({ page }) => {
await resourceConnectionAction(
page,
kindResourceCard,
Expand All @@ -128,4 +130,52 @@ test.describe.serial('Kind End-to-End Tests', { tag: '@k8s_e2e' }, () => {
await deleteCluster(page, RESOURCE_NAME, KIND_NODE, CLUSTER_NAME);
});
});
test.describe('Kind cluster operations - Details', () => {
test.skip(
!!process.env.GITHUB_ACTIONS && process.env.RUNNER_OS === 'Linux',
'Tests suite should not run on Linux platform',
);
test('Create a Kind cluster', async ({ page }) => {
test.setTimeout(CLUSTER_CREATION_TIMEOUT);
if (process.env.GITHUB_ACTIONS && process.env.RUNNER_OS === 'Linux') {
await createKindCluster(page, CLUSTER_NAME, false, CLUSTER_CREATION_TIMEOUT, { useIngressController: false });
} else {
await createKindCluster(page, CLUSTER_NAME, true, CLUSTER_CREATION_TIMEOUT);
}
});

test('Kind cluster operations details - STOP', async ({ page }) => {
await resourceConnectionActionDetails(
page,
kindResourceCard,
CLUSTER_NAME,
ResourceElementActions.Stop,
ResourceElementState.Off,
);
});

test('Kind cluster operations details - START', async ({ page }) => {
await resourceConnectionActionDetails(
page,
kindResourceCard,
CLUSTER_NAME,
ResourceElementActions.Start,
ResourceElementState.Running,
);
});

test('Kind cluster operations details - RESTART', async ({ page }) => {
await resourceConnectionActionDetails(
page,
kindResourceCard,
CLUSTER_NAME,
ResourceElementActions.Restart,
ResourceElementState.Running,
);
});

test('Kind cluster operations details - DELETE', async ({ page }) => {
await deleteClusterFromDetails(page, RESOURCE_NAME, KIND_NODE, CLUSTER_NAME);
});
});
});
122 changes: 104 additions & 18 deletions tests/playwright/src/utility/cluster-operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { ContainerState, ResourceElementState } from '../model/core/states';
import type { KindClusterOptions } from '../model/core/types';
import { CreateKindClusterPage } from '../model/pages/create-kind-cluster-page';
import { ResourceConnectionCardPage } from '../model/pages/resource-connection-card-page';
import { ResourceDetailsPage } from '../model/pages/resource-details-page';
import { ResourcesPage } from '../model/pages/resources-page';
import { VolumesPage } from '../model/pages/volumes-page';
import { NavigationBar } from '../model/workbench/navigation';
Expand Down Expand Up @@ -83,6 +84,7 @@ export async function deleteCluster(
resourceName: string = 'kind',
containerName: string = 'kind-cluster-control-plane',
clusterName: string = 'kind-cluster',
timeout: number = 30_000,
): Promise<void> {
return test.step(`Delete ${resourceName} cluster`, async () => {
const navigationBar = new NavigationBar(page);
Expand All @@ -99,27 +101,13 @@ export async function deleteCluster(

await resourceCard.performConnectionAction(ResourceElementActions.Stop);
await playExpect(resourceCard.resourceElementConnectionStatus).toHaveText(ResourceElementState.Off, {
timeout: 50_000,
timeout: timeout,
});
await resourceCard.performConnectionAction(ResourceElementActions.Delete);
await playExpect(resourceCard.markdownContent).toBeVisible({
timeout: 50_000,
timeout: timeout,
});
const containersPage = await navigationBar.openContainers();
await playExpect(containersPage.heading).toBeVisible();
await playExpect
.poll(async () => containersPage.containerExists(containerName), {
timeout: 10_000,
})
.toBeFalsy();

const volumePage = await navigationBar.openVolumes();
await playExpect(volumePage.heading).toBeVisible();
await playExpect
.poll(async () => await volumePage.waitForVolumeDelete(volumeName), {
timeout: 20_000,
})
.toBeTruthy();
await validateClusterResourcesDeletion(page, clusterName, containerName, volumeName);
});
}

Expand All @@ -146,7 +134,7 @@ export async function resourceConnectionAction(
resourceCard: ResourceConnectionCardPage,
resourceConnectionAction: ResourceElementActions,
expectedResourceState: ResourceElementState,
timeout: number = 50_000,
timeout: number = 30_000,
): Promise<void> {
return test.step(`Performs "${resourceConnectionAction}" action, expects "${expectedResourceState}" state.`, async () => {
const navigationBar = new NavigationBar(page);
Expand All @@ -164,3 +152,101 @@ export async function resourceConnectionAction(
});
});
}

export async function resourceConnectionActionDetails(
page: Page,
resourceCard: ResourceConnectionCardPage,
resourceName: string,
resourceConnectionAction: ResourceElementActions,
expectedResourceState: ResourceElementState,
timeout: number = 30_000,
): Promise<void> {
return test.step(`Performs a connection action '${resourceConnectionAction}' on the resource from the details page, verifies the expected resource state '${expectedResourceState}'`, async () => {
const navigationBar = new NavigationBar(page);
const resourceDetailsPage = new ResourceDetailsPage(page, resourceName);

try {
await playExpect(resourceDetailsPage.heading).toBeVisible();
} catch {
const settingsBar = await navigationBar.openSettings();
const resourcesPage = await settingsBar.openTabPage(ResourcesPage);
await playExpect(resourcesPage.heading).toBeVisible({ timeout: 10_000 });
await playExpect(resourceCard.resourceElementDetailsButton).toBeEnabled();
await resourceCard.resourceElementDetailsButton.click();
}

await resourceDetailsPage.performConnectionActionDetails(resourceConnectionAction);
if (resourceConnectionAction === ResourceElementActions.Restart) {
const stopButton = resourceDetailsPage.controlActions.getByRole('button', {
name: ResourceElementActions.Stop,
exact: true,
});
await playExpect(stopButton).toBeEnabled({ timeout: timeout });
}
await playExpect(resourceDetailsPage.resourceStatus).toHaveText(expectedResourceState, {
timeout: timeout,
});
});
}

export async function deleteClusterFromDetails(
page: Page,
resourceName: string = 'kind',
containerName: string = 'kind-cluster-control-plane',
clusterName: string = 'kind-cluster',
timeout: number = 30_000,
): Promise<void> {
return test.step(`Deletes the '${clusterName}' cluster from the details page`, async () => {
const navigationBar = new NavigationBar(page);
const volumeName = await getVolumeNameForContainer(page, containerName);

const settingsBar = await navigationBar.openSettings();
const resourcesPage = await settingsBar.openTabPage(ResourcesPage);
const resourceCard = new ResourceConnectionCardPage(page, resourceName, clusterName);
await playExpect(resourcesPage.heading).toBeVisible({ timeout: 10_000 });
if (!(await resourceCard.doesResourceElementExist())) {
console.log(`Cluster [${clusterName}] not present, skipping deletion.`);
return;
}
await playExpect(resourceCard.resourceElementDetailsButton).toBeEnabled();
await resourceCard.resourceElementDetailsButton.click();

const resourceDetails = new ResourceDetailsPage(page, clusterName);
await playExpect(resourceDetails.heading).toBeVisible();
await resourceDetails.performConnectionActionDetails(ResourceElementActions.Stop);
await playExpect(resourceDetails.resourceStatus).toHaveText(ResourceElementState.Off, {
timeout: timeout,
});
await resourceDetails.performConnectionActionDetails(ResourceElementActions.Delete);

await validateClusterResourcesDeletion(page, clusterName, containerName, volumeName);
});
}

export async function validateClusterResourcesDeletion(
page: Page,
clusterName: string,
containerName: string,
volumeName: string,
timeout: number = 20_000,
): Promise<void> {
return test.step(`Validates that resources associated with the deleted '${clusterName}' cluster are removed`, async () => {
const navigationBar = new NavigationBar(page);
const containersPage = await navigationBar.openContainers();

await playExpect(containersPage.heading).toBeVisible();
await playExpect
.poll(async () => containersPage.containerExists(containerName), {
timeout: timeout,
})
.toBeFalsy();

const volumePage = await navigationBar.openVolumes();
await playExpect(volumePage.heading).toBeVisible();
await playExpect
.poll(async () => await volumePage.waitForVolumeDelete(volumeName), {
timeout: timeout,
})
.toBeTruthy();
});
}

0 comments on commit 9d38b1c

Please sign in to comment.