Java tutorial
/** * Orignal work: Copyright 2015 www.seleniumtests.com * Modified work: Copyright 2016 www.infotel.com * Copyright 2017-2019 B.Hecquet * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.seleniumtests.uipage; import java.io.File; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.time.Clock; import java.time.Instant; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.List; import java.util.Set; import java.util.TreeSet; import org.apache.commons.codec.binary.Base64; import org.apache.commons.io.FileUtils; import org.apache.log4j.Logger; import org.openqa.selenium.Alert; import org.openqa.selenium.By; import org.openqa.selenium.Dimension; import org.openqa.selenium.JavascriptExecutor; import org.openqa.selenium.NoAlertPresentException; import org.openqa.selenium.NoSuchWindowException; import org.openqa.selenium.PageLoadStrategy; import org.openqa.selenium.Point; import org.openqa.selenium.TimeoutException; import org.openqa.selenium.UnhandledAlertException; import org.openqa.selenium.UnsupportedCommandException; 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.ui.ExpectedConditions; import org.openqa.selenium.support.ui.WebDriverWait; import org.testng.Assert; import com.seleniumtests.core.SeleniumTestsContext; import com.seleniumtests.core.SeleniumTestsContextManager; import com.seleniumtests.core.SeleniumTestsPageListener; import com.seleniumtests.core.TestTasks; import com.seleniumtests.customexception.CustomSeleniumTestsException; import com.seleniumtests.customexception.NotCurrentPageException; import com.seleniumtests.customexception.ScenarioException; import com.seleniumtests.driver.BrowserType; import com.seleniumtests.driver.CustomEventFiringWebDriver; import com.seleniumtests.driver.TestType; import com.seleniumtests.driver.WebUIDriver; import com.seleniumtests.driver.screenshots.ScreenShot; import com.seleniumtests.driver.screenshots.ScreenshotUtil; import com.seleniumtests.driver.screenshots.ScreenshotUtil.Target; import com.seleniumtests.reporter.logger.TestLogging; import com.seleniumtests.uipage.htmlelements.HtmlElement; import com.seleniumtests.uipage.htmlelements.LinkElement; import com.seleniumtests.util.helper.WaitHelper; import com.seleniumtests.util.logging.SeleniumRobotLogger; public class PageObject extends BasePage implements IPage { private static final Logger logger = SeleniumRobotLogger.getLogger(PageObject.class); private boolean frameFlag = false; private HtmlElement pageIdentifierElement = null; private String title = null; private String windowHandle = null; // store the window / tab on which this page is loaded private String url = null; private String suiteName = null; private String outputDirectory = null; private String htmlFilePath = null; private String imageFilePath = null; private Clock systemClock; /** * Constructor for non-entry point page. The control is supposed to have reached the page from other API call. * * @throws Exception */ public PageObject() throws IOException { this(null, null); } /** * Constructor for non-entry point page. The control is supposed to have reached the page from other API call. * * @param pageIdentifierElement * @throws IOException * * @throws Exception */ public PageObject(final HtmlElement pageIdentifierElement) throws IOException { this(pageIdentifierElement, null); } /** * Base Constructor. * Represents a page on our web site or mobile application. * * @param pageIdentifierElement The element to search for so that we check we are on the right page. * May be null if we do not want to check we are on the page * @param url the URL to which we should connect. May be null if we do not want to go to a specific URL * @throws IOException * * @throws Exception */ public PageObject(final HtmlElement pageIdentifierElement, final String url) throws IOException { systemClock = Clock.systemUTC(); Calendar start = Calendar.getInstance(); start.setTime(new Date()); if (SeleniumTestsContextManager.getGlobalContext() != null && SeleniumTestsContextManager.getGlobalContext().getTestNGContext() != null) { suiteName = SeleniumTestsContextManager.getGlobalContext().getTestNGContext().getSuite().getName(); outputDirectory = SeleniumTestsContextManager.getGlobalContext().getTestNGContext() .getOutputDirectory(); } this.pageIdentifierElement = pageIdentifierElement; driver = WebUIDriver.getWebDriver(); // open page openPage(url); assertCurrentPage(false); SeleniumTestsPageListener.informPageLoad(this); Calendar end = Calendar.getInstance(); start.setTime(new Date()); long startTime = start.getTimeInMillis(); long endTime = end.getTimeInMillis(); if ((endTime - startTime) / 1000 > 0) { TestLogging.log("Open web page in :" + (endTime - startTime) / 1000 + "seconds"); } } protected void setTitle(final String title) { this.title = title; } protected void setUrl(final String openUrl) { this.url = openUrl; } public String getHtmlFilePath() { return htmlFilePath; } @Override public String getHtmlSource() { return driver.getPageSource(); } public String getImageFilePath() { return imageFilePath; } @Override public String getLocation() { return driver.getCurrentUrl(); } /** * Open page * Wait for page loading * @param url * @throws IOException */ private void openPage(String url) throws IOException { if (url != null) { open(url); ((CustomEventFiringWebDriver) driver).updateWindowsHandles(); } // Wait for page load is applicable only for web test // When running tests on an iframe embedded site then test will fail if this command is not used // in case of mobile application, only capture screenshot if (SeleniumTestsContextManager.isWebTest()) { waitForPageToLoad(); } else if (SeleniumTestsContextManager.isAppTest()) { capturePageSnapshot(); } } public void assertCookiePresent(final String name) { assertHTML(getCookieByName(name) != null, "Cookie: {" + name + "} not found."); } @Override protected void assertCurrentPage(final boolean log) { if (pageIdentifierElement != null && !pageIdentifierElement.isElementPresent()) { ScreenShot screenShot = new ScreenshotUtil(driver).capture(Target.PAGE, ScreenShot.class); throw new NotCurrentPageException( getClass().getCanonicalName() + " is not the current page.\nPageIdentifierElement " + pageIdentifierElement.toString() + " is not found."); } } /** * Get parameter from configuration * * @param key * * @return String */ public static String param(String key) { return TestTasks.param(key); } /** * returns the robot configuration * @return */ public SeleniumTestsContext robotConfig() { return SeleniumTestsContextManager.getThreadContext(); } /** * Add step inside a page * @param stepName * @param passwordsToMask array of strings that must be replaced by '*****' in reports */ public void addStep(String stepName) { TestTasks.addStep(stepName); } public void addStep(String stepName, String... passwordToMask) { TestTasks.addStep(stepName, passwordToMask); } /** * Method for creating or updating a variable on the seleniumRobot server ONLY. This will raise a ScenarioException if variables are get from * env.ini file * @param key * @param value */ public void createOrUpdateParam(String key, String value) { TestTasks.createOrUpdateParam(key, value); } /** * Method for creating or updating a variable on the seleniumRobot server ONLY. This will raise a ScenarioException if variables are get from * env.ini file * @param key name of the param * @param value value of the parameter (or new value if we update it) * @param specificToVersion if true, this param will be stored on server with a reference to the application version. This will have no effect if changing a * current variable. */ public void createOrUpdateParam(String key, String value, boolean specificToVersion) { TestTasks.createOrUpdateParam(key, value, specificToVersion); } /** * Method for creating or updating a variable on the seleniumRobot server ONLY. This will raise a ScenarioException if variables are get from * env.ini file * Moreover, created custom variable is specific to tuple (application, version, test environment) * @param key name of the param * @param newValue value of the parameter (or new value if we update it) * @param specificToVersion if true, this param will be stored on server with a reference to the application version. This will have no effect if changing a * current variable. * @param timeToLive if > 0, this variable will be destroyed after some days (defined by variable) * @param reservable if true, this variable will be set as reservable in variable server. This means it can be used by only one test at the same time */ public void createOrUpdateParam(String key, String value, boolean specificToVersion, int timeToLive, boolean reservable) { TestTasks.createOrUpdateParam(key, value, specificToVersion, timeToLive, reservable); } public void assertHtmlSource(final String text) { assertHTML(getHtmlSource().contains(text), "Text: {" + text + "} not found on page source."); } public void assertKeywordNotPresent(final String text) { Assert.assertFalse(getHtmlSource().contains(text), "Text: {" + text + "} not found on page source."); } public void assertLocation(final String urlPattern) { assertHTML(getLocation().contains(urlPattern), "Pattern: {" + urlPattern + "} not found on page location."); } public void assertTitle(final String text) { assertHTML(getTitle().contains(text), "Text: {" + text + "} not found on page title."); } @Override public void capturePageSnapshot() { capturePageSnapshot(null); } public void capturePageSnapshot(String snapshotName) { ScreenShot screenShot = new ScreenshotUtil(driver).capture(Target.PAGE, ScreenShot.class); this.title = screenShot.getTitle(); if (screenShot.getHtmlSourcePath() != null) { htmlFilePath = screenShot.getHtmlSourcePath().replace(suiteName, outputDirectory); } if (screenShot.getImagePath() != null) { imageFilePath = screenShot.getImagePath().replace(suiteName, outputDirectory); } if (snapshotName != null) { screenShot.setTitle(snapshotName); } TestLogging.logScreenshot(screenShot, snapshotName); // store the window / tab on which this page is loaded windowHandle = driver.getWindowHandle(); } /** * Get focus on this page, using the handle we stored when creating it * When called, you should write {@code myPage.<MyPageClassName>getFocus().someMethodOfMyPage();} * @return */ @SuppressWarnings("unchecked") public <T extends PageObject> T getFocus() { selectWindow(windowHandle); return (T) this; } /** * Close a PageObject. This method can be called when a web session opens several pages and one of them is closed after some action * In case there are multiple windows opened, switch back to the previous window in the list * * @throws NotCurrentPageException */ public final void close() { if (WebUIDriver.getWebDriver() == null) { return; } SeleniumTestsPageListener.informPageUnload(this); TestLogging.info("close web page: " + getTitle()); boolean isMultipleWindow = false; List<String> handles = new ArrayList<>(driver.getWindowHandles()); if (handles.size() > 1) { isMultipleWindow = true; } logger.debug("Current handles: " + handles); try { driver.close(); } catch (WebDriverException ignore) { logger.info("Error closing driver: " + ignore.getMessage()); } // wait a bit before going back to main window WaitHelper.waitForSeconds(2); try { if (isMultipleWindow) { try { selectWindow(handles.get(handles.indexOf(windowHandle) - 1)); } catch (IndexOutOfBoundsException | NoSuchWindowException e) { selectMainWindow(); } } else { WebUIDriver.setWebDriver(null); } } catch (UnreachableBrowserException ex) { WebUIDriver.setWebDriver(null); } } /** * Close the current tab / window which leads to the previous window / tab in the list. * This uses the default constructor which MUST be available * @param previousPage the page we go back to, so that we can check we are on the right page * @return */ public <T extends PageObject> T close(Class<T> previousPage) { close(); try { return previousPage.getConstructor().newInstance(); } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) { throw new ScenarioException("Cannot check for previous page: " + e.getMessage(), e); } } /** * Drags an element a certain distance and then drops it. * * @param element to dragAndDrop * @param offsetX in pixels from the current location to which the element should be moved, e.g., 70 * @param offsetY in pixels from the current location to which the element should be moved, e.g., -300 */ public void dragAndDrop(final HtmlElement element, final int offsetX, final int offsetY) { new Actions(driver).dragAndDropBy((WebElement) element.getElement(), offsetX, offsetY).perform(); } public final String getCookieByName(final String name) { if (driver.manage().getCookieNamed(name) == null) { return null; } return driver.manage().getCookieNamed(name).getValue(); } public final int getElementCount(final HtmlElement element) { return driver.findElements(element.getBy()).size(); } public String getEval(final String expression) { Assert.assertTrue(false, "focus not implemented yet for " + expression); return null; } public int getTimeout() { return SeleniumTestsContextManager.getThreadContext().getWebSessionTimeout(); } @Override public String getTitle() { return driver.getTitle(); } public String getUrl() { return url; } public String getCanonicalURL() { return new LinkElement("Canonical URL", By.cssSelector("link[rel=canonical]")).getAttribute("href"); } public final void goBack() { driver.navigate().back(); frameFlag = false; } public final void goForward() { driver.navigate().forward(); frameFlag = false; } public final boolean isCookiePresent(final String name) { return getCookieByName(name) != null; } public boolean isFrame() { return frameFlag; } public final void maximizeWindow() { try { // app test are not compatible with window if (SeleniumTestsContextManager.getThreadContext().getTestType().family() == TestType.APP || SeleniumTestsContextManager.getThreadContext().getBrowser() == BrowserType.BROWSER) { return; } driver.manage().window().maximize(); } catch (Exception ex) { try { ((JavascriptExecutor) driver).executeScript( "if (window.screen){window.moveTo(0, 0);window.resizeTo(window.screen.availWidth,window.screen.availHeight);}"); } catch (Exception ignore) { TestLogging.log("Unable to maximize browser window. Exception occured: " + ignore.getMessage()); } } } /** * On init set window to size requested by user. Window is maximized if no size is set */ public final void setWindowToRequestedSize() { if (!SeleniumTestsContextManager.isWebTest()) { return; } Integer width = SeleniumTestsContextManager.getThreadContext().getViewPortWidth(); Integer height = SeleniumTestsContextManager.getThreadContext().getViewPortHeight(); if (width == null || height == null) { maximizeWindow(); } else { resizeTo(width, height); } } private void open(final String url) { if (this.getDriver() == null) { driver = webUXDriver.createWebDriver(); } setUrl(url); try { // Navigate to app URL for browser test if (SeleniumTestsContextManager.isWebTest()) { setWindowToRequestedSize(); driver.navigate().to(url); } } catch (UnreachableBrowserException e) { driver = webUXDriver.createWebDriver(); if (SeleniumTestsContextManager.isWebTest()) { setWindowToRequestedSize(); driver.navigate().to(url); } } catch (UnsupportedCommandException e) { TestLogging.log("get UnsupportedCommandException, retry"); driver = webUXDriver.createWebDriver(); if (SeleniumTestsContextManager.isWebTest()) { setWindowToRequestedSize(); driver.navigate().to(url); } } catch (org.openqa.selenium.TimeoutException ex) { TestLogging.log("got time out when loading " + url + ", ignored"); } catch (org.openqa.selenium.UnhandledAlertException ex) { TestLogging.log("got UnhandledAlertException, retry"); driver.navigate().to(url); } catch (WebDriverException e) { logger.error(e); throw new CustomSeleniumTestsException(e); } } public final void refresh() { try { driver.navigate().refresh(); } catch (org.openqa.selenium.TimeoutException ex) { TestLogging.log("got time out customexception, ignore"); } } /** * Resize window to given dimensions. * * @param width * @param height */ public final void resizeTo(final int width, final int height) { // app test are not compatible with window if (SeleniumTestsContextManager.getThreadContext().getTestType().family() == TestType.APP) { return; } try { Dimension setSize = new Dimension(width, height); driver.manage().window().setPosition(new Point(0, 0)); int retries = 5; for (int i = 0; i < retries; i++) { driver.manage().window().setSize(setSize); Dimension viewPortSize = ((CustomEventFiringWebDriver) driver) .getViewPortDimensionWithoutScrollbar(); if (viewPortSize.height == height && viewPortSize.width == width) { break; } else { setSize = new Dimension(2 * width - viewPortSize.width, 2 * height - viewPortSize.height); } } } catch (Exception ex) { logger.error(ex); } } public final void selectFrame(final Integer index) { driver.switchTo().frame(index); frameFlag = true; } public final void selectFrame(final By by) { WebElement element = driver.findElement(by); driver.switchTo().frame(element); frameFlag = true; } public final void selectFrame(final String locator) { driver.switchTo().frame(locator); frameFlag = true; } public final void exitFrame() { driver.switchTo().defaultContent(); frameFlag = false; } public final void selectMainWindow() { selectWindow(0); } public final void selectWindow(final int index) { // app test are not compatible with window if (SeleniumTestsContextManager.getThreadContext().getTestType().family() == TestType.APP) { throw new ScenarioException("Application are not compatible with Windows"); } driver.switchTo().window((String) driver.getWindowHandles().toArray()[index]); WaitHelper.waitForSeconds(1); } /** * Selects the first unknown window. To use we an action creates a new window or tab * @return */ public final String selectNewWindow() { return selectNewWindow(SeleniumTestsContextManager.getThreadContext().getExplicitWaitTimeout() * 1000); } /** * Selects the first unknown window. To use we an action creates a new window or tab * @param waitMs wait for N milliseconds before raising error * @return */ public final String selectNewWindow(int waitMs) { // app test are not compatible with window if (SeleniumTestsContextManager.getThreadContext().getTestType().family() == TestType.APP) { throw new ScenarioException("Application are not compatible with Windows"); } // Keep the name of the current window handle before switching // sometimes, our action made window disappear String mainWindowHandle; try { mainWindowHandle = driver.getWindowHandle(); } catch (Exception e) { mainWindowHandle = ""; } logger.debug("Current handle: " + mainWindowHandle); // wait for window to be displayed Instant end = systemClock.instant().plusMillis(waitMs + 250L); Set<String> handles = new TreeSet<>(); boolean found = false; while (end.isAfter(systemClock.instant()) && !found) { handles = driver.getWindowHandles(); logger.debug("All handles: " + handles.toString()); for (String handle : handles) { // we already know this handle if (getCurrentHandles().contains(handle)) { continue; } selectWindow(handle); // wait for a valid address String address = ""; Instant endLoad = systemClock.instant().plusMillis(5000); while (address.isEmpty() && endLoad.isAfter(systemClock.instant())) { address = driver.getCurrentUrl(); } // make window display in foreground // TODO: reactivate feature try { // Point windowPosition = driver.manage().window().getPosition(); // org.openqa.selenium.interactions.Mouse mouse = ((HasInputDevices) driver).getMouse(); // mouse.click(); // Mouse mouse = new DesktopMouse(); // mouse.click(new DesktopScreenRegion(Math.max(0, windowPosition.x) + driver.manage().window().getSize().width / 2, Math.max(0, windowPosition.y) + 5, 2, 2).getCenter()); } catch (Exception e) { logger.warn("error while giving focus to window"); } found = true; break; } WaitHelper.waitForMilliSeconds(300); } // check window has changed if (waitMs > 0 && mainWindowHandle.equals(driver.getWindowHandle())) { throw new CustomSeleniumTestsException("new window has not been found. Handles: " + handles); } return mainWindowHandle; } /** * Switch to the default content */ public void switchToDefaultContent() { try { driver.switchTo().defaultContent(); } catch (UnhandledAlertException e) { logger.warn("Alert found, you should handle it"); } } private void waitForPageToLoad() { try { if (robotConfig().getPageLoadStrategy() == PageLoadStrategy.NORMAL) { new WebDriverWait(driver, 5).until(ExpectedConditions .jsReturnsValue("if (document.readyState === \"complete\") { return \"ok\"; }")); } else if (robotConfig().getPageLoadStrategy() == PageLoadStrategy.EAGER) { new WebDriverWait(driver, 5).until(ExpectedConditions .jsReturnsValue("if (document.readyState === \"interactive\") { return \"ok\"; }")); } } catch (TimeoutException e) { // nothing } // populate page info try { capturePageSnapshot(); } catch (Exception ex) { logger.error(ex); throw ex; } } public Alert waitForAlert(int waitInSeconds) { Instant end = systemClock.instant().plusSeconds(waitInSeconds); while (end.isAfter(systemClock.instant())) { try { return driver.switchTo().alert(); } catch (NoAlertPresentException e) { WaitHelper.waitForSeconds(1); } } return null; } /** * Method to handle file upload through robot class * /!\ This should only be used as the last option when uploading file cannot be done an other way * https://saucelabs.com/resources/articles/best-practices-tips-selenium-file-upload * <code> * driver.setFileDetector(new LocalFileDetector()); * driver.get("http://sso.dev.saucelabs.com/test/guinea-file-upload"); * WebElement upload = driver.findElement(By.id("myfile")); * upload.sendKeys("/Users/sso/the/local/path/to/darkbulb.jpg"); * </code> * @param filePath */ public void uploadFile(String filePath) { try { byte[] encoded = Base64.encodeBase64(FileUtils.readFileToByteArray(new File(filePath))); CustomEventFiringWebDriver.uploadFile(new File(filePath).getName(), new String(encoded), SeleniumTestsContextManager.getThreadContext().getRunMode(), SeleniumTestsContextManager.getThreadContext().getSeleniumGridConnector()); // ((JavascriptExecutor) driver).executeScript(CustomEventFiringWebDriver.NON_JS_UPLOAD_FILE_THROUGH_POPUP, new File(filePath).getName(), new String(encoded)); Alert alert = waitForAlert(5); if (alert != null) { alert.accept(); } } catch (IOException e) { throw new ScenarioException( String.format("could not read file to upload %s: %s", filePath, e.getMessage())); } } /** * get the name of the PageObject that made the call * * @param stack : the stacktrace of the caller */ public static String getCallingPage(StackTraceElement[] stack) { String page = null; Class<?> stackClass = null; //find the PageObject Loader for (int i = 0; i < stack.length; i++) { try { stackClass = Class.forName(stack[i].getClassName()); } catch (ClassNotFoundException e) { continue; } if (PageObject.class.isAssignableFrom(stackClass)) { page = stack[i].getClassName(); } } return page; } }