package base; 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 java.io.File; import java.io.IOException; import java.text.MessageFormat; 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; /** * 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 { private static Logger log = LoggerFactory.getLogger(BasePage.class); // 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; protected LoginProfile loginProfile; protected boolean loginStatus; protected String url; protected String basePageUrl; protected String baseUrl; protected String className; protected Select selectBoxLanguages; private Actions actions; private String currentWindowHandle; 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 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"); } /** * This method perform a screenshot. * This screenshot is stored in a folder using LoginProfile name */ public void takePageScreenshot(String className) { if (Loader.instance().config("Debug").equals("true")) { String folderName = "./AS-" + loginProfile.getName().toUpperCase(); try { boolean existingDir = (new File(folderName)).exists(); if (!existingDir) { (new File(folderName)).mkdirs(); } if (!new File(className + "*").exists()) { File scrFile = ((TakesScreenshot) getDriver()).getScreenshotAs(OutputType.FILE); //Copy Driver screenshot to current jenkin's job workspace, add current datetime in file name. FileUtils.copyFile(scrFile, new File(folderName + "/" + className + "-" + Tool.getDate() + ".png")); } } catch (IOException e) { e.printStackTrace(); } } } 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 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 = scrollToElement(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 WDE : " + e); throw e; } return we; } /** * This method allow to make a javascript scroll to the given element before any * interaction with it. It's used in getWebElement method. * @param el * @return */ public WebElement scrollToElement(WebElement el) { if (driver instanceof JavascriptExecutor) { ((JavascriptExecutor) driver) .executeScript("arguments[0].scrollIntoView(true);", el); } return el; } /** * This method perform a driver.findElements() with a xPath in the xPaths hashmap. * It is used to return List of WebElements * 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 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 { getWebElement("Menu.AccountPreference").click(); getWebElement("Link.Logout").click(); } catch (WebDriverException e) { log.debug("Can't logout"); } loginStatus = false; } /** * Disconnect the current user. It won't retry or check anything. */ public void logoutWithoutCheck() { getWebElement("Menu.AccountPreference").click(); getWebElement("Link.Logout").click(); } /** * 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); } } /** * 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 : 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 overlay with spinner 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 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()); } }