package base; import java.io.File; import java.io.IOException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.codeborne.selenide.CollectionCondition; import com.codeborne.selenide.Condition; import com.codeborne.selenide.Selenide; import com.codeborne.selenide.SelenideElement; import net.ihe.gazelle.menu.Pages; import net.ihe.gazelle.test.data_structure.LoginProfile; import net.ihe.gazelle.test.enums.Languages; import net.ihe.gazelle.test.page.SignIn; import net.ihe.gazelle.test.tool.ErrorChecker; import net.ihe.gazelle.test.tool.Loader; import net.ihe.gazelle.test.tool.Tool; import org.apache.commons.io.FileUtils; import org.openqa.selenium.*; import org.openqa.selenium.interactions.Actions; import org.openqa.selenium.remote.UnreachableBrowserException; import org.openqa.selenium.support.PageFactory; import org.openqa.selenium.support.ui.ExpectedConditions; import org.openqa.selenium.support.ui.Select; import org.openqa.selenium.support.ui.WebDriverWait; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.Assert; import static com.codeborne.selenide.Condition.disappears; import static com.codeborne.selenide.Condition.empty; import static com.codeborne.selenide.Condition.exist; import static com.codeborne.selenide.Selectors.byText; import static com.codeborne.selenide.Selenide.$; import static com.codeborne.selenide.Selenide.$$; import static com.codeborne.selenide.Selenide.open; import static com.codeborne.selenide.WebDriverRunner.getWebDriver; import static org.openqa.selenium.By.xpath; /** * Mother class for every PageObject class. * Contains all basic things : loginprofile, webdriver, language, etc. * Does all basic operations : login, changing language, error-detection, popups management, etc. * @author sbs * */ public abstract class BasePage { // The list of components xpath of the page // K : the name of the component in the config.properties file (e.g. MainPage.Button.Login.XPath) // V : the xpath of the component, dynamically updated with crowdin values etc. protected Map xPaths; protected Languages language; protected LoginProfile loginProfile; protected boolean loginStatus; protected String url; protected String basePageUrl; protected String baseUrl; private String currentWindowHandle; protected String className; private static Logger log = LoggerFactory.getLogger(BasePage.class); protected Select selectBoxLanguages; public BasePage(String url, LoginProfile lp) { xPaths = new HashMap(); setLoginProfile(lp); this.language = lp.getLanguage(); this.url = url; basePageUrl = Loader.instance().config("base.page.url"); baseUrl = Loader.instance().config("base.url"); currentWindowHandle = getWebDriver().getWindowHandle(); // Get the child class name className = Thread.currentThread().getStackTrace()[2].getClassName(); try { loadXPaths(); } catch(Exception e) { if(Loader.instance().config("Debug").equals("true")) e.printStackTrace(); Assert.fail("There were error during the loading of xpaths for the page " + className + "\nCheck that all Loader.config() and Loader.crowdin() calls refer to the right properties"); } checkXPaths(); } /** * This method is here to check all added xPaths. * It detects duplication and null (null = properties not here) * It should be called by every PageObject's constructor. */ protected void checkXPaths() { for(String s : xPaths.keySet()) if(xPaths.get(s) == null) Assert.fail("xPath named [" + s + "] is null. It probably does not exist in the .properties file"); } protected void loadXPaths() { xPaths.put("Link.SignIn", MessageFormat.format( Loader.instance().xpath("MainPage.Link.SignIn.XPath"), Loader.instance().crowdin("gazelle.users.registration.Login"))); xPaths.put("Button.Login", MessageFormat.format( Loader.instance().xpath("BasePage.Button.Login.XPath"), Loader.instance().crowdin("gazelle.users.registration.Login"))); xPaths.put("Link.Logout", MessageFormat.format( Loader.instance().xpath("BasePage.Link.Logout.XPath"), Loader.instance().crowdin("gazelle.users.registration.Logout"))); xPaths.put("Select.Languages", Loader.instance().xpath("BasePage.Select.Languages.XPath")); xPaths.put("TextBox.Login", MessageFormat.format( Loader.instance().xpath("BasePage.TextBox.Login.XPath"), Loader.instance().crowdin("gazelle.users.registration.Username"))); xPaths.put("TextBox.Password", MessageFormat.format( Loader.instance().xpath("BasePage.TextBox.Password.XPath"), Loader.instance().crowdin("gazelle.users.registration.Password"))); xPaths.put("Div.Loading", Loader.instance().xpath("General.Div.Loading.XPath")); xPaths.put("Menu.AccountPreference", MessageFormat.format( Loader.instance().xpath("MainPage.Menu.AccountPreference.XPath"), loginProfile.getName())); xPaths.put("Link.Page", Loader.instance().xpath("MainPage.Link.Page.XPath")); } /** * This method perform a driver.findElement() with a xPath in the xPaths hashmap. * It allows error tracking (deprecated xPath, invalid xPaths, etc.) * This method should be user for every xPath request. * * @param name : the name of the xPath in the xPaths hasMap * @param options : an xPath may contains {option1} {option2} {optionx} ... Replacements strings can be passed here * @return */ public SelenideElement getWebElement(String name, String... options) { String callerClassName = Thread.currentThread().getStackTrace()[2].getClassName(); SelenideElement we = null; String xPath = xPaths.get(name); if(xPath == null) { log.error("The requested xPath : [" + name + "] is not present in this page xPaths list (called by [" + callerClassName + "])"); Assert.fail("The current test made an erroneous xPath request"); } int i = 1; for(String option : options) { if(xPath.contains("{option" + i + "}")) xPath = xPath.replace("{option" + i + "}", option); i++; } try { we = $(xpath(xPath)); } catch(NoSuchElementException e) { log.error("The element [" + name + "] can't be found (called by [" + callerClassName + "])"); log.error("The xPaths value is [" + xPath + "]"); throw e; } catch(WebDriverException e) { log.error("ERREUR WDE : "+ e); throw e; } return we; } public List getWebElements(String name, String... options) { String callerClassName = Thread.currentThread().getStackTrace()[2].getClassName(); List we = null; String xPath = xPaths.get(name); if(xPath == null) { log.error("The requested xPath : [" + name + "] is not present in this page xPaths list (called by [" + callerClassName + "])"); Assert.fail("The current test made an erroneous xPath request"); } int i = 1; for(String option : options) { if(xPath.contains("{option" + i + "}")) xPath = xPath.replace("{option" + i + "}", option); i++; } try { we = $$(xpath(xPath)); } catch(NoSuchElementException e) { System.out.println("The element [" + name + "] can't be found (called by [" + callerClassName + "])"); log.error("The xPaths value is [" + xPath + "]"); throw e; } catch(WebDriverException e) { throw e; } return we; } /** * Checks if an element is present. * As for getWebElement, this method should be used for every existence check. * @param name : the name of the xPath in the xPaths hashMap * @return */ public boolean isWebElementPresent(String name, String... options) { String callerClassName = Thread.currentThread().getStackTrace()[2].getClassName(); String xPath = xPaths.get(name); if(xPath == null) { log.error("The requested xPath : [" + name + "] is not present in this page xPaths list (called by [" + callerClassName + "])"); Assert.fail("The current test made an erroneous xPath request"); } int i = 1; for(String option : options) { if(xPath.contains("{option" + i + "}")) xPath = xPath.replace("{option" + i + "}", option); i++; } return !$(xpath(xPath)).is(empty); //TODO : manage invalid xpath exception } /** * Calls for login(name, password) with the current LoginProfile informations */ public void login() { login(loginProfile.getName(), loginProfile.getPassword()); } /** * Opens the Sign In page * @return the corresponding PageObject */ public SignIn goToSignIn() { SignIn si = new SignIn(getLoginProfile()); si.setLoginProfile(getLoginProfile()); getWebElement("Link.SignIn").click(); checkForError(); return si; } /** * Login with an error check. * If the login fails, the method will retry several times (the number of attempts is configurable in * the config.properties file) * @param name * @param password */ public void login(String name, String password) { int loginAttempts = 1; final int LOGIN_MAX_ATTEMPT = Integer.parseInt(Loader.instance().config("login.attempts.max")); changeLanguage(); while(loginAttempts <= LOGIN_MAX_ATTEMPT) { try { goToSignIn(); // Fill in login form getWebElement("TextBox.Login").clear(); getWebElement("TextBox.Login").sendKeys(name); getWebElement("TextBox.Password").clear(); getWebElement("TextBox.Password").sendKeys(password); getWebElement("Button.Login").click(); sleep(2); break; } catch(Exception e) { log.warn("Can't login, trying again (" + loginAttempts + " attempts)"); loginAttempts++; try { logout(); } catch(WebDriverException ex) { if(ex instanceof UnreachableBrowserException) throw ex; log.debug("Can't logout"); } e.printStackTrace(); } } } /** * Go to the url of the page. This url is set in the constructor. * For most of the PageObject there is a default value automatically set. */ public void go() { if(url != "") open(url); checkForError(); } /** * Disconnect the current user. It won't retry or check anything. */ public void logout() { try{ getWebElement("Menu.AccountPreference").scrollTo().click(); getWebElement("Link.Logout").scrollTo().click(); } catch(WebDriverException e) { log.debug("Can't logout"); } loginStatus = false; } /** * A simple pause method * @param seconds */ public void sleep(int seconds) { try { Thread.sleep(seconds * 1000); } catch (InterruptedException e) {} } public void changeLanguage(){ try { changeLanguage(this.getExpectedLanguage()); } catch(WebDriverException ex) { if(ex instanceof UnreachableBrowserException) throw ex; log.debug("Can't change language"); } } /** * Fetch the currently set language. It happened to fail a lot (for some reason) so it will try several times * (amount of trials in config.properties). * @return An element of Languages that correspond to the current selected language in the selectBox. */ public Languages getCurrentLanguage() { int attemptAmount = 1; final int MAX_ATTEMPT_LANGUAGE_FETCH = Integer.parseInt(Loader.instance().config("max.attempt.language.fetch")); boolean success = false; while(!success && attemptAmount <= MAX_ATTEMPT_LANGUAGE_FETCH) { try { // Waiting for a possible refresh sleep(2); selectBoxLanguages = new Select(getWebElement("Select.Languages")); String optionText = selectBoxLanguages.getFirstSelectedOption().getAttribute("value"); // We don't return a string, we return an enum. Thus we browse all existent enum and // we return the one who corresponds to what we read in the option box for(Languages l : Languages.values()) { if(l.getOptionText().equals(optionText)) return l; } } catch(WebDriverException e) { if(e instanceof UnreachableBrowserException) throw e; log.debug("Couldn't get language, trying again (" + attemptAmount + " attempt)"); attemptAmount++; } } log.warn("Couldn't get language at all, giving up"); return null; } /** * Attempts to change the language in Gazelle-TM. Will try several times (Max attemps in config.properties). * @param language */ public void changeLanguage(Languages language) { int languageChangeAttempt = 1; int LANGUAGE_CHANGE_ATTEMPT_MAX = Integer.parseInt(Loader.instance().config("language.change.attempt.max")); while((getCurrentLanguage() != language) && languageChangeAttempt <= LANGUAGE_CHANGE_ATTEMPT_MAX) { try { selectBoxLanguages = new Select(getWebElement("Select.Languages")); selectBoxLanguages.selectByValue(language.getOptionText()); } catch(Exception e) { if(e instanceof UnreachableBrowserException) throw (UnreachableBrowserException)e; log.debug("Error changing language. Attempt n°" + languageChangeAttempt + " (to " + language.name() + ")"); } languageChangeAttempt++; cancelRedirection(); // Because selenium sometimes ends up in some lost url stuff } if((getCurrentLanguage() != language)) { log.debug("Couldn't change language. Giving up"); } } /** * Brings automatically back to home url when we are at a blocking page * A page is blocking when (or) : * - the current url doesn't contain the base url, which means we are out of the gazelle website * - the current url contains "viewer.html" which means we are seeing a pdf file (selenium completely loses when confronted to pdf) * - the current page doesn't contain any home button, which means we can't go back to normal pages by randomly clicking */ public void cancelRedirection () { if(!getWebDriver().getCurrentUrl().contains(baseUrl) || getWebDriver().getCurrentUrl().contains("viewer.html") || getWebDriver().findElements(xpath("//a[contains(@href, \"" + Pages.HOME.getLink().replace(".xhtml", ".seam") + "\")]")).size() == 0){ open(baseUrl + basePageUrl); } } public String getGazelleVersion(String previousUrl) { String result = "version not found"; Pattern myPattern = Pattern.compile("(\\d\\.?)+"); // used to grab only the x.x.x-SNAPSHOT inside the xml open(Loader.instance().config("base.url") + Loader.instance().config("gazelle.version.url")); Matcher m = myPattern.matcher(getWebDriver().getPageSource()); if(m.find()) result = m.group(); open(previousUrl); return result; } /** * Browse every WindowHandles of the webDriver If it appears that there are 2 of them, it means that the other on juste poped out In this case we transfer the current WindowHandle to the newly * created window */ public void replaceWithPopup() { log.debug(" Transfering to other window ..."); cancelAlert(); if (getWebDriver().getWindowHandles().size() > 1) { for (String windowHandle : getWebDriver().getWindowHandles()) { if (!windowHandle.equals(currentWindowHandle)) { getWebDriver().switchTo().window(windowHandle); } } currentWindowHandle = getWebDriver().getWindowHandle(); closePopupWindows(); } log.debug(" Transfering done"); } /** * Attempts to close all windows popups Can also perform an error check of every popup * @param * : If true, check for error in popup windows (error.seam etc.) */ public void closePopupWindows() { log.debug(" Closing all other windows ..."); String parentWindowHandle = getWebDriver().getWindowHandle(); // save the current window handle. WebDriver popup = null; for (String windowHandle : getWebDriver().getWindowHandles()) { if (!windowHandle.equals(parentWindowHandle)) { popup = getWebDriver().switchTo().window(windowHandle); popup.close(); } } getWebDriver().switchTo().window(parentWindowHandle); log.debug(" Closing all other windows done"); } /** * Close one javascript alert box */ public void cancelAlert() { log.debug(" Closing alert windows ..."); try { getWebDriver().switchTo().alert().accept(); } catch (NoAlertPresentException e) { } log.debug(" Closing alert windows done"); } public Languages getExpectedLanguage() { return language; } public void setExpectedLanguage(Languages l) { language = l; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public LoginProfile getLoginProfile() { return loginProfile; } public void setLoginProfile(LoginProfile lp) { loginProfile = lp; language = lp.getLanguage(); } /** * Checks for an error and throws a new WebDriverException if one is found. * Errors are defined in the class ErrorPages. * An error is, for example, error.seam. * This method is used to tell the run() method that a test didn't run as expected but should be tried again. * @return * @throws WebDriverException */ public boolean checkForError() throws WebDriverException { return ErrorChecker.checkForError(); } }