From 0d5bc42b16f91201ec600c14f518d730532d29bd Mon Sep 17 00:00:00 2001 From: Vladimir Lazar Date: Tue, 14 May 2024 12:01:29 +0200 Subject: [PATCH 01/13] chore(test): add POM page for bootc Signed-off-by: Vladimir Lazar --- tests/playwright/src/bootc-extension.spec.ts | 6 +- tests/playwright/src/model/bootc-page.ts | 115 +++++++++++++++++++ 2 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 tests/playwright/src/model/bootc-page.ts diff --git a/tests/playwright/src/bootc-extension.spec.ts b/tests/playwright/src/bootc-extension.spec.ts index 832ef16d..afa63115 100644 --- a/tests/playwright/src/bootc-extension.spec.ts +++ b/tests/playwright/src/bootc-extension.spec.ts @@ -23,9 +23,11 @@ import { expect as playExpect } from '@playwright/test'; import { RunnerTestContext } from '@podman-desktop/tests-playwright'; import * as path from 'node:path'; import * as os from 'node:os'; +import { BootcPage } from './model/bootc-page'; let pdRunner: PodmanDesktopRunner; let page: Page; +let webview: Page; let navBar: NavigationBar; let extensionInstalled = false; const imageName = 'quay.io/centos-bootc/fedora-bootc'; @@ -116,7 +118,9 @@ describe('BootC Extension', async () => { await playExpect(imageDetailPage.heading).toBeVisible(); const pathToStore = path.resolve(__dirname, '..', 'output', 'images', `${type}-${architecture}`); - const result = await imageDetailPage.buildDiskImage(pdRunner, type, architecture, pathToStore); + [page, webview] = await imageDetailPage.buildDiskImage(pdRunner); + const bootcPAge = new BootcPage(page, webview); + const result = await bootcPAge.buildDiskImage(pathToStore, type, architecture); playExpect(result).toBeTruthy(); }, 350000, diff --git a/tests/playwright/src/model/bootc-page.ts b/tests/playwright/src/model/bootc-page.ts new file mode 100644 index 00000000..661eafbe --- /dev/null +++ b/tests/playwright/src/model/bootc-page.ts @@ -0,0 +1,115 @@ +/********************************************************************** + * Copyright (C) 2023 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 type { Locator, Page } from '@playwright/test'; +import { expect as playExpect } from '@playwright/test'; + +export class BootcPage { + readonly page: Page; + readonly webview: Page; + readonly heading: Locator; + readonly outputFolderPath: Locator; + readonly rawCheckbox: Locator; + readonly qcow2Checkbox: Locator; + readonly isoCheckbox: Locator; + readonly vmdkCheckbox: Locator; + readonly amiCheckbox: Locator; + readonly amd64Button: Locator; + readonly arm64Button: Locator; + readonly buildButton: Locator; + + constructor(page: Page, webview: Page) { + this.page = page; + this.webview = webview; + this.heading = webview.getByLabel('Build Disk Image'); + this.outputFolderPath = webview.getByLabel('folder-select'); + this.rawCheckbox = webview.locator('label[for="raw"]'); + this.qcow2Checkbox = webview.locator('label[for="qcow2"]'); + this.isoCheckbox = webview.locator('label[for="iso"]'); + this.vmdkCheckbox = webview.locator('label[for="vmdk"]'); + this.amiCheckbox = webview.locator('label[for="ami"]'); + this.amd64Button = webview.locator('label[for="amd64"]'); + this.arm64Button = webview.locator('label[for="arm64"]'); + this.buildButton = webview.getByRole('button', { name: 'Build' }); + } + + async buildDiskImage(pathToStore: string, type: string, architecture: string): Promise { + let result = false; + + try { + await this.outputFolderPath.fill(pathToStore); + await this.uncheckedAllCheckboxes(); + + switch (type.toLocaleLowerCase()) { + case 'raw': + await this.rawCheckbox.check(); + break; + case 'qcow2': + await this.qcow2Checkbox.check(); + break; + case 'iso': + await this.isoCheckbox.check(); + break; + case 'vmdk': + await this.vmdkCheckbox.check(); + break; + case 'ami': + await this.amiCheckbox.check(); + break; + default: + throw new Error(`Unknown type: ${type}`); + } + + switch (architecture.toLocaleLowerCase()) { + case 'amd64': + await playExpect(this.amd64Button).toBeEnabled(); + await this.amd64Button.click(); + break; + case 'arm64': + await playExpect(this.arm64Button).toBeEnabled(); + await this.arm64Button.click(); + break; + default: + throw new Error(`Unknown architecture: ${architecture}`); + } + + await playExpect(this.buildButton).toBeEnabled(); + await this.buildButton.click(); + + const dialogLocator = this.page.getByRole('dialog', { name: 'Bootable Container', exact: true }); + await playExpect.poll(async () => (await dialogLocator.count()) > 0, { timeout: 340000 }).toBeTruthy(); + + const dialogMessageLocator = this.page.getByLabel('Dialog Message'); + result = (await dialogMessageLocator.innerText()).includes('Success!'); + } finally { + const okButtonLocator = this.page.getByRole('button', { name: 'OK' }); + await playExpect(okButtonLocator).toBeEnabled(); + await okButtonLocator.click(); + } + + return result; + } + + private async uncheckedAllCheckboxes(): Promise { + await this.rawCheckbox.uncheck(); + await this.qcow2Checkbox.uncheck(); + await this.isoCheckbox.uncheck(); + await this.vmdkCheckbox.uncheck(); + await this.amiCheckbox.uncheck(); + } +} From 14c1615d187a3e0303c71bdc34de6bb97e08547c Mon Sep 17 00:00:00 2001 From: Vladimir Lazar Date: Tue, 14 May 2024 12:07:22 +0200 Subject: [PATCH 02/13] chore(test): debugging Signed-off-by: Vladimir Lazar --- tests/playwright/src/bootc-extension.spec.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/playwright/src/bootc-extension.spec.ts b/tests/playwright/src/bootc-extension.spec.ts index afa63115..2902cd8f 100644 --- a/tests/playwright/src/bootc-extension.spec.ts +++ b/tests/playwright/src/bootc-extension.spec.ts @@ -99,15 +99,15 @@ describe('BootC Extension', async () => { await playExpect.poll(async () => await imagesPage.waitForImageExists(imageName)).toBeTruthy(); }, 150000); - test.skipIf(isLinux).each([ + test.each([ ['QCOW2', 'ARM64'], - ['QCOW2', 'AMD64'], + //['QCOW2', 'AMD64'], ['AMI', 'ARM64'], - ['AMI', 'AMD64'], + //['AMI', 'AMD64'], ['RAW', 'ARM64'], - ['RAW', 'AMD64'], + //['RAW', 'AMD64'], ['ISO', 'ARM64'], - ['ISO', 'AMD64'], + //['ISO', 'AMD64'], ])( 'Building bootable image type: %s for architecture: %s', async (type, architecture) => { From a4be856ef82f587fd14a3aa422c3a0fc1b32044d Mon Sep 17 00:00:00 2001 From: Vladimir Lazar Date: Tue, 14 May 2024 13:20:27 +0200 Subject: [PATCH 03/13] chore(test): change image in containerfile Signed-off-by: Vladimir Lazar --- tests/playwright/resources/bootable-containerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/playwright/resources/bootable-containerfile b/tests/playwright/resources/bootable-containerfile index 2df480d9..de594264 100644 --- a/tests/playwright/resources/bootable-containerfile +++ b/tests/playwright/resources/bootable-containerfile @@ -16,7 +16,7 @@ * SPDX-License-Identifier: Apache-2.0 ***********************************************************************/ -FROM quay.io/centos-bootc/fedora-bootc:eln +FROM quay.io/centos-bootc/centos-bootc:stream9 # Change the root password RUN echo "root:supersecret" | chpasswd From 11f3aa1bd934966f78923ca9bc35162781aba113 Mon Sep 17 00:00:00 2001 From: Vladimir Lazar Date: Tue, 14 May 2024 13:39:47 +0200 Subject: [PATCH 04/13] chore(test): changing image name Signed-off-by: Vladimir Lazar --- tests/playwright/src/bootc-extension.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/playwright/src/bootc-extension.spec.ts b/tests/playwright/src/bootc-extension.spec.ts index 2902cd8f..cba794d6 100644 --- a/tests/playwright/src/bootc-extension.spec.ts +++ b/tests/playwright/src/bootc-extension.spec.ts @@ -30,7 +30,7 @@ let page: Page; let webview: Page; let navBar: NavigationBar; let extensionInstalled = false; -const imageName = 'quay.io/centos-bootc/fedora-bootc'; +const imageName = 'quay.io/centos-bootc/centos-bootc'; const extensionName = 'bootc'; const extensionLabel = 'redhat.bootc'; const containerFilePath = path.resolve(__dirname, '..', 'resources', 'bootable-containerfile'); @@ -95,7 +95,7 @@ describe('BootC Extension', async () => { const buildImagePage = await imagesPage.openBuildImage(); await playExpect(buildImagePage.heading).toBeVisible(); - imagesPage = await buildImagePage.buildImage(`${imageName}:eln`, containerFilePath, contextDirectory); + imagesPage = await buildImagePage.buildImage(`${imageName}:stream9`, containerFilePath, contextDirectory); await playExpect.poll(async () => await imagesPage.waitForImageExists(imageName)).toBeTruthy(); }, 150000); From 117987286bc6ce122fb607fadf24a15af90d847b Mon Sep 17 00:00:00 2001 From: Vladimir Lazar Date: Tue, 14 May 2024 14:22:18 +0200 Subject: [PATCH 05/13] chore(test): debugging for screen change Signed-off-by: Vladimir Lazar --- tests/playwright/src/bootc-extension.spec.ts | 27 ++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/tests/playwright/src/bootc-extension.spec.ts b/tests/playwright/src/bootc-extension.spec.ts index cba794d6..997cb4ac 100644 --- a/tests/playwright/src/bootc-extension.spec.ts +++ b/tests/playwright/src/bootc-extension.spec.ts @@ -18,7 +18,7 @@ import type { Page } from '@playwright/test'; import { afterAll, beforeAll, test, describe, beforeEach } from 'vitest'; -import { NavigationBar, PodmanDesktopRunner, WelcomePage, deleteImage } from '@podman-desktop/tests-playwright'; +import { ImageDetailsPage, NavigationBar, PodmanDesktopRunner, WelcomePage, deleteImage } from '@podman-desktop/tests-playwright'; import { expect as playExpect } from '@playwright/test'; import { RunnerTestContext } from '@podman-desktop/tests-playwright'; import * as path from 'node:path'; @@ -118,7 +118,8 @@ describe('BootC Extension', async () => { await playExpect(imageDetailPage.heading).toBeVisible(); const pathToStore = path.resolve(__dirname, '..', 'output', 'images', `${type}-${architecture}`); - [page, webview] = await imageDetailPage.buildDiskImage(pdRunner); + //[page, webview] = await imageDetailPage.buildDiskImage(pdRunner); + [page, webview] = await buildDebug(imageDetailPage, pdRunner); const bootcPAge = new BootcPage(page, webview); const result = await bootcPAge.buildDiskImage(pathToStore, type, architecture); playExpect(result).toBeTruthy(); @@ -143,3 +144,25 @@ async function ensureBootcIsRemoved(): Promise { .poll(async () => await extensionsPage.extensionIsInstalled(extensionLabel), { timeout: 30000 }) .toBeFalsy(); } + +async function buildDebug(imageDetailsPage: ImageDetailsPage,runner: PodmanDesktopRunner): Promise<[Page, Page]>{ + await imageDetailsPage.actionsButton.click(); + await playExpect(imageDetailsPage.buildDiskImageButton).toBeEnabled(); + await imageDetailsPage.buildDiskImageButton.click(); + await page.waitForTimeout(5000); + + const webView = page.getByRole('document', { name: 'Bootable Containers' }); + await playExpect(webView).toBeVisible(); + await new Promise(resolve => setTimeout(resolve, 1000)); + const [mainPage, webViewPage] = pdRunner.getElectronApp().windows(); + await mainPage.evaluate(() => { + const element = document.querySelector('webview'); + if (element) { + (element as HTMLElement).focus(); + } else { + console.log(`element is null`); + } + }); + + return [mainPage, webViewPage]; +} \ No newline at end of file From 187c1a94a7173ddee68ef2af56c7bed0cbaa6cec Mon Sep 17 00:00:00 2001 From: Vladimir Lazar Date: Tue, 14 May 2024 14:29:06 +0200 Subject: [PATCH 06/13] chore(test): removing unneeded param Signed-off-by: Vladimir Lazar --- tests/playwright/src/bootc-extension.spec.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/tests/playwright/src/bootc-extension.spec.ts b/tests/playwright/src/bootc-extension.spec.ts index 997cb4ac..b1c35c26 100644 --- a/tests/playwright/src/bootc-extension.spec.ts +++ b/tests/playwright/src/bootc-extension.spec.ts @@ -18,7 +18,13 @@ import type { Page } from '@playwright/test'; import { afterAll, beforeAll, test, describe, beforeEach } from 'vitest'; -import { ImageDetailsPage, NavigationBar, PodmanDesktopRunner, WelcomePage, deleteImage } from '@podman-desktop/tests-playwright'; +import { + ImageDetailsPage, + NavigationBar, + PodmanDesktopRunner, + WelcomePage, + deleteImage, +} from '@podman-desktop/tests-playwright'; import { expect as playExpect } from '@playwright/test'; import { RunnerTestContext } from '@podman-desktop/tests-playwright'; import * as path from 'node:path'; @@ -119,7 +125,7 @@ describe('BootC Extension', async () => { const pathToStore = path.resolve(__dirname, '..', 'output', 'images', `${type}-${architecture}`); //[page, webview] = await imageDetailPage.buildDiskImage(pdRunner); - [page, webview] = await buildDebug(imageDetailPage, pdRunner); + [page, webview] = await buildDebug(imageDetailPage); const bootcPAge = new BootcPage(page, webview); const result = await bootcPAge.buildDiskImage(pathToStore, type, architecture); playExpect(result).toBeTruthy(); @@ -145,7 +151,7 @@ async function ensureBootcIsRemoved(): Promise { .toBeFalsy(); } -async function buildDebug(imageDetailsPage: ImageDetailsPage,runner: PodmanDesktopRunner): Promise<[Page, Page]>{ +async function buildDebug(imageDetailsPage: ImageDetailsPage): Promise<[Page, Page]> { await imageDetailsPage.actionsButton.click(); await playExpect(imageDetailsPage.buildDiskImageButton).toBeEnabled(); await imageDetailsPage.buildDiskImageButton.click(); @@ -165,4 +171,4 @@ async function buildDebug(imageDetailsPage: ImageDetailsPage,runner: PodmanDeskt }); return [mainPage, webViewPage]; -} \ No newline at end of file +} From 62823627b79443db5f88a4935fa1661483fa53f3 Mon Sep 17 00:00:00 2001 From: Vladimir Lazar Date: Tue, 14 May 2024 15:01:13 +0200 Subject: [PATCH 07/13] chore(test): propagating image name Signed-off-by: Vladimir Lazar --- tests/playwright/src/bootc-extension.spec.ts | 2 +- tests/playwright/src/model/bootc-page.ts | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/tests/playwright/src/bootc-extension.spec.ts b/tests/playwright/src/bootc-extension.spec.ts index b1c35c26..1fd71569 100644 --- a/tests/playwright/src/bootc-extension.spec.ts +++ b/tests/playwright/src/bootc-extension.spec.ts @@ -127,7 +127,7 @@ describe('BootC Extension', async () => { //[page, webview] = await imageDetailPage.buildDiskImage(pdRunner); [page, webview] = await buildDebug(imageDetailPage); const bootcPAge = new BootcPage(page, webview); - const result = await bootcPAge.buildDiskImage(pathToStore, type, architecture); + const result = await bootcPAge.buildDiskImage(imageName, pathToStore, type, architecture); playExpect(result).toBeTruthy(); }, 350000, diff --git a/tests/playwright/src/model/bootc-page.ts b/tests/playwright/src/model/bootc-page.ts index 661eafbe..a5d5f7b2 100644 --- a/tests/playwright/src/model/bootc-page.ts +++ b/tests/playwright/src/model/bootc-page.ts @@ -32,12 +32,14 @@ export class BootcPage { readonly amd64Button: Locator; readonly arm64Button: Locator; readonly buildButton: Locator; + readonly imageSelect: Locator; constructor(page: Page, webview: Page) { this.page = page; this.webview = webview; this.heading = webview.getByLabel('Build Disk Image'); this.outputFolderPath = webview.getByLabel('folder-select'); + this.imageSelect = webview.getByLabel('image-select'); this.rawCheckbox = webview.locator('label[for="raw"]'); this.qcow2Checkbox = webview.locator('label[for="qcow2"]'); this.isoCheckbox = webview.locator('label[for="iso"]'); @@ -48,9 +50,17 @@ export class BootcPage { this.buildButton = webview.getByRole('button', { name: 'Build' }); } - async buildDiskImage(pathToStore: string, type: string, architecture: string): Promise { + async buildDiskImage(imageName: string, pathToStore: string, type: string, architecture: string): Promise { let result = false; + if(await this.buildButton.isEnabled()){ + await this.buildButton.click(); + } + + await playExpect(this.buildButton).toBeDisabled(); + + await this.imageSelect.selectOption({ label: imageName }); + try { await this.outputFolderPath.fill(pathToStore); await this.uncheckedAllCheckboxes(); From 34d3d7d2f00bbbc2e5c7d7f3efd3acdda348c5ad Mon Sep 17 00:00:00 2001 From: Vladimir Lazar Date: Tue, 14 May 2024 15:22:13 +0200 Subject: [PATCH 08/13] chore(test): debugging Signed-off-by: Vladimir Lazar --- tests/playwright/src/bootc-extension.spec.ts | 2 +- tests/playwright/src/model/bootc-page.ts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/playwright/src/bootc-extension.spec.ts b/tests/playwright/src/bootc-extension.spec.ts index 1fd71569..90425a53 100644 --- a/tests/playwright/src/bootc-extension.spec.ts +++ b/tests/playwright/src/bootc-extension.spec.ts @@ -127,7 +127,7 @@ describe('BootC Extension', async () => { //[page, webview] = await imageDetailPage.buildDiskImage(pdRunner); [page, webview] = await buildDebug(imageDetailPage); const bootcPAge = new BootcPage(page, webview); - const result = await bootcPAge.buildDiskImage(imageName, pathToStore, type, architecture); + const result = await bootcPAge.buildDiskImage(imageName+':stream9', pathToStore, type, architecture); playExpect(result).toBeTruthy(); }, 350000, diff --git a/tests/playwright/src/model/bootc-page.ts b/tests/playwright/src/model/bootc-page.ts index a5d5f7b2..08f7fb5f 100644 --- a/tests/playwright/src/model/bootc-page.ts +++ b/tests/playwright/src/model/bootc-page.ts @@ -53,12 +53,11 @@ export class BootcPage { async buildDiskImage(imageName: string, pathToStore: string, type: string, architecture: string): Promise { let result = false; - if(await this.buildButton.isEnabled()){ + if (await this.buildButton.isEnabled()) { await this.buildButton.click(); } await playExpect(this.buildButton).toBeDisabled(); - await this.imageSelect.selectOption({ label: imageName }); try { From 071959fc1f062e874257f97e5341b3f9c34e9b73 Mon Sep 17 00:00:00 2001 From: Vladimir Lazar Date: Wed, 15 May 2024 10:44:17 +0200 Subject: [PATCH 09/13] chore(test): changing arch for tests Signed-off-by: Vladimir Lazar --- tests/playwright/src/bootc-extension.spec.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/playwright/src/bootc-extension.spec.ts b/tests/playwright/src/bootc-extension.spec.ts index 90425a53..807995d9 100644 --- a/tests/playwright/src/bootc-extension.spec.ts +++ b/tests/playwright/src/bootc-extension.spec.ts @@ -106,14 +106,14 @@ describe('BootC Extension', async () => { }, 150000); test.each([ - ['QCOW2', 'ARM64'], - //['QCOW2', 'AMD64'], - ['AMI', 'ARM64'], - //['AMI', 'AMD64'], - ['RAW', 'ARM64'], - //['RAW', 'AMD64'], - ['ISO', 'ARM64'], - //['ISO', 'AMD64'], + //['QCOW2', 'ARM64'], + ['QCOW2', 'AMD64'], + //['AMI', 'ARM64'], + ['AMI', 'AMD64'], + //['RAW', 'ARM64'], + ['RAW', 'AMD64'], + //['ISO', 'ARM64'], + ['ISO', 'AMD64'], ])( 'Building bootable image type: %s for architecture: %s', async (type, architecture) => { From 6240b69b67d7c2153471ea7b93e8c97a3f1e2441 Mon Sep 17 00:00:00 2001 From: Vladimir Lazar Date: Wed, 15 May 2024 13:25:40 +0200 Subject: [PATCH 10/13] chore(test): enable skipif Signed-off-by: Vladimir Lazar --- tests/playwright/src/bootc-extension.spec.ts | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/tests/playwright/src/bootc-extension.spec.ts b/tests/playwright/src/bootc-extension.spec.ts index 807995d9..d3b9833b 100644 --- a/tests/playwright/src/bootc-extension.spec.ts +++ b/tests/playwright/src/bootc-extension.spec.ts @@ -105,14 +105,14 @@ describe('BootC Extension', async () => { await playExpect.poll(async () => await imagesPage.waitForImageExists(imageName)).toBeTruthy(); }, 150000); - test.each([ - //['QCOW2', 'ARM64'], + test.skipIf(isLinux).each([ + ['QCOW2', 'ARM64'], ['QCOW2', 'AMD64'], - //['AMI', 'ARM64'], + ['AMI', 'ARM64'], ['AMI', 'AMD64'], - //['RAW', 'ARM64'], + ['RAW', 'ARM64'], ['RAW', 'AMD64'], - //['ISO', 'ARM64'], + ['ISO', 'ARM64'], ['ISO', 'AMD64'], ])( 'Building bootable image type: %s for architecture: %s', @@ -124,8 +124,7 @@ describe('BootC Extension', async () => { await playExpect(imageDetailPage.heading).toBeVisible(); const pathToStore = path.resolve(__dirname, '..', 'output', 'images', `${type}-${architecture}`); - //[page, webview] = await imageDetailPage.buildDiskImage(pdRunner); - [page, webview] = await buildDebug(imageDetailPage); + [page, webview] = await handleWebview(imageDetailPage); const bootcPAge = new BootcPage(page, webview); const result = await bootcPAge.buildDiskImage(imageName+':stream9', pathToStore, type, architecture); playExpect(result).toBeTruthy(); @@ -151,7 +150,7 @@ async function ensureBootcIsRemoved(): Promise { .toBeFalsy(); } -async function buildDebug(imageDetailsPage: ImageDetailsPage): Promise<[Page, Page]> { +async function handleWebview(imageDetailsPage: ImageDetailsPage): Promise<[Page, Page]> { await imageDetailsPage.actionsButton.click(); await playExpect(imageDetailsPage.buildDiskImageButton).toBeEnabled(); await imageDetailsPage.buildDiskImageButton.click(); From 2ea6e4561a98c32291bd4f215b8ed7d7184f9065 Mon Sep 17 00:00:00 2001 From: Vladimir Lazar Date: Wed, 15 May 2024 13:28:11 +0200 Subject: [PATCH 11/13] chore(test): string interpolation Signed-off-by: Vladimir Lazar --- tests/playwright/src/bootc-extension.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/playwright/src/bootc-extension.spec.ts b/tests/playwright/src/bootc-extension.spec.ts index d3b9833b..9c2024a8 100644 --- a/tests/playwright/src/bootc-extension.spec.ts +++ b/tests/playwright/src/bootc-extension.spec.ts @@ -126,7 +126,7 @@ describe('BootC Extension', async () => { const pathToStore = path.resolve(__dirname, '..', 'output', 'images', `${type}-${architecture}`); [page, webview] = await handleWebview(imageDetailPage); const bootcPAge = new BootcPage(page, webview); - const result = await bootcPAge.buildDiskImage(imageName+':stream9', pathToStore, type, architecture); + const result = await bootcPAge.buildDiskImage(`${imageName}:stream9`, pathToStore, type, architecture); playExpect(result).toBeTruthy(); }, 350000, From 5032ae27064f223e2c1428eec5d5aea783413da7 Mon Sep 17 00:00:00 2001 From: Vladimir Lazar Date: Fri, 17 May 2024 17:35:49 +0200 Subject: [PATCH 12/13] chore(tests): refactor bootc tests Signed-off-by: Vladimir Lazar --- tests/playwright/src/bootc-extension.spec.ts | 77 ++++++----- tests/playwright/src/model/bootc-page.ts | 130 ++++++++++++------- 2 files changed, 123 insertions(+), 84 deletions(-) diff --git a/tests/playwright/src/bootc-extension.spec.ts b/tests/playwright/src/bootc-extension.spec.ts index 9c2024a8..fc3b684a 100644 --- a/tests/playwright/src/bootc-extension.spec.ts +++ b/tests/playwright/src/bootc-extension.spec.ts @@ -30,6 +30,7 @@ import { RunnerTestContext } from '@podman-desktop/tests-playwright'; import * as path from 'node:path'; import * as os from 'node:os'; import { BootcPage } from './model/bootc-page'; +import { ArchitectureType } from '@podman-desktop/tests-playwright'; let pdRunner: PodmanDesktopRunner; let page: Page; @@ -37,6 +38,7 @@ let webview: Page; let navBar: NavigationBar; let extensionInstalled = false; const imageName = 'quay.io/centos-bootc/centos-bootc'; +const imageTag = 'stream9'; const extensionName = 'bootc'; const extensionLabel = 'redhat.bootc'; const containerFilePath = path.resolve(__dirname, '..', 'resources', 'bootable-containerfile'); @@ -61,6 +63,8 @@ beforeAll(async () => { afterAll(async () => { try { await deleteImage(page, imageName); + } catch (error) { + console.log(`Error deleting image: ${error}`); } finally { await pdRunner.close(); } @@ -94,42 +98,43 @@ describe('BootC Extension', async () => { 200000, ); - test('Build bootc image from containerfile', async () => { - let imagesPage = await navBar.openImages(); - await playExpect(imagesPage.heading).toBeVisible(); - - const buildImagePage = await imagesPage.openBuildImage(); - await playExpect(buildImagePage.heading).toBeVisible(); - - imagesPage = await buildImagePage.buildImage(`${imageName}:stream9`, containerFilePath, contextDirectory); - await playExpect.poll(async () => await imagesPage.waitForImageExists(imageName)).toBeTruthy(); - }, 150000); - - test.skipIf(isLinux).each([ - ['QCOW2', 'ARM64'], - ['QCOW2', 'AMD64'], - ['AMI', 'ARM64'], - ['AMI', 'AMD64'], - ['RAW', 'ARM64'], - ['RAW', 'AMD64'], - ['ISO', 'ARM64'], - ['ISO', 'AMD64'], - ])( - 'Building bootable image type: %s for architecture: %s', - async (type, architecture) => { - const imagesPage = await navBar.openImages(); - await playExpect(imagesPage.heading).toBeVisible(); - - const imageDetailPage = await imagesPage.openImageDetails(imageName); - await playExpect(imageDetailPage.heading).toBeVisible(); - - const pathToStore = path.resolve(__dirname, '..', 'output', 'images', `${type}-${architecture}`); - [page, webview] = await handleWebview(imageDetailPage); - const bootcPAge = new BootcPage(page, webview); - const result = await bootcPAge.buildDiskImage(`${imageName}:stream9`, pathToStore, type, architecture); - playExpect(result).toBeTruthy(); + describe.each([ArchitectureType.ARM64, ArchitectureType.AMD64])( + 'Bootc images for architecture: %s', + async architecture => { + test('Build bootc image from containerfile', async () => { + let imagesPage = await navBar.openImages(); + await playExpect(imagesPage.heading).toBeVisible(); + + const buildImagePage = await imagesPage.openBuildImage(); + await playExpect(buildImagePage.heading).toBeVisible(); + + imagesPage = await buildImagePage.buildImage( + `${imageName}:${imageTag}`, + containerFilePath, + contextDirectory, + architecture, + ); + await playExpect.poll(async () => await imagesPage.waitForImageExists(imageName)).toBeTruthy(); + }, 150000); + + test.skipIf(isLinux).each(['QCOW2', 'AMI', 'RAW', 'VMDK', 'ISO'])( + `Building bootable image type: %s`, + async type => { + const imagesPage = await navBar.openImages(); + await playExpect(imagesPage.heading).toBeVisible(); + + const imageDetailPage = await imagesPage.openImageDetails(imageName); + await playExpect(imageDetailPage.heading).toBeVisible(); + + const pathToStore = path.resolve(__dirname, '..', 'tests', 'output', 'images', `${type}-${architecture}`); + [page, webview] = await handleWebview(imageDetailPage); + const bootcPAge = new BootcPage(page, webview); + const result = await bootcPAge.buildDiskImage(`${imageName}:${imageTag}`, pathToStore, type, architecture); + playExpect(result).toBeTruthy(); + }, + 350000, + ); }, - 350000, ); test('Remove bootc extension through Settings', async () => { @@ -154,7 +159,7 @@ async function handleWebview(imageDetailsPage: ImageDetailsPage): Promise<[Page, await imageDetailsPage.actionsButton.click(); await playExpect(imageDetailsPage.buildDiskImageButton).toBeEnabled(); await imageDetailsPage.buildDiskImageButton.click(); - await page.waitForTimeout(5000); + await page.waitForTimeout(2000); const webView = page.getByRole('document', { name: 'Bootable Containers' }); await playExpect(webView).toBeVisible(); diff --git a/tests/playwright/src/model/bootc-page.ts b/tests/playwright/src/model/bootc-page.ts index 08f7fb5f..e8f2ae47 100644 --- a/tests/playwright/src/model/bootc-page.ts +++ b/tests/playwright/src/model/bootc-page.ts @@ -18,6 +18,8 @@ import type { Locator, Page } from '@playwright/test'; import { expect as playExpect } from '@playwright/test'; +import { waitUntil, waitWhile } from '@podman-desktop/tests-playwright'; +import { ArchitectureType } from '@podman-desktop/tests-playwright'; export class BootcPage { readonly page: Page; @@ -33,6 +35,10 @@ export class BootcPage { readonly arm64Button: Locator; readonly buildButton: Locator; readonly imageSelect: Locator; + readonly goBackButton: Locator; + readonly rowGroup: Locator; + readonly latestBuiltImage: Locator; + readonly getCurrentStatusOfLatestBuildImage: Locator; constructor(page: Page, webview: Page) { this.page = page; @@ -48,9 +54,18 @@ export class BootcPage { this.amd64Button = webview.locator('label[for="amd64"]'); this.arm64Button = webview.locator('label[for="arm64"]'); this.buildButton = webview.getByRole('button', { name: 'Build' }); + this.goBackButton = webview.getByRole('button', { name: 'Go Back' }); + this.rowGroup = webview.getByRole('rowgroup').nth(1); + this.latestBuiltImage = this.rowGroup.getByRole('row').first(); + this.getCurrentStatusOfLatestBuildImage = this.latestBuiltImage.getByRole('status'); } - async buildDiskImage(imageName: string, pathToStore: string, type: string, architecture: string): Promise { + async buildDiskImage( + imageName: string, + pathToStore: string, + type: string, + architecture: ArchitectureType, + ): Promise { let result = false; if (await this.buildButton.isEnabled()) { @@ -60,57 +75,57 @@ export class BootcPage { await playExpect(this.buildButton).toBeDisabled(); await this.imageSelect.selectOption({ label: imageName }); - try { - await this.outputFolderPath.fill(pathToStore); - await this.uncheckedAllCheckboxes(); - - switch (type.toLocaleLowerCase()) { - case 'raw': - await this.rawCheckbox.check(); - break; - case 'qcow2': - await this.qcow2Checkbox.check(); - break; - case 'iso': - await this.isoCheckbox.check(); - break; - case 'vmdk': - await this.vmdkCheckbox.check(); - break; - case 'ami': - await this.amiCheckbox.check(); - break; - default: - throw new Error(`Unknown type: ${type}`); - } - - switch (architecture.toLocaleLowerCase()) { - case 'amd64': - await playExpect(this.amd64Button).toBeEnabled(); - await this.amd64Button.click(); - break; - case 'arm64': - await playExpect(this.arm64Button).toBeEnabled(); - await this.arm64Button.click(); - break; - default: - throw new Error(`Unknown architecture: ${architecture}`); - } - - await playExpect(this.buildButton).toBeEnabled(); - await this.buildButton.click(); + await this.outputFolderPath.fill(pathToStore); + await this.uncheckedAllCheckboxes(); - const dialogLocator = this.page.getByRole('dialog', { name: 'Bootable Container', exact: true }); - await playExpect.poll(async () => (await dialogLocator.count()) > 0, { timeout: 340000 }).toBeTruthy(); + switch (type.toLocaleLowerCase()) { + case 'raw': + await this.rawCheckbox.check(); + break; + case 'qcow2': + await this.qcow2Checkbox.check(); + break; + case 'iso': + await this.isoCheckbox.check(); + break; + case 'vmdk': + await this.vmdkCheckbox.check(); + break; + case 'ami': + await this.amiCheckbox.check(); + break; + default: + throw new Error(`Unknown type: ${type}`); + } - const dialogMessageLocator = this.page.getByLabel('Dialog Message'); - result = (await dialogMessageLocator.innerText()).includes('Success!'); - } finally { - const okButtonLocator = this.page.getByRole('button', { name: 'OK' }); - await playExpect(okButtonLocator).toBeEnabled(); - await okButtonLocator.click(); + switch (architecture) { + case ArchitectureType.AMD64: + await playExpect(this.amd64Button).toBeEnabled(); + await this.amd64Button.click(); + break; + case ArchitectureType.ARM64: + await playExpect(this.arm64Button).toBeEnabled(); + await this.arm64Button.click(); + break; + default: + throw new Error(`Unknown architecture: ${architecture}`); } + await playExpect(this.buildButton).toBeEnabled(); + await this.buildButton.click(); + + await playExpect(this.goBackButton).toBeEnabled(); + await this.goBackButton.click(); + + await this.waitUntilCurrentBuildIsFinished(); + if ((await this.getCurrentStatusOfLatestEntry()) === 'error') return false; + + const dialogMessageLocator = this.page.getByLabel('Dialog Message'); + result = (await dialogMessageLocator.innerText()).includes('Success!'); + const okButtonLocator = this.page.getByRole('button', { name: 'OK' }); + await playExpect(okButtonLocator).toBeEnabled(); + await okButtonLocator.click(); + return result; } @@ -121,4 +136,23 @@ export class BootcPage { await this.vmdkCheckbox.uncheck(); await this.amiCheckbox.uncheck(); } + + async getCurrentStatusOfLatestEntry(): Promise { + const status = await this.getCurrentStatusOfLatestBuildImage.getAttribute('title'); + + if (status) return status; + return ''; + } + + async waitUntilCurrentBuildIsFinished(): Promise { + await waitUntil( + async () => + (await this.getCurrentStatusOfLatestEntry()).toLocaleLowerCase() === 'error' || + (await this.getCurrentStatusOfLatestEntry()).toLocaleLowerCase() === 'success', + 340000, + 2500, + true, + `Build didn't finish before timeout!`, + ); + } } From e7df9609ffc0b60646b7ff2bd72c70673edc2f93 Mon Sep 17 00:00:00 2001 From: Vladimir Lazar Date: Tue, 21 May 2024 10:51:08 +0200 Subject: [PATCH 13/13] chore(tests): address comments in review Signed-off-by: Vladimir Lazar --- tests/playwright/src/bootc-extension.spec.ts | 4 ++-- tests/playwright/src/model/bootc-page.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/playwright/src/bootc-extension.spec.ts b/tests/playwright/src/bootc-extension.spec.ts index fc3b684a..c7e1e25a 100644 --- a/tests/playwright/src/bootc-extension.spec.ts +++ b/tests/playwright/src/bootc-extension.spec.ts @@ -128,8 +128,8 @@ describe('BootC Extension', async () => { const pathToStore = path.resolve(__dirname, '..', 'tests', 'output', 'images', `${type}-${architecture}`); [page, webview] = await handleWebview(imageDetailPage); - const bootcPAge = new BootcPage(page, webview); - const result = await bootcPAge.buildDiskImage(`${imageName}:${imageTag}`, pathToStore, type, architecture); + const bootcPage = new BootcPage(page, webview); + const result = await bootcPage.buildDiskImage(`${imageName}:${imageTag}`, pathToStore, type, architecture); playExpect(result).toBeTruthy(); }, 350000, diff --git a/tests/playwright/src/model/bootc-page.ts b/tests/playwright/src/model/bootc-page.ts index e8f2ae47..e6afb66b 100644 --- a/tests/playwright/src/model/bootc-page.ts +++ b/tests/playwright/src/model/bootc-page.ts @@ -1,5 +1,5 @@ /********************************************************************** - * Copyright (C) 2023 Red Hat, Inc. + * 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.