/*********************************************************************
 * 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 'reflect-metadata' ; import { injectable , inject } from 'inversify' ; import { DriverHelper } from '../../utils/DriverHelper' ; import { CLASSES } from '../../inversify.types' ; import { Ide , LeftToolbarButton } from './Ide' ; import { TestConstants } from '../../TestConstants' ; import { By , error } from 'selenium-webdriver' ; import { Editor } from './Editor' ; import { Logger } from '../../utils/Logger' ; import { TimeoutConstants } from '../../TimeoutConstants' ; @ injectable ( ) export class ProjectTree { private static readonly PROJECT_TREE_CONTAINER_LOCATOR : By = By . css ( '#theia-left-side-panel #explorer-view-container--files .theia-TreeContainer' ) ; constructor ( @ inject ( CLASSES . DriverHelper ) private readonly driverHelper , @ inject ( CLASSES . Ide ) private readonly ide , @ inject ( CLASSES . Editor ) private readonly editor ) { } async clickCollapseAllButton ( ) { Logger . debug ( 'ProjectTree.clickCollapseAllButton' ) ; const collapseAllButtonLocator = By . id ( 'navigator.collapse.all' ) ; await this . driverHelper . waitAndClick ( collapseAllButtonLocator ) ; } async waitTreeCollapsed ( projectName , rootSubItem ) { Logger . debug ( `ProjectTree.waitTreeCollapsed project: "${ projectName }", subitem: "${ rootSubItem }"` ) ; const rootSubitemLocator = By . css ( this . getTreeItemCssLocator ( `${ projectName }/${ rootSubItem }` ) ) ; await this . driverHelper . waitDisappearanceWithTimeout ( rootSubitemLocator , TimeoutConstants . TS_SELENIUM_CLICK_ON_VISIBLE_ITEM ) ; } async collapseProjectTree ( projectName , rootSubItem ) { Logger . debug ( `ProjectTree.collapseProjectTree project: "${ projectName }", subitem: "${ rootSubItem }"` ) ; await this . clickCollapseAllButton ( ) ; await this . waitTreeCollapsed ( projectName , rootSubItem ) ; } async waitAssociatedWorkspaceProjectTreeCollapsed ( projectName , expandedRootItem ) { Logger . debug ( `ProjectTree.waitTreeCollapsed project name: "${ projectName }", expanded root item: "${ expandedRootItem }"` ) ; await this . waitItemCollapsed ( `${ projectName }/${ expandedRootItem }` ) ; } async openProjectTreeContainer ( timeout = TimeoutConstants . TS_PROJECT_TREE_TIMEOUT ) { Logger . debug ( 'ProjectTree.openProjectTreeContainer' ) ; Logger . trace ( `ProjectTree.openProjectTreeContainer waitLeftToolbarButtonPresence` ) ; await this . ide . waitLeftToolbarButton ( LeftToolbarButton . Explorer , timeout ) ; Logger . trace ( `ProjectTree.openProjectTreeContainer waitForExplorerToolbarButton` ) ; const explorerButtonActiveLocator = this . getLeftToolbarButtonActiveLocator ( LeftToolbarButton . Explorer ) ; const isButtonActive = await this . driverHelper . waitVisibilityBoolean ( explorerButtonActiveLocator ) ; Logger . debug ( `ProjectTree.openProjectTreeContainer leftToolbarButtonActive:${ isButtonActive }` ) ; if ( ! isButtonActive ) { await this . ide . waitAndClickLeftToolbarButton ( LeftToolbarButton . Explorer , timeout ) ; } await this . waitProjectTreeContainer ( ) ; } async waitItemExpanded ( itemPath , timeout = TimeoutConstants . TS_PROJECT_TREE_TIMEOUT ) { Logger . debug ( `ProjectTree.waitItemExpanded "${ itemPath }"` ) ; const locator = await this . getExpandedItemCssLocator ( itemPath ) ; const expandedItemLocator = By . css ( locator ) ; await this . driverHelper . waitVisibility ( expandedItemLocator , timeout ) ; } async waitItemCollapsed ( itemPath , timeout = TimeoutConstants . TS_PROJECT_TREE_TIMEOUT ) { Logger . debug ( `ProjectTree.waitItemCollapsed "${ itemPath }"` ) ; const locator = await this . getCollapsedItemCssLocator ( itemPath ) ; const collapsedItemLocator = By . css ( locator ) ; await this . driverHelper . waitVisibility ( collapsedItemLocator , timeout ) ; } async waitProjectTreeContainer ( timeout = TimeoutConstants . TS_PROJECT_TREE_TIMEOUT ) { Logger . debug ( 'ProjectTree.waitProjectTreeContainer' ) ; await this . driverHelper . waitPresence ( ProjectTree . PROJECT_TREE_CONTAINER_LOCATOR , timeout ) ; } async waitProjectTreeContainerClosed ( attempts = TestConstants . TS_SELENIUM_DEFAULT_ATTEMPTS , polling = TestConstants . TS_SELENIUM_DEFAULT_POLLING ) { Logger . debug ( 'ProjectTree.waitProjectTreeContainerClosed' ) ; await this . driverHelper . waitDisappearance ( ProjectTree . PROJECT_TREE_CONTAINER_LOCATOR , attempts , polling ) ; } async waitItem ( itemPath , timeout = TimeoutConstants . TS_PROJECT_TREE_TIMEOUT ) { Logger . debug ( `ProjectTree.waitItem "${ itemPath }"` ) ; const locator = await this . getItemCss ( itemPath ) ; await this . driverHelper . waitVisibility ( By . css ( locator ) , timeout ) ; } async waitItemDisappearance ( itemPath , attempts = TestConstants . TS_SELENIUM_DEFAULT_ATTEMPTS , polling = TestConstants . TS_SELENIUM_DEFAULT_POLLING ) { Logger . debug ( `ProjectTree.waitItemDisappearance "${ itemPath }"` ) ; const locator = await this . getItemCss ( itemPath ) ; await this . driverHelper . waitDisappearance ( By . css ( locator ) , attempts , polling ) ; } async clickOnItem ( itemPath , timeout = TimeoutConstants . TS_PROJECT_TREE_CLICK_ON_ITEM_TIMEOUT ) { Logger . debug ( `ProjectTree.clickOnItem "${ itemPath }"` ) ; const locator = await this . getItemCss ( itemPath ) ; await this . driverHelper . waitAndClick ( By . css ( locator ) , timeout ) ; await this . waitItemSelected ( itemPath , timeout ) ; } async waitItemSelected ( itemPath , timeout = TimeoutConstants . TS_PROJECT_TREE_TIMEOUT ) { Logger . debug ( `ProjectTree.waitItemSelected "${ itemPath }"` ) ; const selectedItemLocator = By . css ( `div[title='/projects/${ itemPath }'].theia-mod-selected.theia-mod-focus` ) ; await this . driverHelper . waitVisibility ( selectedItemLocator , timeout ) ; } async expandItem ( itemPath , timeout = TimeoutConstants . TS_PROJECT_TREE_CLICK_ON_ITEM_TIMEOUT ) { Logger . debug ( `ProjectTree.expandItem "${ itemPath }"` ) ; const locator = await this . getExpandIconCssLocator ( itemPath ) ; const expandIconLocator = By . css ( locator ) ; const treeItemLocator = By . css ( this . getTreeItemCssLocator ( itemPath ) ) ; await this . driverHelper . getDriver ( ) . wait ( async ( ) => { const classAttributeValue = await this . driverHelper . waitAndGetElementAttribute ( expandIconLocator , 'class' , timeout ) ; const isItemCollapsed = classAttributeValue . search ( 'theia-mod-collapsed' ) > 0 ; if ( isItemCollapsed ) { await this . driverHelper . waitAndClick ( treeItemLocator , timeout ) ; } try { await this . waitItemExpanded ( itemPath , TestConstants . TS_SELENIUM_DEFAULT_POLLING ) ; return true ; } catch ( err ) { if ( ! ( err instanceof error . TimeoutError ) ) { throw err ; } console . log ( `The '${ itemPath }' item has not been expanded, try again` ) ; await this . driverHelper . wait ( TestConstants . TS_SELENIUM_DEFAULT_POLLING ) ; } } , timeout ) ; } async collapseItem ( itemPath , timeout = TimeoutConstants . TS_PROJECT_TREE_CLICK_ON_ITEM_TIMEOUT ) { Logger . debug ( `ProjectTree.collapseItem "${ itemPath }"` ) ; const locator = await this . getExpandIconCssLocator ( itemPath ) ; const expandIconLocator = By . css ( locator ) ; const treeItemLocator = By . css ( this . getTreeItemCssLocator ( itemPath ) ) ; const classAttributeValue = await this . driverHelper . waitAndGetElementAttribute ( expandIconLocator , 'class' , timeout ) ; const isItemCollapsed = classAttributeValue . search ( 'theia-mod-collapsed' ) > 0 ; if ( ! isItemCollapsed ) { await this . driverHelper . waitAndClick ( treeItemLocator , timeout ) ; } await this . waitItemCollapsed ( itemPath , timeout ) ; } async expandPath ( path , timeout = TimeoutConstants . TS_EXPAND_PROJECT_TREE_ITEM_TIMEOUT ) { Logger . debug ( `ProjectTree.expandPath "${ path }"` ) ; let items = path . split ( '/' ) ; let projectName = items [ 0 ] ; let paths = new Array ( ) ; paths . push ( projectName ) ; for ( let i = 1 ; i < items . length ; i ++ ) { let item = items [ i ] ; projectName = `${ projectName }/${ item }` ; paths . push ( projectName ) ; } for ( const path of paths ) { await this . expandItem ( path , timeout ) ; } } param pathToItem param fileName param timeoutForSigleItem async expandPathAndOpenFile ( pathToItem , fileName , timeoutForSigleItem = TimeoutConstants . TS_OPEN_EDITOR_TIMEOUT ) { Logger . debug ( `ProjectTree.expandPathAndOpenFile "${ pathToItem }" filename: ${ fileName }` ) ; await this . expandPath ( pathToItem , timeoutForSigleItem ) ; await this . clickOnItem ( `${ pathToItem }/${ fileName }` , timeoutForSigleItem ) ; await this . editor . waitEditorOpened ( fileName ) ; await this . editor . waitTab ( fileName ) ; } async waitProjectImported ( projectName , rootSubItem , attempts = TestConstants . TS_SELENIUM_DEFAULT_ATTEMPTS , visibilityItemPolling = TimeoutConstants . TS_IMPORT_PROJECT_DEFAULT_POLLING , triesPolling = TestConstants . TS_SELENIUM_DEFAULT_POLLING * 30 ) { Logger . debug ( `ProjectTree.waitProjectImported "${ projectName }" rootSubItem: "${ rootSubItem }"` ) ; const rootItem = `${ projectName }` ; const rootItemLocator = By . css ( this . getTreeItemCssLocator ( `${ projectName }` ) ) ; const rootSubitemLocator = By . css ( this . getTreeItemCssLocator ( `${ projectName }/${ rootSubItem }` ) ) ; for ( let i = 0 ; i < attempts ; i ++ ) { const isProjectFolderVisible = await this . driverHelper . waitVisibilityBoolean ( rootItemLocator , 5 , visibilityItemPolling / 5 ) ; if ( ! isProjectFolderVisible ) { Logger . trace ( `ProjectTree.waitProjectImported project not located, reloading page.` ) ; await this . driverHelper . reloadPage ( ) ; await this . ide . waitAndSwitchToIdeFrame ( ) ; await this . ide . waitIde ( ) ; await this . openProjectTreeContainer ( ) ; continue ; } Logger . trace ( `ProjectTree.waitProjectImported project found, waiting for sub-items` ) ; await this . expandItem ( rootItem ) ; await this . waitItemExpanded ( rootItem ) ; const isRootSubItemVisible = await this . driverHelper . waitVisibilityBoolean ( rootSubitemLocator , 5 , visibilityItemPolling / 5 ) ; if ( ! isRootSubItemVisible ) { Logger . trace ( `ProjectTree.waitProjectImported sub-items not found, reloading page.` ) ; await this . driverHelper . reloadPage ( ) ; await this . ide . waitAndSwitchToIdeFrame ( ) ; await this . ide . waitIde ( ) ; await this . openProjectTreeContainer ( ) ; continue ; } return ; } throw new error . TimeoutError ( 'Exceeded the maximum number of checking attempts, project has not been imported' ) ; } async waitProjectImportedNoSubfolder ( projectName , attempts = TestConstants . TS_SELENIUM_DEFAULT_ATTEMPTS , visibilityItemPolling = TimeoutConstants . TS_IMPORT_PROJECT_DEFAULT_POLLING , triesPolling = TestConstants . TS_SELENIUM_DEFAULT_POLLING * 60 ) { Logger . debug ( `ProjectTree.waitProjectImportedNoSubfolder "${ projectName }"` ) ; const rootItemLocator = By . css ( this . getTreeItemCssLocator ( `${ projectName }` ) ) ; for ( let i = 0 ; i < attempts ; i ++ ) { const isProjectFolderVisible = await this . driverHelper . waitVisibilityBoolean ( rootItemLocator , 5 , visibilityItemPolling / 5 ) ; if ( ! isProjectFolderVisible ) { Logger . trace ( `ProjectTree.waitProjectImportedNoSubfolder project not located, reloading page.` ) ; await this . driverHelper . reloadPage ( ) ; await this . driverHelper . wait ( triesPolling ) ; await this . ide . waitAndSwitchToIdeFrame ( ) ; await this . ide . waitIde ( ) ; await this . openProjectTreeContainer ( ) ; continue ; } return ; } throw new error . TimeoutError ( 'Exceeded the maximum number of checking attempts, project has not been imported' ) ; } private async getWorkspacePathEntry ( ) { const nodeAttribute = 'data-node-id' ; const splitDelimeter = ':' ; const attribute = await this . driverHelper . waitAndGetElementAttribute ( By . css ( `div[${ nodeAttribute }]` ) , nodeAttribute , TimeoutConstants . TS_SELENIUM_CLICK_ON_VISIBLE_ITEM ) ; return attribute . split ( splitDelimeter ) [ 0 ] + splitDelimeter ; } private getLeftToolbarButtonActiveLocator ( buttonTitle ) { return By . xpath ( `//div[@id='theia-left-content-panel']//ul[@class='p-TabBar-content']` + `//li[@title[contains(.,'${ buttonTitle }')] and contains(@id, 'shell-tab') and contains(@class, 'p-mod-current')]` ) ; } private async getItemCss ( itemPath ) { const entry = await this . getWorkspacePathEntry ( ) ; return `div[id='${ entry }/projects/${ itemPath }']` ; } private async getCollapsedItemCssLocator ( itemPath ) { const item = await this . getExpandIconCssLocator ( itemPath ) ; return item + '.theia-mod-collapsed' ; } private async getExpandedItemCssLocator ( itemPath ) { const item = await this . getExpandIconCssLocator ( itemPath ) ; return item + ':not(.theia-mod-collapsed)' ; } private async getExpandIconCssLocator ( itemPath ) { const items = itemPath . split ( '/' ) ; const entry = items . length > 1 ? await this . getWorkspacePathEntry ( ) : '' ; return `div[data-node-id='${ entry }/projects/${ itemPath }']` ; } private getTreeItemCssLocator ( itemPath ) { return `.theia-TreeNode[title='/projects/${ itemPath }']` ; } }