package base; 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 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 org.openqa.selenium.By; import org.openqa.selenium.NoAlertPresentException; import org.openqa.selenium.NoSuchElementException; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebDriverException; import org.openqa.selenium.WebElement; 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; /** * 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 WebDriver driver; protected WebDriverWait driverWait; protected Languages language; private Actions actions; protected LoginProfile loginProfile; protected boolean loginStatus; protected String url; protected String basePageUrl; protected String baseUrl; private String currentWindowHandle; private static Logger log = LoggerFactory.getLogger(BasePage.class); protected Select selectBoxLanguages; public BasePage(WebDriver driver, String url, LoginProfile lp) { xPaths = new HashMap(); setLoginProfile(lp); this.language = lp.getLanguage(); this.url = url; this.driver = driver; driverWait = new WebDriverWait(driver, 10); actions = new Actions(driver); basePageUrl = Loader.instance().config("base.page.url"); baseUrl = Loader.instance().config("base.url"); currentWindowHandle = driver.getWindowHandle(); // Get the child class name String 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("Button.Logout", MessageFormat.format( Loader.instance().xpath("BasePage.Button.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 WebElement getWebElement(String name, String... options) { String callerClassName = Thread.currentThread().getStackTrace()[2].getClassName(); WebElement 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 = getDriver().findElement(By.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 WEBEXTEITOEN"); 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 = getDriver().findElements(By.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 !getDriver().findElements(By.xpath(xPath)).isEmpty(); //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(getDriver(), getLoginProfile()); si.setLoginProfile(getLoginProfile()); getWebElement("Link.SignIn").click(); driverWait.until(ExpectedConditions.elementToBeClickable( getWebElement("TextBox.Login"))); 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")); try { changeLanguage(this.getExpectedLanguage()); } catch(WebDriverException ex) { if(ex instanceof UnreachableBrowserException) throw ex; log.debug("Can't change language"); } 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)"); autoFix(); loginAttempts++; try { logout(); } catch(WebDriverException ex) { if(ex instanceof UnreachableBrowserException) throw ex; log.debug("Can't logout"); } e.printStackTrace(); } } } /** * This method is here to fix errors that (irregularly) happens when selenium "goes too fast" * Typically, the language or the mode which is not configured, the page that can't load fast enough, etc. * It can also check logins etc. * This is a full refresh, to ensure that we're on the right page */ public void autoFix() { try { cancelRedirection(); } catch(WebDriverException ex) { if(ex instanceof UnreachableBrowserException) throw ex; log.debug("Can't get back to home page"); } try { refresh(); } catch(WebDriverException ex) { if(ex instanceof UnreachableBrowserException) throw ex; log.debug("Can't refresh"); } try { changeLanguage(this.getExpectedLanguage()); } catch(WebDriverException ex) { if(ex instanceof UnreachableBrowserException) throw ex; log.debug("Can't change language"); } } /** * 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 != "") driver.get(url); checkForError(); } /** * Disconnect the current user. It won't retry or check anything. */ public void logout() { try{ new Actions(getDriver()).moveToElement(getWebElement("Menu.AccountPreference")).perform(); getWebElement("Button.Logout").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) {} } /** * Put the move over a component. This is useful to trigger some event, deploy some menus, etc. * @param XPath XPath of the component */ public void hoverComponent(String XPath) { actions.moveToElement(driver.findElement(By.xpath(XPath))); actions.perform(); sleep(1); } /** * Same as go(), go to the default url of the page */ public void refresh() { driver.get(driver.getCurrentUrl()); } /** * 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; } /** * Calls for changeLanguage(Languages) with the current defaultLanguage of the page */ public void setToDefaultLanguage() { try { changeLanguage(this.getExpectedLanguage()); } catch(WebDriverException ex) { if(ex instanceof UnreachableBrowserException) throw ex; log.debug("Can't change language"); } } /** * 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()); try { driverWait.wait(1000); } catch(Exception e) {} } 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(!driver.getCurrentUrl().contains(baseUrl) || driver.getCurrentUrl().contains("viewer.html") || driver.findElements(By.xpath("//a[contains(@href, \"" + Pages.HOME.getLink().replace(".xhtml", ".seam") + "\")]")).size() == 0){ driver.get(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 driver.get(Loader.instance().config("base.url") + Loader.instance().config("gazelle.version.url")); Matcher m = myPattern.matcher(driver.getPageSource()); if(m.find()) result = m.group(); driver.get(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 (driver.getWindowHandles().size() > 1) { for (String windowHandle : driver.getWindowHandles()) { if (!windowHandle.equals(currentWindowHandle)) { driver = driver.switchTo().window(windowHandle); } } currentWindowHandle = driver.getWindowHandle(); closePopupWindows(); } log.debug(" Transfering done"); } /** * Attempts to close all windows popups Can also perform an error check of every popup * @param check * : If true, check for error in popup windows (error.seam etc.) */ public void closePopupWindows() { log.debug(" Closing all other windows ..."); String parentWindowHandle = driver.getWindowHandle(); // save the current window handle. WebDriver popup = null; for (String windowHandle : driver.getWindowHandles()) { if (!windowHandle.equals(parentWindowHandle)) { popup = driver.switchTo().window(windowHandle); popup.close(); } } driver.switchTo().window(parentWindowHandle); log.debug(" Closing all other windows done"); } /** * Close one javascript alert box */ public void cancelAlert() { log.debug(" Closing alert windows ..."); try { driver.switchTo().alert().accept(); } catch (NoAlertPresentException e) { } log.debug(" Closing alert windows done"); } /** * Wait until the load bar with the gif disapears. Performs at least a 4 second sleep. * @param timeout in seconds */ public void waitUntilLoaded(int timeout) { double startTime = System.currentTimeMillis(); driver.manage().timeouts().implicitlyWait(1, TimeUnit.SECONDS); sleep(1); while(isWebElementPresent("Div.Loading") && System.currentTimeMillis() < startTime + timeout * 1000) sleep(1); driver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS); return; } 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 WebDriver getDriver() { return driver; } public void setDriver(WebDriver driver) { this.driver = driver; driverWait = new WebDriverWait(driver, 10); PageFactory.initElements(driver, this); } 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(getDriver()); } }