diff --git a/package.json b/package.json index 9e0fbe1a39d..9ec3fff7d50 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "test:e2e:extension": "npm run test:e2e:build && npm run test:e2e:extension:run", "test:e2e:extension:run": "xvfb-maybe --auto-servernum --server-args='-screen 0 1280x960x24' -- npx playwright test tests/playwright/src/specs/extension-installation.spec.ts", "test:e2e:pw": "npm run test:e2e:build && npm run test:e2e:pw:run", - "test:e2e:pw:run": "xvfb-maybe --auto-servernum --server-args='-screen 0 1280x960x24' -- npx playwright test tests/playwright/src/specs/pod-smoke.spec.ts", + "test:e2e:pw:run": "xvfb-maybe --auto-servernum --server-args='-screen 0 1280x960x24' -- npx playwright test tests/playwright/src/specs/podman-machine-tests.spec.ts", "test:e2e:update": "npm run test:e2e:build && npm run test:e2e:update:run", "test:e2e:update:run": "xvfb-maybe --auto-servernum --server-args='-screen 0 1280x960x24' -- npx playwright test tests/playwright/src/specs/installation/ -g @update-install", "test:main": "vitest run -r packages/main --passWithNoTests --coverage", diff --git a/tests/playwright/src/model/pages/podman-machine-details-page.ts b/tests/playwright/src/model/pages/podman-machine-details-page.ts index 9568f1055a6..600a9d5873f 100644 --- a/tests/playwright/src/model/pages/podman-machine-details-page.ts +++ b/tests/playwright/src/model/pages/podman-machine-details-page.ts @@ -29,9 +29,9 @@ export class PodmanMachineDetails extends ResourcesPage { readonly podmanMachineStopButton: Locator; readonly podmanMachineDeleteButton: Locator; - constructor(page: Page) { + constructor(page: Page, podmanMachineName: string) { super(page); - this.podmanMachineName = this.getPage().getByRole('heading', { name: 'Podman Machine' }); + this.podmanMachineName = this.getPage().getByRole('heading', { name: podmanMachineName }); this.podmanMachineStatus = this.getPage().getByLabel('Connection Status Label'); this.podmanMachineConnectionActions = this.getPage().getByRole('group', { name: 'Connection Actions' }); this.podmanMachineStartButton = this.podmanMachineConnectionActions.getByRole('button', { diff --git a/tests/playwright/src/model/pages/podman-onboarding-page.ts b/tests/playwright/src/model/pages/podman-onboarding-page.ts index 3fb3a0b668d..a56574fe715 100644 --- a/tests/playwright/src/model/pages/podman-onboarding-page.ts +++ b/tests/playwright/src/model/pages/podman-onboarding-page.ts @@ -34,6 +34,7 @@ export class PodmanOnboardingPage extends OnboardingPage { readonly podmanMachineStartAfterCreationCheckbox: Locator; readonly podmanMachineCreateButton: Locator; readonly podmanMachineShowLogsButton: Locator; + readonly goBackButton: Locator; constructor(page: Page) { super(page); @@ -41,7 +42,7 @@ export class PodmanOnboardingPage extends OnboardingPage { name: 'Autostart Podman engine when launching Podman Desktop', }); this.createMachinePageTitle = this.onboardingComponent.getByLabel('title'); - this.podmanMachineConfiguration = this.mainPage.getByRole('form', { name: 'Properties Information' }); + this.podmanMachineConfiguration = this.page.getByRole('form', { name: 'Properties Information' }); this.podmanMachineName = this.podmanMachineConfiguration.getByRole('textbox', { name: 'Name' }); this.podmanMachineCPUs = this.podmanMachineConfiguration.getByRole('slider', { name: 'CPU(s)' }); this.podmanMachineMemory = this.podmanMachineConfiguration.getByRole('slider', { name: 'Memory' }); @@ -59,5 +60,6 @@ export class PodmanOnboardingPage extends OnboardingPage { }); this.podmanMachineCreateButton = this.podmanMachineConfiguration.getByRole('button', { name: 'Create' }); this.podmanMachineShowLogsButton = this.mainPage.getByRole('button', { name: 'Show Logs' }); + this.goBackButton = this.page.getByRole('button', { name: 'Go back to resources' }); } } diff --git a/tests/playwright/src/model/pages/resources-page.ts b/tests/playwright/src/model/pages/resources-page.ts index 8bd1e25619c..7c059ac8b69 100644 --- a/tests/playwright/src/model/pages/resources-page.ts +++ b/tests/playwright/src/model/pages/resources-page.ts @@ -31,7 +31,20 @@ export class ResourcesPage extends SettingsPage { } public async resourceCardIsVisible(resourceLabel: string): Promise { - const resourceCard = this.content.getByRole('region', { name: resourceLabel }); - return (await resourceCard.count()) > 0; + return (await this.resourceCardLocatorGenerator(resourceLabel).count()) > 0; + } + + public async goToCreateNewResourcePage(resourceLabel: string): Promise { + if (!(await this.resourceCardIsVisible(resourceLabel))) { + throw new Error(`Resource card with label ${resourceLabel} is not available`); + } + + await this.resourceCardLocatorGenerator(resourceLabel) + .getByRole('button', { name: `Create new ${resourceLabel}` }) + .click(); + } + + private resourceCardLocatorGenerator(resourceLabel: string): Locator { + return this.content.getByRole('region', { name: resourceLabel, exact: true }); } } diff --git a/tests/playwright/src/specs/podman-machine-onboarding.spec.ts b/tests/playwright/src/specs/podman-machine-onboarding.spec.ts index c9549b5c9aa..92daada948c 100644 --- a/tests/playwright/src/specs/podman-machine-onboarding.spec.ts +++ b/tests/playwright/src/specs/podman-machine-onboarding.spec.ts @@ -153,7 +153,7 @@ test.describe.serial('Podman Machine verification', () => { await playExpect(resourcesPodmanConnections.resourceElement).toBeVisible({ timeout: 20_000 }); await playExpect(resourcesPodmanConnections.resourceElementDetailsButton).toBeVisible(); await resourcesPodmanConnections.resourceElementDetailsButton.click(); - const podmanMachineDetails = new PodmanMachineDetails(page); + const podmanMachineDetails = new PodmanMachineDetails(page, PODMAN_MACHINE_NAME); await playExpect(podmanMachineDetails.podmanMachineStatus).toBeVisible(); await playExpect(podmanMachineDetails.podmanMachineConnectionActions).toBeVisible(); await playExpect(podmanMachineDetails.podmanMachineStartButton).toBeVisible(); @@ -163,7 +163,7 @@ test.describe.serial('Podman Machine verification', () => { }); test('Podman machine operations - STOP', async ({ page }) => { - const podmanMachineDetails = new PodmanMachineDetails(page); + const podmanMachineDetails = new PodmanMachineDetails(page, PODMAN_MACHINE_NAME); await playExpect(podmanMachineDetails.podmanMachineStatus).toHaveText('RUNNING', { timeout: 50_000 }); await playExpect(podmanMachineDetails.podmanMachineStopButton).toBeEnabled(); await podmanMachineDetails.podmanMachineStopButton.click(); @@ -171,14 +171,14 @@ test.describe.serial('Podman Machine verification', () => { }); test('Podman machine operations - START', async ({ page }) => { - const podmanMachineDetails = new PodmanMachineDetails(page); + const podmanMachineDetails = new PodmanMachineDetails(page, PODMAN_MACHINE_NAME); await playExpect(podmanMachineDetails.podmanMachineStartButton).toBeEnabled(); await podmanMachineDetails.podmanMachineStartButton.click(); await playExpect(podmanMachineDetails.podmanMachineStatus).toHaveText('RUNNING', { timeout: 50_000 }); }); test('Podman machine operations - RESTART', async ({ page }) => { - const podmanMachineDetails = new PodmanMachineDetails(page); + const podmanMachineDetails = new PodmanMachineDetails(page, PODMAN_MACHINE_NAME); await playExpect(podmanMachineDetails.podmanMachineRestartButton).toBeEnabled(); await podmanMachineDetails.podmanMachineRestartButton.click(); await playExpect(podmanMachineDetails.podmanMachineStatus).toHaveText('OFF', { timeout: 50_000 }); diff --git a/tests/playwright/src/specs/podman-machine-tests.spec.ts b/tests/playwright/src/specs/podman-machine-tests.spec.ts new file mode 100644 index 00000000000..56a2d1d3cab --- /dev/null +++ b/tests/playwright/src/specs/podman-machine-tests.spec.ts @@ -0,0 +1,178 @@ +/********************************************************************** + * Copyright (C) 2024 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 * as os from 'node:os'; + +import type { Page } from '@playwright/test'; + +import { PodmanMachineDetails } from '../model/pages/podman-machine-details-page'; +import { PodmanOnboardingPage } from '../model/pages/podman-onboarding-page'; +import { ResourceConnectionCardPage } from '../model/pages/resource-connection-card-page'; +import { ResourcesPage } from '../model/pages/resources-page'; +import { expect as playExpect, test } from '../utility/fixtures'; +import { deletePodmanMachine } from '../utility/operations'; +import { waitForPodmanMachineStartup } from '../utility/wait'; + +const DEFAULT_PODMAN_MACHINE = 'Podman Machine'; +const DEFAULT_PODMAN_MACHINE_VISIBLE = 'podman-machine-default'; +const ROOTLESS_PODMAN_MACHINE_VISIBLE = 'podman-machine-rootless'; +const ROOTLESS_PODMAN_MACHINE = 'Podman Machine rootless'; +const RESOURCE_NAME = 'podman'; + +test.skip(os.platform() === 'linux', 'Tests suite should not run on Linux platform'); + +test.beforeAll(async ({ runner, welcomePage, page }) => { + runner.setVideoAndTraceName('podman-machine-tests'); + await welcomePage.handleWelcomePage(true); + await waitForPodmanMachineStartup(page); +}); + +test.afterAll(async ({ runner }) => { + await runner.close(); +}); + +test.describe.serial(`Podman machine switching validation `, () => { + test('Check data for available Podman Machine and stop machine', async ({ page, navigationBar }) => { + const settingsBar = await navigationBar.openSettings(); + await settingsBar.resourcesTab.click(); + const resourcesPage = new ResourcesPage(page); + await playExpect(resourcesPage.heading).toBeVisible(); + await playExpect.poll(async () => await resourcesPage.resourceCardIsVisible(RESOURCE_NAME)).toBeTruthy(); + const resourcesPodmanConnections = new ResourceConnectionCardPage( + page, + RESOURCE_NAME, + DEFAULT_PODMAN_MACHINE_VISIBLE, + ); + await playExpect(resourcesPodmanConnections.providerConnections).toBeVisible({ timeout: 10_000 }); + await playExpect(resourcesPodmanConnections.resourceElement).toBeVisible({ timeout: 20_000 }); + await playExpect(resourcesPodmanConnections.resourceElementDetailsButton).toBeVisible(); + await resourcesPodmanConnections.resourceElementDetailsButton.click(); + const podmanMachineDetails = new PodmanMachineDetails(page, DEFAULT_PODMAN_MACHINE); + await playExpect(podmanMachineDetails.podmanMachineStatus).toBeVisible(); + await playExpect(podmanMachineDetails.podmanMachineConnectionActions).toBeVisible(); + await playExpect(podmanMachineDetails.podmanMachineStartButton).toBeVisible(); + await playExpect(podmanMachineDetails.podmanMachineRestartButton).toBeVisible(); + await playExpect(podmanMachineDetails.podmanMachineStopButton).toBeVisible(); + await playExpect(podmanMachineDetails.podmanMachineDeleteButton).toBeVisible(); + await playExpect(podmanMachineDetails.podmanMachineStatus).toHaveText('RUNNING', { timeout: 50_000 }); + await playExpect(podmanMachineDetails.podmanMachineStopButton).toBeEnabled(); + await podmanMachineDetails.podmanMachineStopButton.click(); + await playExpect(podmanMachineDetails.podmanMachineStatus).toHaveText('OFF', { timeout: 50_000 }); + }); + + test('Create rootless podman machine', async ({ page, navigationBar }) => { + test.setTimeout(120000); + + const dashboard = await navigationBar.openDashboard(); + await playExpect(dashboard.heading).toBeVisible(); + const settingsBar = await navigationBar.openSettings(); + await settingsBar.resourcesTab.click(); + + const resourcesPage = new ResourcesPage(page); + await playExpect(resourcesPage.heading).toBeVisible(); + await playExpect.poll(async () => await resourcesPage.resourceCardIsVisible(RESOURCE_NAME)).toBeTruthy(); + await resourcesPage.goToCreateNewResourcePage(RESOURCE_NAME); + + const podmanMachineCreatePage = new PodmanOnboardingPage(page); + await playExpect(podmanMachineCreatePage.podmanMachineName).toBeVisible(); + await podmanMachineCreatePage.podmanMachineName.clear(); + await podmanMachineCreatePage.podmanMachineName.fill(ROOTLESS_PODMAN_MACHINE_VISIBLE); + + await playExpect(podmanMachineCreatePage.podmanMachineRootfulCheckbox).toBeChecked(); + await podmanMachineCreatePage.podmanMachineRootfulCheckbox.locator('..').click(); + await playExpect(podmanMachineCreatePage.podmanMachineRootfulCheckbox).not.toBeChecked(); + + await playExpect(podmanMachineCreatePage.podmanMachineStartAfterCreationCheckbox).toBeChecked(); + await podmanMachineCreatePage.podmanMachineStartAfterCreationCheckbox.locator('..').click(); + await playExpect(podmanMachineCreatePage.podmanMachineStartAfterCreationCheckbox).not.toBeChecked(); + + await podmanMachineCreatePage.podmanMachineCreateButton.click(); + await playExpect(podmanMachineCreatePage.goBackButton).toBeEnabled({ timeout: 100000 }); + await podmanMachineCreatePage.goBackButton.click(); + + await playExpect(resourcesPage.heading).toBeVisible(); + }); + + test('Switch to rootless podman machine', async ({ page }) => { + const resourcesPodmanConnections = new ResourceConnectionCardPage( + page, + RESOURCE_NAME, + ROOTLESS_PODMAN_MACHINE_VISIBLE, + ); + + await playExpect(resourcesPodmanConnections.resourceElementDetailsButton).toBeVisible(); + await resourcesPodmanConnections.resourceElementDetailsButton.click(); + + const podmanMachineDetails = new PodmanMachineDetails(page, ROOTLESS_PODMAN_MACHINE); + await playExpect(podmanMachineDetails.podmanMachineName).toBeVisible(); + await playExpect(podmanMachineDetails.podmanMachineStatus).toHaveText('OFF'); + + await playExpect(podmanMachineDetails.podmanMachineStartButton).toBeEnabled(); + await podmanMachineDetails.podmanMachineStartButton.click(); + await playExpect(podmanMachineDetails.podmanMachineStatus).toHaveText('RUNNING', { timeout: 50_000 }); + + await handlePopupDialog(page, 'Yes'); + await handlePopupDialog(page, 'OK'); + }); + + test('Stop rootless podman machine', async ({ page }) => { + const podmanMachineDetails = new PodmanMachineDetails(page, ROOTLESS_PODMAN_MACHINE); + await playExpect(podmanMachineDetails.podmanMachineName).toBeVisible(); + await playExpect(podmanMachineDetails.podmanMachineStatus).toHaveText('RUNNING'); + await playExpect(podmanMachineDetails.podmanMachineStopButton).toBeEnabled(); + await podmanMachineDetails.podmanMachineStopButton.click(); + await playExpect(podmanMachineDetails.podmanMachineStatus).toHaveText('OFF', { timeout: 50_000 }); + }); + + test('Restart default podman machine', async ({ page, navigationBar }) => { + const dashboard = await navigationBar.openDashboard(); + await playExpect(dashboard.heading).toBeVisible(); + const settingsBar = await navigationBar.openSettings(); + await settingsBar.resourcesTab.click(); + const resourcesPage = new ResourcesPage(page); + await playExpect(resourcesPage.heading).toBeVisible(); + await playExpect.poll(async () => await resourcesPage.resourceCardIsVisible(RESOURCE_NAME)).toBeTruthy(); + + const resourcesPodmanConnections = new ResourceConnectionCardPage( + page, + RESOURCE_NAME, + DEFAULT_PODMAN_MACHINE_VISIBLE, + ); + await playExpect(resourcesPodmanConnections.resourceElementDetailsButton).toBeVisible(); + await resourcesPodmanConnections.resourceElementDetailsButton.click(); + const podmanMachineDetails = new PodmanMachineDetails(page, DEFAULT_PODMAN_MACHINE); + await playExpect(podmanMachineDetails.podmanMachineStartButton).toBeEnabled(); + await podmanMachineDetails.podmanMachineStartButton.click(); + await playExpect(podmanMachineDetails.podmanMachineStatus).toHaveText('RUNNING', { timeout: 50_000 }); + await handlePopupDialog(page, 'Yes'); + await handlePopupDialog(page, 'OK'); + }); + + test('Clean up rootless podman machine', async ({ page }) => { + await deletePodmanMachine(page, ROOTLESS_PODMAN_MACHINE_VISIBLE); + }); + + async function handlePopupDialog(page: Page, action: string): Promise { + const dialog = page.getByRole('dialog', { name: 'Podman', exact: true }); + await playExpect(dialog).toBeVisible(); + + const clickOnButton = dialog.getByRole('button', { name: action, exact: true }); + await playExpect(clickOnButton).toBeEnabled(); + await clickOnButton.click(); + } +}); diff --git a/tests/playwright/src/utility/operations.ts b/tests/playwright/src/utility/operations.ts index f74c3784f87..c3d2b44b36c 100644 --- a/tests/playwright/src/utility/operations.ts +++ b/tests/playwright/src/utility/operations.ts @@ -177,7 +177,7 @@ export async function deletePodmanMachine(page: Page, machineVisibleName: string const RESOURCE_NAME: string = 'podman'; const navigationBar = new NavigationBar(page); const dashboardPage = await navigationBar.openDashboard(); - await playExpect(dashboardPage.mainPage).toBeVisible({ timeout: 3000 }); + await playExpect(dashboardPage.heading).toBeVisible(); const settingsBar = await navigationBar.openSettings(); const resourcesPage = await settingsBar.openTabPage(ResourcesPage); await playExpect