diff --git a/.github/workflows/monokle-ui-tests.yml b/.github/workflows/monokle-ui-tests.yml index 34610a05a7..87251da036 100644 --- a/.github/workflows/monokle-ui-tests.yml +++ b/.github/workflows/monokle-ui-tests.yml @@ -3,7 +3,7 @@ name: monokle-ui-tests on: push: branches: - - andreiv1992/test/more-tests-around-projects + - andreiv1992/feat/work-with-git-folder workflow_dispatch: diff --git a/docs/testing.md b/docs/testing.md index d3be367313..43968fc4ba 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -42,5 +42,6 @@ await electronApp.evaluate(({ ipcMain }, params) => { ``` Examples: -- [override in electron](src/components/atoms/FileExplorer/FileExplorer.tsx) -- [override in tests](tests/models/projectsDropdown.ts) + +- [override in electron](https://github.com/kubeshop/monokle/blob/main/src/components/atoms/FileExplorer/FileExplorer.tsx) +- [override in tests](https://github.com/kubeshop/monokle/blob/main/tests/models/projectsDropdown.ts) diff --git a/playwright.config.ts b/playwright.config.ts index 87fbe23105..8de4fbada9 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -2,10 +2,11 @@ import {PlaywrightTestConfig} from '@playwright/test'; const config: PlaywrightTestConfig = { testDir: './tests', - timeout: 80000, + timeout: 200000, expect: { toMatchSnapshot: {threshold: 0.2}, }, + retries: 3 }; export default config; diff --git a/src/components/organisms/PaneManager/PaneManager.tsx b/src/components/organisms/PaneManager/PaneManager.tsx index 247ddec359..00235a40f1 100644 --- a/src/components/organisms/PaneManager/PaneManager.tsx +++ b/src/components/organisms/PaneManager/PaneManager.tsx @@ -219,6 +219,7 @@ const PaneManager = () => { placement="right" > setLeftActiveMenu('kustomize-pane')} @@ -245,6 +246,7 @@ const PaneManager = () => { placement="right" > setLeftActiveMenu('helm-pane')} diff --git a/src/components/organisms/UpdateModal/UpdateModal.tsx b/src/components/organisms/UpdateModal/UpdateModal.tsx index 2b42650c2c..b217b17be2 100644 --- a/src/components/organisms/UpdateModal/UpdateModal.tsx +++ b/src/components/organisms/UpdateModal/UpdateModal.tsx @@ -65,9 +65,11 @@ const UpdateModal = () => { ) } > - {newVersion.code === NewVersionCode.Errored ? getErrorMessage(newVersion.data?.errorCode) : null} - {newVersion.code === NewVersionCode.NotAvailable ?
New version is not available!
: null} - {newVersion.code === NewVersionCode.Downloaded ?
New version is downloaded!
: null} + + {newVersion.code === NewVersionCode.Errored ? getErrorMessage(newVersion.data?.errorCode) : null} + {newVersion.code === NewVersionCode.NotAvailable ?
New version is not available!
: null} + {newVersion.code === NewVersionCode.Downloaded ?
New version is downloaded!
: null} +
); }; diff --git a/src/navsections/KustomizePatchSectionBlueprint/KustomizePatchSectionBlueprint.ts b/src/navsections/KustomizePatchSectionBlueprint/KustomizePatchSectionBlueprint.ts index 1f7c578080..c3c29fc690 100644 --- a/src/navsections/KustomizePatchSectionBlueprint/KustomizePatchSectionBlueprint.ts +++ b/src/navsections/KustomizePatchSectionBlueprint/KustomizePatchSectionBlueprint.ts @@ -45,7 +45,7 @@ const KustomizePatchSectionBlueprint: SectionBlueprint { const patchResources = Object.values(scope.resourceMap).filter(resource => resource.name.startsWith('Patch:')); - const patcheResourcesByKind: Record = patchResources.reduce>( + const patchResourcesByKind: Record = patchResources.reduce>( (acc, resource) => { if (acc[resource.kind]) { acc[resource.kind].push(resource); @@ -56,7 +56,7 @@ const KustomizePatchSectionBlueprint: SectionBlueprint { return { id: resourceKind, diff --git a/tests/base.test.ts b/tests/base.test.ts index e2e1096f75..0c3b625927 100644 --- a/tests/base.test.ts +++ b/tests/base.test.ts @@ -1,11 +1,12 @@ import {Page} from 'playwright'; import {expect, test} from '@playwright/test'; import { + findDrawer, waitForDrawerToHide, waitForDrawerToShow, } from './antdHelpers'; import {clickOnMonokleLogo, ElectronAppInfo, startApp} from './electronHelpers'; -import {pause} from './utils'; +import {getRecordingPath, pause} from './utils'; let appWindow: Page = {} as any; let appInfo: ElectronAppInfo = {} as any; @@ -58,24 +59,28 @@ test('Validate ClusterContainer', async () => { }); test('Validate settings drawer', async () => { - const settingsTitle = appWindow.locator('.ant-drawer-open .ant-drawer-title'); - expect(await settingsTitle.isVisible()).toBe(false); + await appWindow.screenshot({path: getRecordingPath(appInfo.platform, 'before-settings-drawer.png')}); + let drawer = await findDrawer(appWindow, 'Settings'); + expect(drawer).toBeFalsy(); await appWindow.click("span[aria-label='setting']", {noWaitAfter: true, force: true}); - await pause(20000); - expect(await settingsTitle.isVisible()).toBe(true); + await appWindow.screenshot({path: getRecordingPath(appInfo.platform, 'settings-drawer.png')}); + drawer = await waitForDrawerToShow(appWindow, 'Settings', 40000); + + expect(drawer).toBeTruthy(); await clickOnMonokleLogo(appWindow); - await pause(20000); - expect(await settingsTitle.isVisible()).toBe(false); + + expect(await waitForDrawerToHide(appWindow, 'Settings')).toBeTruthy(); }); test('Validate notifications drawer', async () => { - appWindow.click("//span[@aria-label='bell' and contains(@class,'anticon')]", { + await appWindow.click("//span[@aria-label='bell' and contains(@class,'anticon')]", { noWaitAfter: true, force: true, }); + await appWindow.screenshot({path: getRecordingPath(appInfo.platform, 'notifications-drawer.png')}); expect(await waitForDrawerToShow(appWindow, 'Notifications', 5000)).toBeTruthy(); await clickOnMonokleLogo(appWindow); @@ -83,7 +88,7 @@ test('Validate notifications drawer', async () => { }); test.afterAll(async () => { - await appWindow.screenshot({path: `test-output/${appInfo.platform}/screenshots/final-screen.png`}); + await appWindow.screenshot({path: getRecordingPath(appInfo.platform, 'final-screen.png')}); await appWindow.context().close(); await appWindow.close(); }); diff --git a/tests/electronHelpers.ts b/tests/electronHelpers.ts index c9634c824b..ffdfb46542 100644 --- a/tests/electronHelpers.ts +++ b/tests/electronHelpers.ts @@ -17,6 +17,8 @@ interface StartAppResponse { appInfo: ElectronAppInfo; } +const modalsToWait = ['UpdateModal', 'WelcomeModal']; + /** * Find the latest build and start monokle app for testing */ @@ -51,10 +53,19 @@ export async function startApp(): Promise { const appWindow: Page = windows[0]; appWindow.on('console', console.log); - if (await waitForModalToShow(appWindow, 'WelcomeModal', 20000)) { - await clickOnMonokleLogo(appWindow); - await pause(500); - await waitForModalToHide(appWindow, 'WelcomeModal'); + await appWindow.screenshot({ + path: getRecordingPath(appInfo.platform, 'before-modals.png') + }); + + for (const modalName of modalsToWait) { + if (await waitForModalToShow(appWindow, modalName, 20000)) { + await clickOnMonokleLogo(appWindow); + await pause(500); + await waitForModalToHide(appWindow, modalName); + } + await appWindow.screenshot({ + path: getRecordingPath(appInfo.platform, `modal-gone-${modalName}.png`) + }); } // Capture a screenshot. diff --git a/tests/git.test.ts b/tests/git.test.ts new file mode 100644 index 0000000000..4e16d853ac --- /dev/null +++ b/tests/git.test.ts @@ -0,0 +1,124 @@ +import {expect, test} from '@playwright/test'; +import {Page} from 'playwright'; +import {ElectronApplication} from 'playwright-core'; +import {execSync} from 'child_process'; +import * as path from 'path'; +import * as fs from 'fs'; +import {ElectronAppInfo, startApp} from './electronHelpers'; +import {pause} from './utils'; +import {MainWindow} from './models/mainWindow'; +import {FileExplorerPane} from './models/fileExplorerPane'; +import {KustomizePane} from './models/kustomizePane'; +import {HelmPane} from './models/helmPane'; +import {StartProjectPane} from './models/startProjectPane'; +import {NavigatorPane} from './models/navigatorPane'; + +let appWindow: Page; +let appInfo: ElectronAppInfo; +let electronApp: ElectronApplication; + +const clonePath = path.join(__dirname, '..', '..'); +const projectPath = path.join(clonePath, 'manifest-test-data'); +const repo = 'https://github.com/kubeshop/manifest-test-data'; + +let mainWindow: MainWindow; +let fileExplorerPane: FileExplorerPane; +let kustomizePane: KustomizePane; +let helmPane: HelmPane; +let startProjectPane: StartProjectPane; +let navigatorPane: NavigatorPane; + +test.beforeAll(async () => { + const startAppResponse = await startApp(); + appWindow = startAppResponse.appWindow; + appInfo = startAppResponse.appInfo; + electronApp = startAppResponse.electronApp; + + mainWindow = new MainWindow(appWindow); + fileExplorerPane = new FileExplorerPane(appWindow); + kustomizePane = new KustomizePane(appWindow); + helmPane = new HelmPane(appWindow); + startProjectPane = new StartProjectPane(appWindow); + navigatorPane = new NavigatorPane(appWindow); + + appWindow.on('console', console.log); + + execSync(`git clone ${repo}`, { + cwd: `${clonePath}`, + }); +}); + +const startCommit = 'aeb1e59a03913b00020eca6ac2a416d085f34a6b'; +const removeSomeFiles = 'f5518240cf7cac1f686c1bc9e4ca8099bfd7daa1'; +const removeMoreFiles = '28879f29f62c8357b5ca988e475db30e13c8300b'; + +async function goToCommit(hash: string) { + execSync(`git checkout ${hash}`, { + cwd: `${projectPath}`, + }); + + await pause(5000); +} + +const testData = [ + { + hash: startCommit, + fileExplorerCount: 53, + kustomizeCount: 22, + helmCount: 4, + navigatorCount: 55, + }, + { + hash: removeSomeFiles, + fileExplorerCount: 32, + kustomizeCount: 5, + helmCount: 4, + navigatorCount: 48, + }, + { + hash: removeMoreFiles, + fileExplorerCount: 15, + kustomizeCount: 2, + helmCount: 4, + navigatorCount: 35, + }, + { + hash: startCommit, + fileExplorerCount: 53, + kustomizeCount: 22, + helmCount: 4, + navigatorCount: 55, + }, +]; + +test('all files should be loaded', async () => { + await startProjectPane.createProjectFromFolder(electronApp, projectPath); + await pause(10000); + + // eslint-disable-next-line no-restricted-syntax + for (const data of testData) { + console.log(`testing commit hash: ${data.hash}`); + await goToCommit(data.hash); + + expect(parseInt(await navigatorPane.resourcesCount.textContent(), 10)).toEqual(data.navigatorCount); + + await mainWindow.clickFileExplorer(); + expect((await fileExplorerPane.projectName.textContent())?.includes(projectPath)).toBe(true); + expect(await fileExplorerPane.fileCount.textContent()).toEqual(`${data.fileExplorerCount} files`); + + await mainWindow.clickKustomizeButton(); + const kustomizeInnerTexts = (await kustomizePane.kustomizeItemsContainer.allInnerTexts())[0].split('\n'); + expect(kustomizeInnerTexts.length).toEqual(data.kustomizeCount); + + await mainWindow.clickHelmButton(); + const helmInnerTexts = (await helmPane.helmItemsContainer.allInnerTexts())[0].split('\n'); + expect(helmInnerTexts.length).toEqual(data.helmCount); + } +}); + +test.afterAll(async () => { + fs.rmSync(projectPath, { recursive: true, force: true }); + await appWindow.screenshot({path: `test-output/${appInfo.platform}/screenshots/final-screen.png`}); + await appWindow.context().close(); + await appWindow.close(); +}); diff --git a/tests/models/helmPane.ts b/tests/models/helmPane.ts new file mode 100644 index 0000000000..131b552ad6 --- /dev/null +++ b/tests/models/helmPane.ts @@ -0,0 +1,19 @@ +import {Locator, Page} from 'playwright'; + +export class HelmPane { + + private _page: Page; + + private readonly _helmItemsContainer: Locator; + + constructor(page: Page) { + this._page = page; + + this._helmItemsContainer = page.locator('#helm-sections-container'); + } + + get helmItemsContainer() { + return this._helmItemsContainer; + } + +} diff --git a/tests/models/kustomizePane.ts b/tests/models/kustomizePane.ts new file mode 100644 index 0000000000..bac8fd4f54 --- /dev/null +++ b/tests/models/kustomizePane.ts @@ -0,0 +1,19 @@ +import {Locator, Page} from 'playwright'; + +export class KustomizePane { + + private _page: Page; + + private readonly _kustomizeItemsContainer: Locator; + + constructor(page: Page) { + this._page = page; + + this._kustomizeItemsContainer = page.locator('#kustomize-sections-container'); + } + + get kustomizeItemsContainer() { + return this._kustomizeItemsContainer; + } + +} diff --git a/tests/models/mainWindow.ts b/tests/models/mainWindow.ts index 5c412b7d30..3bd5e17103 100644 --- a/tests/models/mainWindow.ts +++ b/tests/models/mainWindow.ts @@ -8,6 +8,8 @@ export class MainWindow { private readonly _projectsDropdown: Locator; private readonly _backToProject: Locator; private readonly _fileExplorerButton: Locator; + private readonly _kustomizeButton: Locator; + private readonly _helmButton: Locator; constructor(page: Page) { this._page = page; @@ -17,6 +19,8 @@ export class MainWindow { this._backToProject = page.locator('#projects-dropdown-container > button:last-child'); this._fileExplorerButton = page.locator('#file-explorer'); + this._kustomizeButton = page.locator('#kustomize-pane'); + this._helmButton = page.locator('#helm-pane'); } async clickLogo() { @@ -35,4 +39,12 @@ export class MainWindow { await this._fileExplorerButton.click(); } + async clickKustomizeButton() { + await this._kustomizeButton.click(); + } + + async clickHelmButton() { + await this._helmButton.click(); + } + } diff --git a/tests/models/navigatorPane.ts b/tests/models/navigatorPane.ts index 8d40af4ed8..a1f45c2c1f 100644 --- a/tests/models/navigatorPane.ts +++ b/tests/models/navigatorPane.ts @@ -5,15 +5,23 @@ export class NavigatorPane { private _page: Page; private readonly _createResourceButton: Locator; + private readonly _sectionsContainer: Locator; + private readonly _resourcesCount: Locator; constructor(page: Page) { this._page = page; this._createResourceButton = page.locator('#create-resource-button'); + this._sectionsContainer = page.locator('#navigator-sections-container'); + this._resourcesCount = page.locator('#navigator-sections-container li:first-child span > span:nth-child(2)'); } async clickOnNewResource() { await this._createResourceButton.click(); } + get resourcesCount() { + return this._resourcesCount; + } + } diff --git a/tests/models/projectsDropdown.ts b/tests/models/projectsDropdown.ts index d3805bdd73..034bc804da 100644 --- a/tests/models/projectsDropdown.ts +++ b/tests/models/projectsDropdown.ts @@ -2,6 +2,7 @@ import {Locator, Page} from 'playwright'; import {v4 as uuidV4} from 'uuid'; import {ElectronApplication} from 'playwright-core'; import {getChannelName} from '../../src/utils/ipc'; +import {mockHandle} from './util'; export class ProjectsDropdown { @@ -21,13 +22,8 @@ export class ProjectsDropdown { async createNewProject(electronApp: ElectronApplication, name = `project-${uuidV4()}`) { await this._projectsDropdown.click(); - const chanel = getChannelName('select-file', true); - await electronApp.evaluate(({ ipcMain }, params) => { - ipcMain.handle(params.chanel, () => { - return [params.name]; - }); - }, { chanel, name }); + await mockHandle(electronApp, chanel, name); await this._openProjectFromFolder.click(); diff --git a/tests/models/startProjectPane.ts b/tests/models/startProjectPane.ts index 070eca3f02..9b846f7237 100644 --- a/tests/models/startProjectPane.ts +++ b/tests/models/startProjectPane.ts @@ -1,5 +1,8 @@ import {Locator, Page} from 'playwright'; import {v4 as uuidV4} from 'uuid'; +import {ElectronApplication} from 'playwright-core'; +import {getChannelName} from '../../src/utils/ipc'; +import {mockHandle} from './util'; export class StartProjectPane { @@ -34,4 +37,15 @@ export class StartProjectPane { return name; } + async createProjectFromFolder(electronApp: ElectronApplication, path: string) { + const chanel = getChannelName('select-file', true); + await mockHandle(electronApp, chanel, path); + + await this._selectExisingFolderLink.click(); + + await electronApp.evaluate(({ ipcMain }, params) => { + ipcMain.removeAllListeners(params.chanel); + }, { chanel }); + } + } diff --git a/tests/models/util.ts b/tests/models/util.ts new file mode 100644 index 0000000000..03f53ddedd --- /dev/null +++ b/tests/models/util.ts @@ -0,0 +1,9 @@ +import {ElectronApplication} from 'playwright-core'; + +export async function mockHandle(electronApp: ElectronApplication, channel: string, name: string) { + await electronApp.evaluate(({ ipcMain }, params) => { + ipcMain.handle(params.channel, () => { + return [params.name]; + }); + }, { channel, name }); +} diff --git a/tests/project.test.ts b/tests/project.test.ts index b5dad7bf25..0756699a10 100644 --- a/tests/project.test.ts +++ b/tests/project.test.ts @@ -1,7 +1,7 @@ import {Page} from 'playwright'; import {expect, test} from '@playwright/test'; import {ElectronApplication} from 'playwright-core'; -import {startApp} from './electronHelpers'; +import {ElectronAppInfo, startApp} from './electronHelpers'; import {pause} from './utils'; import {StartProjectPane} from './models/startProjectPane'; import {MainWindow} from './models/mainWindow'; @@ -14,6 +14,8 @@ import {FileExplorerPane} from './models/fileExplorerPane'; let appWindow: Page; let electronApp: ElectronApplication; +let appInfo: ElectronAppInfo; + let startPane: StartProjectPane; let mainWindow: MainWindow; let projectsDropdown: ProjectsDropdown; @@ -27,6 +29,7 @@ test.beforeAll(async () => { const startAppResponse = await startApp(); appWindow = startAppResponse.appWindow; electronApp = startAppResponse.electronApp; + appInfo = startAppResponse.appInfo; startPane = new StartProjectPane(appWindow); mainWindow = new MainWindow(appWindow); projectsDropdown = new ProjectsDropdown(appWindow); @@ -81,3 +84,9 @@ test('should create new resource', async () => { expect(await fileExplorerPane.fileCount.textContent()).toEqual('1 files'); expect((await fileExplorerPane.tree.textContent())?.includes(fileName)).toBe(true); }); + +test.afterAll(async () => { + await appWindow.screenshot({path: `test-output/${appInfo.platform}/screenshots/final-screen.png`}); + await appWindow.context().close(); + await appWindow.close(); +});