/*********************************************************************
 * 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 { TestConstants } from '../../TestConstants' ; import { By , Key , error , ActionSequence , Button } from 'selenium-webdriver' ; import { Logger } from '../../utils/Logger' ; import { TimeoutConstants } from '../../TimeoutConstants' ; @ injectable ( ) export class Editor { private static readonly SUGGESTION_WIDGET_BODY_CSS : string = 'div.visible[widgetId=\'editor.widget.suggestWidget\']' ; private static readonly SUGGESTION_LOADING_XPATH : string = '//div[@widgetid=\'editor.widget.suggestWidget\']//div[@class=\'message\' and contains(.,\'Loading...\')]' ; private static readonly ADDITIONAL_SHIFTING_TO_Y : number = 19 ; private static readonly ADDITIONAL_SHIFTING_TO_X : number = 1 ; constructor ( @ inject ( CLASSES . DriverHelper ) private readonly driverHelper ) { } public async deleteAllText ( editorTab ) { Logger . debug ( 'Editor.deleteAllText' ) ; await this . selectTab ( editorTab ) ; await this . type ( editorTab , Key . chord ( Key . CONTROL , 'a' , Key . DELETE ) , 1 ) ; } public async waitSuggestionContainer ( timeout = TimeoutConstants . TS_SUGGESTION_TIMEOUT ) { Logger . debug ( 'Editor.waitSuggestionContainer' ) ; await this . driverHelper . waitVisibility ( By . css ( Editor . SUGGESTION_WIDGET_BODY_CSS ) , timeout ) ; } public async waitSuggestionContainerClosed ( timeout = TimeoutConstants . TS_SUGGESTION_TIMEOUT ) { Logger . debug ( 'Editor.waitSuggestionContainerClosed' ) ; await this . driverHelper . waitDisappearanceWithTimeout ( By . css ( Editor . SUGGESTION_WIDGET_BODY_CSS ) , timeout ) ; } public async waitSuggestion ( editorTabTitle , suggestionText , timeout = TimeoutConstants . TS_SUGGESTION_TIMEOUT , lineNumber ? , charNumber ? ) { const charInLineNumber = ( charNumber ? charNumber : 1 ) ; if ( lineNumber ) { await this . waitSuggestionWithResettingCursor ( editorTabTitle , suggestionText , timeout , lineNumber , charInLineNumber ) ; return ; } await this . waitSuggestionWithoutResettingCursor ( editorTabTitle , suggestionText , timeout ) ; } public async closeSuggestionContainer ( editorTabTitle , timeout = TimeoutConstants . TS_SUGGESTION_TIMEOUT ) { Logger . debug ( `Editor.closeSuggestionContainer tabTitle: "${ editorTabTitle }"` ) ; await this . driverHelper . getDriver ( ) . wait ( async ( ) => { try { const suggestionContainerTimeout = timeout / 2 ; await this . waitSuggestionContainer ( suggestionContainerTimeout ) ; } catch ( err ) { if ( err instanceof error . TimeoutError ) { return true ; } throw err ; } try { await this . pressEscapeButton ( editorTabTitle ) ; await this . waitSuggestionContainerClosed ( 2000 ) ; return true ; } catch ( err ) { if ( ! ( err instanceof error . TimeoutError ) ) { throw err ; } } } , timeout ) ; } public async waitSuggestionWithScrolling ( editorTabTitle , suggestionText , timeout = TimeoutConstants . TS_SUGGESTION_TIMEOUT ) { Logger . debug ( `Editor.waitSuggestion tabTitle: "${ editorTabTitle }" suggestion: "${ suggestionText }"` ) ; const suggestionLocator = this . getSuggestionLineXpathLocator ( suggestionText ) ; await this . driverHelper . getDriver ( ) . wait ( async ( ) => { try { await this . scrollAndSearchSuggestion ( editorTabTitle , suggestionLocator , timeout / 3 ) ; return true ; } catch ( err ) { if ( ! ( err instanceof error . TimeoutError ) ) { throw err ; } await this . closeSuggestionContainer ( editorTabTitle , TimeoutConstants . TS_CLOSE_SUGGESTION_CONTAINER_TIMEOUT ) ; await this . pressControlSpaceCombination ( editorTabTitle ) ; } } , timeout ) ; } public async pressControlSpaceCombination ( editorTabTitle ) { Logger . debug ( `Editor.pressControlSpaceCombination "${ editorTabTitle }"` ) ; await this . performKeyCombination ( editorTabTitle , Key . chord ( Key . CONTROL , Key . SPACE ) ) ; } public async pressEscapeButton ( editorTabTitle ) { Logger . debug ( `Editor.pressEscapeButton "${ editorTabTitle }"` ) ; await this . performKeyCombination ( editorTabTitle , Key . ESCAPE ) ; } public async clickOnSuggestion ( suggestionText , timeout = TimeoutConstants . TS_SUGGESTION_TIMEOUT ) { Logger . debug ( `Editor.clickOnSuggestion "${ suggestionText }"` ) ; await this . driverHelper . waitAndClick ( this . getSuggestionLineXpathLocator ( suggestionText ) , timeout ) ; } public async waitTab ( tabTitle , timeout = TimeoutConstants . TS_EDITOR_TAB_INTERACTION_TIMEOUT ) { Logger . debug ( `Editor.waitTab "${ tabTitle }"` ) ; await this . driverHelper . waitVisibility ( By . xpath ( this . getTabXpathLocator ( tabTitle ) ) , timeout ) ; } public async waitTabDisappearance ( tabTitle , attempt = TestConstants . TS_SELENIUM_DEFAULT_ATTEMPTS , polling = TestConstants . TS_SELENIUM_DEFAULT_POLLING ) { Logger . debug ( `Editor.waitTabDisappearance "${ tabTitle }"` ) ; await this . driverHelper . waitDisappearance ( By . xpath ( this . getTabXpathLocator ( tabTitle ) ) , attempt , polling ) ; } public async clickOnTab ( tabTitle , timeout = TimeoutConstants . TS_EDITOR_TAB_INTERACTION_TIMEOUT ) { Logger . debug ( `Editor.clickOnTab "${ tabTitle }"` ) ; await this . driverHelper . waitAndClick ( By . xpath ( this . getTabXpathLocator ( tabTitle ) ) , timeout ) ; } public async waitTabFocused ( tabTitle , timeout = TimeoutConstants . TS_EDITOR_TAB_INTERACTION_TIMEOUT ) { Logger . debug ( `Editor.waitTabFocused "${ tabTitle }"` ) ; const focusedTabLocator = By . xpath ( `//li[contains(@class, 'p-TabBar-tab') and contains(@class, 'theia-mod-active')]//div[text()='${ tabTitle }']` ) ; await this . driverHelper . waitVisibility ( focusedTabLocator , timeout ) ; } public async selectTab ( tabTitle ) { Logger . debug ( `Editor.selectTab "${ tabTitle }"` ) ; await this . waitTab ( tabTitle ) ; await this . clickOnTab ( tabTitle ) ; await this . waitTabFocused ( tabTitle ) ; } async closeTab ( tabTitle , timeout = TimeoutConstants . TS_EDITOR_TAB_INTERACTION_TIMEOUT ) { Logger . debug ( `Editor.closeTab "${ tabTitle }"` ) ; const tabCloseButtonLocator = By . xpath ( `//div[text()='${ tabTitle }']/parent::li//div[contains(@class, 'p-TabBar-tabCloseIcon')]` ) ; await this . driverHelper . waitAndClick ( tabCloseButtonLocator , timeout ) ; } async waitTabWithUnsavedStatus ( tabTitle , timeout = TimeoutConstants . TS_EDITOR_TAB_INTERACTION_TIMEOUT ) { Logger . debug ( `Editor.waitTabWithUnsavedStatus "${ tabTitle }"` ) ; const unsavedTabLocator = this . getTabWithUnsavedStatus ( tabTitle ) ; await this . driverHelper . waitVisibility ( unsavedTabLocator , timeout ) ; } async waitTabWithSavedStatus ( tabTitle , timeout = TimeoutConstants . TS_EDITOR_TAB_INTERACTION_TIMEOUT ) { Logger . debug ( `Editor.waitTabWithSavedStatus "${ tabTitle }"` ) ; const unsavedTabLocator = this . getTabWithUnsavedStatus ( tabTitle ) ; await this . driverHelper . getDriver ( ) . wait ( async ( ) => { try { await this . driverHelper . waitDisappearanceWithTimeout ( unsavedTabLocator , TestConstants . TS_SELENIUM_DEFAULT_POLLING ) ; await this . waitTab ( tabTitle , timeout ) ; return true ; } catch ( err ) { if ( ! ( err instanceof error . TimeoutError ) ) { throw err ; } console . log ( `The editor tab with title "${ tabTitle }" has unsaved status, wait once again` ) ; } } , timeout ) ; } async waitEditorOpened ( editorTabTitle , timeout = TimeoutConstants . TS_EDITOR_TAB_INTERACTION_TIMEOUT ) { Logger . debug ( `Editor.waitEditorOpened "${ editorTabTitle }"` ) ; const firstEditorLineLocator = By . xpath ( this . getEditorLineXpathLocator ( 1 ) ) ; await this . driverHelper . waitPresence ( this . getEditorBodyLocator ( editorTabTitle ) , timeout ) ; await this . driverHelper . waitPresence ( firstEditorLineLocator , timeout ) ; } async waitEditorAvailable ( tabTitle , timeout = TimeoutConstants . TS_EDITOR_TAB_INTERACTION_TIMEOUT ) { Logger . debug ( `Editor.waitEditorAvailable "${ tabTitle }"` ) ; await this . waitTab ( tabTitle , timeout ) ; await this . waitEditorOpened ( tabTitle , timeout ) ; } async getLineText ( tabTitle , lineNumber ) { Logger . debug ( `Editor.getLineText "${ tabTitle }"` ) ; const lineIndex = lineNumber - 1 ; const editorText = await this . getEditorVisibleText ( tabTitle ) ; const editorLines = editorText . split ( '\n' ) ; const editorLine = editorLines [ lineIndex ] + '\n' ; return editorLine ; } async getEditorVisibleText ( tabTitle ) { Logger . debug ( `Editor.getEditorVisibleText "${ tabTitle }"` ) ; const editorBodyLocator = By . xpath ( `//div[contains(@data-uri, \'${ tabTitle }')]//div[contains(@class,\'view-lines\')]` ) ; const editorText = await this . driverHelper . waitAndGetText ( editorBodyLocator ) ; return editorText ; } async waitText ( tabTitle , expectedText , timeout = TimeoutConstants . TS_EDITOR_TAB_INTERACTION_TIMEOUT , polling = TestConstants . TS_SELENIUM_DEFAULT_POLLING ) { Logger . debug ( `Editor.waitText "${ tabTitle }"` ) ; await this . driverHelper . getDriver ( ) . wait ( async ( ) => { const editorText = await this . getEditorVisibleText ( tabTitle ) ; const isEditorContainText = editorText . includes ( expectedText ) ; if ( isEditorContainText ) { return true ; } await this . driverHelper . wait ( polling ) ; } , timeout ) ; } async followAndWaitForText ( editorTabTitle , expectedText , timeout = TimeoutConstants . TS_EDITOR_TAB_INTERACTION_TIMEOUT , polling = TestConstants . TS_SELENIUM_DEFAULT_POLLING ) { Logger . debug ( `Editor.followAndWaitForText title: "${ editorTabTitle }" text: "${ expectedText }"` ) ; await this . selectTab ( editorTabTitle ) ; await this . driverHelper . getDriver ( ) . wait ( async ( ) => { await this . performKeyCombination ( editorTabTitle , Key . chord ( Key . CONTROL , Key . END ) ) ; const editorText = await this . getEditorVisibleText ( editorTabTitle ) ; const isEditorContainText = editorText . includes ( expectedText ) ; if ( isEditorContainText ) { return true ; } await this . driverHelper . wait ( polling ) ; } , timeout ) ; } async moveCursorToLineAndChar ( editorTabTitle , line , char ) { Logger . debug ( `Editor.moveCursorToLineAndChar title: "${ editorTabTitle }" line: "${ line }" char: "${ char }"` ) ; await this . performKeyCombination ( editorTabTitle , Key . chord ( Key . CONTROL , Key . HOME ) ) ; for ( let i = 1 ; i < line ; i ++ ) { await this . performKeyCombination ( editorTabTitle , Key . ARROW_DOWN ) ; } for ( let i = 1 ; i < char ; i ++ ) { await this . performKeyCombination ( editorTabTitle , Key . ARROW_RIGHT ) ; } } async performKeyCombination ( editorTabTitle , text ) { Logger . debug ( `Editor.performKeyCombination title: "${ editorTabTitle }" text: "${ text }"` ) ; const interactionContainerLocator = this . getEditorActionArreaLocator ( editorTabTitle ) ; await this . driverHelper . type ( interactionContainerLocator , text ) ; } async type ( editorTabTitle , text , line ) { Logger . debug ( `Editor.type title: "${ editorTabTitle }" text: "${ text }"` ) ; await this . selectTab ( editorTabTitle ) ; await this . moveCursorToLineAndChar ( editorTabTitle , line , 1 ) ; await this . performKeyCombination ( editorTabTitle , text ) ; } async waitErrorInLine ( lineNumber , fileName , timeout = TimeoutConstants . TS_ERROR_HIGHLIGHTING_TIMEOUT ) { Logger . debug ( `Editor.waitErrorInLine line: "${ lineNumber }"` ) ; const errorInLineLocator = await this . getErrorInLineLocator ( lineNumber , fileName ) ; await this . driverHelper . waitVisibility ( errorInLineLocator , timeout ) ; } async waitErrorInLineDisappearance ( lineNumber , fileName , timeout = TimeoutConstants . TS_ERROR_HIGHLIGHTING_TIMEOUT ) { Logger . debug ( `Editor.waitErrorInLineDisappearance line: "${ lineNumber }"` ) ; const errorInLineLocator = await this . getErrorInLineLocator ( lineNumber , fileName ) ; await this . driverHelper . waitDisappearanceWithTimeout ( errorInLineLocator , timeout ) ; } async waitWarningInLine ( lineNumber , fileName , timeout = TimeoutConstants . TS_ERROR_HIGHLIGHTING_TIMEOUT ) { Logger . debug ( `Editor.waitWarningInLine line: "${ lineNumber }"` ) ; const warningInLineLocator = await this . getWarningInLineLocator ( lineNumber , fileName ) ; await this . driverHelper . waitVisibility ( warningInLineLocator , timeout ) ; } async waitInfoInLine ( lineNumber , fileName , timeout = TimeoutConstants . TS_ERROR_HIGHLIGHTING_TIMEOUT ) { Logger . debug ( `Editor.waitInfoInLine line: "${ lineNumber }"` ) ; const infoInLineLocator = await this . getInfoInLineLocator ( lineNumber , fileName ) ; await this . driverHelper . waitVisibility ( infoInLineLocator , timeout ) ; } async waitWarningInLineDisappearance ( lineNumber , fileName , timeout = TimeoutConstants . TS_ERROR_HIGHLIGHTING_TIMEOUT ) { Logger . debug ( `Editor.waitWarningInLineDisappearance line: "${ lineNumber }"` ) ; const warningInLineLocator = await this . getWarningInLineLocator ( lineNumber , fileName ) ; await this . driverHelper . waitDisappearanceWithTimeout ( warningInLineLocator , timeout ) ; } async waitStoppedDebugBreakpoint ( tabTitle , lineNumber , timeout = TimeoutConstants . TS_BREAKPOINT_DEFAULT_TIMEOUT ) { Logger . debug ( `Editor.waitStoppedDebugBreakpoint title: "${ tabTitle }" line: "${ lineNumber }"` ) ; await this . driverHelper . waitUntilTrue ( ( ) => this . isBreakpointPresent ( tabTitle , lineNumber , true ) , timeout ) ; } async waitBreakpoint ( tabTitle , lineNumber , timeout = TimeoutConstants . TS_BREAKPOINT_DEFAULT_TIMEOUT ) { Logger . debug ( `Editor.waitBreakpoint title: "${ tabTitle }" line: "${ lineNumber }"` ) ; await this . driverHelper . waitUntilTrue ( ( ) => this . isBreakpointPresent ( tabTitle , lineNumber ) , timeout ) ; } async waitBreakpointAbsence ( tabTitle , lineNumber , timeout = TimeoutConstants . TS_BREAKPOINT_DEFAULT_TIMEOUT ) { Logger . debug ( `Editor.waitBreakpointAbsence title: "${ tabTitle }" line: "${ lineNumber }"` ) ; await this . driverHelper . waitUntilTrue ( ( ) => ! this . isBreakpointPresent ( tabTitle , lineNumber ) , timeout ) ; } async activateBreakpoint ( tabTitle , lineNumber ) { Logger . debug ( `Editor.activateBreakpoint title: "${ tabTitle }" line: "${ lineNumber }"` ) ; const attempts = TestConstants . TS_SELENIUM_DEFAULT_ATTEMPTS ; const polling = TestConstants . TS_SELENIUM_DEFAULT_POLLING ; const breakpointStatus = await this . isBreakpointPresent ( tabTitle , lineNumber ) ; if ( breakpointStatus ) { return ; } for ( let i = 0 ; i < attempts ; i ++ ) { try { await this . selectTab ( tabTitle ) ; await this . moveCursorToLineAndChar ( tabTitle , lineNumber , 1 ) ; await this . performKeyCombination ( tabTitle , Key . F9 ) ; await this . waitBreakpoint ( tabTitle , lineNumber ) ; return ; } catch ( err ) { if ( i === attempts - 1 ) { throw new error . TimeoutError ( `Exceeded maximum breakpoint activation attempts` ) ; } Logger . debug ( `Editor.activateBreakpoint - Error: ${ err }` ) ; await this . driverHelper . wait ( polling ) ; } } } async getLineYCoordinates ( lineNumber , fileName ) { Logger . debug ( `Editor.getLineYCoordinates line: "${ lineNumber }"` ) ; const lineNumberLocator = By . xpath ( `//div[contains(@data-uri, '${ fileName }')]` + `//div[contains(@class, 'line-numbers') and text()='${ lineNumber }']` + `//parent::div[contains(@style, 'position')]` ) ; let elementStyleValue = await this . driverHelper . waitAndGetElementAttribute ( lineNumberLocator , 'style' , TimeoutConstants . TS_SELENIUM_CLICK_ON_VISIBLE_ITEM ) ; Logger . trace ( `Editor.getLineYCoordinates style string:${ elementStyleValue }` ) ; const lineYCoordinate = Number . parseInt ( elementStyleValue . split ( 'top:' ) [ 1 ] . split ( 'px' ) [ 0 ] , 10 ) ; if ( Number . isNaN ( lineYCoordinate ) ) { throw new error . UnsupportedOperationError ( `Failed to parse the ${ elementStyleValue } row to number format` ) ; } return lineYCoordinate ; } async clickOnLineAndChar ( line , char , fileName ) { Logger . debug ( `Editor.clickOnLineAndChar line: "${ line }" char: "${ char }"` ) ; const yPosition = await this . getLineYCoordinates ( line , fileName ) + Editor . ADDITIONAL_SHIFTING_TO_Y ; const xPosition = char + Editor . ADDITIONAL_SHIFTING_TO_X ; new ActionSequence ( this . driverHelper . getDriver ( ) ) . mouseMove ( { x : xPosition , y : yPosition } ) . click ( ) . perform ( ) ; } async goToDefinitionWithMouseClicking ( line , char , fileName ) { Logger . debug ( `Editor.goToDefinitionWithMouseClicking line: "${ line }" char: "${ char }"` ) ; const yPosition = await this . getLineYCoordinates ( line , fileName ) + Editor . ADDITIONAL_SHIFTING_TO_Y ; new ActionSequence ( this . driverHelper . getDriver ( ) ) . keyDown ( Key . CONTROL ) . mouseMove ( { x : char + Editor . ADDITIONAL_SHIFTING_TO_X , y : yPosition } ) . click ( ) . keyDown ( Key . CONTROL ) . perform ( ) ; } async mouseRightButtonClick ( line , char , fileName ) { Logger . debug ( `Editor.mouseRightButtonClick line: "${ line }" char: "${ char }"` ) ; const yPosition = await this . getLineYCoordinates ( line , fileName ) + Editor . ADDITIONAL_SHIFTING_TO_Y ; new ActionSequence ( this . driverHelper . getDriver ( ) ) . mouseMove ( { x : char + Editor . ADDITIONAL_SHIFTING_TO_X , y : yPosition } ) . click ( Button . RIGHT ) . perform ( ) ; } private async scrollAndSearchSuggestion ( editorTabTitle , suggestionLocator , timeout = 10000 ) { await this . driverHelper . getDriver ( ) . wait ( async ( ) => { const loadingLocator = By . xpath ( Editor . SUGGESTION_LOADING_XPATH ) ; await this . waitSuggestionContainer ( ) ; await this . driverHelper . waitDisappearance ( loadingLocator ) ; await this . driverHelper . wait ( 1000 ) ; if ( await this . driverHelper . isVisible ( suggestionLocator ) ) { return true ; } await this . performKeyCombination ( editorTabTitle , Key . ARROW_DOWN ) ; } , timeout ) ; } private getTabWithUnsavedStatus ( tabTitle ) { return By . xpath ( `//div[text()='${ tabTitle }']/parent::li[contains(@class, 'theia-mod-dirty')]` ) ; } private getEditorBodyLocator ( editorTabTitle ) { const editorXpathLocator = `//div[@id='theia-main-content-panel']//div[contains(@class, 'monaco-editor')` + ` and contains(@data-uri, '${ editorTabTitle }')]//*[contains(@class, 'lines-content')]` ; return By . xpath ( editorXpathLocator ) ; } private getEditorActionArreaLocator ( editorTabTitle ) { const editorActionArreaXpathLocator = `//div[@id='theia-main-content-panel']//div[contains(@class, 'monaco-editor')` + ` and contains(@data-uri, '${ editorTabTitle }')]//textarea` ; return By . xpath ( editorActionArreaXpathLocator ) ; } private getEditorLineXpathLocator ( lineNumber ) { return `(//div[contains(@class,'lines-content')]//div[@class='view-lines monaco-mouse-cursor-text']/div[@class='view-line'])[${ lineNumber }]` ; } private getSuggestionLineXpathLocator ( suggestionText ) { return By . xpath ( `//div[@widgetid='editor.widget.suggestWidget']//span[@class='monaco-highlighted-label' and contains(.,'${ suggestionText }')]` ) ; } private getTabXpathLocator ( tabTitle ) { return `//li[contains(@class, 'p-TabBar-tab')]//div[text()='${ tabTitle }']` ; } private async getErrorInLineLocator ( lineNumber , fileName ) { const lineYCoordinates = await this . getLineYCoordinates ( lineNumber , fileName ) ; return By . xpath ( `//div[contains(@style, 'top:${ lineYCoordinates }px')]//div[contains(@class, 'squiggly-error')]` ) ; } private async getWarningInLineLocator ( lineNumber , fileName ) { const lineYCoordinates = await this . getLineYCoordinates ( lineNumber , fileName ) ; return By . xpath ( `//div[contains(@style, 'top:${ lineYCoordinates }px')]//div[contains(@class, 'squiggly-warning')]` ) ; } private async getInfoInLineLocator ( lineNumber , fileName ) { const lineYCoordinates = await this . getLineYCoordinates ( lineNumber , fileName ) ; return By . xpath ( `//div[contains(@style, 'top:${ lineYCoordinates }px')]//div[contains(@class, 'squiggly-info')]` ) ; } private async waitSuggestionWithResettingCursor ( editorTabTitle , suggestionText , timeout = TimeoutConstants . TS_SUGGESTION_TIMEOUT , lineNumber , charNumber ) { const suggestionLocator = this . getSuggestionLineXpathLocator ( suggestionText ) ; const methodLogText = `Editor.waitSuggestion tabTitle: "${ editorTabTitle }" ` + `suggestion: "${ suggestionText }" ` + `line: "${ lineNumber }" ` + `char: "${ charNumber }"` ; Logger . debug ( methodLogText ) ; await this . driverHelper . getDriver ( ) . wait ( async ( ) => { await this . selectTab ( editorTabTitle ) ; await this . moveCursorToLineAndChar ( editorTabTitle , lineNumber , charNumber ) ; await this . pressControlSpaceCombination ( editorTabTitle ) ; try { await this . driverHelper . waitVisibility ( suggestionLocator , 5000 ) ; await this . closeSuggestionContainer ( editorTabTitle ) ; return true ; } catch ( err ) { if ( ! ( err instanceof error . TimeoutError ) ) { throw err ; } } } , timeout ) ; } private async waitSuggestionWithoutResettingCursor ( editorTabTitle , suggestionText , timeout = TimeoutConstants . TS_SUGGESTION_TIMEOUT ) { Logger . debug ( `Editor.waitSuggestion tabTitle: "${ editorTabTitle }" suggestion: "${ suggestionText }"` ) ; const suggestionLocator = this . getSuggestionLineXpathLocator ( suggestionText ) ; await this . driverHelper . getDriver ( ) . wait ( async ( ) => { try { await this . driverHelper . waitVisibility ( suggestionLocator , 5000 ) ; await this . closeSuggestionContainer ( editorTabTitle ) ; return true ; } catch ( err ) { if ( ! ( err instanceof error . TimeoutError ) ) { throw err ; } await this . closeSuggestionContainer ( editorTabTitle , timeout ) ; await this . pressControlSpaceCombination ( editorTabTitle ) ; } } , timeout ) ; } param lineNumber param triggered private async isBreakpointPresent ( tabTitle , lineNumber , triggered = false ) { Logger . debug ( 'Editor.isBreakpointPresent' ) ; const linesBarXpathLocator = `//div[contains(@id, '${ tabTitle }')]//div[@class='margin']` ; const triggeredBreakpointXpathLocator = `//div[contains(@class, 'theia-debug-breakpoint') and contains(@class, 'theia-debug-top-stack-frame')]` ; const regularBreakpointXpathLocator = `//div[contains(@class, 'theia-debug-breakpoint')]` ; const lineNumberRelativeXpathLocator = `/parent::div/div[contains(@class, 'line-numbers') and text()=${ lineNumber }]` ; const breakpointLocator = ( triggered ? triggeredBreakpointXpathLocator : regularBreakpointXpathLocator ) ; const breakpointLineNumberXpathLocator = linesBarXpathLocator + breakpointLocator + lineNumberRelativeXpathLocator ; return await this . driverHelper . isVisible ( By . xpath ( breakpointLineNumberXpathLocator ) ) ; } }