diff --git a/tests/e2e/index.ts b/tests/e2e/index.ts index 6c15e1b9669..8c790ccaf56 100644 --- a/tests/e2e/index.ts +++ b/tests/e2e/index.ts @@ -15,9 +15,9 @@ export * from './utils/workspace/TestWorkspaceUtil'; export * from './utils/workspace/WorkspaceStatus'; export * from './pageobjects/dashboard/Dashboard'; export * from './pageobjects/dashboard/NewWorkspace'; -export * from './pageobjects/dashboard/workspace-details/WorkspaceDetailsPlugins'; -export * from './pageobjects/dashboard/workspace-details/WorkspaceDetails'; export * from './pageobjects/dashboard/Workspaces'; +export * from './pageobjects/dashboard/workspace-details/WorkspaceDetails'; +export * from './pageobjects/dashboard/workspace-details/WorkspaceDetailsPlugins'; export * from './pageobjects/ide/ContextMenu'; export * from './pageobjects/ide/DebugView'; export * from './pageobjects/ide/DialogWindow'; diff --git a/tests/e2e/mocha-python-django.opts b/tests/e2e/mocha-python-django.opts new file mode 100644 index 00000000000..cbc1584de96 --- /dev/null +++ b/tests/e2e/mocha-python-django.opts @@ -0,0 +1,8 @@ +--timeout 2200000 +--reporter 'dist/driver/CheReporter.js' +-u tdd +--bail +--full-trace +--spec dist/tests/login/Login.spec.js +--spec dist/tests/devfiles/PythonDjango.spec.js +--require source-map-support/register diff --git a/tests/e2e/package.json b/tests/e2e/package.json index b2ae4bb8451..30ec4f63d88 100644 --- a/tests/e2e/package.json +++ b/tests/e2e/package.json @@ -14,6 +14,7 @@ "test-operatorhub-installation": "./generateIndex.sh && npm run lint && npm run tsc && mocha --opts mocha-che-operatorhub.opts", "test-wkspc-creation-and-ls": "./generateIndex.sh && npm run lint && npm run tsc && mocha --opts mocha-wkspc-creation-and-ls.opts", "test-java-vertx": "./generateIndex.sh && npm run lint && npm run tsc && mocha --opts mocha-java-vertx.opts", + "test-python-django": "./generateIndex.sh && npm run lint && npm run tsc && mocha --opts mocha-python-django.opts", "test-all-devfiles": "./generateIndex.sh && npm run lint && npm run tsc && mocha --opts mocha-all-devfiles.opts", "lint": "tslint --fix -p .", "tsc": "tsc -p ." diff --git a/tests/e2e/pageobjects/ide/DialogWindow.ts b/tests/e2e/pageobjects/ide/DialogWindow.ts index 789b97a7b39..55abc5a1785 100644 --- a/tests/e2e/pageobjects/ide/DialogWindow.ts +++ b/tests/e2e/pageobjects/ide/DialogWindow.ts @@ -75,8 +75,9 @@ export class DialogWindow { Logger.debug('DialogWindow.waitDialogAndOpenLink'); await this.waitDialog(timeout, dialogText); - await this.ide.waitApllicationIsReady(await this.getApplicationUrlFromDialog(dialogText), timeout); + const applicationUrlFromDialog: string = await this.getApplicationUrlFromDialog(dialogText); await this.clickToOpenLinkButton(); + await this.ide.waitApllicationIsReady(applicationUrlFromDialog, timeout); await this.waitDialogDissappearance(); } diff --git a/tests/e2e/pageobjects/ide/Ide.ts b/tests/e2e/pageobjects/ide/Ide.ts index 0838dcd295d..72ca40200a2 100644 --- a/tests/e2e/pageobjects/ide/Ide.ts +++ b/tests/e2e/pageobjects/ide/Ide.ts @@ -12,7 +12,7 @@ import { DriverHelper } from '../../utils/DriverHelper'; import { injectable, inject } from 'inversify'; import { CLASSES } from '../../inversify.types'; import { TestConstants } from '../../TestConstants'; -import { By, WebElement, error } from 'selenium-webdriver'; +import { By, error } from 'selenium-webdriver'; import { Logger } from '../../utils/Logger'; export enum RightToolbarButton { @@ -112,10 +112,8 @@ export class Ide { await this.driverHelper.waitAndClick(By.xpath(yesButtonLocator)); } - async waitWorkspaceAndIde(workspaceNamespace: string, - workspaceName: string, - timeout: number = TestConstants.TS_SELENIUM_LOAD_PAGE_TIMEOUT) { - + async waitWorkspaceAndIde(workspaceNamespace: string, workspaceName: string, timeout: number = TestConstants.TS_SELENIUM_LOAD_PAGE_TIMEOUT) { + // why are there unused arguments in this method? Logger.debug('Ide.waitWorkspaceAndIde'); await this.waitAndSwitchToIdeFrame(timeout); @@ -218,32 +216,21 @@ export class Ide { await this.waitStatusBarContains(expectedTextInStatusBar, 20000); } - async closeAllNotifications() { - const notificationLocator: By = By.css('.theia-Notification'); - + async closeAllNotifications(timeout: number = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { + const statusBarNotificationsLocator: By = By.xpath(`//div[@id="theia-statusBar"]/div[@class="area right"]//div[contains(@title, 'Notification')]`); + const notificationCenterActionCloseAllButtonLocator: By = By.xpath(`//div[contains(@class, 'theia-notifications-container theia-notification-center open')]//ul[@class="theia-notification-actions"]/li[@class="clear-all"]`); + const notificationCenterClosedLocator: By = By.xpath(`//div[contains(@class, 'theia-notifications-container theia-notification-center closed')]`); + const notificationCenterContainerNotificationsLocator: By = By.xpath(`//div[contains(@class, 'theia-notifications-container theia-notification-center')]//div[@class='theia-notification-list']//*`); Logger.debug('Ide.closeAllNotifications'); - if (! await this.driverHelper.isVisible(notificationLocator)) { - return; - } - - const notifications: WebElement[] = await this.driverHelper.waitAllPresence(notificationLocator); - const notificationsCapacity: number = notifications.length; - - for (let i = 1; i <= notificationsCapacity; i++) { - const notificationLocator: By = By.xpath('//div[@class=\'theia-Notification\']//button[text()=\'Close\']'); - - try { - await this.driverHelper.waitAndClick(notificationLocator); - } catch (err) { - if (err instanceof error.TimeoutError) { - console.log(`The '${notificationLocator}' element is not visible and can't be clicked`); - continue; - } - - throw err; - } - } + Logger.trace(`Ide.closeAllNotifications driverHelper click Notification Center icon.`); + await this.driverHelper.waitAndClick(statusBarNotificationsLocator, 5_000); + Logger.trace(`Ide.closeAllNotifications driverHelper Notifications Center click closeAllNotifications.`); + await this.driverHelper.waitAndClick(notificationCenterActionCloseAllButtonLocator, 5_000); + Logger.trace(`Ide.closeAllNotifications driverHelper waitng for Notification Center to close.`); + await this.driverHelper.waitPresence(notificationCenterClosedLocator, 5_000); + Logger.trace(`Ide.closeAllNotifications driverHelper test if any notifications remain in Notification Center.`); + await this.driverHelper.waitDisappearance(notificationCenterContainerNotificationsLocator, 5_000); } async performKeyCombination(keyCombination: string) { diff --git a/tests/e2e/pageobjects/ide/TopMenu.ts b/tests/e2e/pageobjects/ide/TopMenu.ts index 85be726ca4b..25541469685 100644 --- a/tests/e2e/pageobjects/ide/TopMenu.ts +++ b/tests/e2e/pageobjects/ide/TopMenu.ts @@ -3,7 +3,6 @@ import { CLASSES } from '../../inversify.types'; import { DriverHelper } from '../../utils/DriverHelper'; import { TestConstants } from '../../TestConstants'; import { By, error } from 'selenium-webdriver'; -import { Ide } from './Ide'; import { Logger } from '../../utils/Logger'; import { QuickOpenContainer } from './QuickOpenContainer'; @@ -22,8 +21,7 @@ export class TopMenu { private static readonly TOP_MENU_BUTTONS: string[] = ['File', 'Edit', 'Selection', 'View', 'Go', 'Debug', 'Terminal', 'Help']; constructor(@inject(CLASSES.DriverHelper) private readonly driverHelper: DriverHelper, - @inject(CLASSES.Ide) private readonly ide: Ide, - @inject(CLASSES.QuickOpenContainer) private readonly quickOpenContainer: QuickOpenContainer) { } + @inject(CLASSES.QuickOpenContainer) private readonly quickOpenContainer: QuickOpenContainer) { } public async waitTopMenu(timeout: number = TestConstants.TS_SELENIUM_DEFAULT_TIMEOUT) { Logger.debug('TopMenu.waitTopMenu'); @@ -45,8 +43,6 @@ export class TopMenu { Logger.debug(`TopMenu.clickOnTopMenuButton "${buttonText}"`); const buttonLocator: By = this.getTopMenuButtonLocator(buttonText); - - await this.ide.closeAllNotifications(); await this.driverHelper.waitAndClick(buttonLocator, timeout); } diff --git a/tests/e2e/tests/devfiles/JavaMaven.spec.ts b/tests/e2e/tests/devfiles/JavaMaven.spec.ts index d6979cb425d..a5adcafd1a3 100644 --- a/tests/e2e/tests/devfiles/JavaMaven.spec.ts +++ b/tests/e2e/tests/devfiles/JavaMaven.spec.ts @@ -22,20 +22,20 @@ const codeNavigationClassName: string = 'String.class'; const stack : string = 'Java Maven'; const taskName: string = 'maven build'; -suite('Java Maven test', async () => { +suite(`${stack} test`, async () => { suite (`Create ${stack} workspace ${workspaceName}`, async () => { workspaceHandling.createAndOpenWorkspace(workspaceName, stack); projectAndFileTests.waitWorkspaceReadiness(workspaceName, sampleName, 'src'); }); suite('Validation of workspace build and run', async () => { - codeExecutionTests.runTask(taskName, 120000); + codeExecutionTests.runTask(taskName, 120_000); codeExecutionTests.closeTerminal(taskName); }); suite('Language server validation', async () => { projectAndFileTests.openFile(fileFolderPath, tabTitle); - commonLsTests.waitLSInitialization('Starting Java Language Server', 1800000, 360000); + commonLsTests.waitLSInitialization('Starting Java Language Server', 1_800_000, 360_000); commonLsTests.suggestionInvoking(tabTitle, 10, 20, 'append(char c) : PrintStream'); commonLsTests.errorHighlighting(tabTitle, 'error', 11); commonLsTests.autocomplete(tabTitle, 10, 11, 'System - java.lang'); diff --git a/tests/e2e/tests/devfiles/JavaVertx.spec.ts b/tests/e2e/tests/devfiles/JavaVertx.spec.ts index 5dfc19d1f94..35d806e05cd 100644 --- a/tests/e2e/tests/devfiles/JavaVertx.spec.ts +++ b/tests/e2e/tests/devfiles/JavaVertx.spec.ts @@ -23,7 +23,7 @@ const buildTaskName: string = 'maven build'; const LSstarting: string = 'Starting Java Language Server'; const stack: string = 'Java Vert.x'; -suite('Java Vert.x test', async () => { +suite(`${stack} test`, async () => { suite (`Create ${stack} workspace ${workspaceName}`, async () => { workspaceHandling.createAndOpenWorkspace(workspaceName, stack); @@ -32,7 +32,7 @@ suite('Java Vert.x test', async () => { suite('Language server validation', async () => { projectAndFileTests.openFile(fileFolderPath, tabTitle); - commonLsTests.waitLSInitialization(LSstarting, 1800000, 360000); + commonLsTests.waitLSInitialization(LSstarting, 1_800_000, 360_000); commonLsTests.suggestionInvoking(tabTitle, 19, 31, 'router(Vertx vertx) : Router'); commonLsTests.errorHighlighting(tabTitle, 'error', 20); commonLsTests.autocomplete(tabTitle, 19, 7, 'Router - io.vertx.ext.web'); @@ -40,7 +40,7 @@ suite('Java Vert.x test', async () => { }); suite('Validation of project build', async () => { - codeExecutionTests.runTask(buildTaskName, 120000); + codeExecutionTests.runTask(buildTaskName, 120_000); codeExecutionTests.closeTerminal(buildTaskName); }); diff --git a/tests/e2e/tests/devfiles/PythonDjango.spec.ts b/tests/e2e/tests/devfiles/PythonDjango.spec.ts new file mode 100644 index 00000000000..93235ddc8c0 --- /dev/null +++ b/tests/e2e/tests/devfiles/PythonDjango.spec.ts @@ -0,0 +1,52 @@ +/********************************************************************* + * Copyright (c) 2019 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + **********************************************************************/ +import { NameGenerator } from '../../utils/NameGenerator'; +import 'reflect-metadata'; +import * as codeExecutionHelper from '../../testsLibrary/CodeExecutionTests'; +import * as workspaceHandler from '../../testsLibrary/WorksapceHandlingTests'; +import * as projectManager from '../../testsLibrary/ProjectAndFileTests'; + +const workspaceName: string = NameGenerator.generate('wksp-test-', 5); +const workspaceStack: string = 'Python Django'; +const workspaceSampleName: string = 'django-realworld-example-app'; +const workspaceRootFolderName: string = 'conduit'; + +const taskInstallDependencies: string = 'install dependencies'; +const taskMigrate: string = 'migrate'; +const taskRunServer: string = 'run server'; +const taskExpectedDialogText: string = 'A process is now listening on port 7000'; + +suite(`${workspaceStack} test`, async () => { + + suite(`Create ${workspaceStack} workspace ${workspaceName}`, async () => { + workspaceHandler.createAndOpenWorkspace(workspaceName, workspaceStack); + projectManager.waitWorkspaceReadiness(workspaceName, workspaceSampleName, workspaceRootFolderName); + }); + + suite('Install dependencies', async () => { + codeExecutionHelper.runTask(taskInstallDependencies, 60_000); + codeExecutionHelper.closeTerminal(taskInstallDependencies); + }); + + suite('Migrate Django application project', async () => { + codeExecutionHelper.runTask(taskMigrate, 30_000); + codeExecutionHelper.closeTerminal(taskMigrate); + }); + + suite('Run django server', async () => { + codeExecutionHelper.runTaskWithDialogShellAndOpenLink(taskRunServer, taskExpectedDialogText, 30_000); + }); + + suite('Stop and remove workspace', async() => { + workspaceHandler.stopWorkspace(workspaceName); + workspaceHandler.removeWorkspace(workspaceName); + }); + +}); diff --git a/tests/e2e/testsLibrary/CodeExecutionTests.ts b/tests/e2e/testsLibrary/CodeExecutionTests.ts index 149ae90d667..8ee86ed8df7 100644 --- a/tests/e2e/testsLibrary/CodeExecutionTests.ts +++ b/tests/e2e/testsLibrary/CodeExecutionTests.ts @@ -8,22 +8,42 @@ * SPDX-License-Identifier: EPL-2.0 **********************************************************************/ -import { CLASSES, Terminal, TopMenu, Ide } from '..'; +import { CLASSES, Terminal, TopMenu, Ide, DialogWindow } from '..'; import { e2eContainer } from '../inversify.config'; const terminal: Terminal = e2eContainer.get(CLASSES.Terminal); const topMenu: TopMenu = e2eContainer.get(CLASSES.TopMenu); const ide: Ide = e2eContainer.get(CLASSES.Ide); +const dialogWindow: DialogWindow = e2eContainer.get(CLASSES.DialogWindow); + +const dialogWindowCloseButtonText: string = 'close'; export function runTask(taskName: string, timeout: number) { - test( `Run command '${taskName}'`, async () => { + test(`Run command '${taskName}'`, async () => { await topMenu.runTask(taskName); await ide.waitNotification('has exited with code 0.', timeout); }); } +export function runTaskWithDialogShellAndOpenLink(taskName: string, expectedDialogText: string, timeout: number) { + test(`Run command '${taskName}' expecting dialog shell`, async () => { + await topMenu.runTask(taskName); + await dialogWindow.waitDialogAndOpenLink(timeout, expectedDialogText); + }); +} + +export function runTaskWithDialogShellAndClose(taskName: string, expectedDialogText: string, timeout: number) { + test(`Run command '${taskName}' expecting dialog shell`, async () => { + await topMenu.runTask(taskName); + await dialogWindow.waitDialog(timeout, expectedDialogText); + await dialogWindow.clickToButton(dialogWindowCloseButtonText); + await dialogWindow.waitDialogDissappearance(); + }); +} + export function closeTerminal(taskName: string) { test('Close the terminal tasks', async () => { + await ide.closeAllNotifications(); await terminal.closeTerminalTab(taskName); }); } diff --git a/tests/e2e/utils/DriverHelper.ts b/tests/e2e/utils/DriverHelper.ts index 81a10a47ff8..1a26870c877 100644 --- a/tests/e2e/utils/DriverHelper.ts +++ b/tests/e2e/utils/DriverHelper.ts @@ -20,6 +20,7 @@ import { Logger } from './Logger'; @injectable() export class DriverHelper { private readonly driver: ThenableWebDriver; + private readonly BY_XPATH_LOCATOR_START_INDEX: number = 10; constructor(@inject(TYPES.Driver) driver: IDriver) { this.driver = driver.get(); @@ -521,6 +522,32 @@ export class DriverHelper { throw new error.TimeoutError(`Exceeded maximum mouse move attempts, for the '${elementLocator}' element`); } + public async hasChildren(elementLocator: By): Promise { + const attempts: number = TestConstants.TS_SELENIUM_DEFAULT_ATTEMPTS; + const polling: number = TestConstants.TS_SELENIUM_DEFAULT_POLLING; + Logger.trace(`DriverHelper.hasChildren ${elementLocator}`); + + for (let i = 0; i < attempts; i++) { + try { + const childrenLocatorString: string = elementLocator.toString().substr(this.BY_XPATH_LOCATOR_START_INDEX, elementLocator.toString().length - this.BY_XPATH_LOCATOR_START_INDEX - 1) + `//*`; + const childrenLocator: By = By.xpath(childrenLocatorString); + const children: Array = await this.driver.findElements(childrenLocator); + Logger.trace(`DriverHelper.hasChildren child elements locator '${childrenLocator}', child elements:${children.length}`); + if (children.length > 0) { return true; } + return false; + } catch (err) { + if (err instanceof error.StaleElementReferenceError) { + await this.wait(polling); + continue; + } + + throw err; + } + } + + throw new error.TimeoutError(`Exceeded maximum attempts to get children for '${elementLocator}'.`); + } + getDriver(): ThenableWebDriver { Logger.trace('DriverHelper.getDriver');