├── .gitignore ├── README.md ├── pom.xml ├── src ├── main │ ├── java │ │ └── com │ │ │ └── jivesoftware │ │ │ └── selenium │ │ │ └── pagefactory │ │ │ └── framework │ │ │ ├── actions │ │ │ ├── AndroidSeleniumActions.java │ │ │ ├── BaseSeleniumActions.java │ │ │ ├── ChromeSeleniumActions.java │ │ │ ├── FirefoxSeleniumActions.java │ │ │ ├── GeneralUtils.java │ │ │ ├── IOSSeleniumActions.java │ │ │ ├── InternetExplorerActions.java │ │ │ ├── SafariSeleniumActions.java │ │ │ ├── SeleniumActions.java │ │ │ └── WebElementHelpers.java │ │ │ ├── browser │ │ │ ├── Browser.java │ │ │ ├── BrowserUtil.java │ │ │ ├── CachedPage.java │ │ │ ├── LocalBrowserBuilder.java │ │ │ ├── MobileBrowserBuilder.java │ │ │ ├── RemoteBrowserBuilder.java │ │ │ ├── mobile │ │ │ │ ├── AndroidMobileBrowser.java │ │ │ │ ├── IOSMobileBrowser.java │ │ │ │ ├── MobileBrowser.java │ │ │ │ ├── MobilePlatformName.java │ │ │ │ └── SwipeableWebDriver.java │ │ │ └── web │ │ │ │ ├── ChromeBrowser.java │ │ │ │ ├── FirefoxBrowser.java │ │ │ │ ├── InternetExplorerBrowser.java │ │ │ │ ├── RemoteBrowser.java │ │ │ │ ├── SafariBrowser.java │ │ │ │ ├── WebBrowser.java │ │ │ │ └── WebBrowserType.java │ │ │ ├── config │ │ │ ├── DefaultTimeouts.java │ │ │ ├── TimeoutType.java │ │ │ └── TimeoutsConfig.java │ │ │ ├── exception │ │ │ ├── InvalidPageUrlException.java │ │ │ ├── JiveWebDriverException.java │ │ │ └── SeleniumActionsException.java │ │ │ ├── pages │ │ │ ├── BaseSubPage.java │ │ │ ├── BaseTopLevelPage.java │ │ │ ├── Page.java │ │ │ ├── PageUtils.java │ │ │ ├── SubPage.java │ │ │ ├── SubPageField.java │ │ │ ├── TopLevelPage.java │ │ │ └── WebPagePath.java │ │ │ └── webservice │ │ │ └── EndpointBuilder.java │ └── resources │ │ └── log4j.xml └── test │ ├── java │ └── com │ │ └── jivesoftware │ │ └── selenium │ │ └── pagefactory │ │ └── framework │ │ ├── CreateWebBrowserTests.java │ │ └── TestSystemProps.java │ └── resources │ └── log4j.xml └── tools └── ci ├── DEFS └── commit ├── commit └── j-version /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.class 3 | *.swp 4 | *.iml 5 | target/ 6 | *.log 7 | *.log* 8 | *.exe 9 | *.pyc 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | jive-selenium-pages-framework 2 | ============================= 3 | 4 |

Jive Selenium Pages Framework

5 | 6 |

Author: Charles Capps

7 |

Contact Email: qa.automators@jivesoftware.com

8 |

Javadoc can be found 9 | here 10 |

11 | 12 |

13 | This framework provides many valuable features for simplifying Selenium Browser testing. 14 | The framework has been used internally at Jive Software with much success. 15 | It simplifies the configuration and creation of Selenium WebDrivers for different browsers. 16 | The framework also provides a Page abstraction for modeling your webapp's pages. 17 |

18 | 19 |

Key Features:

20 | 21 |

Browser configuration and instantiation

22 | 34 | 35 |

Adding jive-selenium-pages-framework as a dependency

36 | This project is in the Maven Central Repository, 37 | so if your project uses maven you can just add this as a dependency with: 38 | 39 |
 40 |     <dependency>
 41 |         <groupId>com.jivesoftware</groupId>
 42 |         <artifactId>jive-selenium-pages-framework</artifactId>
 43 |         <version>1.0.10</version>
 44 |     </dependency>
 45 | 
46 | 47 |

Building

48 | This project uses maven. For maven, you must have JAVA_HOME set to a valid Java installation of Java7 or above. 49 | As long as you have maven 3.0.5 or above installed and Java7 or above, then you should be 50 | able to execute the following: 51 | 52 |
mvn clean install -DskipTests
53 | 54 | to install a new version to your local repo. You can then use it by adding the version you installed to the POM 55 | of any local project. 56 | 57 |

Sample code creating a Browser instance (Chrome)

58 | 59 |
 60 |     // Create a TimeoutsConfig instance. You can also just use TimeoutsConfig.defaultTimeoutsConfig().
 61 |     TimeoutsConfig timeouts = TimeoutsConfig.builder()
 62 |         .clickTimeoutSeconds(2)                  // Timeout waiting for a WebElement to be clickable (used by the framework)
 63 |         .webElementPresenceTimeoutSeconds(5)     // Timeout when polling for a web element to be present (or visible, depending on the method)
 64 |         .pageLoadTimoutSeconds(10)               // Timeout waiting for a new page to load (used by the framework, and to configure underlying WebDriver).
 65 |         .implicitWaitTimeoutMillis(2000)         // Implicit wait timeout used by the underlying WebDriver.
 66 |         .build();
 67 | 
 68 |     // Create a ChromeBrowser
 69 |     Browser browser = LocalBrowserBuilder.getChromeBuilder("http://my.webapp.com/webapp")  // Base URL for testing. 
 70 |                            .withTimeoutsConfig(timeouts)             // TimeoutsConfig created above.
 71 |                            .withBrowserLocale(Locale.US.toString())  // Browser locale
 72 |                            .withStartWindowWidth(1280)               // Starting width for the browser window in pixels
 73 |                            .withStartWindowHeight(1024)              // Starting height for the browser window in pixels
 74 |                            .withBrowserLogLevel(Level.INFO)          // Logging Level for the WebDriver's logs
 75 |                            .withBrowserLogFile("chromedriver.log")   // Path to logfile, only supported for Chrome and IE. 
 76 |                            .build();
 77 |                            
 78 |      // Load a web page
 79 |      TopLevelPage googleHomePage = browser.openPageByUrl("http://google.com");
 80 | 
81 | 82 |

SeleniumActions

83 | 93 | 94 |

Pages

95 | 103 | 104 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | com.jivesoftware 7 | jive-selenium-pages-framework 8 | 1.0.11 9 | jar 10 | jive-selenium-pages-framework 11 | Framework for automated testing using Selenium. Provides easy configuration of WebDrivers with BrowserFactory. Provides a Page abstraction. 12 | https://github.com/charlescapps/jive-selenium-pages-framework 13 | 14 | 15 | The Apache Software License, Version 2.0 16 | http://www.apache.org/licenses/LICENSE-2.0.txt 17 | repo 18 | 19 | 20 | 21 | git:git://github.com:charlescapps/jive-selenium-pages-framework.git 22 | https://github.com/charlescapps/jive-selenium-pages-framework 23 | 24 | 25 | 26 | charlescapps 27 | Charles Capps 28 | charles.capps@jivesoftware.com 29 | 30 | 31 | 32 | 33 | 2.45.0 34 | ossrh 35 | https://oss.sonatype.org/content/repositories/snapshots 36 | ossrh 37 | https://oss.sonatype.org/content/repositories/releases 38 | 39 | 40 | 41 | 42 | 43 | org.apache.httpcomponents 44 | httpclient 45 | 4.3.2 46 | 47 | 48 | 49 | org.apache.httpcomponents 50 | httpcore 51 | 4.3.2 52 | 53 | 54 | 55 | org.slf4j 56 | slf4j-api 57 | 1.7.7 58 | 59 | 60 | 61 | com.google.code.findbugs 62 | jsr305 63 | 3.0.0 64 | 65 | 66 | 67 | com.google.guava 68 | guava 69 | 18.0 70 | 71 | 72 | 73 | org.seleniumhq.selenium 74 | selenium-api 75 | ${seleniumVersion} 76 | 77 | 78 | 79 | org.seleniumhq.selenium 80 | selenium-java 81 | ${seleniumVersion} 82 | 83 | 84 | 85 | org.seleniumhq.selenium 86 | selenium-remote-driver 87 | ${seleniumVersion} 88 | 89 | 90 | 91 | io.appium 92 | java-client 93 | 2.1.0 94 | 95 | 96 | 97 | org.testng 98 | testng 99 | 6.8.8 100 | 101 | 102 | 103 | 104 | org.codehaus.jackson 105 | jackson-mapper-asl 106 | 1.9.13 107 | 108 | 109 | 110 | org.codehaus.jackson 111 | jackson-core-asl 112 | 1.9.13 113 | 114 | 115 | 116 | 117 | 118 | 119 | org.apache.maven.plugins 120 | maven-compiler-plugin 121 | 2.4 122 | 123 | 1.8 124 | 1.8 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | release 135 | 136 | 137 | 138 | 139 | org.apache.maven.plugins 140 | maven-source-plugin 141 | 2.4 142 | 143 | 144 | attach-sources 145 | 146 | jar-no-fork 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | org.apache.maven.plugins 155 | maven-javadoc-plugin 156 | 2.10.2 157 | 158 | 159 | attach-javadocs 160 | 161 | jar 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | sign 173 | 174 | 175 | 176 | 177 | org.apache.maven.plugins 178 | maven-gpg-plugin 179 | 1.5 180 | 181 | 182 | sign-artifacts 183 | verify 184 | 185 | sign 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | central-release 196 | 197 | 198 | 199 | 200 | org.sonatype.plugins 201 | nexus-staging-maven-plugin 202 | 1.6.5 203 | true 204 | 205 | ossrh 206 | https://oss.sonatype.org/ 207 | true 208 | true 209 | true 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | ${snapshotRepoId} 221 | ${snapshotRepoUrl} 222 | 223 | 224 | ${releaseRepoId} 225 | ${releaseRepoUrl} 226 | 227 | 228 | 229 | -------------------------------------------------------------------------------- /src/main/java/com/jivesoftware/selenium/pagefactory/framework/actions/AndroidSeleniumActions.java: -------------------------------------------------------------------------------- 1 | package com.jivesoftware.selenium.pagefactory.framework.actions; 2 | 3 | 4 | import com.jivesoftware.selenium.pagefactory.framework.browser.mobile.AndroidMobileBrowser; 5 | 6 | /** 7 | * Created by Shiran Dadon on 8/11/14. 8 | * 9 | * Selenium Actions for Android Applications 10 | * 11 | * Currently, this is the same as BaseSeleniumActions, as we don't have any need to implement anything differently 12 | * for Android. 13 | */ 14 | public class AndroidSeleniumActions extends BaseSeleniumActions { 15 | 16 | public AndroidSeleniumActions(AndroidMobileBrowser browser) { 17 | super(browser); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/jivesoftware/selenium/pagefactory/framework/actions/ChromeSeleniumActions.java: -------------------------------------------------------------------------------- 1 | package com.jivesoftware.selenium.pagefactory.framework.actions; 2 | 3 | import com.jivesoftware.selenium.pagefactory.framework.browser.web.ChromeBrowser; 4 | 5 | /** 6 | * Selenium Actions for Chrome Browser. 7 | * 8 | * Currently, this is the same as BaseSeleniumActions, as we don't have any need to implement anything differently 9 | * for Chrome. 10 | */ 11 | public class ChromeSeleniumActions extends BaseSeleniumActions { 12 | public ChromeSeleniumActions(ChromeBrowser browser) { 13 | super(browser); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/jivesoftware/selenium/pagefactory/framework/actions/FirefoxSeleniumActions.java: -------------------------------------------------------------------------------- 1 | package com.jivesoftware.selenium.pagefactory.framework.actions; 2 | 3 | import com.jivesoftware.selenium.pagefactory.framework.browser.web.FirefoxBrowser; 4 | 5 | /** 6 | * Selenium Actions for Firefox Browser. 7 | * 8 | * Currently, this is the same as BaseSeleniumActions, as we don't have any need to implement anything differently 9 | * for Firefox. 10 | */ 11 | public class FirefoxSeleniumActions extends BaseSeleniumActions { 12 | public FirefoxSeleniumActions(FirefoxBrowser browser) { 13 | super(browser); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/jivesoftware/selenium/pagefactory/framework/actions/GeneralUtils.java: -------------------------------------------------------------------------------- 1 | package com.jivesoftware.selenium.pagefactory.framework.actions; 2 | 3 | /** 4 | * General Utilities. 5 | * 6 | * So far just trivial utilities for waiting. 7 | * Arbitrary waits are discouraged, but in some cases they are needed. 8 | */ 9 | public class GeneralUtils { 10 | 11 | public static void waitMillis(int millis) { 12 | try { 13 | Thread.sleep(millis); 14 | } catch (InterruptedException e) { 15 | // Don't care. 16 | } 17 | } 18 | 19 | public static void waitSeconds(int seconds) { 20 | try { 21 | Thread.sleep(seconds * 1000); 22 | } catch (InterruptedException e) { 23 | // Don't care. 24 | } 25 | } 26 | 27 | public static void waitOneSecond() { 28 | waitSeconds(1); 29 | } 30 | 31 | public static void waitFiveSeconds() { 32 | waitSeconds(5); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/jivesoftware/selenium/pagefactory/framework/actions/IOSSeleniumActions.java: -------------------------------------------------------------------------------- 1 | package com.jivesoftware.selenium.pagefactory.framework.actions; 2 | 3 | import com.jivesoftware.selenium.pagefactory.framework.browser.mobile.IOSMobileBrowser; 4 | import org.openqa.selenium.WebElement; 5 | import org.openqa.selenium.remote.RemoteWebElement; 6 | 7 | import java.util.HashMap; 8 | 9 | /** 10 | * Created by Shiran Dadon on 8/11/14. 11 | * 12 | * Selenium Actions for iOS Applications 13 | * 14 | * Currently, only scrollTo option is implemented differently for iOS 15 | */ 16 | public class IOSSeleniumActions extends BaseSeleniumActions { 17 | 18 | public IOSSeleniumActions(IOSMobileBrowser browser) { 19 | super(browser); 20 | } 21 | 22 | @Override 23 | public void scrollIntoView(WebElement el) { 24 | boolean elementInView = el.isDisplayed(); 25 | while (!elementInView) { 26 | getBrowser().dragUp(); 27 | elementInView = el.isDisplayed(); 28 | } 29 | HashMap scrollObject = new HashMap(); 30 | String widId = ((RemoteWebElement) el).getId(); 31 | scrollObject.put("element", widId); 32 | getBrowser().getWebDriver().executeScript("mobile: scrollTo", scrollObject); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/jivesoftware/selenium/pagefactory/framework/actions/InternetExplorerActions.java: -------------------------------------------------------------------------------- 1 | package com.jivesoftware.selenium.pagefactory.framework.actions; 2 | 3 | import com.jivesoftware.selenium.pagefactory.framework.browser.web.InternetExplorerBrowser; 4 | import com.jivesoftware.selenium.pagefactory.framework.config.TimeoutType; 5 | import org.openqa.selenium.By; 6 | import org.openqa.selenium.WebDriverException; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | /** 11 | * SeleniumActions class for InternetExplorer. 12 | * There is only one workaround, due to a bug in Selenium 2.42. 13 | */ 14 | public class InternetExplorerActions extends BaseSeleniumActions { 15 | private final static Logger logger = LoggerFactory.getLogger(InternetExplorerActions.class); 16 | 17 | public InternetExplorerActions(InternetExplorerBrowser browser) { 18 | super(browser); 19 | } 20 | 21 | //Workaround for http://code.google.com/p/selenium/issues/detail?id=7524, just for IE 22 | @Override 23 | public void verifyElementInvisible(By locator, TimeoutType timeout) { 24 | try { 25 | super.verifyElementInvisible(locator, timeout); 26 | } catch (WebDriverException e) { 27 | logger.debug("WebDriverException in InternetExplorerActions#verifyElementInvisible: " + e.getMessage(), e); 28 | // The issue happens when the element is removed from the DOM, so just try again and it should work 29 | super.verifyElementInvisible(locator, timeout); 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/jivesoftware/selenium/pagefactory/framework/actions/SafariSeleniumActions.java: -------------------------------------------------------------------------------- 1 | package com.jivesoftware.selenium.pagefactory.framework.actions; 2 | 3 | import com.jivesoftware.selenium.pagefactory.framework.browser.web.SafariBrowser; 4 | 5 | /** 6 | * Selenium Actions for Chrome Browser. 7 | * 8 | * Currently, this is the same as BaseSeleniumActions, as we don't have any need to implement anything differently 9 | * for Chrome. 10 | */ 11 | public class SafariSeleniumActions extends BaseSeleniumActions { 12 | public SafariSeleniumActions(SafariBrowser browser) { 13 | super(browser); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/jivesoftware/selenium/pagefactory/framework/actions/SeleniumActions.java: -------------------------------------------------------------------------------- 1 | package com.jivesoftware.selenium.pagefactory.framework.actions; 2 | 3 | import com.google.common.base.Function; 4 | import com.google.common.base.Predicate; 5 | import com.jivesoftware.selenium.pagefactory.framework.browser.Browser; 6 | import com.jivesoftware.selenium.pagefactory.framework.config.TimeoutType; 7 | import com.jivesoftware.selenium.pagefactory.framework.config.TimeoutsConfig; 8 | import com.jivesoftware.selenium.pagefactory.framework.exception.JiveWebDriverException; 9 | import com.jivesoftware.selenium.pagefactory.framework.exception.SeleniumActionsException; 10 | import com.jivesoftware.selenium.pagefactory.framework.pages.SubPage; 11 | import com.jivesoftware.selenium.pagefactory.framework.pages.TopLevelPage; 12 | import org.openqa.selenium.By; 13 | import org.openqa.selenium.TimeoutException; 14 | import org.openqa.selenium.WebElement; 15 | import org.openqa.selenium.interactions.Actions; 16 | import org.openqa.selenium.support.ui.ExpectedCondition; 17 | 18 | import javax.annotation.Nonnull; 19 | import javax.annotation.Nullable; 20 | import java.util.List; 21 | 22 | /** 23 | *

24 | * This interface represents actions that one can perform with a Browser interacting with a web page. 25 | * Upon creation, a Browser instance will instantiate the correct type of SeleniumActions. 26 | * Pages are endowed with a SeleniumActions when created via standard methods such as 27 | * {@link Browser#loadTopLevelPage}. 28 | *

29 | *

30 | * Throughout, the {@link com.jivesoftware.selenium.pagefactory.framework.config.TimeoutType} enum is used. 31 | * This works as follows: if you provide DEFAULT as 32 | * the argument to a method, then the default Timeout for the given context is used. For example, if you are 33 | * calling "findElementContainingTextWithRefresh" then using DEFAULT will result in TimeoutType.POLLING_WITH_REFRESH_TIMEOUT 34 | * being used, because that method refreshes the page and polls until it finds an element containing the given text. 35 | *

36 | *

37 | * If you use anything other than TimeoutType.DEFAULT, then you are overriding the timeout for the given method. 38 | * This is still restricted to the timeouts defined by the enum, so that timeouts are configurable and we have 39 | * fewer magic numbers in our code. 40 | *

41 | */ 42 | public interface SeleniumActions { 43 | 44 | /** 45 | * Wait for a javascript confirmation dialog to be present, then accept it. 46 | */ 47 | void acceptAlert(TimeoutType timeout); 48 | 49 | /** 50 | * Clear text from an input element. 51 | * 52 | * @param locator - locator defining the input element 53 | * @return - the input element 54 | */ 55 | WebElement clearText(By locator); 56 | 57 | WebElement clearText(WebElement el); 58 | 59 | /** 60 | * Click the web element defined by the given CSS, with proper waiting until the element is clickable. 61 | */ 62 | WebElement click(By locator, TimeoutType timeout); 63 | 64 | /** 65 | * Click the given web element, with proper waiting until the element is clickable. 66 | */ 67 | WebElement click(WebElement el, TimeoutType timeout); 68 | 69 | /** 70 | * Click a button or link, then load a SubPage that will be added to the DOM after the click. 71 | * 72 | * @param locatorToClick - locator of the element to be clicked 73 | * @param pageClass - class of the {@link SubPage} object to be returned. 74 | * @param timeout - timeout object indicating how long to wait, or just TIMEOUT.DEFAULT to use the default 75 | * @return - the fully initialized {@link SubPage} object 76 | */ 77 | T clickAndLoadSubPage(By locatorToClick, Class pageClass, TimeoutType timeout); 78 | 79 | T clickAndLoadSubPage(WebElement el, Class pageClass, TimeoutType timeout); 80 | 81 | /** 82 | * Click a button or link that redirects the browser to a new Page, then load the new Page and return it. 83 | * 84 | * @param locatorToClick - locator of the element to be clicked 85 | * @param pageClass - Class of the {@link TopLevelPage} object to be returned. 86 | * @param timeout - timeout object indicating how long to wait, or just TIMEOUT.DEFAULT to use the default 87 | * @return - the fully initialized {@link TopLevelPage} object 88 | */ 89 | T clickAndLoadTopLevelPage(By locatorToClick, Class pageClass, TimeoutType timeout); 90 | 91 | T clickAndLoadTopLevelPage(WebElement el, Class pageClass, TimeoutType timeout); 92 | 93 | /** 94 | * Click a web element defined by CSS cssToClick, then click a popup that is required to be displayed after clicking. 95 | * 96 | * @param locatorToClick - locator for the element to be clicked 97 | * @param popoverLocator - locator for the popover element that must be present after clicking 98 | */ 99 | void clickAndSelectFromList(By locatorToClick, By popoverLocator); 100 | 101 | void clickAndSelectFromList(WebElement clickable, By popoverLocator); 102 | 103 | /** 104 | * Click a web element, then verify another element is NOT present on the DOM (so also not visible). 105 | * @param locatorToClick the locator to click 106 | * @param locatorToVerifyNotPresent the locator to verify not present after clicking. 107 | * @param timeout the timeout type 108 | */ 109 | void clickAndVerifyNotPresent(By locatorToClick, By locatorToVerifyNotPresent, TimeoutType timeout); 110 | 111 | void clickAndVerifyNotPresent(WebElement elToClick, By locatorToVerifyNotPresent, TimeoutType timeout); 112 | 113 | /** 114 | * Click a web element, then verify another element is NOT present on the DOM (so also not visible). 115 | */ 116 | void clickAndVerifyNotVisible(By locatorToClick, By locatorToVerifyNotVisible, TimeoutType timeout); 117 | 118 | void clickAndVerifyNotVisible(WebElement elToClick, By locatorToVerifyNotVisible, TimeoutType timeout); 119 | 120 | /** 121 | * Click a web element, then verify another element is present on the DOM (not necessarily visible). 122 | * 123 | * @return - the WebElement we verified was present 124 | */ 125 | WebElement clickAndVerifyPresent(By locatorToClick, By locatorToVerifyPresent, TimeoutType timeout); 126 | 127 | WebElement clickAndVerifyPresent(WebElement elToClick, By locatorToVerifyPresent, TimeoutType timeout); 128 | 129 | /** 130 | * Click a web element (if it's unselected), then verify it is selected (checked for a checkbox, etc.) 131 | * 132 | * @return - the WebElement we verified was selected 133 | */ 134 | WebElement clickAndVerifySelected(By locatorToClick, TimeoutType timeout); 135 | 136 | WebElement clickAndVerifySelected(WebElement elToClick, TimeoutType timeout); 137 | 138 | /** 139 | * Click a web element, then verify it is selected (checked for a checkbox, etc.) 140 | * 141 | * @return - the WebElement we verified was selected 142 | */ 143 | WebElement clickAndVerifyNotSelected(By locatorToClick, TimeoutType timeout); 144 | 145 | WebElement clickAndVerifyNotSelected(WebElement elToClick, TimeoutType timeout); 146 | 147 | /** 148 | * Click a web element, then verify another element is present on the DOM (not necessarily visible). 149 | * 150 | * @return - the WebElement we verified was present 151 | */ 152 | WebElement clickAndVerifyVisible(By locatorToClick, By locatorToVerifyVisible, TimeoutType timeout); 153 | 154 | WebElement clickAndVerifyVisible(WebElement elToClick, By locatorToVerifyVisible, TimeoutType timeout); 155 | 156 | /** 157 | * Click without polling for the element to be clickable or waiting until it's ready. 158 | * Uses the implicit wait timeout built-in to Selenium. 159 | * 160 | * @throws JiveWebDriverException - if the element isn't clickable when this method is called. 161 | */ 162 | WebElement clickNoWait(By locator) throws JiveWebDriverException; 163 | 164 | /** 165 | * Wait for a javascript confirmation dialog to be present, then dismiss it. 166 | */ 167 | void dismissAlert(TimeoutType timeout); 168 | 169 | /** 170 | * @return true if-and-only-if the web element found by the given locator has the CSS class "cssClass" 171 | */ 172 | boolean doesElementHaveClass(By locator, String locatorClass); 173 | 174 | /** 175 | * Enter the given text into the input defined by inputCSS, one character at a time. 176 | * At each step, verify the previous popup was removed from the DOM, and find the new popup. 177 | * Then see if there is a popup containing the required text on the page. If so, click it and return. 178 | * 179 | * @param inputLocator - locator for the input element 180 | * @param text - text you are entering into the input element 181 | * @param popoverLocator - locator for the popup element containing required text, or list element if there multiple 182 | * @param requiredPopupText - text required to be present in popup element defined by popupItemCss 183 | */ 184 | void enterTextForAutoCompleteAndSelectFirstMatch(By inputLocator, String text, By popoverLocator, 185 | String requiredPopupText); 186 | 187 | /** 188 | * Same as above, but enter minChars characters before checking for the popup to exist. 189 | */ 190 | void enterTextForAutoCompleteAndSelectFirstMatch(By inputLocator, int minChars, String text, By popoverLocator, 191 | String requiredPopupText); 192 | 193 | /** 194 | * Execute the given javascript synchronously and return the result. 195 | * 196 | * @return - see Selenium docs for what can be returned here, probably just Integer, Boolean, Double, or null 197 | */ 198 | Object executeJavascript(String script); 199 | 200 | /** 201 | * Immediately return true or false as to whether a web element exists on the page. 202 | */ 203 | boolean exists(By locator); 204 | 205 | boolean exists(By locator, WebElement parentEl); 206 | 207 | /** 208 | * Find the first element located by 'parentLocator' that has at least 1 child element located by the relative locator 'childLocator' 209 | * 210 | * @param parentLocator - locator to find parent elements 211 | * @param childLocator - relative locator to find child elements inside a parent element 212 | * @return - parent element that have at least 1 child element located by 'childLocator', or null if there are none. 213 | */ 214 | @Nullable 215 | WebElement findElementContainingChild(final By parentLocator, final By childLocator); 216 | 217 | /** 218 | * Search for a WebElement located by 'locator' that has a child element located in its sub-tree 219 | * located by 'childLocator'. 220 | * 221 | * Poll repeatedly until a timeout occurs, but do not refresh the page. 222 | * 223 | * @param parentLocator - parent locator 224 | * @param childLocator - a locator relative to the parent to find the child element 225 | * @return - the parent element located by the 'locator' param 226 | */ 227 | @Nonnull 228 | WebElement findElementContainingChildWithWait(final By parentLocator, final By childLocator, TimeoutType timeout); 229 | 230 | @Nullable 231 | WebElement findElementContainingText(By locator, String text); 232 | 233 | /** 234 | * @param caseSensitive whether to require that the element contains this exact text, case-sensitively, or not. 235 | * @return 236 | */ 237 | @Nullable 238 | WebElement findElementContainingText(By locator, String text, boolean caseSensitive); 239 | 240 | /** 241 | * @throws TimeoutException if no such element is found before timeout is reached. 242 | */ 243 | @Nonnull 244 | WebElement findElementContainingTextWithRefresh(final By locator, final String text, TimeoutType timeout); 245 | 246 | /** 247 | * @param caseSensitive whether to require that the element contains this exact text, case-sensitively, or not. 248 | * 249 | * @throws TimeoutException if no such element is found before timeout is reached. 250 | */ 251 | @Nonnull 252 | WebElement findElementContainingTextWithRefresh(final By locator, final String text, boolean caseSensitive, TimeoutType timeout); 253 | 254 | /** 255 | * @param caseSensitive whether to require that the element contains this exact text, case-sensitively, or not. 256 | * 257 | * @throws TimeoutException if no such element is found before timeout is reached. 258 | */ 259 | @Nonnull 260 | WebElement findElementContainingTextWithWait(final By locator, final String text, boolean caseSensitive, TimeoutType timeout); 261 | 262 | /** 263 | * @throws TimeoutException if no such element is found before timeout is reached. 264 | */ 265 | @Nonnull 266 | WebElement findElementContainingTextWithWait(By locator, String text, TimeoutType timeout); 267 | 268 | /** 269 | * @throws TimeoutException if no such element is found before timeout is reached. 270 | */ 271 | @Nonnull 272 | WebElement findElementWithRefresh(By locator, TimeoutType timeout); 273 | 274 | /** 275 | * Find elements located by 'parentLocator' that have child elements located by the relative locator 'childLocator' 276 | * 277 | * @param parentLocator - locator to find parent elements 278 | * @param childLocator - relative locator to find child elements inside a parent element 279 | * @return - parent elements that have at least 1 child element located by 'childLocator' 280 | */ 281 | @Nonnull 282 | List findElementsContainingChild(final By parentLocator, final By childLocator); 283 | 284 | /** 285 | * Waits until finding at least one element located by "childLocator" inside an element located by "parentLocator" 286 | * 287 | * @throws TimeoutException if no such parent-child pairs are found before timeout is reached. 288 | */ 289 | @Nonnull 290 | List findElementsContainingChildWithWait(final By parentLocator, final By childLocator, TimeoutType timeout); 291 | 292 | @Nullable 293 | WebElement findVisibleElement(By locator); 294 | 295 | @Nullable 296 | WebElement findVisibleElementContainingText(By locator, String text); 297 | 298 | @Nullable 299 | WebElement findVisibleElementContainingText(By locator, String text, boolean caseSensitive); 300 | 301 | /** 302 | * @throws TimeoutException if a matching visible element isn't found after the timeout. 303 | * @return a visible element matching the By given 304 | */ 305 | @Nonnull 306 | WebElement findVisibleElementWithRefresh(By locator, TimeoutType timeout); 307 | 308 | /** 309 | * @param text the text that much be present in the element. You can pass in null or the empty string to ignore this value. 310 | * @throws TimeoutException if a matching visible element (containing the given text) isn't found after the timeout. 311 | * @return a visible element matching the By given 312 | */ 313 | @Nonnull 314 | WebElement findVisibleElementContainingTextWithRefresh(final By locator, final String text, TimeoutType timeout); 315 | 316 | /** 317 | * @throws TimeoutException if a matching visible element isn't found after the timeout. 318 | * @return a visible element matching the By given 319 | */ 320 | @Nonnull 321 | WebElement findVisibleElementWithWait(final By locator, TimeoutType timeout); 322 | 323 | /** 324 | * @param text the text that much be present in the element. You can pass in null or the empty string to ignore this value. 325 | * @throws TimeoutException if a matching visible element (containing the given text) isn't found after the timeout. 326 | * @return a visible element matching the By given 327 | */ 328 | @Nonnull 329 | WebElement findVisibleElementContainingTextWithWait(final By locator, final String text, TimeoutType timeout); 330 | 331 | @Nonnull 332 | List findVisibleElements(By locator); 333 | 334 | @Nonnull 335 | List findVisibleElementsContainingText(By locator, String text); 336 | 337 | @Nonnull 338 | List findVisibleElementsContainingText(By locator, String text, boolean caseSensitive); 339 | 340 | /** 341 | * Get a {@link org.openqa.selenium.interactions.Actions} object--used to build sequences of actions like clicking + dragging 342 | */ 343 | Actions getActionsBuilder(); 344 | 345 | B getBrowser(); 346 | 347 | void setBrowser(Browser browser); 348 | 349 | @Nullable 350 | WebElement getChildElement(By locator, WebElement parentEl); 351 | 352 | @Nonnull 353 | WebElement getChildElementWithWait(By locator, WebElement parentEl); 354 | 355 | @Nonnull 356 | List getChildElements(By locator, WebElement parentEl); 357 | 358 | /** 359 | * Get the current URL that the browser has open. 360 | * I'm not sure what Appium WebDrivers return for mobile testing (?) 361 | */ 362 | String getCurrentURL(); 363 | 364 | /** 365 | * Immediately try to return a WebElement without any implicit or explicit waiting. 366 | * 367 | * @return - the WebElement, or null if not present. 368 | */ 369 | 370 | @Nullable 371 | WebElement getElement(By locator); 372 | 373 | /** 374 | * Get a WebElement using the implicit wait configured for the Selenium WebDriver. 375 | * 376 | * @return the WebElement when found. Null is never returned. 377 | * @throws RuntimeException - if the web element isn't present after waiting. 378 | */ 379 | 380 | @Nonnull 381 | WebElement getElementWithWait(By locator); 382 | 383 | List getElements(By locator); 384 | 385 | WebElement getParentElement(WebElement el); 386 | 387 | //////////////////////////////////////Timeouts////////////////////////////////////////////// 388 | TimeoutsConfig getTimeoutsConfig(); 389 | 390 | String getWebPageReadyState() throws Exception; 391 | 392 | WebElement inputText(By locator, String text); 393 | 394 | WebElement inputText(@Nonnull WebElement el, String text); 395 | 396 | WebElement inputTextAndSelectFromList(WebElement inputField, String value, By popoverLocator) throws SeleniumActionsException; 397 | 398 | WebElement inputTextAndSelectFromList(WebElement inputField, String value, By popoverLocator, int withRetryCount) throws SeleniumActionsException; 399 | 400 | WebElement inputTextSlowly(By locator, String text); 401 | 402 | WebElement inputTextSlowly(WebElement el, String text); 403 | 404 | WebElement inputTextSlowlyAndSelectFromList(WebElement inputField, String value, By popoverLocator) throws SeleniumActionsException; 405 | 406 | WebElement inputTextSlowlyAndSelectFromList(WebElement inputField, String value, By popoverLocator, int withRetryCount) throws SeleniumActionsException; 407 | 408 | /** 409 | * Enter text into the active tiny MCE editor. 410 | */ 411 | void inputTinyMceText(String text); 412 | 413 | /** 414 | * Return immediately with an answer as to whether an element is clickable. 415 | * 416 | * @return - true if the element is present and clickable, false otherwise. 417 | */ 418 | boolean isClickable(By locator); 419 | 420 | boolean isClickable(WebElement el); 421 | 422 | boolean isSelected(By locator); 423 | 424 | boolean isSelected(WebElement css); 425 | 426 | /** 427 | * Return immediately with an answer as to whether an element is visible. 428 | * 429 | * @return - true if the element is present and visible, false otherwise. 430 | * See Selenium's docs for the definition of visible, it has to be on the page, scrolled into view, 431 | * have a height and width greater than 0, etc. 432 | */ 433 | boolean isVisible(By locator); 434 | 435 | boolean isVisible(WebElement css); 436 | 437 | T loadSubPage(Class pageClass); 438 | 439 | /** 440 | * Returns a Page object with initialized WebElements that is a valid page class for the currently open page in the web driver. 441 | * 442 | * @param pageClass - a Class representing the type of page to be initialized. 443 | */ 444 | T loadTopLevelPage(Class pageClass); 445 | 446 | /** 447 | * Scroll to the top of the page 448 | */ 449 | void scrollToTop(); 450 | 451 | /** 452 | * Scroll so that the element is in the middle of the page. 453 | */ 454 | void scrollIntoView(By locator); 455 | 456 | void scrollIntoView(WebElement el); 457 | 458 | /** 459 | * Scroll the given element with a scroll bar defined by parentCSS so that the web element given by css is in view 460 | */ 461 | void scrollIntoView(By scrollContainerLocator, By locator); 462 | 463 | void scrollIntoView(By scrollContainerLocator, WebElement el); 464 | 465 | void verifyElementContainsText(By locator, String text, TimeoutType timeout); 466 | 467 | WebElement verifyElementDoesNotHaveClass(final By locator, final String locatorClass, TimeoutType timeout); 468 | 469 | WebElement verifyElementHasClass(By locator, String locatorClass, TimeoutType timeout); 470 | 471 | void verifyElementInvisible(By locator, TimeoutType timeout); 472 | 473 | void verifyElementNotPresented(By locator, TimeoutType timeout); 474 | 475 | WebElement verifyElementNotSelected(By locator, TimeoutType timeout); 476 | 477 | WebElement verifyElementNotSelected(WebElement el, TimeoutType timeout); 478 | 479 | WebElement verifyElementPresented(By locator, TimeoutType timeout); 480 | 481 | /** 482 | * Verify the given WebElement becomes stale (removed from the DOM). 483 | * 484 | * @param element - element we expect to be removed from the DOM 485 | * @param timeout - timeout type, defaults to the webElementPresenceTimeout (typically 5 seconds). 486 | */ 487 | void verifyElementRemoved(WebElement element, TimeoutType timeout); 488 | 489 | WebElement verifyElementSelected(By locator, TimeoutType timeout); 490 | 491 | WebElement verifyElementSelected(WebElement el, TimeoutType timeout); 492 | 493 | /** 494 | * Verify that the element found with the given By is visible, then returns this element. 495 | * 496 | * This method assumes that the given By uniquely defines an element. It may act unexpectedly if 497 | * there are multiple elements on the page matching the By, some visible and some invisible. 498 | * 499 | * @throws TimeoutException if the element is not found 500 | * @return - the visible, matching web element 501 | * @see #verifyAnyElementVisible 502 | */ 503 | WebElement verifyElementVisible(By locator, TimeoutType timeout); 504 | 505 | /** 506 | * Verifies that there exists any element on the page that matches the given By, and is visible. 507 | * Returns the first such element found. 508 | * 509 | * This differs from "verifyElementVisible", because that method uses the Selenium "ExpectedConditions.visibilityOfElementLocatedBy()", which 510 | * requires that the *first* element matching the given By is visible. 511 | * @return - the found WebElement 512 | * @see #verifyElementVisible 513 | */ 514 | WebElement verifyAnyElementVisible(By locator, TimeoutType timeout); 515 | 516 | void verifyElementWithTextIsInvisible(By locator, String text, TimeoutType timeout); 517 | 518 | void verifyElementWithTextNotPresented(By locator, String text, TimeoutType timeout); 519 | 520 | WebElement verifyPageRefreshed(WebElement elementFromBeforeRefresh, By locatorAfterRefresh, TimeoutType timeout); 521 | 522 | /** 523 | * Wait for the given symbol to be defined AND non-null in javascript 524 | */ 525 | void waitForJavascriptSymbolToBeDefined(String symbol, TimeoutType timeout); 526 | 527 | void waitForJavascriptSymbolToHaveValue(String symbol, String value, TimeoutType timeout); 528 | 529 | /** 530 | * Wait for the HTML of a page to be stable, by verifying the length of the HTML doesn't change for a second. 531 | * @see com.thoughtworks.selenium.webdriven.commands.WaitForPageToLoad#getLengthCheckingWait(org.openqa.selenium.WebDriver) 532 | * 533 | * This will probably will only be useful for ordinary WebBrowsers (not mobile) at the moment. 534 | */ 535 | void waitForPageToBeStable(TimeoutType timeout); 536 | 537 | /** 538 | * Wait for tinyMCE.activeEditor.initialized to be true, see Tiny MCE documentation online for why. 539 | */ 540 | void waitForTinyMceToBeReady(); 541 | 542 | void waitForWebPageReadyStateToBeComplete(); 543 | 544 | T waitOnExpectedCondition(ExpectedCondition expectedCondition, String message, TimeoutType timeout); 545 | 546 | /** 547 | * Method to simplify general waiting code in Pages and Keywords. Takes a function and waits until the return value is non-null. 548 | **/ 549 | V waitOnFunction(Function function, T input, String message, TimeoutType timeout); 550 | 551 | /** 552 | * Wait on a Predicate on a TopLevelPage class until it returns true. 553 | * Each iteration, the page is refreshed, then the new TopLevelPage object is passed in to the Predicate.apply() method. 554 | * 555 | * @return - the last TopLevelPage object after the Predicate returned true. 556 | */ 557 | T waitOnPagePredicateWithRefresh(Predicate predicate, Class pageClass, String message, TimeoutType timeout); 558 | 559 | /* Method to simplify general waiting code in Pages and Keywords. Takes a predicate and waits until it returns true.*/ 560 | void waitOnPredicate(Predicate predicate, T input, String message, TimeoutType timeout); 561 | 562 | /* Same, but a helper for predicates that don't require an Input, because they use closure to interact with a containing class. */ 563 | void waitOnPredicate(Predicate predicate, String message, TimeoutType timeout); 564 | 565 | /* Same, but refresh the page after each time the predicate is checked. */ 566 | void waitOnPredicateWithRefresh(Predicate predicate, T input, String message, TimeoutType timeout); 567 | 568 | /* Same, but no input is required. */ 569 | void waitOnPredicateWithRefresh(Predicate predicate, String message, TimeoutType timeout); 570 | 571 | WebElement waitUntilClickable(By locator, TimeoutType timeout); 572 | 573 | WebElement waitUntilClickable(WebElement el, TimeoutType timeout); 574 | } 575 | -------------------------------------------------------------------------------- /src/main/java/com/jivesoftware/selenium/pagefactory/framework/actions/WebElementHelpers.java: -------------------------------------------------------------------------------- 1 | package com.jivesoftware.selenium.pagefactory.framework.actions; 2 | 3 | import com.google.common.collect.Lists; 4 | import org.openqa.selenium.WebElement; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * Static helper methods that don't require a WebDriver instance belong here. 10 | */ 11 | public class WebElementHelpers { 12 | /** 13 | * Determine whether the given WebElement has the given Class 14 | * 15 | * @param webElement element to check 16 | * @param className to search for 17 | * @return boolean 18 | */ 19 | public static boolean webElementHasClass(WebElement webElement, String className) { 20 | return getClasses(webElement).contains(className); 21 | } 22 | 23 | /** 24 | * Get the classes of a given WebElement. Split on whitespace in case it's possible to have multiple spaces in the 25 | * "class" attribute of an HTML element. 26 | * 27 | * @param webElement - the WebElement to return a list of classes for. 28 | * @return - a List<String> of the classes for the given WebElement 29 | */ 30 | public static List getClasses(WebElement webElement) { 31 | if (webElement.getAttribute("class") == null) { 32 | return Lists.newArrayList(); 33 | } 34 | return Lists.newArrayList(webElement.getAttribute("class").split("\\s+")); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/jivesoftware/selenium/pagefactory/framework/browser/Browser.java: -------------------------------------------------------------------------------- 1 | package com.jivesoftware.selenium.pagefactory.framework.browser; 2 | 3 | import com.google.common.base.Preconditions; 4 | import com.jivesoftware.selenium.pagefactory.framework.actions.SeleniumActions; 5 | import com.jivesoftware.selenium.pagefactory.framework.browser.web.WebBrowserType; 6 | import com.jivesoftware.selenium.pagefactory.framework.config.TimeoutsConfig; 7 | import com.jivesoftware.selenium.pagefactory.framework.exception.JiveWebDriverException; 8 | import com.jivesoftware.selenium.pagefactory.framework.pages.PageUtils; 9 | import com.jivesoftware.selenium.pagefactory.framework.pages.SubPage; 10 | import com.jivesoftware.selenium.pagefactory.framework.pages.TopLevelPage; 11 | import org.apache.commons.io.FileUtils; 12 | import org.openqa.selenium.OutputType; 13 | import org.openqa.selenium.TakesScreenshot; 14 | import org.openqa.selenium.WebDriver; 15 | import org.openqa.selenium.remote.DesiredCapabilities; 16 | import org.slf4j.Logger; 17 | import org.slf4j.LoggerFactory; 18 | 19 | import java.io.File; 20 | import java.io.IOException; 21 | import java.net.URI; 22 | import java.util.Objects; 23 | import java.util.Optional; 24 | 25 | /** 26 | * Created by shirand on 8/12/14. 27 | */ 28 | public abstract class Browser { 29 | private static final Logger logger = LoggerFactory.getLogger(Browser.class); 30 | protected D webDriver; 31 | protected String baseTestUrl; 32 | protected TimeoutsConfig timeouts; 33 | protected Optional optionalCachedPage = Optional.empty(); 34 | protected static final PageUtils PAGE_UTILS = new PageUtils(); 35 | 36 | protected Browser(String baseTestUrl, TimeoutsConfig timeoutsConfig) { 37 | this.baseTestUrl = Preconditions.checkNotNull(baseTestUrl); 38 | this.timeouts = timeoutsConfig; 39 | } 40 | 41 | public abstract WebBrowserType getBrowserType(); 42 | 43 | public abstract SeleniumActions getActions(); 44 | 45 | public abstract DesiredCapabilities getDesiredCapabilities(); 46 | 47 | public abstract void initializeBrowser() throws JiveWebDriverException; 48 | 49 | public String getBaseTestUrl() { 50 | return baseTestUrl; 51 | } 52 | 53 | public void setBaseTestUrl(String baseTestUrl) { 54 | this.baseTestUrl = baseTestUrl; 55 | } 56 | 57 | public TimeoutsConfig getTimeouts() { 58 | return timeouts; 59 | } 60 | 61 | protected abstract D createWebDriver() throws JiveWebDriverException; 62 | 63 | public D getWebDriver() { 64 | return webDriver; 65 | } 66 | 67 | public long getPageTimeoutSeconds() { 68 | return timeouts.getPageLoadTimeoutSeconds(); 69 | } 70 | 71 | public long getImplicitWaitTimeoutMillis() { 72 | return timeouts.getImplicitWaitTimeoutMillis(); 73 | } 74 | 75 | public Optional getOptionalCachedPage() { 76 | return optionalCachedPage; 77 | } 78 | 79 | /** 80 | * Invalidate cached page, and return a fresh TopLevelPage with newly initialized WebElements. 81 | * 82 | * This method does not do a Browser Refresh of the page. 83 | * 84 | * It does: 85 | * Invalidate the cache. 86 | * Initialize the current page again by loading webelements and running page load hooks 87 | * 88 | * @param pageClass - the class of the current Page 89 | * @return the new instance of a TopLevelPage 90 | */ 91 | public T reloadTopLevelPage(Class pageClass) { 92 | invalidateCachedPage(); 93 | return loadTopLevelPage(pageClass); 94 | } 95 | 96 | /** 97 | * Load a sub page. No caching is used for {@link com.jivesoftware.selenium.pagefactory.framework.pages.SubPage}'s. 98 | * 99 | * @param pageClass - the class of the SubPage that is currently present on the DOM in the browser to load. 100 | */ 101 | public T loadSubPage(Class pageClass) { 102 | return PAGE_UTILS.loadCurrentPage(pageClass, webDriver, getActions()); 103 | } 104 | 105 | /** 106 | * If the current page is still valid, and the URL hasn't changed, and the 107 | * class given as input is assignable from the cached page, 108 | * THEN return the cached page and avoid re-initializing web elements and running page hooks. 109 | * Otherwise, invalidate the cache and load as normal. 110 | * 111 | * @param pageClass - the class of the current Page 112 | */ 113 | public T loadTopLevelPage(Class pageClass) { 114 | if (shouldUseCachedPage(pageClass)) { 115 | logger.info("CACHE HIT: Fetching page of type " + pageClass.getSimpleName() + " from the Page Cache"); 116 | // This cast is safe, because we check in shouldUseCachedPage 117 | return (T) optionalCachedPage.get().getCachedPage(); 118 | } 119 | logger.info("Loading page of type " + pageClass.getSimpleName()); 120 | // If the page wasn't valid, then invalidate the cache. 121 | runLeavePageHook(); 122 | invalidateCachedPage(); 123 | 124 | // First load the page without the page load hook so that we can store the failing page in the cache 125 | T page = PAGE_UTILS.loadCurrentPageWithoutPageLoadHook(pageClass, webDriver, getActions()); 126 | setCachedPage(page); 127 | 128 | // Next, run page load hook and sub-page load hooks 129 | page.pageLoadHook(); 130 | PAGE_UTILS.runPageLoadHooksForSubPages(page, getActions()); 131 | 132 | return page; 133 | } 134 | 135 | 136 | /** 137 | * Save a screenshot in PNG format to given file name. 138 | * 139 | * @param filename 140 | * @return - a File representing the saved screenshot. 141 | */ 142 | public File saveScreenshotToFile(String filename) { 143 | TakesScreenshot screenshotDriver; 144 | screenshotDriver = ((TakesScreenshot) getWebDriver()); 145 | File scrFile = screenshotDriver.getScreenshotAs(OutputType.FILE); 146 | 147 | // Now you can do whatever you need to do with it, for example copy somewhere 148 | File outFile = new File(filename); 149 | try { 150 | FileUtils.copyFile(scrFile, outFile); 151 | } catch (IOException e) { 152 | logger.error("Error saving screenshot!", e); 153 | } 154 | return outFile; 155 | } 156 | 157 | public void invalidateCachedPage() { 158 | optionalCachedPage = Optional.empty(); 159 | } 160 | 161 | //--------------Private helpers------------ 162 | protected void setCachedPage(TopLevelPage p) { 163 | if (getBrowserType()!=WebBrowserType.MOBILE) { 164 | final String url = webDriver.getCurrentUrl(); 165 | CachedPage cachedPage = new CachedPage(url, p); 166 | optionalCachedPage = Optional.of(cachedPage); 167 | logger.debug("Set cached page of type {} with URL {}", p.getClass().getSimpleName(), url); 168 | } 169 | } 170 | 171 | private boolean shouldUseCachedPage(Class pageClass) { 172 | if (!optionalCachedPage.isPresent()) { 173 | return false; 174 | } 175 | CachedPage cachedPage = optionalCachedPage.get(); 176 | 177 | // The cached page must be an instance of the required page class. 178 | if (!pageClass.isInstance(cachedPage.getCachedPage())) { 179 | return false; 180 | } 181 | 182 | try { 183 | URI currentURI = URI.create(webDriver.getCurrentUrl()); 184 | URI cachedURI = URI.create(cachedPage.getUrl()); 185 | 186 | // Hosts must be equal 187 | if (!Objects.equals(currentURI.getHost(), cachedURI.getHost())) { 188 | return false; 189 | } 190 | 191 | // Paths must be equal 192 | if (!Objects.equals(currentURI.getPath(), cachedURI.getPath())) { 193 | return false; 194 | } 195 | 196 | } catch (Exception e) { 197 | logger.debug("Error constructing URIs from the current webdriver URL", e); 198 | return false; 199 | } 200 | 201 | return true; 202 | } 203 | 204 | public void runLeavePageHook() { 205 | if (optionalCachedPage.isPresent()) { 206 | optionalCachedPage.get().getCachedPage().leavePageHook(); 207 | } 208 | } 209 | 210 | /** 211 | * Refresh the current page, without giving back a newly initialized Page object. 212 | */ 213 | public abstract void refreshPage(); 214 | 215 | /** 216 | * @param pageClass - the class of the expected Page after refreshing. 217 | */ 218 | public abstract T refreshPage(Class pageClass); 219 | 220 | public void quit() { 221 | logger.info("Quitting WebDriver: {}", webDriver); 222 | webDriver.quit(); 223 | logger.info("SUCCESS - quit WebDriver: {}", webDriver); 224 | } 225 | } -------------------------------------------------------------------------------- /src/main/java/com/jivesoftware/selenium/pagefactory/framework/browser/BrowserUtil.java: -------------------------------------------------------------------------------- 1 | package com.jivesoftware.selenium.pagefactory.framework.browser; 2 | 3 | import com.google.common.base.Throwables; 4 | import com.jivesoftware.selenium.pagefactory.framework.browser.web.RemoteBrowser; 5 | import com.thoughtworks.selenium.Wait; 6 | import com.thoughtworks.selenium.webdriven.commands.WaitForPageToLoad; 7 | import org.apache.http.HttpHost; 8 | import org.apache.http.HttpResponse; 9 | import org.apache.http.impl.client.DefaultHttpClient; 10 | import org.apache.http.message.BasicHttpEntityEnclosingRequest; 11 | import org.codehaus.jackson.JsonNode; 12 | import org.codehaus.jackson.map.ObjectMapper; 13 | import org.openqa.selenium.remote.CommandExecutor; 14 | import org.openqa.selenium.remote.HttpCommandExecutor; 15 | import org.openqa.selenium.remote.RemoteWebDriver; 16 | import org.openqa.selenium.remote.SessionId; 17 | import org.slf4j.Logger; 18 | import org.slf4j.LoggerFactory; 19 | 20 | import java.io.InputStream; 21 | import java.net.URI; 22 | import java.net.URL; 23 | import java.util.Optional; 24 | import java.util.concurrent.TimeUnit; 25 | 26 | /** 27 | * Created by charles.capps on 10/1/14. 28 | */ 29 | public class BrowserUtil { 30 | private static final Logger logger = LoggerFactory.getLogger(BrowserUtil.class); 31 | private static final ObjectMapper objectMapper = new ObjectMapper(); 32 | public static final int DEFAULT_TIMEOUT_SECONDS = 30; 33 | 34 | /** 35 | * Helper to wait until the length of an HTML page (as a String) is stable for 1 second. 36 | * Useful to wait for javascript actions that modify the DOM of the page to complete. 37 | * 38 | * @param browser - this will probably only be useful for a {@link com.jivesoftware.selenium.pagefactory.framework.browser.web.WebBrowser} 39 | */ 40 | public static void waitForPageHtmlToBeStable(Browser browser, int timeoutSeconds) { 41 | final long TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(timeoutSeconds); 42 | final long START = System.currentTimeMillis(); 43 | WaitForPageToLoad waitForPageToLoad = new WaitForPageToLoad(); 44 | Wait lengthWait = waitForPageToLoad.getLengthCheckingWait(browser.getWebDriver()); 45 | try { 46 | lengthWait.wait("Timeout waiting for the page HTML to be stable with !", TIMEOUT_MILLIS); 47 | } catch (Exception e) { 48 | logger.warn("Error waiting for the page HTML to be stable. {}: {} ", e.getClass().getName(), e.getMessage()); 49 | logger.info(Throwables.getStackTraceAsString(e)); 50 | } 51 | final long END = System.currentTimeMillis(); 52 | logger.info("Success - waited for the page HTML to be stable! Took {} ms", END - START); 53 | } 54 | 55 | public static void waitForPageHtmlToBeStable(Browser browser) { 56 | waitForPageHtmlToBeStable(browser, DEFAULT_TIMEOUT_SECONDS); 57 | } 58 | 59 | /** 60 | * Helper to determine the Selenium Node we're running on via the Selenium API 61 | * @param browser - a remote browser that is running in a Selenium Grid 62 | * @return - the URL for the Node that the test is running on. 63 | */ 64 | public static Optional getSeleniumNodeUrl(RemoteBrowser browser) { 65 | RemoteWebDriver remoteWebDriver = (RemoteWebDriver) browser.getWebDriver(); 66 | SessionId sessionId = remoteWebDriver.getSessionId(); 67 | CommandExecutor commandExecutor = remoteWebDriver.getCommandExecutor(); 68 | if (commandExecutor instanceof HttpCommandExecutor) { 69 | HttpCommandExecutor httpCommandExecutor = (HttpCommandExecutor) commandExecutor; 70 | URL remoteServer = httpCommandExecutor.getAddressOfRemoteServer(); 71 | return getSeleniumNodeUrl(remoteServer, sessionId.toString()); 72 | } 73 | return Optional.empty(); 74 | } 75 | 76 | // Helper for above method. 77 | private static Optional getSeleniumNodeUrl(URL remoteServer, String sessionId) { 78 | try { 79 | URI gridApiURI = new URI(remoteServer.getProtocol(), null, remoteServer.getHost(), remoteServer.getPort(), 80 | "/grid/api/testsession", "session=" + sessionId, null); 81 | DefaultHttpClient client = new DefaultHttpClient(); 82 | BasicHttpEntityEnclosingRequest request = new BasicHttpEntityEnclosingRequest("POST", gridApiURI.toString()); 83 | HttpHost host = new HttpHost(remoteServer.getHost(), remoteServer.getPort()); 84 | HttpResponse response = client.execute(host, request); 85 | InputStream inputStream = response.getEntity().getContent(); 86 | JsonNode obj = objectMapper.readTree(inputStream); 87 | String nodeHost = obj.get("proxyId").asText(); 88 | return Optional.ofNullable(nodeHost); 89 | } catch (Exception e) { 90 | logger.warn("Error determining Selenium Node URL: {}", e.getMessage()); 91 | logger.debug(Throwables.getStackTraceAsString(e)); 92 | return Optional.empty(); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/com/jivesoftware/selenium/pagefactory/framework/browser/CachedPage.java: -------------------------------------------------------------------------------- 1 | package com.jivesoftware.selenium.pagefactory.framework.browser; 2 | 3 | import com.jivesoftware.selenium.pagefactory.framework.pages.TopLevelPage; 4 | 5 | import javax.annotation.Nonnull; 6 | 7 | /** 8 | * Created by charles.capps on 7/29/14. 9 | */ 10 | public final class CachedPage { 11 | private final String url; 12 | private final TopLevelPage cachedPage; 13 | 14 | public CachedPage(@Nonnull String url, @Nonnull TopLevelPage cachedPage) { 15 | this.url = url; 16 | this.cachedPage = cachedPage; 17 | } 18 | 19 | public String getUrl() { 20 | return url; 21 | } 22 | 23 | public TopLevelPage getCachedPage() { 24 | return cachedPage; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/jivesoftware/selenium/pagefactory/framework/browser/LocalBrowserBuilder.java: -------------------------------------------------------------------------------- 1 | package com.jivesoftware.selenium.pagefactory.framework.browser; 2 | 3 | import com.google.common.base.Objects; 4 | import com.google.common.base.Preconditions; 5 | import com.jivesoftware.selenium.pagefactory.framework.browser.web.ChromeBrowser; 6 | import com.jivesoftware.selenium.pagefactory.framework.browser.web.FirefoxBrowser; 7 | import com.jivesoftware.selenium.pagefactory.framework.browser.web.InternetExplorerBrowser; 8 | import com.jivesoftware.selenium.pagefactory.framework.browser.web.SafariBrowser; 9 | import com.jivesoftware.selenium.pagefactory.framework.browser.web.WebBrowser; 10 | import com.jivesoftware.selenium.pagefactory.framework.browser.web.WebBrowserType; 11 | import com.jivesoftware.selenium.pagefactory.framework.config.TimeoutsConfig; 12 | import com.jivesoftware.selenium.pagefactory.framework.exception.JiveWebDriverException; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | 16 | import java.util.List; 17 | import java.util.Optional; 18 | import java.util.logging.Level; 19 | 20 | /** 21 | * Created by charles.capps on 8/18/14. 22 | * 23 | *

Builder class for creating a Browser that is running on the same host as the test code. 24 | * Creates either a {@link com.jivesoftware.selenium.pagefactory.framework.browser.web.ChromeBrowser}, 25 | * {@link com.jivesoftware.selenium.pagefactory.framework.browser.web.FirefoxBrowser}, or 26 | * {@link com.jivesoftware.selenium.pagefactory.framework.browser.web.InternetExplorerBrowser}.

27 | * 28 | *

You can call the more general {@link #getBuilder(WebBrowserType, String)}, or the more specific methods 29 | * {@link #getChromeBuilder(String)}, {@link #getFirefoxBuilder(String)}, and {@link #getInternetExplorerBuilder(String)}. 30 | * 31 | * Then call the methods to add parameters, such as {@link #withBrowserBinaryPath(String)}, and finally call 32 | * {@link #build()} to create the Browser instance. 33 | *

34 | * 35 | *

A Browser is basically a wrapper for a WebDriver that greatly simplifies configuration, 36 | * adds useful utilities, and has methods 37 | * for loading {@link com.jivesoftware.selenium.pagefactory.framework.pages.Page}'s. 38 | * 39 | * Pages provide an object-oriented solution to Selenium testing. You can write Page classes that model a web page 40 | * in the web app you are testing.

41 | */ 42 | public class LocalBrowserBuilder { 43 | private static final Logger logger = LoggerFactory.getLogger(LocalBrowserBuilder.class); 44 | 45 | private final WebBrowserType browserType; 46 | private final String baseTestUrl; 47 | 48 | private TimeoutsConfig timeoutsConfig; 49 | 50 | private Optional webDriverPath = Optional.empty(); 51 | private Optional browserBinaryPath = Optional.empty(); 52 | private Optional browserLocale = Optional.empty(); 53 | private Optional startWindowWidth = Optional.empty(); 54 | private Optional startWindowHeight = Optional.empty(); 55 | private Optional browserLogLevel = Optional.empty(); 56 | private Optional browserLogFile = Optional.empty(); 57 | private Optional> options = Optional.empty(); 58 | 59 | private LocalBrowserBuilder(WebBrowserType browserType, String baseTestUrl) { 60 | this.browserType = Preconditions.checkNotNull(browserType, "You must provide a non-null browserType!"); 61 | this.baseTestUrl = Preconditions.checkNotNull(baseTestUrl, "You must provide a non-null baseTestUrl!"); 62 | this.timeoutsConfig = TimeoutsConfig.defaultTimeoutsConfig(); 63 | } 64 | 65 | //------------Getters in case the client wants to inspect the config they have so far----------- 66 | public WebBrowserType getBrowserType() { 67 | return browserType; 68 | } 69 | 70 | public String getBaseTestUrl() { 71 | return baseTestUrl; 72 | } 73 | 74 | public TimeoutsConfig getTimeoutsConfig() { 75 | return timeoutsConfig; 76 | } 77 | 78 | public Optional getWebDriverPath() { 79 | return webDriverPath; 80 | } 81 | 82 | public Optional getBrowserBinaryPath() { 83 | return browserBinaryPath; 84 | } 85 | 86 | public Optional getBrowserLocale() { 87 | return browserLocale; 88 | } 89 | 90 | public Optional getStartWindowWidth() { 91 | return startWindowWidth; 92 | } 93 | 94 | public Optional getStartWindowHeight() { 95 | return startWindowHeight; 96 | } 97 | 98 | public Optional getBrowserLogLevel() { 99 | return browserLogLevel; 100 | } 101 | 102 | public Optional getBrowserLogFile() { 103 | return browserLogFile; 104 | } 105 | 106 | public Optional> getOptions() { 107 | return options; 108 | } 109 | 110 | /** 111 | * Get a LocalBrowserBuilder for the given browser and base URL for the webapp you are testing against. 112 | * @param browserType - type of Browser, either CHROME, FIREFOX, or IE 113 | * @param baseTestUrl - base URL for your webapp, e.g. http://my.site.com/base 114 | */ 115 | public static LocalBrowserBuilder getBuilder(WebBrowserType browserType, String baseTestUrl) { 116 | return new LocalBrowserBuilder(browserType, baseTestUrl); 117 | } 118 | 119 | /** 120 | * Get a LocalBrowserBuilder for CHROME and base URL for the webapp you are testing against. 121 | * @param baseTestUrl - base URL for your webapp, e.g. http://my.site.com/base 122 | */ 123 | public static LocalBrowserBuilder getChromeBuilder(String baseTestUrl) { 124 | return new LocalBrowserBuilder(WebBrowserType.CHROME, baseTestUrl); 125 | } 126 | 127 | /** 128 | * Get a LocalBrowserBuilder for FIREFOX and base URL for the webapp you are testing against. 129 | * @param baseTestUrl - base URL for your webapp, e.g. http://my.site.com/base 130 | */ 131 | public static LocalBrowserBuilder getFirefoxBuilder(String baseTestUrl) { 132 | return new LocalBrowserBuilder(WebBrowserType.FIREFOX, baseTestUrl); 133 | } 134 | 135 | /** 136 | * Get a LocalBrowserBuilder for IE and base URL for the webapp you are testing against. 137 | * @param baseTestUrl - base URL for your webapp, e.g. http://my.site.com/base 138 | */ 139 | public static LocalBrowserBuilder getInternetExplorerBuilder(String baseTestUrl) { 140 | return new LocalBrowserBuilder(WebBrowserType.IE, baseTestUrl); 141 | } 142 | 143 | /** 144 | * Creates the Browser instance, which includes creating the actual Browser process via the underlying WebDriver. 145 | * @return - a {@link com.jivesoftware.selenium.pagefactory.framework.browser.web.FirefoxBrowser}, 146 | * {@link com.jivesoftware.selenium.pagefactory.framework.browser.web.ChromeBrowser}, 147 | * or {@link com.jivesoftware.selenium.pagefactory.framework.browser.web.InternetExplorerBrowser} 148 | * @throws JiveWebDriverException when something goes wrong with creating a new WebDriver instance. 149 | */ 150 | public WebBrowser build() throws JiveWebDriverException { 151 | logger.info("Building Local Browser with the following config: \n{}", toString()); 152 | WebBrowser browser; 153 | switch (browserType) { 154 | case FIREFOX: 155 | browser = new FirefoxBrowser(baseTestUrl, timeoutsConfig, webDriverPath, browserBinaryPath, Optional.empty(), browserLocale, startWindowWidth, startWindowHeight, Optional.empty()); 156 | break; 157 | case CHROME: 158 | browser = new ChromeBrowser(baseTestUrl, timeoutsConfig, webDriverPath, browserBinaryPath, Optional.empty(), browserLocale, startWindowWidth, startWindowHeight, 159 | browserLogLevel, browserLogFile, Optional.empty(), options); 160 | break; 161 | case IE: 162 | browser = new InternetExplorerBrowser(baseTestUrl, timeoutsConfig, webDriverPath, browserBinaryPath, Optional.empty(), browserLocale, startWindowWidth, startWindowHeight, 163 | browserLogLevel, browserLogFile, Optional.empty()); 164 | break; 165 | case SAFARI: 166 | browser = new SafariBrowser(baseTestUrl, timeoutsConfig, webDriverPath, browserBinaryPath, Optional.empty(), browserLocale, startWindowWidth, startWindowHeight, 167 | browserLogLevel, browserLogFile, Optional.empty()); 168 | break; 169 | default: 170 | throw new IllegalArgumentException("Only Firefox, Chrome, and IE are currently supported!"); 171 | } 172 | browser.initializeBrowser(); 173 | return browser; 174 | } 175 | 176 | public LocalBrowserBuilder withTimeoutsConfig(TimeoutsConfig timeoutsConfig) { 177 | this.timeoutsConfig = timeoutsConfig == null ? TimeoutsConfig.defaultTimeoutsConfig() : timeoutsConfig; 178 | return this; 179 | } 180 | 181 | public LocalBrowserBuilder withWebDriverPath(String pathToWebDriver) { 182 | this.webDriverPath = Optional.ofNullable(pathToWebDriver); 183 | return this; 184 | } 185 | 186 | public LocalBrowserBuilder withBrowserBinaryPath(String pathToBrowserBinary) { 187 | this.browserBinaryPath = Optional.ofNullable(pathToBrowserBinary); 188 | return this; 189 | } 190 | 191 | public LocalBrowserBuilder withBrowserLocale(String browserLocale) { 192 | this.browserLocale = Optional.ofNullable(browserLocale); 193 | return this; 194 | } 195 | 196 | public LocalBrowserBuilder withStartWindowWidth(Integer startWindowWidth) { 197 | this.startWindowWidth = Optional.ofNullable(startWindowWidth); 198 | return this; 199 | } 200 | 201 | public LocalBrowserBuilder withStartWindowHeight(Integer startWindowHeight) { 202 | this.startWindowHeight = Optional.ofNullable(startWindowHeight); 203 | return this; 204 | } 205 | 206 | public LocalBrowserBuilder withBrowserLogLevel(Level browserLogLevel) { 207 | this.browserLogLevel = Optional.ofNullable(browserLogLevel); 208 | return this; 209 | } 210 | 211 | public LocalBrowserBuilder withBrowserLogFile(String browserLogFile) { 212 | this.browserLogFile = Optional.ofNullable(browserLogFile); 213 | return this; 214 | } 215 | 216 | public LocalBrowserBuilder withOptions(List options) { 217 | this.options = Optional.ofNullable(options); 218 | return this; 219 | } 220 | 221 | @Override 222 | public String toString() { 223 | return Objects.toStringHelper(this) 224 | .add("browserType", browserType) 225 | .add("baseTestUrl", baseTestUrl) 226 | .add("webDriverPath", webDriverPath) 227 | .add("browserBinaryPath", browserBinaryPath) 228 | .add("browserLocale", browserLocale) 229 | .add("startWindowWidth", startWindowWidth) 230 | .add("startWindowHeight", startWindowHeight) 231 | .add("browserLogLevel", browserLogLevel) 232 | .add("browserLogFile", browserLogFile) 233 | .add("options", options) 234 | .toString(); 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /src/main/java/com/jivesoftware/selenium/pagefactory/framework/browser/MobileBrowserBuilder.java: -------------------------------------------------------------------------------- 1 | package com.jivesoftware.selenium.pagefactory.framework.browser; 2 | 3 | import com.google.common.base.Objects; 4 | import com.google.common.base.Preconditions; 5 | import com.jivesoftware.selenium.pagefactory.framework.browser.mobile.AndroidMobileBrowser; 6 | import com.jivesoftware.selenium.pagefactory.framework.browser.mobile.IOSMobileBrowser; 7 | import com.jivesoftware.selenium.pagefactory.framework.browser.mobile.MobileBrowser; 8 | import com.jivesoftware.selenium.pagefactory.framework.browser.mobile.MobilePlatformName; 9 | import com.jivesoftware.selenium.pagefactory.framework.config.TimeoutsConfig; 10 | import com.jivesoftware.selenium.pagefactory.framework.exception.JiveWebDriverException; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | /** 15 | * Created By amir.simhi on 8/20/14. 16 | * 17 | *

Builder class for creating an App that is running on an emulator or a connected device that connected 18 | * to the same host as the test code. 19 | * Creates either a {@link com.jivesoftware.selenium.pagefactory.framework.browser.mobile.AndroidMobileBrowser}, 20 | * {@link com.jivesoftware.selenium.pagefactory.framework.browser.mobile.IOSMobileBrowser}..

21 | * 22 | *

A Browser is basically a wrapper for a WebDriver that greatly simplifies configuration, 23 | * adds useful utilities, and has methods 24 | * for loading {@link com.jivesoftware.selenium.pagefactory.framework.pages.Page}'s. 25 | * 26 | * Pages provide an object-oriented solution to Selenium testing. You can write Page classes that model a web page 27 | * in the web app you are testing.

28 | */ 29 | public class MobileBrowserBuilder { 30 | private static final Logger logger = LoggerFactory.getLogger(MobileBrowserBuilder.class); 31 | 32 | private String baseTestUrl; 33 | private TimeoutsConfig timeoutsConfig; 34 | private String browserName; 35 | private MobilePlatformName platformName; 36 | private String platformVersion; 37 | private String platform; 38 | private String deviceName; 39 | private String app; 40 | private String appPackage; 41 | private String appActivity; 42 | private String newCommandTimeout; 43 | private String automationName; 44 | private String version; 45 | private String autoLaunch; 46 | private boolean fullReset; 47 | private boolean touchMode; 48 | 49 | 50 | private MobileBrowserBuilder(String baseTestUrl, 51 | MobilePlatformName platformName) { 52 | this.baseTestUrl = Preconditions.checkNotNull(baseTestUrl, "You must provide a non-null baseTestUrl!"); 53 | this.timeoutsConfig = TimeoutsConfig.defaultTimeoutsConfig(); 54 | this.platformName = Preconditions.checkNotNull(platformName, "You must provide a non-null platformName!"); 55 | this.fullReset = true; 56 | 57 | } 58 | 59 | //------------Getters in case the client wants to inspect the config they have so far----------- 60 | public String getBaseTestUrl() { 61 | return baseTestUrl; 62 | } 63 | 64 | public TimeoutsConfig getTimeoutsConfig() { 65 | return timeoutsConfig; 66 | } 67 | 68 | public String getBrowserName() { 69 | return browserName; 70 | } 71 | 72 | public MobilePlatformName getPlatformName() { 73 | return platformName; 74 | } 75 | 76 | public String getPlatformVersion() { 77 | return platformVersion; 78 | } 79 | 80 | public String getPlatform() { 81 | return platform; 82 | } 83 | 84 | public String getNewCommandTimeout() { 85 | return newCommandTimeout; 86 | } 87 | 88 | public String getAutomationName() { 89 | return automationName; 90 | } 91 | 92 | public String getVersion() { 93 | return version; 94 | } 95 | 96 | public String getAutoLaunch() { 97 | return autoLaunch; 98 | } 99 | 100 | public String getDeviceName() { 101 | return deviceName; 102 | } 103 | 104 | public String getApp() { 105 | return app; 106 | } 107 | 108 | public String getAppPackage() { 109 | return appPackage; 110 | } 111 | 112 | public String getAppActivity() { 113 | return appActivity; 114 | } 115 | 116 | public boolean isTouchMode() { 117 | return touchMode; 118 | } 119 | 120 | public boolean isFullReset() { 121 | return fullReset; 122 | } 123 | 124 | /** 125 | * Get a MobileBrowserBuilder for Android and base URL for the webapp you are testing against. 126 | * @param baseTestUrl - base URL for your webapp, e.g. http://my.site.com/base 127 | */ 128 | public static MobileBrowserBuilder getAndroidBuilder(String baseTestUrl) { 129 | return new MobileBrowserBuilder(baseTestUrl, MobilePlatformName.ANDROID); 130 | } 131 | 132 | /** 133 | * Get a MobileBrowserBuilder for iOS and base URL for the webapp you are testing against. 134 | * @param baseTestUrl - base URL for your webapp, e.g. http://my.site.com/base 135 | */ 136 | public static MobileBrowserBuilder getIOSBuilder(String baseTestUrl) { 137 | return new MobileBrowserBuilder(baseTestUrl, MobilePlatformName.IOS); 138 | } 139 | 140 | 141 | /** 142 | * Creates the MobileBrowser instance, which includes creating the actual Browser process via the underlying Appium 143 | * Server 144 | * @return - a {@link com.jivesoftware.selenium.pagefactory.framework.browser.mobile.AndroidMobileBrowser}, 145 | * {@link com.jivesoftware.selenium.pagefactory.framework.browser.mobile.IOSMobileBrowser} 146 | * @throws com.jivesoftware.selenium.pagefactory.framework.exception.JiveWebDriverException when something goes wrong with creating a new WebDriver 147 | */ 148 | public MobileBrowser build() throws JiveWebDriverException { 149 | logger.info("Building Mobile Browser with the following config: \n{}", toString()); 150 | MobileBrowser browser; 151 | switch (platformName) { 152 | case ANDROID: 153 | browser = new AndroidMobileBrowser(baseTestUrl, browserName, platform, platformName.getPlatformName(), 154 | platformVersion, deviceName, newCommandTimeout, automationName, version, autoLaunch, 155 | app, appPackage, appActivity, timeoutsConfig, touchMode, fullReset); 156 | break; 157 | case IOS: 158 | browser = new IOSMobileBrowser(baseTestUrl, browserName, platform, platformName.getPlatformName(), 159 | platformVersion, deviceName, newCommandTimeout, automationName, version, autoLaunch, 160 | app, fullReset, timeoutsConfig); 161 | break; 162 | default: 163 | throw new IllegalArgumentException("Only IOS and Android are currently supported!"); 164 | } 165 | browser.initializeBrowser(); 166 | return browser; 167 | } 168 | 169 | public MobileBrowserBuilder withTouchMode(boolean touchMode) { 170 | this.touchMode = touchMode; 171 | return this; 172 | } 173 | 174 | public MobileBrowserBuilder withTimeoutsConfig(TimeoutsConfig timeoutsConfig) { 175 | this.timeoutsConfig = timeoutsConfig == null ? TimeoutsConfig.defaultTimeoutsConfig() : timeoutsConfig; 176 | return this; 177 | } 178 | 179 | public MobileBrowserBuilder withBrowserName(String browserName) { 180 | this.browserName = browserName; 181 | return this; 182 | } 183 | 184 | public MobileBrowserBuilder withPlatformName(MobilePlatformName platformName) { 185 | this.platformName = platformName; 186 | return this; 187 | } 188 | 189 | public MobileBrowserBuilder withPlatformVersion(String platformVersion) { 190 | this.platformVersion = platformVersion; 191 | return this; 192 | } 193 | 194 | public MobileBrowserBuilder withDeviceName(String deviceName) { 195 | this.deviceName = deviceName; 196 | return this; 197 | } 198 | 199 | public MobileBrowserBuilder withApp(String app) { 200 | this.app = app; 201 | return this; 202 | } 203 | 204 | public MobileBrowserBuilder withAppPackage(String appPackage) { 205 | this.appPackage = appPackage; 206 | return this; 207 | } 208 | 209 | public MobileBrowserBuilder withAppActivity(String appActivity) { 210 | this.appActivity = appActivity; 211 | return this; 212 | } 213 | 214 | public MobileBrowserBuilder withNewCommandTimeout(String newCommandTimeout) { 215 | this.newCommandTimeout = newCommandTimeout; 216 | return this; 217 | } 218 | 219 | public MobileBrowserBuilder withAutomationName(String automationName) { 220 | this.automationName = automationName; 221 | return this; 222 | } 223 | 224 | public MobileBrowserBuilder withVersion(String version) { 225 | this.version = version; 226 | return this; 227 | } 228 | 229 | public MobileBrowserBuilder withAutoLaunch(String autoLaunch) { 230 | this.autoLaunch = autoLaunch; 231 | return this; 232 | } 233 | 234 | public MobileBrowserBuilder withPlatform(String platform) { 235 | this.platform = platform; 236 | return this; 237 | } 238 | 239 | public MobileBrowserBuilder withFullReset(boolean fullReset) { 240 | this.fullReset = fullReset; 241 | return this; 242 | } 243 | 244 | @Override 245 | public String toString() { 246 | return Objects.toStringHelper(this) 247 | .add("baseTestUrl", baseTestUrl) 248 | .add("browserName", browserName) 249 | .add("platformName", platformName.getPlatformName()) 250 | .add("platform", platform) 251 | .add("platformVersion", platformVersion) 252 | .add("deviceName", deviceName) 253 | .add("app", app) 254 | .add("appPackage", appPackage) 255 | .add("appActivity", appActivity) 256 | .add("newCommandTimeout", newCommandTimeout) 257 | .add("automationName", automationName) 258 | .add("version", version) 259 | .add("autoLaunch", autoLaunch) 260 | .toString(); 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /src/main/java/com/jivesoftware/selenium/pagefactory/framework/browser/RemoteBrowserBuilder.java: -------------------------------------------------------------------------------- 1 | package com.jivesoftware.selenium.pagefactory.framework.browser; 2 | 3 | import com.google.common.base.Objects; 4 | import com.google.common.base.Preconditions; 5 | import com.jivesoftware.selenium.pagefactory.framework.browser.web.ChromeBrowser; 6 | import com.jivesoftware.selenium.pagefactory.framework.browser.web.FirefoxBrowser; 7 | import com.jivesoftware.selenium.pagefactory.framework.browser.web.InternetExplorerBrowser; 8 | import com.jivesoftware.selenium.pagefactory.framework.browser.web.RemoteBrowser; 9 | import com.jivesoftware.selenium.pagefactory.framework.browser.web.SafariBrowser; 10 | import com.jivesoftware.selenium.pagefactory.framework.browser.web.WebBrowser; 11 | import com.jivesoftware.selenium.pagefactory.framework.browser.web.WebBrowserType; 12 | import com.jivesoftware.selenium.pagefactory.framework.config.TimeoutsConfig; 13 | import com.jivesoftware.selenium.pagefactory.framework.exception.JiveWebDriverException; 14 | import org.openqa.selenium.Platform; 15 | import org.slf4j.Logger; 16 | import org.slf4j.LoggerFactory; 17 | 18 | import java.util.List; 19 | import java.util.Optional; 20 | import java.util.logging.Level; 21 | 22 | /** 23 | * Created by charles.capps on 8/18/14. 24 | * 25 | *

Builder class for creating a {@link com.jivesoftware.selenium.pagefactory.framework.browser.web.RemoteBrowser}. 26 | * A RemoteBrowser is a browser running in a Selenium Grid, that works 27 | * by connecting to a Selenium Hub. See https://code.google.com/p/selenium/wiki/Grid2

28 | * 29 | *

In other words, a RemoteBrowser is a wrapper around a {@link org.openqa.selenium.remote.RemoteWebDriver} 30 | * that simplifies configuration and unifies options across all Browsers.

31 | * 32 | *

You can call {@link #getBuilder(WebBrowserType, String, String)} to get a builder, or you can equivalently call 33 | * {@link #getChromeBuilder(String, String)}, {@link #getFirefoxBuilder(String, String)}, or {@link #getInternetExplorerBuilder(String, String)}. 34 | * 35 | * Calling RemoteBrowserBuilder.getBuilder(BrowserType.CHROME, ...) 36 | * is equivalent to calling RemoteBrowserBuilder.getChromeBuilder(...).

37 | */ 38 | public class RemoteBrowserBuilder { 39 | private static final Logger logger = LoggerFactory.getLogger(RemoteBrowserBuilder.class); 40 | 41 | private final WebBrowserType browserType; 42 | private final String baseTestUrl; 43 | private final String seleniumHubURL; 44 | 45 | private TimeoutsConfig timeoutsConfig; 46 | 47 | private Optional browserVersion = Optional.empty(); 48 | private Optional browserLocale = Optional.empty(); 49 | private Optional startWindowWidth = Optional.empty(); 50 | private Optional startWindowHeight = Optional.empty(); 51 | private Optional browserLogLevel = Optional.empty(); 52 | private Optional browserLogFile = Optional.empty(); 53 | private Optional platform = Optional.empty(); 54 | private Optional> options = Optional.empty(); 55 | 56 | private RemoteBrowserBuilder(WebBrowserType browserType, 57 | String baseTestUrl, 58 | String seleniumHubURL) { 59 | this.browserType = Preconditions.checkNotNull(browserType, "You must provide a non-null BrowserType!"); 60 | this.baseTestUrl = Preconditions.checkNotNull(baseTestUrl, "You must provide a non-null baseTestUrl!"); 61 | this.seleniumHubURL = Preconditions.checkNotNull(seleniumHubURL, "You must provide a non-null seleniumHubURL"); 62 | this.timeoutsConfig = TimeoutsConfig.defaultTimeoutsConfig(); 63 | } 64 | 65 | //------------Getters in case the client wants to inspect the config they have so far----------- 66 | public WebBrowserType getBrowserType() { 67 | return browserType; 68 | } 69 | 70 | public String getBaseTestUrl() { 71 | return baseTestUrl; 72 | } 73 | 74 | public String getSeleniumHubURL() { 75 | return seleniumHubURL; 76 | } 77 | 78 | public TimeoutsConfig getTimeoutsConfig() { 79 | return timeoutsConfig; 80 | } 81 | 82 | public Optional getBrowserVersion() { 83 | return browserVersion; 84 | } 85 | 86 | public Optional getBrowserLocale() { 87 | return browserLocale; 88 | } 89 | 90 | public Optional getStartWindowWidth() { 91 | return startWindowWidth; 92 | } 93 | 94 | public Optional getStartWindowHeight() { 95 | return startWindowHeight; 96 | } 97 | 98 | public Optional getBrowserLogLevel() { 99 | return browserLogLevel; 100 | } 101 | 102 | public Optional getBrowserLogFile() { 103 | return browserLogFile; 104 | } 105 | 106 | public Optional getPlatform() { 107 | return platform; 108 | } 109 | 110 | public Optional> getOptions() { 111 | return options; 112 | } 113 | 114 | 115 | /** 116 | * Get a RemoteBrowserBuilder used to construct a RemoteBrowser instance that helps you to run Selenium tests 117 | * against a remote Browser running in a Selenium Grid. 118 | * 119 | * @param browserType - CHROME, FIREFOX, or IE 120 | * @param baseTestUrl - base URL of the webapp you are testing, e.g. http://my.site.com/base 121 | * @param seleniumHubURL - URL with port to the Selenium HUB, e.g. http://selenium.my.company.com:4444/wd/hub 122 | */ 123 | public static RemoteBrowserBuilder getBuilder(WebBrowserType browserType, 124 | String baseTestUrl, 125 | String seleniumHubURL) { 126 | return new RemoteBrowserBuilder(browserType, baseTestUrl, seleniumHubURL); 127 | } 128 | 129 | /** 130 | * Get a RemoteBrowserBuilder used to construct a RemoteBrowser instance that helps you to run Selenium tests 131 | * against a remote Browser running in a Selenium Grid. For CHROME browser. 132 | * 133 | * @param baseTestUrl - base URL of the webapp you are testing, e.g. http://my.site.com/base 134 | * @param seleniumHubURL - URL with port to the Selenium HUB, e.g. http://selenium.my.company.com:4444/wd/hub 135 | */ 136 | public static RemoteBrowserBuilder getChromeBuilder(String baseTestUrl, String seleniumHubURL) { 137 | return new RemoteBrowserBuilder(WebBrowserType.CHROME, baseTestUrl, seleniumHubURL); 138 | } 139 | 140 | /** 141 | * Get a RemoteBrowserBuilder used to construct a RemoteBrowser instance that helps you to run Selenium tests 142 | * against a remote Browser running in a Selenium Grid. For FIREFOX browser. 143 | * 144 | * @param baseTestUrl - base URL of the webapp you are testing, e.g. http://my.site.com/base 145 | * @param seleniumHubURL - URL with port to the Selenium HUB, e.g. http://selenium.my.company.com:4444/wd/hub 146 | */ 147 | public static RemoteBrowserBuilder getFirefoxBuilder(String baseTestUrl, String seleniumHubURL) { 148 | return new RemoteBrowserBuilder(WebBrowserType.FIREFOX, baseTestUrl, seleniumHubURL); 149 | } 150 | 151 | /** 152 | * Get a RemoteBrowserBuilder used to construct a RemoteBrowser instance that helps you to run Selenium tests 153 | * against a remote Browser running in a Selenium Grid. For IE browser. 154 | * 155 | * @param baseTestUrl - base URL of the webapp you are testing, e.g. http://my.site.com/base 156 | * @param seleniumHubURL - URL with port to the Selenium HUB, e.g. http://selenium.my.company.com:4444/wd/hub 157 | */ 158 | public static RemoteBrowserBuilder getInternetExplorerBuilder(String baseTestUrl, String seleniumHubURL) { 159 | return new RemoteBrowserBuilder(WebBrowserType.IE, baseTestUrl, seleniumHubURL); 160 | } 161 | 162 | /** 163 | * Creates the RemoteBrowser instance, which includes creating the actual Browser process via the underlying WebDriver. 164 | * 165 | * @return - a {@link com.jivesoftware.selenium.pagefactory.framework.browser.web.RemoteBrowser}, 166 | * @throws JiveWebDriverException when something goes wrong with creating a new WebDriver. 167 | */ 168 | public RemoteBrowser build() throws JiveWebDriverException { 169 | logger.info("Building Remote Browser with the following config: \n{}", toString()); 170 | WebBrowser browser; 171 | switch (browserType) { 172 | case FIREFOX: 173 | browser = new FirefoxBrowser(baseTestUrl, timeoutsConfig, Optional.empty(), Optional.empty(), browserVersion, browserLocale, startWindowWidth, startWindowHeight, platform); 174 | break; 175 | case CHROME: 176 | browser = new ChromeBrowser(baseTestUrl, timeoutsConfig, Optional.empty(), Optional.empty(), browserVersion, browserLocale, startWindowWidth, startWindowHeight, 177 | browserLogLevel, browserLogFile, platform, options); 178 | break; 179 | case IE: 180 | browser = new InternetExplorerBrowser(baseTestUrl, timeoutsConfig, Optional.empty(), Optional.empty(), browserVersion, browserLocale, startWindowWidth, startWindowHeight, 181 | browserLogLevel, browserLogFile, platform); 182 | break; 183 | case SAFARI: 184 | browser = new SafariBrowser(baseTestUrl, timeoutsConfig, Optional.empty(), Optional.empty(), browserVersion, browserLocale, startWindowWidth, startWindowHeight, 185 | browserLogLevel, browserLogFile, Optional.empty()); 186 | break; 187 | default: 188 | throw new IllegalArgumentException("Only FIREFOX, CHROME, IE, and SAFARI are currently supported!"); 189 | } 190 | RemoteBrowser remoteBrowser = new RemoteBrowser(browser, seleniumHubURL); 191 | remoteBrowser.initializeBrowser(); 192 | return remoteBrowser; 193 | } 194 | 195 | public RemoteBrowserBuilder withTimeoutsConfig(TimeoutsConfig timeoutsConfig) { 196 | this.timeoutsConfig = timeoutsConfig == null ? TimeoutsConfig.defaultTimeoutsConfig() : timeoutsConfig; 197 | return this; 198 | } 199 | 200 | public RemoteBrowserBuilder withBrowserVersion(String browserVersion) { 201 | this.browserVersion = Optional.ofNullable(browserVersion); 202 | return this; 203 | } 204 | 205 | public RemoteBrowserBuilder withBrowserLocale(String browserLocale) { 206 | this.browserLocale = Optional.ofNullable(browserLocale); 207 | return this; 208 | } 209 | 210 | public RemoteBrowserBuilder withStartWindowWidth(Integer startWindowWidth) { 211 | this.startWindowWidth = Optional.ofNullable(startWindowWidth); 212 | return this; 213 | } 214 | 215 | public RemoteBrowserBuilder withStartWindowHeight(Integer startWindowHeight) { 216 | this.startWindowHeight = Optional.ofNullable(startWindowHeight); 217 | return this; 218 | } 219 | 220 | public RemoteBrowserBuilder withBrowserLogLevel(Level browserLogLevel) { 221 | this.browserLogLevel = Optional.ofNullable(browserLogLevel); 222 | return this; 223 | } 224 | 225 | public RemoteBrowserBuilder withBrowserLogFile(String browserLogFile) { 226 | this.browserLogFile = Optional.ofNullable(browserLogFile); 227 | return this; 228 | } 229 | 230 | public RemoteBrowserBuilder withPlatform(Platform platform) { 231 | this.platform = Optional.ofNullable(platform); 232 | return this; 233 | } 234 | 235 | public RemoteBrowserBuilder withOptions(List options) { 236 | this.options = Optional.ofNullable(options); 237 | return this; 238 | } 239 | 240 | @Override 241 | public String toString() { 242 | return Objects.toStringHelper(this) 243 | .add("browserType", browserType) 244 | .add("baseTestUrl", baseTestUrl) 245 | .add("seleniumHubURL", seleniumHubURL) 246 | .add("browserVersion", browserVersion) 247 | .add("browserLocale", browserLocale) 248 | .add("startWindowWidth", startWindowWidth) 249 | .add("startWindowHeight", startWindowHeight) 250 | .add("browserLogLevel", browserLogLevel) 251 | .add("browserLogFile", browserLogFile) 252 | .add("platform", platform) 253 | .add("options", options) 254 | .toString(); 255 | } 256 | 257 | } 258 | -------------------------------------------------------------------------------- /src/main/java/com/jivesoftware/selenium/pagefactory/framework/browser/mobile/AndroidMobileBrowser.java: -------------------------------------------------------------------------------- 1 | package com.jivesoftware.selenium.pagefactory.framework.browser.mobile; 2 | 3 | import com.jivesoftware.selenium.pagefactory.framework.actions.AndroidSeleniumActions; 4 | import com.jivesoftware.selenium.pagefactory.framework.config.TimeoutsConfig; 5 | import com.jivesoftware.selenium.pagefactory.framework.exception.JiveWebDriverException; 6 | import io.appium.java_client.android.AndroidDriver; 7 | import io.appium.java_client.android.AndroidKeyCode; 8 | import org.openqa.selenium.WebElement; 9 | import org.openqa.selenium.interactions.touch.TouchActions; 10 | import org.openqa.selenium.remote.CapabilityType; 11 | import org.openqa.selenium.remote.DesiredCapabilities; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | 15 | import java.io.IOException; 16 | import java.net.URL; 17 | 18 | public class AndroidMobileBrowser extends MobileBrowser { 19 | 20 | protected boolean touchMode; 21 | private String appPackage; 22 | private String appActivity; 23 | private static final Logger logger = LoggerFactory.getLogger(AndroidMobileBrowser.class); 24 | 25 | public AndroidMobileBrowser(String baseTestUrl, 26 | String browserName, 27 | String platform, 28 | String platformName, 29 | String platformVersion, 30 | String deviceName, 31 | String newCommandTimeout, 32 | String automationName, 33 | String version, 34 | String autoLaunch, 35 | String app, 36 | String appPackage, 37 | String appActivity, 38 | TimeoutsConfig timeouts, 39 | boolean touchMode, 40 | boolean fullReset) throws JiveWebDriverException { 41 | super(baseTestUrl, timeouts, browserName, platform, platformName, platformVersion, deviceName, 42 | newCommandTimeout, automationName, version, autoLaunch, app, fullReset); 43 | this.touchMode = touchMode; 44 | this.appPackage = appPackage; 45 | this.appActivity = appActivity; 46 | } 47 | 48 | @Override 49 | public DesiredCapabilities getDesiredCapabilities() { 50 | DesiredCapabilities desiredCapabilities = new DesiredCapabilities(); 51 | desiredCapabilities.setCapability(CapabilityType.BROWSER_NAME, browserName); 52 | desiredCapabilities.setCapability("platform", platform); 53 | desiredCapabilities.setCapability("platformName", platformName); 54 | desiredCapabilities.setCapability("platformVersion", platformVersion); 55 | desiredCapabilities.setCapability("deviceName", deviceName); 56 | desiredCapabilities.setCapability("newCommandTimeout", newCommandTimeout); 57 | desiredCapabilities.setCapability("automationName", automationName); 58 | desiredCapabilities.setCapability("version", version); 59 | desiredCapabilities.setCapability("autoLaunch", autoLaunch); 60 | desiredCapabilities.setCapability("app", app); 61 | desiredCapabilities.setCapability("appPackage", appPackage); 62 | desiredCapabilities.setCapability("appWaitActivity", appActivity); 63 | desiredCapabilities.setCapability("fullReset", fullReset); 64 | desiredCapabilities.setCapability(CapabilityType.ForSeleniumServer.ENSURING_CLEAN_SESSION, true); 65 | return desiredCapabilities; 66 | } 67 | 68 | protected AndroidDriver createWebDriver() throws JiveWebDriverException { 69 | try { 70 | printCapabilities(getDesiredCapabilities()); 71 | return new SwipeableWebDriver(new URL(getBaseTestUrl()), getDesiredCapabilities()); 72 | } catch (IOException e) { 73 | throw new JiveWebDriverException("Error starting appium driver service", e); 74 | } 75 | } 76 | 77 | @Override 78 | public AndroidSeleniumActions getActions() { 79 | return new AndroidSeleniumActions(this); 80 | } 81 | 82 | public String getAppPackage() { 83 | return appPackage; 84 | } 85 | 86 | public String getAppActivity() { 87 | return appActivity; 88 | } 89 | 90 | /** 91 | * 92 | * @return true if Android is API 17 or down, and as a result uses touch mode 93 | */ 94 | public boolean isTouchMode() { 95 | return touchMode; 96 | } 97 | 98 | /** 99 | * 100 | * @param touchMode - true if Android is API 17 or lower 101 | */ 102 | public void setTouchMode(boolean touchMode) { 103 | this.touchMode = touchMode; 104 | } 105 | 106 | /** 107 | * Swipe from the top to bottom for a second 108 | */ 109 | @Override 110 | public void dragDown() { 111 | int midScreen = getScreenWidth() / 2; 112 | if (touchMode) { 113 | TouchActions action = new TouchActions(webDriver); 114 | action.down(midScreen, 360).move(midScreen, 300).up(midScreen, 300).perform(); 115 | } else { 116 | webDriver.swipe(midScreen, 450, midScreen, getScreenHeight() - 250, 1500); 117 | } 118 | } 119 | 120 | /** 121 | * Swipe from the down to up for a second 122 | */ 123 | @Override 124 | public void dragUp() { 125 | int midScreen = webDriver.manage().window().getSize().getWidth() / 2; 126 | if (touchMode) { 127 | TouchActions action = new TouchActions(webDriver); 128 | action.down(midScreen, 300).move(midScreen, 250).up(midScreen, 250).perform(); 129 | } else { 130 | webDriver.swipe(midScreen, getScreenHeight() - 250, midScreen, 250, 2500); 131 | } 132 | } 133 | 134 | /** 135 | * Swipe from the top to bottom for a second 136 | * 137 | * @param yStart - coordinate to start swiping 138 | * @param yEnd - coordinate to stop swiping 139 | */ 140 | @Override 141 | public void drag(int yStart, int yEnd) { 142 | int midScreen = getScreenWidth() / 2; 143 | if (touchMode) { 144 | TouchActions action = new TouchActions(webDriver); 145 | action.down(midScreen, yStart).move(midScreen, yEnd).up(midScreen, yEnd).perform(); 146 | } else { 147 | webDriver.swipe(midScreen, yStart, midScreen, yEnd, 2500); 148 | } 149 | } 150 | 151 | /** 152 | * Swipe from the top to bottom for a second 153 | * 154 | * @param yStart - coordinate to start swiping 155 | * @param yEnd - coordinate to stop swiping 156 | */ 157 | 158 | @Override 159 | public void drag(int yStart, int yEnd, int duration) { 160 | int midScreen = getScreenWidth() / 2; 161 | if (touchMode) { 162 | TouchActions action = new TouchActions(webDriver); 163 | action.down(midScreen, yStart).move(midScreen, yEnd).up(midScreen, yEnd).perform(); 164 | } else { 165 | webDriver.swipe(midScreen, yStart, midScreen, yEnd, duration); 166 | } 167 | } 168 | 169 | @Override 170 | public void tap(int fingersNum, WebElement webElement, int duration) { 171 | if (touchMode) { 172 | TouchActions action = new TouchActions(webDriver); 173 | try { 174 | action.down(webElement.getLocation().getX(), webElement.getLocation().getY()).clickAndHold() 175 | .release(webElement).perform(); 176 | } catch (NullPointerException e) { 177 | 178 | } 179 | } else { 180 | webDriver.tap(fingersNum, webElement, duration); 181 | } 182 | } 183 | 184 | @Override 185 | public void tap(int fingersNum, int xLocation, int yLocation, int duration) { 186 | if (touchMode) { 187 | TouchActions action = new TouchActions(webDriver); 188 | try { 189 | action.down(xLocation, yLocation).clickAndHold().perform(); 190 | } catch (NullPointerException e) { 191 | logger.error("Failed To Tap due to NullPointerException", e.getStackTrace()); 192 | } 193 | } else { 194 | webDriver.tap(fingersNum, xLocation, yLocation, duration); 195 | } 196 | } 197 | 198 | public void clickHomePage() { 199 | webDriver.sendKeyEvent(AndroidKeyCode.HOME); 200 | } 201 | 202 | public void clickBack() { 203 | webDriver.sendKeyEvent(AndroidKeyCode.BACK); 204 | } 205 | 206 | @Override 207 | public void scrollToTop() { 208 | logger.error("Method ScrollToTop is not yet implemented"); 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /src/main/java/com/jivesoftware/selenium/pagefactory/framework/browser/mobile/IOSMobileBrowser.java: -------------------------------------------------------------------------------- 1 | package com.jivesoftware.selenium.pagefactory.framework.browser.mobile; 2 | 3 | import com.jivesoftware.selenium.pagefactory.framework.actions.IOSSeleniumActions; 4 | import com.jivesoftware.selenium.pagefactory.framework.config.TimeoutsConfig; 5 | import com.jivesoftware.selenium.pagefactory.framework.exception.JiveWebDriverException; 6 | import io.appium.java_client.ios.IOSDriver; 7 | import org.openqa.selenium.remote.CapabilityType; 8 | import org.openqa.selenium.remote.DesiredCapabilities; 9 | 10 | import java.io.IOException; 11 | import java.net.URL; 12 | import java.util.HashMap; 13 | 14 | 15 | /** 16 | * Added by Shiran.Dadon 17 | * Known bug of Apple from Xcode 5 and iOS 7.1 Simulator - swipe is not working on simulator. 18 | * As a workaround, using scrollTo in JavaScript. 19 | * As in real devices regular swipe works but not scrollTo, using the regular command as well 20 | */ 21 | 22 | public class IOSMobileBrowser extends MobileBrowser { 23 | 24 | public IOSMobileBrowser(String baseTestUrl, 25 | String browserName, 26 | String platform, 27 | String platformName, 28 | String platformVersion, 29 | String deviceName, 30 | String newCommandTimeout, 31 | String automationName, 32 | String version, 33 | String autoLaunch, 34 | String app, 35 | boolean fullReset, 36 | TimeoutsConfig timeouts) throws JiveWebDriverException { 37 | super(baseTestUrl, timeouts, browserName, platform, platformName, platformVersion, deviceName, 38 | newCommandTimeout, automationName, version, autoLaunch, app, fullReset); 39 | } 40 | 41 | @Override 42 | public DesiredCapabilities getDesiredCapabilities() { 43 | DesiredCapabilities desiredCapabilities = new DesiredCapabilities(); 44 | desiredCapabilities.setCapability(CapabilityType.BROWSER_NAME, browserName); 45 | desiredCapabilities.setCapability("platform", platform); 46 | desiredCapabilities.setCapability("platformName", platformName); 47 | desiredCapabilities.setCapability("platformVersion", platformVersion); 48 | desiredCapabilities.setCapability("deviceName", deviceName); 49 | desiredCapabilities.setCapability("newCommandTimeout", newCommandTimeout); 50 | desiredCapabilities.setCapability("automationName", automationName); 51 | desiredCapabilities.setCapability("version", version); 52 | desiredCapabilities.setCapability("autoLaunch", autoLaunch); 53 | desiredCapabilities.setCapability("app", app); 54 | desiredCapabilities.setCapability("fullReset", fullReset); 55 | desiredCapabilities.setCapability("rotatable", "true"); 56 | return desiredCapabilities; 57 | } 58 | 59 | protected IOSDriver createWebDriver() throws JiveWebDriverException { 60 | try { 61 | printCapabilities(getDesiredCapabilities()); 62 | return new IOSDriver(new URL(getBaseTestUrl()), getDesiredCapabilities()); 63 | } catch (IOException e) { 64 | throw new JiveWebDriverException("Error starting appium driver service", e); 65 | } 66 | } 67 | 68 | @Override 69 | public IOSSeleniumActions getActions() { 70 | return new IOSSeleniumActions(this); 71 | } 72 | /** 73 | * Swipe from the right to left for a second 74 | */ 75 | public void swipeLeft() { 76 | super.swipeLeft(); 77 | HashMap scrollObject = new HashMap(); 78 | scrollObject.put("direction", "left"); 79 | webDriver.executeScript("mobile: scroll", scrollObject); 80 | } 81 | 82 | /** 83 | * Swipe from the left to right for a second 84 | */ 85 | public void swipeRight() { 86 | super.swipeRight(); 87 | HashMap scrollObject = new HashMap(); 88 | scrollObject.put("direction", "right"); 89 | webDriver.executeScript("mobile: scroll", scrollObject); 90 | } 91 | 92 | /** 93 | * Swipe from the top to buttom for a second 94 | */ 95 | public void dragDown() { 96 | int midScreen = getScreenWidth() / 2; 97 | webDriver.swipe(midScreen, 140, midScreen, getScreenHeight() - 140, 1500); 98 | //HashMap scrollObject = new HashMap(); 99 | //scrollObject.put("direction", "up"); 100 | //webDriver.executeScript("mobile: scroll", scrollObject); 101 | } 102 | 103 | /** 104 | * Swipe from the down to up for a second 105 | */ 106 | public void dragUp() { 107 | int midScreen = getScreenWidth() / 2; 108 | webDriver.swipe(midScreen, getScreenHeight() - 140, midScreen, 140, 1500); 109 | // HashMap scrollObject = new HashMap(); 110 | //scrollObject.put("direction", "down"); 111 | //webDriver.executeScript("mobile: scroll", scrollObject); 112 | } 113 | 114 | /** 115 | * Will function only with real device 116 | * @param startX - 0 is the left side of the smart-phone 117 | * @param endX - coordinate to stop swipe 118 | * @param startY - 0 is the upper side of the smart-phone 119 | * @param endY - coordinate to stop swipe 120 | * @param duration - in milliseconds 121 | */ 122 | public void swipe(int startX, int endX, int startY, int endY, int duration) { 123 | webDriver.swipe(startX, startY, endX, endY, duration); 124 | } 125 | 126 | /** 127 | * Uses iOS functionality of automatic scroll to top when clicking status bar 128 | */ 129 | public void scrollToTop() { 130 | getWebDriver().findElementByClassName("UIAStatusBar").click(); 131 | } 132 | 133 | public void openNotifications() { 134 | int midScreenWidth = getScreenWidth() / 2; 135 | webDriver.swipe(midScreenWidth, 0, midScreenWidth, getScreenHeight(), 1000); 136 | webDriver.quit(); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/main/java/com/jivesoftware/selenium/pagefactory/framework/browser/mobile/MobileBrowser.java: -------------------------------------------------------------------------------- 1 | package com.jivesoftware.selenium.pagefactory.framework.browser.mobile; 2 | 3 | import com.jivesoftware.selenium.pagefactory.framework.browser.Browser; 4 | import com.jivesoftware.selenium.pagefactory.framework.browser.web.WebBrowserType; 5 | import com.jivesoftware.selenium.pagefactory.framework.config.TimeoutsConfig; 6 | import com.jivesoftware.selenium.pagefactory.framework.exception.JiveWebDriverException; 7 | import com.jivesoftware.selenium.pagefactory.framework.pages.BaseTopLevelPage; 8 | import com.jivesoftware.selenium.pagefactory.framework.pages.TopLevelPage; 9 | import io.appium.java_client.AppiumDriver; 10 | import org.openqa.selenium.ScreenOrientation; 11 | import org.openqa.selenium.WebElement; 12 | import org.openqa.selenium.remote.DesiredCapabilities; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | 16 | import java.util.Map; 17 | import java.util.concurrent.TimeUnit; 18 | 19 | 20 | /** 21 | * Mobile Browser - Extends Selenium's Appium Driver functionality 22 | * Working on Android and iOS 23 | * Supports pages 24 | */ 25 | public abstract class MobileBrowser extends Browser { 26 | private static Logger logger = LoggerFactory.getLogger(MobileBrowser.class); 27 | 28 | protected String browserName; 29 | protected String platform; 30 | protected String platformName; 31 | protected String platformVersion; 32 | protected String deviceName; 33 | protected String newCommandTimeout; 34 | protected String automationName; 35 | protected String version; 36 | protected String autoLaunch; 37 | protected String app; 38 | protected boolean fullReset; 39 | 40 | protected MobileBrowser(String baseTestUrl, 41 | TimeoutsConfig timeoutsConfig, String browserName, 42 | String platform, 43 | String platformName, 44 | String platformVersion, 45 | String deviceName, 46 | String newCommandTimeout, 47 | String automationName, 48 | String version, 49 | String autoLaunch, 50 | String app, 51 | boolean fullReset) throws JiveWebDriverException { 52 | super(baseTestUrl, timeoutsConfig); 53 | this.browserName = browserName; 54 | this.platform = platform; 55 | this.platformName = platformName; 56 | this.platformVersion = platformVersion; 57 | this.deviceName = deviceName; 58 | this.newCommandTimeout = newCommandTimeout; 59 | this.automationName = automationName; 60 | this.version = version; 61 | this.autoLaunch = autoLaunch; 62 | this.app = app; 63 | this.fullReset = fullReset; 64 | } 65 | 66 | public void initializeBrowser() throws JiveWebDriverException { 67 | this.webDriver = createWebDriver(); 68 | this.webDriver.manage().timeouts().implicitlyWait(getImplicitWaitTimeoutMillis(), TimeUnit.MILLISECONDS); 69 | } 70 | 71 | public int getScreenWidth() { 72 | return this.webDriver.manage().window().getSize().getWidth(); 73 | } 74 | 75 | public int getScreenHeight() { 76 | return this.webDriver.manage().window().getSize().getHeight(); 77 | } 78 | 79 | protected abstract AppiumDriver createWebDriver() throws JiveWebDriverException; 80 | 81 | protected void printCapabilities(DesiredCapabilities desiredCapabilities) { 82 | logger.info("Loading capabilities.."); 83 | for (Map.Entry desiredCapability : desiredCapabilities.asMap().entrySet()) { 84 | logger.info(desiredCapability.getKey() + " - " + desiredCapability.getValue()); 85 | } 86 | } 87 | 88 | /** 89 | * Refresh the current page, without giving back a newly initialized Page object. 90 | */ 91 | @Override 92 | public void refreshPage() { 93 | runLeavePageHook(); 94 | BaseTopLevelPage currentPage = PAGE_UTILS.loadCurrentPage(BaseTopLevelPage.class, webDriver, this.getActions()); 95 | currentPage.refreshPage(); 96 | if (optionalCachedPage.isPresent()) { 97 | TopLevelPage cachedPage = optionalCachedPage.get().getCachedPage(); 98 | cachedPage.refreshElements(); 99 | } 100 | } 101 | 102 | /** 103 | * 104 | * @param pageClass - the class of the expected Page after refreshing. 105 | * @param - class that extends TopLevelPage class 106 | * @return - a page of the requested class 107 | */ 108 | @Override 109 | public T refreshPage(Class pageClass) { 110 | runLeavePageHook(); 111 | invalidateCachedPage(); 112 | T page = loadTopLevelPage(pageClass); 113 | page.refreshPage(); 114 | page = loadTopLevelPage(pageClass); 115 | setCachedPage(page); 116 | return page; 117 | } 118 | 119 | @Override 120 | public WebBrowserType getBrowserType() { 121 | return WebBrowserType.MOBILE; 122 | } 123 | 124 | public String getPlatformName() { 125 | return platformName; 126 | } 127 | 128 | public String getPlatform() { 129 | return platform; 130 | } 131 | 132 | public String getPlatformVersion() { 133 | return platformVersion; 134 | } 135 | 136 | public String getDeviceName() { 137 | return deviceName; 138 | } 139 | 140 | public String getApp() { 141 | return app; 142 | } 143 | 144 | //**********~~~~~~~~~~~~~ Mobile Actions ~~~~~~~~~~~~~~~************* 145 | 146 | public void rotateLandscape() { 147 | webDriver.rotate(ScreenOrientation.LANDSCAPE); 148 | } 149 | 150 | public void rotatePortrait() { 151 | webDriver.rotate(ScreenOrientation.PORTRAIT); 152 | } 153 | 154 | /** 155 | * Swipe from the right to left for a second 156 | */ 157 | public void swipeLeft() { 158 | webDriver.swipe(getScreenWidth() - 5, getScreenHeight() / 2, 5, getScreenHeight() / 2, 1000); 159 | } 160 | 161 | /** 162 | * Swipe from the left to right for a second 163 | */ 164 | public void swipeRight() { 165 | webDriver.swipe(5, getScreenHeight() / 2, getScreenWidth() - 5, getScreenHeight() / 2, 1000); 166 | } 167 | 168 | /** 169 | * Swipe from the top to bottom for a second 170 | */ 171 | public void dragDown() { 172 | int midScreen = getScreenWidth() / 2; 173 | webDriver.swipe(midScreen, 250, midScreen, getScreenHeight() - 250, 1500); 174 | } 175 | 176 | /** 177 | * Swipe from the down to up for a second 178 | */ 179 | public void dragUp() { 180 | int midScreen = webDriver.manage().window().getSize().getWidth() / 2; 181 | webDriver.swipe(midScreen, getScreenHeight() - 250, midScreen, 250, 2500); 182 | } 183 | 184 | /** 185 | * Swipe from the top to bottom for 2.5 seconds 186 | * @param yStart - 0 is the upper side of the smart-phone 187 | * @param yEnd - the end coordinate of the drag function 188 | */ 189 | public void drag(int yStart, int yEnd) { 190 | int midScreen = getScreenWidth() / 2; 191 | webDriver.swipe(midScreen, yStart, midScreen, yEnd, 2500); 192 | } 193 | 194 | public void drag(int yStart, int yEnd, int duration) { 195 | int midScreen = getScreenWidth() / 2; 196 | webDriver.swipe(midScreen, yStart, midScreen, yEnd, duration); 197 | } 198 | 199 | /** 200 | * 201 | * @param startX - 0 is the left side of the smart-phone 202 | * @param endX - end coordinate of the right/left movement 203 | * @param startY - 0 is the upper side of the smart-phone 204 | * @param endY - end coordinate of the up/down movement 205 | * @param duration - in milliseconds 206 | */ 207 | public void swipe(int startX, int endX, int startY, int endY, int duration) { 208 | webDriver.swipe(startX, startY, endX, endY, duration); 209 | } 210 | 211 | public void putApplicationToBackground(int duration) { 212 | webDriver.runAppInBackground(duration); 213 | } 214 | 215 | public void lockMobile(int duration) { 216 | webDriver.lockScreen(duration); 217 | } 218 | 219 | public void tap(int fingersNum, WebElement webElement, int duration) { 220 | webDriver.tap(fingersNum, webElement, duration); 221 | } 222 | public void tap(int fingersNum, int xLocation, int yLocation, int duration) { 223 | webDriver.tap(fingersNum, xLocation, yLocation, duration); 224 | } 225 | 226 | public void initApp() { 227 | webDriver.closeApp(); 228 | webDriver.launchApp(); 229 | } 230 | 231 | public abstract void scrollToTop(); 232 | } 233 | -------------------------------------------------------------------------------- /src/main/java/com/jivesoftware/selenium/pagefactory/framework/browser/mobile/MobilePlatformName.java: -------------------------------------------------------------------------------- 1 | package com.jivesoftware.selenium.pagefactory.framework.browser.mobile; 2 | 3 | /** 4 | * Created by amir on 8/13/14. 5 | */ 6 | public enum MobilePlatformName { 7 | IOS("iOS"), 8 | ANDROID("Android"); 9 | 10 | private String platformName; 11 | 12 | private MobilePlatformName(String platformName) { 13 | this.platformName = platformName; 14 | } 15 | 16 | public String getPlatformName() { 17 | return platformName; 18 | } 19 | 20 | public static MobilePlatformName forName(String name) { 21 | for (MobilePlatformName platform : MobilePlatformName.values()) { 22 | if (platform.toString().equalsIgnoreCase(name)) { 23 | return platform; 24 | } 25 | } 26 | throw new IllegalArgumentException("Mobile Platform Name must be 'ANDROID', 'IOS'"); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/jivesoftware/selenium/pagefactory/framework/browser/mobile/SwipeableWebDriver.java: -------------------------------------------------------------------------------- 1 | package com.jivesoftware.selenium.pagefactory.framework.browser.mobile; 2 | 3 | import io.appium.java_client.MobileElement; 4 | import io.appium.java_client.android.AndroidDriver; 5 | import org.openqa.selenium.Capabilities; 6 | import org.openqa.selenium.interactions.HasTouchScreen; 7 | import org.openqa.selenium.interactions.TouchScreen; 8 | import org.openqa.selenium.remote.RemoteTouchScreen; 9 | 10 | import java.net.URL; 11 | 12 | /** 13 | * Created by amir on 11/18/14. 14 | */ 15 | public class SwipeableWebDriver extends AndroidDriver implements HasTouchScreen { 16 | private RemoteTouchScreen touch; 17 | 18 | public SwipeableWebDriver(URL remoteAddress, Capabilities desiredCapabilities) { 19 | super(remoteAddress, desiredCapabilities); 20 | touch = new RemoteTouchScreen(getExecuteMethod()); 21 | } 22 | 23 | @Override 24 | public TouchScreen getTouch() { 25 | return touch; 26 | } 27 | 28 | @Override 29 | public MobileElement scrollTo(String s) { 30 | return null; 31 | } 32 | 33 | @Override 34 | public MobileElement scrollToExact(String s) { 35 | return null; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/jivesoftware/selenium/pagefactory/framework/browser/web/ChromeBrowser.java: -------------------------------------------------------------------------------- 1 | package com.jivesoftware.selenium.pagefactory.framework.browser.web; 2 | 3 | import com.google.common.collect.Maps; 4 | import com.jivesoftware.selenium.pagefactory.framework.actions.ChromeSeleniumActions; 5 | import com.jivesoftware.selenium.pagefactory.framework.config.TimeoutsConfig; 6 | import com.jivesoftware.selenium.pagefactory.framework.exception.JiveWebDriverException; 7 | import org.openqa.selenium.Platform; 8 | import org.openqa.selenium.WebDriver; 9 | import org.openqa.selenium.chrome.ChromeDriver; 10 | import org.openqa.selenium.chrome.ChromeDriverService; 11 | import org.openqa.selenium.chrome.ChromeOptions; 12 | import org.openqa.selenium.logging.LogEntries; 13 | import org.openqa.selenium.logging.LogType; 14 | import org.openqa.selenium.logging.LoggingPreferences; 15 | import org.openqa.selenium.remote.CapabilityType; 16 | import org.openqa.selenium.remote.DesiredCapabilities; 17 | import org.slf4j.Logger; 18 | import org.slf4j.LoggerFactory; 19 | 20 | import javax.annotation.Nullable; 21 | import java.io.File; 22 | import java.io.IOException; 23 | import java.util.*; 24 | import java.util.logging.Level; 25 | 26 | public class ChromeBrowser extends WebBrowser { 27 | private Optional> options; 28 | 29 | public ChromeBrowser(String baseTestUrl, 30 | TimeoutsConfig timeouts, 31 | Optional driverPath, 32 | Optional browserBinaryPath, 33 | Optional browserVersion, 34 | Optional browserLocale, 35 | Optional startWindowWidth, 36 | Optional startWindowHeight, 37 | Optional browserLogLevel, 38 | Optional browserLogFile, 39 | Optional platform, 40 | Optional> options) { 41 | 42 | super(baseTestUrl, timeouts, driverPath, browserBinaryPath, browserVersion, browserLocale, 43 | startWindowWidth, startWindowHeight, browserLogLevel, browserLogFile, platform); 44 | this.options = options; 45 | } 46 | 47 | private static final Logger logger = LoggerFactory.getLogger(ChromeBrowser.class); 48 | 49 | 50 | @Override 51 | public WebBrowserType getBrowserType() { 52 | return WebBrowserType.CHROME; 53 | } 54 | 55 | @Override 56 | public LoggingPreferences getLoggingPreferences() { 57 | Level level = getLogLevel(); 58 | LoggingPreferences loggingPreferences = new LoggingPreferences(); 59 | loggingPreferences.enable(LogType.BROWSER, level); 60 | loggingPreferences.enable(LogType.DRIVER, level); 61 | return loggingPreferences; 62 | } 63 | 64 | @Override 65 | public DesiredCapabilities getDesiredCapabilities() { 66 | DesiredCapabilities desiredCapabilities = DesiredCapabilities.chrome(); 67 | 68 | setCommonWebBrowserCapabilities(desiredCapabilities); 69 | 70 | desiredCapabilities.setCapability(CapabilityType.ForSeleniumServer.ENSURING_CLEAN_SESSION, true); 71 | 72 | // If the locale option is present and is not empty, then set this option in Chromedriver 73 | Optional browserLocale = getBrowserLocale(); 74 | if (browserLocale.isPresent() && !browserLocale.get().isEmpty()) { 75 | Map chromePrefs = Maps.newHashMap(); 76 | chromePrefs.put("intl.accept_languages", browserLocale.get()); 77 | desiredCapabilities.setCapability("chrome.prefs", chromePrefs); 78 | } 79 | 80 | // If the browser binary path is present and not empty, then set this as the Chrome Binary file 81 | Optional browserBinaryPath = getBrowserBinaryPath(); 82 | if (browserBinaryPath.isPresent() && !browserBinaryPath.get().isEmpty()) { 83 | desiredCapabilities.setCapability("chrome.binary", browserBinaryPath.get()); 84 | } 85 | 86 | // ChromeOptions 87 | ChromeOptions chromeOptions = new ChromeOptions(); 88 | 89 | // This tells Chromedriver we're running tests. 90 | // This eliminates the banner with the message "You are using an unsupported command-line flag --ignore-certificate-errors" 91 | if(options.isPresent()) { 92 | if (!options.get().contains("test-type")) { 93 | options.get().add("test-type"); 94 | } 95 | 96 | chromeOptions.addArguments(options.get()); 97 | desiredCapabilities.setCapability(ChromeOptions.CAPABILITY, chromeOptions); 98 | } 99 | return desiredCapabilities; 100 | } 101 | 102 | @Override 103 | public ChromeSeleniumActions getActions() { 104 | return new ChromeSeleniumActions(this); 105 | } 106 | 107 | @Override 108 | protected WebDriver createWebDriver() throws JiveWebDriverException { 109 | Optional driverPath = getWebDriverPath(); 110 | Optional browserLogFile = getBrowserLogFile(); 111 | 112 | ChromeDriverService.Builder builder = new ChromeDriverService.Builder() 113 | .usingAnyFreePort(); 114 | 115 | if (driverPath.isPresent() && !driverPath.get().isEmpty()) { 116 | File chromedriverFile = new File(driverPath.get()); 117 | builder.usingDriverExecutable(chromedriverFile); 118 | } 119 | 120 | if (browserLogFile.isPresent() && !browserLogFile.get().isEmpty()) { 121 | builder.withLogFile(new File(browserLogFile.get())); 122 | } 123 | 124 | ChromeDriverService service = builder.build(); 125 | 126 | try { 127 | service.start(); 128 | } catch (IOException e) { 129 | throw new JiveWebDriverException("Error starting Chrome driver service", e); 130 | } 131 | return new ChromeDriver(service, getDesiredCapabilities()); 132 | } 133 | 134 | @Nullable 135 | public LogEntries getBrowserLogEntries() { 136 | if (webDriver == null) { 137 | logger.info("WebDriver was null in ChromeBrowser#getBrowserLogEntries! Returning null."); 138 | return null; 139 | } 140 | logger.debug("Getting available log types..."); 141 | Set availableLogTypes = webDriver.manage().logs().getAvailableLogTypes(); 142 | logger.debug("Found log types: {}", availableLogTypes); 143 | if (availableLogTypes == null || !availableLogTypes.contains(LogType.BROWSER)) { 144 | return null; 145 | } 146 | LogEntries logs = webDriver.manage().logs().get(LogType.BROWSER); 147 | logger.info("Success - obtained Browser logs for a local ChromeBrowser!"); 148 | return logs; 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/main/java/com/jivesoftware/selenium/pagefactory/framework/browser/web/FirefoxBrowser.java: -------------------------------------------------------------------------------- 1 | package com.jivesoftware.selenium.pagefactory.framework.browser.web; 2 | 3 | import com.jivesoftware.selenium.pagefactory.framework.actions.FirefoxSeleniumActions; 4 | import com.jivesoftware.selenium.pagefactory.framework.config.TimeoutsConfig; 5 | import org.openqa.selenium.Platform; 6 | import org.openqa.selenium.WebDriver; 7 | import org.openqa.selenium.firefox.FirefoxBinary; 8 | import org.openqa.selenium.firefox.FirefoxDriver; 9 | import org.openqa.selenium.firefox.FirefoxProfile; 10 | import org.openqa.selenium.logging.LogEntries; 11 | import org.openqa.selenium.logging.LogType; 12 | import org.openqa.selenium.remote.CapabilityType; 13 | import org.openqa.selenium.remote.DesiredCapabilities; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | 17 | import javax.annotation.Nullable; 18 | import java.io.File; 19 | import java.util.Optional; 20 | import java.util.Set; 21 | 22 | public class FirefoxBrowser extends WebBrowser { 23 | 24 | public FirefoxBrowser(String baseTestUrl, 25 | TimeoutsConfig timeoutsConfig, 26 | Optional webDriverPath, 27 | Optional browserBinaryPath, 28 | Optional browserVersion, 29 | Optional browserLocale, 30 | Optional startWindowWidth, 31 | Optional startWindowHeight, 32 | Optional platform) { 33 | 34 | super(baseTestUrl, timeoutsConfig, webDriverPath, browserBinaryPath, browserVersion, browserLocale, startWindowWidth, startWindowHeight, platform); 35 | } 36 | 37 | private static final Logger logger = LoggerFactory.getLogger(FirefoxBrowser.class); 38 | 39 | 40 | @Override 41 | public WebBrowserType getBrowserType() { 42 | return WebBrowserType.FIREFOX; 43 | } 44 | 45 | @Override 46 | public DesiredCapabilities getDesiredCapabilities() { 47 | DesiredCapabilities desiredCapabilities = DesiredCapabilities.firefox(); 48 | 49 | setCommonWebBrowserCapabilities(desiredCapabilities); 50 | 51 | desiredCapabilities.setCapability(CapabilityType.ForSeleniumServer.ENSURING_CLEAN_SESSION, true); 52 | 53 | FirefoxProfile profile = new FirefoxProfile(); 54 | profile.setEnableNativeEvents(true); 55 | desiredCapabilities.setCapability(FirefoxDriver.PROFILE, profile); 56 | 57 | // If the browerBinaryPath is present, and it points to a real file, then set this as the Firefox Binary 58 | Optional browserBinaryPath = getBrowserBinaryPath(); 59 | if (browserBinaryPath.isPresent() && !browserBinaryPath.get().isEmpty()) { 60 | final String browserBinaryPathStr = browserBinaryPath.get(); 61 | File file = new File(browserBinaryPathStr); 62 | if (file.exists()) { 63 | desiredCapabilities.setCapability(FirefoxDriver.BINARY, new FirefoxBinary(file)); 64 | } 65 | } 66 | 67 | return desiredCapabilities; 68 | } 69 | 70 | @Override 71 | protected WebDriver createWebDriver() { 72 | DesiredCapabilities desiredCapabilities = getDesiredCapabilities(); 73 | return new FirefoxDriver(desiredCapabilities); 74 | } 75 | 76 | @Override 77 | public FirefoxSeleniumActions getActions() { 78 | return new FirefoxSeleniumActions(this); 79 | } 80 | 81 | @Nullable 82 | public LogEntries getBrowserLogEntries() { 83 | if (webDriver == null) { 84 | logger.info("WebDriver was null in FirefoxBrowser#getBrowserLogEntries! Returning null."); 85 | return null; 86 | } 87 | logger.debug("Getting available log types..."); 88 | Set availableLogTypes = webDriver.manage().logs().getAvailableLogTypes(); 89 | logger.debug("Found log types: {}", availableLogTypes); 90 | if (availableLogTypes == null || !availableLogTypes.contains(LogType.BROWSER)) { 91 | return null; 92 | } 93 | LogEntries logs = webDriver.manage().logs().get(LogType.BROWSER); 94 | logger.info("Success - obtained Browser logs for a local FirefoxBrowser!"); 95 | return logs; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/com/jivesoftware/selenium/pagefactory/framework/browser/web/InternetExplorerBrowser.java: -------------------------------------------------------------------------------- 1 | package com.jivesoftware.selenium.pagefactory.framework.browser.web; 2 | 3 | import com.jivesoftware.selenium.pagefactory.framework.actions.InternetExplorerActions; 4 | import com.jivesoftware.selenium.pagefactory.framework.config.TimeoutsConfig; 5 | import com.jivesoftware.selenium.pagefactory.framework.exception.JiveWebDriverException; 6 | import org.openqa.selenium.Platform; 7 | import org.openqa.selenium.WebDriver; 8 | import org.openqa.selenium.ie.InternetExplorerDriver; 9 | import org.openqa.selenium.ie.InternetExplorerDriverLogLevel; 10 | import org.openqa.selenium.logging.LogEntries; 11 | import org.openqa.selenium.remote.CapabilityType; 12 | import org.openqa.selenium.remote.DesiredCapabilities; 13 | 14 | import javax.annotation.Nullable; 15 | import java.util.Optional; 16 | import java.util.logging.Level; 17 | 18 | public class InternetExplorerBrowser extends WebBrowser { 19 | public InternetExplorerBrowser(String baseTestUrl, 20 | TimeoutsConfig timeouts, 21 | Optional driverPath, 22 | Optional browserBinaryPath, 23 | Optional browserVersion, 24 | Optional browserLocale, 25 | Optional startWindowWidth, 26 | Optional startWindowHeight, 27 | Optional browserLogLevel, 28 | Optional browserLogFile, 29 | Optional platform) { 30 | super(baseTestUrl, timeouts, driverPath, browserBinaryPath, browserVersion, browserLocale, startWindowWidth, startWindowHeight, browserLogLevel, browserLogFile, platform); 31 | } 32 | 33 | @Override 34 | public WebBrowserType getBrowserType() { 35 | return WebBrowserType.IE; 36 | } 37 | 38 | @Override 39 | public DesiredCapabilities getDesiredCapabilities() { 40 | DesiredCapabilities desiredCapabilities = DesiredCapabilities.internetExplorer(); 41 | 42 | setCommonWebBrowserCapabilities(desiredCapabilities); 43 | 44 | desiredCapabilities.setCapability(CapabilityType.ForSeleniumServer.ENSURING_CLEAN_SESSION, true); 45 | desiredCapabilities.setCapability(InternetExplorerDriver.NATIVE_EVENTS, false); 46 | desiredCapabilities.setCapability(InternetExplorerDriver.IGNORE_ZOOM_SETTING, true); 47 | desiredCapabilities.setCapability(InternetExplorerDriver.IE_ENSURE_CLEAN_SESSION, true); 48 | desiredCapabilities.setCapability(InternetExplorerDriver.ENABLE_PERSISTENT_HOVERING, true); 49 | 50 | 51 | Level logLevel = getLogLevel(); 52 | desiredCapabilities.setCapability(InternetExplorerDriver.LOG_LEVEL, convertJavaLogLevelToIeLogLevel(logLevel.toString())); 53 | 54 | Optional browserLogFile = getBrowserLogFile(); 55 | if (browserLogFile.isPresent() && !browserLogFile.get().isEmpty()) { 56 | desiredCapabilities.setCapability(InternetExplorerDriver.LOG_FILE, browserLogFile.get()); 57 | } 58 | 59 | return desiredCapabilities; 60 | } 61 | 62 | private static String convertJavaLogLevelToIeLogLevel(String javaLogLevel) { 63 | if ("WARN".equals(javaLogLevel)) { 64 | javaLogLevel = "WARNING"; 65 | } 66 | Level javaLevel = Level.parse(javaLogLevel); 67 | if (Level.ALL.equals(javaLevel)) { 68 | return InternetExplorerDriverLogLevel.TRACE.toString(); 69 | } else if (Level.CONFIG.equals(javaLevel)) { 70 | return InternetExplorerDriverLogLevel.TRACE.toString(); 71 | } else if (Level.FINE.equals(javaLevel)) { 72 | return InternetExplorerDriverLogLevel.DEBUG.toString(); 73 | } else if (Level.FINER.equals(javaLevel)) { 74 | return InternetExplorerDriverLogLevel.TRACE.toString(); 75 | } else if (Level.FINEST.equals(javaLevel)) { 76 | return InternetExplorerDriverLogLevel.TRACE.toString(); 77 | } else if (Level.INFO.equals(javaLevel)) { 78 | return InternetExplorerDriverLogLevel.INFO.toString(); 79 | } else if (Level.OFF.equals(javaLevel)) { 80 | return InternetExplorerDriverLogLevel.FATAL.toString(); 81 | } else if (Level.SEVERE.equals(javaLevel)) { 82 | return InternetExplorerDriverLogLevel.ERROR.toString(); 83 | } else if (Level.WARNING.equals(javaLevel)) { 84 | return InternetExplorerDriverLogLevel.WARN.toString(); 85 | } 86 | return InternetExplorerDriverLogLevel.INFO.toString(); 87 | } 88 | 89 | @Override 90 | public InternetExplorerActions getActions() { 91 | return new InternetExplorerActions(this); 92 | } 93 | 94 | @Override 95 | protected WebDriver createWebDriver() throws JiveWebDriverException { 96 | return new InternetExplorerDriver(getDesiredCapabilities()); 97 | } 98 | 99 | @Nullable 100 | public LogEntries getBrowserLogEntries() { 101 | return null; // Can't get Console logs for Internet Explorer, at least not remotely. 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/com/jivesoftware/selenium/pagefactory/framework/browser/web/RemoteBrowser.java: -------------------------------------------------------------------------------- 1 | package com.jivesoftware.selenium.pagefactory.framework.browser.web; 2 | 3 | import com.jivesoftware.selenium.pagefactory.framework.actions.SeleniumActions; 4 | import com.jivesoftware.selenium.pagefactory.framework.exception.JiveWebDriverException; 5 | import org.apache.commons.io.FileUtils; 6 | import org.openqa.selenium.OutputType; 7 | import org.openqa.selenium.TakesScreenshot; 8 | import org.openqa.selenium.WebDriver; 9 | import org.openqa.selenium.logging.LogEntries; 10 | import org.openqa.selenium.logging.LogType; 11 | import org.openqa.selenium.remote.Augmenter; 12 | import org.openqa.selenium.remote.DesiredCapabilities; 13 | import org.openqa.selenium.remote.LocalFileDetector; 14 | import org.openqa.selenium.remote.RemoteWebDriver; 15 | import org.slf4j.Logger; 16 | import org.slf4j.LoggerFactory; 17 | 18 | import javax.annotation.Nullable; 19 | import java.io.File; 20 | import java.io.IOException; 21 | import java.net.MalformedURLException; 22 | import java.net.URL; 23 | import java.util.Set; 24 | import java.util.logging.Level; 25 | 26 | /** 27 | *

Represents a RemoteBrowser, i.e. running a Browser on a Selenium Node controlled by a Selenium Hub. 28 | * To create an instance, pass in the "delegate" browser and the URL to the Selenium Hub. 29 | * Example Selenium Hub URL: http://hub.my.company.com:4444/wd/hub

30 | * 31 | * See http://code.google.com/p/selenium/wiki/Grid2 32 | */ 33 | public class RemoteBrowser extends WebBrowser { 34 | protected WebBrowser delegate; 35 | protected String seleniumHubURL; 36 | private static final Logger logger = LoggerFactory.getLogger(RemoteBrowser.class); 37 | 38 | 39 | public RemoteBrowser(WebBrowser delegate, String seleniumHubURL) { 40 | super(delegate.getBaseTestUrl(), 41 | delegate.getTimeouts(), 42 | delegate.getWebDriverPath(), 43 | delegate.getBrowserBinaryPath(), 44 | delegate.getBrowserVersion(), 45 | delegate.getBrowserLocale(), 46 | delegate.getStartWindowWidth(), 47 | delegate.getStartWindowHeight(), delegate.getBrowserLogLevel(), delegate.getBrowserLogFile(), delegate.getPlatform()); 48 | this.delegate = delegate; 49 | this.seleniumHubURL = seleniumHubURL; 50 | } 51 | 52 | @Override 53 | public WebBrowserType getBrowserType() { 54 | return delegate.getBrowserType(); 55 | } 56 | 57 | @Override 58 | public DesiredCapabilities getDesiredCapabilities() { 59 | return delegate.getDesiredCapabilities(); 60 | } 61 | 62 | @Override 63 | protected WebDriver createWebDriver() throws JiveWebDriverException { 64 | try { 65 | RemoteWebDriver driver = new RemoteWebDriver(new URL(seleniumHubURL), delegate.getDesiredCapabilities()); 66 | Level level = getLogLevel(); 67 | driver.setLogLevel(level); 68 | driver.setFileDetector(new LocalFileDetector()); // Allow to upload local files to remote webdriver 69 | // https://code.google.com/p/selenium/source/browse/java/client/src/org/openqa/selenium/remote/LocalFileDetector.java 70 | return driver; 71 | } catch (MalformedURLException e) { 72 | throw new JiveWebDriverException("Invalid Selenium Hub URL given: " + seleniumHubURL, e); 73 | } 74 | } 75 | 76 | @Override 77 | public SeleniumActions getActions() { 78 | SeleniumActions actions = delegate.getActions(); 79 | actions.setBrowser(this); //We are running remotely, so the Actions should use the RemoteBrowser and RemoteWebDriver 80 | return actions; 81 | } 82 | 83 | /** 84 | * Get the Browser logs (console logs) from the Remote Browser. 85 | * Added more logging to debug a 5 minute gap in time we saw in a recent failed test run. 86 | * The issue is probably unrelated to this, but it can't hurt to log more data so we can rule it out. 87 | * 88 | * @return - a {@link org.openqa.selenium.logging.LogEntries} with all the log entries since last time this was called. 89 | */ 90 | @Nullable 91 | public LogEntries getBrowserLogEntries() { 92 | if (delegate.getBrowserType() == WebBrowserType.IE) { 93 | logger.info("IE does not support getting Browser Logs remotely. Returning null from getBrowserLogEntries"); 94 | return null; 95 | } 96 | try { 97 | if (webDriver == null) { 98 | logger.info("The web driver was null in getBrowserLogEntries. Returning null."); 99 | return null; 100 | } 101 | logger.debug("Getting the available log types from remote Selenium node..."); 102 | Set availableLogTypes = webDriver.manage().logs().getAvailableLogTypes(); 103 | 104 | logger.debug("Found available log types: {}", String.valueOf(availableLogTypes)); 105 | 106 | if (availableLogTypes == null || !availableLogTypes.contains(LogType.BROWSER)) { 107 | logger.info("{} log type not allowed. Returning null.", LogType.BROWSER); 108 | return null; 109 | } 110 | logger.debug("Fetching logs from remote server..."); 111 | 112 | LogEntries logs = webDriver.manage().logs().get(LogType.BROWSER); 113 | 114 | logger.info("Success getting remote logs!"); 115 | 116 | return logs; 117 | } catch (Exception e) { 118 | // If some error occurs making the HTTP request to get logs, just return null. 119 | logger.info("Error retrieving remote logs: " + e.getMessage()); 120 | return null; 121 | } 122 | } 123 | 124 | /** 125 | * Save a screenshot in PNG format to given file name. 126 | * 127 | * @param filename 128 | * @return - a File representing the saved screenshot. 129 | */ 130 | @Override 131 | public File saveScreenshotToFile(String filename) { 132 | TakesScreenshot screenshotDriver; 133 | screenshotDriver = (TakesScreenshot) new Augmenter().augment(getWebDriver()); 134 | File scrFile = screenshotDriver.getScreenshotAs(OutputType.FILE); 135 | // Now you can do whatever you need to do with it, for example copy somewhere 136 | File outFile = new File(filename); 137 | try { 138 | FileUtils.copyFile(scrFile, outFile); 139 | } catch (IOException e) { 140 | logger.error("Error saving screenshot!", e); 141 | } 142 | return outFile; 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/main/java/com/jivesoftware/selenium/pagefactory/framework/browser/web/SafariBrowser.java: -------------------------------------------------------------------------------- 1 | package com.jivesoftware.selenium.pagefactory.framework.browser.web; 2 | 3 | import com.jivesoftware.selenium.pagefactory.framework.actions.SafariSeleniumActions; 4 | import com.jivesoftware.selenium.pagefactory.framework.config.TimeoutsConfig; 5 | import com.jivesoftware.selenium.pagefactory.framework.exception.JiveWebDriverException; 6 | import org.openqa.selenium.Platform; 7 | import org.openqa.selenium.WebDriver; 8 | import org.openqa.selenium.logging.LogEntries; 9 | import org.openqa.selenium.logging.LogType; 10 | import org.openqa.selenium.logging.LoggingPreferences; 11 | import org.openqa.selenium.remote.CapabilityType; 12 | import org.openqa.selenium.remote.DesiredCapabilities; 13 | import org.openqa.selenium.safari.SafariDriver; 14 | import org.openqa.selenium.safari.SafariOptions; 15 | import org.slf4j.Logger; 16 | import org.slf4j.LoggerFactory; 17 | 18 | import javax.annotation.Nullable; 19 | import java.util.Optional; 20 | import java.util.Set; 21 | import java.util.logging.Level; 22 | 23 | public class SafariBrowser extends WebBrowser { 24 | public SafariBrowser(String baseTestUrl, 25 | TimeoutsConfig timeouts, 26 | Optional driverPath, 27 | Optional browserBinaryPath, 28 | Optional browserVersion, 29 | Optional browserLocale, 30 | Optional startWindowWidth, 31 | Optional startWindowHeight, 32 | Optional browserLogLevel, 33 | Optional browserLogFile, 34 | Optional platform) { 35 | 36 | super(baseTestUrl, timeouts, driverPath, browserBinaryPath, browserVersion, browserLocale, 37 | startWindowWidth, startWindowHeight, browserLogLevel, browserLogFile, platform); 38 | } 39 | 40 | private static final Logger logger = LoggerFactory.getLogger(SafariBrowser.class); 41 | 42 | 43 | @Override 44 | public WebBrowserType getBrowserType() { 45 | return WebBrowserType.SAFARI; 46 | } 47 | 48 | @Override 49 | public LoggingPreferences getLoggingPreferences() { 50 | Level level = getLogLevel(); 51 | LoggingPreferences loggingPreferences = new LoggingPreferences(); 52 | loggingPreferences.enable(LogType.BROWSER, level); 53 | loggingPreferences.enable(LogType.DRIVER, level); 54 | return loggingPreferences; 55 | } 56 | 57 | @Override 58 | public DesiredCapabilities getDesiredCapabilities() { 59 | DesiredCapabilities desiredCapabilities = DesiredCapabilities.safari(); 60 | 61 | setCommonWebBrowserCapabilities(desiredCapabilities); 62 | 63 | desiredCapabilities.setCapability(CapabilityType.ForSeleniumServer.ENSURING_CLEAN_SESSION, true); 64 | 65 | SafariOptions safariOptions = new SafariOptions(); 66 | safariOptions.setUseCleanSession(true); 67 | 68 | // Selenium seems to be broken if we specify SafariOptions for a RemoteWebDriver. 69 | // java.lang.ClassCastException: org.json.JSONObject cannot be cast to java.lang.String 70 | // at org.openqa.selenium.remote.BeanToJsonConverter.convertObject(BeanToJsonConverter.java:202) 71 | 72 | // desiredCapabilities.setCapability(SafariOptions.CAPABILITY, safariOptions); 73 | 74 | return desiredCapabilities; 75 | } 76 | 77 | @Override 78 | public SafariSeleniumActions getActions() { 79 | return new SafariSeleniumActions(this); 80 | } 81 | 82 | @Override 83 | protected WebDriver createWebDriver() throws JiveWebDriverException { 84 | 85 | return new SafariDriver(getDesiredCapabilities()); 86 | } 87 | 88 | @Nullable 89 | public LogEntries getBrowserLogEntries() { 90 | if (webDriver == null) { 91 | logger.info("WebDriver was null in ChromeBrowser#getBrowserLogEntries! Returning null."); 92 | return null; 93 | } 94 | logger.debug("Getting available log types..."); 95 | Set availableLogTypes = webDriver.manage().logs().getAvailableLogTypes(); 96 | logger.debug("Found log types: {}", availableLogTypes); 97 | if (availableLogTypes == null || !availableLogTypes.contains(LogType.BROWSER)) { 98 | return null; 99 | } 100 | LogEntries logs = webDriver.manage().logs().get(LogType.BROWSER); 101 | logger.info("Success - obtained Browser logs for a local ChromeBrowser!"); 102 | return logs; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/com/jivesoftware/selenium/pagefactory/framework/browser/web/WebBrowser.java: -------------------------------------------------------------------------------- 1 | package com.jivesoftware.selenium.pagefactory.framework.browser.web; 2 | 3 | import com.jivesoftware.selenium.pagefactory.framework.browser.Browser; 4 | import com.jivesoftware.selenium.pagefactory.framework.config.TimeoutsConfig; 5 | import com.jivesoftware.selenium.pagefactory.framework.exception.JiveWebDriverException; 6 | import com.jivesoftware.selenium.pagefactory.framework.pages.BaseTopLevelPage; 7 | import com.jivesoftware.selenium.pagefactory.framework.pages.TopLevelPage; 8 | import com.jivesoftware.selenium.pagefactory.framework.webservice.EndpointBuilder; 9 | import org.openqa.selenium.Dimension; 10 | import org.openqa.selenium.Platform; 11 | import org.openqa.selenium.WebDriver; 12 | import org.openqa.selenium.logging.LogEntries; 13 | import org.openqa.selenium.logging.LogType; 14 | import org.openqa.selenium.logging.LoggingPreferences; 15 | import org.openqa.selenium.remote.CapabilityType; 16 | import org.openqa.selenium.remote.DesiredCapabilities; 17 | import org.slf4j.Logger; 18 | import org.slf4j.LoggerFactory; 19 | 20 | import javax.annotation.Nullable; 21 | import java.net.URI; 22 | import java.util.Optional; 23 | import java.util.concurrent.TimeUnit; 24 | import java.util.logging.Level; 25 | 26 | /** 27 | * Base Browser class. 28 | * Contains a lot of the configuration that is common across browsers. 29 | * Subclasses must implement getBrowserType, getDesiredCapabilities, isRemote, and getActions 30 | */ 31 | public abstract class WebBrowser extends Browser { 32 | private static final Logger logger = LoggerFactory.getLogger(WebBrowser.class); 33 | 34 | private final Optional webDriverPath; 35 | private final Optional browserBinaryPath; 36 | private final Optional browserVersion; 37 | private final Optional browserLocale; 38 | private final Optional startWindowWidth; 39 | private final Optional startWindowHeight; 40 | private final Optional browserLogLevel; 41 | private final Optional browserLogFile; 42 | private final Optional platform; 43 | 44 | public WebBrowser(String baseTestUrl, 45 | TimeoutsConfig timeouts, 46 | Optional webDriverPath, 47 | Optional browserBinaryPath, 48 | Optional browserVersion, 49 | Optional browserLocale, 50 | Optional startWindowWidth, 51 | Optional startWindowHeight, 52 | Optional platform) { 53 | 54 | this(baseTestUrl, timeouts, webDriverPath, browserBinaryPath, browserVersion, browserLocale, 55 | startWindowWidth, startWindowHeight, 56 | Optional.empty(), Optional.empty(), platform); 57 | 58 | } 59 | 60 | public WebBrowser(String baseTestUrl, 61 | TimeoutsConfig timeouts, 62 | Optional webDriverPath, 63 | Optional browserBinaryPath, 64 | Optional browserVersion, 65 | Optional browserLocale, 66 | Optional startWindowWidth, 67 | Optional startWindowHeight, 68 | Optional browserLogLevel, 69 | Optional browserLogFile, 70 | Optional platform) { 71 | super(baseTestUrl, timeouts); 72 | this.webDriverPath = webDriverPath; 73 | this.browserBinaryPath = browserBinaryPath; 74 | this.browserVersion = browserVersion; 75 | this.browserLocale = browserLocale; 76 | this.startWindowWidth = startWindowWidth; 77 | this.startWindowHeight = startWindowHeight; 78 | this.browserLogLevel = browserLogLevel; 79 | this.browserLogFile = browserLogFile; 80 | this.platform = platform; 81 | } 82 | 83 | /** 84 | * Initialize the browser. This creates a web driver instance, which opens the Browser to a blank page. 85 | * Resize the window to the configured values. 86 | * 87 | * @throws JiveWebDriverException 88 | */ 89 | public void initializeBrowser() throws JiveWebDriverException { 90 | this.webDriver = createWebDriver(); 91 | if (startWindowWidth.isPresent() && startWindowHeight.isPresent()) { 92 | this.webDriver.manage().window().setSize(new Dimension(startWindowWidth.get(), startWindowHeight.get())); 93 | } 94 | // Safari web driver doesn't support setting timeouts. 95 | if (getBrowserType() != WebBrowserType.SAFARI) { 96 | this.webDriver.manage().timeouts().pageLoadTimeout(getPageTimeoutSeconds(), TimeUnit.SECONDS); 97 | this.webDriver.manage().timeouts().implicitlyWait(getImplicitWaitTimeoutMillis(), TimeUnit.MILLISECONDS); 98 | } 99 | logger.info("SUCCESS - Created WebBrowser of type {}: {}", getBrowserType(), webDriver); 100 | } 101 | 102 | public abstract WebBrowserType getBrowserType(); 103 | 104 | public abstract DesiredCapabilities getDesiredCapabilities(); 105 | 106 | public LoggingPreferences getLoggingPreferences() { 107 | Level level = getLogLevel(); 108 | LoggingPreferences loggingPreferences = new LoggingPreferences(); 109 | loggingPreferences.enable(LogType.BROWSER, level); 110 | loggingPreferences.enable(LogType.CLIENT, level); 111 | loggingPreferences.enable(LogType.DRIVER, level); 112 | loggingPreferences.enable(LogType.SERVER, level); 113 | return loggingPreferences; 114 | } 115 | 116 | public Optional getStartWindowWidth() { 117 | return startWindowWidth; 118 | } 119 | 120 | public Optional getStartWindowHeight() { 121 | return startWindowHeight; 122 | } 123 | 124 | public Optional getWebDriverPath() { 125 | return webDriverPath; 126 | } 127 | 128 | public Optional getBrowserBinaryPath() { 129 | return browserBinaryPath; 130 | } 131 | 132 | public Optional getBrowserVersion() { 133 | return browserVersion; 134 | } 135 | 136 | public Optional getBrowserLocale() { 137 | return browserLocale; 138 | } 139 | 140 | public Optional getBrowserLogLevel() { 141 | return browserLogLevel; 142 | } 143 | 144 | public Level getLogLevel() { 145 | return browserLogLevel.isPresent() ? browserLogLevel.get() : Level.WARNING; 146 | } 147 | 148 | public Optional getBrowserLogFile() { 149 | return browserLogFile; 150 | } 151 | 152 | public Optional getPlatform() { 153 | return platform; 154 | } 155 | 156 | public TimeoutsConfig getTimeouts() { 157 | return timeouts; 158 | } 159 | 160 | /** 161 | * Opens a new page in the Browser by URL. An absolute URL or the path can be provided. 162 | * If a path is provided, then the baseTestUrl provided when creating the browser will be used as the 163 | * base of the URL. 164 | * 165 | * Invalidates the cached page and loads a fresh new page. 166 | * 167 | * @param href - the href from a link, which may be a relative path from baseTestUrl or may be absolute 168 | * @return - a generic {@link com.jivesoftware.selenium.pagefactory.framework.pages.BaseTopLevelPage} 169 | * page object. To open a page with more specific functionality, you must extend 170 | * {@link com.jivesoftware.selenium.pagefactory.framework.pages.BaseTopLevelPage} and then 171 | * call {@link #openPageByURL(String, Class)}. 172 | */ 173 | public TopLevelPage openPageByURL(String href) { 174 | return openPageByURL(href, BaseTopLevelPage.class); 175 | } 176 | 177 | /** 178 | * Opens a new page in the Browser by URL. An absolute URL or the path can be provided. 179 | * 180 | * Invalidates the cached page and loads a fresh new page. 181 | * 182 | * @param uri - the href from a link, which may be a relative path from baseTestUrl or may be absolute 183 | * @param pageClass - the {@link com.jivesoftware.selenium.pagefactory.framework.pages.TopLevelPage} class to load. 184 | */ 185 | public T openPageByURL(URI uri, Class pageClass) { 186 | URI absoluteURI; 187 | if (uri.isAbsolute()) { 188 | absoluteURI = uri; 189 | } else { 190 | String fullURIStr = EndpointBuilder.uri(baseTestUrl, "/", uri.toString()); 191 | absoluteURI = URI.create(fullURIStr); 192 | } 193 | logger.info("Opening web page by URL {}", absoluteURI); 194 | runLeavePageHook(); 195 | invalidateCachedPage(); 196 | T page = PAGE_UTILS.loadPageFromURL(absoluteURI, pageClass, getWebDriver(), getActions()); 197 | setCachedPage(page); 198 | return page; 199 | } 200 | 201 | /** 202 | * Opens a new page in the Browser by URL. An absolute URL or the path can be provided. 203 | * 204 | * Invalidates the cached page and loads a fresh new page. 205 | * 206 | * @param href - the href from a link, which may be a relative path from baseTestUrl or may be absolute 207 | * @param pageClass - the {@link com.jivesoftware.selenium.pagefactory.framework.pages.TopLevelPage} class to load. 208 | */ 209 | public T openPageByURL(String href, Class pageClass) { 210 | URI uri = URI.create(href); 211 | return openPageByURL(uri, pageClass); 212 | } 213 | 214 | /** 215 | * Refresh the current page, without giving back a newly initialized Page object. 216 | */ 217 | public void refreshPage() { 218 | runLeavePageHook(); 219 | webDriver.navigate().refresh(); 220 | if (optionalCachedPage.isPresent()) { 221 | TopLevelPage cachedPage = optionalCachedPage.get().getCachedPage(); 222 | cachedPage.refreshElements(); 223 | } 224 | } 225 | 226 | /** 227 | * @param pageClass - the class of the expected Page after refreshing. 228 | */ 229 | public T refreshPage(Class pageClass) { 230 | runLeavePageHook(); 231 | invalidateCachedPage(); 232 | webDriver.navigate().refresh(); 233 | T page = loadTopLevelPage(pageClass); 234 | setCachedPage(page); 235 | return page; 236 | } 237 | 238 | public void cleanSession() { 239 | webDriver.manage().deleteAllCookies(); 240 | } 241 | 242 | @Nullable 243 | public abstract LogEntries getBrowserLogEntries(); 244 | 245 | /** 246 | * Helper to set properties of the DesiredCapabilities that are common across all browsers. 247 | * @param desiredCapabilities 248 | */ 249 | protected void setCommonWebBrowserCapabilities(DesiredCapabilities desiredCapabilities) { 250 | // If a required version is present, then set this as a desired capability. Only affects Remote browsers. 251 | Optional browserVersion = getBrowserVersion(); 252 | if (browserVersion.isPresent() && !browserVersion.get().isEmpty()) { 253 | desiredCapabilities.setCapability(CapabilityType.VERSION, browserVersion.get()); 254 | } 255 | 256 | // Set logging preferences. 257 | LoggingPreferences loggingPreferences = getLoggingPreferences(); 258 | desiredCapabilities.setCapability(CapabilityType.LOGGING_PREFS, loggingPreferences); 259 | 260 | // If a platform is specified, set this desired capability. Only affects Remote browsers. 261 | Optional platform = getPlatform(); 262 | if (platform.isPresent()) { 263 | desiredCapabilities.setPlatform(platform.get()); 264 | } 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /src/main/java/com/jivesoftware/selenium/pagefactory/framework/browser/web/WebBrowserType.java: -------------------------------------------------------------------------------- 1 | package com.jivesoftware.selenium.pagefactory.framework.browser.web; 2 | 3 | /** 4 | * Represents the type of Browser being created. 5 | * TODO: Add support for Safari by creating a SafariBrowser class and figure out the correct DesiredCapabilities for configuring Safari. 6 | */ 7 | public enum WebBrowserType { 8 | IE, CHROME, FIREFOX, SAFARI, MOBILE; 9 | 10 | public static WebBrowserType forName(String name) { 11 | for (WebBrowserType type: WebBrowserType.values()) { 12 | if (type.toString().equalsIgnoreCase(name)) { 13 | return type; 14 | } 15 | } 16 | throw new IllegalArgumentException("WebBrowserType must be 'IE', 'CHROME', 'FIREFOX', 'SAFARI', or 'MOBILE'"); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/jivesoftware/selenium/pagefactory/framework/config/DefaultTimeouts.java: -------------------------------------------------------------------------------- 1 | package com.jivesoftware.selenium.pagefactory.framework.config; 2 | 3 | /** 4 | * Created by charles.capps on 8/13/14. 5 | * 6 | * Default timeout constants, only for use within this package. 7 | * @see TimeoutType 8 | * @see TimeoutsConfig 9 | */ 10 | interface DefaultTimeouts { 11 | public static final int CLICK_TIMEOUT_SECONDS = 5; 12 | public static final int PRESENCE_TIMEOUT_SECONDS = 5; 13 | public static final int POLLING_WITH_REFRESH_TIMEOUT_SECONDS = 30; 14 | public static final int REFRESH_TIMEOUT_SECONDS = 5; 15 | public static final int SHORT_TIMEOUT_SECONDS = 1; 16 | public static final int MEDIUM_TIMEOUT_SECONDS = 5; 17 | public static final int LONG_TIMEOUT_SECONDS = 20; 18 | public static final int PAUSE_BETWEEN_KEYS_MILLIS = 50; 19 | public static final int PAUSE_BETWEEN_TRIES_MILLIS = 200; 20 | public static final int PAUSE_BETWEEN_REFRESH_SECONDS = 5; 21 | public static final int PAGE_LOAD_TIMEOUT_SECONDS = 80; 22 | public static final int PAGE_READY_TIMEOUT_SECONDS = 10; 23 | public static final int IMPLICIT_WAIT_TIMEOUT_MILLIS = 2000; 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/jivesoftware/selenium/pagefactory/framework/config/TimeoutType.java: -------------------------------------------------------------------------------- 1 | package com.jivesoftware.selenium.pagefactory.framework.config; 2 | 3 | /** 4 | * Created by charles.capps on 8/13/14. 5 | * 6 | *

Represents the type of Timeout. This is used rather than allowing Magic Numbers to be used arbitrarily for timeouts. 7 | * All timeouts represent values in seconds.

8 | * 9 | *

A TimeoutType is passed into various methods of {@link com.jivesoftware.selenium.pagefactory.framework.actions.SeleniumActions}. 10 | * This indicates whether to use the DEFAULT timeout for the action, or to use a different TimeoutType.

11 | * 12 | *

The "DEFAULT" value is special. It causes the timeout used to be the default timeout for the context. 13 | * DEFAULT causes the TimeoutType that is the default for the given method being called to be used.

14 | * 15 | *

You want to use DEFAULT in the vast majority of cases. The TimeoutType used for a SeleniumActions method should 16 | * only be overridden when absolutely necessary.

17 | * 18 | * @see com.jivesoftware.selenium.pagefactory.framework.config.TimeoutsConfig 19 | * @see com.jivesoftware.selenium.pagefactory.framework.browser.LocalBrowserBuilder 20 | * @see com.jivesoftware.selenium.pagefactory.framework.browser.RemoteBrowserBuilder 21 | * 22 | */ 23 | public enum TimeoutType { 24 | /** Default timeout. The timeout used is context-sensitive based on the method in SeleniumActions you are calling. 25 | * This is what you want to use most of the time. **/ 26 | DEFAULT, 27 | /** Timeout to wait for an element to be click-able when attempting to click. **/ 28 | CLICK_TIMEOUT, 29 | /** Timeout to wait for an element to be present on the DOM or to be visible. **/ 30 | WEB_ELEMENT_PRESENCE_TIMEOUT, 31 | /** Timeout waiting for the page to refresh, or for an element to become stale or removed from the DOM. **/ 32 | PAGE_REFRESH_TIMEOUT, 33 | /** Timeout waiting for a new page to load, used to set the internal Selenium page load timeout **/ 34 | PAGE_LOAD_TIMEOUT, 35 | /** Timeout waiting for elements to be present on a page after page load **/ 36 | PAGE_READY_TIMEOUT, 37 | /** Timeout waiting on anything that requires refreshing the page many times, e.g. something like an Activity Stream. 38 | * Used by SeleniumActions#findElementContainingTextWithRefresh and other methods that refresh the page until something is present. **/ 39 | POLLING_WITH_REFRESH_TIMEOUT, 40 | /** Arbitrary short timeout configured by the client. **/ 41 | SHORT, 42 | /** Arbitrary medium timeout configured by the client. **/ 43 | MEDIUM, 44 | /** Arbitrary long timeout configured by the client. **/ 45 | LONG, 46 | /** Fixed timeouts not affected by configuration. 47 | * Usage is discouraged; but this is useful when you need an exact timeout that can't be changed by configuration. **/ 48 | ONE_SECOND, TWO_SECONDS, FIVE_SECONDS, TEN_SECONDS, TWENTY_SECONDS, SIXTY_SECONDS, NINETY_SECONDS, 49 | TWO_MINUTES, THREE_MINUTES, FIVE_MINUTES, TEN_MINUTES, THIRTY_MINUTES, SIXTY_MINUTES, NINETY_MINUTES, TWO_HOURS 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/jivesoftware/selenium/pagefactory/framework/config/TimeoutsConfig.java: -------------------------------------------------------------------------------- 1 | package com.jivesoftware.selenium.pagefactory.framework.config; 2 | 3 | import com.google.common.base.Preconditions; 4 | import com.jivesoftware.selenium.pagefactory.framework.pages.Page; 5 | 6 | import java.util.concurrent.TimeUnit; 7 | 8 | /** 9 | *

Represents the timeout configuration used by a {@link com.jivesoftware.selenium.pagefactory.framework.browser.Browser}.

10 | * 11 | *

Use TimeoutsConfig.defaultTimeoutsConfig() to get an instance with default timeout values.

12 | * 13 | *

Use TimeoutsConfig.builder() to get a Builder that begins with default timeout values. 14 | * Then call builder.build() to get a finalized TimeoutsConfig instance.

15 | * 16 | * @see TimeoutType 17 | */ 18 | public final class TimeoutsConfig { 19 | 20 | // Standard timeouts for most common usages 21 | private final int clickTimeoutSeconds; 22 | private final int webElementPresenceTimeoutSeconds; 23 | private final int pollingWithRefreshTimeoutSeconds; 24 | private final int pageRefreshTimeoutSeconds; 25 | 26 | // Arbitrary timeouts configured by client 27 | private final int shortTimeoutSeconds; 28 | private final int mediumTimeoutSeconds; 29 | private final int longTimeoutSeconds; 30 | 31 | // Pauses when polling or entering keys 32 | private final int pauseBetweenKeysMillis; 33 | private final int pauseBetweenTriesMillis; 34 | private final int pauseBetweenRefreshSeconds; 35 | 36 | // Timeouts used for configuring the underlying WebDriver 37 | private final int pageLoadTimeoutSeconds; 38 | private final int pageReadyTimeoutSeconds; 39 | private final int implicitWaitTimeoutMillis; 40 | 41 | /** 42 | * Return a Builder for constructing a TimeoutsConfig instance. 43 | * The Builder is populated with default timeouts, and you can modify them as desired, then call builder.build(). 44 | * @return - a {@link com.jivesoftware.selenium.pagefactory.framework.config.TimeoutsConfig.Builder} 45 | */ 46 | public static Builder builder() { 47 | return new Builder(); 48 | } 49 | 50 | /** 51 | * @return a {@link TimeoutsConfig} with default timeout values 52 | */ 53 | public static TimeoutsConfig defaultTimeoutsConfig() { 54 | return new Builder().build(); 55 | } 56 | 57 | private TimeoutsConfig(int clickTimeoutSeconds, int webElementPresenceTimeoutSeconds, int pollingWithRefreshTimeoutSeconds, 58 | int pageRefreshTimeoutSeconds, int shortTimeoutSeconds, int mediumTimeoutSeconds, int longTimeoutSeconds, 59 | int pauseBetweenKeysMillis, int pauseBetweenTriesMillis, int pauseBetweenRefreshSeconds, 60 | int pageLoadTimeoutSeconds, int pageReadyTimeoutSeconds, int implicitWaitTimeoutMillis) { 61 | this.clickTimeoutSeconds = clickTimeoutSeconds; 62 | this.webElementPresenceTimeoutSeconds = webElementPresenceTimeoutSeconds; 63 | this.pollingWithRefreshTimeoutSeconds = pollingWithRefreshTimeoutSeconds; 64 | this.pageRefreshTimeoutSeconds = pageRefreshTimeoutSeconds; 65 | this.shortTimeoutSeconds = shortTimeoutSeconds; 66 | this.mediumTimeoutSeconds = mediumTimeoutSeconds; 67 | this.longTimeoutSeconds = longTimeoutSeconds; 68 | this.pauseBetweenKeysMillis = pauseBetweenKeysMillis; 69 | this.pauseBetweenTriesMillis = pauseBetweenTriesMillis; 70 | this.pauseBetweenRefreshSeconds = pauseBetweenRefreshSeconds; 71 | this.pageLoadTimeoutSeconds = pageLoadTimeoutSeconds; 72 | this.pageReadyTimeoutSeconds = pageReadyTimeoutSeconds; 73 | this.implicitWaitTimeoutMillis = implicitWaitTimeoutMillis; 74 | } 75 | 76 | /** 77 | * Return the timeout in seconds for the given TimeoutType. 78 | * 79 | * It doesn't make sense to pass in the DEFAULT timeout type, because that is a context-sensitive timeout. 80 | * 81 | * @param timeout - the TimeoutType (other than DEFAULT) to get the timeout in seconds for. 82 | * @return - the timeout in seconds 83 | */ 84 | public int getTimeoutInSeconds(TimeoutType timeout) { 85 | Preconditions.checkNotNull(timeout, "Cannot get timeout for null timeout."); 86 | Preconditions.checkArgument(timeout != TimeoutType.DEFAULT, "Can only get the standard timeout for timeout types other than DEFAULT"); 87 | switch (timeout) { 88 | 89 | case CLICK_TIMEOUT: 90 | return getClickTimeoutSeconds(); 91 | case WEB_ELEMENT_PRESENCE_TIMEOUT: 92 | return getWebElementPresenceTimeoutSeconds(); 93 | case POLLING_WITH_REFRESH_TIMEOUT: 94 | return getPollingWithRefreshTimeoutSeconds(); 95 | case PAGE_REFRESH_TIMEOUT: 96 | return getPageRefreshTimeoutSeconds(); 97 | case PAGE_LOAD_TIMEOUT: 98 | return getPageLoadTimeoutSeconds(); 99 | case PAGE_READY_TIMEOUT: 100 | return getPageReadyTimeoutSeconds(); 101 | case SHORT: 102 | return getShortTimeoutSeconds(); 103 | case MEDIUM: 104 | return getMediumTimeoutSeconds(); 105 | case LONG: 106 | return getLongTimeoutSeconds(); 107 | case ONE_SECOND: 108 | return 1; 109 | case TWO_SECONDS: 110 | return 2; 111 | case FIVE_SECONDS: 112 | return 5; 113 | case TEN_SECONDS: 114 | return 10; 115 | case TWENTY_SECONDS: 116 | return 20; 117 | case SIXTY_SECONDS: 118 | return 60; 119 | case NINETY_SECONDS: 120 | return 90; 121 | case TWO_MINUTES: 122 | return (int)TimeUnit.MINUTES.toSeconds(2); 123 | case THREE_MINUTES: 124 | return (int)TimeUnit.MINUTES.toSeconds(3); 125 | case FIVE_MINUTES: 126 | return (int)TimeUnit.MINUTES.toSeconds(5); 127 | case TEN_MINUTES: 128 | return (int)TimeUnit.MINUTES.toSeconds(10); 129 | case THIRTY_MINUTES: 130 | return (int)TimeUnit.MINUTES.toSeconds(30); 131 | case SIXTY_MINUTES: 132 | return (int)TimeUnit.MINUTES.toSeconds(60); 133 | case NINETY_MINUTES: 134 | return (int)TimeUnit.MINUTES.toSeconds(90); 135 | case TWO_HOURS: 136 | return (int)TimeUnit.HOURS.toSeconds(2); 137 | default: 138 | return getWebElementPresenceTimeoutSeconds(); 139 | } 140 | } 141 | 142 | public int getClickTimeoutSeconds() { 143 | return clickTimeoutSeconds; 144 | } 145 | 146 | public int getWebElementPresenceTimeoutSeconds() { 147 | return webElementPresenceTimeoutSeconds; 148 | } 149 | 150 | public int getPollingWithRefreshTimeoutSeconds() { 151 | return pollingWithRefreshTimeoutSeconds; 152 | } 153 | 154 | public int getPageRefreshTimeoutSeconds() { 155 | return pageRefreshTimeoutSeconds; 156 | } 157 | 158 | public int getShortTimeoutSeconds() { 159 | return shortTimeoutSeconds; 160 | } 161 | 162 | public int getMediumTimeoutSeconds() { 163 | return mediumTimeoutSeconds; 164 | } 165 | 166 | public int getLongTimeoutSeconds() { 167 | return longTimeoutSeconds; 168 | } 169 | 170 | public int getPauseBetweenKeysMillis() { 171 | return pauseBetweenKeysMillis; 172 | } 173 | 174 | public int getPauseBetweenTriesMillis() { 175 | return pauseBetweenTriesMillis; 176 | } 177 | 178 | public int getPauseBetweenRefreshSeconds() { 179 | return pauseBetweenRefreshSeconds; 180 | } 181 | 182 | public int getPageLoadTimeoutSeconds() { 183 | return pageLoadTimeoutSeconds; 184 | } 185 | 186 | public int getPageReadyTimeoutSeconds() { 187 | return pageReadyTimeoutSeconds; 188 | } 189 | 190 | public int getImplicitWaitTimeoutMillis() { 191 | return implicitWaitTimeoutMillis; 192 | } 193 | 194 | public static final class Builder { 195 | public Builder() { 196 | this.clickTimeoutSeconds = DefaultTimeouts.CLICK_TIMEOUT_SECONDS; 197 | this.webElementPresenceTimeoutSeconds = DefaultTimeouts.PRESENCE_TIMEOUT_SECONDS; 198 | this.pollingWithRefreshTimeoutSeconds = DefaultTimeouts.POLLING_WITH_REFRESH_TIMEOUT_SECONDS; 199 | this.pageRefreshTimeoutSeconds = DefaultTimeouts.REFRESH_TIMEOUT_SECONDS; 200 | this.shortTimeoutSeconds = DefaultTimeouts.SHORT_TIMEOUT_SECONDS; 201 | this.mediumTimeoutSeconds = DefaultTimeouts.MEDIUM_TIMEOUT_SECONDS; 202 | this.longTimeoutSeconds = DefaultTimeouts.LONG_TIMEOUT_SECONDS; 203 | this.pauseBetweenKeysMillis = DefaultTimeouts.PAUSE_BETWEEN_KEYS_MILLIS; 204 | this.pauseBetweenTriesMillis = DefaultTimeouts.PAUSE_BETWEEN_TRIES_MILLIS; 205 | this.pauseBetweenRefreshSeconds = DefaultTimeouts.PAUSE_BETWEEN_REFRESH_SECONDS; 206 | this.pageLoadTimeoutSeconds = DefaultTimeouts.PAGE_LOAD_TIMEOUT_SECONDS; 207 | this.pageReadyTimeoutSeconds = DefaultTimeouts.PAGE_READY_TIMEOUT_SECONDS; 208 | this.implicitWaitTimeoutMillis = DefaultTimeouts.IMPLICIT_WAIT_TIMEOUT_MILLIS; 209 | } 210 | 211 | public TimeoutsConfig build() { 212 | return new TimeoutsConfig(clickTimeoutSeconds, 213 | webElementPresenceTimeoutSeconds, 214 | pollingWithRefreshTimeoutSeconds, 215 | pageRefreshTimeoutSeconds, 216 | shortTimeoutSeconds, 217 | mediumTimeoutSeconds, 218 | longTimeoutSeconds, 219 | pauseBetweenKeysMillis, 220 | pauseBetweenTriesMillis, 221 | pauseBetweenRefreshSeconds, 222 | pageLoadTimeoutSeconds, 223 | pageReadyTimeoutSeconds, 224 | implicitWaitTimeoutMillis); 225 | } 226 | 227 | /** 228 | * Set the timeout waiting for an element to be clickable, in seconds 229 | * @param clickTimeoutSeconds - time in seconds 230 | */ 231 | public Builder clickTimeoutSeconds(int clickTimeoutSeconds) { 232 | this.clickTimeoutSeconds = clickTimeoutSeconds; 233 | return this; 234 | } 235 | 236 | /** 237 | * Set the timeout waiting for a web element to be present on the DOM, in seconds. 238 | * @param webElementPresenceTimeoutSeconds - time in seconds 239 | */ 240 | public Builder webElementPresenceTimeoutSeconds(int webElementPresenceTimeoutSeconds) { 241 | this.webElementPresenceTimeoutSeconds = webElementPresenceTimeoutSeconds; 242 | return this; 243 | } 244 | 245 | /** 246 | * Set the timeout for long polling activities such as polling for an element to be present by 247 | * refreshing the page repeatedly. 248 | * 249 | * @param pollingWithRefreshTimeoutSeconds - time in seconds 250 | */ 251 | public Builder pollingWithRefreshTimeoutSeconds(int pollingWithRefreshTimeoutSeconds) { 252 | this.pollingWithRefreshTimeoutSeconds = pollingWithRefreshTimeoutSeconds; 253 | return this; 254 | } 255 | 256 | /** 257 | * Set the timeout for waiting for an element to become stale or for the page to be refreshed. 258 | * @param pageRefreshTimeoutSeconds - time in seconds 259 | */ 260 | public Builder pageRefreshTimeoutSeconds(int pageRefreshTimeoutSeconds) { 261 | this.pageRefreshTimeoutSeconds = pageRefreshTimeoutSeconds; 262 | return this; 263 | } 264 | 265 | /** 266 | * Set the "short" timeout in seconds. Arbitrary timeout configurable by client. 267 | * @param shortTimeoutSeconds - time in seconds 268 | */ 269 | public Builder shortTimeoutSeconds(int shortTimeoutSeconds) { 270 | this.shortTimeoutSeconds = shortTimeoutSeconds; 271 | return this; 272 | } 273 | 274 | /** 275 | * Set the "medium" timeout in seconds. Arbitrary timeout configurable by client. 276 | * @param mediumTimeoutSeconds 277 | */ 278 | public Builder mediumTimeoutSeconds(int mediumTimeoutSeconds) { 279 | this.mediumTimeoutSeconds = mediumTimeoutSeconds; 280 | return this; 281 | } 282 | 283 | /** 284 | * Set the "long" timeout in seconds. Arbitrary timeout configurable by client. 285 | * @param longTimeoutSeconds 286 | */ 287 | public Builder longTimeoutSeconds(int longTimeoutSeconds) { 288 | this.longTimeoutSeconds = longTimeoutSeconds; 289 | return this; 290 | } 291 | 292 | /** 293 | * Set the pause between sending keys when entering text slowly. 294 | * @param pauseBetweenKeysMillis - time in ms 295 | * @return - the Builder 296 | */ 297 | public Builder pauseBetweenKeysMillis(int pauseBetweenKeysMillis) { 298 | this.pauseBetweenKeysMillis = pauseBetweenKeysMillis; 299 | return this; 300 | } 301 | 302 | /** 303 | * Set the pause between tries in milliseconds. This is used for pausing between checks to see if an element 304 | * is visible. 305 | * @param pauseBetweenTriesMillis - time in ms 306 | * @return - the Builder 307 | */ 308 | public Builder pauseBetweenTriesMillis(int pauseBetweenTriesMillis) { 309 | this.pauseBetweenTriesMillis = pauseBetweenTriesMillis; 310 | return this; 311 | } 312 | 313 | /** 314 | * Set the pause between refreshing the page when polling for something to be present by refreshing the page 315 | * repeatedly. 316 | * @param pauseBetweenRefreshSeconds - time in seconds 317 | * @return - the Builder 318 | */ 319 | public Builder pauseBetweenRefreshSeconds(int pauseBetweenRefreshSeconds) { 320 | this.pauseBetweenRefreshSeconds = pauseBetweenRefreshSeconds; 321 | return this; 322 | } 323 | 324 | /** 325 | * Set the timeout waiting for a new page to load in the web browser. 326 | * This is both used by the framework and passed on to the Selenium WebDriver for its configuration. 327 | * @param pageLoadTimeoutSeconds - time in seconds 328 | * @return - the Builder 329 | */ 330 | public Builder pageLoadTimoutSeconds(int pageLoadTimeoutSeconds) { 331 | this.pageLoadTimeoutSeconds = pageLoadTimeoutSeconds; 332 | return this; 333 | } 334 | 335 | /** 336 | * Set the timeout waiting for a new page to have elements on the page that are checked after the page loads. 337 | * @see Page#getPageIdentifier() 338 | * This is both used by the framework and passed on to the Selenium WebDriver for its configuration. 339 | * @param pageReadyTimeoutSeconds - time in seconds 340 | * @return - the Builder 341 | */ 342 | public Builder pageReadyTimoutSeconds(int pageReadyTimeoutSeconds) { 343 | this.pageReadyTimeoutSeconds = pageReadyTimeoutSeconds; 344 | return this; 345 | } 346 | 347 | /** 348 | * Set the implicit wait timeout for checking if a web element is present. 349 | * This is used when configuring a Selenium WebDriver. 350 | * @param implicitWaitTimeoutMillis 351 | * @return - the Builder 352 | */ 353 | public Builder implicitWaitTimeoutMillis(int implicitWaitTimeoutMillis) { 354 | this.implicitWaitTimeoutMillis = implicitWaitTimeoutMillis; 355 | return this; 356 | } 357 | 358 | // Standard timeouts for most common usages, all in seconds 359 | private int clickTimeoutSeconds; 360 | private int webElementPresenceTimeoutSeconds; 361 | private int pollingWithRefreshTimeoutSeconds; 362 | private int pageRefreshTimeoutSeconds; 363 | 364 | // Arbitrary timeouts configured by client, all in seconds 365 | private int shortTimeoutSeconds; 366 | private int mediumTimeoutSeconds; 367 | private int longTimeoutSeconds; 368 | 369 | // Pauses when polling or entering keys 370 | private int pauseBetweenKeysMillis; 371 | private int pauseBetweenTriesMillis; 372 | private int pauseBetweenRefreshSeconds; 373 | 374 | // Timeouts used for configuring the underlying WebDriver 375 | private int pageLoadTimeoutSeconds; 376 | private int pageReadyTimeoutSeconds; 377 | private int implicitWaitTimeoutMillis; 378 | } 379 | } 380 | -------------------------------------------------------------------------------- /src/main/java/com/jivesoftware/selenium/pagefactory/framework/exception/InvalidPageUrlException.java: -------------------------------------------------------------------------------- 1 | package com.jivesoftware.selenium.pagefactory.framework.exception; 2 | 3 | public class InvalidPageUrlException extends RuntimeException { 4 | public InvalidPageUrlException(String msg) { 5 | super(msg); 6 | } 7 | 8 | public InvalidPageUrlException(String msg, Exception cause) { 9 | super(msg, cause); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/jivesoftware/selenium/pagefactory/framework/exception/JiveWebDriverException.java: -------------------------------------------------------------------------------- 1 | package com.jivesoftware.selenium.pagefactory.framework.exception; 2 | 3 | public class JiveWebDriverException extends Exception { 4 | public JiveWebDriverException(String msg) { 5 | super(msg); 6 | } 7 | 8 | public JiveWebDriverException(String msg, Exception e) { 9 | super(msg, e); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/jivesoftware/selenium/pagefactory/framework/exception/SeleniumActionsException.java: -------------------------------------------------------------------------------- 1 | package com.jivesoftware.selenium.pagefactory.framework.exception; 2 | 3 | public class SeleniumActionsException extends Exception { 4 | public SeleniumActionsException(String msg) { 5 | super(msg); 6 | } 7 | 8 | public SeleniumActionsException(String msg, Exception e) { 9 | super(msg, e); 10 | } 11 | } -------------------------------------------------------------------------------- /src/main/java/com/jivesoftware/selenium/pagefactory/framework/pages/BaseSubPage.java: -------------------------------------------------------------------------------- 1 | package com.jivesoftware.selenium.pagefactory.framework.pages; 2 | 3 | import com.jivesoftware.selenium.pagefactory.framework.actions.SeleniumActions; 4 | import com.jivesoftware.selenium.pagefactory.framework.config.TimeoutType; 5 | import org.openqa.selenium.By; 6 | import org.openqa.selenium.support.PageFactory; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import javax.annotation.Nullable; 11 | 12 | /** 13 | * Base class for a SubPage. Implements the default pageLoadHook that waits for the page identifier to be present. 14 | * Subclasses should call super.pageLoadHook() if they want to wait on the page identifier. 15 | */ 16 | public class BaseSubPage implements SubPage { 17 | @SuppressWarnings("unused") 18 | private static Logger logger = LoggerFactory.getLogger(BaseSubPage.class); 19 | private static final PageUtils PAGE_UTILS = new PageUtils(); 20 | protected S a; 21 | protected Page parent = null; 22 | 23 | public final void setParent(Page parent) { 24 | this.parent = parent; 25 | } 26 | 27 | public final Page getParent() { 28 | return this.parent; 29 | } 30 | 31 | public final boolean hasParent() { 32 | return this.parent != null; 33 | } 34 | 35 | public final S getActions() { 36 | return a; 37 | } 38 | 39 | public final void setActions(SeleniumActions actions) { 40 | this.a = (S) actions; 41 | } 42 | 43 | public void pageLoadHook() { 44 | PAGE_UTILS.defaultPageLoadHook(this, a, getPageReadyTimeout()); 45 | } 46 | 47 | public TimeoutType getPageReadyTimeout() { 48 | return TimeoutType.PAGE_READY_TIMEOUT; 49 | } 50 | 51 | @Nullable 52 | public By getPageIdentifier() { 53 | return null; 54 | } 55 | 56 | @Nullable 57 | public By getPageContainer() { 58 | return null; 59 | } 60 | 61 | public final void initSubPages() { 62 | PAGE_UTILS.initSubPages(this, a); 63 | } 64 | 65 | @Override 66 | public final void refreshElements() { 67 | PageFactory.initElements(getActions().getBrowser().getWebDriver(), this); 68 | initSubPages(); 69 | pageLoadHook(); 70 | } 71 | 72 | @Override 73 | public final void refreshPage() { 74 | getActions().getBrowser().refreshPage(); 75 | refreshElements(); 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/com/jivesoftware/selenium/pagefactory/framework/pages/BaseTopLevelPage.java: -------------------------------------------------------------------------------- 1 | package com.jivesoftware.selenium.pagefactory.framework.pages; 2 | 3 | import com.jivesoftware.selenium.pagefactory.framework.actions.SeleniumActions; 4 | import com.jivesoftware.selenium.pagefactory.framework.browser.web.WebBrowser; 5 | import com.jivesoftware.selenium.pagefactory.framework.config.TimeoutType; 6 | import com.jivesoftware.selenium.pagefactory.framework.exception.InvalidPageUrlException; 7 | import org.openqa.selenium.By; 8 | import org.openqa.selenium.support.PageFactory; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import javax.annotation.Nonnull; 13 | import java.net.URI; 14 | import java.util.Optional; 15 | import java.util.regex.Matcher; 16 | import java.util.regex.Pattern; 17 | 18 | /** 19 | * Base abstract class for a TopLevelPage. Implements the default pageLoadHook that waits for the page identifier to be present. 20 | * Subclasses should call super.pageLoadHook() if they want to wait on the page identifier. 21 | */ 22 | public class BaseTopLevelPage implements TopLevelPage { 23 | @SuppressWarnings("unused") 24 | private static Logger logger = LoggerFactory.getLogger(BaseTopLevelPage.class); 25 | 26 | private static final PageUtils PAGE_UTILS = new PageUtils(); 27 | 28 | private long pageLoadTime; 29 | 30 | protected S a; 31 | 32 | public final S getActions() { 33 | return a; 34 | } 35 | 36 | public final void setActions(SeleniumActions actions) { 37 | this.a = (S) actions; 38 | } 39 | 40 | @Nonnull 41 | @Override 42 | public String getWebPagePath() { 43 | Optional optionalPathFromAnnotation = PAGE_UTILS.getWebPagePathForClass(getClass()); 44 | if (optionalPathFromAnnotation.isPresent()) { 45 | return optionalPathFromAnnotation.get(); 46 | } 47 | return "/"; 48 | } 49 | 50 | @Override 51 | public void pageLoadHook() { 52 | if (pageLoadTime == 0) { 53 | pageLoadTime = System.currentTimeMillis(); 54 | } 55 | 56 | // First do the default load hook, which verifies an element is present 57 | PAGE_UTILS.defaultPageLoadHook(this, a, getPageReadyTimeout()); 58 | 59 | // Next, verify that the current URL matches the value annotated with @WebPagePath 60 | verifyCurrentURL(); 61 | } 62 | 63 | @Override 64 | public TimeoutType getPageReadyTimeout() { 65 | return TimeoutType.PAGE_READY_TIMEOUT; 66 | } 67 | 68 | /** 69 | * Verify that the current URL the browser is pointing to matches the path given by the @WebPagePath annotation. 70 | * 71 | * For example, if the current URL is "http://example.com/foo/bar" and we specify @WebPagePath(path = "/bar") then this would be a match, because we only 72 | * require the URL to end with the given path. (Since we can't know the root context of the server). 73 | * 74 | * If the current URL is "http:/example.com/foo/1234" and we specify @WebPagePath(isRegex = true, path = "/foo/\\d+"), then this would match as a regex. 75 | */ 76 | public void verifyCurrentURL() { 77 | WebPagePath webPagePath = getClass().getAnnotation(WebPagePath.class); 78 | 79 | // If the @WebPagePath annotation isn't present, or browser isn't a WebBrowser, then return. 80 | if (webPagePath == null || !(a.getBrowser() instanceof WebBrowser)) { 81 | return; 82 | } 83 | 84 | String expectedPath = webPagePath.path(); 85 | boolean regex = webPagePath.isRegex(); 86 | 87 | String currentURL = a.getCurrentURL(); 88 | 89 | // Not sure when a WebDriver returns null for current URL, but just don't validate in this case 90 | if (currentURL == null) { 91 | return; 92 | } 93 | 94 | URI currentURI = URI.create(currentURL); 95 | String currentPath = currentURI.getPath(); 96 | 97 | // Remove trailing slashes 98 | if (currentPath.endsWith("/")) { 99 | currentPath = currentPath.substring(0, currentPath.length() - 1); 100 | } 101 | if (expectedPath.endsWith("/")) { 102 | expectedPath = expectedPath.substring(0, expectedPath.length() - 1); 103 | } 104 | 105 | if (regex) { 106 | Pattern pattern = Pattern.compile(expectedPath); 107 | Matcher m = pattern.matcher(currentPath); 108 | if (!m.find() || m.regionEnd() != currentPath.length()) { 109 | throw new InvalidPageUrlException(String.format("The current path of the web browser is %s, but expected the path to end with an expression " + 110 | "matching the regex '%s'", 111 | currentPath, expectedPath)); 112 | } 113 | 114 | logger.info("SUCCESS - the current path {} matches the regex '{}'", currentPath, expectedPath); 115 | 116 | } else { 117 | // The current path should end with the expected path --- we don't know what the Root context of the server is. 118 | if (!currentPath.endsWith(expectedPath)) { 119 | throw new InvalidPageUrlException(String.format("The current path of the web browser is %s, but expected the path to end with '%s'", 120 | currentPath, expectedPath)); 121 | } 122 | 123 | logger.info("SUCCESS - the current path {} matches the required path '{}'", currentPath, expectedPath); 124 | } 125 | } 126 | 127 | 128 | @Override 129 | public By getPageIdentifier() { 130 | return null; 131 | } 132 | 133 | public final void initSubPages() { 134 | PAGE_UTILS.initSubPages(this, a); 135 | } 136 | 137 | @Override 138 | public final void refreshElements() { 139 | PageFactory.initElements(getActions().getBrowser().getWebDriver(), this); 140 | initSubPages(); 141 | pageLoadHook(); 142 | } 143 | 144 | @Override 145 | public void refreshPage() { 146 | getActions().getBrowser().refreshPage(); 147 | refreshElements(); 148 | } 149 | 150 | @Override 151 | public void leavePageHook() { 152 | 153 | } 154 | 155 | @Override 156 | public long getPageLoadTime() { 157 | return pageLoadTime; 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/main/java/com/jivesoftware/selenium/pagefactory/framework/pages/Page.java: -------------------------------------------------------------------------------- 1 | package com.jivesoftware.selenium.pagefactory.framework.pages; 2 | 3 | import com.jivesoftware.selenium.pagefactory.framework.actions.SeleniumActions; 4 | import com.jivesoftware.selenium.pagefactory.framework.config.TimeoutType; 5 | import org.openqa.selenium.By; 6 | 7 | import javax.annotation.Nullable; 8 | 9 | public interface Page{ 10 | 11 | /** 12 | * Get the SeleniumActions for the current page and browser, for how to interact with the page using Selenium. 13 | */ 14 | SeleniumActions getActions(); 15 | 16 | /** 17 | * Set the selenium actions. 18 | */ 19 | void setActions(SeleniumActions actions); 20 | 21 | /** 22 | * Override this to have a page do something different when a page is loaded. 23 | * Make sure to call super.pageLoadHook(), since the BasicPage class defines the check that the page identifier is present. 24 | */ 25 | void pageLoadHook(); 26 | 27 | TimeoutType getPageReadyTimeout(); 28 | 29 | /** 30 | * A Selenium Locator that uniquely identifies a page as being successfully loaded. 31 | * Return null to not verify any element is present on page load. 32 | */ 33 | @Nullable 34 | By getPageIdentifier(); 35 | 36 | /** 37 | * How to initialize sub-page fields defined with the @SubPage annotation 38 | */ 39 | void initSubPages(); 40 | 41 | /** 42 | * Call this if a Page has been refreshed by javascript, so it has stale WebElement member variables. 43 | * This is useful when a method of a Page class performs actions requiring multiple refreshes. 44 | * In other situations, you can just use Browser#refreshPage() to get an entirely new Page instance. 45 | */ 46 | void refreshElements(); 47 | 48 | /** 49 | * Call this to do a full page refresh with the browser, and refresh the web elements as well. 50 | */ 51 | void refreshPage(); 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/jivesoftware/selenium/pagefactory/framework/pages/PageUtils.java: -------------------------------------------------------------------------------- 1 | package com.jivesoftware.selenium.pagefactory.framework.pages; 2 | 3 | import com.google.common.base.Preconditions; 4 | import com.google.common.collect.Lists; 5 | import com.jivesoftware.selenium.pagefactory.framework.actions.SeleniumActions; 6 | import com.jivesoftware.selenium.pagefactory.framework.config.TimeoutType; 7 | import org.openqa.selenium.By; 8 | import org.openqa.selenium.WebDriver; 9 | import org.openqa.selenium.support.PageFactory; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import java.lang.reflect.Field; 14 | import java.net.URI; 15 | import java.util.List; 16 | import java.util.Optional; 17 | 18 | /** 19 | * Created by charles.capps on 7/29/14. 20 | * 21 | * Helpers for interacting with Pages and TopLevelPages 22 | */ 23 | public class PageUtils { 24 | private static final Logger logger = LoggerFactory.getLogger(PageUtils.class); 25 | 26 | /** 27 | * Return an Optional<String> representing the path to a web page for a TopLevelPage class. 28 | * The path is extracted using reflection from the {@link WebPagePath} annotation, if present. 29 | * 30 | * @param pageClass 31 | * @return - the Optional<String> that is present with a value if the given Page class was annotated with 32 | * {@link com.jivesoftware.selenium.pagefactory.framework.pages.WebPagePath} 33 | */ 34 | public Optional getWebPagePathForClass(Class pageClass) { 35 | WebPagePath annotation = pageClass.getAnnotation(WebPagePath.class); 36 | if (annotation == null) { 37 | return Optional.empty(); 38 | } 39 | return Optional.ofNullable(annotation.path()); 40 | } 41 | 42 | /** 43 | * Default implementation of pageLoadHook(). 44 | * 45 | * Just verify the page identifier Locator is present on the DOM. 46 | * 47 | * @param page 48 | * @param a 49 | */ 50 | public void defaultPageLoadHook(Page page, SeleniumActions a) { 51 | By pageIdentifier = page.getPageIdentifier(); 52 | if (pageIdentifier != null) { 53 | a.verifyElementPresented(pageIdentifier, TimeoutType.PAGE_LOAD_TIMEOUT); 54 | } 55 | } 56 | 57 | /** 58 | * Overloaded default implementation of pageLoadHook(). 59 | * 60 | * Just verify the page identifier Locator is present on the DOM. 61 | * 62 | * @param page 63 | * @param a 64 | * @param timeout 65 | */ 66 | public void defaultPageLoadHook(Page page, SeleniumActions a, TimeoutType timeout) { 67 | By pageIdentifier = page.getPageIdentifier(); 68 | if (pageIdentifier != null) { 69 | a.verifyElementPresented(pageIdentifier, timeout); 70 | } 71 | } 72 | 73 | /** 74 | * Use reflection to recursively get all fields annotated with {@link SubPageField} on a given class. 75 | * @param type 76 | * @return - List of Fields that are annotated with {@link com.jivesoftware.selenium.pagefactory.framework.pages.SubPageField} 77 | * and are of type {@link com.jivesoftware.selenium.pagefactory.framework.pages.SubPage}, 78 | * recursively including fields from super classes. 79 | */ 80 | public static List getAllSubpageFields(Class type) { 81 | List subpageFields = Lists.newArrayList(); 82 | for (Field field : type.getDeclaredFields()) { 83 | if (field.getAnnotation(SubPageField.class) != null) { 84 | if (SubPage.class.isAssignableFrom(field.getType())) { 85 | subpageFields.add(field); 86 | } else { 87 | logger.warn("Class {} has a field annotated with @SubPageField that isn't a SubPage type", type.getSimpleName()); 88 | } 89 | } 90 | } 91 | 92 | if (type.getSuperclass() != null) { 93 | subpageFields.addAll(getAllSubpageFields(type.getSuperclass())); 94 | } 95 | 96 | return subpageFields; 97 | } 98 | 99 | public void initSubPages(Page page, SeleniumActions a) { 100 | Preconditions.checkNotNull(a); 101 | Preconditions.checkNotNull(page); 102 | List fields = getAllSubpageFields(page.getClass()); 103 | for (Field field : fields) { 104 | Class type = field.getType(); 105 | if (!SubPage.class.isAssignableFrom(type)) { 106 | logger.warn("Invalid @SubPageField in class '%s'. Must be a subclass of SubPage."); 107 | continue; 108 | } 109 | 110 | SubPage subPage = PageFactory.initElements(a.getBrowser().getWebDriver(), (Class) field.getType()); 111 | subPage.setActions(a); 112 | subPage.setParent(page); 113 | subPage.pageLoadHook(); 114 | subPage.initSubPages(); 115 | 116 | //Set the subpage field 117 | try { 118 | field.setAccessible(true); 119 | field.set(page, subPage); 120 | } catch (IllegalAccessException ex) { 121 | logger.error("Error instantiating SubPage field: " + field, ex); 122 | throw new RuntimeException(ex); 123 | } 124 | } 125 | } 126 | 127 | public void initSubPagesWithoutPageLoadHooks(Page page, SeleniumActions a) { 128 | Preconditions.checkNotNull(a); 129 | Preconditions.checkNotNull(page); 130 | List fields = getAllSubpageFields(page.getClass()); 131 | for (Field field : fields) { 132 | Class type = field.getType(); 133 | if (!SubPage.class.isAssignableFrom(type)) { 134 | logger.warn("Invalid @SubPageField in class '%s'. Must be a subclass of SubPage."); 135 | continue; 136 | } 137 | 138 | SubPage subPage = PageFactory.initElements(a.getBrowser().getWebDriver(), (Class) field.getType()); 139 | subPage.setActions(a); 140 | subPage.setParent(page); 141 | initSubPagesWithoutPageLoadHooks(subPage, a); 142 | 143 | //Set the subpage field 144 | try { 145 | field.setAccessible(true); 146 | field.set(page, subPage); 147 | } catch (IllegalAccessException ex) { 148 | logger.error("Error instantiating SubPage field: " + field, ex); 149 | throw new RuntimeException(ex); 150 | } 151 | } 152 | } 153 | 154 | public void runPageLoadHooksForSubPages(Page page, SeleniumActions a) { 155 | Preconditions.checkNotNull(a); 156 | Preconditions.checkNotNull(page); 157 | List fields = getAllSubpageFields(page.getClass()); 158 | for (Field field : fields) { 159 | Class type = field.getType(); 160 | if (!SubPage.class.isAssignableFrom(type)) { 161 | logger.warn("Invalid @SubPageField in class '%s'. Must be a subclass of SubPage."); 162 | continue; 163 | } 164 | 165 | //Get the subpage field 166 | SubPage subPage; 167 | try { 168 | field.setAccessible(true); 169 | subPage = (SubPage) field.get(page); 170 | } catch (IllegalAccessException ex) { 171 | logger.error("Error instantiating SubPage field: " + field, ex); 172 | throw new RuntimeException(ex); 173 | } 174 | if (subPage != null) { 175 | subPage.pageLoadHook(); 176 | runPageLoadHooksForSubPages(subPage, a); 177 | } 178 | } 179 | } 180 | 181 | public T loadPageFromURL(URI absoluteURL, Class pageClass, WebDriver driver, SeleniumActions actions) { 182 | Preconditions.checkNotNull(absoluteURL, "Error: URI provided cannot be null in PageUtils#loadPageFromURL"); 183 | Preconditions.checkNotNull(pageClass); 184 | Preconditions.checkNotNull(driver); 185 | Preconditions.checkNotNull(actions); 186 | Preconditions.checkArgument(absoluteURL.isAbsolute(), "Error: must provide an absolute URL to PageUtils#loadPageFromURL"); 187 | driver.get(absoluteURL.toString()); 188 | return loadCurrentPage(pageClass, driver, actions); 189 | } 190 | 191 | public T loadCurrentPage(Class pageClass, WebDriver driver, SeleniumActions actions) { 192 | T page = PageFactory.initElements(driver, pageClass); 193 | page.setActions(actions); 194 | page.initSubPages(); 195 | page.pageLoadHook(); 196 | return page; 197 | } 198 | 199 | public T loadCurrentPageWithoutPageLoadHook(Class pageClass, WebDriver driver, SeleniumActions actions) { 200 | T page = PageFactory.initElements(driver, pageClass); 201 | page.setActions(actions); 202 | initSubPagesWithoutPageLoadHooks(page, actions); 203 | return page; 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /src/main/java/com/jivesoftware/selenium/pagefactory/framework/pages/SubPage.java: -------------------------------------------------------------------------------- 1 | package com.jivesoftware.selenium.pagefactory.framework.pages; 2 | 3 | import org.openqa.selenium.By; 4 | 5 | /** 6 | * Created by charles.capps on 7/29/14. 7 | */ 8 | public interface SubPage extends Page { 9 | /** 10 | * Set parent page for the subpages. 11 | */ 12 | void setParent(Page parent); 13 | 14 | /** 15 | * get parent page from the subpages. 16 | */ 17 | Page getParent(); 18 | 19 | boolean hasParent(); 20 | 21 | /** 22 | * A Selenium locator that returns page container locator; useful for nav bars, side bars, 23 | * inbox message items. 24 | */ 25 | By getPageContainer(); 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/jivesoftware/selenium/pagefactory/framework/pages/SubPageField.java: -------------------------------------------------------------------------------- 1 | package com.jivesoftware.selenium.pagefactory.framework.pages; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Marker interface indicating that a field that extends {@link SubPage} 10 | * should be loaded when the page is initialized. 11 | */ 12 | @Retention(RetentionPolicy.RUNTIME) 13 | @Target(ElementType.FIELD) 14 | public @interface SubPageField { 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/jivesoftware/selenium/pagefactory/framework/pages/TopLevelPage.java: -------------------------------------------------------------------------------- 1 | package com.jivesoftware.selenium.pagefactory.framework.pages; 2 | 3 | import javax.annotation.Nonnull; 4 | 5 | /** 6 | * Represents a Page that is a top-level web page. 7 | */ 8 | public interface TopLevelPage extends Page { 9 | 10 | @Nonnull 11 | String getWebPagePath(); 12 | 13 | void leavePageHook(); 14 | 15 | long getPageLoadTime(); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/jivesoftware/selenium/pagefactory/framework/pages/WebPagePath.java: -------------------------------------------------------------------------------- 1 | package com.jivesoftware.selenium.pagefactory.framework.pages; 2 | 3 | import javax.annotation.Nonnull; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | /** 10 | * Created by charles.capps on 7/29/14. 11 | * 12 | * An annotation on a TopLevelPage class indicating the path part of the URI to the web page resource. 13 | * 14 | * e.g. "/social-business-software/social-community-software/" would be the path for 15 | * http://www.jivesoftware.com/social-business-software/social-community-software/ 16 | */ 17 | @Retention(RetentionPolicy.RUNTIME) 18 | @Target(ElementType.TYPE) 19 | public @interface WebPagePath { 20 | 21 | @Nonnull String path(); 22 | 23 | boolean isRegex() default false; 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/jivesoftware/selenium/pagefactory/framework/webservice/EndpointBuilder.java: -------------------------------------------------------------------------------- 1 | package com.jivesoftware.selenium.pagefactory.framework.webservice; 2 | 3 | public class EndpointBuilder { 4 | public static String uri(String host, String root, String relativePath) { 5 | String absolutePath = buildAbsolutePath(root, relativePath); 6 | return buildUri(host, absolutePath); 7 | } 8 | 9 | private static String buildUri(String host, String absolutePath) { 10 | while (host.endsWith("/") && host.length() > 1) { 11 | host = host.substring(0, host.length() - 1); 12 | } 13 | return host + absolutePath; 14 | } 15 | 16 | private static String buildAbsolutePath(String root, String relativePath) { 17 | String separator = root.endsWith("/") ? "" : "/"; 18 | return root + separator + relativePath; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/resources/log4j.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/test/java/com/jivesoftware/selenium/pagefactory/framework/CreateWebBrowserTests.java: -------------------------------------------------------------------------------- 1 | package com.jivesoftware.selenium.pagefactory.framework; 2 | 3 | import com.jivesoftware.selenium.pagefactory.framework.browser.LocalBrowserBuilder; 4 | import com.jivesoftware.selenium.pagefactory.framework.browser.web.WebBrowser; 5 | import com.jivesoftware.selenium.pagefactory.framework.browser.web.WebBrowserType; 6 | import com.jivesoftware.selenium.pagefactory.framework.config.TimeoutType; 7 | import com.jivesoftware.selenium.pagefactory.framework.exception.JiveWebDriverException; 8 | import org.openqa.selenium.By; 9 | import org.testng.annotations.Test; 10 | 11 | /** 12 | * Created by charles.capps on 8/21/14. 13 | * 14 | * Basic Functional tests for creating different web browsers. 15 | */ 16 | public class CreateWebBrowserTests { 17 | 18 | @Test 19 | public void openGoogleChrome() throws Exception { 20 | WebBrowser chromeBrowser = createMinimalChrome(); 21 | chromeBrowser.openPageByURL("http://google.com"); 22 | 23 | chromeBrowser.getActions().verifyElementVisible( 24 | By.cssSelector("form[action='/search']"), // The google search form 25 | TimeoutType.DEFAULT); 26 | 27 | chromeBrowser.quit(); 28 | } 29 | 30 | 31 | private WebBrowser createMinimalChrome() throws JiveWebDriverException { 32 | return LocalBrowserBuilder.getBuilder(WebBrowserType.CHROME, "http://google.com") 33 | .withWebDriverPath(TestSystemProps.WEB_DRIVER_PATH) 34 | .build(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/test/java/com/jivesoftware/selenium/pagefactory/framework/TestSystemProps.java: -------------------------------------------------------------------------------- 1 | package com.jivesoftware.selenium.pagefactory.framework; 2 | 3 | /** 4 | * Created by charles.capps on 8/21/14. 5 | */ 6 | public class TestSystemProps { 7 | public static final String WEB_DRIVER_PATH = System.getProperty("tests.webDriverPath"); 8 | } 9 | -------------------------------------------------------------------------------- /src/test/resources/log4j.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /tools/ci/DEFS: -------------------------------------------------------------------------------- 1 | WORKSPACE=${WORKSPACE:-`pwd`} 2 | GIT=git 3 | if [ -f "/opt/tools/maven/apache-maven-3.0.4/bin/mvn" ]; then 4 | MVN="/opt/tools/maven/apache-maven-3.0.4/bin/mvn -U --batch-mode --define maven.repo.local=$WORKSPACE/.repository" 5 | else 6 | MVN="mvn" 7 | fi 8 | 9 | echo "MVN=$MVN" 10 | 11 | PYTHON=/usr/bin/python2.6 12 | if [ -d "/opt/tools/java/jdk1.8.0_31" ]; then 13 | export JAVA_HOME="/opt/tools/java/jdk1.8.0_31" 14 | fi 15 | 16 | if [ ! -f $PYTHON ]; 17 | then 18 | PYTHON=python 19 | 20 | # if 'python2.6' dones't exist, make sure python is 2.6 or later 21 | PYTHON_OK=`$PYTHON -c 'import sys; print (sys.version_info >= (2, 6) and "1" or "0")'` 22 | if [ "$PYTHON_OK" = '0' ]; then 23 | echo "Python version too old" 24 | exit 1 25 | fi 26 | fi -------------------------------------------------------------------------------- /tools/ci/commit/commit: -------------------------------------------------------------------------------- 1 | . tools/ci/DEFS 2 | 3 | # First deploy the SNAPSHOT version 4 | $MVN deploy -Prelease -DsnapshotRepoId=${SNAPSHOT_REPO_ID} -DsnapshotRepoUrl=${SNAPSHOT_REPO_URL} -DreleaseRepoId=${RELEASE_REPO_ID} -DreleaseRepoUrl=${RELEASE_REPO_URL} -DskipTests 5 | 6 | # Next deploy the RELEASE version 7 | # 2.0.4-6-g012333 : version number 8 | version=$(./tools/ci/commit/j-version) 9 | echo "Deploying version: $version" 10 | 11 | $MVN versions:set -DnewVersion=$version 12 | $MVN deploy -Prelease -DsnapshotRepoId=${SNAPSHOT_REPO_ID} -DsnapshotRepoUrl=${SNAPSHOT_REPO_URL} -DreleaseRepoId=${RELEASE_REPO_ID} -DreleaseRepoUrl=${RELEASE_REPO_URL} -DskipTests 13 | -------------------------------------------------------------------------------- /tools/ci/commit/j-version: -------------------------------------------------------------------------------- 1 | #version based on commit count since last tag 2 | last_tag=$(git rev-list --tags --max-count=1) 3 | base_version=$(git describe --tags $last_tag) 4 | 5 | artifactPattern="jive-selenium-pages-framework<\/artifactId>" 6 | 7 | # First get the 2 lines surrounding the main tag 8 | # Next get the line with the 9 | # Next use sed to get everything before "SNAPSHOT" 10 | # Example result: 8.0.0.0-8c6 11 | pom_version=$(grep -2 ${artifactPattern} pom.xml | \ 12 | grep -Eo "[^<>\s]*-SNAPSHOT<\/version>" | \ 13 | sed -E "s/([^<>\s]*)-SNAPSHOT<\/version>/\1/") 14 | 15 | last_commitish=$(git rev-list --max-count=1 $base_version --) 16 | commit_count=$(git rev-list $last_commitish..HEAD | wc -w) 17 | head_short_sha=$(git rev-parse --short HEAD) 18 | 19 | printf "%s-%s-g%s\n" $pom_version $commit_count $head_short_sha 20 | --------------------------------------------------------------------------------